Improvements for ExpCommands

This commit is contained in:
Cooldude2606
2024-11-08 12:59:12 +00:00
parent c61c931b58
commit c9bf85835f
9 changed files with 142 additions and 137 deletions

View File

@@ -9,13 +9,14 @@ The default permission authorities controlled by the flags: admin_only, system_o
]]
local Storage = require("modules/exp_util/storage")
local Commands = require("modules/exp_commands")
local Commands = require("modules/exp_commands") --- @class Commands
local add, allow, deny = Commands.add_permission_authority, Commands.status.success, Commands.status.unauthorised
local authorities = {}
local system_players = {}
local disabled_commands = {}
local system_players = {} --- @type table<string, boolean>
local disabled_commands = {} --- @type table<string, boolean>
Storage.register({
system_players,
disabled_commands,
@@ -100,7 +101,7 @@ authorities.system_only =
--- If Commands.disable was called then no one can use the command
authorities.disabled =
add(function(_player, command)
add(function(player, command)
if disabled_commands[command.name] then
return deny{ "exp-commands-authorities.disabled" }
else

View File

@@ -1,6 +1,5 @@
--[[-- Command Module - Help
--[[-- Commands - Help
Game command to list and search all registered commands in a nice format
@commands _system-ipc
--- Get all messages related to banning a player
/commands ban
@@ -13,13 +12,16 @@ local Commands = require("modules/exp_commands")
local PAGE_SIZE = 5
local search_cache = {}
--- @alias ResultsPage LocalisedString[]
--- @class HelpCacheEntry: { keyword: string, pages: ResultsPage[], found: number }
local search_cache = {} --- @type table<number, HelpCacheEntry>
Storage.register(search_cache, function(tbl)
search_cache = tbl
end)
--- Format commands into a strings across multiple pages
--- @param commands { [string]: Commands.Command } The commands to split into pages
--- @param commands table<string, Commands.Command> The commands to split into pages
--- @param page_size number The number of requests to show per page
--- @return LocalisedString[][], number
local function format_as_pages(commands, page_size)

View File

@@ -1,6 +1,5 @@
--[[-- Command Module - IPC
--[[-- Commands - IPC
System command which sends an object to the clustorio api, should be used for debugging / echo commands
@commands _system-ipc
--- Send a message on your custom channel, message is a json string
/_ipc myChannel { "myProperty": "foo", "playerName": "Cooldude2606" }

View File

@@ -1,6 +1,5 @@
--[[-- Command Module - Rcon
--[[-- Commands - Rcon
System command which runs arbitrary code within a custom (not sandboxed) environment
@commands _system-rcon
--- Get the names of all online players, using rcon
/_system-rcon local names = {}; for index, player in pairs(game.connected_player) do names[index] = player.name end; return names;
@@ -12,53 +11,60 @@ System command which runs arbitrary code within a custom (not sandboxed) environ
local ExpUtil = require("modules/exp_util")
local Async = require("modules/exp_util/async")
local Storage = require("modules/exp_util/storage")
local Commands = require("modules/exp_commands")
local Clustorio = require("modules/clusterio/api")
local rcon_env = {}
local rcon_statics = {}
local rcon_callbacks = {}
setmetatable(rcon_statics, { __index = _G })
setmetatable(rcon_env, { __index = rcon_statics })
local Commands = require("modules/exp_commands") --- @class Commands
local rcon_env = {} --- @type table<string, any>
local rcon_static = {} --- @type table<string, any>
local rcon_dynamic = {} --- @type table<string, ExpCommand.RconDynamic>
setmetatable(rcon_static, { __index = _G })
setmetatable(rcon_env, { __index = rcon_static })
--- Some common static values which can be added now
--- @diagnostic disable: name-style-check
rcon_statics.Async = Async
rcon_statics.ExpUtil = ExpUtil
rcon_statics.Commands = Commands
rcon_statics.Clustorio = Clustorio
rcon_statics.print = Commands.print
rcon_statics.ipc = Clustorio.send_json
rcon_static.Async = Async
rcon_static.ExpUtil = ExpUtil
rcon_static.Commands = Commands
rcon_static.Clustorio = Clustorio
rcon_static.print = Commands.print
rcon_static.ipc = Clustorio.send_json
--- @diagnostic enable: name-style-check
--- Some common callback values which are useful when a player uses the command
function rcon_callbacks.player(player) return player end
--- @alias ExpCommand.RconDynamic fun(player: LuaPlayer?): any
function rcon_callbacks.surface(player) return player and player.surface end
function rcon_dynamic.player(player) return player end
function rcon_callbacks.force(player) return player and player.force end
function rcon_dynamic.surface(player) return player and player.surface end
function rcon_callbacks.position(player) return player and player.position end
function rcon_dynamic.force(player) return player and player.force end
function rcon_callbacks.entity(player) return player and player.selected end
function rcon_dynamic.position(player) return player and player.position end
function rcon_callbacks.tile(player) return player and player.surface.get_tile(player.position) end
function rcon_dynamic.entity(player) return player and player.selected end
function rcon_dynamic.tile(player) return player and player.surface.get_tile(player.position.x, player.position.y) end
--- The rcon env is saved between command runs to prevent desyncs
Storage.register(rcon_env, function(tbl)
rcon_env = setmetatable(tbl, { __index = rcon_statics })
rcon_env = setmetatable(tbl, { __index = rcon_static })
end)
--- Static values can be added to the rcon env which are not stored in global such as modules
--- @param name string Name of the value as it will appear in the rcon environment
--- @param value any Value it is have
function Commands.add_rcon_static(name, value)
ExpUtil.assert_not_runtime()
rcon_statics[name] = value
rcon_static[name] = value
end
--- Callback values can be added to the rcon env, these are called on each invocation and should return one value
function Commands.add_rcon_callback(name, callback)
--- @param name string Name of the value as it will appear in the rcon environment
--- @param callback ExpCommand.RconDynamic Callback called to get the current value
function Commands.add_rcon_dynamic(name, callback)
ExpUtil.assert_not_runtime()
rcon_callbacks[name] = callback
rcon_dynamic[name] = callback
end
Commands.new("_rcon", { "exp-commands_rcon.description" })
@@ -70,7 +76,7 @@ Commands.new("_rcon", { "exp-commands_rcon.description" })
-- Construct the environment the command will run within
local env = setmetatable({}, { __index = rcon_env, __newindex = rcon_env })
for name, callback in pairs(rcon_callbacks) do
for name, callback in pairs(rcon_dynamic) do
local _, rtn = pcall(callback, player.index > 0 and player or nil)
rawset(env, name, rtn)
end

View File

@@ -1,6 +1,5 @@
--[[-- Command Module - Sudo
--[[-- Commands - Sudo
System command to execute a command as another player using their permissions (except for permissions group actions)
@commands _system-sudo
--- Run the example command as another player
-- As Cooldude2606: /repeat 5

View File

@@ -25,10 +25,9 @@ local Commands = require("modules/exp_commands")
local add, parse = Commands.add_data_type, Commands.parse_input
local valid, invalid = Commands.status.success, Commands.status.invalid_input
local types = {}
local types = {} --- @class Commands._types
--- A boolean value where true is one of: yes, y, true, 1
--- @type Commands.InputParser
types.boolean =
add("boolean", function(input)
input = input:lower()
@@ -43,14 +42,12 @@ types.boolean =
end)
--- A string, validation does nothing but it is a requirement
--- @type Commands.InputParser
types.string =
add("string", function(input)
return valid(input)
end)
--- A string from a set of options, takes one argument which is an array of options
--- @type Commands.InputParserFactory
types.enum =
add("enum", function(options)
--- @cast options string[]
@@ -65,10 +62,9 @@ types.enum =
end)
--- A string which is the key of a table, takes one argument which is an map of string keys to values
--- @type Commands.InputParserFactory
types.key_of =
add("key_of", function(map)
--- @cast map { [string]: any }
--- @cast map table<string, any>
return function(input)
local option = auto_complete(map, input, true)
if option == nil then
@@ -80,7 +76,6 @@ types.key_of =
end)
--- A string with a maximum length, takes one argument which is the maximum length of a string
--- @type Commands.InputParserFactory
types.string_max_length =
add("string_max_length", function(maximum)
--- @cast maximum number
@@ -94,7 +89,6 @@ types.string_max_length =
end)
--- A number
--- @type Commands.InputParser
types.number =
add("number", function(input)
local number = tonumber(input)
@@ -106,7 +100,6 @@ types.number =
end)
--- An integer, number which has been floored
--- @type Commands.InputParser
types.integer =
add("integer", function(input)
local number = tonumber(input)
@@ -118,7 +111,6 @@ types.integer =
end)
--- A number in a given inclusive range
--- @type Commands.InputParserFactory
types.number_range =
add("number_range", function(minimum, maximum)
--- @cast minimum number
@@ -137,7 +129,6 @@ types.number_range =
end)
--- An integer in a given inclusive range
--- @type Commands.InputParserFactory
types.integer_range =
add("integer_range", function(minimum, maximum)
--- @cast minimum number
@@ -156,7 +147,6 @@ types.integer_range =
end)
--- A player who has joined the game at least once
--- @type Commands.InputParser
types.player =
add("player", function(input)
local player = game.get_player(input)
@@ -168,7 +158,6 @@ types.player =
end)
--- A player who is online
--- @type Commands.InputParser
types.player_online =
add("player_online", function(input, player)
local success, status, result = parse(input, player, Commands.types.player)
@@ -183,8 +172,7 @@ types.player_online =
end)
--- A player who is online and alive
--- @type Commands.InputParser
types.player_online =
types.player_alive =
add("player_alive", function(input, player)
local success, status, result = parse(input, player, Commands.types.player_online)
--- @cast result LuaPlayer
@@ -198,7 +186,6 @@ types.player_online =
end)
--- A force within the game
--- @type Commands.InputParser
types.force =
add("force", function(input)
local force = game.forces[input]
@@ -210,7 +197,6 @@ types.force =
end)
--- A surface within the game
--- @type Commands.InputParser
types.surface =
add("surface", function(input)
local surface = game.surfaces[input]
@@ -222,7 +208,6 @@ types.surface =
end)
--- A planet within the game
--- @type Commands.InputParser
types.planet =
add("planet", function(input)
local surface = game.planets[input]
@@ -234,7 +219,6 @@ types.planet =
end)
--- A name of a color from the predefined list, too many colours to use string-key
--- @type Commands.InputParser
types.color =
add("color", function(input)
local color = auto_complete(Commands.color, input, true)

View File

@@ -1,5 +1,3 @@
color-tag=[color=__1__]__2__[/color]
[exp-commands]
help=__1__- __2____3__
aliases=\n Aliaies: __1__

View File

@@ -57,6 +57,7 @@ end)
local ExpUtil = require("modules/exp_util")
local Search = require("modules/exp_commands/search")
--- @class Commands
local Commands = {
color = ExpUtil.color,
format_rich_text_color = ExpUtil.format_rich_text_color,
@@ -64,11 +65,9 @@ local Commands = {
format_player_name = ExpUtil.format_player_name,
format_player_name_locale = ExpUtil.format_player_name_locale,
types = {}, --- @type { [string]: Commands.InputParser | Commands.InputParserFactory } Stores all input parsers and validators for different data types
registered_commands = {}, --- @type { [string]: Commands.ExpCommand } Stores a reference to all registered commands
registered_commands = {}, --- @type table<string, Commands.ExpCommand> Stores a reference to all registered commands
permission_authorities = {}, --- @type Commands.PermissionAuthority[] Stores a reference to all active permission authorities
status = {}, -- Contains the different status values a command can return
--- @package Stores the event handlers
events = {
[defines.events.on_player_locale_changed] = Search.on_player_locale_changed,
@@ -77,6 +76,14 @@ local Commands = {
},
}
--- @class Commands._status: table<string, Commands.Status>
--- Contains the different status values a command can return
Commands.status = {}
--- @class Commands._types: table<string, Commands.InputParser | Commands.InputParserFactory>
--- Stores all input parsers and validators for different data types
Commands.types = {}
--- @package
function Commands.on_init() Search.prepare(Commands.registered_commands) end
@@ -91,7 +98,7 @@ end
--- @class Commands.Argument
--- @field name string The name of the argument
--- @field description LocalisedString The description of the argument
--- @field description LocalisedString? The description of the argument
--- @field input_parser Commands.InputParser The input parser for the argument
--- @field optional boolean True when the argument is optional
--- @field default any? The default value of the argument
@@ -135,15 +142,17 @@ Commands.server = setmetatable({
show_on_map = false,
valid = true,
object_name = "LuaPlayer",
print = rcon.print,
}, {
-- To prevent unnecessary logging Commands.error is called here and error is filtered by command_callback
__index = function(_, key)
if key == "__self" or type(key) == "number" then return nil end
Commands.error("Command does not support rcon usage, requires reading player." .. key)
error("Command does not support rcon usage, requires reading player." .. key)
Commands.error("Command does not support rcon usage, requires LuaPlayer." .. key)
error("Command does not support rcon usage, requires LuaPlayer." .. key)
end,
__newindex = function(_, key)
Commands.error("Command does not support rcon usage, requires reading player." .. key)
error("Command does not support rcon usage, requires setting player." .. key)
Commands.error("Command does not support rcon usage, requires LuaPlayer." .. key)
error("Command does not support rcon usage, requires LuaPlayer." .. key)
end,
})
@@ -155,7 +164,6 @@ Commands.server = setmetatable({
--- Used to signal success from a command, data type parser, or permission authority
--- @param msg LocalisedString? An optional message to be included when a command completes (only has an effect in command callbacks)
--- @return Commands.Status, LocalisedString # Should be returned directly without modification
--- @type Commands.Status
function Commands.status.success(msg)
return Commands.status.success, msg or { "exp-commands.success" }
end
@@ -164,7 +172,6 @@ end
--- For data type parsers and permission authority, an error return will prevent the command from being executed
--- @param msg LocalisedString? An optional error message to be included in the output, a generic message is used if not provided
--- @return Commands.Status, LocalisedString # Should be returned directly without modification
--- @type Commands.Status
function Commands.status.error(msg)
return Commands.status.error, { "exp-commands.error", msg or { "exp-commands.error-default" } }
end
@@ -173,7 +180,6 @@ end
--- For permission authorities, an unauthorised return will prevent the command from being executed
--- @param msg LocalisedString? An optional error message to be included in the output, a generic message is used if not provided
--- @return Commands.Status, LocalisedString # Should be returned directly without modification
--- @type Commands.Status
function Commands.status.unauthorised(msg)
return Commands.status.unauthorised, msg or { "exp-commands.unauthorized", msg or { "exp-commands.unauthorized-default" } }
end
@@ -182,7 +188,6 @@ end
--- For data type parsers, an invalid_input return will prevent the command from being executed
--- @param msg LocalisedString? An optional error message to be included in the output, a generic message is used if not provided
--- @return Commands.Status, LocalisedString # Should be returned directly without modification
--- @type Commands.Status
function Commands.status.invalid_input(msg)
return Commands.status.invalid_input, msg or { "exp-commands.invalid-input" }
end
@@ -191,12 +196,11 @@ end
--- @param msg LocalisedString A message detailing the error which has occurred, will be logged and outputted
--- @return Commands.Status, LocalisedString # Should be returned directly without modification
--- @package
--- @type Commands.Status
function Commands.status.internal_error(msg)
return Commands.status.internal_error, { "exp-commands.internal-error", msg }
end
--- @type { [Commands.Status]: string }
--- @type table<Commands.Status, string>
local valid_command_status = {} -- Hashmap lookup for testing if a status is valid
for name, status in pairs(Commands.status) do
valid_command_status[status] = name
@@ -205,7 +209,7 @@ end
--- Permission Authority.
-- Functions that control who can use commands
--- @alias Commands.PermissionAuthority fun(player: LuaPlayer, command: Commands.ExpCommand): boolean | Commands.Status, LocalisedString?
--- @alias Commands.PermissionAuthority fun(player: LuaPlayer, command: Commands.ExpCommand): boolean|Commands.Status, LocalisedString?
--- Add a permission authority, a permission authority is a function which provides access control for commands, multiple can be active at once
--- When multiple are active, all authorities must give permission for the command to execute, if any deny access then the command is not ran
@@ -241,7 +245,7 @@ end
--- @param player LuaPlayer? The player to test the permission of, nil represents the server and always returns true
--- @param command Commands.ExpCommand The command the player is attempting to use
--- @return boolean # True if the player has permission to use the command
--- @return LocalisedString # When permission is denied, this is the reason permission was denied
--- @return LocalisedString? # When permission is denied, this is the reason permission was denied
function Commands.player_has_permission(player, command)
if player == nil or player == Commands.server then return true end
@@ -257,9 +261,7 @@ function Commands.player_has_permission(player, command)
return false, msg
end
else
local class_name = ExpUtil.get_class_name(status)
local _, rtn_msg = Commands.status.internal_error("Permission authority returned unexpected value: " .. class_name)
return false, rtn_msg
error("Permission authority returned unexpected value: " .. ExpUtil.get_class_name(status))
end
end
@@ -269,16 +271,14 @@ end
--- Data Type Parsing.
-- Functions that parse and validate player input
--- @generic T
--- @alias Commands.InputParser (fun(input: string, player: LuaPlayer): T) | (fun(input: string, player: LuaPlayer): Commands.Status, LocalisedString | T)
--- @alias Commands.InputParser<T> fun(input: string, player: LuaPlayer): Commands.Status, (T | LocalisedString)
--- @generic T
--- @alias Commands.InputParserFactory fun(...: any): Commands.InputParser<T>
--- @alias Commands.InputParserFactory<T> fun(...: any): Commands.InputParser<T>
--- Add a new input parser to the command library, this method validates that it does not already exist
--- @generic T : Commands.InputParser | Commands.InputParserFactory
--- @param data_type string The name of the data type the input parser reads in and validates, becomes a key of Commands.types
--- @param input_parser `T` The function used to parse and validate the data type
--- @param input_parser T The function used to parse and validate the data type
--- @return T # The function which was provided as the second argument
function Commands.add_data_type(data_type, input_parser)
if Commands.types[data_type] then
@@ -310,33 +310,25 @@ end
--- @return Commands.Status status, T | LocalisedString result # If success is false then Remaining values should be returned directly without modification
function Commands.parse_input(input, player, input_parser)
local status, status_msg = input_parser(input, player)
if status == nil then
if status == nil or not valid_command_status[status] then
local data_type = table.get_key(Commands.types, input_parser) or ExpUtil.get_function_name(input_parser, true)
local rtn_status, rtn_msg = Commands.status.internal_error("Parser for data type \"" .. data_type .. "\" returned a nil value")
return false, rtn_status, rtn_msg
elseif valid_command_status[status] then
if status ~= Commands.status.success then
return false, status, status_msg
else
return true, status, status_msg -- status_msg is the parsed data
end
else
return true, Commands.status.success, status -- status is the parsed data
error("Parser for data type \"" .. data_type .. "\" did not return a valid status got: " .. ExpUtil.get_class_name(status))
end
return status == Commands.status.success, status, status_msg
end
--- List and Search
-- Functions used to list and search for commands
--- Returns a list of all registered custom commands
--- @return { [string]: Commands.ExpCommand } # A dictionary of commands
--- @return table<string,Commands.ExpCommand> # A dictionary of commands
function Commands.list_all()
return Commands.registered_commands
end
--- Returns a list of all registered custom commands which the given player has permission to use
--- @param player LuaPlayer? The player to get the command of, nil represents the server but list_all should be used
--- @return { [string]: Commands.ExpCommand } # A dictionary of commands
--- @return table<string,Commands.ExpCommand> # A dictionary of commands
function Commands.list_for_player(player)
local rtn = {}
@@ -351,7 +343,7 @@ end
--- Searches all custom commands and game commands for the given keyword
--- @param keyword string The keyword to search for
--- @return { [string]: Commands.Command } # A dictionary of commands
--- @return table<string,Commands.Command> # A dictionary of commands
function Commands.search_all(keyword)
return Search.search_commands(keyword, Commands.list_all(), "en")
end
@@ -359,7 +351,7 @@ end
--- Searches custom commands allowed for this player and all game commands for the given keyword
--- @param keyword string The keyword to search for
--- @param player LuaPlayer? The player to search the commands of, nil represents server but search_all should be used
--- @return { [string]: Commands.Command } # A dictionary of commands
--- @return table<string,Commands.Command> # A dictionary of commands
function Commands.search_for_player(keyword, player)
return Search.search_commands(keyword, Commands.list_for_player(player), player and player.locale)
end
@@ -368,7 +360,13 @@ end
-- Prints output to the player or rcon connection
local print_format_options = { max_line_count = 20 }
local print_default_settings = { sound_path = "utility/scenario_message" }
local print_settings_default = { sound_path = "utility/scenario_message", color = ExpUtil.color.white }
local print_settings_error = { sound_path = "utility/wire_pickup", color = ExpUtil.color.orange_red }
Commands.print_settings = {
default = print_settings_default,
error = print_settings_error,
}
--- Print a message to the user of a command, accepts any value and will print in a readable and safe format
--- @param message any The message / value to be printed
@@ -379,9 +377,9 @@ function Commands.print(message, settings)
rcon.print(ExpUtil.format_any(message))
else
if not settings then
settings = print_default_settings
settings = print_settings_default
elseif not settings.sound_path then
settings.sound_path = print_default_settings.sound_path
settings.sound_path = print_settings_default.sound_path
end
player.print(ExpUtil.format_any(message, print_format_options), settings)
end
@@ -390,18 +388,21 @@ end
--- Print an error message to the user of a command, accepts any value and will print in a readable and safe format
--- @param message any The message / value to be printed
function Commands.error(message)
Commands.print(message, {
color = ExpUtil.color.orange_red,
sound_path = "utility/wire_pickup",
})
Commands.print(message, print_settings_error)
end
--- Command Prototype
-- The prototype definition for command objects
local function assert_command_mutable(command)
if not Commands.registered_commands[command.name] then
error("Command cannot be modified after being registered.", 3)
end
end
--- Returns a new command object, this will not register the command but act as a way to start construction
--- @param name string The name of the command as it will be registered later
--- @param description LocalisedString The description of the command displayed in the help message
--- @param description LocalisedString? The description of the command displayed in the help message
--- @return Commands.ExpCommand
function Commands.new(name, description)
ExpUtil.assert_argument_type(name, "string", 1, "name")
@@ -411,7 +412,7 @@ function Commands.new(name, description)
return setmetatable({
name = name,
description = description,
description = description or "",
help_text = description, -- Will be replaced in command:register
callback = default_command_callback, -- Will be replaced in command:register
defined_at = ExpUtil.safe_file_path(2),
@@ -426,10 +427,11 @@ end
--- Add a new required argument to the command of the given data type
--- @param name string The name of the argument being added
--- @param description LocalisedString The description of the argument being added
--- @param description LocalisedString? The description of the argument being added
--- @param input_parser Commands.InputParser The input parser to be used for the argument
--- @return Commands.ExpCommand
function Commands._prototype:argument(name, description, input_parser)
assert_command_mutable(self)
if self.min_arg_count ~= self.max_arg_count then
error("Can not have required arguments after optional arguments", 2)
end
@@ -446,10 +448,11 @@ end
--- Add a new optional argument to the command of the given data type
--- @param name string The name of the argument being added
--- @param description LocalisedString The description of the argument being added
--- @param description LocalisedString? The description of the argument being added
--- @param input_parser Commands.InputParser The input parser to be used for the argument
--- @return Commands.ExpCommand
function Commands._prototype:optional(name, description, input_parser)
assert_command_mutable(self)
self.max_arg_count = self.max_arg_count + 1
self.arguments[#self.arguments + 1] = {
name = name,
@@ -461,9 +464,10 @@ function Commands._prototype:optional(name, description, input_parser)
end
--- Set the defaults for optional arguments, any not provided will have their value as nil
--- @param defaults table The default values for the optional arguments, the key is the name of the argument
--- @param defaults table<string, (fun(player: LuaPlayer): any) | any> The default values for the optional arguments, the key is the name of the argument
--- @return Commands.ExpCommand
function Commands._prototype:defaults(defaults)
assert_command_mutable(self)
local matched = {}
for _, argument in ipairs(self.arguments) do
if defaults[argument.name] then
@@ -489,6 +493,7 @@ end
--- @param flags table An array of strings or a dictionary of flag names and values, when an array is used the flags values are set to true
--- @return Commands.ExpCommand
function Commands._prototype:add_flags(flags)
assert_command_mutable(self)
for name, value in pairs(flags) do
if type(name) == "number" then
self.flags[value] = true
@@ -504,6 +509,7 @@ end
--- @param aliases string[] An array of string names to use as aliases to this command
--- @return Commands.ExpCommand
function Commands._prototype:add_aliases(aliases)
assert_command_mutable(self)
local start_index = #self.aliases
for index, alias in ipairs(aliases) do
self.aliases[start_index + index] = alias
@@ -515,32 +521,41 @@ end
--- Enable concatenation of all arguments after the last, this should be used for user provided reason text
--- @return Commands.ExpCommand
function Commands._prototype:enable_auto_concatenation()
assert_command_mutable(self)
self.auto_concat = true
return self
end
--- Register the command to the game with the given callback, this must be the final step as the object becomes immutable afterwards
--- @param callback Commands.Callback The function which is called to perform the command action
--- @return Commands.ExpCommand
function Commands._prototype:register(callback)
assert_command_mutable(self)
Commands.registered_commands[self.name] = self
self.callback = callback
-- Generates a description to be used
local argument_names = { "" } --- @type LocalisedString
local argument_help_text = { "", "" } --- @type LocalisedString
local help_text = { "exp-commands.help", argument_names, self.description, argument_help_text } --- @type LocalisedString
self.help_text = help_text
local argument_verbose = { "" } --- @type LocalisedString
self.help_text = { "exp-commands.help", argument_names, self.description, argument_verbose } --- @type LocalisedString
if next(self.aliases) then
argument_help_text[2] = { "exp-commands.aliases", table.concat(self.aliases, ", ") }
argument_verbose[2] = { "exp-commands.aliases", table.concat(self.aliases, ", ") }
end
local verbose_index = #argument_verbose
for index, argument in pairs(self.arguments) do
if argument.optional then
argument_names[index + 2] = { "exp-commands.optional", argument.name }
argument_help_text[index + 2] = { "exp-commands.optional-verbose", argument.name, argument.description }
argument_names[index + 1] = { "exp-commands.optional", argument.name }
if argument.description and argument.description ~= "" then
verbose_index = verbose_index + 1
argument_verbose[verbose_index] = { "exp-commands.optional-verbose", argument.name, argument.description }
end
else
argument_names[index + 2] = { "exp-commands.argument", argument.name }
argument_help_text[index + 2] = { "exp-commands.argument-verbose", argument.name, argument.description }
argument_names[index + 1] = { "exp-commands.argument", argument.name }
if argument.description and argument.description ~= "" then
verbose_index = verbose_index + 1
argument_verbose[verbose_index] = { "exp-commands.argument-verbose", argument.name, argument.description }
end
end
end
@@ -565,6 +580,8 @@ function Commands._prototype:register(callback)
for _, alias in ipairs(self.aliases) do
commands.add_command(alias, self.help_text, command_callback)
end
return self
end
--- Command Runner
@@ -633,7 +650,6 @@ end
--- Internal event handler for the command event
--- @param event CustomCommandData
--- @return nil
--- @package
function Commands._event_handler(event)
local command = Commands.registered_commands[event.name]
if command == nil then
@@ -695,20 +711,20 @@ function Commands._event_handler(event)
end
end
-- Run the command, dont need xpcall here because errors are caught in command_callback
-- Run the command, don't need xpcall here because errors are caught in command_callback
local status, status_msg = command.callback(player, table.unpack(arguments))
if status and valid_command_status[status] then
if status ~= Commands.status.success then
log_command("Custom Error", command, player, event.parameter, status_msg)
return Commands.error(status_msg)
else
log_command("Command Ran", command, player, event.parameter)
return Commands.print(status_msg)
end
else
if status == nil then
log_command("Command Ran", command, player, event.parameter)
local _, msg = Commands.status.success()
return Commands.print(msg)
elseif not valid_command_status[status] then
error("Command \"" .. command.name .. "\" did not return a valid status got: " .. ExpUtil.get_class_name(status))
elseif status ~= Commands.status.success then
log_command("Custom Error", command, player, event.parameter, status_msg)
return Commands.error(status_msg)
else
log_command("Command Ran", command, player, event.parameter)
return Commands.print(status_msg)
end
end

View File

@@ -3,8 +3,8 @@ local Search = {}
local Storage = require("modules/exp_util/storage")
--- Setup the storage to contain the pending translations and the completed ones
local pending = {} --- @type { [uint]: { [1]: string, [2]: string }? }
local translations = {} --- @type { [string]: { [string]: string } }
local pending = {} --- @type { [1]: string, [2]: string }[]
local translations = {} --- @type table<string, table<string, string>>
Storage.register({
pending,
translations,
@@ -14,13 +14,13 @@ Storage.register({
end)
local command_names = {} --- @type string[]
local command_objects = {} --- @type { [string]: Commands.Command }
local command_objects = {} --- @type table<string, Commands.Command>
local required_translations = {} --- @type LocalisedString[]
--- Gets the descriptions of all commands, not including their aliases
--- @param custom_commands { [string]: Commands.ExpCommand } The complete list of registered custom commands
--- @param custom_commands table<string, Commands.ExpCommand> The complete list of registered custom commands
function Search.prepare(custom_commands)
local known_aliases = {} --- @type { [string]: string }
local known_aliases = {} --- @type table<string, string>
for name, command in pairs(custom_commands) do
for _, alias in ipairs(command.aliases) do
known_aliases[alias] = name
@@ -85,11 +85,11 @@ end
--- Searches all game commands and the provided custom commands for the given keyword
--- @param keyword string The keyword to search for
--- @param custom_commands { [string]: Commands.ExpCommand } A dictionary of commands to search
--- @param custom_commands table<string, Commands.ExpCommand> A dictionary of commands to search
--- @param locale string? The local to search, default is english ("en")
--- @return { [string]: Commands.Command } # A dictionary of commands
--- @return table<string, Commands.Command> # A dictionary of commands
function Search.search_commands(keyword, custom_commands, locale)
local rtn = {} --- @type { [string]: Commands.Command }
local rtn = {} --- @type table<string, Commands.Command>
keyword = keyword:lower()
locale = locale or "en"