mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2025-12-27 03:25:23 +09:00
Update Command Lib
This commit is contained in:
@@ -1,9 +1,7 @@
|
||||
--[[-- Core Module - Commands
|
||||
- Factorio command making module that makes commands with better parse and more modularity
|
||||
@core Commands
|
||||
@alias Commands
|
||||
|
||||
@usage-- Adding a permission authority
|
||||
--- Adding a permission authority
|
||||
-- You are only required to return a boolean, but by using the unauthorised status you can provide better feedback to the user
|
||||
Commands.add_permission_authority(function(player, command)
|
||||
if command.flags.admin_only and not player.admin then
|
||||
@@ -12,7 +10,7 @@ Commands.add_permission_authority(function(player, command)
|
||||
return Commands.status.success()
|
||||
end)
|
||||
|
||||
@usage-- Adding a data type
|
||||
--- Adding a data type
|
||||
-- You can not return nil from this function, doing so will raise an error, you must return a status
|
||||
Commands.add_data_type("integer", function(input, player)
|
||||
local number = tonumber(input)
|
||||
@@ -35,7 +33,7 @@ Commands.add_data_type("integer-range", function(input, player, minimum, maximum
|
||||
end
|
||||
end)
|
||||
|
||||
@usage-- Adding a command
|
||||
--- Adding a command
|
||||
Commands.new("repeat", "This is my new command, it will repeat a message a number of times")
|
||||
:add_flags{ "admin_only" } -- Using the permission authority above, this makes the command admin only
|
||||
:add_aliases{ "repeat-message" } -- You can add as many aliases as you want
|
||||
@@ -57,27 +55,76 @@ end)
|
||||
]]
|
||||
|
||||
local ExpUtil = require("modules/exp_util")
|
||||
local Color = require("modules/exp_util/include/color")
|
||||
local Search = require("modules/exp_commands/search")
|
||||
|
||||
local Commands = {
|
||||
color = Color, -- A useful reference to the color utils to be used with command outputs
|
||||
_prototype = {}, -- Contains the methods for the command object
|
||||
registered_commands = {}, -- Stores a reference to all registered commands
|
||||
permission_authorities = {}, -- Stores a reference to all active permission authorities
|
||||
data_types = {}, -- Stores all input parsers and validators for different data types
|
||||
color = ExpUtil.color,
|
||||
format_rich_text_color = ExpUtil.format_rich_text_color,
|
||||
format_rich_text_color_locale = ExpUtil.format_rich_text_color_locale,
|
||||
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
|
||||
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,
|
||||
[defines.events.on_player_joined_game] = Search.on_player_locale_changed,
|
||||
[defines.events.on_string_translated] = Search.on_string_translated,
|
||||
},
|
||||
}
|
||||
|
||||
--- @package
|
||||
function Commands.on_init() Search.prepare(Commands.registered_commands) end
|
||||
|
||||
--- @package
|
||||
function Commands.on_load() Search.prepare(Commands.registered_commands) end
|
||||
|
||||
--- @alias Commands.Callback fun(player: LuaPlayer, ...: any): Commands.Status?, LocalisedString?
|
||||
--- This is a default callback that should never be called
|
||||
local function default_command_callback()
|
||||
return Commands.status.internal_error("No callback registered")
|
||||
end
|
||||
|
||||
--- @class Commands.Argument
|
||||
--- @field name string The name 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
|
||||
|
||||
--- @class Commands.Command
|
||||
--- @field name string The name of the command
|
||||
--- @field description LocalisedString The description of the command
|
||||
--- @field help_text LocalisedString The full help text for the command
|
||||
--- @field aliases string[] Aliases that the command will also be registered under
|
||||
--- @field defined_at? string If present then this is an ExpCommand
|
||||
|
||||
--- @class Commands.ExpCommand: Commands.Command
|
||||
--- @field callback Commands.Callback The callback which is ran for the command
|
||||
--- @field defined_at string The file location that the command is defined at
|
||||
--- @field auto_concat boolean True if the command auto concatenates tailing parameters into a single string
|
||||
--- @field min_arg_count number The minimum number of expected arguments
|
||||
--- @field max_arg_count number The maximum number of expected arguments
|
||||
--- @field flags table Stores flags which can be used by permission authorities
|
||||
--- @field arguments Commands.Argument[] The arguments for this command
|
||||
Commands._prototype = {}
|
||||
|
||||
Commands._metatable = {
|
||||
__index = Commands._prototype,
|
||||
__class = "ExpCommand",
|
||||
}
|
||||
|
||||
Commands.player_server = setmetatable({
|
||||
--- @type LuaPlayer
|
||||
Commands.server = setmetatable({
|
||||
index = 0,
|
||||
color = Color.white,
|
||||
chat_color = Color.white,
|
||||
color = ExpUtil.color.white,
|
||||
chat_color = ExpUtil.color.white,
|
||||
name = "<server>",
|
||||
locale = "en",
|
||||
tag = "",
|
||||
connected = true,
|
||||
admin = true,
|
||||
@@ -102,41 +149,54 @@ Commands.player_server = setmetatable({
|
||||
|
||||
--- Status Returns.
|
||||
-- Return values used by command callbacks
|
||||
-- @section command-status
|
||||
|
||||
--- @alias Commands.Status fun(msg: LocalisedString?): Commands.Status, LocalisedString
|
||||
|
||||
--- Used to signal success from a command, data type parser, or permission authority
|
||||
-- @tparam[opt] LocaleString|string msg An optional message to be included when a command completes (only has an effect in command callbacks)
|
||||
--- @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
|
||||
|
||||
--- Used to signal an error has occurred in a command, data type parser, or permission authority
|
||||
-- For data type parsers and permission authority, an error return will prevent the command from being executed
|
||||
-- @tparam[opt] LocaleString|string msg An optional error message to be included in the output, a generic message is used if not provided
|
||||
--- 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
|
||||
|
||||
--- Used to signal the player is unauthorised to use a command, primarily used by permission authorities but can be used in a command callback
|
||||
-- For permission authorities, an error return will prevent the command from being executed
|
||||
-- @tparam[opt] LocaleString|string msg An optional error message to be included in the output, a generic message is used if not provided
|
||||
--- 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
|
||||
|
||||
--- Used to signal the player provided invalid input to an command, primarily used by data type parsers but can be used in a command callback
|
||||
-- For data type parsers, an error return will prevent the command from being executed
|
||||
-- @tparam[opt] LocaleString|string msg An optional error message to be included in the output, a generic message is used if not provided
|
||||
--- 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
|
||||
|
||||
--- Used to signal an internal error has occurred, this is reserved for internal use
|
||||
-- @tparam LocaleString|string msg A message detailing the error which has occurred, will be logged and outputted
|
||||
--- Used to signal an internal error has occurred, this is reserved for internal use only
|
||||
--- @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 }
|
||||
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
|
||||
@@ -144,39 +204,46 @@ end
|
||||
|
||||
--- Permission Authority.
|
||||
-- Functions that control who can use commands
|
||||
-- @section permission-authority
|
||||
|
||||
--- @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
|
||||
-- @tparam function permission_authority The function to provide access control to commands, see module usage.
|
||||
-- @treturn function The function which was provided as the first argument
|
||||
--- When multiple are active, all authorities must give permission for the command to execute, if any deny access then the command is not ran
|
||||
--- @param permission_authority Commands.PermissionAuthority The function to provide access control to commands, see module usage.
|
||||
--- @return Commands.PermissionAuthority # The function which was provided as the first argument
|
||||
function Commands.add_permission_authority(permission_authority)
|
||||
for _, value in ipairs(Commands.permission_authorities) do
|
||||
if value == permission_authority then
|
||||
return permission_authority
|
||||
end
|
||||
end
|
||||
|
||||
local next_index = #Commands.permission_authorities + 1
|
||||
Commands.permission_authorities[next_index] = permission_authority
|
||||
return permission_authority
|
||||
end
|
||||
|
||||
--- Remove a permission authority, must be the same function reference which was passed to add_permission_authority
|
||||
-- @tparam function permission_authority The access control function to remove as a permission authority
|
||||
--- @param permission_authority Commands.PermissionAuthority The access control function to remove as a permission authority
|
||||
function Commands.remove_permission_authority(permission_authority)
|
||||
local pms = Commands.permission_authorities
|
||||
for index, value in pairs(pms) do
|
||||
local pas = Commands.permission_authorities
|
||||
for index, value in ipairs(pas) do
|
||||
if value == permission_authority then
|
||||
local last = #pms
|
||||
pms[index] = pms[last]
|
||||
pms[last] = nil
|
||||
local last = #pas
|
||||
pas[index] = pas[last]
|
||||
pas[last] = nil
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if a player has permission to use a command, calling all permission authorities
|
||||
-- @tparam LuaPlayer player The player to test the permission of, nil represents the server and always returns true
|
||||
-- @tparam Command command The command the player is attempting to use
|
||||
-- @treturn boolean true if the player has permission to use the command
|
||||
-- @treturn LocaleString|string when permission is denied, this is the reason permission was denied
|
||||
--- @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
|
||||
function Commands.player_has_permission(player, command)
|
||||
if player == nil or player == Commands.player_server then return true end
|
||||
if player == nil or player == Commands.server then return true end
|
||||
|
||||
for _, permission_authority in ipairs(Commands.permission_authorities) do
|
||||
local status, msg = permission_authority(player, command)
|
||||
@@ -185,12 +252,14 @@ function Commands.player_has_permission(player, command)
|
||||
local _, rtn_msg = Commands.status.unauthorised(msg)
|
||||
return false, rtn_msg
|
||||
end
|
||||
elseif valid_command_status[status] then
|
||||
elseif status and valid_command_status[status] then
|
||||
if status ~= Commands.status.success then
|
||||
return false, msg
|
||||
end
|
||||
else
|
||||
return false, "Permission authority returned unexpected value"
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
@@ -199,67 +268,68 @@ end
|
||||
|
||||
--- Data Type Parsing.
|
||||
-- Functions that parse and validate player input
|
||||
-- @section input-parse-and-validation
|
||||
|
||||
--- Add a new input parser to the command library, this allows use of a data type without needing to pass the function directly
|
||||
-- @tparam string data_type The name of the data type the input parser reads in and validates
|
||||
-- @tparam function parser The function used to parse and validate the data type
|
||||
-- @treturn string The data type passed as the first argument
|
||||
function Commands.add_data_type(data_type, parser)
|
||||
if Commands.data_types[data_type] then
|
||||
error("Data type \"" .. tostring(data_type) .. "\" already has a parser registered", 2)
|
||||
--- @generic T
|
||||
--- @alias Commands.InputParser (fun(input: string, player: LuaPlayer): T) | (fun(input: string, player: LuaPlayer): Commands.Status, LocalisedString | T)
|
||||
|
||||
--- @generic T
|
||||
--- @alias Commands.InputParserFactory fun(...: any): Commands.InputParser<T>
|
||||
|
||||
--- Add a new input parser to the command library, this method validates that it does not already exist
|
||||
--- @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 Commands.InputParser | Commands.InputParserFactory The function used to parse and validate the data type
|
||||
--- @return string # The data type passed as the first argument
|
||||
function Commands.add_data_type(data_type, input_parser)
|
||||
if Commands.types[data_type] then
|
||||
local defined_at = ExpUtil.get_function_name(Commands.types[data_type], true)
|
||||
error("Data type \"" .. tostring(data_type) .. "\" already has a parser registered: " .. defined_at, 2)
|
||||
end
|
||||
Commands.data_types[data_type] = parser
|
||||
Commands.types[data_type] = input_parser
|
||||
return data_type
|
||||
end
|
||||
|
||||
--- Remove an input parser for a data type, must be the same string that was passed to add_input_parser
|
||||
-- @tparam string data_type The data type for which you want to remove the input parser of
|
||||
--- @param data_type string The name of data type you want to remove the input parser for
|
||||
function Commands.remove_data_type(data_type)
|
||||
Commands.data_types[data_type] = nil
|
||||
Commands.types[data_type] = nil
|
||||
end
|
||||
|
||||
--- Parse and validate an input string as a given data type
|
||||
-- @tparam string|function data_type The name of the data type parser to use to read and validate the input text
|
||||
-- @tparam string input The input string that will be read by the parser
|
||||
-- @param ... Any other arguments that the parser is expecting
|
||||
-- @treturn boolean true when the input was successfully parsed and validated to be the correct type
|
||||
-- @return When The error status for why parsing failed, otherwise it is the parsed value
|
||||
-- @return When first is false, this is the error message, otherwise this is the parsed value
|
||||
function Commands.parse_data_type(data_type, input, ...)
|
||||
local parser = Commands.data_types[data_type]
|
||||
if type(data_type) == "function" then
|
||||
parser = data_type
|
||||
elseif parser == nil then
|
||||
return false, Commands.status.internal_error, { "exp-commands.internal-error", "Data type \"" .. tostring(data_type) .. "\" does not have a registered parser" }
|
||||
end
|
||||
|
||||
local status, parsed = parser(input, ...)
|
||||
--- @generic T
|
||||
--- @param input string The input string
|
||||
--- @param player LuaPlayer The player who gave the input
|
||||
--- @param input_parser Commands.InputParser<T> The parser to apply to the input string
|
||||
--- @return boolean success True when the input was successfully parsed and validated to be the correct type
|
||||
--- @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
|
||||
return Commands.status.internal_error, { "exp-commands.internal-error", "Parser for data type \"" .. tostring(data_type) .. "\" returned a nil value" }
|
||||
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, parsed -- error_type, error_msg
|
||||
return false, status, status_msg
|
||||
else
|
||||
return true, status, parsed -- success, parsed_data
|
||||
return true, status, status_msg -- status_msg is the parsed data
|
||||
end
|
||||
else
|
||||
return true, Commands.status.success, status -- success, parsed_data
|
||||
return true, Commands.status.success, status -- status is the parsed data
|
||||
end
|
||||
end
|
||||
|
||||
--- List and Search
|
||||
-- Functions used to list and search for commands
|
||||
-- @section list-and-search
|
||||
|
||||
--- Returns a list of all registered custom commands
|
||||
-- @treturn table An array of registered commands
|
||||
--- @return { [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
|
||||
-- @treturn table An array of registered commands
|
||||
--- @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
|
||||
function Commands.list_for_player(player)
|
||||
local rtn = {}
|
||||
|
||||
@@ -272,160 +342,87 @@ function Commands.list_for_player(player)
|
||||
return rtn
|
||||
end
|
||||
|
||||
--- Searches all game commands and the provided custom commands for the given keyword
|
||||
local function search_commands(keyword, custom_commands)
|
||||
keyword = keyword:lower()
|
||||
local rtn = {}
|
||||
|
||||
-- Search all custom commands
|
||||
for name, command in pairs(custom_commands) do
|
||||
local search = string.format("%s %s %s", name, command.help, table.concat(command.aliases, " "))
|
||||
if search:lower():match(keyword) then
|
||||
rtn[name] = command
|
||||
end
|
||||
end
|
||||
|
||||
-- Search all game commands
|
||||
for name, description in pairs(commands.game_commands) do
|
||||
local search = string.format("%s %s", name, description)
|
||||
if search:lower():match(keyword) then
|
||||
rtn[name] = {
|
||||
name = name,
|
||||
help = description,
|
||||
description = "",
|
||||
aliases = {},
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return rtn
|
||||
end
|
||||
|
||||
--- Searches all custom commands and game commands for the given keyword
|
||||
-- @treturn table An array of registered commands
|
||||
--- @param keyword string The keyword to search for
|
||||
--- @return { [string]: Commands.Command } # A dictionary of commands
|
||||
function Commands.search_all(keyword)
|
||||
return search_commands(keyword, Commands.list_all())
|
||||
return Search.search_commands(keyword, Commands.list_all(), "en")
|
||||
end
|
||||
|
||||
--- Searches custom commands allowed for this player and all game commands for the given keyword
|
||||
-- @treturn table An array of registered commands
|
||||
--- @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
|
||||
function Commands.search_for_player(keyword, player)
|
||||
return search_commands(keyword, Commands.list_for_player(player))
|
||||
return Search.search_commands(keyword, Commands.list_for_player(player), player and player.locale)
|
||||
end
|
||||
|
||||
--- Command Output
|
||||
-- Prints output to the player or rcon connection
|
||||
-- @section player-print
|
||||
|
||||
--- Set the color of a message using rich text chat
|
||||
-- @tparam string message The message to set the color of
|
||||
-- @tparam Color color The color that the message should be
|
||||
-- @treturn string The string which can be printed to game chat
|
||||
function Commands.set_chat_message_color(message, color)
|
||||
local color_tag = math.round(color.r, 3) .. ", " .. math.round(color.g, 3) .. ", " .. math.round(color.b, 3)
|
||||
return string.format("[color=%s]%s[/color]", color_tag, message)
|
||||
end
|
||||
|
||||
--- Set the color of a locale message using rich text chat
|
||||
-- @tparam LocaleString message The message to set the color of
|
||||
-- @tparam Color color The color that the message should be
|
||||
-- @treturn LocaleString The locale string which can be printed to game chat
|
||||
function Commands.set_locale_chat_message_color(message, color)
|
||||
local color_tag = math.round(color.r, 3) .. ", " .. math.round(color.g, 3) .. ", " .. math.round(color.b, 3)
|
||||
return { "color-tag", color_tag, message }
|
||||
end
|
||||
|
||||
--- Get a string representing the name of the given player in their chat colour
|
||||
-- @tparam LuaPlayer player The player to use the name and color of, nil represents the server
|
||||
-- @treturn string The players name formatted as a string in their chat color
|
||||
function Commands.format_player_name(player)
|
||||
local player_name = player and player.name or "<server>"
|
||||
local player_color = player and player.chat_color or Color.white
|
||||
local color_tag = math.round(player_color.r, 3) .. ", " .. math.round(player_color.g, 3) .. ", " .. math.round(player_color.b, 3)
|
||||
return string.format("[color=%s]%s[/color]", color_tag, player_name)
|
||||
end
|
||||
|
||||
--- Get a locale string representing the name of the given player in their chat colour
|
||||
-- @tparam LuaPlayer player The player to use the name and color of, nil represents the server
|
||||
-- @treturn LocaleString The players name formatted as a locale string in their chat color
|
||||
function Commands.format_locale_player_name(player)
|
||||
local player_name = player and player.name or "<server>"
|
||||
local player_color = player and player.chat_color or Color.white
|
||||
local color_tag = math.round(player_color.r, 3) .. ", " .. math.round(player_color.g, 3) .. ", " .. math.round(player_color.b, 3)
|
||||
return { "color-tag", color_tag, player_name }
|
||||
end
|
||||
local print_format_options = { max_line_count = 20 }
|
||||
local print_default_settings = { sound_path = "utility/scenario_message" }
|
||||
|
||||
--- Print a message to the user of a command, accepts any value and will print in a readable and safe format
|
||||
-- @tparam any message The message / value to be printed
|
||||
-- @tparam[opt] Color color The color the message should be printed in
|
||||
-- @tparam[opt] string sound The sound path to be played when the message is printed
|
||||
function Commands.print(message, color, sound)
|
||||
--- @param message any The message / value to be printed
|
||||
--- @param settings PrintSettings? The settings to print with
|
||||
function Commands.print(message, settings)
|
||||
local player = game.player
|
||||
if not player then
|
||||
rcon.print(ExpUtil.format_any(message))
|
||||
else
|
||||
local formatted = ExpUtil.format_any(message, { max_line_count = 20 })
|
||||
player.print(formatted, {
|
||||
color = color or Color.white,
|
||||
sound_path = sound or "utility/scenario_message",
|
||||
})
|
||||
if not settings then
|
||||
settings = print_default_settings
|
||||
elseif not settings.sound_path then
|
||||
settings.sound_path = print_default_settings.sound_path
|
||||
end
|
||||
player.print(ExpUtil.format_any(message, print_format_options), settings)
|
||||
end
|
||||
end
|
||||
|
||||
--- Print an error message to the user of a command, accepts any value and will print in a readable and safe format
|
||||
-- @tparam any message The message / value to be printed
|
||||
--- @param message any The message / value to be printed
|
||||
function Commands.error(message)
|
||||
return Commands.print(message, Color.orange_red, "utility/wire_pickup")
|
||||
Commands.print(message, {
|
||||
color = ExpUtil.color.orange_red,
|
||||
sound_path = "utility/wire_pickup",
|
||||
})
|
||||
end
|
||||
|
||||
--- Command Prototype
|
||||
-- The prototype defination for command objects
|
||||
-- @section command-prototype
|
||||
-- The prototype definition for command objects
|
||||
|
||||
--- This is a default callback that should never be called
|
||||
local function default_command_callback()
|
||||
return Commands.status.internal_error("No callback registered")
|
||||
end
|
||||
|
||||
--- Returns a new command object, this will not register the command to the game
|
||||
-- @tparam string name The name of the command as it will be registered later
|
||||
-- @tparam string help The help message / description of the command
|
||||
-- @treturn Command A new command object which can be registered
|
||||
function Commands.new(name, help)
|
||||
--- 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
|
||||
--- @return Commands.ExpCommand
|
||||
function Commands.new(name, description)
|
||||
ExpUtil.assert_argument_type(name, "string", 1, "name")
|
||||
ExpUtil.assert_argument_type(help, "string", 2, "help")
|
||||
if Commands.registered_commands[name] then
|
||||
error("Command is already defined at: " .. Commands.registered_commands[name].defined_at, 2)
|
||||
end
|
||||
|
||||
return setmetatable({
|
||||
name = name,
|
||||
help = help,
|
||||
callback = default_command_callback,
|
||||
description = description,
|
||||
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),
|
||||
auto_concat = false,
|
||||
min_arg_count = 0,
|
||||
max_arg_count = 0,
|
||||
flags = {}, -- stores flags that can be used by auth
|
||||
aliases = {}, -- stores aliases to this command
|
||||
arguments = {}, -- [{name: string, optional: boolean, default: any, data_type: function, parse_args: table}]
|
||||
flags = {},
|
||||
aliases = {},
|
||||
arguments = {},
|
||||
}, Commands._metatable)
|
||||
end
|
||||
|
||||
--- Get the data type parser from a name, will raise an error if it doesnt exist
|
||||
local function get_parser(data_type)
|
||||
local rtn = Commands.data_types[data_type]
|
||||
if rtn == nil then
|
||||
error("Unknown data type: " .. tostring(data_type), 3)
|
||||
end
|
||||
return data_type, rtn
|
||||
end
|
||||
|
||||
--- Add a new required argument to the command of the given data type
|
||||
-- @tparam string name The name of the argument being added
|
||||
-- @tparam string data_type The data type of this argument, must have previously been registered with add_data_type
|
||||
-- @treturn Command The command object to allow chaining method calls
|
||||
function Commands._prototype:argument(name, data_type, ...)
|
||||
--- @param name string The name 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)
|
||||
if self.min_arg_count ~= self.max_arg_count then
|
||||
error("Can not have required arguments after optional arguments", 2)
|
||||
end
|
||||
@@ -433,39 +430,38 @@ function Commands._prototype:argument(name, data_type, ...)
|
||||
self.max_arg_count = self.max_arg_count + 1
|
||||
self.arguments[#self.arguments + 1] = {
|
||||
name = name,
|
||||
description = description,
|
||||
input_parser = input_parser,
|
||||
optional = false,
|
||||
data_type = data_type,
|
||||
data_type_parser = get_parser(data_type),
|
||||
parse_args = { ... },
|
||||
}
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a new optional argument to the command of the given data type
|
||||
-- @tparam string name The name of the argument being added
|
||||
-- @tparam string data_type The data type of this argument, must have previously been registered with add_data_type
|
||||
-- @treturn Command The command object to allow chaining method calls
|
||||
function Commands._prototype:optional(name, data_type, ...)
|
||||
--- @param name string The name 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)
|
||||
self.max_arg_count = self.max_arg_count + 1
|
||||
self.arguments[#self.arguments + 1] = {
|
||||
name = name,
|
||||
description = description,
|
||||
input_parser = input_parser,
|
||||
optional = true,
|
||||
data_type = data_type,
|
||||
data_type_parser = get_parser(data_type),
|
||||
parse_args = { ... },
|
||||
}
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the defaults for optional arguments, any not provided will have their value as nil
|
||||
-- @tparam table defaults A table who's keys are the argument names and values are the defaults or function which returns a default
|
||||
-- @treturn Command The command object to allow chaining method calls
|
||||
--- @param defaults table The default values for the optional arguments, the key is the name of the argument
|
||||
--- @return Commands.ExpCommand
|
||||
function Commands._prototype:defaults(defaults)
|
||||
local matched = {}
|
||||
for _, argument in ipairs(self.arguments) do
|
||||
if defaults[argument.name] then
|
||||
if not argument.optional then
|
||||
error("Attempting to set default value for required argument: " .. argument.name)
|
||||
error("Attempting to set default value for required argument: " .. argument.name, 2)
|
||||
end
|
||||
argument.default = defaults[argument.name]
|
||||
matched[argument.name] = true
|
||||
@@ -475,16 +471,16 @@ function Commands._prototype:defaults(defaults)
|
||||
-- Check that there are no extra values in the table
|
||||
for name in pairs(defaults) do
|
||||
if not matched[name] then
|
||||
error("No argument with name: " .. name)
|
||||
error("No argument with name: " .. name, 2)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the flags for the command, these can be accessed by permission authorities to check who should use a command
|
||||
-- @tparam table flags An array of string flags, or a table who's keys are the flag names and values are the flag values
|
||||
-- @treturn Command The command object to allow chaining method calls
|
||||
--- Set the flags for the command, these can be accessed by permission authorities to check who can use a command
|
||||
--- @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)
|
||||
for name, value in pairs(flags) do
|
||||
if type(name) == "number" then
|
||||
@@ -498,8 +494,8 @@ function Commands._prototype:add_flags(flags)
|
||||
end
|
||||
|
||||
--- Set the aliases for the command, these are alternative names that the command can be ran under
|
||||
-- @tparam table aliases An array of string names to use as aliases to this command
|
||||
-- @treturn Command The command object to allow chaining method calls
|
||||
--- @param aliases string[] An array of string names to use as aliases to this command
|
||||
--- @return Commands.ExpCommand
|
||||
function Commands._prototype:add_aliases(aliases)
|
||||
local start_index = #self.aliases
|
||||
for index, alias in ipairs(aliases) do
|
||||
@@ -510,69 +506,84 @@ function Commands._prototype:add_aliases(aliases)
|
||||
end
|
||||
|
||||
--- Enable concatenation of all arguments after the last, this should be used for user provided reason text
|
||||
-- @treturn Command The command object to allow chaining method calls
|
||||
--- @return Commands.ExpCommand
|
||||
function Commands._prototype:enable_auto_concatenation()
|
||||
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
|
||||
-- @tparam function callback The function which is called to perform the command action
|
||||
--- @param callback Commands.Callback The function which is called to perform the command action
|
||||
function Commands._prototype:register(callback)
|
||||
Commands.registered_commands[self.name] = self
|
||||
self.callback = callback
|
||||
|
||||
-- Generates a description to be used
|
||||
local description = {}
|
||||
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
|
||||
if next(self.aliases) then
|
||||
argument_help_text[2] = { "exp-commands.aliases", table.concat(self.aliases, ", ") }
|
||||
end
|
||||
|
||||
for index, argument in pairs(self.arguments) do
|
||||
if argument.optional then
|
||||
description[index] = "[" .. argument.name .. "]"
|
||||
argument_names[index + 2] = { "exp-commands.optional", argument.name }
|
||||
argument_help_text[index + 2] = { "exp-commands.optional-verbose", argument.name, argument.description }
|
||||
else
|
||||
description[index] = "<" .. argument.name .. ">"
|
||||
argument_names[index + 2] = { "exp-commands.argument", argument.name }
|
||||
argument_help_text[index + 2] = { "exp-commands.argument-verbose", argument.name, argument.description }
|
||||
end
|
||||
end
|
||||
|
||||
self.description = table.concat(description, " ")
|
||||
|
||||
-- Callback which is called by the game engine
|
||||
---@param event CustomCommandData
|
||||
local function command_callback(event)
|
||||
event.name = self.name
|
||||
local success, traceback = xpcall(Commands._event_handler, debug.traceback, event)
|
||||
--- @cast traceback string
|
||||
if not success and not traceback:find("Command does not support rcon usage") then
|
||||
local _, msg = Commands.status.internal_error(event.tick)
|
||||
local key = "<" .. table.concat({ event.name, event.player_index, event.tick }, ":") .. ">"
|
||||
local _, msg = Commands.status.internal_error(key)
|
||||
Commands.error(msg)
|
||||
log("Internal Command Error " .. event.tick .. "\n" .. traceback)
|
||||
log("Internal Command Error " .. key .. "\n" .. traceback)
|
||||
end
|
||||
end
|
||||
|
||||
-- Registers the command under its own name
|
||||
local help = { "exp-commands.command-help", self.description, self.help }
|
||||
commands.add_command(self.name, help, command_callback)
|
||||
commands.add_command(self.name, self.help_text, command_callback)
|
||||
|
||||
-- Registers the command under its aliases
|
||||
for _, alias in ipairs(self.aliases) do
|
||||
commands.add_command(alias, help, command_callback)
|
||||
commands.add_command(alias, self.help_text, command_callback)
|
||||
end
|
||||
end
|
||||
|
||||
--- Command Runner
|
||||
-- Used internally to run commands
|
||||
-- @section command-runner
|
||||
|
||||
--- Log that a command was attempted and its outcome (error / success)
|
||||
local function log_command(comment, command, player, args, detail)
|
||||
local player_name = player and player.name or "<Server>"
|
||||
--- @param comment string The main comment to include in the log
|
||||
--- @param command Commands.ExpCommand The command that is being executed
|
||||
--- @param player LuaPlayer The player who is running the command
|
||||
--- @param parameter string The raw command parameter that was used
|
||||
--- @param detail any
|
||||
local function log_command(comment, command, player, parameter, detail)
|
||||
ExpUtil.write_json("log/commands.log", {
|
||||
comment = comment,
|
||||
detail = detail,
|
||||
player_name = player_name,
|
||||
command_name = command.name,
|
||||
args = args,
|
||||
player_name = player.name,
|
||||
parameter = parameter,
|
||||
detail = detail,
|
||||
})
|
||||
end
|
||||
|
||||
--- Extract the arguments from a string input string
|
||||
--- @param raw_input string? The raw input from the player
|
||||
--- @param max_args number The maximum number of allowed arguments
|
||||
--- @param auto_concat boolean True when remaining arguments should be concatenated
|
||||
--- @return table? # Nil if there are too many arguments
|
||||
local function extract_arguments(raw_input, max_args, auto_concat)
|
||||
-- nil check when no input given
|
||||
if raw_input == nil then return {} end
|
||||
@@ -613,13 +624,16 @@ local function extract_arguments(raw_input, max_args, auto_concat)
|
||||
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
|
||||
error("Command not recognised: " .. event.name)
|
||||
end
|
||||
|
||||
local player = nil -- nil represents the server until the command is called
|
||||
local player = Commands.server
|
||||
if event.player_index then
|
||||
player = game.players[event.player_index]
|
||||
end
|
||||
@@ -644,7 +658,7 @@ function Commands._event_handler(event)
|
||||
return Commands.error{ "exp-commands.invalid-usage", command.name, command.description }
|
||||
end
|
||||
|
||||
-- Check the minimum number of arguments is fullfiled
|
||||
-- Check the minimum number of arguments is fulfilled
|
||||
if #raw_arguments < command.min_arg_count then
|
||||
log_command("Too few arguments", command, player, event.parameter, { minimum = command.min_arg_count, maximum = command.max_arg_count })
|
||||
return Commands.error{ "exp-commands.invalid-usage", command.name, command.description }
|
||||
@@ -655,7 +669,7 @@ function Commands._event_handler(event)
|
||||
for index, argument in ipairs(command.arguments) do
|
||||
local input = raw_arguments[index]
|
||||
if input == nil then
|
||||
-- We know this is an optional argument because the mimimum count is satisfied
|
||||
-- We know this is an optional argument because the minimum count is satisfied
|
||||
assert(argument.optional == true, "Argument was required")
|
||||
if type(argument.default) == "function" then
|
||||
arguments[index] = argument.default(player)
|
||||
@@ -664,7 +678,7 @@ function Commands._event_handler(event)
|
||||
end
|
||||
else
|
||||
-- Parse the raw argument to get the correct data type
|
||||
local success, status, parsed = Commands.parse_data_type(argument.data_type_parser, input, player, table.unpack(argument.parse_args))
|
||||
local success, status, parsed = Commands.parse_input(input, player, argument.input_parser)
|
||||
if success == false then
|
||||
log_command("Input parse failed", command, player, event.parameter, { status = valid_command_status[status], index = index, argument = argument, reason = parsed })
|
||||
return Commands.error{ "exp-commands.invalid-argument", argument.name, parsed }
|
||||
@@ -675,8 +689,8 @@ function Commands._event_handler(event)
|
||||
end
|
||||
|
||||
-- Run the command, dont need xpcall here because errors are caught in command_callback
|
||||
local status, status_msg = command.callback(player or Commands.player_server, table.unpack(arguments))
|
||||
if valid_command_status[status] then
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user