From 6a2f8c03283b7d4900734ec5b384a73670eefddf Mon Sep 17 00:00:00 2001 From: juror #9 <64419198+Jurorno9@users.noreply.github.com> Date: Sat, 25 Jan 2025 09:44:45 -0500 Subject: [PATCH 1/9] menu revamp - core --- build/scripts/commands.js | 8 +- build/scripts/menus.js | 145 +++++++++++++++++++++++++++----- build/scripts/playerCommands.js | 22 ++--- build/scripts/players.js | 13 ++- build/scripts/staffCommands.js | 99 +++++++++++----------- src/commands.ts | 8 +- src/menus.ts | 137 ++++++++++++++++++++++++------ src/playerCommands.ts | 26 +++--- src/players.ts | 15 ++-- src/staffCommands.ts | 85 ++++++++++--------- 10 files changed, 370 insertions(+), 188 deletions(-) diff --git a/build/scripts/commands.js b/build/scripts/commands.js index 187fb01..5415f89 100644 --- a/build/scripts/commands.js +++ b/build/scripts/commands.js @@ -805,11 +805,11 @@ function resolveArgsRecursive(processedArgs, unresolvedArgs, sender, callback) { break; default: (0, funcs_4.crash)("Unable to resolve arg of type ".concat(argToResolve_1.type)); } - (0, menus_1.menu)("Select a player", "Select a player for the argument \"".concat(argToResolve_1.name, "\""), optionsList_1, sender, function (_a) { - var option = _a.option; - processedArgs[argToResolve_1.name] = players_1.FishPlayer.get(option); + (0, menus_1.menu)("Select a player", "Select a player for the argument \"".concat(argToResolve_1.name, "\""), [new menus_1.GUI_Container(optionsList_1, "auto", function (player) { return Strings.stripColors(player.name).length >= 3 ? Strings.stripColors(player.name) : (0, funcs_3.escapeStringColorsClient)(player.name); }), new menus_1.GUI_Cancel()], sender, function (_a) { + var data = _a.data; + processedArgs[argToResolve_1.name] = players_1.FishPlayer.get(data); resolveArgsRecursive(processedArgs, unresolvedArgs, sender, callback); - }, true, function (player) { return Strings.stripColors(player.name).length >= 3 ? Strings.stripColors(player.name) : (0, funcs_3.escapeStringColorsClient)(player.name); }); + }); } } function initialize() { diff --git a/build/scripts/menus.js b/build/scripts/menus.js index d96768a..22b83b4 100644 --- a/build/scripts/menus.js +++ b/build/scripts/menus.js @@ -30,8 +30,17 @@ var __read = (this && this.__read) || function (o, n) { } return ar; }; +var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || Array.prototype.slice.call(from)); +}; Object.defineProperty(exports, "__esModule", { value: true }); -exports.listeners = void 0; +exports.listeners = exports.GUI_Confirm = exports.GUI_Page = exports.GUI_Cancel = exports.GUI_Container = void 0; exports.registerListeners = registerListeners; exports.menu = menu; var commands_1 = require("./commands"); @@ -39,6 +48,7 @@ var players_1 = require("./players"); var utils_1 = require("./utils"); var funcs_1 = require("./funcs"); var funcs_2 = require("./funcs"); +//#region Draw Menu /** Stores a mapping from name to the numeric id of a listener that has been registered. */ var registeredListeners = {}; exports.listeners = registeredListeners; @@ -79,39 +89,49 @@ function registerListeners() { } } //this is a minor abomination but theres no good way to do overloads in typescript -function menu(title, description, options, target, callback, includeCancel, optionStringifier, //this is dubious -columns) { - if (includeCancel === void 0) { includeCancel = true; } - if (optionStringifier === void 0) { optionStringifier = function (t) { return t; }; } - if (columns === void 0) { columns = 3; } +function menu(title, description, elements, target, callback) { + target.activeMenu.cancelOptionId = -1; + var ArrangedElements = { data: [], stringified: [] }; + elements.forEach(function (element) { + var _a; + (_a = ArrangedElements.data).push.apply(_a, __spreadArray([], __read(element.data()), false)); + if (element instanceof GUI_Cancel) { + target.activeMenu.cancelOptionId = ArrangedElements.data.length; + } + }); + elements.forEach(function (element) { + var _a; + return (_a = ArrangedElements.stringified).push.apply(_a, __spreadArray([], __read(element.format()), false)); + }); + //flatten to arrays + var PackedElements = { data: ArrangedElements.data.flat(), stringified: ArrangedElements.stringified.flat() }; + if (PackedElements.data.length == 0) { + ArrangedElements.stringified.push([""]); + ArrangedElements.data.push([null]); // not needed, but nice to keep data and string in sync. + } if (!callback) { //overload 1, just display a menu with no callback - Call.menu(target.con, registeredListeners.none, title, description, options.length == 0 ? [[""]] : (0, funcs_2.to2DArray)(options.map(optionStringifier), columns)); + Call.menu(target.con, registeredListeners.none, title, description, ArrangedElements.stringified); } else { //overload 2, display a menu with callback - //Set up the 2D array of options, and add cancel - //Use "" as a fallback, because Call.menu with an empty array of options causes a client crash - var arrangedOptions = (options.length == 0 && !includeCancel) ? [[""]] : (0, funcs_2.to2DArray)(options.map(optionStringifier), columns); - if (includeCancel) { - arrangedOptions.push(["Cancel"]); - target.activeMenu.cancelOptionId = options.length; - } - else { - target.activeMenu.cancelOptionId = -1; - } //The target fishPlayer has a property called activeMenu, which stores information about the last menu triggered. - target.activeMenu.callback = function (fishSender, option) { + target.activeMenu.callback = function (_fishSender, option) { //Additional permission validation could be done here, but the only way that callback() can be called is if the above statement executed, //and on sensitive menus such as the stop menu, the only way to reach that is if menu() was called by the /stop command, //which already checks permissions. //Additionally, the callback is cleared by the generic menu listener after it is executed. //We do need to validate option though, as it can be any number. - if (!(option in options)) + Log.info("Option ".concat(option, " in ").concat(PackedElements.data.length)); + if (!(option in PackedElements.data)) return; + if (typeof PackedElements.data[option] === 'string' && PackedElements.data[option] == "cancel") { + return; + } // cancel button pressed, no need to callback try { callback({ - option: options[option], + data: PackedElements.data[option], + text: PackedElements.stringified[option], sender: target, outputFail: function (message) { return (0, utils_1.outputFail)(message, target); }, outputSuccess: function (message) { return (0, utils_1.outputSuccess)(message, target); }, @@ -131,6 +151,89 @@ columns) { } } }; - Call.menu(target.con, registeredListeners.generic, title, description, arrangedOptions); + Call.menu(target.con, registeredListeners.generic, title, description, ArrangedElements.stringified); } } +//#endregion +//#region Draw Page Menus +//draws a page menu with arbitrary pages +function pageMenu(title, description, elements, target, callback) { + var pages = elements.length; + function drawpage(index) { + var e = [new GUI_Page(index + 1, pages)]; + e.push.apply(e, __spreadArray([], __read(elements[index]), false)); + menu(title, description, e, target, function (res) { + if (typeof res.data === 'string') { + switch (res.data) { + case "left": + drawpage((index == 0) ? (0) : (index - 1)); + break; + case "right": + drawpage((index == pages - 1) ? (pages - 1) : (index + 1)); + break; + case "center": + drawpage(index); + break; + default: + callback(res); + } + } + }); + return; + } +} +//auto formats a array into a page menu +//TODO make list a GUI_Element[] instead of a single Container +function listMenu(title, description, list, target, callback, pageSize) { + if (pageSize === void 0) { pageSize = 10; } + var buttons = { data: [], }; + list.data()[0].reduce(function (result, _, index) { if (index % pageSize === 0) { + buttons.data.push(buttons.data.slice(index, index + pageSize)); + } return result; }); + var pages = []; + buttons.data.forEach(function (page) { pages.push([new GUI_Container(page, 1, list.stringifier)]); }); //wrap each page in a container + pageMenu(title, description, pages, target, callback); +} +//const reservedStrings = ["left", "center", "right"] // strings used for paged menus, cannot be handled correct +var GUI_Container = /** @class */ (function () { + function GUI_Container(options, columns, stringifier) { + if (columns === void 0) { columns = 3; } + if (stringifier === void 0) { stringifier = function (option) { return option; }; } + var _this = this; + this.options = options; + this.columns = columns; + this.stringifier = stringifier; + this.format = function () { return ((0, funcs_2.to2DArray)(_this.options.map(_this.stringifier), (_this.columns == 'auto') ? (3) : (_this.columns))); }; + this.data = function () { return (0, funcs_2.to2DArray)(_this.options, (_this.columns == 'auto') ? (3) : (_this.columns)); }; + } + ; + return GUI_Container; +}()); +exports.GUI_Container = GUI_Container; +var GUI_Cancel = /** @class */ (function () { + function GUI_Cancel() { + this.format = function () { return ([["cancel"]]); }; + this.data = function () { return ([["cancel"]]); }; + } + return GUI_Cancel; +}()); +exports.GUI_Cancel = GUI_Cancel; +var GUI_Page = /** @class */ (function () { + function GUI_Page(currentPage, pages) { + var _this = this; + this.currentPage = currentPage; + this.pages = pages; + this.format = function () { return ([["<--"], ["".concat(_this.currentPage, "/").concat(_this.pages)], ["-->"]]); }; + this.data = function () { return ([["left", "center", "left"]]); }; + } + return GUI_Page; +}()); +exports.GUI_Page = GUI_Page; +var GUI_Confirm = /** @class */ (function () { + function GUI_Confirm() { + this.format = function () { return [["[green]Yes, do it", "[red] No, cancel"]]; }; + this.data = function () { return [[true, false]]; }; + } + return GUI_Confirm; +}()); +exports.GUI_Confirm = GUI_Confirm; diff --git a/build/scripts/playerCommands.js b/build/scripts/playerCommands.js index d6d4213..a68a2f0 100644 --- a/build/scripts/playerCommands.js +++ b/build/scripts/playerCommands.js @@ -565,11 +565,11 @@ exports.commands = (0, commands_1.commandList)(__assign(__assign({ about: { if (target.hasPerm("blockTrolling")) (0, commands_1.fail)(f(templateObject_9 || (templateObject_9 = __makeTemplateObject(["Player ", " is insufficiently trollable."], ["Player ", " is insufficiently trollable."])), args.player)); } - (0, menus_1.menu)("Rules for [#0000ff]>|||> FISH [white]servers", config_1.rules.join("\n\n"), ["[green]I agree to abide by these rules[]", "No"], target, function (_a) { - var option = _a.option; - if (option == "No") + (0, menus_1.menu)("Rules for [#0000ff]>|||> FISH [white]servers", config_1.rules.join("\n\n"), [new menus_1.GUI_Container(["[green]I agree to abide by these rules[]", "No"])], target, function (_a) { + var text = _a.text; + if (text == "No") target.kick("You must agree to the rules to play on this server. Rejoin to agree to the rules.", 1); - }, false); + }); if (target !== sender) outputSuccess(f(templateObject_10 || (templateObject_10 = __makeTemplateObject(["Reminded ", " of the rules."], ["Reminded ", " of the rules."])), target)); }, @@ -587,7 +587,7 @@ exports.commands = (0, commands_1.commandList)(__assign(__assign({ about: { (0, commands_1.fail)("You do not have permission to show popups to other players, please run /void with no arguments to send a chat message to everyone."); if (args.player !== sender && args.player.hasPerm("blockTrolling")) (0, commands_1.fail)("Target player is insufficiently trollable."); - (0, menus_1.menu)("\uf83f [scarlet]WARNING[] \uf83f", "[white]Don't break the Power Void (\uF83F), it's a trap!\nPower voids disable anything they are connected to.\nIf you break it, [scarlet]you will get attacked[] by enemy units.\nPlease stop attacking and [lime]build defenses[] first!", ["I understand"], args.player); + (0, menus_1.menu)("\uf83f [scarlet]WARNING[] \uf83f", "[white]Don't break the Power Void (\uF83F), it's a trap!\nPower voids disable anything they are connected to.\nIf you break it, [scarlet]you will get attacked[] by enemy units.\nPlease stop attacking and [lime]build defenses[] first!", [new menus_1.GUI_Container(["I understand"])], args.player); (0, utils_1.logAction)("showed void warning", sender, args.player); outputSuccess(f(templateObject_11 || (templateObject_11 = __makeTemplateObject(["Warned ", " about power voids with a popup message."], ["Warned ", " about power voids with a popup message."])), args.player)); } @@ -667,20 +667,20 @@ exports.commands = (0, commands_1.commandList)(__assign(__assign({ about: { handler: function (_a) { var sender = _a.sender, manager = _a.data.manager; if (!manager.session) { - (0, menus_1.menu)("Start a Next Wave Vote", "Select the amount of waves you would like to skip, or click \"Cancel\" to abort.", [1, 5, 10], sender, function (_a) { - var option = _a.option; + (0, menus_1.menu)("Start a Next Wave Vote", "Select the amount of waves you would like to skip, or click \"Cancel\" to abort.", [new menus_1.GUI_Container([1, 5, 10], "auto", function (n) { return "".concat(n, " waves"); }), new menus_1.GUI_Cancel()], sender, function (_a) { + var data = _a.data; if (manager.session) { //Someone else started a vote - if (manager.session.data != option) + if (manager.session.data != data) (0, commands_1.fail)("Someone else started a vote with a different number of waves to skip."); else - manager.vote(sender, sender.voteWeight(), option); + manager.vote(sender, sender.voteWeight(), data); } else { //this is still a race condition technically... shouldn't be that bad right? - manager.start(sender, sender.voteWeight(), option); + manager.start(sender, sender.voteWeight(), data); } - }, true, function (n) { return "".concat(n, " waves"); }); + }); } else { manager.vote(sender, sender.voteWeight(), null); diff --git a/build/scripts/players.js b/build/scripts/players.js index 9196d5a..05a0310 100644 --- a/build/scripts/players.js +++ b/build/scripts/players.js @@ -352,7 +352,7 @@ var FishPlayer = /** @class */ (function () { }); //I think this is a better spot for this if (fishPlayer.firstJoin()) - (0, menus_1.menu)("Rules for [#0000ff] >|||> FISH [white] servers [white]", config_1.rules.join("\n\n[white]") + "\nYou can view these rules again by running [cyan]/rules[].", ["[green]I understand and agree to these terms"], fishPlayer); + (0, menus_1.menu)("Rules for [#0000ff] >|||> FISH [white] servers [white]", config_1.rules.join("\n\n[white]") + "\nYou can view these rules again by running [cyan]/rules[].", [new menus_1.GUI_Container(["[green]I understand and agree to these terms"])], fishPlayer); } }; /** Must be run on PlayerJoinEvent. */ @@ -676,15 +676,12 @@ var FishPlayer = /** @class */ (function () { api.sendStaffMessage("Autoflagged player ".concat(_this.name, "[cyan] for suspected vpn!"), "AntiVPN"); FishPlayer.messageStaff("[yellow]WARNING:[scarlet] player [cyan]\"".concat(_this.name, "[cyan]\"[yellow] is new (").concat(info.timesJoined - 1, " joins) and using a vpn. They have been automatically stopped and muted. Unless there is an ongoing griefer raid, they are most likely innocent. Free them with /free.")); Log.warn("Player ".concat(_this.name, " (").concat(_this.uuid, ") was autoflagged.")); - (0, menus_1.menu)("[gold]Welcome to Fish Community!", "[gold]Hi there! You have been automatically [scarlet]stopped and muted[] because we've found something to be [pink]a bit sus[]. You can still talk to staff and request to be freed. ".concat(config_1.FColor.discord(templateObject_1 || (templateObject_1 = __makeTemplateObject(["Join our Discord"], ["Join our Discord"]))), " to request a staff member come online if none are on."), ["Close", "Discord"], _this, function (_a) { - var option = _a.option, sender = _a.sender; - if (option == "Discord") { + (0, menus_1.menu)("[gold]Welcome to Fish Community!", "[gold]Hi there! You have been automatically [scarlet]stopped and muted[] because we've found something to be [pink]a bit sus[]. You can still talk to staff and request to be freed. ".concat(config_1.FColor.discord(templateObject_1 || (templateObject_1 = __makeTemplateObject(["Join our Discord"], ["Join our Discord"]))), " to request a staff member come online if none are on."), [new menus_1.GUI_Container(["Close", "Discord"], 1, function (str) { return ((str == "Discord") ? (config_1.FColor.discord(str)) : (str)); })], _this, function (_a) { + var result = _a.data, sender = _a.sender; + if (result == "Discord") { Call.openURI(sender.con, config_1.text.discordURL); } - }, false, function (str) { return ({ - "Close": "Close", - "Discord": config_1.FColor.discord("Discord") - }[str]); }); + }); _this.sendMessage("[gold]Welcome to Fish Community!\n[gold]Hi there! You have been automatically [scarlet]stopped and muted[] because we've found something to be [pink]a bit sus[]. You can still talk to staff and request to be freed. ".concat(config_1.FColor.discord(templateObject_2 || (templateObject_2 = __makeTemplateObject(["Join our Discord"], ["Join our Discord"]))), " to request a staff member come online if none are on.")); } } diff --git a/build/scripts/staffCommands.js b/build/scripts/staffCommands.js index 2b307b7..fe19208 100644 --- a/build/scripts/staffCommands.js +++ b/build/scripts/staffCommands.js @@ -74,7 +74,7 @@ exports.commands = (0, commands_1.commandList)({ if (args.player.hasPerm("blockTrolling")) (0, commands_1.fail)("Player ".concat(args.player, " is insufficiently trollable.")); var message = (_b = args.message) !== null && _b !== void 0 ? _b : "You have been warned. I suggest you stop what you're doing"; - (0, menus_1.menu)('Warning', message, ["[green]Accept"], args.player); + (0, menus_1.menu)('Warning', message, [new menus_1.GUI_Container(["[green]Accept"])], args.player); (0, utils_1.logAction)('warned', sender, args.player, message); outputSuccess(f(templateObject_1 || (templateObject_1 = __makeTemplateObject(["Warned player ", " for \"", "\""], ["Warned player ", " for \"", "\""])), args.player, message)); } @@ -278,22 +278,22 @@ exports.commands = (0, commands_1.commandList)({ else { possiblePlayers = players_1.FishPlayer.recentLeaves.map(function (p) { return p.info(); }); } - (0, menus_1.menu)("Stop", "Choose a player to mark", possiblePlayers, sender, function (_a) { - var optionPlayer = _a.option, sender = _a.sender; + (0, menus_1.menu)("Stop", "Choose a player to mark", [new menus_1.GUI_Container(possiblePlayers, "auto", function (p) { return p.lastName; }), new menus_1.GUI_Cancel()], sender, function (_a) { + var optionPlayer = _a.data, sender = _a.sender; if (args.time == null) { - (0, menus_1.menu)("Stop", "Select stop time", ["2 days", "7 days", "30 days", "forever"], sender, function (_a) { - var optionTime = _a.option, sender = _a.sender; + (0, menus_1.menu)("Stop", "Select stop time", [new menus_1.GUI_Container(["2 days", "7 days", "30 days", "forever"])], sender, function (_a) { + var optionTime = _a.text; var time = optionTime == "2 days" ? 172800000 : optionTime == "7 days" ? 604800000 : optionTime == "30 days" ? 2592000000 : (globals_1.maxTime - Date.now() - 10000); stop(optionPlayer, time); - }, false); + }); } else { stop(optionPlayer, args.time); } - }, true, function (p) { return p.lastName; }); + }); } }, restart: { @@ -419,9 +419,9 @@ exports.commands = (0, commands_1.commandList)({ if ((data_1 = admins.getInfoOptional(uuid_1)) != null && data_1.admin) (0, commands_1.fail)("Cannot ban an admin."); var name = data_1 ? "".concat((0, funcs_2.escapeStringColorsClient)(data_1.lastName), " (").concat(uuid_1, "/").concat(data_1.lastIP, ")") : uuid_1; - (0, menus_1.menu)("Confirm", "Are you sure you want to ban ".concat(name, "?"), ["[red]Yes", "[green]Cancel"], sender, function (_a) { - var confirm = _a.option; - if (confirm != "[red]Yes") + (0, menus_1.menu)("Confirm", "Are you sure you want to ban ".concat(name, "?"), [new menus_1.GUI_Confirm()], sender, function (_a) { + var confirm = _a.data; + if (!confirm) (0, commands_1.fail)("Cancelled."); admins.banPlayerID(uuid_1); if (data_1) { @@ -440,15 +440,15 @@ exports.commands = (0, commands_1.commandList)({ outputSuccess(f(templateObject_28 || (templateObject_28 = __makeTemplateObject(["Banned player ", ". [yellow]Unable to determine IP.[]"], ["Banned player ", ". [yellow]Unable to determine IP.[]"])), uuid_1)); } (0, utils_1.updateBans)(function (player) { return "[scarlet]Player [yellow]".concat(player.name, "[scarlet] has been whacked by ").concat(sender.prefixedName, "."); }); - }, false); + }); return; } else if (args.uuid_or_ip && globals_2.ipPattern.test(args.uuid_or_ip)) { //Overload 2: ban by uuid var ip_1 = args.uuid_or_ip; - (0, menus_1.menu)("Confirm", "Are you sure you want to ban IP ".concat(ip_1, "?"), ["[red]Yes", "[green]Cancel"], sender, function (_a) { - var confirm = _a.option; - if (confirm != "[red]Yes") + (0, menus_1.menu)("Confirm", "Are you sure you want to ban IP ".concat(ip_1, "?"), [new menus_1.GUI_Confirm()], sender, function (_a) { + var confirm = _a.data; + if (!confirm) (0, commands_1.fail)("Cancelled."); api.ban({ ip: ip_1 }); var info = admins.findByIP(ip_1); @@ -464,26 +464,26 @@ exports.commands = (0, commands_1.commandList)({ outputSuccess(f(templateObject_30 || (templateObject_30 = __makeTemplateObject(["IP ", " has been banned. Ban was synced to other servers."], ["IP ", " has been banned. Ban was synced to other servers."])), ip_1)); } (0, utils_1.updateBans)(function (player) { return "[scarlet]Player [yellow]".concat(player.name, "[scarlet] has been whacked by ").concat(sender.prefixedName, "."); }); - }, false); + }); return; } //Overload 3: ban by menu - (0, menus_1.menu)("[scarlet]BAN[]", "Choose a player to ban.", (0, funcs_5.setToArray)(Groups.player), sender, function (_a) { - var option = _a.option; - if (option.admin) + (0, menus_1.menu)("[scarlet]BAN[]", "Choose a player to ban.", [new menus_1.GUI_Container((0, funcs_5.setToArray)(Groups.player), "auto", function (opt) { return opt.name; }), new menus_1.GUI_Cancel()], sender, function (_a) { + var target = _a.data; + if (target.admin) (0, commands_1.fail)("Cannot ban an admin."); - (0, menus_1.menu)("Confirm", "Are you sure you want to ban ".concat(option.name, "?"), ["[red]Yes", "[green]Cancel"], sender, function (_a) { - var confirm = _a.option; - if (confirm != "[red]Yes") + (0, menus_1.menu)("Confirm", "Are you sure you want to ban ".concat(target.name, "?"), [new menus_1.GUI_Confirm()], sender, function (_a) { + var confirm = _a.data; + if (!confirm) (0, commands_1.fail)("Cancelled."); - admins.banPlayerIP(option.ip()); //this also bans the UUID - api.ban({ ip: option.ip(), uuid: option.uuid() }); - Log.info("".concat(option.ip(), "/").concat(option.uuid(), " was banned.")); - (0, utils_1.logAction)("banned", sender, option.getInfo()); - outputSuccess(f(templateObject_31 || (templateObject_31 = __makeTemplateObject(["Banned player ", "."], ["Banned player ", "."])), option)); + admins.banPlayerIP(target.ip()); //this also bans the UUID + api.ban({ ip: target.ip(), uuid: target.uuid() }); + Log.info("".concat(target.ip(), "/").concat(target.uuid(), " was banned.")); + (0, utils_1.logAction)("banned", sender, target.getInfo()); + outputSuccess(f(templateObject_31 || (templateObject_31 = __makeTemplateObject(["Banned player ", "."], ["Banned player ", "."])), target)); (0, utils_1.updateBans)(function (player) { return "[scarlet]Player [yellow]".concat(player.name, "[scarlet] has been whacked by ").concat(sender.prefixedName, "."); }); - }, false); - }, true, function (opt) { return opt.name; }); + }); + }); } }, kill: { @@ -510,9 +510,9 @@ exports.commands = (0, commands_1.commandList)({ handler: function (_a) { var _b = _a.args, team = _b.team, unit = _b.unit, sender = _a.sender, outputSuccess = _a.outputSuccess, outputFail = _a.outputFail, f = _a.f; if (team) { - (0, menus_1.menu)("Confirm", "This will kill [scarlet]every ".concat(unit ? unit.localizedName : "unit", "[] on the team ").concat(team.coloredName(), "."), ["[orange]Kill units[]", "[green]Cancel[]"], sender, function (_a) { - var option = _a.option; - if (option == "[orange]Kill units[]") { + (0, menus_1.menu)("Confirm", "This will kill [scarlet]every ".concat(unit ? unit.localizedName : "unit", "[] on the team ").concat(team.coloredName(), "."), [new menus_1.GUI_Confirm()], sender, function (_a) { + var confirm = _a.data; + if (confirm) { if (unit) { var i_1 = 0; team.data().units.each(function (u) { return u.type == unit; }, function (u) { @@ -529,12 +529,12 @@ exports.commands = (0, commands_1.commandList)({ } else outputFail("Cancelled."); - }, false); + }); } else { - (0, menus_1.menu)("Confirm", "This will kill [scarlet]every single ".concat(unit ? unit.localizedName : "unit", "[]."), ["[orange]Kill all units[]", "[green]Cancel[]"], sender, function (_a) { - var option = _a.option; - if (option == "[orange]Kill all units[]") { + (0, menus_1.menu)("Confirm", "This will kill [scarlet]every single ".concat(unit ? unit.localizedName : "unit", "[]."), [new menus_1.GUI_Confirm], sender, function (_a) { + var option = _a.data; + if (option) { if (unit) { var i_2 = 0; Groups.unit.each(function (u) { return u.type == unit; }, function (u) { @@ -551,7 +551,7 @@ exports.commands = (0, commands_1.commandList)({ } else outputFail("Cancelled."); - }, false); + }); } } }, @@ -562,28 +562,28 @@ exports.commands = (0, commands_1.commandList)({ handler: function (_a) { var team = _a.args.team, sender = _a.sender, outputSuccess = _a.outputSuccess, outputFail = _a.outputFail, f = _a.f; if (team) { - (0, menus_1.menu)("Confirm", "This will kill [scarlet]every building[] on the team ".concat(team.coloredName(), ", except cores."), ["[orange]Kill buildings[]", "[green]Cancel[]"], sender, function (_a) { - var option = _a.option; - if (option == "[orange]Kill buildings[]") { + (0, menus_1.menu)("Confirm", "This will kill [scarlet]every building[] on the team ".concat(team.coloredName(), ", except cores."), [new menus_1.GUI_Confirm()], sender, function (_a) { + var confirm = _a.data; + if (confirm) { var count = team.data().buildings.size; team.data().buildings.each(function (b) { return !(b.block instanceof CoreBlock); }, function (b) { return b.tile.remove(); }); outputSuccess(f(templateObject_38 || (templateObject_38 = __makeTemplateObject(["Killed ", " buildings on ", ""], ["Killed ", " buildings on ", ""])), count, team)); } else outputFail("Cancelled."); - }, false); + }); } else { - (0, menus_1.menu)("Confirm", "This will kill [scarlet]every building[] except cores.", ["[orange]Kill buildings[]", "[green]Cancel[]"], sender, function (_a) { - var option = _a.option; - if (option == "[orange]Kill buildings[]") { + (0, menus_1.menu)("Confirm", "This will kill [scarlet]every building[] except cores.", [new menus_1.GUI_Confirm()], sender, function (_a) { + var confirm = _a.data; + if (confirm) { var count = Groups.build.size(); Groups.build.each(function (b) { return !(b.block instanceof CoreBlock); }, function (b) { return b.tile.remove(); }); outputSuccess(f(templateObject_39 || (templateObject_39 = __makeTemplateObject(["Killed ", " buildings."], ["Killed ", " buildings."])), count)); } else outputFail("Cancelled."); - }, false); + }); } } }, @@ -935,13 +935,16 @@ exports.commands = (0, commands_1.commandList)({ if (matches_1.isEmpty()) (0, commands_1.fail)(f(templateObject_60 || (templateObject_60 = __makeTemplateObject(["No stored data matched name ", ""], ["No stored data matched name ", ""])), input)); output(f(templateObject_61 || (templateObject_61 = __makeTemplateObject(["[accent]Found ", " match", " for search \"", "\"."], ["[accent]Found ", " match", " for search \"", "\"."])), matches_1.size, matches_1.size == 1 ? "" : "es", input)); - var displayMatches = function () { + var displayMatches_1 = function () { matches_1.each(function (info) { return output(f(templateObject_62 || (templateObject_62 = __makeTemplateObject(["[accent]Player with uuid ", "\nLast name used: \"", "\" [gray](", ")[] [[", "]\nIPs used: ", ""], ["[accent]\\\nPlayer with uuid ", "\nLast name used: \"", "\" [gray](", ")[] [[", "]\nIPs used: ", ""])), info.id, info.plainLastName(), (0, funcs_2.escapeStringColorsClient)(info.lastName), info.names.map(funcs_2.escapeStringColorsClient).items.join(", "), info.ips.map(function (i) { return "[blue]".concat(i, "[]"); }).toString(", "))); }); }; if (matches_1.size > 20) - (0, menus_1.menu)("Confirm", "Are you sure you want to view all ".concat(matches_1.size, " matches?"), ["Yes"], sender, displayMatches); - else - displayMatches(); + (0, menus_1.menu)("Confirm", "Are you sure you want to view all ".concat(matches_1.size, " matches?"), [new menus_1.GUI_Confirm()], sender, function (_a) { + var confirm = _a.data; + if (!confirm) + (0, commands_1.fail)("aborted."); + displayMatches_1(); + }); } } }, diff --git a/src/commands.ts b/src/commands.ts index 73b8328..a463e9f 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -6,7 +6,7 @@ This file contains the commands system. import { FColor, Gamemode, GamemodeName, text } from "./config"; import { ipPattern, uuidPattern } from "./globals"; -import { menu } from "./menus"; +import { GUI_Cancel, GUI_Container, menu } from "./menus"; import { FishPlayer } from "./players"; import { Rank, RankName, RoleFlag } from "./ranks"; import type { ClientCommandHandler, CommandArg, FishCommandArgType, FishCommandData, FishCommandHandlerData, FishCommandHandlerUtils, FishConsoleCommandData, Formattable, PartialFormatString, SelectEnumClassKeys, ServerCommandHandler } from "./types"; @@ -676,10 +676,10 @@ function resolveArgsRecursive(processedArgs: Record, case "player": Groups.player.each(player => optionsList.push(player)); break; default: crash(`Unable to resolve arg of type ${argToResolve.type}`); } - menu(`Select a player`, `Select a player for the argument "${argToResolve.name}"`, optionsList, sender, ({option}) => { - processedArgs[argToResolve.name] = FishPlayer.get(option); + menu(`Select a player`, `Select a player for the argument "${argToResolve.name}"`, [new GUI_Container(optionsList, "auto", player => Strings.stripColors(player.name).length >= 3 ? Strings.stripColors(player.name) : escapeStringColorsClient(player.name)), new GUI_Cancel()], sender, ({data}) => { + processedArgs[argToResolve.name] = FishPlayer.get(data); resolveArgsRecursive(processedArgs, unresolvedArgs, sender, callback); - }, true, player => Strings.stripColors(player.name).length >= 3 ? Strings.stripColors(player.name) : escapeStringColorsClient(player.name)) + }, ) } diff --git a/src/menus.ts b/src/menus.ts index 6fa9a44..f41b147 100644 --- a/src/menus.ts +++ b/src/menus.ts @@ -9,6 +9,9 @@ import { outputFail, outputSuccess } from "./utils"; import { parseError } from './funcs'; import { to2DArray } from './funcs'; + +//#region Draw Menu + /** Stores a mapping from name to the numeric id of a listener that has been registered. */ const registeredListeners:{ [index:string]: number; @@ -40,55 +43,62 @@ export function registerListeners(){ } } + + /** Displays a menu to a player. */ -function menu(title:string, description:string, options:string[], target:FishPlayer):void; +function menu(title:string, description:string, elements:GUI_Element[], target:FishPlayer):void; /** Displays a menu to a player with callback. */ -function menu( - title:string, description:string, options:T[], target:FishPlayer, +function menu( + title:string, description:string, elements:GUI_Element[], target:FishPlayer, callback: (opts: { - option:T, sender:FishPlayer, outputSuccess:(message:string) => void, outputFail:(message:string) => void; + data:any, text:string, sender:FishPlayer, outputSuccess:(message:string) => void, outputFail:(message:string) => void; }) => void, - includeCancel?:boolean, optionStringifier?:(opt:T) => string, columns?:number + ):void; //this is a minor abomination but theres no good way to do overloads in typescript -function menu( - title:string, description:string, options:T[], target:FishPlayer, +function menu( + title:string, description:string, elements:GUI_Element[], target:FishPlayer, callback?: (opts: { - option:T, sender:FishPlayer, outputSuccess:(message:string) => void, outputFail:(message:string) => void; + data:any, text:string, sender:FishPlayer, outputSuccess:(message:string) => void, outputFail:(message:string) => void; }) => void, - includeCancel:boolean = true, - optionStringifier:(opt:T) => string = t => t as unknown as string, //this is dubious - columns:number = 3, ){ + target.activeMenu.cancelOptionId = -1; + let ArrangedElements = { data:[] as any[][], stringified:[] as string[][] } + elements.forEach(element => { + ArrangedElements.data.push(...element.data()); + if(element instanceof GUI_Cancel){ + target.activeMenu.cancelOptionId = ArrangedElements.data.length + } + }); + elements.forEach(element => ArrangedElements.stringified.push(...element.format())); + //flatten to arrays + let PackedElements = { data:ArrangedElements.data.flat(), stringified:ArrangedElements.stringified.flat() } + if(PackedElements.data.length == 0){ + ArrangedElements.stringified.push([""]) + ArrangedElements.data.push([null]); // not needed, but nice to keep data and string in sync. + } if(!callback){ //overload 1, just display a menu with no callback - Call.menu(target.con, registeredListeners.none, title, description, options.length == 0 ? [[""]] : to2DArray(options.map(optionStringifier), columns)); + Call.menu(target.con, registeredListeners.none, title, description, ArrangedElements.stringified); } else { //overload 2, display a menu with callback - - //Set up the 2D array of options, and add cancel - //Use "" as a fallback, because Call.menu with an empty array of options causes a client crash - const arrangedOptions = (options.length == 0 && !includeCancel) ? [[""]] : to2DArray(options.map(optionStringifier), columns); - if(includeCancel){ - arrangedOptions.push(["Cancel"]); - target.activeMenu.cancelOptionId = options.length; - } else { - target.activeMenu.cancelOptionId = -1; - } //The target fishPlayer has a property called activeMenu, which stores information about the last menu triggered. - target.activeMenu.callback = (fishSender, option) => { + target.activeMenu.callback = (_fishSender, option) => { //Additional permission validation could be done here, but the only way that callback() can be called is if the above statement executed, //and on sensitive menus such as the stop menu, the only way to reach that is if menu() was called by the /stop command, //which already checks permissions. //Additionally, the callback is cleared by the generic menu listener after it is executed. //We do need to validate option though, as it can be any number. - if(!(option in options)) return; + Log.info(`Option ${option} in ${PackedElements.data.length}`) + if(!(option in PackedElements.data)) return; + if(typeof PackedElements.data[option] === 'string' && PackedElements.data[option] == "cancel"){return;} // cancel button pressed, no need to callback try { callback({ - option: options[option], + data: PackedElements.data[option], + text: PackedElements.stringified[option], sender: target, outputFail: message => outputFail(message, target), outputSuccess: message => outputSuccess(message, target), @@ -106,11 +116,86 @@ function menu( } }; - Call.menu(target.con, registeredListeners.generic, title, description, arrangedOptions); + Call.menu(target.con, registeredListeners.generic, title, description, ArrangedElements.stringified); + } + +} +//#endregion +//#region Draw Page Menus + +//draws a page menu with arbitrary pages +function pageMenu(title:string, description:string, elements:GUI_Element[][], target:FishPlayer, callback: (opts: {data:any, text:string, sender:FishPlayer, outputSuccess:(message:string) => void, outputFail:(message:string) => void;}) => void){ + let pages = elements.length + function drawpage(index:number){ + let e:GUI_Element[] = [new GUI_Page(index+1,pages)] + e.push(...elements[index]) + menu(title,description,e, target, (res) => { + if(typeof res.data === 'string'){ + switch (res.data) { + case "left": + drawpage((index == 0)?(0):(index-1)); + break; + case "right": + drawpage((index == pages-1)?(pages-1):(index+1)); + break; + case "center": + drawpage(index); + break; + default: + callback(res); + } + } + }) + return; } + +} +//auto formats a array into a page menu +//TODO make list a GUI_Element[] instead of a single Container +function listMenu(title:string, description:string, list:GUI_Container,target:FishPlayer, callback: (opts: {data:any, text:string, sender:FishPlayer, outputSuccess:(message:string) => void, outputFail:(message:string) => void;}) => void, pageSize:number = 10){ + let buttons = { data:[] as any[][],} + list.data()[0].reduce((result, _, index) => { if (index % pageSize === 0) { buttons.data.push(buttons.data.slice(index, index + pageSize));}return result;}); + let pages:GUI_Element[][] = []; + buttons.data.forEach(page => {pages.push([new GUI_Container(page,1,list.stringifier)])})//wrap each page in a container + pageMenu(title,description,pages,target,callback); +} +//#endregion +//#region GUI Elements +interface GUI_Element{ + format():string[][], + data():any[][] } +//const reservedStrings = ["left", "center", "right"] // strings used for paged menus, cannot be handled correct +export class GUI_Container implements GUI_Element { + constructor( + public options:any[], + public columns:number | "auto" = 3, + public stringifier:((option:any) => string) = option => option as unknown as string + ){}; + format = () => {return(to2DArray(this.options.map(this.stringifier), (this.columns == 'auto')?(3):(this.columns)))}; + data = () => {return to2DArray(this.options, (this.columns == 'auto')?(3):(this.columns))}; +} +export class GUI_Cancel implements GUI_Element{ + format = () => {return([["cancel"]])}; + data = () => {return([["cancel"]])} +} +export class GUI_Page implements GUI_Element{ + constructor( + public currentPage:number, + public pages:number + ){} + public format = () => {return([["<--"], [`${this.currentPage}/${this.pages}`], ["-->"]])}; + public data = () => {return([["left","center","left"]])} + +} +export class GUI_Confirm implements GUI_Element{ + public format = () => {return[["[green]Yes, do it", "[red] No, cancel"]]} + public data = () => {return[[true, false]]} +} +//#endregion +//#region Exports export { registeredListeners as listeners, menu diff --git a/src/playerCommands.ts b/src/playerCommands.ts index a1da1b4..94d5c09 100644 --- a/src/playerCommands.ts +++ b/src/playerCommands.ts @@ -6,8 +6,8 @@ This file contains most in-game chat commands that can be run by untrusted playe import * as api from './api'; import { command, commandList, fail, formatArg, Perm, Req } from './commands'; import { FishServer, Gamemode, rules, text } from './config'; -import { FishEvents, fishPlugin, fishState, ipPortPattern, recentWhispers, tileHistory, uuidPattern } from './globals'; -import { menu } from './menus'; +import { FishEvents, fishState, fishPlugin, ipPortPattern, recentWhispers, tileHistory, uuidPattern } from './globals'; +import { GUI_Cancel, GUI_Confirm, GUI_Container, menu } from './menus'; import { FishPlayer } from './players'; import { Rank, RoleFlag } from './ranks'; import type { FishCommandData } from './types'; @@ -542,10 +542,10 @@ Available types:[yellow] } menu( "Rules for [#0000ff]>|||> FISH [white]servers", rules.join("\n\n"), - ["[green]I agree to abide by these rules[]", "No"], target, - ({option}) => { - if(option == "No") target.kick("You must agree to the rules to play on this server. Rejoin to agree to the rules.", 1); - }, false + [new GUI_Container(["[green]I agree to abide by these rules[]", "No"])], target, + ({text}) => { + if(text == "No") target.kick("You must agree to the rules to play on this server. Rejoin to agree to the rules.", 1); + } ); if(target !== sender) outputSuccess(f`Reminded ${target} of the rules.`); }, @@ -566,7 +566,7 @@ Available types:[yellow] Power voids disable anything they are connected to. If you break it, [scarlet]you will get attacked[] by enemy units. Please stop attacking and [lime]build defenses[] first!`, - ["I understand"], args.player + [new GUI_Container(["I understand"])], args.player ); logAction("showed void warning", sender, args.player); outputSuccess(f`Warned ${args.player} about power voids with a popup message.`); @@ -647,20 +647,18 @@ Please stop attacking and [lime]build defenses[] first!` menu( "Start a Next Wave Vote", "Select the amount of waves you would like to skip, or click \"Cancel\" to abort.", - [1, 5, 10], + [new GUI_Container([1, 5, 10], "auto", n => `${n} waves`), new GUI_Cancel()], sender, - ({option}) => { + ({data}) => { if(manager.session){ //Someone else started a vote - if(manager.session.data != option) fail(`Someone else started a vote with a different number of waves to skip.`); - else manager.vote(sender, sender.voteWeight(), option); + if(manager.session.data != data) fail(`Someone else started a vote with a different number of waves to skip.`); + else manager.vote(sender, sender.voteWeight(), data); } else { //this is still a race condition technically... shouldn't be that bad right? - manager.start(sender, sender.voteWeight(), option); + manager.start(sender, sender.voteWeight(), data); } }, - true, - n => `${n} waves` ); } else { manager.vote(sender, sender.voteWeight(), null); diff --git a/src/players.ts b/src/players.ts index e1bceaa..c79330c 100644 --- a/src/players.ts +++ b/src/players.ts @@ -8,7 +8,7 @@ import { Perm, PermType } from "./commands"; import * as globals from "./globals"; import { FColor, Gamemode, heuristics, Mode, prefixes, rules, stopAntiEvadeTime, text, tips } from "./config"; import { uuidPattern } from "./globals"; -import { menu } from "./menus"; +import { GUI_Container, menu } from "./menus"; import { Rank, RankName, RoleFlag, RoleFlagName } from "./ranks"; import type { FishCommandArgType, FishPlayerData, PlayerHistoryEntry } from "./types"; import { cleanText, formatTime, formatTimeRelative, isImpersonator, logAction, logHTrip, matchFilter } from "./utils"; @@ -320,7 +320,7 @@ export class FishPlayer { fishPlayer.updateName(); }); //I think this is a better spot for this - if(fishPlayer.firstJoin()) menu("Rules for [#0000ff] >|||> FISH [white] servers [white]", rules.join("\n\n[white]") + "\nYou can view these rules again by running [cyan]/rules[].",["[green]I understand and agree to these terms"],fishPlayer); + if(fishPlayer.firstJoin()) menu("Rules for [#0000ff] >|||> FISH [white] servers [white]", rules.join("\n\n[white]") + "\nYou can view these rules again by running [cyan]/rules[].",[new GUI_Container(["[green]I understand and agree to these terms"])],fishPlayer); } } @@ -611,18 +611,13 @@ Previously used UUID \`${uuid}\`(${Vars.netServer.admins.getInfoOptional(uuid)?. menu( "[gold]Welcome to Fish Community!", `[gold]Hi there! You have been automatically [scarlet]stopped and muted[] because we've found something to be [pink]a bit sus[]. You can still talk to staff and request to be freed. ${FColor.discord`Join our Discord`} to request a staff member come online if none are on.`, - ["Close", "Discord"], + [new GUI_Container(["Close", "Discord"], 1, str => ((str == "Discord")?(FColor.discord(str)):(str)))], this, - ({option, sender}) => { - if(option == "Discord"){ + ({data:result, sender}) => { + if(result == "Discord"){ Call.openURI(sender.con, text.discordURL); } }, - false, - str => ({ - "Close": "Close", - "Discord": FColor.discord("Discord") - }[str]) ); this.sendMessage(`[gold]Welcome to Fish Community!\n[gold]Hi there! You have been automatically [scarlet]stopped and muted[] because we've found something to be [pink]a bit sus[]. You can still talk to staff and request to be freed. ${FColor.discord`Join our Discord`} to request a staff member come online if none are on.`); } diff --git a/src/staffCommands.ts b/src/staffCommands.ts index 7f304f3..8e8bbce 100644 --- a/src/staffCommands.ts +++ b/src/staffCommands.ts @@ -10,7 +10,7 @@ import { maxTime } from "./globals"; import { updateMaps } from "./files"; import * as fjsContext from "./fjsContext"; import { fishState, ipPattern, uuidPattern } from "./globals"; -import { menu } from './menus'; +import { GUI_Cancel, GUI_Confirm, GUI_Container, menu } from './menus'; import { FishPlayer } from "./players"; import { Rank } from "./ranks"; import { addToTileHistory, colorBadBoolean, formatTime, formatTimeRelative, getAntiBotInfo, logAction, match, serverRestartLoop, untilForever, updateBans } from "./utils"; @@ -32,7 +32,7 @@ export const commands = commandList({ if(args.player.hasPerm("blockTrolling")) fail(`Player ${args.player} is insufficiently trollable.`); const message = args.message ?? "You have been warned. I suggest you stop what you're doing"; - menu('Warning', message, ["[green]Accept"], args.player); + menu('Warning', message, [new GUI_Container(["[green]Accept"])], args.player); logAction('warned', sender, args.player, message); outputSuccess(f`Warned player ${args.player} for "${message}"`); } @@ -223,20 +223,20 @@ export const commands = commandList({ } - menu("Stop", "Choose a player to mark", possiblePlayers, sender, ({option: optionPlayer, sender}) => { + menu("Stop", "Choose a player to mark", [new GUI_Container(possiblePlayers, "auto", p => p.lastName), new GUI_Cancel()], sender, ({data: optionPlayer, sender}) => { if(args.time == null){ - menu("Stop", "Select stop time", ["2 days", "7 days", "30 days", "forever"], sender, ({option: optionTime, sender}) => { + menu("Stop", "Select stop time", [new GUI_Container(["2 days", "7 days", "30 days", "forever"])], sender, ({text: optionTime}) => { const time = optionTime == "2 days" ? 172800000 : optionTime == "7 days" ? 604800000 : optionTime == "30 days" ? 2592000000 : (maxTime - Date.now() - 10000); stop(optionPlayer, time); - }, false); + }); } else { stop(optionPlayer, args.time); } - }, true, p => p.lastName); + },); } }, @@ -364,8 +364,8 @@ export const commands = commandList({ let data:PlayerInfo | null; if((data = admins.getInfoOptional(uuid)) != null && data.admin) fail(`Cannot ban an admin.`); const name = data ? `${escapeStringColorsClient(data.lastName)} (${uuid}/${data.lastIP})` : uuid; - menu("Confirm", `Are you sure you want to ban ${name}?`, ["[red]Yes", "[green]Cancel"], sender, ({option:confirm}) => { - if(confirm != "[red]Yes") fail("Cancelled."); + menu("Confirm", `Are you sure you want to ban ${name}?`, [new GUI_Confirm()], sender, ({data:confirm}) => { + if(!confirm) fail("Cancelled."); admins.banPlayerID(uuid); if(data){ const ip = data.lastIP; @@ -382,14 +382,13 @@ export const commands = commandList({ outputSuccess(f`Banned player ${uuid}. [yellow]Unable to determine IP.[]`); } updateBans(player => `[scarlet]Player [yellow]${player.name}[scarlet] has been whacked by ${sender.prefixedName}.`); - }, false); + }); return; } else if(args.uuid_or_ip && ipPattern.test(args.uuid_or_ip)){ //Overload 2: ban by uuid const ip = args.uuid_or_ip; - menu("Confirm", `Are you sure you want to ban IP ${ip}?`, ["[red]Yes", "[green]Cancel"], sender, ({option:confirm}) => { - if(confirm != "[red]Yes") fail("Cancelled."); - + menu("Confirm", `Are you sure you want to ban IP ${ip}?`, [new GUI_Confirm()], sender, ({data:confirm}) => { + if(!confirm) fail("Cancelled."); api.ban({ip}); const info = admins.findByIP(ip); if(info) logAction("banned", sender, info); @@ -403,22 +402,22 @@ export const commands = commandList({ } updateBans(player => `[scarlet]Player [yellow]${player.name}[scarlet] has been whacked by ${sender.prefixedName}.`); - }, false); + }); return; } //Overload 3: ban by menu - menu(`[scarlet]BAN[]`, "Choose a player to ban.", setToArray(Groups.player), sender, ({option}) => { - if(option.admin) fail(`Cannot ban an admin.`); - menu("Confirm", `Are you sure you want to ban ${option.name}?`, ["[red]Yes", "[green]Cancel"], sender, ({option:confirm}) => { - if(confirm != "[red]Yes") fail("Cancelled."); - admins.banPlayerIP(option.ip()); //this also bans the UUID - api.ban({ip: option.ip(), uuid: option.uuid()}); - Log.info(`${option.ip()}/${option.uuid()} was banned.`); - logAction("banned", sender, option.getInfo()); - outputSuccess(f`Banned player ${option}.`); + menu(`[scarlet]BAN[]`, "Choose a player to ban.", [new GUI_Container(setToArray(Groups.player), "auto", opt => opt.name), new GUI_Cancel()], sender, ({data:target}) => { + if(target.admin) fail(`Cannot ban an admin.`); + menu("Confirm", `Are you sure you want to ban ${target.name}?`, [new GUI_Confirm()], sender, ({data:confirm}) => { + if(!confirm) fail("Cancelled."); + admins.banPlayerIP(target.ip()); //this also bans the UUID + api.ban({ip: target.ip(), uuid: target.uuid()}); + Log.info(`${target.ip()}/${target.uuid()} was banned.`); + logAction("banned", sender, target.getInfo()); + outputSuccess(f`Banned player ${target}.`); updateBans(player => `[scarlet]Player [yellow]${player.name}[scarlet] has been whacked by ${sender.prefixedName}.`); - }, false); - }, true, opt => opt.name); + }); + }); } }, @@ -447,10 +446,10 @@ export const commands = commandList({ menu( `Confirm`, `This will kill [scarlet]every ${unit ? unit.localizedName : "unit"}[] on the team ${team.coloredName()}.`, - ["[orange]Kill units[]", "[green]Cancel[]"], + [new GUI_Confirm()], sender, - ({option}) => { - if(option == "[orange]Kill units[]"){ + ({data:confirm}) => { + if(confirm){ if(unit){ let i = 0; team.data().units.each(u => u.type == unit, u => { @@ -464,16 +463,16 @@ export const commands = commandList({ outputSuccess(f`Killed ${before} units on ${team}.`); } } else outputFail(`Cancelled.`); - }, false + }, ); } else { menu( `Confirm`, `This will kill [scarlet]every single ${unit ? unit.localizedName : "unit"}[].`, - ["[orange]Kill all units[]", "[green]Cancel[]"], + [new GUI_Confirm], sender, - ({option}) => { - if(option == "[orange]Kill all units[]"){ + ({data:option}) => { + if(option){ if(unit){ let i = 0; Groups.unit.each(u => u.type == unit, u => { @@ -487,7 +486,7 @@ export const commands = commandList({ outputSuccess(f`Killed ${before} units.`); } } else outputFail(`Cancelled.`); - }, false + }, ); } } @@ -501,29 +500,29 @@ export const commands = commandList({ menu( `Confirm`, `This will kill [scarlet]every building[] on the team ${team.coloredName()}, except cores.`, - ["[orange]Kill buildings[]", "[green]Cancel[]"], + [new GUI_Confirm()], sender, - ({option}) => { - if(option == "[orange]Kill buildings[]"){ + ({data:confirm}) => { + if(confirm){ const count = team.data().buildings.size; team.data().buildings.each(b => !(b.block instanceof CoreBlock), b => b.tile.remove()); outputSuccess(f`Killed ${count} buildings on ${team}`); } else outputFail(`Cancelled.`); - }, false + } ); } else { menu( `Confirm`, `This will kill [scarlet]every building[] except cores.`, - ["[orange]Kill buildings[]", "[green]Cancel[]"], + [new GUI_Confirm()], sender, - ({option}) => { - if(option == "[orange]Kill buildings[]"){ + ({data:confirm}) => { + if(confirm){ const count = Groups.build.size(); Groups.build.each(b => !(b.block instanceof CoreBlock), b => b.tile.remove()); outputSuccess(f`Killed ${count} buildings.`); } else outputFail(`Cancelled.`); - }, false + }, ); } } @@ -870,8 +869,10 @@ Last name used: "${info.plainLastName()}" [gray](${escapeStringColorsClient(info IPs used: ${info.ips.map(i => `[blue]${i}[]`).toString(", ")}` )); }; - if(matches.size > 20) menu("Confirm", `Are you sure you want to view all ${matches.size} matches?`, ["Yes"], sender, displayMatches); - else displayMatches(); + if(matches.size > 20) menu("Confirm", `Are you sure you want to view all ${matches.size} matches?`, [new GUI_Confirm()], sender, ({data:confirm}) => { + if(!confirm) fail(`aborted.`); + displayMatches() + }); } } }, From 6d1c616e0d2286c94db424b9e3393e6ac598694c Mon Sep 17 00:00:00 2001 From: juror #9 <64419198+Jurorno9@users.noreply.github.com> Date: Sat, 25 Jan 2025 10:50:01 -0500 Subject: [PATCH 2/9] fixed GUI_Cancel --- build/scripts/menus.js | 8 +++----- src/menus.ts | 13 ++++++------- src/playerCommands.ts | 12 +++++++++++- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/build/scripts/menus.js b/build/scripts/menus.js index 22b83b4..79f0e08 100644 --- a/build/scripts/menus.js +++ b/build/scripts/menus.js @@ -42,6 +42,8 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { Object.defineProperty(exports, "__esModule", { value: true }); exports.listeners = exports.GUI_Confirm = exports.GUI_Page = exports.GUI_Cancel = exports.GUI_Container = void 0; exports.registerListeners = registerListeners; +exports.pageMenu = pageMenu; +exports.listMenu = listMenu; exports.menu = menu; var commands_1 = require("./commands"); var players_1 = require("./players"); @@ -90,14 +92,11 @@ function registerListeners() { } //this is a minor abomination but theres no good way to do overloads in typescript function menu(title, description, elements, target, callback) { - target.activeMenu.cancelOptionId = -1; + //target.activeMenu.cancelOptionId = -1; GUI_Cancel handles cancel already var ArrangedElements = { data: [], stringified: [] }; elements.forEach(function (element) { var _a; (_a = ArrangedElements.data).push.apply(_a, __spreadArray([], __read(element.data()), false)); - if (element instanceof GUI_Cancel) { - target.activeMenu.cancelOptionId = ArrangedElements.data.length; - } }); elements.forEach(function (element) { var _a; @@ -122,7 +121,6 @@ function menu(title, description, elements, target, callback) { //which already checks permissions. //Additionally, the callback is cleared by the generic menu listener after it is executed. //We do need to validate option though, as it can be any number. - Log.info("Option ".concat(option, " in ").concat(PackedElements.data.length)); if (!(option in PackedElements.data)) return; if (typeof PackedElements.data[option] === 'string' && PackedElements.data[option] == "cancel") { diff --git a/src/menus.ts b/src/menus.ts index f41b147..2c386ae 100644 --- a/src/menus.ts +++ b/src/menus.ts @@ -62,13 +62,11 @@ function menu( data:any, text:string, sender:FishPlayer, outputSuccess:(message:string) => void, outputFail:(message:string) => void; }) => void, ){ - target.activeMenu.cancelOptionId = -1; + //target.activeMenu.cancelOptionId = -1; GUI_Cancel handles cancel already + let ArrangedElements = { data:[] as any[][], stringified:[] as string[][] } elements.forEach(element => { ArrangedElements.data.push(...element.data()); - if(element instanceof GUI_Cancel){ - target.activeMenu.cancelOptionId = ArrangedElements.data.length - } }); elements.forEach(element => ArrangedElements.stringified.push(...element.format())); @@ -92,7 +90,6 @@ function menu( //Additionally, the callback is cleared by the generic menu listener after it is executed. //We do need to validate option though, as it can be any number. - Log.info(`Option ${option} in ${PackedElements.data.length}`) if(!(option in PackedElements.data)) return; if(typeof PackedElements.data[option] === 'string' && PackedElements.data[option] == "cancel"){return;} // cancel button pressed, no need to callback try { @@ -124,7 +121,7 @@ function menu( //#region Draw Page Menus //draws a page menu with arbitrary pages -function pageMenu(title:string, description:string, elements:GUI_Element[][], target:FishPlayer, callback: (opts: {data:any, text:string, sender:FishPlayer, outputSuccess:(message:string) => void, outputFail:(message:string) => void;}) => void){ +export function pageMenu(title:string, description:string, elements:GUI_Element[][], target:FishPlayer, callback: (opts: {data:any, text:string, sender:FishPlayer, outputSuccess:(message:string) => void, outputFail:(message:string) => void;}) => void){ let pages = elements.length function drawpage(index:number){ let e:GUI_Element[] = [new GUI_Page(index+1,pages)] @@ -152,7 +149,7 @@ function pageMenu(title:string, description:string, elements:GUI_Element[][], ta } //auto formats a array into a page menu //TODO make list a GUI_Element[] instead of a single Container -function listMenu(title:string, description:string, list:GUI_Container,target:FishPlayer, callback: (opts: {data:any, text:string, sender:FishPlayer, outputSuccess:(message:string) => void, outputFail:(message:string) => void;}) => void, pageSize:number = 10){ +export function listMenu(title:string, description:string, list:GUI_Container,target:FishPlayer, callback: (opts: {data:any, text:string, sender:FishPlayer, outputSuccess:(message:string) => void, outputFail:(message:string) => void;}) => void, pageSize:number = 10){ let buttons = { data:[] as any[][],} list.data()[0].reduce((result, _, index) => { if (index % pageSize === 0) { buttons.data.push(buttons.data.slice(index, index + pageSize));}return result;}); let pages:GUI_Element[][] = []; @@ -200,3 +197,5 @@ export { registeredListeners as listeners, menu }; + + diff --git a/src/playerCommands.ts b/src/playerCommands.ts index 94d5c09..8de3606 100644 --- a/src/playerCommands.ts +++ b/src/playerCommands.ts @@ -7,7 +7,7 @@ import * as api from './api'; import { command, commandList, fail, formatArg, Perm, Req } from './commands'; import { FishServer, Gamemode, rules, text } from './config'; import { FishEvents, fishState, fishPlugin, ipPortPattern, recentWhispers, tileHistory, uuidPattern } from './globals'; -import { GUI_Cancel, GUI_Confirm, GUI_Container, menu } from './menus'; +import { GUI_Cancel, GUI_Confirm, GUI_Container, listMenu, menu } from './menus'; import { FishPlayer } from './players'; import { Rank, RoleFlag } from './ranks'; import type { FishCommandData } from './types'; @@ -906,4 +906,14 @@ Win rate: ${target.stats.gamesWon / target.stats.gamesFinished}` ); } }, + /* + debug:{ + args: [], + description: "mcdebug, do not add to pr", + perm:Perm.none, + handler({sender}){ + menu("debug list menu", "", [new GUI_Container(["option1", "option2", "option3"]), new GUI_Container(["option4", "option5", "option6"], 1)],sender, ({data}) => {Log.info(`Data ${data}`)}) + } + }, + */ }); From 9cb5d03b461b5a4c3926d9b5cd736c908630c774 Mon Sep 17 00:00:00 2001 From: juror #9 <64419198+Jurorno9@users.noreply.github.com> Date: Sat, 25 Jan 2025 13:59:01 -0500 Subject: [PATCH 3/9] fixed listmenu --- build/scripts/menus.js | 30 ++++++++++++++++++------------ build/scripts/playerCommands.js | 13 +++++++++++++ src/menus.ts | 31 +++++++++++++++++-------------- src/playerCommands.ts | 10 ++++++---- 4 files changed, 54 insertions(+), 30 deletions(-) diff --git a/build/scripts/menus.js b/build/scripts/menus.js index 79f0e08..2cef2f8 100644 --- a/build/scripts/menus.js +++ b/build/scripts/menus.js @@ -158,9 +158,11 @@ function menu(title, description, elements, target, callback) { function pageMenu(title, description, elements, target, callback) { var pages = elements.length; function drawpage(index) { - var e = [new GUI_Page(index + 1, pages)]; + var e = []; e.push.apply(e, __spreadArray([], __read(elements[index]), false)); + e.push(new GUI_Page(index + 1, pages)); menu(title, description, e, target, function (res) { + // handle control element of the ui if (typeof res.data === 'string') { switch (res.data) { case "left": @@ -176,23 +178,26 @@ function pageMenu(title, description, elements, target, callback) { callback(res); } } + else { + callback(res.data); + } }); return; } + drawpage(0); } -//auto formats a array into a page menu //TODO make list a GUI_Element[] instead of a single Container function listMenu(title, description, list, target, callback, pageSize) { if (pageSize === void 0) { pageSize = 10; } - var buttons = { data: [], }; - list.data()[0].reduce(function (result, _, index) { if (index % pageSize === 0) { - buttons.data.push(buttons.data.slice(index, index + pageSize)); - } return result; }); - var pages = []; - buttons.data.forEach(function (page) { pages.push([new GUI_Container(page, 1, list.stringifier)]); }); //wrap each page in a container - pageMenu(title, description, pages, target, callback); + var pooledData = []; + list.data().flat().forEach(function (data) { pooledData.push(data); }); + var pagedData = pooledData.reduce(function (res, _, index) { if (index % pageSize === 0) { + res.push(pooledData.slice(index, index + pageSize)); + } return res; }, []); + var pagesElements = []; + pagedData.forEach(function (pageData) { return pagesElements.push([new GUI_Container(pageData, 1, list.stringifier)]); }); + pageMenu(title, description, pagesElements, target, callback); } -//const reservedStrings = ["left", "center", "right"] // strings used for paged menus, cannot be handled correct var GUI_Container = /** @class */ (function () { function GUI_Container(options, columns, stringifier) { if (columns === void 0) { columns = 3; } @@ -221,8 +226,8 @@ var GUI_Page = /** @class */ (function () { var _this = this; this.currentPage = currentPage; this.pages = pages; - this.format = function () { return ([["<--"], ["".concat(_this.currentPage, "/").concat(_this.pages)], ["-->"]]); }; - this.data = function () { return ([["left", "center", "left"]]); }; + this.format = function () { return ((0, funcs_2.to2DArray)(["<--", "".concat(_this.currentPage, "/").concat(_this.pages), "-->"], 3)); }; + this.data = function () { return ([["left", "center", "right"]]); }; } return GUI_Page; }()); @@ -235,3 +240,4 @@ var GUI_Confirm = /** @class */ (function () { return GUI_Confirm; }()); exports.GUI_Confirm = GUI_Confirm; +//#endregion diff --git a/build/scripts/playerCommands.js b/build/scripts/playerCommands.js index a68a2f0..619a1a1 100644 --- a/build/scripts/playerCommands.js +++ b/build/scripts/playerCommands.js @@ -887,5 +887,18 @@ exports.commands = (0, commands_1.commandList)(__assign(__assign({ about: { var target = _a.args.target, output = _a.output, f = _a.f; output(f(templateObject_17 || (templateObject_17 = __makeTemplateObject(["[accent]Statistics for player ", ":\n(note: we started recording statistics on 22 Jan 2024)\n[white]--------------[]\nBlocks broken: ", "\nBlocks placed: ", "\nChat messages sent: ", "\nGames finished: ", "\nTime in-game: ", "\nWin rate: ", ""], ["[accent]\\\nStatistics for player ", ":\n(note: we started recording statistics on 22 Jan 2024)\n[white]--------------[]\nBlocks broken: ", "\nBlocks placed: ", "\nChat messages sent: ", "\nGames finished: ", "\nTime in-game: ", "\nWin rate: ", ""])), target, target.stats.blocksBroken, target.stats.blocksPlaced, target.stats.chatMessagesSent, target.stats.gamesFinished, (0, utils_1.formatTime)(target.stats.timeInGame), target.stats.gamesWon / target.stats.gamesFinished)); } + }, debug: { + args: [], + description: "mcdebug, do not add to pr", + perm: commands_1.Perm.none, + handler: function (_a) { + var sender = _a.sender; + //menu("debug page menu", "", [new GUI_Page(1,2)], sender) + //pageMenu("Multi Page Menu", "", [[new GUI_Container(["1", "2", "3", "4"], 1)], [new GUI_Container(["5", "6", "7", "8"],1)], [new GUI_Confirm()]], sender, ({data}) =>{Log.info(`Button ${data} Pressed`)}); + (0, menus_1.listMenu)("listmenu", "", new menus_1.GUI_Container(["option1", "option2", "option3", "option4", "option5", "option6", "option7", "option8", "option9", "option10", "option11", "option12", "option13", "option14", "option15", "option16", "option17", "option18", "option19", "option20",]), sender, function (_a) { + var data = _a.data; + Log.info("Button ".concat(data, " Pressed")); + }); + } } })); var templateObject_1, templateObject_2, templateObject_3, templateObject_4, templateObject_5, templateObject_6, templateObject_7, templateObject_8, templateObject_9, templateObject_10, templateObject_11, templateObject_12, templateObject_13, templateObject_14, templateObject_15, templateObject_16, templateObject_17; diff --git a/src/menus.ts b/src/menus.ts index 2c386ae..c826120 100644 --- a/src/menus.ts +++ b/src/menus.ts @@ -76,6 +76,7 @@ function menu( ArrangedElements.stringified.push([""]) ArrangedElements.data.push([null]); // not needed, but nice to keep data and string in sync. } + if(!callback){ //overload 1, just display a menu with no callback Call.menu(target.con, registeredListeners.none, title, description, ArrangedElements.stringified); @@ -124,9 +125,11 @@ function menu( export function pageMenu(title:string, description:string, elements:GUI_Element[][], target:FishPlayer, callback: (opts: {data:any, text:string, sender:FishPlayer, outputSuccess:(message:string) => void, outputFail:(message:string) => void;}) => void){ let pages = elements.length function drawpage(index:number){ - let e:GUI_Element[] = [new GUI_Page(index+1,pages)] + let e:GUI_Element[] = []; e.push(...elements[index]) + e.push(new GUI_Page(index+1, pages)) menu(title,description,e, target, (res) => { + // handle control element of the ui if(typeof res.data === 'string'){ switch (res.data) { case "left": @@ -141,20 +144,23 @@ export function pageMenu(title:string, description:string, elements:GUI_Element[ default: callback(res); } + }else{ + callback(res.data); } }) return; } + drawpage(0); } -//auto formats a array into a page menu //TODO make list a GUI_Element[] instead of a single Container -export function listMenu(title:string, description:string, list:GUI_Container,target:FishPlayer, callback: (opts: {data:any, text:string, sender:FishPlayer, outputSuccess:(message:string) => void, outputFail:(message:string) => void;}) => void, pageSize:number = 10){ - let buttons = { data:[] as any[][],} - list.data()[0].reduce((result, _, index) => { if (index % pageSize === 0) { buttons.data.push(buttons.data.slice(index, index + pageSize));}return result;}); - let pages:GUI_Element[][] = []; - buttons.data.forEach(page => {pages.push([new GUI_Container(page,1,list.stringifier)])})//wrap each page in a container - pageMenu(title,description,pages,target,callback); +export function listMenu(title:string, description:string, list:GUI_Container, target:FishPlayer, callback: (opts: {data:any, text:string, sender:FishPlayer, outputSuccess:(message:string) => void, outputFail:(message:string) => void;}) => void, pageSize:number = 10){ + let pooledData:any[] = []; + list.data().flat().forEach((data) => {pooledData.push(data)}); + let pagedData:any[][] = pooledData.reduce((res, _, index) => {if (index % pageSize === 0) {res.push(pooledData.slice(index, index + pageSize));}return res;}, [] as any[][]); + let pagesElements:GUI_Element[][] = []; + pagedData.forEach(pageData => pagesElements.push([new GUI_Container(pageData,1,list.stringifier)])); + pageMenu(title,description,pagesElements,target,callback); } //#endregion //#region GUI Elements @@ -163,8 +169,6 @@ interface GUI_Element{ format():string[][], data():any[][] } - -//const reservedStrings = ["left", "center", "right"] // strings used for paged menus, cannot be handled correct export class GUI_Container implements GUI_Element { constructor( public options:any[], @@ -183,8 +187,8 @@ export class GUI_Page implements GUI_Element{ public currentPage:number, public pages:number ){} - public format = () => {return([["<--"], [`${this.currentPage}/${this.pages}`], ["-->"]])}; - public data = () => {return([["left","center","left"]])} + public format = () => {return(to2DArray(["<--", `${this.currentPage}/${this.pages}`, "-->"],3))}; + public data = () => {return([["left","center","right"]])} } export class GUI_Confirm implements GUI_Element{ @@ -197,5 +201,4 @@ export { registeredListeners as listeners, menu }; - - +//#endregion diff --git a/src/playerCommands.ts b/src/playerCommands.ts index 8de3606..304bb7a 100644 --- a/src/playerCommands.ts +++ b/src/playerCommands.ts @@ -7,7 +7,7 @@ import * as api from './api'; import { command, commandList, fail, formatArg, Perm, Req } from './commands'; import { FishServer, Gamemode, rules, text } from './config'; import { FishEvents, fishState, fishPlugin, ipPortPattern, recentWhispers, tileHistory, uuidPattern } from './globals'; -import { GUI_Cancel, GUI_Confirm, GUI_Container, listMenu, menu } from './menus'; +import { GUI_Cancel, GUI_Confirm, GUI_Container, GUI_Page, listMenu, menu, pageMenu } from './menus'; import { FishPlayer } from './players'; import { Rank, RoleFlag } from './ranks'; import type { FishCommandData } from './types'; @@ -906,14 +906,16 @@ Win rate: ${target.stats.gamesWon / target.stats.gamesFinished}` ); } }, - /* + debug:{ args: [], description: "mcdebug, do not add to pr", perm:Perm.none, handler({sender}){ - menu("debug list menu", "", [new GUI_Container(["option1", "option2", "option3"]), new GUI_Container(["option4", "option5", "option6"], 1)],sender, ({data}) => {Log.info(`Data ${data}`)}) + //menu("debug page menu", "", [new GUI_Page(1,2)], sender) + //pageMenu("Multi Page Menu", "", [[new GUI_Container(["1", "2", "3", "4"], 1)], [new GUI_Container(["5", "6", "7", "8"],1)], [new GUI_Confirm()]], sender, ({data}) =>{Log.info(`Button ${data} Pressed`)}); + listMenu("listmenu", "", new GUI_Container(["option1","option2","option3","option4","option5","option6","option7","option8","option9","option10","option11","option12","option13","option14","option15","option16","option17","option18","option19","option20",]),sender,({data}) =>{Log.info(`Button ${data} Pressed`)}) } }, - */ + }); From 26193f591bbf0d4d87ef2116c7c230f1298c043e Mon Sep 17 00:00:00 2001 From: juror #9 <64419198+Jurorno9@users.noreply.github.com> Date: Sat, 25 Jan 2025 14:22:56 -0500 Subject: [PATCH 4/9] sample implementation of listmenu --- build/scripts/playerCommands.js | 33 ++++++++++++++++++++++----------- src/playerCommands.ts | 32 +++++++++++++++++++++----------- 2 files changed, 43 insertions(+), 22 deletions(-) diff --git a/build/scripts/playerCommands.js b/build/scripts/playerCommands.js index 619a1a1..f1d78bf 100644 --- a/build/scripts/playerCommands.js +++ b/build/scripts/playerCommands.js @@ -833,25 +833,36 @@ exports.commands = (0, commands_1.commandList)(__assign(__assign({ about: { Events.on(EventType.GameOverEvent, resetVotes); Events.on(EventType.ServerLoadEvent, resetVotes); return { - args: ['map:map'], + args: ['map:map?'], description: 'Allows you to vote for the next map. Use /maps to see all available maps.', perm: commands_1.Perm.play, data: { votes: votes, voteEndTime: function () { return voteEndTime; }, resetVotes: resetVotes, endVote: endVote }, requirements: [commands_1.Req.cooldown(10000), commands_1.Req.modeNot("hexed")], handler: function (_a) { var map = _a.args.map, sender = _a.sender; - if (votes.get(sender)) - (0, commands_1.fail)("You have already voted."); - votes.set(sender, map); - if (voteEndTime == -1) { - if ((Date.now() - lastVoteTime) < 60000) - (0, commands_1.fail)("Please wait 1 minute before starting a new map vote."); - startVote(); - Call.sendMessage("[cyan]Next Map Vote: ".concat(sender.name, "[cyan] started a map vote, and voted for [yellow]").concat(map.name(), "[cyan]. Use /nextmap ").concat(map.plainName(), " to add your vote!")); + if (!map) { + (0, menus_1.listMenu)("Please Select a Map", "", new menus_1.GUI_Container(Vars.maps.customMaps().toArray(), 1, function (map) { return "[accent]".concat(map.name()); }), sender, function (_a) { + var data = _a.data; + playervote(data); + }); } else { - Call.sendMessage("[cyan]Next Map Vote: ".concat(sender.name, "[cyan] voted for [yellow]").concat(map.name(), "[cyan]. Time left: [scarlet]").concat((0, utils_1.formatTimeRelative)(voteEndTime, true))); - showVotes(); + playervote(map); + } + function playervote(option) { + if (votes.get(sender)) + (0, commands_1.fail)("You have already voted."); + votes.set(sender, option); + if (voteEndTime == -1) { + if ((Date.now() - lastVoteTime) < 60000) + (0, commands_1.fail)("Please wait 1 minute before starting a new map vote."); + startVote(); + Call.sendMessage("[cyan]Next Map Vote: ".concat(sender.name, "[cyan] started a map vote, and voted for [yellow]").concat(option.name(), "[cyan]. Use /nextmap ").concat(option.plainName(), " to add your vote!")); + } + else { + Call.sendMessage("[cyan]Next Map Vote: ".concat(sender.name, "[cyan] voted for [yellow]").concat(option.name(), "[cyan]. Time left: [scarlet]").concat((0, utils_1.formatTimeRelative)(voteEndTime, true))); + showVotes(); + } } } }; diff --git a/src/playerCommands.ts b/src/playerCommands.ts index 304bb7a..c5b490b 100644 --- a/src/playerCommands.ts +++ b/src/playerCommands.ts @@ -834,22 +834,32 @@ ${highestVotedMaps.map(({key:map, value:votes}) => Events.on(EventType.ServerLoadEvent, resetVotes); return { - args: ['map:map'], + args: ['map:map?'], description: 'Allows you to vote for the next map. Use /maps to see all available maps.', perm: Perm.play, data: {votes, voteEndTime: () => voteEndTime, resetVotes, endVote}, requirements: [Req.cooldown(10000), Req.modeNot("hexed")], handler({args:{map}, sender}){ - if(votes.get(sender)) fail(`You have already voted.`); - - votes.set(sender, map); - if(voteEndTime == -1){ - if((Date.now() - lastVoteTime) < 60_000) fail(`Please wait 1 minute before starting a new map vote.`); - startVote(); - Call.sendMessage(`[cyan]Next Map Vote: ${sender.name}[cyan] started a map vote, and voted for [yellow]${map.name()}[cyan]. Use /nextmap ${map.plainName()} to add your vote!`); - } else { - Call.sendMessage(`[cyan]Next Map Vote: ${sender.name}[cyan] voted for [yellow]${map.name()}[cyan]. Time left: [scarlet]${formatTimeRelative(voteEndTime, true)}`); - showVotes(); + if(!map){ + listMenu("Please Select a Map","", new GUI_Container(Vars.maps.customMaps().toArray(),1,(map:MMap) => {return `[accent]${map.name()}`}), sender, ({data}) => { + playervote(data); + }) + }else{ + playervote(map) + } + + + function playervote(option:MMap){ + if(votes.get(sender)) fail(`You have already voted.`); + votes.set(sender, option); + if(voteEndTime == -1){ + if((Date.now() - lastVoteTime) < 60_000) fail(`Please wait 1 minute before starting a new map vote.`); + startVote(); + Call.sendMessage(`[cyan]Next Map Vote: ${sender.name}[cyan] started a map vote, and voted for [yellow]${option.name()}[cyan]. Use /nextmap ${option.plainName()} to add your vote!`); + } else { + Call.sendMessage(`[cyan]Next Map Vote: ${sender.name}[cyan] voted for [yellow]${option.name()}[cyan]. Time left: [scarlet]${formatTimeRelative(voteEndTime, true)}`); + showVotes(); + } } } }; From db95f1b5368462323e728895ac6fc8806fa69ed2 Mon Sep 17 00:00:00 2001 From: juror #9 <64419198+Jurorno9@users.noreply.github.com> Date: Sat, 25 Jan 2025 14:37:14 -0500 Subject: [PATCH 5/9] autoformat --- src/menus.ts | 136 +++++++++---------- src/playerCommands.ts | 304 +++++++++++++++++++++--------------------- 2 files changed, 220 insertions(+), 220 deletions(-) diff --git a/src/menus.ts b/src/menus.ts index c826120..610d19a 100644 --- a/src/menus.ts +++ b/src/menus.ts @@ -13,32 +13,32 @@ import { to2DArray } from './funcs'; //#region Draw Menu /** Stores a mapping from name to the numeric id of a listener that has been registered. */ -const registeredListeners:{ - [index:string]: number; +const registeredListeners: { + [index: string]: number; } = {}; /** Stores all listeners in use by fish-commands. */ const listeners = ( - void>>(d:T) => d + void>>(d: T) => d )({ - generic(player, option){ + generic(player, option) { const fishSender = FishPlayer.get(player); - if(option === -1 || option === fishSender.activeMenu.cancelOptionId) return; + if (option === -1 || option === fishSender.activeMenu.cancelOptionId) return; const prevCallback = fishSender.activeMenu.callback; fishSender.activeMenu.callback?.(fishSender, option); //if the callback wasn't modified, then clear it - if(fishSender.activeMenu.callback === prevCallback) + if (fishSender.activeMenu.callback === prevCallback) fishSender.activeMenu.callback = undefined; //otherwise, the menu spawned another menu that needs to be handled }, - none(player, option){ + none(player, option) { //do nothing } }); /** Registers all listeners, should be called on server load. */ -export function registerListeners(){ - for(const [key, listener] of Object.entries(listeners)){ +export function registerListeners() { + for (const [key, listener] of Object.entries(listeners)) { registeredListeners[key] ??= Menus.registerMenu(listener); } } @@ -46,43 +46,43 @@ export function registerListeners(){ /** Displays a menu to a player. */ -function menu(title:string, description:string, elements:GUI_Element[], target:FishPlayer):void; +function menu(title: string, description: string, elements: GUI_Element[], target: FishPlayer): void; /** Displays a menu to a player with callback. */ function menu( - title:string, description:string, elements:GUI_Element[], target:FishPlayer, + title: string, description: string, elements: GUI_Element[], target: FishPlayer, callback: (opts: { - data:any, text:string, sender:FishPlayer, outputSuccess:(message:string) => void, outputFail:(message:string) => void; + data: any, text: string, sender: FishPlayer, outputSuccess: (message: string) => void, outputFail: (message: string) => void; }) => void, - -):void; + +): void; //this is a minor abomination but theres no good way to do overloads in typescript function menu( - title:string, description:string, elements:GUI_Element[], target:FishPlayer, + title: string, description: string, elements: GUI_Element[], target: FishPlayer, callback?: (opts: { - data:any, text:string, sender:FishPlayer, outputSuccess:(message:string) => void, outputFail:(message:string) => void; + data: any, text: string, sender: FishPlayer, outputSuccess: (message: string) => void, outputFail: (message: string) => void; }) => void, -){ +) { //target.activeMenu.cancelOptionId = -1; GUI_Cancel handles cancel already - let ArrangedElements = { data:[] as any[][], stringified:[] as string[][] } + let ArrangedElements = { data: [] as any[][], stringified: [] as string[][] } elements.forEach(element => { ArrangedElements.data.push(...element.data()); }); elements.forEach(element => ArrangedElements.stringified.push(...element.format())); //flatten to arrays - let PackedElements = { data:ArrangedElements.data.flat(), stringified:ArrangedElements.stringified.flat() } - if(PackedElements.data.length == 0){ + let PackedElements = { data: ArrangedElements.data.flat(), stringified: ArrangedElements.stringified.flat() } + if (PackedElements.data.length == 0) { ArrangedElements.stringified.push([""]) ArrangedElements.data.push([null]); // not needed, but nice to keep data and string in sync. } - if(!callback){ + if (!callback) { //overload 1, just display a menu with no callback Call.menu(target.con, registeredListeners.none, title, description, ArrangedElements.stringified); } else { //overload 2, display a menu with callback - + //The target fishPlayer has a property called activeMenu, which stores information about the last menu triggered. target.activeMenu.callback = (_fishSender, option) => { //Additional permission validation could be done here, but the only way that callback() can be called is if the above statement executed, @@ -91,8 +91,8 @@ function menu( //Additionally, the callback is cleared by the generic menu listener after it is executed. //We do need to validate option though, as it can be any number. - if(!(option in PackedElements.data)) return; - if(typeof PackedElements.data[option] === 'string' && PackedElements.data[option] == "cancel"){return;} // cancel button pressed, no need to callback + if (!(option in PackedElements.data)) return; + if (typeof PackedElements.data[option] === 'string' && PackedElements.data[option] == "cancel") { return; } // cancel button pressed, no need to callback try { callback({ data: PackedElements.data[option], @@ -101,19 +101,19 @@ function menu( outputFail: message => outputFail(message, target), outputSuccess: message => outputSuccess(message, target), }); - } catch(err){ - if(err instanceof CommandError){ + } catch (err) { + if (err instanceof CommandError) { //If the error is a command error, then just outputFail outputFail(err.data, target); } else { target.sendMessage(`[scarlet]\u274C An error occurred while executing the command!`); - if(target.hasPerm("seeErrorMessages")) target.sendMessage(parseError(err)); + if (target.hasPerm("seeErrorMessages")) target.sendMessage(parseError(err)); Log.err(`Unhandled error in menu callback: ${target.cleanedName} submitted menu "${title}" "${description}"`); Log.err(err as Error); } } }; - + Call.menu(target.con, registeredListeners.generic, title, description, ArrangedElements.stringified); } @@ -122,78 +122,78 @@ function menu( //#region Draw Page Menus //draws a page menu with arbitrary pages -export function pageMenu(title:string, description:string, elements:GUI_Element[][], target:FishPlayer, callback: (opts: {data:any, text:string, sender:FishPlayer, outputSuccess:(message:string) => void, outputFail:(message:string) => void;}) => void){ +export function pageMenu(title: string, description: string, elements: GUI_Element[][], target: FishPlayer, callback: (opts: { data: any, text: string, sender: FishPlayer, outputSuccess: (message: string) => void, outputFail: (message: string) => void; }) => void) { let pages = elements.length - function drawpage(index:number){ - let e:GUI_Element[] = []; + function drawpage(index: number) { + let e: GUI_Element[] = []; e.push(...elements[index]) - e.push(new GUI_Page(index+1, pages)) - menu(title,description,e, target, (res) => { + e.push(new GUI_Page(index + 1, pages)) + menu(title, description, e, target, (res) => { // handle control element of the ui - if(typeof res.data === 'string'){ + if (typeof res.data === 'string') { switch (res.data) { case "left": - drawpage((index == 0)?(0):(index-1)); + drawpage((index == 0) ? (0) : (index - 1)); break; case "right": - drawpage((index == pages-1)?(pages-1):(index+1)); + drawpage((index == pages - 1) ? (pages - 1) : (index + 1)); break; case "center": drawpage(index); break; - default: - callback(res); + default: + callback(res); } - }else{ + } else { callback(res.data); } }) return; } drawpage(0); - + } //TODO make list a GUI_Element[] instead of a single Container -export function listMenu(title:string, description:string, list:GUI_Container, target:FishPlayer, callback: (opts: {data:any, text:string, sender:FishPlayer, outputSuccess:(message:string) => void, outputFail:(message:string) => void;}) => void, pageSize:number = 10){ - let pooledData:any[] = []; - list.data().flat().forEach((data) => {pooledData.push(data)}); - let pagedData:any[][] = pooledData.reduce((res, _, index) => {if (index % pageSize === 0) {res.push(pooledData.slice(index, index + pageSize));}return res;}, [] as any[][]); - let pagesElements:GUI_Element[][] = []; - pagedData.forEach(pageData => pagesElements.push([new GUI_Container(pageData,1,list.stringifier)])); - pageMenu(title,description,pagesElements,target,callback); +export function listMenu(title: string, description: string, list: GUI_Container, target: FishPlayer, callback: (opts: { data: any, text: string, sender: FishPlayer, outputSuccess: (message: string) => void, outputFail: (message: string) => void; }) => void, pageSize: number = 10) { + let pooledData: any[] = []; + list.data().flat().forEach((data) => { pooledData.push(data) }); + let pagedData: any[][] = pooledData.reduce((res, _, index) => { if (index % pageSize === 0) { res.push(pooledData.slice(index, index + pageSize)); } return res; }, [] as any[][]); + let pagesElements: GUI_Element[][] = []; + pagedData.forEach(pageData => pagesElements.push([new GUI_Container(pageData, 1, list.stringifier)])); + pageMenu(title, description, pagesElements, target, callback); } //#endregion //#region GUI Elements -interface GUI_Element{ - format():string[][], - data():any[][] +interface GUI_Element { + format(): string[][], + data(): any[][] } export class GUI_Container implements GUI_Element { constructor( - public options:any[], - public columns:number | "auto" = 3, - public stringifier:((option:any) => string) = option => option as unknown as string - ){}; - format = () => {return(to2DArray(this.options.map(this.stringifier), (this.columns == 'auto')?(3):(this.columns)))}; - data = () => {return to2DArray(this.options, (this.columns == 'auto')?(3):(this.columns))}; + public options: any[], + public columns: number | "auto" = 3, + public stringifier: ((option: any) => string) = option => option as unknown as string + ) { }; + format = () => { return (to2DArray(this.options.map(this.stringifier), (this.columns == 'auto') ? (3) : (this.columns))) }; + data = () => { return to2DArray(this.options, (this.columns == 'auto') ? (3) : (this.columns)) }; } -export class GUI_Cancel implements GUI_Element{ - format = () => {return([["cancel"]])}; - data = () => {return([["cancel"]])} +export class GUI_Cancel implements GUI_Element { + format = () => { return ([["cancel"]]) }; + data = () => { return ([["cancel"]]) } } -export class GUI_Page implements GUI_Element{ +export class GUI_Page implements GUI_Element { constructor( - public currentPage:number, - public pages:number - ){} - public format = () => {return(to2DArray(["<--", `${this.currentPage}/${this.pages}`, "-->"],3))}; - public data = () => {return([["left","center","right"]])} + public currentPage: number, + public pages: number + ) { } + public format = () => { return (to2DArray(["<--", `${this.currentPage}/${this.pages}`, "-->"], 3)) }; + public data = () => { return ([["left", "center", "right"]]) } } -export class GUI_Confirm implements GUI_Element{ - public format = () => {return[["[green]Yes, do it", "[red] No, cancel"]]} - public data = () => {return[[true, false]]} +export class GUI_Confirm implements GUI_Element { + public format = () => { return [["[green]Yes, do it", "[red] No, cancel"]] } + public data = () => { return [[true, false]] } } //#endregion //#region Exports diff --git a/src/playerCommands.ts b/src/playerCommands.ts index c5b490b..9758a85 100644 --- a/src/playerCommands.ts +++ b/src/playerCommands.ts @@ -47,9 +47,9 @@ export const commands = commandList({ perm: Perm.play, requirements: [Req.modeNot("pvp")], handler({ args, sender }) { - if(!sender.unit()?.spawnedByCore) fail(`Can only teleport while in a core unit.`); - if(sender.team() !== args.player.team()) fail(`Cannot teleport to players on another team.`); - if(sender.unit().hasPayload?.()) fail(`Cannot teleport to players while holding a payload.`); + if (!sender.unit()?.spawnedByCore) fail(`Can only teleport while in a core unit.`); + if (sender.team() !== args.player.team()) fail(`Cannot teleport to players on another team.`); + if (sender.unit().hasPayload?.()) fail(`Cannot teleport to players while holding a payload.`); teleportPlayer(sender.player!, args.player.player!); }, }, @@ -59,13 +59,13 @@ export const commands = commandList({ description: 'Removes all boulders from the map.', perm: Perm.play, requirements: [Req.cooldownGlobal(100_000)], - handler({sender, outputSuccess}){ + handler({ sender, outputSuccess }) { Timer.schedule( () => Call.sound(sender.con, Sounds.rockBreak, 1, 1, 0), 0, 0.05, 10 ); - Vars.world.tiles.eachTile((t:Tile) => { - if(t.breakable() && t.block() instanceof Prop){ + Vars.world.tiles.eachTile((t: Tile) => { + if (t.breakable() && t.block() instanceof Prop) { t.removeNet(); } }); @@ -97,9 +97,9 @@ export const commands = commandList({ args: ['persist:boolean?'], description: 'Checks the history of a tile.', perm: Perm.none, - handler({args, output, outputSuccess, currentTapMode, handleTaps}){ - if(currentTapMode == "off"){ - if(args.persist){ + handler({ args, output, outputSuccess, currentTapMode, handleTaps }) { + if (currentTapMode == "off") { + if (args.persist) { handleTaps("on"); outputSuccess(`Tilelog mode enabled. Click tiles to check their recent history. Run /tilelog again to disable.`); } else { @@ -111,7 +111,7 @@ export const commands = commandList({ outputSuccess(`Tilelog disabled.`); } }, - tapped({tile, x, y, output, sender, admins}){ + tapped({ tile, x, y, output, sender, admins }) { const historyData = tileHistory[`${x},${y}`] ?? fail(`There is no recorded history for the selected tile (${tile.x}, ${tile.y}).`); const history = StringIO.read(historyData, str => str.readArray(d => ({ action: d.readString(2), @@ -121,10 +121,10 @@ export const commands = commandList({ }), 1)); output(`[yellow]Tile history for tile (${tile.x}, ${tile.y}):\n` + history.map(e => uuidPattern.test(e.uuid) - ? (sender.hasPerm("viewUUIDs") - ? `[yellow]${admins.getInfoOptional(e.uuid)?.plainLastName()}[lightgray](${e.uuid})[yellow] ${e.action} a [cyan]${e.type}[] ${formatTimeRelative(e.time)}` - : `[yellow]${admins.getInfoOptional(e.uuid)?.plainLastName()} ${e.action} a [cyan]${e.type}[] ${formatTimeRelative(e.time)}`) - : `[yellow]${e.uuid}[yellow] ${e.action} a [cyan]${e.type}[] ${formatTimeRelative(e.time)}` + ? (sender.hasPerm("viewUUIDs") + ? `[yellow]${admins.getInfoOptional(e.uuid)?.plainLastName()}[lightgray](${e.uuid})[yellow] ${e.action} a [cyan]${e.type}[] ${formatTimeRelative(e.time)}` + : `[yellow]${admins.getInfoOptional(e.uuid)?.plainLastName()} ${e.action} a [cyan]${e.type}[] ${formatTimeRelative(e.time)}`) + : `[yellow]${e.uuid}[yellow] ${e.action} a [cyan]${e.type}[] ${formatTimeRelative(e.time)}` ).join('\n')); } }, @@ -136,7 +136,7 @@ export const commands = commandList({ handler({ sender, outputSuccess }) { sender.manualAfk = !sender.manualAfk; sender.updateName(); - if(sender.manualAfk) outputSuccess(`You are now marked as AFK.`); + if (sender.manualAfk) outputSuccess(`You are now marked as AFK.`); else outputSuccess(`You are no longer marked as AFK.`); }, }, @@ -144,29 +144,29 @@ export const commands = commandList({ args: ['target:player?'], description: `Toggles visibility of your rank and flags.`, perm: Perm.vanish, - handler({ args, sender, outputSuccess }){ - if(sender.stelled()) fail(`Marked players may not hide flags.`); - if(sender.muted) fail(`Muted players may not hide flags.`); + handler({ args, sender, outputSuccess }) { + if (sender.stelled()) fail(`Marked players may not hide flags.`); + if (sender.muted) fail(`Muted players may not hide flags.`); args.target ??= sender; - if(sender != args.target && args.target.hasPerm("blockTrolling")) fail(`Target is insufficentlly trollable.`); - if(sender != args.target && !sender.ranksAtLeast("mod")) fail(`You do not have permission to vanish other players.`); + if (sender != args.target && args.target.hasPerm("blockTrolling")) fail(`Target is insufficentlly trollable.`); + if (sender != args.target && !sender.ranksAtLeast("mod")) fail(`You do not have permission to vanish other players.`); args.target.showRankPrefix = !args.target.showRankPrefix; outputSuccess( -`${args.target == sender ? `Your` : `${args.target.name}'s`} rank prefix is now ${args.target.showRankPrefix ? "visible" : "hidden"}.` + `${args.target == sender ? `Your` : `${args.target.name}'s`} rank prefix is now ${args.target.showRankPrefix ? "visible" : "hidden"}.` ); }, }, - + tileid: { args: [], description: 'Checks id of a tile.', perm: Perm.none, - handler({output, handleTaps}){ + handler({ output, handleTaps }) { handleTaps("once"); output(`Click a tile to see its id...`); }, - tapped({output, f, tile}){ + tapped({ output, f, tile }) { output(f`ID is ${tile.block().id}`); } }, @@ -191,11 +191,11 @@ export const commands = commandList({ args: ["server:string", "target:player?"], description: "Switches to another server.", perm: Perm.play, - handler({args, sender, f}){ - if(args.target != null && args.target != sender && !sender.canModerate(args.target, true, "admin", true)) + handler({ args, sender, f }) { + if (args.target != null && args.target != sender && !sender.canModerate(args.target, true, "admin", true)) fail(f`You do not have permission to switch player ${args.target}.`); const target = args.target ?? sender; - if(ipPortPattern.test(args.server) && sender.hasPerm("admin")){ + if (ipPortPattern.test(args.server) && sender.hasPerm("admin")) { //direct connect Call.connect(target.con, ...args.server.split(":")); } else { @@ -204,10 +204,10 @@ export const commands = commandList({ ?? fail(unknownServerMessage); //Pretend the server doesn't exist - if(server.requiredPerm && !sender.hasPerm(server.requiredPerm)) + if (server.requiredPerm && !sender.hasPerm(server.requiredPerm)) fail(unknownServerMessage); - if(target == sender) + if (target == sender) FishPlayer.messageAllWithPerm(server.requiredPerm, `${sender.name}[magenta] has gone to the ${server.name} server. Use [cyan]/${server.name} [magenta]to join them!`); Call.connect(target.con, server.ip, server.port); @@ -219,17 +219,17 @@ export const commands = commandList({ args: ['message:string'], description: `Sends a message to staff only.`, perm: Perm.chat, - handler({ sender, args, outputSuccess, outputFail, lastUsedSender }){ - if(!sender.hasPerm("mod")){ - if(Date.now() - lastUsedSender < 4000) fail(`This command was used recently and is on cooldown. [orange]Misuse of this command may result in a mute.`); + handler({ sender, args, outputSuccess, outputFail, lastUsedSender }) { + if (!sender.hasPerm("mod")) { + if (Date.now() - lastUsedSender < 4000) fail(`This command was used recently and is on cooldown. [orange]Misuse of this command may result in a mute.`); } api.sendStaffMessage(args.message, sender.name, (sent) => { - if(!sender.hasPerm("mod")){ - if(sent){ + if (!sender.hasPerm("mod")) { + if (sent) { outputSuccess(`Message sent to [orange]all online staff.`); } else { const wasReceived = FishPlayer.messageStaff(sender.prefixedName, args.message); - if(wasReceived) outputSuccess(`Message sent to staff.`); + if (wasReceived) outputSuccess(`Message sent to staff.`); else outputFail(`No staff were online to receive your message.`); } } @@ -250,16 +250,16 @@ export const commands = commandList({ description: `Watch/unwatch a player.`, perm: Perm.none, handler({ args, sender, outputSuccess, outputFail }) { - if(sender.watch){ + if (sender.watch) { outputSuccess(`No longer watching a player.`); sender.watch = false; - } else if(args.player){ + } else if (args.player) { sender.watch = true; const stayX = sender.unit().x; const stayY = sender.unit().y; const target = args.player.player!; const watch = () => { - if(sender.watch){ + if (sender.watch) { // Self.X+(172.5-Self.X)/10 Call.setCameraPosition(sender.con, target.unit().x, target.unit().y); sender.unit().set(stayX, stayY); @@ -279,30 +279,30 @@ export const commands = commandList({ //TODO revise code /** Mapping between player and original team */ const spectators = new Map(); - function spectate(target:FishPlayer){ + function spectate(target: FishPlayer) { spectators.set(target, target.team()); target.forceRespawn(); target.setTeam(Team.derelict); target.forceRespawn(); } - function resume(target:FishPlayer){ - if(spectators.get(target) == null) return; // this state is possible for a person who left not in spectate + function resume(target: FishPlayer) { + if (spectators.get(target) == null) return; // this state is possible for a person who left not in spectate target.setTeam(spectators.get(target)!); spectators.delete(target); target.forceRespawn(); } Events.on(EventType.GameOverEvent, () => spectators.clear()); - Events.on(EventType.PlayerLeave, ({player}:{player:mindustryPlayer}) => resume(FishPlayer.get(player))); + Events.on(EventType.PlayerLeave, ({ player }: { player: mindustryPlayer }) => resume(FishPlayer.get(player))); return { args: ["target:player?"], description: `Toggles spectator mode in PVP games.`, perm: Perm.play, - handler({args, sender, outputSuccess, f}){ + handler({ args, sender, outputSuccess, f }) { args.target ??= sender; - if(!Gamemode.pvp() && !sender.hasPerm("mod")) fail(`You do not have permission to spectate on a non-pvp server.`); - if(args.target !== sender && args.target.hasPerm("blockTrolling")) fail(`Target player is insufficiently trollable.`); - if(args.target !== sender && !sender.ranksAtLeast("admin")) fail(`You do not have permission to force other players to spectate.`); - if(spectators.has(args.target)){ + if (!Gamemode.pvp() && !sender.hasPerm("mod")) fail(`You do not have permission to spectate on a non-pvp server.`); + if (args.target !== sender && args.target.hasPerm("blockTrolling")) fail(`Target player is insufficiently trollable.`); + if (args.target !== sender && !sender.ranksAtLeast("admin")) fail(`You do not have permission to force other players to spectate.`); + if (spectators.has(args.target)) { resume(args.target); outputSuccess(args.target == sender ? f`Rejoining game as team ${args.target.team()}.` @@ -313,7 +313,7 @@ export const commands = commandList({ outputSuccess(args.target == sender ? f`Now spectating. Run /spectate again to resume gameplay.` : f`Forced ${args.target} into spectator mode.`) - ; + ; } } }; @@ -334,7 +334,7 @@ export const commands = commandList({ //name is not a number or a category, therefore it is probably a command name if (args.name in allCommands && (!allCommands[args.name].isHidden || allCommands[args.name].perm.check(sender))) { output( -`Help for command ${args.name}: + `Help for command ${args.name}: ${allCommands[args.name].description} Usage: [sky]/${args.name} [white]${allCommands[args.name].args.map(formatArg).join(' ')} Permission required: ${allCommands[args.name].perm.name}` @@ -396,7 +396,7 @@ export const commands = commandList({ perm: Perm.chat, handler({ args, sender, output, f }) { const recipient = FishPlayer.getById(recentWhispers[sender.uuid] ?? fail(`It doesn't look like someone has messaged you recently. Try whispering to them with [white]"/msg "`)); - if(!(recipient?.connected())) fail(`The person who last messaged you doesn't seem to exist anymore. Try whispering to someone with [white]"/msg "`); + if (!(recipient?.connected())) fail(`The person who last messaged you doesn't seem to exist anymore. Try whispering to someone with [white]"/msg "`); recentWhispers[recentWhispers[sender.uuid]] = sender.uuid; recipient.sendMessage(`${sender.name}[lightgray] whispered:[#BBBBBB] ${args.message}`); output(f`[#BBBBBB]Message sent to ${recipient}[#BBBBBB].`); @@ -409,8 +409,8 @@ export const commands = commandList({ perm: Perm.none, handler({ args, sender, output, outputFail, outputSuccess }) { //overload 1: type not specified - if(!args.type){ - if(sender.trail != null){ + if (!args.type) { + if (sender.trail != null) { sender.trail = null; outputSuccess(`Trail turned off.`); } else { @@ -441,8 +441,8 @@ Available types:[yellow] }; const selectedType = trailTypes[args.type as keyof typeof trailTypes] as string | undefined; - if(!selectedType){ - if(Object.values(trailTypes).includes(args.type)) fail(`Please use the numeric id to refer to a trail type.`); + if (!selectedType) { + if (Object.values(trailTypes).includes(args.type)) fail(`Please use the numeric id to refer to a trail type.`); else fail(`"${args.type}" is not an available type.`); } @@ -454,7 +454,7 @@ Available types:[yellow] }; } else { outputFail( -`[scarlet]Sorry, "${args.color}" is not a valid color. + `[scarlet]Sorry, "${args.color}" is not a valid color. [yellow]Color can be in the following formats: [pink]pink [white]| [gray]#696969 [white]| 255,0,0.` ); @@ -466,11 +466,11 @@ Available types:[yellow] args: [], description: 'Spawns an ohno.', perm: Perm.play, - init(){ + init() { const Ohnos = { enabled: true, ohnos: new Array(), - makeOhno(team:Team, x:number, y:number){ + makeOhno(team: Team, x: number, y: number) { const ohno = UnitTypes.atrax.create(team); ohno.set(x, y); ohno.type = UnitTypes.alpha; @@ -480,14 +480,14 @@ Available types:[yellow] this.ohnos.push(ohno); return ohno; }, - updateLength(){ + updateLength() { this.ohnos = this.ohnos.filter(o => o && o.isAdded() && !o.dead); }, - killAll(){ + killAll() { this.ohnos.forEach(ohno => ohno?.kill?.()); this.ohnos = []; }, - amount(){ + amount() { return this.ohnos.length; }, }; @@ -497,17 +497,17 @@ Available types:[yellow] return Ohnos; }, requirements: [Req.gameRunning, Req.modeNot("pvp")], - handler({sender, data:Ohnos}){ - if(!Ohnos.enabled) fail(`Ohnos have been temporarily disabled.`); - if(!(sender.connected() && sender.unit().added && !sender.unit().dead)) fail(`You cannot spawn ohnos while dead.`); + handler({ sender, data: Ohnos }) { + if (!Ohnos.enabled) fail(`Ohnos have been temporarily disabled.`); + if (!(sender.connected() && sender.unit().added && !sender.unit().dead)) fail(`You cannot spawn ohnos while dead.`); Ohnos.updateLength(); - if( + if ( Ohnos.ohnos.length >= (Groups.player.size() + 1) || sender.team().data().countType(UnitTypes.alpha) >= Units.getCap(sender.team()) ) fail(`Sorry, the max number of ohno units has been reached.`); - if(nearbyEnemyTile(sender.unit(), 6) != null) fail(`Too close to an enemy tile!`); - if(!UnitTypes.alpha.supportsEnv(Vars.state.rules.env)) fail(`Ohnos cannot survive in this map.`); - + if (nearbyEnemyTile(sender.unit(), 6) != null) fail(`Too close to an enemy tile!`); + if (!UnitTypes.alpha.supportsEnv(Vars.state.rules.env)) fail(`Ohnos cannot survive in this map.`); + Ohnos.makeOhno(sender.team(), sender.player!.x, sender.player!.y); }, }), @@ -516,12 +516,12 @@ Available types:[yellow] args: [], description: 'Displays information about all ranks.', perm: Perm.none, - handler({ output }){ + handler({ output }) { output( `List of ranks:\n` + - Object.values(Rank.ranks) - .map((rank) => `${rank.prefix} ${rank.color}${capitalizeText(rank.name)}[]: ${rank.color}${rank.description}[]\n`) - .join("") + + Object.values(Rank.ranks) + .map((rank) => `${rank.prefix} ${rank.color}${capitalizeText(rank.name)}[]: ${rank.color}${rank.description}[]\n`) + .join("") + `List of flags:\n` + Object.values(RoleFlag.flags) .map((flag) => `${flag.prefix} ${flag.color}${capitalizeText(flag.name)}[]: ${flag.color}${flag.description}[]\n`) @@ -534,20 +534,20 @@ Available types:[yellow] args: ['player:player?'], description: 'Displays the server rules.', perm: Perm.none, - handler({args, sender, outputSuccess, f}){ + handler({ args, sender, outputSuccess, f }) { const target = args.player ?? sender; - if(target !== sender){ - if(!sender.hasPerm("warn")) fail(`You do not have permission to show rules to other players.`); - if(target.hasPerm("blockTrolling")) fail(f`Player ${args.player!} is insufficiently trollable.`); + if (target !== sender) { + if (!sender.hasPerm("warn")) fail(`You do not have permission to show rules to other players.`); + if (target.hasPerm("blockTrolling")) fail(f`Player ${args.player!} is insufficiently trollable.`); } menu( "Rules for [#0000ff]>|||> FISH [white]servers", rules.join("\n\n"), [new GUI_Container(["[green]I agree to abide by these rules[]", "No"])], target, - ({text}) => { - if(text == "No") target.kick("You must agree to the rules to play on this server. Rejoin to agree to the rules.", 1); + ({ text }) => { + if (text == "No") target.kick("You must agree to the rules to play on this server. Rejoin to agree to the rules.", 1); } ); - if(target !== sender) outputSuccess(f`Reminded ${target} of the rules.`); + if (target !== sender) outputSuccess(f`Reminded ${target} of the rules.`); }, }, @@ -556,13 +556,13 @@ Available types:[yellow] description: 'Warns other players about power voids.', perm: Perm.play, requirements: [Req.mode("attack")], - handler({args, sender, lastUsedSuccessfullySender, lastUsedSuccessfully, outputSuccess, f}){ - if(args.player){ - if(Date.now() - lastUsedSuccessfullySender < 20000) fail(`This command was used recently and is on cooldown.`); - if(!sender.hasPerm("trusted")) fail(`You do not have permission to show popups to other players, please run /void with no arguments to send a chat message to everyone.`); - if(args.player !== sender && args.player.hasPerm("blockTrolling")) fail(`Target player is insufficiently trollable.`); + handler({ args, sender, lastUsedSuccessfullySender, lastUsedSuccessfully, outputSuccess, f }) { + if (args.player) { + if (Date.now() - lastUsedSuccessfullySender < 20000) fail(`This command was used recently and is on cooldown.`); + if (!sender.hasPerm("trusted")) fail(`You do not have permission to show popups to other players, please run /void with no arguments to send a chat message to everyone.`); + if (args.player !== sender && args.player.hasPerm("blockTrolling")) fail(`Target player is insufficiently trollable.`); menu("\uf83f [scarlet]WARNING[] \uf83f", -`[white]Don't break the Power Void (\uf83f), it's a trap! + `[white]Don't break the Power Void (\uf83f), it's a trap! Power voids disable anything they are connected to. If you break it, [scarlet]you will get attacked[] by enemy units. Please stop attacking and [lime]build defenses[] first!`, @@ -571,9 +571,9 @@ Please stop attacking and [lime]build defenses[] first!`, logAction("showed void warning", sender, args.player); outputSuccess(f`Warned ${args.player} about power voids with a popup message.`); } else { - if(Date.now() - lastUsedSuccessfully < 10000) fail(`This command was used recently and is on cooldown.`); + if (Date.now() - lastUsedSuccessfully < 10000) fail(`This command was used recently and is on cooldown.`); Call.sendMessage( -`[white]Don't break the Power Void (\uf83f), it's a trap! + `[white]Don't break the Power Void (\uf83f), it's a trap! Power voids disable anything they are connected to. If you break it, [scarlet]you will get attacked[] by enemy units. Please stop attacking and [lime]build defenses[] first!` ); @@ -585,18 +585,18 @@ Please stop attacking and [lime]build defenses[] first!` args: ['team:team', 'target:player?'], description: 'Changes the team of a player.', perm: Perm.changeTeam, - handler({args, sender, outputSuccess, f}){ + handler({ args, sender, outputSuccess, f }) { args.target ??= sender; - if(!sender.canModerate(args.target, true, "mod", true)) fail(f`You do not have permission to change the team of ${args.target}`); - if(Gamemode.sandbox() && fishState.peacefulMode && !sender.hasPerm("admin")) fail(`You do not have permission to change teams because peaceful mode is on.`); - if(!sender.hasPerm("changeTeamExternal")){ - if(args.team.data().cores.size <= 0) fail(`You do not have permission to change to a team with no cores.`); - if(!sender.player!.dead() && !sender.unit()?.spawnedByCore) + if (!sender.canModerate(args.target, true, "mod", true)) fail(f`You do not have permission to change the team of ${args.target}`); + if (Gamemode.sandbox() && fishState.peacefulMode && !sender.hasPerm("admin")) fail(`You do not have permission to change teams because peaceful mode is on.`); + if (!sender.hasPerm("changeTeamExternal")) { + if (args.team.data().cores.size <= 0) fail(`You do not have permission to change to a team with no cores.`); + if (!sender.player!.dead() && !sender.unit()?.spawnedByCore) args.target.forceRespawn(); } - if(!sender.hasPerm("mod")) args.target.changedTeam = true; + if (!sender.hasPerm("mod")) args.target.changedTeam = true; args.target.setTeam(args.team); - if(args.target === sender) outputSuccess(f`Changed your team to ${args.team}.`); + if (args.target === sender) outputSuccess(f`Changed your team to ${args.team}.`); else outputSuccess(f`Changed team of player ${args.target} to ${args.team}.`); }, }, @@ -605,23 +605,23 @@ Please stop attacking and [lime]build defenses[] first!` args: ['player:player'], description: 'Displays the rank of a player.', perm: Perm.none, - handler({args, output, f}) { + handler({ args, output, f }) { output(f`Player ${args.player}'s rank is ${args.player.rank}.`); }, }, - + forcevnw: { args: ["force:boolean?"], description: 'Force skip to the next wave.', perm: Perm.admin, - handler({allCommands, sender, args:{force}}){ + handler({ allCommands, sender, args: { force } }) { force ??= true; - if(allCommands.vnw.data.manager.session == null){ - if(force == false) fail(`Cannot clear votes for VNW because no vote is currently ongoing.`); + if (allCommands.vnw.data.manager.session == null) { + if (force == false) fail(`Cannot clear votes for VNW because no vote is currently ongoing.`); skipWaves(1, false); } else { - if(force) Call.sendMessage(`VNW: [green]Vote was forced by admin [yellow]${sender.name}[green], skipping wave.`); + if (force) Call.sendMessage(`VNW: [green]Vote was forced by admin [yellow]${sender.name}[green], skipping wave.`); else Call.sendMessage(`VNW: [red]Votes cleared by admin [yellow]${sender.name}[red].`); allCommands.vnw.data.manager.forceVote(force); } @@ -641,18 +641,18 @@ Please stop attacking and [lime]build defenses[] first!` .on("player vote removed", (t, player) => Call.sendMessage(`VNW: ${player.name} [white] has left. [green]${t.currentVotes()}[white] votes, [green]${t.requiredVotes()}[white] required.`)) }), requirements: [Req.cooldown(3000), Req.mode("survival"), Req.gameRunning], - handler({sender, data:{manager}}){ + handler({ sender, data: { manager } }) { - if(!manager.session){ + if (!manager.session) { menu( "Start a Next Wave Vote", "Select the amount of waves you would like to skip, or click \"Cancel\" to abort.", [new GUI_Container([1, 5, 10], "auto", n => `${n} waves`), new GUI_Cancel()], sender, - ({data}) => { - if(manager.session){ + ({ data }) => { + if (manager.session) { //Someone else started a vote - if(manager.session.data != data) fail(`Someone else started a vote with a different number of waves to skip.`); + if (manager.session.data != data) fail(`Someone else started a vote with a different number of waves to skip.`); else manager.vote(sender, sender.voteWeight(), data); } else { //this is still a race condition technically... shouldn't be that bad right? @@ -663,20 +663,20 @@ Please stop attacking and [lime]build defenses[] first!` } else { manager.vote(sender, sender.voteWeight(), null); } - } + } }), forcertv: { args: ["force:boolean?"], description: 'Force skip to the next map.', perm: Perm.admin, - handler({args:{force}, sender, allCommands}){ + handler({ args: { force }, sender, allCommands }) { force ??= true; - if(allCommands.rtv.data.manager.session == null){ - if(force == false) fail(`Cannot clear votes for RTV because no vote is currently ongoing.`); + if (allCommands.rtv.data.manager.session == null) { + if (force == false) fail(`Cannot clear votes for RTV because no vote is currently ongoing.`); allCommands.rtv.data.manager.forceVote(true); } else { - if(force) Call.sendMessage(`RTV: [green]Vote was forced by admin [yellow]${sender.name}[green].`); + if (force) Call.sendMessage(`RTV: [green]Vote was forced by admin [yellow]${sender.name}[green].`); else Call.sendMessage(`RTV: [red]Votes cleared by admin [yellow]${sender.name}[red].`); allCommands.rtv.data.manager.forceVote(force); } @@ -696,7 +696,7 @@ Please stop attacking and [lime]build defenses[] first!` .on("player vote removed", (t, player) => Call.sendMessage(`RTV: ${player.name}[white] has left the game. [green]${t.currentVotes()}[white] votes, [green]${t.requiredVotes()}[white] required.`)) }), requirements: [Req.cooldown(3000), Req.gameRunning], - handler({sender, data:{manager}}){ + handler({ sender, data: { manager } }) { manager.vote(sender, 1, 0); //No weighting for RTV except for removing AFK players } }), @@ -728,9 +728,9 @@ Please stop attacking and [lime]build defenses[] first!` description: 'Override the next map in queue.', perm: Perm.admin, requirements: [Req.modeNot("hexed")], - handler({allCommands, args, sender, outputSuccess, f}){ + handler({ allCommands, args, sender, outputSuccess, f }) { Vars.maps.setNextMapOverride(args.map); - if(allCommands.nextmap.data.voteEndTime() > -1){ + if (allCommands.nextmap.data.voteEndTime() > -1) { //Cancel /nextmap vote if it's ongoing allCommands.nextmap.data.resetVotes(); Call.sendMessage(`[red]Admin ${sender.name}[red] has cancelled the vote. The next map will be [yellow]${args.map.name()}.`); @@ -745,15 +745,15 @@ Please stop attacking and [lime]build defenses[] first!` args: [], description: 'Lists the available maps.', perm: Perm.none, - handler({output}){ + handler({ output }) { output(`\ [yellow]Use [white]/nextmap [lightgray] [yellow]to vote on a map. [blue]Available maps: _________________________ ${Vars.maps.customMaps().toArray().map((map, i) => -`[yellow]${map.name()}` -).join("\n")}` + `[yellow]${map.name()}` + ).join("\n")}` ); } }, @@ -766,7 +766,7 @@ ${Vars.maps.customMaps().toArray().map((map, i) => const voteDuration = 1.5 * 60000; // 1.5 mins let task: TimerTask | null = null; - function resetVotes(){ + function resetVotes() { votes.clear(); voteEndTime = -1; task?.cancel(); @@ -774,32 +774,32 @@ ${Vars.maps.customMaps().toArray().map((map, i) => lastVoteTime = 1; } - function getMapData():Seq> { + function getMapData(): Seq> { return [...votes.values()].reduce( (acc, map) => (acc.increment(map), acc), new ObjectIntMap() ).entries().toArray(); } - function showVotes(){ + function showVotes() { Call.sendMessage(`\ [green]Current votes: ------------------------------ -${getMapData().map(({key:map, value:votes}) => -`[cyan]${map.name()}[yellow]: ${votes}` -).toString("\n")}` +${getMapData().map(({ key: map, value: votes }) => + `[cyan]${map.name()}[yellow]: ${votes}` + ).toString("\n")}` ); } - function startVote(){ + function startVote() { voteEndTime = Date.now() + voteDuration; task = Timer.schedule(endVote, voteDuration / 1000); } - function endVote(){ - if(voteEndTime == -1) return; //aborted somehow - if(votes.size == 0) return; //no votes? + function endVote() { + if (voteEndTime == -1) return; //aborted somehow + if (votes.size == 0) return; //no votes? - if((votes.size / Groups.player.size()) + 0.2 < lastVoteTurnout){ + if ((votes.size / Groups.player.size()) + 0.2 < lastVoteTurnout) { Call.sendMessage("[cyan]Next Map Vote: [scarlet]Vote aborted because a previous vote had significantly higher turnout"); resetVotes(); return; @@ -811,15 +811,15 @@ ${getMapData().map(({key:map, value:votes}) => const mapData = getMapData(); const highestVoteCount = mapData.max(floatf(e => e.value)).value; const highestVotedMaps = mapData.select(e => e.value == highestVoteCount); - let winner:MMap; + let winner: MMap; - if(highestVotedMaps.size > 1){ + if (highestVotedMaps.size > 1) { winner = highestVotedMaps.random()!.key; Call.sendMessage( -`[green]There was a tie between the following maps: -${highestVotedMaps.map(({key:map, value:votes}) => -`[cyan]${map.name()}[yellow]: ${votes}` -).toString("\n")} + `[green]There was a tie between the following maps: +${highestVotedMaps.map(({ key: map, value: votes }) => + `[cyan]${map.name()}[yellow]: ${votes}` + ).toString("\n")} [green]Picking random winner: [yellow]${winner.name()}` ); } else { @@ -837,23 +837,23 @@ ${highestVotedMaps.map(({key:map, value:votes}) => args: ['map:map?'], description: 'Allows you to vote for the next map. Use /maps to see all available maps.', perm: Perm.play, - data: {votes, voteEndTime: () => voteEndTime, resetVotes, endVote}, + data: { votes, voteEndTime: () => voteEndTime, resetVotes, endVote }, requirements: [Req.cooldown(10000), Req.modeNot("hexed")], - handler({args:{map}, sender}){ - if(!map){ - listMenu("Please Select a Map","", new GUI_Container(Vars.maps.customMaps().toArray(),1,(map:MMap) => {return `[accent]${map.name()}`}), sender, ({data}) => { + handler({ args: { map }, sender }) { + if (!map) { + listMenu("Please Select a Map", "", new GUI_Container(Vars.maps.customMaps().toArray(), 1, (map: MMap) => { return `[accent]${map.name()}` }), sender, ({ data }) => { playervote(data); }) - }else{ - playervote(map) + } else { + playervote(map); } - function playervote(option:MMap){ - if(votes.get(sender)) fail(`You have already voted.`); + function playervote(option: MMap) { + if (votes.get(sender)) fail(`You have already voted.`); votes.set(sender, option); - if(voteEndTime == -1){ - if((Date.now() - lastVoteTime) < 60_000) fail(`Please wait 1 minute before starting a new map vote.`); + if (voteEndTime == -1) { + if ((Date.now() - lastVoteTime) < 60_000) fail(`Please wait 1 minute before starting a new map vote.`); startVote(); Call.sendMessage(`[cyan]Next Map Vote: ${sender.name}[cyan] started a map vote, and voted for [yellow]${option.name()}[cyan]. Use /nextmap ${option.plainName()} to add your vote!`); } else { @@ -867,7 +867,7 @@ ${highestVotedMaps.map(({key:map, value:votes}) => surrender: command(() => { const prefix = "[orange]Surrender[white]: "; const managers = Team.all.map(team => - new VoteManager(1.5 * 60_000, Gamemode.hexed() ? 1 : 3/4, p => p.team() == team && !p.afk()) + new VoteManager(1.5 * 60_000, Gamemode.hexed() ? 1 : 3 / 4, p => p.team() == team && !p.afk()) .on("success", () => team.cores().copy().each(c => c.kill())) .on("vote passed", () => Call.sendMessage( prefix + `Team ${team.coloredName()} has voted to forfeit this match.` @@ -893,7 +893,7 @@ ${highestVotedMaps.map(({key:map, value:votes}) => perm: Perm.play, requirements: [Req.cooldown(30_000), Req.mode("pvp"), Req.teamAlive], data: { managers }, - handler({ sender }){ + handler({ sender }) { managers[sender.team().id].vote(sender, 1, 0); }, }; @@ -902,7 +902,7 @@ ${highestVotedMaps.map(({key:map, value:votes}) => args: ["target:player"], perm: Perm.none, description: "Views a player's stats.", - handler({args:{target}, output, f}){ + handler({ args: { target }, output, f }) { output(f`[accent]\ Statistics for player ${target}: (note: we started recording statistics on 22 Jan 2024) @@ -916,16 +916,16 @@ Win rate: ${target.stats.gamesWon / target.stats.gamesFinished}` ); } }, - - debug:{ + + debug: { args: [], description: "mcdebug, do not add to pr", - perm:Perm.none, - handler({sender}){ + perm: Perm.none, + handler({ sender }) { //menu("debug page menu", "", [new GUI_Page(1,2)], sender) //pageMenu("Multi Page Menu", "", [[new GUI_Container(["1", "2", "3", "4"], 1)], [new GUI_Container(["5", "6", "7", "8"],1)], [new GUI_Confirm()]], sender, ({data}) =>{Log.info(`Button ${data} Pressed`)}); - listMenu("listmenu", "", new GUI_Container(["option1","option2","option3","option4","option5","option6","option7","option8","option9","option10","option11","option12","option13","option14","option15","option16","option17","option18","option19","option20",]),sender,({data}) =>{Log.info(`Button ${data} Pressed`)}) + listMenu("listmenu", "", new GUI_Container(["option1", "option2", "option3", "option4", "option5", "option6", "option7", "option8", "option9", "option10", "option11", "option12", "option13", "option14", "option15", "option16", "option17", "option18", "option19", "option20",]), sender, ({ data }) => { Log.info(`Button ${data} Pressed`) }) } }, - + }); From 168fa64c07ebeda58752019e71fab369a86dfd41 Mon Sep 17 00:00:00 2001 From: juror #9 <64419198+Jurorno9@users.noreply.github.com> Date: Sat, 25 Jan 2025 14:46:28 -0500 Subject: [PATCH 6/9] removing debug func --- build/scripts/playerCommands.js | 13 ------------- src/playerCommands.ts | 14 +------------- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/build/scripts/playerCommands.js b/build/scripts/playerCommands.js index f1d78bf..099d10b 100644 --- a/build/scripts/playerCommands.js +++ b/build/scripts/playerCommands.js @@ -898,18 +898,5 @@ exports.commands = (0, commands_1.commandList)(__assign(__assign({ about: { var target = _a.args.target, output = _a.output, f = _a.f; output(f(templateObject_17 || (templateObject_17 = __makeTemplateObject(["[accent]Statistics for player ", ":\n(note: we started recording statistics on 22 Jan 2024)\n[white]--------------[]\nBlocks broken: ", "\nBlocks placed: ", "\nChat messages sent: ", "\nGames finished: ", "\nTime in-game: ", "\nWin rate: ", ""], ["[accent]\\\nStatistics for player ", ":\n(note: we started recording statistics on 22 Jan 2024)\n[white]--------------[]\nBlocks broken: ", "\nBlocks placed: ", "\nChat messages sent: ", "\nGames finished: ", "\nTime in-game: ", "\nWin rate: ", ""])), target, target.stats.blocksBroken, target.stats.blocksPlaced, target.stats.chatMessagesSent, target.stats.gamesFinished, (0, utils_1.formatTime)(target.stats.timeInGame), target.stats.gamesWon / target.stats.gamesFinished)); } - }, debug: { - args: [], - description: "mcdebug, do not add to pr", - perm: commands_1.Perm.none, - handler: function (_a) { - var sender = _a.sender; - //menu("debug page menu", "", [new GUI_Page(1,2)], sender) - //pageMenu("Multi Page Menu", "", [[new GUI_Container(["1", "2", "3", "4"], 1)], [new GUI_Container(["5", "6", "7", "8"],1)], [new GUI_Confirm()]], sender, ({data}) =>{Log.info(`Button ${data} Pressed`)}); - (0, menus_1.listMenu)("listmenu", "", new menus_1.GUI_Container(["option1", "option2", "option3", "option4", "option5", "option6", "option7", "option8", "option9", "option10", "option11", "option12", "option13", "option14", "option15", "option16", "option17", "option18", "option19", "option20",]), sender, function (_a) { - var data = _a.data; - Log.info("Button ".concat(data, " Pressed")); - }); - } } })); var templateObject_1, templateObject_2, templateObject_3, templateObject_4, templateObject_5, templateObject_6, templateObject_7, templateObject_8, templateObject_9, templateObject_10, templateObject_11, templateObject_12, templateObject_13, templateObject_14, templateObject_15, templateObject_16, templateObject_17; diff --git a/src/playerCommands.ts b/src/playerCommands.ts index 9758a85..e650f31 100644 --- a/src/playerCommands.ts +++ b/src/playerCommands.ts @@ -7,7 +7,7 @@ import * as api from './api'; import { command, commandList, fail, formatArg, Perm, Req } from './commands'; import { FishServer, Gamemode, rules, text } from './config'; import { FishEvents, fishState, fishPlugin, ipPortPattern, recentWhispers, tileHistory, uuidPattern } from './globals'; -import { GUI_Cancel, GUI_Confirm, GUI_Container, GUI_Page, listMenu, menu, pageMenu } from './menus'; +import { GUI_Cancel, GUI_Container, listMenu, menu } from './menus'; import { FishPlayer } from './players'; import { Rank, RoleFlag } from './ranks'; import type { FishCommandData } from './types'; @@ -916,16 +916,4 @@ Win rate: ${target.stats.gamesWon / target.stats.gamesFinished}` ); } }, - - debug: { - args: [], - description: "mcdebug, do not add to pr", - perm: Perm.none, - handler({ sender }) { - //menu("debug page menu", "", [new GUI_Page(1,2)], sender) - //pageMenu("Multi Page Menu", "", [[new GUI_Container(["1", "2", "3", "4"], 1)], [new GUI_Container(["5", "6", "7", "8"],1)], [new GUI_Confirm()]], sender, ({data}) =>{Log.info(`Button ${data} Pressed`)}); - listMenu("listmenu", "", new GUI_Container(["option1", "option2", "option3", "option4", "option5", "option6", "option7", "option8", "option9", "option10", "option11", "option12", "option13", "option14", "option15", "option16", "option17", "option18", "option19", "option20",]), sender, ({ data }) => { Log.info(`Button ${data} Pressed`) }) - } - }, - }); From 6988841692ffffdce5745ba9433f3ead70b24383 Mon Sep 17 00:00:00 2001 From: juror #9 <64419198+Jurorno9@users.noreply.github.com> Date: Sat, 25 Jan 2025 15:55:06 -0500 Subject: [PATCH 7/9] fixed my node --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 35cf62c..99dcf49 100644 --- a/package.json +++ b/package.json @@ -20,5 +20,8 @@ "esbuild": "^0.23.1", "jasmine": "^5.5.0", "typescript": "^5.7.3" + }, + "directories": { + "doc": "docs" } } From c4a5a15d9ec52a16ab3a8b1f18fe6bb9ace44b20 Mon Sep 17 00:00:00 2001 From: juror #9 <64419198+Jurorno9@users.noreply.github.com> Date: Sat, 25 Jan 2025 17:58:53 -0500 Subject: [PATCH 8/9] pagemenu bugfix, player autofill dialog changed from menu to listmenu --- build/scripts/commands.js | 8 +++++++- build/scripts/menus.js | 4 ++-- src/commands.ts | 8 +++++++- src/menus.ts | 6 +++--- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/build/scripts/commands.js b/build/scripts/commands.js index 5415f89..a076cd5 100644 --- a/build/scripts/commands.js +++ b/build/scripts/commands.js @@ -805,7 +805,13 @@ function resolveArgsRecursive(processedArgs, unresolvedArgs, sender, callback) { break; default: (0, funcs_4.crash)("Unable to resolve arg of type ".concat(argToResolve_1.type)); } - (0, menus_1.menu)("Select a player", "Select a player for the argument \"".concat(argToResolve_1.name, "\""), [new menus_1.GUI_Container(optionsList_1, "auto", function (player) { return Strings.stripColors(player.name).length >= 3 ? Strings.stripColors(player.name) : (0, funcs_3.escapeStringColorsClient)(player.name); }), new menus_1.GUI_Cancel()], sender, function (_a) { + /* + menu(`Select a player`, `Select a player for the argument "${argToResolve.name}"`, [new GUI_Container(optionsList, "auto", player => Strings.stripColors(player.name).length >= 3 ? Strings.stripColors(player.name) : escapeStringColorsClient(player.name)), new GUI_Cancel()], sender, ({data}) => { + processedArgs[argToResolve.name] = FishPlayer.get(data); + resolveArgsRecursive(processedArgs, unresolvedArgs, sender, callback); + }, ) + */ + (0, menus_1.listMenu)("Select a player", "Select a player for the argument ".concat(argToResolve_1.name), new menus_1.GUI_Container(optionsList_1, "auto", function (player) { return player.name; }), sender, function (_a) { var data = _a.data; processedArgs[argToResolve_1.name] = players_1.FishPlayer.get(data); resolveArgsRecursive(processedArgs, unresolvedArgs, sender, callback); diff --git a/build/scripts/menus.js b/build/scripts/menus.js index 2cef2f8..f7024c3 100644 --- a/build/scripts/menus.js +++ b/build/scripts/menus.js @@ -179,7 +179,7 @@ function pageMenu(title, description, elements, target, callback) { } } else { - callback(res.data); + callback(res); } }); return; @@ -196,7 +196,7 @@ function listMenu(title, description, list, target, callback, pageSize) { } return res; }, []); var pagesElements = []; pagedData.forEach(function (pageData) { return pagesElements.push([new GUI_Container(pageData, 1, list.stringifier)]); }); - pageMenu(title, description, pagesElements, target, callback); + pageMenu(title, description, pagesElements, target, function (res) { Log.info("".concat(res.data)); callback(res); }); } var GUI_Container = /** @class */ (function () { function GUI_Container(options, columns, stringifier) { diff --git a/src/commands.ts b/src/commands.ts index a463e9f..556c3a9 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -6,7 +6,7 @@ This file contains the commands system. import { FColor, Gamemode, GamemodeName, text } from "./config"; import { ipPattern, uuidPattern } from "./globals"; -import { GUI_Cancel, GUI_Container, menu } from "./menus"; +import { GUI_Cancel, GUI_Container, listMenu, menu, pageMenu } from "./menus"; import { FishPlayer } from "./players"; import { Rank, RankName, RoleFlag } from "./ranks"; import type { ClientCommandHandler, CommandArg, FishCommandArgType, FishCommandData, FishCommandHandlerData, FishCommandHandlerUtils, FishConsoleCommandData, Formattable, PartialFormatString, SelectEnumClassKeys, ServerCommandHandler } from "./types"; @@ -676,10 +676,16 @@ function resolveArgsRecursive(processedArgs: Record, case "player": Groups.player.each(player => optionsList.push(player)); break; default: crash(`Unable to resolve arg of type ${argToResolve.type}`); } + /* menu(`Select a player`, `Select a player for the argument "${argToResolve.name}"`, [new GUI_Container(optionsList, "auto", player => Strings.stripColors(player.name).length >= 3 ? Strings.stripColors(player.name) : escapeStringColorsClient(player.name)), new GUI_Cancel()], sender, ({data}) => { processedArgs[argToResolve.name] = FishPlayer.get(data); resolveArgsRecursive(processedArgs, unresolvedArgs, sender, callback); }, ) + */ + listMenu(`Select a player`, `Select a player for the argument ${argToResolve.name}`,new GUI_Container(optionsList, "auto", (player:Player) => {return player.name}), sender, ({data}) => { + processedArgs[argToResolve.name] = FishPlayer.get(data); + resolveArgsRecursive(processedArgs, unresolvedArgs, sender, callback); + }) } diff --git a/src/menus.ts b/src/menus.ts index 610d19a..b06a147 100644 --- a/src/menus.ts +++ b/src/menus.ts @@ -145,7 +145,7 @@ export function pageMenu(title: string, description: string, elements: GUI_Eleme callback(res); } } else { - callback(res.data); + callback(res); } }) return; @@ -160,13 +160,13 @@ export function listMenu(title: string, description: string, list: GUI_Container let pagedData: any[][] = pooledData.reduce((res, _, index) => { if (index % pageSize === 0) { res.push(pooledData.slice(index, index + pageSize)); } return res; }, [] as any[][]); let pagesElements: GUI_Element[][] = []; pagedData.forEach(pageData => pagesElements.push([new GUI_Container(pageData, 1, list.stringifier)])); - pageMenu(title, description, pagesElements, target, callback); + pageMenu(title, description, pagesElements, target, (res) => {Log.info(`${res.data}`);callback(res)}) } //#endregion //#region GUI Elements interface GUI_Element { - format(): string[][], + format(): string[][], // honestly should have made this a 1d array for simplicity, but 2d lets you define multi-row elements data(): any[][] } export class GUI_Container implements GUI_Element { From 8a92a868b1b4972c59c977eee499d7de47c1c6b1 Mon Sep 17 00:00:00 2001 From: juror #9 <64419198+Jurorno9@users.noreply.github.com> Date: Sat, 25 Jan 2025 18:19:22 -0500 Subject: [PATCH 9/9] /stop offline menu now menulist, pagemenu empty arr support --- build/scripts/menus.js | 14 +++++++++----- build/scripts/staffCommands.js | 20 +++++++++++++++++--- src/menus.ts | 13 ++++++++----- src/staffCommands.ts | 18 ++++++++++++++++-- 4 files changed, 50 insertions(+), 15 deletions(-) diff --git a/build/scripts/menus.js b/build/scripts/menus.js index f7024c3..2d57e46 100644 --- a/build/scripts/menus.js +++ b/build/scripts/menus.js @@ -159,8 +159,13 @@ function pageMenu(title, description, elements, target, callback) { var pages = elements.length; function drawpage(index) { var e = []; - e.push.apply(e, __spreadArray([], __read(elements[index]), false)); - e.push(new GUI_Page(index + 1, pages)); + if (!pages) { + e.push(new GUI_Cancel()); + } + else { + e.push.apply(e, __spreadArray([], __read(elements[index]), false)); + e.push(new GUI_Page(index + 1, pages)); + } menu(title, description, e, target, function (res) { // handle control element of the ui if (typeof res.data === 'string') { @@ -176,17 +181,16 @@ function pageMenu(title, description, elements, target, callback) { break; default: callback(res); + break; } } - else { - callback(res); - } }); return; } drawpage(0); } //TODO make list a GUI_Element[] instead of a single Container +//TODO use GUI_Element for formatting instead of defaulting to single column function listMenu(title, description, list, target, callback, pageSize) { if (pageSize === void 0) { pageSize = 10; } var pooledData = []; diff --git a/build/scripts/staffCommands.js b/build/scripts/staffCommands.js index fe19208..5b96460 100644 --- a/build/scripts/staffCommands.js +++ b/build/scripts/staffCommands.js @@ -278,8 +278,8 @@ exports.commands = (0, commands_1.commandList)({ else { possiblePlayers = players_1.FishPlayer.recentLeaves.map(function (p) { return p.info(); }); } - (0, menus_1.menu)("Stop", "Choose a player to mark", [new menus_1.GUI_Container(possiblePlayers, "auto", function (p) { return p.lastName; }), new menus_1.GUI_Cancel()], sender, function (_a) { - var optionPlayer = _a.data, sender = _a.sender; + (0, menus_1.listMenu)("Stop", "Choose a player to mark", new menus_1.GUI_Container(possiblePlayers, "auto", function (p) { return p.lastName; }), sender, function (_a) { + var optionPlayer = _a.data; if (args.time == null) { (0, menus_1.menu)("Stop", "Select stop time", [new menus_1.GUI_Container(["2 days", "7 days", "30 days", "forever"])], sender, function (_a) { var optionTime = _a.text; @@ -468,7 +468,21 @@ exports.commands = (0, commands_1.commandList)({ return; } //Overload 3: ban by menu - (0, menus_1.menu)("[scarlet]BAN[]", "Choose a player to ban.", [new menus_1.GUI_Container((0, funcs_5.setToArray)(Groups.player), "auto", function (opt) { return opt.name; }), new menus_1.GUI_Cancel()], sender, function (_a) { + /* + menu(`[scarlet]BAN[]`, "Choose a player to ban.", [new GUI_Container(setToArray(Groups.player), "auto", opt => opt.name), new GUI_Cancel()], sender, ({data:target}) => { + if(target.admin) fail(`Cannot ban an admin.`); + menu("Confirm", `Are you sure you want to ban ${target.name}?`, [new GUI_Confirm()], sender, ({data:confirm}) => { + if(!confirm) fail("Cancelled."); + admins.banPlayerIP(target.ip()); //this also bans the UUID + api.ban({ip: target.ip(), uuid: target.uuid()}); + Log.info(`${target.ip()}/${target.uuid()} was banned.`); + logAction("banned", sender, target.getInfo()); + outputSuccess(f`Banned player ${target}.`); + updateBans(player => `[scarlet]Player [yellow]${player.name}[scarlet] has been whacked by ${sender.prefixedName}.`); + }); + }); + */ + (0, menus_1.listMenu)("[scarlet]BAN[]", "Choose a player to ban.", new menus_1.GUI_Container((0, funcs_5.setToArray)(Groups.player), "auto", function (opt) { return opt.name; }), sender, function (_a) { var target = _a.data; if (target.admin) (0, commands_1.fail)("Cannot ban an admin."); diff --git a/src/menus.ts b/src/menus.ts index b06a147..4c1e58c 100644 --- a/src/menus.ts +++ b/src/menus.ts @@ -126,8 +126,12 @@ export function pageMenu(title: string, description: string, elements: GUI_Eleme let pages = elements.length function drawpage(index: number) { let e: GUI_Element[] = []; - e.push(...elements[index]) - e.push(new GUI_Page(index + 1, pages)) + if(!pages){ + e.push(new GUI_Cancel()); + }else{ + e.push(...elements[index]) + e.push(new GUI_Page(index + 1, pages)) + } menu(title, description, e, target, (res) => { // handle control element of the ui if (typeof res.data === 'string') { @@ -143,17 +147,16 @@ export function pageMenu(title: string, description: string, elements: GUI_Eleme break; default: callback(res); + break; } - } else { - callback(res); } }) return; } drawpage(0); - } //TODO make list a GUI_Element[] instead of a single Container +//TODO use GUI_Element for formatting instead of defaulting to single column export function listMenu(title: string, description: string, list: GUI_Container, target: FishPlayer, callback: (opts: { data: any, text: string, sender: FishPlayer, outputSuccess: (message: string) => void, outputFail: (message: string) => void; }) => void, pageSize: number = 10) { let pooledData: any[] = []; list.data().flat().forEach((data) => { pooledData.push(data) }); diff --git a/src/staffCommands.ts b/src/staffCommands.ts index 8e8bbce..176c4e4 100644 --- a/src/staffCommands.ts +++ b/src/staffCommands.ts @@ -10,7 +10,7 @@ import { maxTime } from "./globals"; import { updateMaps } from "./files"; import * as fjsContext from "./fjsContext"; import { fishState, ipPattern, uuidPattern } from "./globals"; -import { GUI_Cancel, GUI_Confirm, GUI_Container, menu } from './menus'; +import { GUI_Cancel, GUI_Confirm, GUI_Container, listMenu, menu } from './menus'; import { FishPlayer } from "./players"; import { Rank } from "./ranks"; import { addToTileHistory, colorBadBoolean, formatTime, formatTimeRelative, getAntiBotInfo, logAction, match, serverRestartLoop, untilForever, updateBans } from "./utils"; @@ -223,7 +223,7 @@ export const commands = commandList({ } - menu("Stop", "Choose a player to mark", [new GUI_Container(possiblePlayers, "auto", p => p.lastName), new GUI_Cancel()], sender, ({data: optionPlayer, sender}) => { + listMenu("Stop", "Choose a player to mark", new GUI_Container(possiblePlayers, "auto", (p:PlayerInfo) => {return p.lastName}), sender, ({data: optionPlayer}) => { if(args.time == null){ menu("Stop", "Select stop time", [new GUI_Container(["2 days", "7 days", "30 days", "forever"])], sender, ({text: optionTime}) => { const time = @@ -406,6 +406,7 @@ export const commands = commandList({ return; } //Overload 3: ban by menu + /* menu(`[scarlet]BAN[]`, "Choose a player to ban.", [new GUI_Container(setToArray(Groups.player), "auto", opt => opt.name), new GUI_Cancel()], sender, ({data:target}) => { if(target.admin) fail(`Cannot ban an admin.`); menu("Confirm", `Are you sure you want to ban ${target.name}?`, [new GUI_Confirm()], sender, ({data:confirm}) => { @@ -418,6 +419,19 @@ export const commands = commandList({ updateBans(player => `[scarlet]Player [yellow]${player.name}[scarlet] has been whacked by ${sender.prefixedName}.`); }); }); + */ + listMenu(`[scarlet]BAN[]`, "Choose a player to ban.", new GUI_Container(setToArray(Groups.player), "auto", opt => opt.name), sender, ({data:target}) => { + if(target.admin) fail(`Cannot ban an admin.`); + menu("Confirm", `Are you sure you want to ban ${target.name}?`, [new GUI_Confirm()], sender, ({data:confirm}) => { + if(!confirm) fail("Cancelled."); + admins.banPlayerIP(target.ip()); //this also bans the UUID + api.ban({ip: target.ip(), uuid: target.uuid()}); + Log.info(`${target.ip()}/${target.uuid()} was banned.`); + logAction("banned", sender, target.getInfo()); + outputSuccess(f`Banned player ${target}.`); + updateBans(player => `[scarlet]Player [yellow]${player.name}[scarlet] has been whacked by ${sender.prefixedName}.`); + }); + }); } },