Files
factorio-scenario-ExpCluster/expcore/commands.lua
2019-04-18 23:29:21 +01:00

741 lines
39 KiB
Lua

--- Factorio command making module that makes commands with better parse and more modularity
-- @author Cooldude2606
-- @module Commands
--[[
>>>>Example Authenticator:
The command system is most useful when you can control who can use commands; to do this would would need to
define an authenticator which is ran every time a command is run; in this example I will show a simple one
that requires some commands to require the user to be a game admin:
When the authenticator is called be the command handler it will be passed 4 vales:
1) the player who used the command
2) the name of the command that is being used
3) any flags which have been set for this command, this is a table of values set using :set_flag(name,value)
4) the reject function which is the preferred method to prevent execution of the command
For our admin only example we will set a flag to true when we want it do be admin only so when we define the
command will will use :set_flag('admin_only',true) and then inside the authenticator we will test if the flag
is present using: if flags.admin_only then
Although no return is required to allow the command to execute it is best practice to return true; we do this in
two cases in our authenticator:
1) when the "admin_only" flag is not set, which we take to mean any one can use it
2) when the "admin_only" flag is set, and the player is admin
Now when the user is not an admin and the command requires you to be an admin then we must reject the request:
1) return false -- this is the most basic block and should only be used while testing
2) return reject -- returning the reject function is only an option as a fail safe, same as returning false
3) reject() -- this will block execution without returning to allow further code to be ran in the authenticator
4) reject('This command is for admins only!') -- Using reject as a function allows a error message to be returned
5) return reject() -- using return on either case above is best practice as you should execute all code before rejecting
Example Code:
Commands.add_authenticator(function(player,command,flags,reject)
if flags.admin_only then -- our test for the "admin_only" flag
if player.admin then
return true -- true return 2
else
return reject('This command is for admins only!') -- reject return 5 with a custom error message
end
else
return true -- true return 1
end
end)
>>>>Example Parse:
Before you go making commands it is important to understand the most powerful feature of this command handler,
when you define a command you are able to type the params and have then be parsed by an handler so before your
command is ever executed you can be sure that all the params are valid. This module should be paired with a general
command parse but you may want to create your own:
For our example we will create a parse to accept only integer numbers in a given range:
1) we will give it the name "number-range-int" this is the "type" that the input is expected to be
2) when we define the type we will also define the min and max of the range so we can use the function more than once
Example parse usage:
:add_param('repeat_count',false,'number-range-int',5,10) -- range 5 to 10 inclusive
The command parse will be passed 3 params and any other you define, in our case:
1) the input that has been given by the user for this param, the role of this function is to transform this value
nb: the input is a string but can be nil if the param is marked as optional
2) the player who is using the command, this is always present
3) the reject function to throw an error to the user, this is always present
4) the range min, this is user defined and has the value given when the param is defined
5) the range max, this is user defined and has the value given when the param is defined
When returning from the param parse you again have a few options with how to do this:
1) you return the new value for the param (any non nil value) this value is then passed to the command callback
2) not returning will cause a generic invalid error and the command callback is blocked, not recommenced
3) return reject -- this is just a failsafe in case the function is not called, same as no return
4) return reject() -- will give a shorter error message as you pass a nil custom error
5) return reject('Number entered is not in range: '..range_min..', '..range_max) -- returns a custom error the the user
nb: if you do not return reject after you call it then you are still returning nil so there will be a duplicate message
It should be noted that if you want to expand on an existing parse you can use Commands.parse(type,input,player,reject)
and this value will either return a new value for the input or nil, if it is nil you should return nil to prevent dobble
messages to the user:
input = Commands.parse('number-int',input,player,reject)
if not input then return end -- nil check
Example Code:
Commands.add_parse('number-range-int',function(input,player,reject,range_min,range_max)
local rtn = tonumber(input) and math.floor(tonumber(input)) or nil -- converts input to number
if not rtn or rtn < range_min or rtn > range_max then
-- the input is either not a number or is outside the range
return reject('Number entered is not in range: '..range_min..', '..range_max)
else
-- returns the input as a number value rather than a string, thus the param is now the correct type
return rtn
end
end)
>>>>Example Command:
How for the fun part making the commands, the commands can be set up with any number of params and flags that you want,
you can add aliases for the commands and set default values for optional params and of course register your command callback
in our example we will just have a command that will repeat the users name in chat X amount of times and only allow admins to use it.
First we create the new command, nb this will not register the command to the game this is done at the end, we will call
the command "repeat-name" and set the help message as follows:
Commands.new_command('repeat-name','Will repeat you name a number of times in chat.')
Now for our first param we will call "repeat-count" and it will be a required value between 1 and 5 inclusive:
:add_param('repeat-count',false,'number-range-int',1,5)
Our second param we need a custom parse for but we have not defined it, this is an option for when it is unlikely for
any other command to use the same input type; however in our case it will just be a boolean which should be noted as being
included in the general command parse config. As for the param its self it will be called "smiley" and will be optional with
a default value of false:
:add_param('smiley',true,function(input,player,reject)
-- since it is optional the input can be nil, in which case we just return
if not input then return end
-- if it is not nil then we check for a truthy value
if input:lower() == 'true' or input:lower() == 'yes' then
return true
else
-- note that because we did not return nil or reject then false will be passed to command callback, see example parse
return false
end
end)
Once all params are defined you can now define some default values if you have optional params, the default value will be used only
when no value is given as input, if an invalid value is given then the command will still fail and this value will not be used, the
default can also be a function which is passed the player using the command and returns a value. Here we set the default for "smiley" to false:
:set_defaults{smiley=false}
Another example of defaults if we have: item, amount[opt], player[opt]
:set_defaults{
amount = 50, -- more than one value can be set at a time
player = function(player)
return player -- default is the player using the command
end
}
Now the params are set up we can alter how the command works, we can set auth flags, add aliases to this command or enable "auto concat"
which is when you want all extra words to be concatenated onto the end of the last param, useful for reason or messages:
:set_flag('admin_only',true) -- in our case we want "admin_only" to be set to true so only admins can use the command
:add_alias('name','rname') -- we also add two aliases here: "name" and "rname" which point to this command
-- :enable_auto_concat() we do not use this in our case but this can also be used to enable the "auto concat" feature
And finally we want to register a callback to this command, the callback is what defines what the command does, can be as complex as you
want it to be to as simple as our example; the command receives two params plus all that you have defines:
1) the player who used the command
2) in our case repeat_count which will be a number
3) in our case smiley which will be a boolean
4) the raw input; this param is always last as is always present as a catch all
:register(function(player,repeat_count,smiley,raw)
-- this is to show the value for raw as this is an example command, the log file will also show this
game.print(player.name..' used a command with input: '..raw)
local msg = ') '..player.name
if smiley then
-- this is where that smiley param is used
msg = ':'..msg
end
for 1 = 1,repeat_count do
-- this print function will return ANY value to the user in a desync safe manor, this includes if the command was used through rcon
Command.print(1..msg)
end
-- see below for what else can be used here
end)
Some other useful functions that can be used are:
Commands.print(any,colour[opt]) -- this will return any value value to the user including if it is ran through rcon console
Commands.error(message[opt]) -- this returns a warning to the user, aka an error that does not prevent execution of the command
return Commands.error(message[opt]) -- this returns an error to the user, and will halt the command execution, ie no success message is returned
Commands.success(message[opt]) -- used to return a success message however dont use this method see below
return Commands.success(message[opt]) -- will return the success message to the user and your given message, halts execution
return <any> if any value is returned then it will be returned to the player via a Commands.success call
Example Code:
Commands.new_command('repeat-name','Will repeat you name a number of times in chat.')
:add_param('repeat-count',false,'number-range-int',1,5) -- required int in range 1 to 5 inclusive
:add_param('smiley',true,function(input,player,reject) -- optional boolean default false
if not input then return end
if input:lower() == 'true' or input:lower() == 'yes' then
return true
else
return false
end
end)
:set_defaults{smiley=false}
:set_flag('admin_only',true) -- command is admin only
:add_alias('name','rname') -- allow alias: name and rname
:register(function(player,repeat_count,smiley,raw)
game.print(player.name..' used a command with input: '..raw)
local msg = ') '..player.name
if smiley then
msg = ':'..msg
end
for 1 = 1,repeat_count do
Command.print(1..msg)
end
end)
>>>>Functions List (see function for more detail):
Commands.add_authenticator(callback) --- Adds an authorization callback, function used to check if a player if allowed to use a command
Commands.remove_authenticator(callback) --- Removes an authorization callback, see add_authenticator for adding them
Commands.authorize(player,command_name) --- Mostly used internally, calls all authorization callbacks, returns if the player is authorized
Commands.get(player) --- Gets all commands that a player is allowed to use, game commands not included
Commands.search(keyword,allowed_player) --- Searches command names and help messages to find possible commands, game commands included
Commands.add_parse(name,callback) --- Adds a parse function which can be called by name rather than callback (used in add_param)
Commands.remove_parse(name) --- Removes a parse function, see add_parse for adding them
Commands.parse(name,input,player,reject,...) --- Intended to be used within other parse functions, runs a parse and returns success and new value
Commands.new_command(name,help) --- Creates a new command object to added details to, note this does not register the command to the game
Commands._prototype:add_param(name,optional,parse,...) --- Adds a new param to the command this will be displayed in the help and used to parse the input
Commands._prototype:set_defaults(defaults) --- Adds default values to params only matters if the param is optional
Commands._prototype:set_flag(name,value) --- Adds a tag to the command which is passed via the flags param to the authenticators, can be used to assign command roles or type
Commands._prototype:add_alias(...) --- Adds an alias or multiple that will also be registered with the same callback, eg /teleport can be /tp with both working
Commands._prototype:enable_auto_concat() --- Enables auto concatenation of any params on the end so quotes are not needed for last param
Commands._prototype:register(callback) --- Adds the callback to the command and registers all aliases, params and help message with the game
Commands.error(error_message,play_sound) --- Sends an error message to the player and returns a constant to return to command handler to exit execution
Commands.internal_error(success,command_name,error_message) --- Sends an error to the player and logs the error, used with pcall within command handler please avoid direct use
Commands.success(value) --- Sends a value to the player, followed by a command complete message
Commands.print(value) --- Short cut for player_return, will return any value given to it (not just strings) in a string format to the player/server
Commands.run_command(command_event) --- Main event function that is ran for all commands, used internally please avoid direct use
]]
local Game = require 'utils.game'
local player_return = ext_require('expcore.common','player_return')
local Commands = {
defines={ -- common values are stored error like signals
error='CommandError',
unauthorized='CommandErrorUnauthorized',
success='CommandSuccess'
},
commands={}, -- custom command data will be stored here
authorization_fail_on_error=false, -- set true to have authorize fail if a callback fails to run, more secure
authorization={}, -- custom function are stored here which control who can use what commands
parse_functions={}, -- used to store default functions which are common parse function such as player or number in range
print=player_return, -- short cut so player_return does not need to be required in every module
_prototype={}, -- used to store functions which gets added to new custom commands
}
--- Adds an authorization callback, function used to check if a player if allowed to use a command
-- @see Commands.authorize
-- @tparam callback function the callback you want to register as an authenticator
-- callback param - player: LuaPlayer - the player who is trying to use the command
-- callback param - command: string - the name of the command which is being used
-- callback param - flags: table - any flags which have been set for the command
-- callback param - reject: function(error_message?: string) - call to fail authorize with optional error message
-- @treturn number the index it was inserted at use to remove the callback, if anon function used
function Commands.add_authenticator(callback)
table.insert(Commands.authorization,callback)
return #Commands.authorization
end
--- Removes an authorization callback
-- @see Commands.add_authenticator
-- @tparam callback function|number the callback to remove, an index returned by add_authenticator can be passed
-- @treturn boolean was the callback found and removed
function Commands.remove_authenticator(callback)
if type(callback) == 'number' then
-- if a number is passed then it is assumed to be the index
if Commands.authorization[callback] then
table.remove(Commands.authorization,callback)
return true
end
else
-- will search the array and remove the key
local index
for key,value in pairs(Commands.authorization) do
if value == callback then
index = key
break
end
end
-- if the function was found it is removed
if index then
table.remove(Commands.authorization,index)
return true
end
end
return false
end
--- Mostly used internally, calls all authorization callbacks, returns if the player is authorized
-- @tparam player LuaPlayer the player that is using the command, passed to callbacks
-- @tparam command_name string the command that is being used, passed to callbacks
-- @treturn[1] boolean true player is authorized
-- @treturn[1] string commands const for success
-- @treturn[2] boolean false player is unauthorized
-- @treturn[2] string|locale_string the reason given by the authenticator
function Commands.authorize(player,command_name)
local failed
if not player then return true end
local command_data = Commands.commands[command_name]
if not command_data then return false end
-- function passed to authorization callback to make it simpler to use
local auth_fail = function(error_message)
failed = error_message or {'expcore-commands.unauthorized'}
return Commands.defines.unauthorized
end
-- loops over each authorization callback if any return false or unauthorized command will fail
for _,callback in pairs(Commands.authorization) do
-- callback(player: LuaPlayer, command: string, flags: table, reject: function(error_message?: string))
local success, rtn = pcall(callback,player,command_name,command_data.flags,auth_fail)
-- error handler
if not success then
-- the callback failed to run
log('[ERROR] Authorization failed: '..rtn)
if Commands.authorization_fail_on_error then
failed = 'Internal Error'
end
elseif rtn == false or rtn == Commands.defines.unauthorized or rtn == auth_fail or failed then
-- the callback returned unauthorized, failed be now be set if no value returned
failed = failed or {'expcore-commands.unauthorized'}
break
end
end
-- checks if the authorization failed
if failed then
return false, failed
else
return true, Commands.defines.success
end
end
--- Gets all commands that a player is allowed to use, game commands not included
-- @tparam[opt] player LuaPlayer the player that you want to get commands of, nil will return all commands
-- @treturn table all commands that that player is allowed to use, or all commands
function Commands.get(player)
player = Game.get_player_from_any(player)
if not player then return Commands.commands end
local allowed = {}
for name,command_data in pairs(Commands.commands) do
if Commands.authorize(player,name) then
allowed[name]=command_data
end
end
return allowed
end
--- Searches command names and help messages to find possible commands, game commands included
-- @tparam keyword string the word which you are trying to find
-- @tparam[opt] allowed_player LuaPlayer the player to get allowed commands of, if nil all commands are searched
-- @treturn table all commands that contain the key word, and allowed by player if player given
function Commands.search(keyword,allowed_player)
local custom_commands = Commands.get(allowed_player)
local matches = {}
keyword = keyword:lower()
-- loops over custom commands
for name,command_data in pairs(custom_commands) do
-- combines name help and aliases into one message to be searched
local search = string.format('%s %s %s',name,command_data.help,table.concat(command_data.aliases,' '))
if search:lower():match(keyword) then
matches[name] = command_data
end
end
-- loops over the names of game commands
for name,description in pairs(commands.game_commands) do
if name:lower():match(keyword) then
-- because game commands lack some stuff that the custom ones have they are formated
matches[name] = {
name=name,
help=description,
description='',
aliases={}
}
end
end
return matches
end
--- Adds a parse function which can be called by name rather than callback (used in add_param)
-- nb: this is not needed as you can use the callback directly this just allows it to be called by name
-- @tparam name string the name of the parse, should be the type like player or player_alive, must be unique
-- @tparam callback function the callback that is ran to parse the input
-- parse param - input: string - the input given by the user for this param
-- parse param - player: LuaPlayer - the player who is using the command
-- parse param - reject: function(error_message) - use this function to send a error to the user and fail running
-- parse return - the value that will be passed to the command callback, must not be nil and if reject then command is not run
-- @treturn boolean was the parse added will be false if the name is already used
function Commands.add_parse(name,callback)
if Commands.parse_functions[name] then
return false
else
Commands.parse_functions[name] = callback
return true
end
end
--- Removes a parse function, see add_parse for adding them
-- @tparam name string the name of the parse to remove
function Commands.remove_parse(name)
Commands.parse_functions[name] = nil
end
--- Intended to be used within other parse functions, runs a parse and returns success and new value
-- @tparam name string the name of the parse to call, must be registered and cant be a function
-- @tparam input string the input to pass to the parse, will always be a string but might not be the orginal input
-- @treturn any the new value for the input, may be nil, if nil then either there was an error or input was nil
function Commands.parse(name,input,player,reject,...)
if not Commands.parse_functions[name] then return end
local success,rtn = pcall(Commands.parse_functions[name],input,player,reject,...)
if not success then error(rtn,2) return end
if not rtn then return end
if rtn == Commands.defines.error then return end
return rtn
end
--- Creates a new command object to added details to, note this does not register the command to the game
-- @tparam name string the name of the command to be created
-- @tparam help string the help message for the command
-- @treturn Commands._prototype this will be used with other functions to generate the command functions
function Commands.new_command(name,help)
local command = setmetatable({
name=name,
help=help,
callback=function() Commands.internal_error(false,name,'No callback registered') end,
auto_concat=false,
min_param_count=0,
max_param_count=0,
flags={}, -- stores flags that can be used by auth
aliases={}, -- n = name: string
params={}, -- [param_name] = {optional: boolean, default: any, parse: function, parse_args: table}
}, {
__index= Commands._prototype
})
Commands.commands[name] = command
return command
end
--- Adds a new param to the command this will be displayed in the help and used to parse the input
-- @tparam name string the name of the new param that is being added to the command
-- @tparam[opt=true] optional is this param required for this command, these must be after all required params
-- @tparam[opt=pass through] parse function this function will take the input and return a new (or same) value
-- @param[opt] ... extra args you want to pass to the parse function; for example if the parse is general use
-- parse param - input: string - the input given by the user for this param
-- parse param - player: LuaPlayer - the player who is using the command
-- parse param - reject: function(error_message) - use this function to send a error to the user and fail running
-- parse return - the value that will be passed to the command callback, must not be nil and if reject then command is not run
-- @treturn Commands._prototype pass through to allow more functions to be called
function Commands._prototype:add_param(name,optional,parse,...)
if optional ~= false then optional = true end
parse = parse or function(string) return string end
self.params[name] = {
optional=optional,
parse=parse,
parse_args={...}
}
self.max_param_count = self.max_param_count+1
if not optional then
self.min_param_count = self.min_param_count+1
end
return self
end
--- Adds default values to params only matters if the param is optional, if default value is a function it is called with param player
-- @tparam defaults table a table keyed by the name of the param with the value as the default value {paramName=defaultValue}
-- callback param - player: LuaPlayer - the player using the command, default value does not need to be a function callback
-- @treturn Commands._prototype pass through to allow more functions to be called
function Commands._prototype:set_defaults(defaults)
for name,value in pairs(defaults) do
if self.params[name] then
self.params[name].default = value
end
end
return self
end
--- Adds a tag to the command which is passed via the flags param to the authenticators, can be used to assign command roles or type
-- @tparam name string the name of the tag to be added; used to keep flags separate
-- @tparam value any the tag that you want can be anything that the authenticators are expecting
-- nb: if value is nil then name will be assumed as the value and added at a numbered index
-- @treturn Commands._prototype pass through to allow more functions to be called
function Commands._prototype:set_flag(name,value)
if not value then
-- value not given so name is the value
table.insert(self.flags,name)
else
-- name is given so its key: value
self.flags[name] = value
end
return self
end
--- Adds an alias or multiple that will also be registered with the same callback, eg /teleport can be /tp with both working
-- @usage command:add_alias('aliasOne','aliasTwo','etc')
-- @tparam ... string any amount of aliases that you want this command to be callable with
-- @treturn Commands._prototype pass through to allow more functions to be called
function Commands._prototype:add_alias(...)
for _,alias in pairs({...}) do
table.insert(self.aliases,alias)
--Commands.alias_map[alias] = self.name
end
return self
end
--- Enables auto concatenation of any params on the end so quotes are not needed for last param
-- nb: this will disable max param checking as they will be concated onto the end of that last param
-- this can be useful for reasons or longs text, can only have one per command
-- @treturn Commands._prototype pass through to allow more functions to be called
function Commands._prototype:enable_auto_concat()
self.auto_concat = true
return self
end
--- Adds the callback to the command and registers all aliases, params and help message with the game
-- nb: this must be the last function ran on the command and must be done for the command to work
-- @tparam callback function the callback for the command, will receive the player running command, and params added with add_param
-- callback param - player: LuaPlayer - the player who used the command
-- callback param - ... - any params which were registered with add_param in the order they where registered
-- callback param - raw: string - the raw input from the user, comes after every param added with add_param
function Commands._prototype:register(callback)
-- generates a description to be used
self.callback = callback
local description = ''
for param_name,param_details in pairs(self.params) do
if param_details.optional then
description = string.format('%s [%s]',description,param_name)
else
description = string.format('%s <%s>',description,param_name)
end
end
self.description = description
-- registers the command under its own name
commands.add_command(self.name,{'expcore-commands.command-help',description,self.help},function(command_event)
local success, err = pcall(Commands.run_command,command_event)
if not success then log('[ERROR] command/'..self.name..' :: '..err) end
end)
-- adds any aliases that it has
for _,alias in pairs(self.aliases) do
if not commands.commands[alias] and not commands.game_commands[alias] then
commands.add_command(alias,{'expcore-commands.command-help',description,self.help},function(command_event)
command_event.name = self.name
local success, err = pcall(Commands.run_command,command_event)
Commands.internal_error(success,self.name,err)
end)
end
end
end
--- Sends an error message to the player and returns a constant to return to command handler to exit execution
-- nb: this is for non fatal errors meaning there is no log of this event
-- nb: if reject is giving as a param to the callback use that instead
-- @usage return Commands.error()
-- @tparam[opt] error_message string an optional error message that can be sent to the user
-- @tparam[opt] play_sound string the sound to play for the error
-- @treturn Commands.defines.error return this to command handler to exit execution
function Commands.error(error_message,play_sound)
error_message = error_message or ''
player_return({'expcore-commands.command-fail',error_message},'orange_red')
if play_sound ~= false then
play_sound = play_sound or 'utility/wire_pickup'
if game.player then game.player.play_sound{path=play_sound} end
end
return Commands.defines.error
end
--- Sends an error to the player and logs the error, used with pcall within command handler please avoid direct use
-- nb: use error(error_message) within your callback to trigger do not trigger directly as the handler may still continue
-- @tparam success boolean the success value returned from pcall, or just false to trigger error
-- @tparam command_name string the name of the command this is used within the log
-- @tparam error_message string the error returned by pcall or some other error, this is logged and not returned to player
-- @treturn boolean the opposite of success so true means to cancel execution, used internally
function Commands.internal_error(success,command_name,error_message)
if not success then
Commands.error('Internal Error, Please contact an admin','utility/cannot_build')
log{'expcore-commands.command-error-log-format',command_name,error_message}
end
return not success
end
--- Sends a value to the player, followed by a command complete message
-- nb: either return a value from your callback to trigger or return the return of this to prevent two messages
-- @tparam[opt] value any the value to return to the player, if nil then only success message returned
-- @treturn Commands.defines.success return this to the command handler to prevent two success messages
function Commands.success(value)
if value ~= nil then player_return(value) end
player_return({'expcore-commands.command-ran'},'cyan')
return Commands.defines.success
end
-- logs command usage to file
local function command_log(player,command,comment,params,raw,details)
local player_name = player and player.name or '<Server>'
game.write_file('log/commands.log',game.table_to_json{
player_name=player_name,
command_name=command.name,
comment=comment,
details=details,
params=params,
raw=raw
}..'\n',true,0)
end
--- Main event function that is ran for all commands, used internally please avoid direct use
-- @tparam command_event table passed directly from command event from the add_command function
function Commands.run_command(command_event)
local command_data = Commands.commands[command_event.name]
-- player can be nil when it is the server
local player
if command_event.player_index and command_event.player_index > 0 then
player = Game.get_player_by_index(command_event.player_index)
end
-- checks if player is allowed to use the command
local authorized, auth_fail = Commands.authorize(player,command_data.name)
if not authorized then
command_log(player,command_data,'Failed Auth',{},command_event.parameter)
Commands.error(auth_fail,'utility/cannot_build')
return
end
-- null param check
if command_data.min_param_count > 0 and not command_event.parameter then
command_log(player,command_data,'No Params Given',{},command_event.parameter)
Commands.error({'expcore-commands.invalid-inputs',command_data.name,command_data.description})
return
end
-- splits the arguments
local input_string = command_event.parameter or ''
local quote_params = {} -- stores any " " params
input_string = input_string:gsub('"[^"]-"',function(w)
-- finds all " " params are removes spaces for the next part
local no_spaces = w:gsub('%s','_')
local no_quotes = w:sub(2,-2)
quote_params[no_spaces]=no_quotes
if command_data.auto_concat then
-- if auto concat then dont remove quotes as it should be included later
quote_params[no_spaces]=w
end
return no_spaces
end)
local raw_params = {} -- stores all params
local param_number = 0
local last_index = 0
for word in input_string:gmatch('%S+') do
param_number = param_number + 1
if param_number > command_data.max_param_count then
-- there are too many params given to the command
if not command_data.auto_concat then
-- error as they should not be more
command_log(player,command_data,'Invalid Input: Too Many Params',raw_params,input_string)
Commands.error({'expcore-commands.invalid-inputs',command_data.name,command_data.description})
return
else
-- concat to the last param
if quote_params[word] then
-- if it was a " " param then the spaces are re added now
raw_params[last_index]=raw_params[last_index]..' '..quote_params[word]
else
raw_params[last_index]=raw_params[last_index]..' '..word
end
end
else
-- new param that needs to be added
-- all words are added to an array
if quote_params[word] then
-- if it was a " " param then the spaces are re added now
table.insert(raw_params,quote_params[word])
last_index = last_index + 1
else
table.insert(raw_params,word)
last_index = last_index + 1
end
end
end
-- checks param count
local param_count = #raw_params
if param_count < command_data.min_param_count then
command_log(player,command_data,'Invalid Input: Not Enough Params',raw_params,input_string)
Commands.error({'expcore-commands.invalid-inputs',command_data.name,command_data.description})
return
end
-- parses the arguments
local index = 1
local params = {}
for param_name, param_data in pairs(command_data.params) do
local parse_callback = param_data.parse
if type(parse_callback) == 'string' then
-- if its a string this allows it to be pulled from the common store
parse_callback = Commands.parse_functions[parse_callback]
end
if not type(parse_callback) == 'function' then
-- if its not a function throw and error
Commands.internal_error(false,command_data.name,'Invalid param parse '..tostring(param_data.parse))
command_log(player,command_data,'Internal Error: Invalid Param Parse',params,command_event.parameter,tostring(param_data.parse))
return
end
-- used below as the reject function
local parse_fail = function(error_message)
error_message = error_message or ''
command_log(player,command_data,'Invalid Param Given',raw_params,input_string)
return Commands.error{'expcore-commands.invalid-param',param_name,error_message}
end
-- input: string, player: LuaPlayer, reject: function, ... extra args
local success,param_parsed = pcall(parse_callback,raw_params[index],player,parse_fail,unpack(param_data.parse_args))
if Commands.internal_error(success,command_data.name,param_parsed) then
return command_log(player,command_data,'Internal Error: Param Parse Fail',params,command_event.parameter,param_parsed)
end
if param_data.optional == true and raw_params[index] == nil then
-- if it is optional and param is nil then it is set to default
param_parsed = param_data.default
if type(param_parsed) == 'function' then
-- player: LuaPlayer
success,param_parsed = pcall(param_parsed,player)
if Commands.internal_error(success,command_data.name,param_parsed) then
return command_log(player,command_data,'Internal Error: Default Value Fail',params,command_event.parameter,param_parsed)
end
end
elseif param_parsed == nil or param_parsed == Commands.defines.error or param_parsed == parse_fail then
-- no value was returned or error was returned, if nil then give generic error
if not param_parsed == Commands.defines.error then
command_log(player,command_data,'Invalid Param Given',raw_params,input_string,param_name)
Commands.error{'expcore-commands.command-error-param-format',param_name,'please make sure it is the correct type'}
end
return
end
-- adds the param to the table to be passed to the command callback
table.insert(params,param_parsed)
index=index+1
end
-- runs the command
-- player: LuaPlayer, ... command params, raw: string
table.insert(params,input_string)
local success, err = pcall(command_data.callback,player,unpack(params))
if Commands.internal_error(success,command_data.name,err) then
return command_log(player,command_data,'Internal Error: Command Callback Fail',params,command_event.parameter,err)
end
if err == Commands.defines.error or err == Commands.error then
return command_log(player,command_data,'Custom Error',raw_params,input_string)
elseif err ~= Commands.defines.success and err ~= Commands.success then
-- in this case the user has not received any output
Commands.success(err)
end
command_log(player,command_data,'Success',raw_params,input_string)
end
return Commands