Added module: ExpGamingCore.Commands

This commit is contained in:
Cooldude2606
2018-06-05 19:32:29 +01:00
parent 6272e29606
commit 9be85f04a6
31 changed files with 686 additions and 289 deletions

View File

@@ -1,74 +1,139 @@
--[[
Explosive Gaming
--- A full ranking system for factorio.
-- @module ExpGamingCore.Commands
-- @alias commands
-- @author Cooldude2606
-- @license https://github.com/explosivegaming/scenario/blob/master/LICENSE
This file can be used with permission but this and the credit below must remain in the file.
Contact a member of management on our discord to seek permission to use our code.
Any changes that you may make to the code are yours but that does not make the script yours.
Discord: https://discord.gg/r6dC2uK
]]
--Please Only Edit Below This Line-----------------------------------------------------------
local command_calls = {}
local command_data = {}
--- Used as an error constant for validation
-- @field commands.error
-- @usage return commands.error, 'err message'
-- @usage return commands.error('err message')
commands.error = setmetatable({},{__call=function(...) return ... end})
commands._add_command = commands.add_command
local data = {}
--- Uses a commands data to return the inputs as a string
-- @usage command = command_data[command_name]
-- command_inputs(command) -- returns "<input1> <input2> "
-- @tparam table the data for the command being run
-- @treturn string the inputs in string format
local function command_inputs(command)
if not is_type(command,'table') then return end
local inputs = ''
for _,input in pairs(command.inputs) do
if input == true then break end
inputs = inputs..'<'..input..'> '
--- Index of all command data
-- @field commands.data
-- @usage commands.command_name -- returns command data
-- @usage commands.data -- returns all data
-- @tparam ?string|table|event key the command that will be returned: string is the name, table is the command data, event is event from add_command
-- @treturn table the command data
setmetatable(commands,{
__index=function(tbl,key) return is_type(key,'table') and (key.command and rawget(data,key.name) or key) or key == 'data' and data or rawget(data,key) end
})
--- Collection of funcations that can be used to validate inputs
-- @table commands.validate
-- @usage commands.validate[type](value,event,...)
-- @tparam string type the type that the value should be
-- @param value the value that will be tested
-- @param ... any other data that can be passed to the function
-- @return[1] the validated value
-- @return[2] error constant
-- @return[2] the err message
-- @field __comment replace _ with - the ldoc did not like me using - in the names
-- @field string basicly does nothing but a type filed is required
-- @field string_inf same as string but is infite in length, must be last arg
-- @field string_len same as string but can define a max lengh
-- @field number converts the input into a number
-- @field number_int conerts the input to a number and floors it
-- @field number_range allows a number in a range min < X <= max
-- @field number_range allows a number in a range after it has been floored min < math.floor(X) <= max
-- @field player converts the input into a valid player
-- @field player_online converts the input to a player if the player is online
-- @field player_alive converts the input to a player if the player is online and alive
-- @field player_rank converts the input to a player if the player is a lower rank than the user
-- @field player_rank-online converts the input to a player if the player is a lower rank than the user and online
-- @field player_rank_alive converts the input to a player if the player is a lower rank than the user and online and alive
commands.validate = {
['string']=function(value,event) return tostring(value) end,
['string-inf']=function(value,event) return tostring(value) end,
['string-len']=function(value,event,max) return tostring(value) and tostring(value):len() <= max and tostring(value) or commands.error{'commands.error-string-len'} end,
['number']=function(value,event) return tonumber(value) or commands.error{'commands.error-number'} end,
['number-int']=function(value,event) return tonumber(value) and math.floor(tonumber(value)) or commands.error{'commands.error-number'} end,
['number-range']=function(value,event,min,max) return tonumber(value) and tonumber(value) > min and tonumber(value) <= max and tonumber(value) or commands.error{'commands.error-number-range'} end,
['number-range-int']=function(value,event,min,max) return tonumber(value) and math.floor(tonumber(value)) > min and math.floor(tonumber(value)) <= max and math.floor(tonumber(value)) or commands.error{'commands.error-number-range'} end,
['player']=function(value,event) return Game.get_player(player) or commands.error{'commands.error-player'} end,
['player-online']=function(value,event) local player,err = commands.validate['player'](value) return err and commands.error(err) or player.conected and player or commands.error{'commands.error-player-online'} end,
['player-alive']=function(value,event) local player,err = commands.validate['player-online'](value) return err and commands.error(err) or player.character and player.character.health > 0 and player or commands.error{'commands.error-player-alive'} end,
['player-rank']=function(value,event) local player,err = commands.validate['player'](value) return err and commands.error(err) or Ranking and Ranking.get_rank(player).power > Ranking.get_rank(event).power or not player.admin or commands.error{'commands.error-player-rank'} end,
['player-rank-online']=function(value,event) local player,err = commands.validate['player-online'](value) return err and commands.error(err) or Ranking and Ranking.get_rank(player).power > Ranking.get_rank(event).power or not player.admin or commands.error{'commands.error-player-rank'} end,
['player-rank-alive']=function(value,event) local player,err = commands.validate['player-alive'](value) return err and commands.error(err) or Ranking and Ranking.get_rank(player).power > Ranking.get_rank(event).power or not player.admin or commands.error{'commands.error-player-rank'} end
}
--- Returns the inputs of this command as a formated string
-- @usage commands.format_inputs('interface') -- returns <code> (if you have ExpGamingCore.Server)
-- @tparam ?string|table|event command the command to get the inputs of
-- @treturn string the formated string for the inputs
function commands.format_inputs(command)
local command = commands[command]
if not is_type(command,'table') then error('Command is not valid',2) end
local rtn = ''
for name,data in pairs(command.inputs) do
if data[1] == false then rtn=rtn..string.format('[%s] ',name)
else rtn=rtn..string.format('<%s> ',name) end
end
return inputs
return rtn
end
--- Uses the command data and the event to return a table of the args
-- @usage command = command_data[command_name]
-- command_args(event,command) -- return {input1='one',input2='two'}
-- @tparam defines.events.on_console_command event the event rasied by the command
-- @tparam table command the data for the command being run
-- @treturn table a table version of the event.parameter based on expected inputs
local function command_args(event,command)
if not event or not is_type(command,'table') then return end
local args = {}
-- haddles no parameters given
--- Used to validate the arguments of a command, will understand strings with "" as a single param else spaces divede the params
-- @usage commands.validate_args(event) -- returns args table
-- @tparam table event this is the event created by add_command not on_console_command
-- @treturn[1] table the args for this command
-- @return[2] command.error
-- @treturn string the error that happend while parsing the args
function commands.validate_args(event)
local command = commands[event.name]
if not is_type(command,'table') then error('Command not valid',2) end
local rtn = {}
local count = 0
local count_opt = 0
for name,data in pairs(command.inputs) do count = count + 1 if data[1] == false then count_opt = count_opt + 1 end end
-- checks that there is some args given if there is ment to be
if not event.parameter then
if #command.inputs > 0 then return args, false
else return args, true
end
if count == count_opt then return rtn
else return commands.error('invalid-inputs') end
end
-- finds all the words and cheaks if the right number were given
-- splits the args into words so that it can be used to asign values
local words = string.split(event.parameter,' ')
if table.last(command.inputs) == true then
if #words < #command.inputs-1 then return args, false end
else
if #words < #command.inputs then return args, false end
end
-- if it is the right number then process and return the args
for index,input in pairs(command.inputs) do
if command.inputs[index+1] == true then
args[input] = table.concat(words,' ',index)
break
else
args[input] = words[index]
local index = 0
for _,word in pairs(words) do
index = index+1
if not word then break end
local pos, _pos = word:find('"')
while pos and pos == _pos do
local next = table.remove(words,index+1)
if not next then return commands.error('invalid-parse') end
words[index] = words[index]..' '..next
_pos = words[index]:find('"',pos+1)
end
end
return args, true
-- assigns the values from the words to the args
local index = 0
for name,data in pairs(command.inputs) do
index = index+1
local arg = words[index]
if not arg and not data[1] then return commands.error('invalid-inputs') end
if data[2] == 'string-inf' then rtn[name] = table.concat(words,' ',index) break end
local valid = is_type(data[2],'function') and data[2] or commands.validate[data[2]] or error('Invalid type for command: "'..command.name..'/'..name..'"')
local temp_tbl = table.deep_copy(data) table.remove(temp_tbl,1) table.remove(temp_tbl,2)
local value, err = valid(arg,event,unpack(temp_tbl))
if value == commands.error then return value, err end
rtn[name] = value
end
return rtn
end
--- Used to return all the commands a player can use
-- @usage get_commands(1) -- return {{command data},{command data}}
-- @param player the player refreced by string|number|LuaPlayer|event
-- @usage get_commands(1) -- return table of command data for each command that the player can use
-- @tparam ?index|name|player| player the player to test as
-- @treturn table a table containg all the commands the player can use
function commands.get_commands(player)
local commands = {}
local player = Game.get_player(player)
if not player then return commands end
if not player then return error('Invalid player',2) end
local rank = Ranking.get_rank(player)
for name,data in pairs(command_data) do
for name,data in pairs(data) do
if rank:allowed(name) then table.insert(commands,data) end
end
return commands
@@ -76,27 +141,27 @@ end
--- Used to call the custom commands
-- @usage You dont its an internal command
-- @tparam defines.events.on_console_command command the event rasied by the command
-- @tparam table command the event rasied by the command
local function run_custom_command(command)
local command_data = command_data[command.name]
local data = commands.data[command.name]
local player_name = Game.get_player(command) and Game.get_player(command).name or 'server'
-- is the player allowed to use this command
if is_type(Ranking,'table') and Ranking._presets and Ranking._presets().meta.rank_count > 0 and not Ranking.get_rank(player_name):allowed(command.name) then
-- is the player allowed to use this command, if no ExpGamingCore.Ranking then this is ingroned
if Ranking and Ranking.meta and Ranking.meta.rank_count > 0 and not Ranking.get_rank(player_name):allowed(command.name) then
player_return({'commands.unauthorized'},defines.textcolor.crit)
if game.player then game.player.play_sound{path='utility/cannot_build'} end
game.write_file('commands.log',
game.tick
..' Player: "'..player_name..'"'
..' Failed to use command (Unauthorized): "'..command.name..'"'
..' With args of: '..table.tostring(command_args(command,command_data))
..' With args of: '..table.tostring(commands.validate_args(command))
..'\n'
, true, 0)
return
end
-- gets the args for the command
local args, valid = command_args(command,command_data)
if not valid then
player_return({'commands.invalid-inputs',command.name,command_inputs(command_data)},defines.textcolor.high)
local args, err = commands.validate_args(command)
if args == commands.error then
player_return({'commands.'..err,command.name,commands.format_inputs(data)},defines.textcolor.high)
if game.player then game.player.play_sound{path='utility/deconstruct_big'} end
game.write_file('commands.log',
game.tick
@@ -108,7 +173,7 @@ local function run_custom_command(command)
return
end
-- runs the command
local success, err = pcall(command_calls[command.name],command,args)
local success, err = pcall(data.callback,command,args)
if not success then error(err) end
if err ~= commands.error and player_name ~= 'server' then player_return({'commands.command-ran'},defines.textcolor.info) end
game.write_file('commands.log',
@@ -120,32 +185,54 @@ local function run_custom_command(command)
, true, 0)
end
-- this is a set of constants you can use
commands._add_command = commands.add_command --if you dont want to use the custom commands interface
commands.expgaming = true --if you want to test if the custom commands are present
commands.error = {} --if returned during a custom command, Command Complete message not printed
--- Used to define commands
-- @usage inputs = {'player','reason',true}
-- commands.add_command('ban','bans a player',inputs,function() return end)
-- @usage --see examples in file
-- @tparam string name the name of the command
-- @tparam[opt='No Description'] string description the description of the command
-- @tparam[opt={'parameter',true}] table inputs a table of the inputs to be used, last index being true makes the last parameter open ended (longer than one word)
-- @tparam function event the function to call on the event
commands.add_command = function(name, description, inputs, event)
if command_calls[name] then return end
if not is_type(name,'string') then return end
if not is_type(event,'function') then return end
-- @tparam[opt=an infite string] table inputs a table of the inputs to be used, last index being true makes the last parameter open ended (longer than one word)
-- @tparam function callback the function to call on the event
commands.add_command = function(name, description, inputs, callback)
if commands[name] then error('That command is already registered',2) end
if not is_type(name,'string') then error('Command name has not been given') end
if not is_type(callback,'function') or not is_type(inputs,'table') then
if is_type(inputs,'function') then commands._add_command(name,description,inputs)
else error('Invalid args given to add_command') end
end
verbose('Created Command: '..name)
local description = is_type(description,'string') and description or 'No Description'
local inputs = is_type(inputs,'table') and inputs or {'parameter',true}
command_data[name] = {
-- test for string and then test for locale string
local description = is_type(description,'string') and description
or is_type(description,'table') and is_type(description[1],'string') and string.find(description[1],'.+[.].+') and {description,''}
or 'No Description'
local inputs = is_type(inputs,'table') and inputs or {['param']={false,'string-inf'}}
data[name] = {
name=name,
description=description,
inputs=inputs
inputs=inputs,
callback=callback
}
command_calls[name] = event
commands._add_command(name,command_inputs(command_data[name])..'- '..description,function(...)
local help = is_type(description,'string') and commands.format_inputs(name)..'- '..description
or is_type(description,'table') and is_type(description[1],'string') and string.find(description[1],'.+[.].+') and {description,commands.format_inputs(name)..'- '}
or commands.format_inputs(name)
data[name].help = help
commands._add_command(name,help,function(...)
local success, err = pcall(run_custom_command,...)
if not success then error(err) end
end)
end
end
--[[
command example
locale file
[foo]
description=__1__ this is a command
commands.add_command('foo',{'foo.description'},{
['player']={true,'player'}, -- a required arg that must be a valid player
['number']={true,'number-range',0,10}, -- a required arg that must be a number 0<X<=10
['reason']={false,'string-inf'} -- an optinal arg that is and infite lengh (useful for reasons)
},function(event,args)
args.player.print(args.number)
if args.reasons then args.player.print(args.reason) end
end)
]]

View File

@@ -84,8 +84,10 @@ Ranking.groups = setmetatable({},{
Ranking.meta = setmetatable({},{
__metatable=false,
__call=function(tbl)
local count = 0
rawset(tbl,'time_ranks',{})
for name,rank in pairs(Ranking.ranks) do
count=count+1
if not rawget(tbl,'default') and rank.is_default then rawset(tbl,'default',rank.name) end
if not rawget(tbl,'root') and rank.is_root then rawset(tbl,'root',rank.name) end
if rank.time then
@@ -94,6 +96,7 @@ Ranking.meta = setmetatable({},{
if not rawget(tbl,'time_lowest') or rank.time < tbl.time_lowest then rawset(tbl,'time_lowest',rank.time) end
end
end
rawset(tbl,'rank_count',count)
if not rawget(tbl,'default') then error('No default rank') end
if not rawget(tbl,'root') then error('No root rank') end
end,
@@ -444,6 +447,7 @@ Ranking.on_init=function(self)
end
end
-- asigning of powers
-- @todo need a better system for non liner rank trees
verbose('Assigning Rank Powers')
local power = 1
local function set_powers(rank)

View File

@@ -193,7 +193,10 @@ end
-- @tparam[opt] ?Server._thread|true use_thread run the command on a premade thread or let it make its own
-- @tparam[opt] table env run the env to run the command in must have _env key as true to be
-- @param[opt] ... any args you want to pass to the function
-- @return if no thread then it will return the value(s) returned by the callback
-- @treturn[1] boolean if no thread then it will return a true for the success
-- @return[1] if no thread then it will return the value(s) returned by the callback
-- @treturn[2] boolean if no thread then it will return a false for the success
-- @treturn[2] string if no thread then it will return a an err value
function Server.interface(callback,use_thread,env,...)
if use_thread then
-- if use_thread is true then it makes a new thread
@@ -217,15 +220,20 @@ function Server.interface(callback,use_thread,env,...)
use_thread:open()
Server.queue_thread(use_thread)
else
local callback = is_type(callback,'function') and callback or loadstring(callback)
local _callback = callback
if not is_type(callback,'function') then
local rtn, err = loadstring(callback)
if err then return false, err end
_callback = rtn
end
if is_type(env,'table') and env._env == true then
local sandbox, success, err = Manager.sandbox(callback,env,unpack(thread.data))
if not success then error(err) end
return err
local sandbox, success, err = Manager.sandbox(_callback,env,...)
if not success then error(err) return success,err
else return success, unpack(err) end
else
local sandbox, success, err = Manager.sandbox(callback,{},env,unpack(thread.data))
if not success then error(err) end
return err
local sandbox, success, err = Manager.sandbox(_callback,{},env,...)
if not success then error(err) return success,err
else return success, unpack(err) end
end
end
end

View File

@@ -10,7 +10,9 @@
--- Runs the given input from the script
-- @command interface
-- @param code The code that will be ran
commands.add_command('interface', 'Runs the given input from the script', {'code',true}, function(event,args)
commands.add_command('interface',{'Server.interface-description'}, {
['code']={true,'string-inf'}
}, function(event,args)
local callback = args.code
-- looks for spaces, if non the it will prefix the command with return
if not string.find(callback,'%s') and not string.find(callback,'return') then callback = 'return '..callback end
@@ -28,7 +30,7 @@ commands.add_command('interface', 'Runs the given input from the script', {'code
-- runs the function
local success, err = Server.interface(callback,false,env)
-- if there is an error then it will remove the stacktrace and return the error
if not success and is_type(err,'string') then local _end = string.find(err,'stack traceback') if _end then err = string.sub(err,0,_end-2) end end
if not success and is_type(err,'string') then local _end = string.find(err,':1:') if _end then err = string.sub(err,_end+4) end end
-- if there is a value returned that is not nill then it will return that value
if err or err == false then player_return(err) end
end)

View File

@@ -102,6 +102,7 @@ end
-- @see EmitEmbededParamaters
function Sync.emit_embeded(args)
if not is_type(args,'table') then error('Args table not given to Sync.emit_embeded',2) end
if not game then error('Game has not loaded',2) end
local title = is_type(args.title,'string') and args.title or ''
local color = is_type(args.color,'string') and args.color:find("0x") and args.color or '0x0'
local description = is_type(args.description,'string') and args.description or ''

View File

@@ -14,8 +14,11 @@
"version": "3.4.0",
"location": "url",
"dependencies": {
"ExpGamingLib": ">=4.0.0",
"ExpGamingCore/Ranking": ">=4.0.0"
"ExpGamingLib": "^4.0.0",
"FactorioStdLib.Table": "^0.8.0",
"FactorioStdLib.Color": "^0.8.0",
"FactorioStdLib.Game": "^0.8.0",
"ExpGamingCore/Ranking": "?^4.0.0"
}
},
"Gui": {
@@ -26,10 +29,10 @@
"version": "3.4.0",
"location": "url",
"dependencies": {
"FactorioModGui": ">=1.0.0",
"ExpGamingLib": ">=4.0.0",
"ExpGamingCore/Ranking": ">=4.0.0",
"ExpGamingCore/Server": "?>=4.0.0"
"FactorioModGui": "^1.0.0",
"ExpGamingLib": "^4.0.0",
"ExpGamingCore/Ranking": "^4.0.0",
"ExpGamingCore/Server": "?^4.0.0"
}
},
"Ranking": {
@@ -40,10 +43,11 @@
"version": "3.4.0",
"location": "url",
"dependencies": {
"ExpGamingLib": ">=4.0.0",
"FactorioStdLib.Color": ">=0.8.0",
"FactorioStdLib.Table": ">=0.8.0",
"ExpGamingCore.Server": "?>=4.0.0"
"ExpGamingLib": "^4.0.0",
"FactorioStdLib.Color": "^0.8.0",
"FactorioStdLib.Table": "^0.8.0",
"FactorioStdLib.Game": "^0.8.0",
"ExpGamingCore.Server": "?^4.0.0"
}
},
"Server": {
@@ -54,10 +58,11 @@
"version": "3.4.0",
"location": "url",
"dependencies": {
"ExpGamingLib": ">=4.0.0",
"FactorioStdLib.Table": ">=0.8.0",
"ExpGamingCore.Ranking": "?>=4.0.0",
"ExpGamingCore.Commands": "?>=4.0.0"
"ExpGamingLib": "^4.0.0",
"FactorioStdLib.Table": "^0.8.0",
"FactorioStdLib.Game": "^0.8.0",
"ExpGamingCore.Ranking": "?^4.0.0",
"ExpGamingCore.Commands": "?^4.0.0"
}
},
"Sync": {
@@ -68,11 +73,11 @@
"version": "3.4.0",
"location": "url",
"dependencies": {
"ExpGamingLib": ">=4.0.0",
"FactorioStdLib.Color": ">=0.8.0",
"FactorioStdLib.Table": ">=0.8.0",
"ExpGamingCore.Ranking": "?>=4.0.0",
"ExpGamingCore.Gui": "?>=4.0.0"
"ExpGamingLib": "^4.0.0",
"FactorioStdLib.Color": "^0.8.0",
"FactorioStdLib.Table": "^0.8.0",
"ExpGamingCore.Ranking": "?^4.0.0",
"ExpGamingCore.Gui": "?^4.0.0"
}
}
},