mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2025-12-27 11:35:22 +09:00
Merge branch 'release/5.0.0'
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
|
||||
57
control.lua
57
control.lua
@@ -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
expcore/commands.lua
Normal file
677
expcore/commands.lua
Normal 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
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
|
||||
142
expcore/common_parse.lua
Normal file
142
expcore/common_parse.lua
Normal 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
expcore/locale/de.cfg
Normal file
9
expcore/locale/de.cfg
Normal 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
expcore/locale/en.cfg
Normal file
17
expcore/locale/en.cfg
Normal 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
expcore/locale/nl.cfg
Normal file
9
expcore/locale/nl.cfg
Normal 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
expcore/locale/sv-SE.cfg
Normal file
9
expcore/locale/sv-SE.cfg
Normal 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
locale/en/commands-local.cfg
Normal file
10
locale/en/commands-local.cfg
Normal 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
locale/en/expcore.cfg
Normal file
17
locale/en/expcore.cfg
Normal 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
modules/commands/admin-chat.lua
Normal file
19
modules/commands/admin-chat.lua
Normal 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
modules/commands/admin-only-auth.lua
Normal file
13
modules/commands/admin-only-auth.lua
Normal 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
modules/commands/cheat-mode.lua
Normal file
13
modules/commands/cheat-mode.lua
Normal 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
modules/commands/commands-local.cfg
Normal file
10
modules/commands/commands-local.cfg
Normal 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
modules/commands/help.lua
Normal file
74
modules/commands/help.lua
Normal 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
modules/commands/interface.lua
Normal file
90
modules/commands/interface.lua
Normal 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
modules/commands/kill.lua
Normal file
20
modules/commands/kill.lua
Normal 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
modules/commands/me.lua
Normal file
8
modules/commands/me.lua
Normal 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
modules/commands/tag.lua
Normal file
27
modules/commands/tag.lua
Normal 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
modules/commands/teleport.lua
Normal file
56
modules/commands/teleport.lua
Normal 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)
|
||||
@@ -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__
|
||||
@@ -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__
|
||||
@@ -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__
|
||||
@@ -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__
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user