Merge branch 'release/5.0.0'

This commit is contained in:
Cooldude2606 committed 2019-03-08 18:15:00 +00:00
commit bc95727cdf
394 files changed
+6978 -916

No files matched your search

-754
View File
@@ -1,754 +0,0 @@
--- Factorio Softmod Manager
-- @module FSM
-- @alias Manager
-- @author Cooldude2606
-- @usage Manager = require("FactorioSoftmodManager")
local moduleIndex = require("/modules/index")
local Manager = {}
-- this is a constant that is used to represent the server
SERVER = setmetatable({index=0,name='<server>',online_time=0,afk_time=0,print=print,admin=true,valid=true,__self={}},{__index=function(tbl,key) if type(game.players[1][key]) == 'function' then return function() end else return nil end end})
--- Setup for metatable of the Manager to force read only nature
-- @usage Manager() -- runs Manager.loadModdules()
-- @usage Manager[name] -- returns module by that name
-- @usage tostring(Manager) -- returns formated list of loaded modules
local ReadOnlyManager = setmetatable({},{
__metatable=false,
__index=function(tbl,key)
-- first looks in manager and then looks in mander.loadModules
return rawget(Manager,key) ~= nil and rawget(Manager,key) or rawget(Manager.loadModules,key)
end,
__call=function(tbl)
-- if there are no modules loaded then it loads them
if #tbl.loadModules == 0 then
tbl.loadModules()
end
end,
__newindex=function(tbl,key,value)
-- provents the changing of any key that is not currentState
if key == 'currentState' then
-- provides a verbose that is always emited describing the change in state
Manager.verbose(string.rep('__',10)..'| Start: '..value..' |'..string.rep('__',10),true)
Manager.verbose('The verbose state is now: '..tostring(Manager.setVerbose[value]),true)
rawset(Manager,key,value)
else error('Manager is read only please use included methods',2) end
end,
__tostring=function(tbl)
-- acts as a redirect
return tostring(Manager.loadModules)
end
})
local function setupModuleName(name)
-- creates a table that acts like a string but is read only
return setmetatable({},{
__index=function(tbl,key) return name end,
__newindex=function(tbl,key,value) error('Module Name Is Read Only') end,
__tostring=function(tbl) return name end,
__concat=function(val1,val2) return type(val1) == 'string' and val1..name or name..val2 end,
__metatable=false,
})
end
Manager.currentState = 'selfInit'
-- selfInit > moduleLoad > moduleInit > modulePost > moduleEnv
--- Default output for the verbose
-- @usage Manager.verbose('Hello, World!')
-- @tparam string rtn the value that will be returned though verbose output
Manager._verbose = function(rtn)
-- creates one file per game, ie clears file on reset
if game and Manager.setVerbose._output ~= true then Manager.setVerbose._output=true game.write_file('verbose.log',rtn)
elseif game then game.write_file('verbose.log','\n'..rtn,true) end
-- standard print and log, _log is a version of log which is ln1 of control.lua for shorter log lines
if print then print(rtn) end
if _log then _log(rtn) end
end
--- Used to call the output of the verbose when the current state allows it
-- @usage Manager.verbose('Hello, World!')
-- @tparam string rtn the value that will be returned though verbose output
-- @tparam string action is used to decide which verbose this is error || event etc
Manager.verbose = function(rtn,action)
local settings = Manager.setVerbose
local state = Manager.currentState
if Manager.error and state == Manager.error.__crash then return end
-- if ran in a module the the global moduleName is present
local rtn = type(rtn) == table and serpent.line(rtn) or tostring(rtn)
if moduleName then rtn='['..moduleName..'] '..rtn
else rtn='[FSM] '..rtn end
-- module_verbose is a local override for a file, action is used in the manager to describe an extra type, state is the current state
-- if action is true then it will always trigger verbose
if module_verbose or (action and (action == true or settings[action])) or (not action and settings[state]) then
if type(settings.output) == 'function' then
-- calls the output function, not pcalled as if this fails some thing is very wrong
settings.output(rtn)
else
error('Verbose set for: '..state..' but output can not be called',2)
end
end
end
--- Main logic for allowing verbose at different stages though out the script
-- @function Manager.setVerbose
-- @usage Manager.setVerbose{output=log}
-- @tparam newTbl settings the table that will be searched for settings to be updated
-- @usage Manager.setVerbose[setting] -- returns the value of that setting
-- @usage tostring(Manager.setVerbose) -- returns a formated list of the current settings
Manager.setVerbose = setmetatable(
--- Different verbose settings used for setVerbose
-- @table Manager.verboseSettings
-- @tfield boolean selfInit called while the manager is being set up
-- @tfield boolean moduleLoad when a module is required by the manager
-- @tfield boolean moduleInit when and within the initation of a module
-- @tfield boolean modulePost when and within the post of a module
-- @tfield boolean moduleEnv during module runtime, this is a global option set within each module(module_verbose=true ln:1) for fine control
-- @tfield boolean eventRegistered when a module registers its event handlers
-- @tfield boolean errorCaught when an error is caught during runtime
-- @tfield function output can be: print || log || or other function
-- @field _output a constant value that can used to store output data
{
selfInit=true,
moduleLoad=false,
moduleInit=false,
modulePost=false,
moduleEnv=false,
eventRegistered=false,
errorCaught=true,
output=Manager._verbose,
_output={}
},
{
__metatable=false,
__call=function(tbl,settings)
-- does not allow any new keys, but will override any existing ones
for key,value in pairs(settings) do
if rawget(tbl,key) ~= nil then
Manager.verbose('Verbose for: "'..key..'" has been set to: '..tostring(value))
rawset(tbl,key,value)
end
end
end,
__newindex=function(tbl,key,value)
-- stops creationg of new keys
error('New settings cannot be added during runtime',2)
end,
__index=function(tbl,key)
-- will always return a value, never nil
return rawget(tbl,key) or false
end,
__tostring=function(tbl)
-- a simple concat function for the settings
local rtn = ''
for key,value in pairs(tbl) do
if type(value) == 'boolean' then
rtn=rtn..key..': '..tostring(value)..', '
end
end
return rtn:sub(1,-3)
end
}
)
-- call to verbose to show start up, will always be present
Manager.verbose(string.rep('__',10)..'| Start: selfInit |'..string.rep('__',10),true)
Manager.verbose('The verbose state is: '..tostring(Manager.setVerbose.selfInit),true)
--- Used to avoid conflicts in the global table
-- @usage global[key] -- used like the normal global table
-- @usage global{'foo','bar'} -- sets the default value
-- @usage global(true) -- restores global to default
-- @usage global(mopdule_name) -- returns that module's global
-- @tparam[opt={}] ?table|string|true if table then the default for the global, if a string then the module to get the global of, if true then reset the global to default
-- @treturn table the new global table for that module
Manager.global=setmetatable({__defaults={},__global={
__call=function(tbl,default) return Manager.global(default) end,
__index=function(tbl,key) return rawget(Manager.global(),key) or tbl(key) end,
__newindex=function(tbl,key,value) rawset(Manager.global(),key,value) end,
__pairs=function(tbl)
local tbl = Manager.global()
local function next_pair(tbl,k)
k, v = next(tbl, k)
if type(v) ~= nil then return k,v end
end
return next_pair, tbl, nil
end
}},{
__call=function(tbl,default,metatable_src)
-- creates varible link to global and module information, use of a metatable is for already formed globals
local Global = _G.global
local metatable = getmetatable(metatable_src)
local moduleName = type(default) == 'string' and default or metatable and metatable._moduleName or moduleName
local module_path = type(default) == 'string' and Manager.loadModules.__load[default] or metatable and metatable._module_path or module_path
-- if there is no name or path then it will return and unedited version of global
if not module_path or not moduleName then return _G.global end
-- edits the link to global to be the corrected dir, path varible is also created
local path = 'global'
for dir in module_path:gmatch('%a+') do
path = path..'.'..dir
if not rawget(Global,dir) then Manager.verbose('Added Global Dir: '..path) rawset(Global,dir,{}) end
Global = rawget(Global,dir)
end
-- the default value is set if there was a default given
if type(default) == 'table' then Manager.verbose('Default global has been set for: global'..string.sub(module_path:gsub('/','.')),2) rawset(rawget(tbl,'__defaults'),tostring(moduleName),default) end
-- if the default value is true then it will reset the global to its default
if default == true and rawget(rawget(tbl,'__defaults'),tostring(moduleName)) then
Manager.verbose('Reset Global Dir to default: '..path)
-- cant set it to be equle otherwise it will lose its global propeity
local function deepcopy(tbl) if type(tbl) ~= 'table' then return tbl end local rtn = {} for key,value in pairs(tbl) do rtn[key] = deepcopy(value) end return rtn end
for key,value in pairs(Global) do rawset(Global,key,nil) end
for key,value in pairs(rawget(rawget(tbl,'__defaults'),tostring(moduleName))) do rawset(Global,key,deepcopy(value)) end
end
-- the metatable is remade if not already present
metatable = metatable or {
__call=function(tbl,default) return Manager.global(default,tbl) end,
__index=function(tbl,key) return rawget(Manager.global(nil,tbl),key) or moduleIndex[key] and Manager.global(key) end,
__newindex=function(tbl,key,value) rawset(Manager.global(nil,tbl),key,value) end,
__pairs=function(tbl)
local tbl = Manager.global(nil,tbl)
local function next_pair(tbl,k)
k, v = next(tbl, k)
if type(v) ~= nil then return k,v end
end
return next_pair, tbl, nil
end,
_module_path=module_path,_moduleName=moduleName
}
return setmetatable(Global,metatable)
end,
__index=function(tbl,key) return rawget(tbl(),key) or rawget(_G.global,key) or moduleIndex[key] and Manager.global(key) end,
__newindex=function(tbl,key,value) rawset(tbl(),key,value) end,
__pairs=function(tbl)
local tbl = Manager.global()
local function next_pair(tbl,k)
k, v = next(tbl, k)
if type(v) ~= nil then return k,v end
end
return next_pair, tbl, nil
end
})
setmetatable(global,Manager.global.__global)
--- Creates a sand box envorment and runs a callback in that sand box; provents global pollution
-- @function Manager.sandbox
-- @usage Manager.sandbox(callback) -- return sandbox, success, other returns from callback
-- @tparam function callback the function that will be ran in the sandbox
-- @param[opt] env any other params that the function will use
-- @usage Manager.sandbox() -- returns and empty sandbox
-- @usage Manager.sandbox[key] -- returns the sand box value in that key
Manager.sandbox = setmetatable({
-- can not use existing keys of _G
verbose=Manager.verbose,
loaded_modules={}, -- this is over riden later
module_verbose=false,
module_exports=false,
_no_error_verbose=true
},{
__metatable=false,
__index=ReadOnlyManager,
__call=function(tbl,callback,env,...)
if type(callback) == 'function' then
-- creates a new sandbox env
local sandbox = tbl()
local env = type(env) == 'table' and env or {}
local _G_mt = getmetatable(_G)
-- creates a new ENV where it will look in the provided env then the sand box and then _G, new indexes saved to sandbox
local tmp_env = setmetatable({},{__index=function(tbl,key) return env[key] or sandbox[key] or rawget(_G,key) end,newindex=sandbox})
tmp_env._ENV = tmp_env
tmp_env._G_mt = _G_mt
-- sets the upvalues for the function
local i = 1
while true do
local name, value = debug.getupvalue(callback,i)
if not name then break else if not value and tmp_env[name] then debug.setupvalue(callback,i,tmp_env[name]) end end
i=i+1
end
-- runs the callback
setmetatable(_G,{__index=tmp_env,newindex=sandbox})
local rtn = {pcall(callback,...)}
local success = table.remove(rtn,1)
setmetatable(_G,_G_mt)
-- this is to allow modules to be access with out the need of using Mangaer[name] also keeps global clean
if success then return success, rtn, sandbox
else return success, rtn[1], sandbox end
else return setmetatable({},{__index=tbl}) end
end
})
--- Allows access to modules via require and collections are returned as one object
-- @function Manager.require
-- @usage local Module = Manager.require(ModuleName)
-- @usage local Module = Manager.require[ModuleName]
-- @usage local SrcData = Manager.require(path)
-- @treturn table the module that was required, one object containg submodules for a
Manager.require = setmetatable({
__require=require
},{
__metatable=false,
__index=function(tbl,key) return tbl(key,nil,true) end,
__call=function(tbl,path,env,mute,noLoad)
local raw_require = rawget(tbl,'__require')
local env = env or {}
-- runs in a sand box becuase sandbox everything
local success, data = Manager.sandbox(raw_require,env,path)
-- if there was no error then it assumed the path existed and returns the data
if success then return unpack(data)
else
if type(path) ~= 'string' then error('Path supplied must be a string; got: '..type(path),2) return end
local override = {}
local softmod = override
local path = path:find('@') and path:sub(1,path:find('@')-1) or path
-- tries to load the module from the modeul index
if moduleIndex[path] and not noLoad or Manager.loadModules.__load[path] then softmod = Manager.loadModules[path] end
-- will then look for any submodules if there are any; only once every module is loaded
for moduleName,subpath in pairs(moduleIndex) do
if moduleName:find(path) == 1 and moduleName ~= path then
local start, _end = moduleName:find(path)
local subname = moduleName:sub(_end+2)
-- does not add the module if it is a subsubmodule; or the key already exitsts
if not softmod then softmod = {} end
if not subname:find('.',nil,true) and not softmod[subname] then softmod[subname] = Manager.require(moduleName,nil,true,true) end
end
end
-- if there is any keys in the softmod it is returned else the errors with the require error
if override ~= softmod then return softmod end
if mute then return false else error(data,2) end
end
end
})
require = Manager.require
--- Loads the modules that are present in the index list
-- @function Manager.loadModules
-- @usage Manager.loadModules() -- loads all moddules in the index list
-- @usage #Manager.loadModules -- returns the number of modules loaded
-- @usage tostring(Manager.loadModules) -- returns a formatted list of all modules loaded
-- @usage pairs(Manager.loadModules) -- loops over the loaded modules moduleName, module
Manager.loadModules = setmetatable({
__load=setmetatable({},{__call=function(self,moduleName)
-- check to provent multiple calls
if self[moduleName] then return end
self[moduleName] = true
self = Manager.loadModules
-- loads the module and its dependices if there are not loaded
local load = moduleIndex[moduleName]
if not load then return end
local path = table.remove(load,1)
Manager.verbose('Loading module: "'..moduleName..'"; path: '..path)
-- loads the parent module
if moduleName:find('.',nil,true) then
local revModuleName = moduleName:reverse()
local start, _end = revModuleName:find('.',nil,true)
local parentName = revModuleName:sub(_end+1):reverse()
Manager.verbose('Loading module parent: "'..parentName..'" for: "'..moduleName..'"; path: '..path)
self.__load(parentName)
end
-- loads the dependices
Manager.verbose('Loading module dependices for: "'..moduleName..'"; path: '..path)
for _,depName in pairs(load) do self.__load(depName) end
self.__load[moduleName] = path
-- runs the module in a sandbox env
local success, module, sandbox = Manager.sandbox(Manager.require.__require,{moduleName=setupModuleName(moduleName),module_path=path},path..'/control')
-- extracts the module into a global index table for later use
if success then
-- verbose to notifie of any globals that were attempted to be created
local globals = ''
for key,value in pairs(sandbox) do globals = globals..key..', ' end
if globals ~= '' then Manager.verbose('Globals caught in "'..moduleName..'": '..globals:sub(1,-3),'errorCaught') end
Manager.verbose('Successfully loaded: "'..moduleName..'"; path: '..path)
-- if it is not a table or nil then it will set up a metatable on it
local currentType = type(rawget(self,moduleName))
if currentType ~= 'nil' and currentType ~= 'table' then
-- if it is a function then it is still able to be called even if more keys are going to be added
-- if it is a string then it will act like one; if it is a number well thats too many metatable indexs
self[moduleName] = setmetatable({__old=self[moduleName]},{
__call=function(self,...) if type(self.__old) == 'function' then self.__old(...) else return self.__old end end,
__tostring=function(self) return self.__old end,
__concat=function(self,val) return self.__old..val end
})
end
-- if you prefere module_exports can be used rather than returning the module
local appendAs = sandbox.module_exports or table.remove(module,1)
if not self[moduleName] then self[moduleName] = appendAs -- if nil it just sets the value
else for key,value in pairs(appendAs) do self[moduleName][key] = value end end -- else it appends the new values
-- if there is a module by this name in _G ex table then it will be indexed to the new module
if rawget(_G,moduleName) and type(rawget(self,moduleName)) == 'table' then setmetatable(rawget(_G,moduleName),{__index=self[moduleName]}) end
if type(rawget(self,moduleName)) == 'table' then self[moduleName]._module_path = path self[moduleName]._moduleName = moduleName end
-- loads the submodule for this softmod
Manager.verbose('Loading submodules for: "'..moduleName..'"; path: '..path)
for subModName,_ in pairs(moduleIndex) do
if subModName:find(moduleName) == 1 and subModName ~= moduleName then self.__load(subModName) end
end
else
Manager.verbose('Failed load: "'..moduleName..'"; path: '..path..' ('..module..')','errorCaught')
for event_name,callbacks in pairs(Manager.event) do Manager.verbose('Removed Event Handler: "'..moduleName..'/'..Manager.event.names[event_name],'eventRegistered') callbacks[moduleName] = nil end
end
end}),
__init=setmetatable({},{__call=function(self,moduleName)
-- check to provent multiple calls
if self[moduleName] or not Manager.loadModules.__load[moduleName] then return end
self[moduleName] = true
self = Manager.loadModules
-- calls on_init for each module
-- looks for init so that init or on_init can be used
local data = self[moduleName]
if type(data) == 'table' and data.init and data.on_init == nil then data.on_init = data.init data.init = nil end
if type(data) == 'table' and data.on_init and type(data.on_init) == 'function' then
Manager.verbose('Initiating module: "'..moduleName..'"')
local success, err = Manager.sandbox(data.on_init,{moduleName=setupModuleName(moduleName),module_path=Manager.loadModules.__load[tostring(moduleName)]},data)
if success then
Manager.verbose('Successfully Initiated: "'..moduleName..'"')
else
Manager.verbose('Failed Initiation: "'..moduleName..'" ('..err..')','errorCaught')
end
-- clears the init function so it cant be used in runtime
data.on_init = nil
end
end}),
__post=setmetatable({},{__call=function(self,moduleName)
-- check to provent multiple calls
if self[moduleName] or not Manager.loadModules.__init[moduleName] then return end
self[moduleName] = true
self = Manager.loadModules
-- calls on_post for each module
-- looks for post so that post or on_post can be used
local data = self[moduleName]
if type(data) == 'table' and data.post and data.on_post == nil then data.on_post = data.post data.post = nil end
if type(data) == 'table' and data.on_post and type(data.on_post) == 'function' then
Manager.verbose('Post for module: "'..moduleName..'"')
local success, err = Manager.sandbox(data.on_post,{moduleName=setupModuleName(moduleName),module_path=Manager.loadModules.__load[tostring(moduleName)]},data)
if success then
Manager.verbose('Successful post: "'..moduleName..'"')
else
Manager.verbose('Failed post: "'..moduleName..'" ('..err..')','errorCaught')
end
-- clears the post function so it cant be used in runtime
data.on_post = nil
end
end})
},
{
__metatable=false,
__index=function(self,moduleName)
-- will load one module if it is not already loaded, will not init during load state or post
self.__load(moduleName)
if (ReadOnlyManager.currentState == 'moduleLoad') then return end
self.__init(moduleName)
if (ReadOnlyManager.currentState == 'moduleInit') then return end
self.__post(moduleName)
return rawget(self,moduleName)
end,
__call=function(self)
-- goes though the index looking for modules to load
ReadOnlyManager.currentState = 'moduleLoad'
for moduleName,path in pairs(moduleIndex) do self.__load(moduleName) end
-- runs though all loaded modules looking for on_init function; all other modules have been loaded use this to load extra code based on opttial dependies
ReadOnlyManager.currentState = 'moduleInit'
for moduleName,path in pairs(self) do
if moduleName ~= '__load' and moduleName ~= '__init' and moduleName ~= '__post' then self.__init(moduleName) end
end
-- runs though all loaded modules looking for on_post function; all other modules have been loaded and inited, do not load extra code in this time only altar your own data
ReadOnlyManager.currentState = 'modulePost'
for moduleName,path in pairs(self) do
if moduleName ~= '__load' and moduleName ~= '__init' and moduleName ~= '__post' then self.__post(moduleName) end
end
ReadOnlyManager.currentState = 'moduleEnv'
end,
__len=function(tbl)
-- counts the number of loaded modules
local rtn = 0
for key,value in pairs(tbl) do
rtn = rtn + 1
end
return rtn-3
end,
__tostring=function(tbl)
-- a concat of all the loaded modules
local rtn = 'Load Modules: '
for key,value in pairs(tbl) do
if key ~= '__load' and key ~= '__init' and key ~= '__post' then rtn=rtn..key..', ' end
end
return rtn:sub(1,-3)
end
}
)
Manager.sandbox.loaded_modules = Manager.loadModules
--- A more detailed replacement for the lua error function to allow for handlers to be added; repleaces default error so error can be used instead of Manager.error
-- @function Manager.error
-- @usage Manager.error(err) -- calls all error handlers that are set or if none then prints to game and if that fails crashs game
-- @usage Manager.error() -- returns an error constant that can be used to crash game
-- @usage Manager.error(Manager.error()) -- crashs the game
-- @usage Manager.error.addHandler(name,callback) -- adds a new handler if handler returns Manager.error() then game will crash
-- @tparam[2] ?string|fucntion err the string to be passed to handlers; if a function it will register a handler
-- @tparam[2] function callback if given the err param will be used to given the handler a name
-- @usage Manager.error[name] -- returns the handler of that name if present
-- @usage #Manager.error -- returns the number of error handlers that are present
-- @usage pairs(Manager.error) -- loops over only the error handlers handler_name,hander
Manager.error = setmetatable({
__crash=false,
__error_call=error,
__error_const={},
__error_handler=function(handler_name,callback)
-- when handler_name is a string it is expeced that callback is a function; other wise handler_name must be a function
if type(handler_name) == 'string' and type(callback) == 'function' then Manager.error[handler_name]=callback
elseif type(handler_name) == 'function' then table.insert(Manager.error,handler_name)
else Manager.error('Handler is not a function',2) end
end,
in_pcall=function(level)
local level = level and level+1 or 2
while true do
if not debug.getinfo(level) then return false end
if debug.getinfo(level).name == 'pcall' then return level end
level=level+1
end
end
},{
__metatalbe=false,
__call=function(tbl,err,...)
-- if no params then return the error constant
if err == nil then return rawget(tbl,'__error_const') end
-- if the error constant is given crash game
if err == rawget(tbl,'__error_const') then Manager.verbose('Force Crash','errorCaught') rawset(tbl,'__crash',true) rawget(tbl,'__error_call')('Force Crash',2) end
-- other wise treat the call as if its been passed an err string
if not _no_error_verbose or Manager.currentState ~= 'moduleEnv' then Manager.verbose('An error has occurred: '..err,'errorCaught') end
if #tbl > 0 then
-- there is at least one error handler loaded; loops over the error handlers
for handler_name,callback in pairs(tbl) do
local success, err = pcall(callback,err,...)
if not success then Manager.verbose('Error handler: "'..handler_name..'" failed to run ('..err..')','errorCaught') end
-- if the error constant is returned from the handler then crash the game
if err == rawget(tbl,'__error_const') then Manager.verbose('Force Stop by: '..handler_name,'errorCaught') rawset(tbl,'__crash',true) rawget(tbl,'__error_call')('Force Stop by: '..handler_name) end
end
elseif game then
-- there are no handlers loaded so it will print to the game if loaded
Manager.verbose('No error handlers loaded; Default game print used','errorCaught')
game.print(err)
else
-- all else fails it will crash the game with the error code
Manager.verbose('No error handlers loaded; Game not loaded; Forced crash: '..err,'errorCaught')
rawset(tbl,'__crash',true)
rawget(tbl,'__error_call')(err,2)
end
local args = {...}
local trace = args[1] and type(args[1]) == 'number' and args[1]+1 or 2
if tbl.in_pcall(2) then rawget(tbl,'__error_call')(err,trace) end
end,
__index=function(tbl,key)
-- this allows the __error_handler to be called from many different names
if type(key) ~= 'string' then return end
if key:lower() == 'addhandler' or key:lower() == 'sethandler' or key:lower() == 'handler' or key:lower() == 'register' then return rawget(tbl,'__error_handler')
else rawget(tbl,'__error_call')('Invalid index for error handler; please use build in methods.') end
end,
__newindex=function(tbl,key,value)
-- making a new index adds it as a handler
if type(value) == 'function' then
Manager.verbose('Added Error Handler: "'..key..'"','eventRegistered')
rawset(tbl,key,value)
end
end,
__len=function(tbl)
-- counts the number of handlers there are
local rtn=0
for handler_name,callback in pairs(tbl) do
rtn=rtn+1
end
return rtn
end,
__pairs=function(tbl)
-- will not return any of the three core values as part of pairs
local function next_pair(tbl,k)
local v
k, v = next(tbl, k)
if k == '__error_call' or k == '__error_const' or k == '__error_handler' or k == '__crash' or k == 'in_pcall' then return next_pair(tbl,k) end
if type(v) == 'function' then return k,v end
end
return next_pair, tbl, nil
end
})
-- overrides the default error function
error=Manager.error
-- event does work a bit differnt from error, and if event breaks error is the fallback
--- Event handler that modules can use, each module can register one function per event
-- @function Manager.event
-- @usage Manager.event[event_name] = callback -- sets the callback for that event
-- @usage Manager.event[event_name] = nil -- clears the callback for that event
-- @usage Manager.event(event_name,callback) -- sets the callback for that event
-- @usage Manager.event[event_name] -- returns the callback for that event or the event id if not registered
-- @usage Manager.event(event_name) -- runs all the call backs for that event
-- @tparam ?int|string event_name that referes to an event
-- @tparam function callback the function that will be set for that event
-- @usage Manager.event() -- returns the stop value for the event proccessor, if returned during an event will stop all other callbacks
-- @usage #Manager.event -- returns the number of callbacks that are registered
-- @usage pairs(Manager.events) -- returns event_id,table of callbacks
Manager.event = setmetatable({
__stop={},
__events={},
__event=script.on_event,
__generate=script.generate_event_name,
__get_handler=script.get_event_handler,
__raise=script.raise_event,
__init=script.on_init,
__load=script.on_load,
__config=script.on_configuration_changed,
events=defines.events,
error_cache={}
},{
__metatable=false,
__call=function(tbl,event_name,new_callback,...)
if Manager.error.__crash then Manager.error.__error_call('No error handlers loaded; Game not loaded; Forced crash: '..tostring(Manager.error.__crash)) end
-- if no params then return the stop constant
if event_name == nil then return rawget(tbl,'__stop') end
-- if the event name is a table then loop over each value in that table
if type(event_name) == 'table' then
for key,_event_name in pairs(event_name) do tbl(_event_name,new_callback,...) end return
end
-- convert the event name to a number index
event_name = tonumber(event_name) or tbl.names[event_name]
-- if there is a callback present then set new callback rather than raise the event
if type(new_callback) == 'function' then
Manager.event[event_name] = new_callback return
end
-- other wise raise the event and call every callback; no use of script.raise_event due to override
local event_functions = tbl.__events[event_name]
if type(event_functions) == 'table' then
for moduleName,callback in pairs(event_functions) do
-- loops over the call backs and which module it is from
if type(callback) ~= 'function' then error('Invalid Event Callback: "'..event_name..'/'..moduleName..'"') end
local success, err = Manager.sandbox(callback,{moduleName=setupModuleName(moduleName),module_path=Manager.loadModules.__load[tostring(moduleName)]},new_callback,...)
if not success then
local cache = tbl.error_cache
local error_message = 'Event Failed: "'..moduleName..'/'..tbl.names[event_name]..'" ('..err..')'
if not cache[error_message] then Manager.verbose(error_message,'errorCaught') error(error_message) end
if tbl.names[event_name] == 'on_tick' then
if not cache[error_message] then cache[error_message] = {game.tick,1} end
if cache[error_message][1] >= game.tick-10 then cache[error_message] = {game.tick,cache[error_message][2]+1}
else cache[error_message] = nil end
if cache[error_message] and cache[error_message][2] > 100 then
Manager.verbose('There was an error happening every tick for 100 ticks, the event handler has been removed!','errorCaught')
event_functions[moduleName] = nil
end
end
end
-- if stop constant is returned then stop further processing
if err == rawget(tbl,'__stop') then Manager.verbose('Event Haulted By: "'..moduleName..'"','errorCaught') break end
end
end
end,
__newindex=function(tbl,key,value)
-- handles the creation of new event handlers
if type(value) ~= 'function' and type(value) ~= nil then error('Attempted to set a non function value to an event',2) end
-- checks for a global module name that is present
local moduleName = moduleName or 'FSM'
-- converts the key to a number index for the event
Manager.verbose('Added Handler: "'..tbl.names[key]..'"','eventRegistered')
-- checks that the event has a valid table to store callbacks; if its not valid it will creat it and register a real event handler
if not rawget(rawget(tbl,'__events'),key) then
if key == -1 or key == -2 then -- this already has a handler
elseif key < 0 then rawget(tbl,tbl.names[key])(function(...) tbl(key,...) end)
else rawget(tbl,'__event')(key,function(...) tbl(key,...) end) end
rawset(rawget(tbl,'__events'),key,{}) end
-- adds callback to Manager.event.__events[event_id][moduleName]
rawset(rawget(rawget(tbl,'__events'),key),tostring(moduleName),value)
end,
__index=function(tbl,key)
-- few redirect key
local redirect={register=tbl,dispatch=tbl,remove=function(event_id) tbl[event_name]=nil end}
if rawget(redirect,key) then return rawget(redirect,key) end
-- proforms different look ups depentding weather the current module has an event handler registered
if moduleName then
-- first looks for the event callback table and then under the module name; does same but converts the key to a number; no handler regisered so returns the converted event id
return rawget(rawget(tbl,'__events'),key) and rawget(rawget(rawget(tbl,'__events'),key),tostring(moduleName))
or rawget(rawget(tbl,'__events'),rawget(tbl,'names')[key]) and rawget(rawget(rawget(tbl,'__events'),rawget(tbl,'names')[key]),tostring(moduleName))
or rawget(tbl,'names')[key]
else
-- if there is no module present then it will return the full list of regisered handlers; or other wise the converted event id
return rawget(rawget(tbl,'__events'),key) or rawget(rawget(tbl,'__events'),rawget(tbl,'names')[key]) or rawget(tbl,'names')[key]
end
end,
__len=function(tbl)
-- counts every handler that is regised not just the the number of events with handlers
local rtn=0
for event,callbacks in pairs(tbl) do
for module,callback in pairs(callbacks) do
rtn=rtn+1
end
end
return rtn
end,
__pairs=function(tbl)
-- will loops over the event handlers and not Manager.event
local function next_pair(tbl,k)
k, v = next(rawget(tbl,'__events'), k)
if type(v) == 'table' then return k,v end
end
return next_pair, tbl, nil
end
})
--- Sub set to Manger.event and acts as a coverter between event_name and event_id
-- @table Manager.event.names
-- @usage Manager.event[event_name]
rawset(Manager.event,'names',setmetatable({},{
__index=function(tbl,key)
if type(key) == 'number' or tonumber(key) then
-- if it is a number then it will first look in the cache
if rawget(tbl,key) then return rawget(tbl,key) end
-- if it is a core event then it will simply return
if key == -1 then rawset(tbl,key,'__init')
elseif key == -2 then rawset(tbl,key,'__load')
elseif key == -3 then rawset(tbl,key,'__config')
else
-- if it is not a core event then it does a value look up on Manager.events aka defines.events
for event,id in pairs(rawget(Manager.event,'events')) do
if id == key then rawset(tbl,key,event) end
end
end
-- returns the value from the cache after being loaded in
return rawget(tbl,key)
-- if it is a string then no reverse look up is required
else
if key == 'on_init' or key == 'init' or key == '__init' then return -1
elseif key == 'on_load' or key == 'load' or key == '__load' then return -2
elseif key == 'on_configuration_changed' or key == 'configuration_changed' or key == '__config' then return -3
else return rawget(rawget(Manager.event,'events'),key) end
end
end
}))
script.on_init(function(...)
Manager.verbose('____________________| SubStart: script.on_init |____________________')
setmetatable(global,Manager.global.__global)
local names = {}
for name,default in pairs(Manager.global.__defaults) do table.insert(names,name) end
Manager.verbose('Global Tables: '..table.concat(names,', '))
for name,default in pairs(Manager.global.__defaults) do global(name)(true) end
Manager.event(-1,...)
Manager.verbose('____________________| SubStop: script.on_init |____________________')
end)
script.on_load(function(...)
Manager.verbose('____________________| SubStart: script.on_load |____________________')
setmetatable(global,Manager.global.__global)
local names = {}
for name,default in pairs(Manager.global.__defaults) do table.insert(names,name) end
Manager.verbose('Global Tables: '..table.concat(names,', '))
--for name,default in pairs(Manager.global.__defaults) do Manager.verbose('Global '..name..' = '..serpent.line(Manager.global(name))) end
Manager.event(-2,...)
Manager.verbose('____________________| SubStop: script.on_load |____________________')
end)
--over rides for the base values; can be called though Event
Event=setmetatable({},{__call=Manager.event,__index=function(tbl,key) return Manager.event[key] or script[key] or error('Invalid Index To Table Event') end})
script.mod_name = setmetatable({},{__index=_G.moduleName})
script.on_event=Manager.event
script.raise_event=Manager.event
script.on_init=function(callback) Manager.event(-1,callback) end
script.on_load=function(callback) Manager.event(-2,callback) end
script.on_configuration_changed=function(callback) Manager.event(-3,callback) end
script.get_event_handler=function(event_name) return type(Manager.event[event_name]) == 'function' and Manager.event[event_name] or nil end
script.generate_event_name=function(event_name) local event_id = Manager.event.__generate() local event_name = event_name or event_id Manager.event.events[event_name]=event_id return event_id end
-- to do set up nth tick
return ReadOnlyManager
+44 -13
View File
@@ -1,14 +1,45 @@
-- not_luadoc=true
function _log(...) log(...) end -- do not remove this is used for smaller verbose lines
Manager = require("FactorioSoftmodManager")
Manager.setVerbose{
selfInit=true, -- called while the manager is being set up
moduleLoad=false, -- when a module is required by the manager
moduleInit=false, -- when and within the initation of a module
modulePost=false, -- when and within the post of a module
moduleEnv=false, -- during module runtime, this is a global option set within each module for fine control
eventRegistered=false, -- when a module registers its event handlers
errorCaught=true, -- when an error is caught during runtime
output=Manager._verbose -- can be: can be: print || log || other function
-- If you're looking to configure anything, you want config.lua. Nearly everything in this file is dictated by the config.
-- Info on the data lifecycle and how we use it: https://github.com/Refactorio/RedMew/wiki/The-data-lifecycle
require 'resources.data_stages'
_LIFECYCLE = _STAGE.control -- Control stage
-- Overrides the _G.print function
require 'utils.print_override'
-- Omitting the math library is a very bad idea
require 'utils.math'
-- Global Debug and make sure our version file is registered
Debug = require 'utils.debug'
require 'resources.version'
local files = {
'modules.commands.me',
'modules.commands.kill',
'modules.commands.admin-chat',
'modules.commands.tag',
'modules.commands.teleport',
'modules.commands.cheat-mode',
'modules.commands.interface',
'modules.commands.help',
}
Manager() -- can be Manager.loadModules() if called else where
-- Loads all files in array above and logs progress
local total_files = string.format('%3d',#files)
local errors = {}
for index,path in pairs(files) do
log(string.format('[INFO] Loading files %3d/%s',index,total_files))
local success,file = pcall(require,path)
-- error checking
if not success then
log('[ERROR] Failed to load file: '..path)
log('[ERROR] '..file)
table.insert(errors,'[ERROR] '..path..' :: '..file)
elseif type(file) == 'string' and file:find('not found') then
log('[ERROR] File not found: '..path)
table.insert(errors,'[ERROR] '..path..' :: Not Found')
end
end
log('[INFO] All files loaded with '..#errors..' errors:')
for _,error in pairs(errors) do log(error) end -- logs all errors again to make it make it easy to find
+677
View File
@@ -0,0 +1,677 @@
--- Factorio command making module that makes commands with better parse and more modularity
-- @author Cooldude2606
-- @module Commands
--[[
>>>>Example Authenticator:
-- 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
end
else -- command does not require admin
return true -- no return is needed for success but is useful to include
end
end)
>>>>Example Parse:
-- 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
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)
:add_defaults{smiley=false} -- adds false as the default for smiley
:add_tag('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
return reject('Number entered is not in range: '..range_min..', '..range_max)
else
return rtn
end
end)
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)
:add_param('smiley',true,function(input,player,reject)
if not input then return end
if input:lower() == 'true' or input:lower() == 'yes' then
return true
else
return false
end
end)
:add_defaults{smiley=false}
:add_tag('admin_only',true)
:add_alias('name','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)
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.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:add_defaults(defaults) --- Adds default values to params only matters if the param is optional
Commands._prototype:add_tag(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: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 = 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 - tags: table - any tags 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, tags: table, reject: function(error_message?: string))
local success, rtn = pcall(callback,player,command_name,command_data.tags,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,
tags={}, -- stores tags 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:add_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 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
-- @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:add_tag(name,value)
if not value then
-- value not given so name is the value
table.insert(self.tags,name)
else
-- name is given so its key: value
self.tags[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 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)
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]
local player = Game.get_player_by_index(command_event.player_index)
-- 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 param_parsed == 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 and err ~= Commands.defines.success and err ~= Commands.error and err ~= Commands.success then
Commands.success(err)
end
command_log(player,command_data,'Success',raw_params,input_string)
end
return Commands
+57
View File
@@ -0,0 +1,57 @@
local Colours = require 'resources.color_presets'
local Game = require 'utils.game'
local Public = {}
--- Compare types faster for faster validation of prams
-- @usage is_type('foo','string') -- return true
-- @usage is_type('foo') -- return false
-- @param v the value to be tested
-- @tparam[opt=nil] string test_type the type to test for if not given then it tests for nil
-- @treturn boolean is v of type test_type
function Public.type_check(value,test_type)
return test_type and value and type(value) == test_type or not test_type and not value or false
end
--- Will return a value of any type to the player/server console, allows colour for in-game players
-- @usage player_return('Hello, World!') -- returns 'Hello, World!' to game.player or server console
-- @usage player_return('Hello, World!','green') -- returns 'Hello, World!' to game.player with colour green or server console
-- @usage player_return('Hello, World!',nil,player) -- returns 'Hello, World!' to the given player
-- @param value any value of any type that will be returned to the player or console
-- @tparam[opt=defines.colour.white] ?defines.color|string colour the colour of the text for the player, ignored when printing to console
-- @tparam[opt=game.player] LuaPlayer player the player that return will go to, if no game.player then returns to server
function Public.player_return(value,colour,player)
colour = Public.type_check(colour,'table') and colour or Colours[colour] ~= Colours.white and Colours[colour] or Colours.white
player = player or game.player
-- converts the value to a string
local returnAsString
if Public.type_check(value,'table') then
if Public.type_check(value.__self,'userdata') then
-- value is userdata
returnAsString = 'Cant Display Userdata'
elseif Public.type_check(value[1],'string') and string.find(value[1],'.+[.].+') and not string.find(value[1],'%s') then
-- value is a locale string
returnAsString = value
elseif getmetatable(value) ~= nil and not tostring(value):find('table: 0x') then
-- value has a tostring meta method
returnAsString = tostring(value)
else
-- value is a table
returnAsString = serpent.block(value)
end
elseif Public.type_check(value,'function') then
-- value is a function
returnAsString = 'Cant Display Functions'
else returnAsString = tostring(value) end
-- returns to the player or the server
if player then
-- allows any valid player identifier to be used
player = Game.get_player_from_any(player)
if not player then error('Invalid Player given to player_return',2) end
-- plays a nice sound that is different to normal message sound
player.play_sound{path='utility/scenario_message'}
player.print(returnAsString,colour)
else rcon.print(returnAsString) end
end
return Public
+142
View File
@@ -0,0 +1,142 @@
local Commands = require 'expcore.commands'
local Game = require 'utils.game'
--[[
>>>>Adds parses:
boolean
string-options - options: array
string-max-length - max_length: number
number
integer
number-range - range_min: number, range_max: number
integer-range - range_min: number, range_max: number
player
player-online
player-alive
force
surface
]]
Commands.add_parse('boolean',function(input,player,reject)
if not input then return end -- nil check
input = input:lower()
if input == 'yes'
or input == 'y'
or input == 'true'
or input == '1' then
return true
else
return false
end
end)
Commands.add_parse('string-options',function(input,player,reject,options)
if not input then return end -- nil check
input = input:lower()
for option in options do
if input == option:lower() then
return true
end
end
return reject{'reject-string-options',options:concat(', ')}
end)
Commands.add_parse('string-max-length',function(input,player,reject,max_length)
if not input then return end -- nil check
local length = input:len()
if length > max_length then
return reject{'expcore-commands.reject-string-max-length',max_length}
else
return input
end
end)
Commands.add_parse('number',function(input,player,reject)
if not input then return end -- nil check
local number = tonumber(input)
if not number then
return reject{'expcore-commands.reject-number'}
else
return number
end
end)
Commands.add_parse('integer',function(input,player,reject)
if not input then return end -- nil check
local number = tonumber(input)
if not number then
return reject{'expcore-commands.reject-number'}
else
return math.floor(number)
end
end)
Commands.add_parse('number-range',function(input,player,reject,range_min,range_max)
local number = Commands.parse('number',input,player,reject)
if not number then return end -- nil check
if number < range_min or number > range_max then
return reject{'expcore-commands.reject-number-range',range_min,range_max}
else
return number
end
end)
Commands.add_parse('integer-range',function(input,player,reject,range_min,range_max)
local number = Commands.parse('integer',input,player,reject)
if not number then return end -- nil check
if number < range_min or number > range_max then
return reject{'expcore-commands.reject-number-range',range_min,range_max}
else
return number
end
end)
Commands.add_parse('player',function(input,player,reject)
if not input then return end -- nil check
local input_player = Game.get_player_from_any(input)
if not input_player then
return reject{'expcore-commands.reject-player',input}
else
return input_player
end
end)
Commands.add_parse('player-online',function(input,player,reject)
local input_player = Commands.parse('player',input,player,reject)
if not input_player then return end -- nil check
if not input_player.connected then
return reject{'expcore-commands.reject-player-online'}
else
return input_player
end
end)
Commands.add_parse('player-alive',function(input,player,reject)
local input_player = Commands.parse('player-online',input,player,reject)
if not input_player then return end -- nil check
if not input_player.character or not input_player.character.health or input_player.character.health <= 0 then
return reject{'expcore-commands.reject-player-alive'}
else
return input_player
end
end)
Commands.add_parse('force',function(input,player,reject)
if not input then return end -- nil check
local force = game.forces[input]
if not force then
return reject{'expcore-commands.reject-force'}
else
return force
end
end)
Commands.add_parse('surface',function(input,player,reject)
if not input then return end
local surface = game.surfaces[input]
if not surface then
return reject{'expcore-commands.reject-surface'}
else
return surface
end
end)
+9
View File
@@ -0,0 +1,9 @@
[expcore-commands]
unauthorized=401 - Unbefugt: Zugang verweigert. Du hast keinen Zugriff auf diese Befehle!
reject-number-range=ungültige Reichweite, Min: __1__, Max: __2__
reject-string-max-length=ungültige Länge, Max: __1__
reject-player=ungültiger Spieler Name, __1__ , Versuche "Tab" zu benutzen, damit sich der Name automatisch vervollständigt.
reject-player-online=Der betroffene Spieler ist offline, Befehl konnte nicht ausgeführt werden.
reject-player-alive=Der betroffene Spieler ist Tod, Befehl konnte nicht ausgeführt werden.
invalid-inputs=ungültige Eingabe, /__1__ __2__
command-ran=Befehl ausgeführt.
+17
View File
@@ -0,0 +1,17 @@
[expcore-commands]
unauthorized=Unauthorized, Access is denied due to invalid credentials
reject-string-options=Invalid Option, Must be one of: __1__
reject-string-max-length=Invalid Length, Max: __1__
reject-number=Invalid Number
reject-number-range=Invalid Range, Min (inclusive): __1__, Max (inclusive): __2__
reject-player=Invaild Player Name, __1__ ,try using tab key to auto-complete the name
reject-player-online=Player is offline.
reject-player-alive=Player is dead.
reject-force=Invaild Force Name
reject-surface=Invaild surface Name
invalid-inputs=Invalid Input, /__1__ __2__
invalid-param=Invalid Param "__1__"; __2__
command-help=__1__ - __2__
command-ran=Command Complete
command-fail=Command failed to run: __1__
command-error-log-format=[ERROR] command/__1__ :: __2__
+9
View File
@@ -0,0 +1,9 @@
[expcore-commands]
unauthorized=401 - Onbevoegd: toegang wordt geweigerd vanwege ongeldige inloggegevens
reject-number-range=Onjuiste radius, Min: __1__, Max: __2__
reject-string-max-length=Onjuiste lengte, Max: __1__
reject-player=Onjuiste naam, __1__ , probeer tab te gebruiken om de naam automatisch in te vullen
reject-player-online=Speler is offline.
reject-player-alive=Speler is dood.
invalid-inputs=Onjuiste invoer, /__1__ __2__
command-ran=Commando uitgevoerd.
+9
View File
@@ -0,0 +1,9 @@
[expcore-commands]
unauthorized=401 - Otillåten: Tillgång nekas på grund av otillräcklig säkerhetsprövning.
reject-number-range=Invalid räckvid, Min: __1__, Max: __2__
reject-string-max-length=ogiltig längd, Max: __1__
reject-player=Ogiltigt spelarnamn, __1__ , försök använda tab-tangenten för att auto-slutföra namn.
reject-player-online=Spelare är offline. Kommando misslyckades med att köras.
reject-player-alive=Spelare är död. Kommando misslyckades med att köras.
invalid-inputs=Igiltig inmatning, /__1__ __2__
command-ran=Kommandot slutfört
+10
View File
@@ -0,0 +1,10 @@
[exp-commands]
kill-already-dead=You are already dead.
admin-chat-format=[Admin Chat] [color=__3__]__1__: __2__
tp-no-position-found=No position to teleport to was found, please try again later.
tp-to-self=Player can not be teleported to themselves.
chelp-title=Help results for "__1__":
chelp-footer=(__1__ results found; page __2__ of __3__)
chelp-format=/__1__ __2__ - __3__ __4__
chelp-alias=Alias: __1__
chelp-out-of-range=__1__ is an invalid page number.
+17
View File
@@ -0,0 +1,17 @@
[expcore-commands]
unauthorized=Unauthorized, Access is denied due to invalid credentials
reject-string-options=Invalid Option, Must be one of: __1__
reject-string-max-length=Invalid Length, Max: __1__
reject-number=Invalid Number
reject-number-range=Invalid Range, Min (inclusive): __1__, Max (inclusive): __2__
reject-player=Invaild Player Name, __1__ ,try using tab key to auto-complete the name
reject-player-online=Player is offline.
reject-player-alive=Player is dead.
reject-force=Invaild Force Name
reject-surface=Invaild surface Name
invalid-inputs=Invalid Input, /__1__ __2__
invalid-param=Invalid Param "__1__"; __2__
command-help=__1__ - __2__
command-ran=Command Complete
command-fail=Command failed to run: __1__
command-error-log-format=[ERROR] command/__1__ :: __2__
@@ -1,9 +0,0 @@
[ExpGamingCore_Command]
unauthorized=401 - Unbefugt: Zugang verweigert. Du hast keinen Zugriff auf diese Befehle!
invalid-inputs=ungültige Eingabe, /__1__ __2__
invalid-range=ungültige Reichweite, Min: __1__, Max: __2__
invalid-length=ungültige Länge, Max: __1__
invalid-player=ungültiger Spieler Name, __1__ , Versuche "Tab" zu benutzen, damit sich der Name automatisch vervollständigt.
offline-player=Der betroffene Spieler ist offline, Befehl konnte nicht ausgeführt werden.
dead-player=Der betroffene Spieler ist Tod, Befehl konnte nicht ausgeführt werden.
command-ran=Befehl ausgeführt.
@@ -1,14 +0,0 @@
[ExpGamingCore_Command]
unauthorized=Unauthorized, Access is denied due to invalid credentials
error-string-list=Invalid Option, Must be one of: __1__
error-string-len=Invalid Length, Max: __1__
error-number=Invalid Number
error-number-range=Invalid Range, Min (exclusive): __1__, Max (inclusive): __2__
error-player=Invaild Player Name, __1__ ,try using tab key to auto-complete the name
error-player-online=Player is offline.
error-player-alive=Player is dead.
error-player-rank=Player is of Higher Rank.
invalid-inputs=Invalid Input, /__1__ __2__
invalid-parse=Invalid Input, There was a problem prasing the paramaters
command-ran=Command Complete
command-fail=Command failed to run: __1__
@@ -1,9 +0,0 @@
[ExpGamingCore_Command]
unauthorized=401 - Unauthorized: Access is denied due to invalid credentials
invalid-inputs=Invalid Input, /__1__ __2__
invalid-range=Invalid Range, Min: __1__, Max: __2__
invalid-length=Invalid Length, Max: __1__
invalid-player=Invaild Player Name, __1__ ,try using tab key to auto-complete the name
offline-player=Player is offline, Command Failed To Run
dead-player=Player is dead, Command Failed To Run
command-ran=Command Complete
@@ -1,9 +0,0 @@
[ExpGamingCore_Command]
unauthorized=401 - Onbevoegd: toegang wordt geweigerd vanwege ongeldige inloggegevens
invalid-inputs=Onjuiste invoer, /__1__ __2__
invalid-range=Onjuiste radius, Min: __1__, Max: __2__
invalid-length=Onjuiste lengte, Max: __1__
invalid-player=Onjuiste naam, __1__ , probeer tab te gebruiken om de naam automatisch in te vullen
offline-player=Speler is offline.
dead-player=Speler is dood.
command-ran=Commando uitgevoerd.
@@ -1,9 +0,0 @@
[ExpGamingCore_Command]
unauthorized=401 - Otillåten: Tillgång nekas på grund av otillräcklig säkerhetsprövning.
invalid-inputs=Igiltig inmatning, /__1__ __2__
invalid-range=Invalid räckvid, Min: __1__, Max: __2__
invalid-length=ogiltig längd, Max: __1__
invalid-player=Ogiltigt spelarnamn, __1__ , försök använda tab-tangenten för att auto-slutföra namn.
offline-player=Spelare är offline. Kommando misslyckades med att köras.
dead-player=Spelare är död. Kommando misslyckades med att köras.
command-ran=Kommandot slutfört
+19
View File
@@ -0,0 +1,19 @@
local Commands = require 'expcore.commands'
require 'expcore.common_parse'
require 'modules.commands.admin-only-auth'
Commands.new_command('admin-chat','Sends a message in chat that only admins can see.')
:add_param('message',false) -- the message to send in the admin chat
:enable_auto_concat()
:add_tag('admin_only',true)
:add_alias('ac')
:register(function(player,message,raw)
local pcc = player.chat_color
local colour = string.format('%s,%s,%s',pcc.r,pcc.g,pcc.b)
for _,return_player in pairs(game.connected_players) do
if return_player.admin then
return_player.print{'exp-commands.admin-chat-format',player.name,message,colour}
end
end
return Commands.success -- prevents command complete message from showing
end)
+13
View File
@@ -0,0 +1,13 @@
local Commands = require 'expcore.commands'
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)
+13
View File
@@ -0,0 +1,13 @@
local Commands = require 'expcore.commands'
require 'expcore.common_parse'
require 'modules.commands.admin-only-auth'
Commands.new_command('toggle-cheat-mode','Toggles cheat mode for your player, or another player.')
:add_param('player',true,'player') -- player to toggle chest mode of, can be nil for self
:add_defaults{player=function(player)
return player -- default is the user using the command
end}
:add_tag('admin_only',true)
:register(function(player,action_player,raw)
action_player.cheat_mode = not action_player.cheat_mode
end)
+10
View File
@@ -0,0 +1,10 @@
[exp-commands]
kill-already-dead=You are already dead.
admin-chat-format=[Admin Chat] [color=__3__]__1__: __2__
tp-no-position-found=No position to teleport to was found, please try again later.
tp-to-self=Player can not be teleported to themselves.
chelp-title=Help results for "__1__":
chelp-footer=(__1__ results found; page __2__ of __3__)
chelp-format=/__1__ __2__ - __3__ __4__
chelp-alias=Alias: __1__
chelp-out-of-range=__1__ is an invalid page number.
+74
View File
@@ -0,0 +1,74 @@
local Commands = require 'expcore.commands'
local Global = require 'utils.global'
require 'expcore.common_parse'
local results_per_page = 5
local search_cache = {}
Global.register(search_cache,function(tbl)
search_cache = tbl
end)
Commands.new_command('chelp','Searches for a keyword in all commands you are allowed to use.')
:add_param('keyword',true) -- the keyword that will be looked for
:add_param('page',true,'integer') -- the keyword that will be looked for
:add_defaults{keyword='',page=1}
:register(function(player,keyword,page,raw)
-- if keyword is a number then treat it as page number
if tonumber(keyword) then
page = math.floor(tonumber(keyword))
keyword = ''
end
-- gets a value for pages, might have result in cache
local pages
local found = 0
if search_cache[player.index] and search_cache[player.index].keyword == keyword:lower() then
pages = search_cache[player.index].pages
found = search_cache[player.index].found
else
pages = {{}}
local current_page = 1
local page_count = 0
local commands = Commands.search(keyword,player)
-- loops other all commands returned by search, includes game commands
for _,command_data in pairs(commands) do
-- if the number of results if greater than the number already added then it moves onto a new page
if page_count >= results_per_page then
page_count = 0
current_page = current_page + 1
table.insert(pages,{})
end
-- adds the new command to the page
page_count = page_count + 1
found = found + 1
local alias_format = #command_data.aliases > 0 and {'exp-commands.chelp-alias',table.concat(command_data.aliases,', ')} or ''
table.insert(pages[current_page],{
'exp-commands.chelp-format',
command_data.name,
command_data.description,
command_data.help,
alias_format
})
end
-- adds the result to the cache
search_cache[player.index] = {
keyword=keyword:lower(),
pages=pages,
found=found
}
end
-- print the requested page
keyword = keyword == '' and '<all>' or keyword
Commands.print({'exp-commands.chelp-title',keyword},'cyan')
if pages[page] then
for _,command in pairs(pages[page]) do
Commands.print(command)
end
Commands.print({'exp-commands.chelp-footer',found,page,#pages},'cyan')
else
Commands.print({'exp-commands.chelp-footer',found,page,#pages},'cyan')
return Commands.error{'exp-commands.chelp-out-of-range',page}
end
-- blocks command complete message
return Commands.success
end)
+90
View File
@@ -0,0 +1,90 @@
local Commands = require 'expcore.commands'
local Global = require 'utils.global'
local Common = require 'expcore.common'
require 'modules.commands.admin-only-auth'
-- modules that are loaded into the interface env to be accessed
local interface_modules = {
['Game']='utils.game',
['Commands']=Commands,
['output']=Common.player_return
}
-- loads all the modules given in the above table
for key,value in pairs(interface_modules) do
if type(value) == 'string' then
interface_modules[key] = require(value)
end
end
local interface_env = {} -- used as a persistent sandbox for interface commands
local interface_callbacks = {} -- saves callbacks which can load new values per use
Global.register({interface_env,interface_callbacks},function(tbl)
interface_env = tbl[1]
interface_callbacks = tbl[2]
end)
--- Adds a callback function when the interface command is used
-- nb: returned value is saved in the env that the interface uses
-- @tparam name string the name that the value is loaded under, cant use upvalues
-- @tparam callback function the function that will run whent he command is used
-- callback param - player: LuaPlayer - the player who used the command
local function add_interface_callback(name,callback)
if type(callback) == 'function' then
interface_callbacks[name] = callback
end
end
-- this is a meta function for __index when self[key] is nil
local function get_index(self,key)
if interface_env[key] then
return interface_env[key]
elseif interface_modules[key] then
return interface_modules[key]
end
end
Commands.new_command('interface','Sends an innovation to be ran and returns the result.')
:add_param('innovation',false) -- the message to send in the admin chat
:enable_auto_concat()
:add_tag('admin_only',true)
:register(function(player,innovation,raw)
if not innovation:find('%s') and not innovation:find('return') then
-- if there are no spaces and return is not present then return is appended to the start
innovation='return '..innovation
end
-- temp_env will index to interface_env and interface_modules if value not found
local temp_env = setmetatable({},{__index=get_index})
for name,callback in pairs(interface_callbacks) do
-- loops over callbacks and loads the values returned
local success, rtn = pcall(callback,player)
temp_env[name]=rtn
end
-- sets the global metatable to prevent new values being made
-- global will index to temp_env and new indexs saved to interface_sandbox
local old_mt = getmetatable(_G)
setmetatable(_G,{__index=temp_env,__newindex=interface_env})
-- runs the innovation and returns values to the player
innovation = loadstring(innovation)
local success, rtn = pcall(innovation)
setmetatable(_G,old_mt)
if not success then
if type(rtn) == 'string' then
-- there may be stack trace that must be removed to avoid desyncs
rtn = rtn:gsub('%.%.%..-/temp/currently%-playing','')
end
return Commands.error(rtn)
else
return Commands.success(rtn)
end
end)
-- adds some basic callbacks for the interface
add_interface_callback('player',function(player) return player end)
add_interface_callback('surface',function(player) return player.surface end)
add_interface_callback('force',function(player) return player.force end)
add_interface_callback('position',function(player) return player.position end)
add_interface_callback('entity',function(player) return player.selected end)
add_interface_callback('tile',function(player) return player.surface.get_tile(player.position) end)
return add_interface_callback
+20
View File
@@ -0,0 +1,20 @@
local Commands = require 'expcore.commands'
require 'expcore.common_parse'
require 'modules.commands.admin-only-auth'
Commands.new_command('kill','Kills yourself or another player.')
:add_param('player',true,'player-alive') -- the player to kill, must be alive to be valid
:add_defaults{player=function(player)
-- default is the player unless they are dead
if player.character and player.character.health > 0 then
return player
end
end}
:add_tag('admin_only',true)
:register(function(player,action_player,raw)
if not action_player then
-- can only be nil if no player given and the user is dead
return Commands.error{'exp-commands.kill-already-dead'}
end
action_player.character.die()
end)
+8
View File
@@ -0,0 +1,8 @@
local Commands = require 'expcore.commands'
Commands.new_command('me','Sends an action message in the chat')
:add_param('action',false) -- action that is done by the player, just text its meaningless
:enable_auto_concat()
:register(function(player,action,raw)
game.print(string.format('* %s %s *',player.name,action),player.chat_color)
end)
+27
View File
@@ -0,0 +1,27 @@
local Commands = require 'expcore.commands'
require 'expcore.common_parse'
Commands.new_command('tag','Sets your player tag.')
:add_param('tag',false,'string-max-length',20) -- new tag for your player max 20 char
:enable_auto_concat()
:register(function(player,tag,raw)
player.tag = ' - '..tag
end)
Commands.new_command('tag-clear','Clears your tag. Or another player if you are admin.')
:add_param('player',true,'player') -- player to remove the tag of, nil to apply to self
:add_defaults{player=function(player)
return player -- default is the user using the command
end}
:register(function(player,action_player,raw)
if action_player.index == player.index then
-- no player given so removes your tag
action_player.tag = ''
elseif player.admin then
-- player given and user is admin so clears that player's tag
action_player.tag = ''
else
-- user is not admin and tried to clear another users tag
return Commands.error{'expcore-commands.unauthorized'}
end
end)
+56
View File
@@ -0,0 +1,56 @@
local Commands = require 'expcore.commands'
require 'expcore.common_parse'
require 'modules.commands.admin-only-auth'
local function teleport(from_player,to_player)
local surface = to_player.surface
local position = surface.find_non_colliding_position('player',to_player.position,32,1)
if not position then return false end -- return false if no new position
from_player.teleport(position,surface)
return true
end
Commands.new_command('teleport','Teleports a player to another player.')
:add_param('from_player',false,'player-alive') -- player that will be teleported, must be alive
:add_param('to_player',false,'player-online') -- player to teleport to, must be online (if dead goes to where they died)
:add_alias('tp')
:add_tag('admin_only',true)
:register(function(player,from_player,to_player,raw)
if from_player.index == to_player.index then
-- return if attempting to teleport to self
return Commands.error{'exp-commands.tp-to-self'}
end
if not teleport(from_player,to_player) then
-- return if the teleport failed
return Commands.error{'exp-commands.tp-no-position-found'}
end
end)
Commands.new_command('bring','Teleports a player to you.')
:add_param('player',false,'player-alive') -- player that will be teleported, must be alive
:add_tag('admin_only',true)
:register(function(player,from_player,raw)
if from_player.index == player.index then
-- return if attempting to teleport to self
return Commands.error{'exp-commands.tp-to-self'}
end
if not teleport(from_player,player) then
-- return if the teleport failed
return Commands.error{'exp-commands.tp-no-position-found'}
end
end)
Commands.new_command('goto','Teleports you to a player.')
:add_param('player',false,'player-online') -- player to teleport to, must be online (if dead goes to where they died)
:add_alias('tp-me','tpme')
:add_tag('admin_only',true)
:register(function(player,to_player,raw)
if to_player.index == player.index then
-- return if attempting to teleport to self
return Commands.error{'exp-commands.tp-to-self'}
end
if not teleport(player,to_player) then
-- return if the teleport failed
return Commands.error{'exp-commands.tp-no-position-found'}
end
end)
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,4 +1,4 @@
[ExpGamingCore_Command]
[expcore-commands]
unauthorized=Unauthorized, Access is denied due to invalid credentials
error-string-list=Invalid Option, Must be one of: __1__
error-string-len=Invalid Length, Max: __1__
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,4 +1,4 @@
[ExpGamingCore_Command]
[expcore-commands]
unauthorized=401 - Unauthorized: Access is denied due to invalid credentials
invalid-inputs=Invalid Input, /__1__ __2__
invalid-range=Invalid Range, Min: __1__, Max: __2__
@@ -1,4 +1,4 @@
[ExpGamingCore_Command]
[expcore-commands]
unauthorized=401 - Unauthorized: Access is denied due to invalid credentials
invalid-inputs=Invalid Input, /__1__ __2__
invalid-range=Invalid Range, Min: __1__, Max: __2__
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,4 +1,4 @@
[ExpGamingCore_Command]
[expcore-commands]
unauthorized=401 - Onbevoegd: toegang wordt geweigerd vanwege ongeldige inloggegevens
invalid-inputs=Onjuiste invoer, /__1__ __2__
invalid-range=Onjuiste radius, Min: __1__, Max: __2__
@@ -1,4 +1,4 @@
[ExpGamingCore_Command]
[expcore-commands]
unauthorized=401 - Onbevoegd: toegang wordt geweigerd vanwege ongeldige inloggegevens
invalid-inputs=Onjuiste invoer, /__1__ __2__
invalid-range=Onjuiste radius, Min: __1__, Max: __2__
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,4 +1,4 @@
[ExpGamingCore_Command]
[expcore-commands]
unauthorized=401 - Otillåten: Tillgång nekas på grund av otillräcklig säkerhetsprövning.
invalid-inputs=Igiltig inmatning, /__1__ __2__
invalid-range=Invalid räckvid, Min: __1__, Max: __2__
@@ -1,4 +1,4 @@
[ExpGamingCore_Command]
[expcore-commands]
unauthorized=401 - Otillåten: Tillgång nekas på grund av otillräcklig säkerhetsprövning.
invalid-inputs=Igiltig inmatning, /__1__ __2__
invalid-range=Invalid räckvid, Min: __1__, Max: __2__
File renamed without changes.
Loaded 100 of 394 files, more files were not shown because too many files have changed in this diff. Show more