Commands core

Core Module - Commands - Factorio command making module that makes commands with better parse and more modularity

Usage

--- Full code example, see below for explanation
Commands.new_command('repeat-name', 'Will repeat you name a number of times in chat.')
:add_param('repeat-count', '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
    input = input:lower()
    if input == 'true' or input == 'yes' then return true end
    return false
end)
:set_defaults{ smiley = false }
:set_flag('admin_only') -- 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)
--- Example Command Explanation:
-- Making commands basics, 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 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, note 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 have named it "repeat-count" and it will be a required value, between 1 and 5 inclusive:
-- By using "number-range-int" we are saying to use this parser to convert our input text, common ones exist in config.expcore.command_general_parse
:add_param('repeat-count', 'number-range-int', 1, 5)

-- Our second param needs a custom parser, meaning it isnt defined with add_parser, this is an option for when it is unlikely for
-- any other command to use the same input type. In the example it is a boolean type and we are just showing it here as part of the example.
-- 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 == 'true' or input == 'yes' then return true end
    -- Note that because we did not return nil or reject then false will be passed to command callback, see example parse
    return false
end)

-- Once all params are defined you can add some default values for your 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 fail and the default will not be used, the
-- default can also be a function which is passed the player as an argument and should return a value to be the default.
-- 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 end -- Default is the player using the command
}

-- Now the params are set up we can alter how the command works, we can set auth flags, add aliases, or enable "auto concat":
:set_flag('admin_only') -- 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, or as simple as our example; the command receives two params plus all param you have defined:
-- 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
        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 can be used here
end)

-- Values that can be returned from register callback
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 don't 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 Authenticator:
-- The command system is best used when you can control who uses commands;
-- to do this you 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 certain commands to require the user to be a game admin.

-- For our admin only example we will set a flag to true when we want it to be admin only;
-- when we define the command will will use :set_flag('admin_only');
-- then inside the authenticator we will test if the flag is present using: if flags.admin_only then

-- When the authenticator is called by the command handler it will be passed 4 arguments:
-- 1) player - the player who used the command
-- 2) command - the name of the command that is being used
-- 3) flags - the flags which have been set for this command, flags are set with :set_flag(name, value)
-- 4) reject - the reject function which is the preferred method to prevent execution of the command

-- No return is required to allow the command to execute but 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 assume that any one can use it
-- 2) when the "admin_only" flag is set, and the player is admin

-- When want to prevent execution of the command we must reject it, listed is how that can be done:
-- 1) return false -- this is the most basic rejection and should only be used while testing
-- 2) return reject -- returning the reject function is as a fail safe in case you forget to call it, same as returning false
-- 3) reject() -- this will block execution while allowing further code to be ran in your 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 your code before rejecting

-- Example Code:
Commands.add_authenticator(function(player, command, flags, reject)
    -- Check if the command is admin only
    if flags.admin_only then
        -- Return true if player is admin, or reject and return error message
        return player.admin or reject('This command is for admins only!')
    else
        -- Return true if command was not admin only
        return true
    end
end)
--- Example Parser:
-- Before you make a command 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 and validated before your command is executed;
-- 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
:add_param('repeat_count', 'number-range-int', 5, 10) -- "repeat_count" is a required "number-range-int" in a range 5 to 10 inclusive

-- The command parse will be passed 3 arguments plus any other which you define, in our case:
-- 1) input - 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) player - the player who is using the command, this is always present
-- 3) reject - the reject function to throw an error to the user, this is always present
-- 4) range_min - the range min, this is user defined and has the value given when the param is defined
-- 5) range_max - the range max, this is user defined and has the value given when the param is defined

-- When returning from the param parse you 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 is rejected, 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 to the user
-- nb: if you do not return reject after you call it then you will still be returning nil so there will be a duplicate error message

-- It should be noted that if you want to expand on an existing parse you can use Commands.parse(type, input, player, reject)
-- this function will either return a new value for the input or nil, if it is nil you should return nil to prevent duplicate
-- error 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)

Dependencies

utils.game
expcore.common

Tables

defines Constant values used by the command system
commands An array of all custom commands that are registered
authenticators An array of all custom authenticators that are registered
parsers Used to store default functions which are common parse function such as player or number in range
_prototype The command prototype which stores all command defining functions

Fields

authorization_failure_on_error When true any authenticator error will result in authorization failure, more secure
print Returns a value to the player, different to success as this does not signal the end of your command

Authentication

add_authenticator(authenticator) Adds an authorization function, function used to check if a player if allowed to use a command
remove_authenticator(authenticator) Removes an authorization function, can use the index or the function value
authorize(player, command_name) Mostly used internally, calls all authenticators, returns if the player is authorized

Parse

add_parse(name, parser) Adds a parse function which can be called by name (used in add_param) nb: this is not required as you can use the callback directly this just allows it to be called by name
remove_parse(name) Removes a parse function, see add_parse for adding them, cant be done during runtime
parse(name, input, player, reject) Intended to be used within other parse functions, runs a parse and returns success and new value

Getters

get([player]) Gets all commands that a player is allowed to use, game commands are not included
search(keyword[, player]) Searches command names and help messages to find possible commands, game commands are included

Creation

new_command(name, help) Creates a new command object to added details to, this does not register the command to the game api
Commands._prototype:add_param(name[, optional=false][, 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) Add default values to params, only as an effect if the param is optional, if default value is a function it is called with the acting player
Commands._prototype:set_flag(name[, value=true]) Adds a flag to the command which is passed via the flags param to the authenticators, can be used to assign command roles or usage type
Commands._prototype:add_alias(...) Adds an alias, or multiple, that will be registered to this command, eg /teleport can be used as /tp
Commands._prototype:enable_auto_concat() Enables auto concatenation for this command, all params after the last are added to the last param, useful for reasons or other long text input nb: this will disable max param checking as they will be concatenated onto the end of that last param
Commands._prototype:register(callback) Adds the callback to the command and registers: aliases, params and help message with the base game api nb: this must be the last function ran on the command and must be done for the command to work

Status

success([value]) Sends a value to the player, followed by a command complete message, returning a value will trigger this automatically
print(value, colour) Sends a value to the player, different to success as this does not signal the end of your command
error([error_message=''][, play_sound=utility/wire_pickup]) Sends an error message to the player and when returned will stop execution of the command nb: this is for non fatal errors meaning there is no log of this event, use during register callback
internal_error(success, command_name, error_message) Sends an error to the player and logs the error, used internally please avoid direct use nb: use error(error_message) within your callback to trigger do not trigger directly as code execution may still continue
run_command(command_event) Main event function that is ran for all commands, used internally please avoid direct use

Dependencies

# utils.game
# expcore.common

Tables

# defines

Constant values used by the command system

Fields:
  • error
  • unauthorized
  • success
# commands

An array of all custom commands that are registered

# authenticators

An array of all custom authenticators that are registered

# parsers

Used to store default functions which are common parse function such as player or number in range

# _prototype

The command prototype which stores all command defining functions

Fields

# authorization_failure_on_error

When true any authenticator error will result in authorization failure, more secure

# print

Returns a value to the player, different to success as this does not signal the end of your command

Authentication

# add_authenticator(authenticator)

Adds an authorization function, function used to check if a player if allowed to use a command

Parameters:
  • authenticator : (function) The function you want to register as an authenticator
Returns:
  • (number) The index it was inserted at, used to remove the authenticator
Usage:
-- If the admin_only flag is set, then make sure the player is an admin
local admin_authenticator =
Commands.add_authenticator(function(player, command, flags, reject)
    if flags.admin_only and not player.admin then
        return reject('This command is for admins only!')
    else
        return true
    end
end)
# remove_authenticator(authenticator)

Removes an authorization function, can use the index or the function value

Parameters:
  • authenticator : (function or number) The authenticator to remove, either the index return from add_authenticator or the function used
Returns:
  • (boolean) If the authenticator was found and removed successfully
Usage:
-- Removing the admin authenticator, can not be done during runtime
Commands.remove_authenticator(admin_authenticator)
# authorize(player, command_name)

Mostly used internally, calls all authenticators, returns if the player is authorized

Parameters:
  • player : (LuaPlayer) The player who is using the command, passed to authenticators
  • command_name : (string) The name of the command being used, passed to authenticators
Returns:
  • (boolean) true Player is authorized
  • (string) commands Define value for success
Or
  • (boolean) false Player is unauthorized
  • (string or locale_string) The reason given by the failed authenticator
Usage:
-- Test if a player can use "repeat-name"
local authorized, status = Commands.authorize(game.player, 'repeat-name')

Parse

# add_parse(name, parser)

Adds a parse function which can be called by name (used in add_param) nb: this is not required as you can use the callback directly this just allows it to be called by name

Parameters:
  • name : (string) The name of the parse, should describe a type of input such as number or player, must be unique
  • parser : (function) The function that is ran to parse the input string
Returns:
  • (boolean) Was the parse added, will be false if the name is already used
Usage:
-- Adding a parse to validate integers in a given range
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 rather than a string, thus the param is now the correct type
        return rtn
    end
end)
# remove_parse(name)

Removes a parse function, see add_parse for adding them, cant be done during runtime

Parameters:
  • name : (string) The name of the parse to remove
Usage:
-- Removing a parse
Commands.remove_parse('number-range-int')
# parse(name, input, player, reject)

Intended to be used within other parse functions, runs a parse and returns success and new value

Parameters:
  • name : (string) The name of the parse to call, must be a registered parser
  • input : (string) The input to pass to the parse, must be a string but not necessarily the original input
  • player : (LuaPlayer) The player that is using the command, pass directly from your arguments
  • reject : (function) The reject function, pass directly from your arguments
Returns:
  • (any) The new value for the input, if nil is return then either there was an error or the input was nil
Usage:
-- Parsing an int after first checking it is a number
Commands.add_parse('number', function(input, player, reject)
    local number = tonumber(input)
    if number then return number end
    return reject('Input must be a number value')
end)

Commands.add_parse('number-int', function(input, player, reject)
    local number = Commands.parse('number', input, player, reject)
    if not number then return end
    return math.floor(number)
end)

Getters

# get([player])

Gets all commands that a player is allowed to use, game commands are not included

Parameters:
  • player : (LuaPlayer) The player that you want to get commands of, nil will return all commands (optional)
Returns:
  • (table) All commands that that player is allowed to use, or all commands
Usage:
-- Get the commands you are allowed to use
local commands = Commands.get(game.player)
-- Get all commands that are registered
local commands = Commands.get()
# search(keyword[, player])

Searches command names and help messages to find possible commands, game commands are included

Parameters:
  • keyword : (string) The word which you are trying to find in your search
  • player : (LuaPlayer) The player to get allowed commands of, if nil all commands are searched (optional)
Returns:
  • (table) All commands that contain the key word, and allowed by the player if a player was given
Usage:
-- Get all commands which "repeat"
local commands = Commands.search('repeat')
-- Get all commands which "repeat" and you are allowed to use
local commands = Commands.search('repeat', game.player)

Creation

# new_command(name, help)

Creates a new command object to added details to, this does not register the command to the game api

Parameters:
  • name : (string) The name of the command to be created
  • help : (string) The help message for the command
Returns:
  • (table) This will be used with other functions to define the new command
Usage:
-- Define a new command
Commands.new_command('repeat-name', 'Will repeat you name a number of times in chat.')
# Commands._prototype:add_param(name[, optional=false][, parse][, ...])

Adds a new param to the command this will be displayed in the help and used to parse the input

Parameters:
  • name : (string) The name of the new param that is being added to the command
  • optional : (boolean) Is this param optional, these must be added after all required params (default: false)
  • parse : (string or function) This function will take the input and return a new value, if not given no parse is done (optional)
  • ... : (any) Extra args you want to pass to the parse function; for example if the parse is general use (optional)
Returns:
  • (table) Pass through to allow more functions to be called
Usage:
-- Adding a required param which has a parser pre-defined
command:add_param('repeat-count', 'number-range-int', 1, 5)
-- Adding an optional param which has a custom parse, see Commands.add_parse for details
command:add_param('smiley', true, function(input, player, reject)
    if not input then return end
    return input:lower() == 'true' or input:lower() == 'yes' or false
end)
# Commands._prototype:set_defaults(defaults)

Add default values to params, only as an effect if the param is optional, if default value is a function it is called with the acting player

Parameters:
  • defaults : (table) A table which is keyed by the name of the param and the value is the default value for that param
Returns:
  • (table) Pass through to allow more functions to be called
Usage:
-- Adding default values
command:set_defaults{
    smiley = false,
    -- not in example just used to show arguments given
    player_name = function(player)
        return player.name
    end
}
# Commands._prototype:set_flag(name[, value=true])

Adds a flag to the command which is passed via the flags param to the authenticators, can be used to assign command roles or usage type

Parameters:
  • name : (string) The name of the flag to be added, set to true if no value is given
  • value : (any) The value for the flag, can be anything that the authenticators are expecting (default: true)
Returns:
  • (table) Pass through to allow more functions to be called
Usage:
-- Setting a custom flag
command:set_flag('admin_only', true)
-- When value is true it does not need to be given
command:set_flag('admin_only')
# Commands._prototype:add_alias(...)

Adds an alias, or multiple, that will be registered to this command, eg /teleport can be used as /tp

Parameters:
  • ... : (string) Any amount of aliases that you want this command to be callable with
Returns:
  • (table) Pass through to allow more functions to be called
Usage:
-- Added multiple aliases to a command
command:add_alias('name', 'rname')
# Commands._prototype:enable_auto_concat()

Enables auto concatenation for this command, all params after the last are added to the last param, useful for reasons or other long text input nb: this will disable max param checking as they will be concatenated onto the end of that last param

Returns:
  • (table) Pass through to allow more functions to be called
Usage:
-- Enable auto concat for a command
command:enable_auto_concat()
# Commands._prototype:register(callback)

Adds the callback to the command and registers: aliases, params and help message with the base game api nb: this must be the last function ran on the command and must be done for the command to work

Parameters:
  • callback : (function) The callback for the command, will receive the player running command, and any params added with add_param
Usage:
-- Registering your command to the base game api
command:register(function(player, repeat_count, smiley, raw)
    local msg = ') '..player.name
    if smiley then msg = ':'..msg end

    for 1 = 1, repeat_count do
        Command.print(1..msg)
    end
end)

Status

# success([value])

Sends a value to the player, followed by a command complete message, returning a value will trigger this automatically

Parameters:
  • value : (any) The value to return to the player, if nil then only the success message is returned (optional)
Returns:
  • (Commands.defines.success) Return this to the command handler to prevent two success messages
Usage:
-- Print a custom success message
return Commands.success('Your message has been printed')
-- Returning the value has the same result
return 'Your message has been printed'
# print(value, colour)

Sends a value to the player, different to success as this does not signal the end of your command

Parameters:
  • value : (any) The value that you want to return to the player
  • colour : (table) The colour of the message that the player sees
Usage:
-- Output a message to the player
Commands.print('Your command is in progress')
# error([error_message=''][, play_sound=utility/wire_pickup])

Sends an error message to the player and when returned will stop execution of the command nb: this is for non fatal errors meaning there is no log of this event, use during register callback

Parameters:
  • error_message : (string) An optional error message that can be sent to the user (default: '')
  • play_sound : (string) The sound to play for the error (default: utility/wire_pickup)
Returns:
  • (Commands.defines.error) Return this to command handler to terminate execution
Usage:
-- Send an error message to the player, and stops further code running
return Commands.error('The player you selected is offline')
# internal_error(success, command_name, error_message)

Sends an error to the player and logs the error, used internally please avoid direct use nb: use error(error_message) within your callback to trigger do not trigger directly as code execution may still continue

Parameters:
  • success : (boolean) The success value returned from pcall, or just false to trigger error
  • command_name : (string) The name of the command this is used within the log
  • error_message : (string) The error returned by pcall or some other error, this is logged and not returned to player
Returns:
  • (boolean) The opposite of success so true means to cancel execution, used internally
Usage:
-- Used in the command system to log handler errors
local success, err = pcall(command_data.callback, player, table.unpack(params))
if Commands.internal_error(success, command_data.name, err) then
    return command_log(player, command_data, 'Internal Error: Command Callback Fail', raw_params, command_event.parameter, err)
end
# run_command(command_event)

Main event function that is ran for all commands, used internally please avoid direct use

Parameters:
  • command_event : (table) Passed directly from the add_command function
Usage:
Commands.run_command(event)