mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2025-12-27 03:25:23 +09:00
Refactor of commands
This commit is contained in:
@@ -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
|
||||
212
container.lua
212
container.lua
@@ -1,212 +0,0 @@
|
||||
local Container = {
|
||||
files={}, -- file paths which get loaded
|
||||
-- these will become globals that are used to keep softmod modules working together
|
||||
handlers={
|
||||
--event
|
||||
--global
|
||||
--error=error
|
||||
--logging=log
|
||||
--debug
|
||||
--tableToString=serpent.line
|
||||
},
|
||||
_raw={}, -- any values that are replaced by handlers are moved here
|
||||
_loaded={},
|
||||
defines={
|
||||
errorLoad='ERRLOAD', -- error when loading a file
|
||||
errorNotFound='ERRNOTFOUND', -- error when file not found
|
||||
logAlways=0, -- will always be logged
|
||||
logBasic=1, -- should be logged but not required
|
||||
logDebug=2, -- longer logs of debugging
|
||||
logEvents=3, -- logs which take place very often such as frequent event triggers if no other filters
|
||||
logVerbose=4, -- basically a log of any thing useful
|
||||
logAll=5 -- what ever is left to log weather you see a current need or not
|
||||
},
|
||||
-- to prevent desyncs during on_load any change to the following must be updated
|
||||
-- example: runtime change to logLevel must be applied during on_load to avoid desyncs
|
||||
safeError=true, -- when true then errors are logged not raised
|
||||
debug=false, -- if debug functions are triggered see Container.inDebug
|
||||
logLevel=1 -- what level of details is given by logs
|
||||
}
|
||||
|
||||
function Container.log(level,...)
|
||||
if level <= Container.logLevel then Container.stdout(...) end
|
||||
end
|
||||
function Container.stdout(...)
|
||||
local msg = ''
|
||||
for _,value in pairs({...}) do
|
||||
msg = msg..' '..Container.tostring(value)
|
||||
end
|
||||
if Container.handlers.logging then
|
||||
Container.handlers.logging(msg)
|
||||
else
|
||||
log(msg)
|
||||
end
|
||||
end
|
||||
|
||||
function Container.error(...)
|
||||
if Container.safeError then Container.stdout('ERROR',...) else Container.stderr(...) end
|
||||
end
|
||||
function Container.stderr(type,...)
|
||||
local msg = 'ERROR: '..tostring(type)
|
||||
for _,value in pairs({...}) do
|
||||
msg = msg..' '..Container.tostring(value)
|
||||
end
|
||||
if Container.handlers.error then
|
||||
Container.handlers.error(msg)
|
||||
else
|
||||
error(msg)
|
||||
end
|
||||
end
|
||||
|
||||
function Container.type(value,test)
|
||||
if not test then return type(value) end
|
||||
return value and type(value) == test
|
||||
end
|
||||
|
||||
function Container.isLocaleString(locale)
|
||||
if Container.type(locale,'table') then
|
||||
local _string = locale[1]
|
||||
-- '.+[.].+' this is a check for the key value pair
|
||||
-- '%s' this is a check for any white space
|
||||
return Container.type(_string,'string') and _string:find('.+[.].+') and not _string:find('%s')
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function Container.isUserdata(userdata)
|
||||
if Container.type(userdata,'table') then
|
||||
return Container.type(userdata.__self,'userdata')
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function Container.tostring(value)
|
||||
local _type = type(value)
|
||||
if _type == 'table' then
|
||||
if Container.isUserdata(value) then
|
||||
-- the value is userdata
|
||||
return '<USERDATA>'
|
||||
elseif getmetatable(rtn) ~= nil and not tostring(rtn):find('table: 0x') then
|
||||
-- the value is a table but contains the metamethod __tostring
|
||||
return tostring(value)
|
||||
else
|
||||
-- the value is a table
|
||||
if Container.handlers.tableToString then
|
||||
return Container.handlers.tableToString(value)
|
||||
else
|
||||
return serpent.line(value)
|
||||
end
|
||||
end
|
||||
elseif Container.type(value,'function') then
|
||||
-- the value is a function and the function name is given
|
||||
local name = debug.getinfo(value,'n').name or 'ANON'
|
||||
return '<FUNCTION:'..name..'>'
|
||||
else
|
||||
-- all other values: number, string and boolean tostring is save to use
|
||||
return tostring(value)
|
||||
end
|
||||
end
|
||||
|
||||
--- Sandboxs a function into the container and the given env, will load upvalues if provied in the given env
|
||||
-- @usage container:sandbox(print,{},'hello from the sandbox')
|
||||
-- @tparam callback function the function that will be run in the sandbox
|
||||
-- @tparam env table the env which the function will run in, place upvalues in this table
|
||||
-- @param[opt] any args you want to pass to the function
|
||||
-- @treturn boolean did the function run without error
|
||||
-- @treturn string|table returns error message or the returns from the function
|
||||
-- @treturn table returns back the env as new values may have been saved
|
||||
function Container.sandbox(callback,env,...)
|
||||
-- creates a sandbox env which will later be loaded onto _G
|
||||
local sandbox_env = setmetatable(env,{
|
||||
__index=function(tbl,key)
|
||||
return rawget(_G,key)
|
||||
end
|
||||
})
|
||||
sandbox_env._ENV = sandbox_env
|
||||
sandbox_env._MT_G = getmetatable(_G)
|
||||
-- sets any upvalues on the callback
|
||||
local i = 1
|
||||
while true do
|
||||
local name, value = debug.getupvalue(callback,i)
|
||||
if not name then break end
|
||||
if not value and sandbox_env[name] then
|
||||
debug.setupvalue(callback,i,sandbox_env[name])
|
||||
end
|
||||
i=i+1
|
||||
end
|
||||
-- adds the sandbox to _G
|
||||
setmetatable(_G,{__index=sandbox_env,__newindex=sandbox_env})
|
||||
local rtn = {pcall(callback,...)}
|
||||
local success = table.remove(rtn,1)
|
||||
setmetatable(_G,_MT_G)
|
||||
-- returns values from the callback, if error then it returns the error
|
||||
if success then return success, rtn, sandbox_env
|
||||
else return success, rtn[1], sandbox_env end
|
||||
end
|
||||
|
||||
function Container.loadFile(filePath)
|
||||
if Container._loaded[filePath] then return Container._loaded[filePath] end
|
||||
local success,file = pcall(require,filePath)
|
||||
if not success then return Container.error(Container.defines.errorLoad,filePath,file) end
|
||||
-- if the file was not found then it returns an error from require which does not trip pcall, tested for here
|
||||
if Container.type(file,'string') and file:find('no such file') then
|
||||
-- tries with modules. appended to the front of the path and .control on the end
|
||||
local success,_file = pcall(require,'modules.'..filePath..'.control')
|
||||
if not success then return Container.error(Container.defines.errorLoad,filePath,_file) end
|
||||
-- again tests for the error not caught by pcall
|
||||
if Container.type(_file,'string') and _file:find('no such file') then return Container.error(Container.defines.errorNotFound,filePath) end
|
||||
Container.log(Container.defines.logDebug,'Loaded file:',filePath)
|
||||
Container._loaded[filePath] = _file
|
||||
return _file
|
||||
end
|
||||
Container.log(Container.defines.logDebug,'Loaded file:',filePath)
|
||||
Container._loaded[filePath] = file
|
||||
return file
|
||||
end
|
||||
|
||||
function Container.loadHandlers()
|
||||
Container.log(Container.defines.logAlways,'Loading Container Handlers')
|
||||
for key,value in pairs(Container.handlers) do
|
||||
if Container.type(value,'string') then
|
||||
-- if it is a string then it is treated as a file path
|
||||
Container.handlers[key] = Container.loadFile(value)
|
||||
end
|
||||
if _G[key] then
|
||||
-- if the key exists then it is moved to _raw before being over ridden
|
||||
Container._raw[key] = _G[key]
|
||||
-- it is also moved to _R for global access
|
||||
if not _R then _R = {} end
|
||||
_R[key] = _G[key]
|
||||
end
|
||||
rawset(_G,key,Container.handlers[key])
|
||||
end
|
||||
end
|
||||
|
||||
function Container.loadFiles()
|
||||
Container.log(Container.defines.logAlways,'Loading Container Files')
|
||||
for _,filePath in pairs(Container.files) do
|
||||
Container.loadFile(filePath)
|
||||
end
|
||||
end
|
||||
|
||||
function Container.initFiles()
|
||||
Container.log(Container.defines.logAlways,'Initiating Container Files')
|
||||
for filePath,file in pairs(Container._loaded) do
|
||||
if file.on_init then
|
||||
file.on_init()
|
||||
Container.log(Container.defines.logDebug,'Initiated file:',filePath)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Container.postFiles()
|
||||
Container.log(Container.defines.logAlways,'POSTing Container Files')
|
||||
for filePath,file in pairs(Container._loaded) do
|
||||
if file.on_post then
|
||||
file.on_post()
|
||||
Container.log(Container.defines.logDebug,'POSTed file:',filePath)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Container
|
||||
135
control.lua
135
control.lua
@@ -1,103 +1,38 @@
|
||||
--[[ 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
|
||||
}
|
||||
Manager() -- can be Manager.loadModules() if called else where
|
||||
]]
|
||||
-- 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
|
||||
|
||||
require 'utils.data_stages'
|
||||
Container = require 'container'
|
||||
Container.debug=false
|
||||
Container.logLevel=Container.defines.logAll
|
||||
Container.safeError=true
|
||||
Container.handlers = {
|
||||
require=function(path,env,...)
|
||||
env = env or {}
|
||||
local success, rtn, sandbox_env = Container.sandbox(_R.require,env,path,...)
|
||||
return rtn
|
||||
end,
|
||||
Event='utils.event',
|
||||
Global='utils.global',
|
||||
--error=error,
|
||||
logging=function(...) log(...) end,
|
||||
tableToString=serpent.line
|
||||
-- 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.test'
|
||||
}
|
||||
Container.loadHandlers()
|
||||
Container.files = {
|
||||
'AdvancedStartingItems',
|
||||
'ChatPopup',
|
||||
'DamagePopup',
|
||||
'DeathMarkers',
|
||||
'DeconControl',
|
||||
'ExpGamingAdmin',
|
||||
'ExpGamingBot',
|
||||
'ExpGamingCommands',
|
||||
'ExpGamingCore',
|
||||
'ExpGamingInfo',
|
||||
'ExpGamingLib',
|
||||
'ExpGamingPlayer',
|
||||
'FactorioStdLib',
|
||||
'GameSettingsGui',
|
||||
'GuiAnnouncements',
|
||||
'PlayerAutoColor',
|
||||
'SpawnArea',
|
||||
'WarpPoints',
|
||||
'WornPaths',
|
||||
'ExpGamingAdmin.Gui',
|
||||
'ExpGamingAdmin.Ban',
|
||||
'ExpGamingAdmin.Reports',
|
||||
'ExpGamingAdmin.ClearInventory',
|
||||
'ExpGamingAdmin.TempBan',
|
||||
'ExpGamingAdmin.Teleport',
|
||||
'ExpGamingAdmin.Commands',
|
||||
'ExpGamingAdmin.Jail',
|
||||
'ExpGamingAdmin.Warnings',
|
||||
'ExpGamingAdmin.Kick',
|
||||
'ExpGamingBot.autoMessage',
|
||||
'ExpGamingBot.discordAlerts',
|
||||
'ExpGamingBot.autoChat',
|
||||
'ExpGamingCommands.cheatMode',
|
||||
'ExpGamingCommands.repair',
|
||||
'ExpGamingCommands.tags',
|
||||
'ExpGamingCommands.home',
|
||||
'ExpGamingCore.Command',
|
||||
'ExpGamingCommands.teleport',
|
||||
'ExpGamingCommands.bonus',
|
||||
'ExpGamingCommands.kill',
|
||||
'ExpGamingCore.Server',
|
||||
'ExpGamingCore.Gui',
|
||||
'ExpGamingInfo.Science',
|
||||
'ExpGamingPlayer.playerList',
|
||||
'ExpGamingCore.Sync',
|
||||
'ExpGamingCore.Role',
|
||||
'ExpGamingInfo.Readme',
|
||||
'ExpGamingInfo.Rockets',
|
||||
'ExpGamingCore.Group',
|
||||
'ExpGamingInfo.Tasklist',
|
||||
'ExpGamingPlayer.playerInfo',
|
||||
'ExpGamingPlayer.afkKick',
|
||||
'FactorioStdLib.Table',
|
||||
'ExpGamingPlayer.polls',
|
||||
'FactorioStdLib.Color',
|
||||
'FactorioStdLib.Game',
|
||||
'FactorioStdLib.String',
|
||||
'ExpGamingPlayer.inventorySearch',
|
||||
'ExpGamingCore.Gui.center',
|
||||
'ExpGamingCore.Gui.popup',
|
||||
'ExpGamingCore.Gui.toolbar',
|
||||
'ExpGamingCore.Gui.left',
|
||||
'ExpGamingCore.Gui.inputs'
|
||||
}
|
||||
Container.loadFiles()
|
||||
Container.initFiles()
|
||||
Container.postFiles()
|
||||
|
||||
-- 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
|
||||
548
expcore/commands.lua
Normal file
548
expcore/commands.lua
Normal file
@@ -0,0 +1,548 @@
|
||||
--- 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) or nil -- converts input to number
|
||||
rtn = type(rtn) == 'number' and math.floor(rtn) or nil -- floor the 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.add_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 false end -- here you can see the default check
|
||||
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_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
|
||||
--: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) or nil
|
||||
rtn = type(rtn) == 'number' and math.floor(rtn) 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.add_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 false end
|
||||
if input:lower() == 'true' or input:lower() == 'yes' then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end)
|
||||
: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)
|
||||
]]
|
||||
|
||||
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 due 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
|
||||
},
|
||||
_prototype={
|
||||
-- used to store functions which gets added to new custom commands
|
||||
},
|
||||
parse={
|
||||
-- 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
|
||||
}
|
||||
|
||||
--- 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)
|
||||
return table.insert(Commands.authorization,callback)
|
||||
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
|
||||
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 {'ExpGamingCore_Command.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 {'ExpGamingCore_Command.unauthorized'}
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- checks if the authorization failed
|
||||
if failed then
|
||||
return false, failed
|
||||
else
|
||||
return true, Commands.defines.success
|
||||
end
|
||||
end
|
||||
|
||||
--- Adds a common parse that can be called by name when it wants to be used
|
||||
-- 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 prase 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[name] then
|
||||
return false
|
||||
else
|
||||
Commands.parse[name] = callback
|
||||
return true
|
||||
end
|
||||
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.add_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, parse: function}
|
||||
}
|
||||
}, {
|
||||
__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 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: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 params = self.params
|
||||
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,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,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({'ExpGamingCore_Command.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('[ERROR] command/'..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({'ExpGamingCore_Command.command-ran'},'cyan')
|
||||
return Commands.defines.success
|
||||
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
|
||||
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
|
||||
Commands.error({'ExpGamingCore_Command.invalid-inputs',command_data.name,command_data.description})
|
||||
return
|
||||
end
|
||||
|
||||
-- splits the arguments
|
||||
local input_string = command_event.parameter
|
||||
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_qoutes = w:sub(2,-2)
|
||||
local no_spaces = no_qoutes:gsub('%s','_')
|
||||
quote_params[no_spaces]=no_qoutes
|
||||
if command_data.auto_concat then
|
||||
-- if auto concat then dont remove quotes as it should be included later
|
||||
quote_params[w:gsub('%s','_')]=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
|
||||
Commands.error({'ExpGamingCore_Command.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
|
||||
last_index = table.insert(raw_params,quote_params[word])
|
||||
else
|
||||
last_index = table.insert(raw_params,word)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- checks param count
|
||||
local param_count = #raw_params
|
||||
if param_count < command_data.min_param_count then
|
||||
Commands.error({'ExpGamingCore_Command.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[parse_callback]
|
||||
end
|
||||
if not type(parse_callback) == 'function' then
|
||||
-- if its not a function throw and error
|
||||
Commands.internal_error(success,command_data.name,'Invalid param parse '..tostring(param_data.parse))
|
||||
return
|
||||
end
|
||||
-- used below as the reject function
|
||||
local parse_fail = function(error_message)
|
||||
error_message = error_message or ''
|
||||
Commands.error('Invalid Param "'..param_name..'"; '..error_message)
|
||||
return
|
||||
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 end
|
||||
-- param_data.optional == false is so that optional parses are still ran even when not present
|
||||
if (param_data.optional == false and 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 error
|
||||
if not param_parsed == Commands.defines.error then Commands.error('Invalid Param "'..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 end
|
||||
if err ~= Commands.defines.error and err ~= Commands.defines.success then Commands.success(err) end
|
||||
end
|
||||
|
||||
return Commands
|
||||
57
expcore/common.lua
Normal file
57
expcore/common.lua
Normal 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
|
||||
@@ -1,7 +1,91 @@
|
||||
local Event = require 'utils.event'
|
||||
|
||||
function thisIsATestFunction(...)
|
||||
game.print(serpent.line({...}))
|
||||
end
|
||||
|
||||
Event.add(defines.events.on_console_chat,function(event)
|
||||
if event.player_index then game.print('Message: '..event.message) end
|
||||
end)
|
||||
|
||||
|
||||
|
||||
local Commands = require 'expcore.commands' -- require the Commands module
|
||||
|
||||
-- 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)
|
||||
|
||||
-- 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) or nil -- converts input to number
|
||||
rtn = type(rtn) == 'number' and math.floor(rtn) or nil -- floor the 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)
|
||||
|
||||
-- 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.add_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 false end -- here you can see the default check
|
||||
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_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
|
||||
--: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)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user