From 247461d21dee6ca52d4b5e808b2ff9e8c3c62d95 Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Wed, 10 Apr 2019 15:25:47 +0100 Subject: [PATCH] Improved comment in commands --- expcore/commands.lua | 263 ++++++++++++++++++++++++++----------------- 1 file changed, 159 insertions(+), 104 deletions(-) diff --git a/expcore/commands.lua b/expcore/commands.lua index 3a3baeea..b9eb5186 100644 --- a/expcore/commands.lua +++ b/expcore/commands.lua @@ -3,115 +3,170 @@ -- @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: - -- adds an admin only authenticator where if a command has the tag admin_only: true - -- then will only allow admins to use this command - Commands.add_authenticator(function(player,command,tags,reject) - if tags.admin_only then -- the command has the tag admin_only set to true - if player.admin then -- the player is an admin - return true -- no return is needed for success but is useful to include - else -- the player is not admin - -- you must return to block a command, they are a few ways to do this: - -- return false -- most basic and has no custom error message - -- return reject -- sill no error message and is here in case people dont know its a function - -- reject() -- rejects the player, return not needed but please return if possible - -- return reject() -- rejects the player and has a failsafe return to block command - -- reject('This command is for admins only!') -- reject but with custom error message, return not needed but please return if possible - return reject('This command is for admins only!') -- reject but with custom error message and has return failsafe + 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 -- command does not require admin - return true -- no return is needed for success but is useful to include + 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: - -- adds a parse that will cover numbers within the given range - -- input, player and reject are common to all parse functions - -- range_min and range_max are passed to the function from add_param + 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 -- check if it is nil or out of the range - -- invalid input for we will reject the input, they are a few ways to do this: - -- dont return anything -- will print generic input error - -- return false -- this WILL NOT reject the input as false can be a valid output - -- return reject -- will print generic input error - -- return reject() -- will print generic input error with no please check type message - -- reject() -- if you do not return the value then they will be a duplicate message - return reject('Number entered is not in range: '..range_min..', '..range_max) -- reject with custom error - else - return rtn -- returns the number value this will be passed to the command callback - end - end) - ->>>>Example Command: - - -- adds a command that will print the players name a given number of times - -- and can only be used by admin to show how auth works - Commands.new_command('repeat-name','Will repeat you name a number of times in chat.') -- creates the new command with the name "repeat-name" and a help message - :add_param('repeat-count',false,'number-range-int',1,5) -- adds a new param called "repeat-count" that is required and is type "number_range_int" the name can be used here as add_parse was used - :add_param('smiley',true,function(input,player,reject) -- this param is optional and has a custom parse function where add_parse was not used before hand - if not input then return end -- when they is an optional param input may be nil, you can return a default value here, but using nil will allow add_defaults to pick a default - if input:lower() == 'true' or input:lower() == 'yes' then - return true -- the value is truthy so true is returned - else - -- it should be noted that this function will be ran even when the param is not present - -- in this case input is nil and so a default can be returned, see above - return false -- false is returned other wise - end - end) - :set_defaults{smiley=false} -- adds false as the default for smiley - :set_flag('admin_only',true) -- adds the tag admin_only: true which because of the above authenticator means you must be added to use this command - :add_alias('name','rname') -- adds two aliases "name" and "rname" for this command which will work as if the ordinal name was used - --:enable_auto_concat() -- cant be used due to optional param here, but this will make all user input params after the last expected one be added to the last expected one - :register(function(player,repeat_count,smiley,raw) -- this registers the command to the game, notice the params are what were defined above - -- prints the raw input to show that it can be used - game.print(player.name..' used a command with input: '..raw) - -- some smiley logic - local msg - if smiley then - msg = ':) '..player.name - else - msg = ') '..player.name - end - -- prints your name alot - for i = 1,repeat_count do - Commands.print(i..msg) -- this command is an alias for ("expcore.common").player_return it will print any value to the player/server not just strings - end - -- if you wanted to you can return some values here - -- no return -- only success message is printed - -- Commands.error('optional message here') -- prints an error message - -- return Commands.error('optional message here') -- prints an error message, and stops success message being printed - -- Commands.success('optional message here') -- same as below but success message is printed twice DONT DO this - -- return Commands.success('optional message here') -- prints your message and then the success message - end) - ->>>>Examples With No Comments (for example formatting): - - Commands.add_authenticator(function(player,command,tags,reject) - if tags.admin_only then - if player.admin then - return true - else - return reject('This command is for admins only!') - end - else - return true - end - end) - - 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 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.') - :add_param('repeat-count',false,'number_range_int',1,5) + + 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 + + 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 @@ -120,16 +175,16 @@ end end) :set_defaults{smiley=false} - :set_flag('admin_only',true) - :add_alias('name','rname') + :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 i = 1,repeat_count do - Commands.print(i..msg) + for 1 = 1,repeat_count do + Command.print(1..msg) end end) @@ -148,7 +203,7 @@ Commands.add_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 tags param to the authenticators, can be used to assign command roles or type + 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 @@ -183,7 +238,7 @@ local Commands = { -- @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 - tags: table - any tags which have been set for the command +-- 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) @@ -241,8 +296,8 @@ function Commands.authorize(player,command_name) -- 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, tags: table, reject: function(error_message?: string)) - local success, rtn = pcall(callback,player,command_name,command_data.tags,auth_fail) + -- 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 @@ -360,7 +415,7 @@ function Commands.new_command(name,help) auto_concat=false, min_param_count=0, max_param_count=0, - tags={}, -- stores tags that can be used by auth + 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} }, { @@ -408,18 +463,18 @@ function Commands._prototype:set_defaults(defaults) return self end ---- Adds a tag to the command which is passed via the tags 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 tags separate +--- 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.tags,name) + table.insert(self.flags,name) else -- name is given so its key: value - self.tags[name] = value + self.flags[name] = value end return self end