diff --git a/ExpLib.lua b/ExpLib.lua deleted file mode 100644 index 7c6751f0..00000000 --- a/ExpLib.lua +++ /dev/null @@ -1,126 +0,0 @@ ---[[ -Explosive Gaming - -This file can be used with permission but this and the credit below must remain in the file. -Contact a member of management on our discord to seek permission to use our code. -Any changes that you may make to the code are yours but that does not make the script yours. -Discord: https://discord.gg/r6dC2uK -]] ---Please Only Edit Below This Line----------------------------------------------------------- - --- @module ExpLib --- @usage require('/ExpLib') - -local ExpLib = {} ---- Loads a table into the global lua table --- @usage a = {k1='foo',k2='bar'} --- _load_to_G(a) --- @tparam table tbl table to add to the global lua table -function ExpLib._unpack_to_G(tbl) - if not type(tbl) == 'table' or game then return end - for name,value in pairs(tbl) do - if not _G[name] then _G[name] = value end - end -end - ---- Returns a bolean based on the type of v matching the test type --- @usage a = 'foo' --- is_type(a,'string') -- return true --- @param v the value to be tested --- @tparam[opt=nil] string test_type the type to test for if nil then it tests for nil --- @treturn bolean is v a matching type -function ExpLib.is_type(v,test_type) - return test_type and v and type(v) == test_type or not test_type and not v or false -end - ---- Returns a value to the player or if no player then log the return --- @usage a = 'to return' --- player_return(a) --- @param rtn the value to return --- @param player the player to print to -function ExpLib.player_return(rtn,colour,player) - local colour = colour or defines.color.white - local player = player or game.player - if player then - local player = Game.get_player(player) - if not player then return end - player.play_sound{path='utility/scenario_message'} - if is_type(rtn,'table') then - -- test if its a localised string - if is_type(rtn.__self,'userdata') then player.print('Cant Display Userdata',colour) - elseif is_type(rtn[1],'string') and string.find(rtn[1],'.+[.].+') and not string.find(rtn[1],'%s') then pcall(player.print,rtn,colour) - else player.print(table.to_string(rtn),colour) - end - elseif is_type(rtn,'function') then player.print('Cant Display Functions',colour) - else player.print(tostring(rtn),colour) - end - else - local _return = 'Invalid' - if is_type(rtn,'table') then _return = table.to_string(rtn) - elseif is_type(rtn,'function') then _return = 'Cant Display Functions' - elseif is_type(rtn,'userdata') then _return = 'Cant Display Userdata' - else _return = tostring(rtn) - end log(_return) rcon.print(_return) - end -end - ---- Convert ticks to hours --- @usage a = 216001 --- tick_to_hour(a) -- return 1 --- @tparam number tick to convert to hours --- @treturn number the number of whole hours from this tick -function ExpLib.tick_to_hour(tick) - if not is_type(tick,'number') then return 0 end - return math.floor(tick/(216000*game.speed)) -end - ---- Convert ticks to minutes --- @usage a = 3601 --- tick_to_hour(a) -- return 1 --- @tparam number tick to convert to minutes --- @treturn number the number of whole minutes from this tick -function ExpLib.tick_to_min (tick) - if not is_type(tick,'number') then return 0 end - return math.floor(tick/(3600*game.speed)) -end - ---- Returns a tick in a displayable format --- @usage a = 3600 --- tick_to_display_format(a) -- return '1.00 M' --- @usage a = 234000 --- tick_to_display_format(a) -- return '1 H 5 M' --- @tparam number tick to convert --- @treturn string the formated string -function ExpLib.tick_to_display_format(tick) - if not is_type(tick,'number') then return '0H 0M' end - if tick_to_min(tick) < 10 then - return string.format('%.2f M',tick/(3600*game.speed)) - else - return string.format('%d H %d M', - tick_to_hour(tick), - tick_to_min(tick)-60*tick_to_hour(tick) - ) - end -end - -function ExpLib.Gui_tree(root) - local tree = {} - for _,child in pairs(root.children) do - if #child.children > 0 then - if child.name then - tree[child.name] = ExpLib.Gui_tree(child) - else - table.insert(tree,ExpLib.Gui_tree(child)) - end - else - if child.name then - tree[child.name] = child.type - else - table.insert(tree,child.type) - end - end - end - return tree -end - -return ExpLib \ No newline at end of file diff --git a/FactorioSoftmodManager.lua b/FactorioSoftmodManager.lua new file mode 100644 index 00000000..34580b2c --- /dev/null +++ b/FactorioSoftmodManager.lua @@ -0,0 +1,474 @@ +-- Used to load all other modules that are indexed in index.lua +local moduleIndex = require("/modules/index") +local Manager = {} +--- 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('Current state is now: "'..value.. '"; 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 > moduleEnv + +--- Default output for the verbose +-- @usage Manager.verbose('Hello, World!') +-- @tparm rtn string 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!') +-- @tparm rtn string the value that will be returned though verbose output +-- @tparm action string 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 ran in a module the the global module_name is present + if module_name then rtn='['..module_name..'] '..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 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 +-- @usage Manager.setVerbose{output=log} +-- @tparam newTbl table 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( + { + 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 + moduleEnv=false, -- during module runtime, this is a global option set within each module(module_verbose=true ln:1) 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: print || log || or other function + _output={} -- a constant value that can used to store output data + }, + { + __metatable=false, + __call=function(tbl,newTbl) + -- does not allow any new keys, but will override any existing ones + for key,value in pairs(newTbl) 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('Current state is now: "selfInit"; The verbose state is: '..tostring(Manager.setVerbose.selfInit),true) + +--- Creates a sand box envorment and runs a callback in that sand box; provents global pollution +-- @usage Manager.sandbox(callback) -- return sandbox, success, other returns from callback +-- @tparam callback function the function that will be ran in the sandbox +-- @param[opt] 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, + module_verbose=false, + module_exports=false +},{ + __metatable=false, + __call=function(tbl,callback,env,...) + if type(callback) == 'function' then + -- creates a new sandbox env + local sandbox = tbl() + -- new indexs are saved into sandbox and if _G does not have the index then look in sandbox + setmetatable(env,{__index=sandbox}) + setmetatable(_G,{__index=env,__newindex=sandbox}) + -- runs the callback + local rtn = {pcall(callback,...)} + local success = table.remove(rtn,1) + -- this is to allow modules to be access with out the need of using Mangaer[name] also keeps global clean + setmetatable(_G,{__index=ReadOnlyManager}) + if success then return sandbox, success, rtn + else return sandbox, success, rtn[1] end + else return setmetatable({},{__index=tbl}) end + end +}) + +--- Loads the modules that are present in the index list +-- @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({}, + { + __metatable=false, + __call=function(tbl) + -- ReadOnlyManager used to trigger verbose change + ReadOnlyManager.currentState = 'moduleLoad' + -- goes though the index looking for modules + for module_name,location in pairs (moduleIndex) do + Manager.verbose('Loading module: "'..module_name..'"; Location: '..location) + -- runs the module in a sandbox env + local sandbox, success, module = Manager.sandbox(require,{module_name=setupModuleName(module_name),module_location=location},location) + -- 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 "'..module_name..'": '..globals:sub(1,-3),'errorCaught') end + Manager.verbose('Successfully loaded: "'..module_name..'"; Location: '..location) + -- sets that it has been loaded and adds to the loaded index + -- if you prefere module_exports can be used rather than returning the module + if type(tbl[module_name]) == 'nil' then + -- if it is a new module then creat the new index + if sandbox.module_exports and type(sandbox.module_exports) == 'table' + then tbl[module_name] = sandbox.module_exports + else tbl[module_name] = table.remove(module,1) end + elseif type(tbl[module_name]) == 'table' then + -- if this module adds onto an existing one then append the keys + if sandbox.module_exports and type(sandbox.module_exports) == 'table' + then for key,value in pairs(sandbox.module_exports) do tbl[module_name][key] = value end + else for key,value in pairs(table.remove(module,1)) do tbl[module_name][key] = value end end + else + -- 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 + tbl[module_name] = setmetatable({__old=tbl[module_name]},{ + __call=function(tbl,...) if type(tbl.__old) == 'function' then tbl.__old(...) else return tbl.__old end end, + __tostring=function(tbl) return tbl.__old end, + __concat=function(tbl,val) return tbl.__old..val end + }) + -- same as above for adding the keys to the table + if sandbox.module_exports and type(sandbox.module_exports) == 'table' + then for key,value in pairs(sandbox.module_exports) do tbl[module_name][key] = value end + else for key,value in pairs(table.remove(module,1)) do tbl[module_name][key] = value end end + end + -- if there is a module by this name in _G ex table then it will be indexed to the new module + if rawget(_G,module_name) then setmetatable(rawget(_G,module_name),{__index=tbl[module_name]}) end + else + Manager.verbose('Failed load: "'..module_name..'"; Location: '..location..' ('..module..')','errorCaught') + end + end + -- new state for the manager to allow control of verbose + ReadOnlyManager.currentState = 'moduleInit' + -- runs though all loaded modules looking for on_init function; all other modules have been loaded + for module_name,data in pairs(tbl) do + -- looks for init so that init or on_init can be used + 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: "'..module_name) + local sandbox, success, err = Manager.sandbox(data.on_init,{module_name=setupModuleName(module_name)},data) + if success then + Manager.verbose('Successfully Initiated: "'..module_name..'"') + else + Manager.verbose('Failed Initiation: "'..module_name..'" ('..err..')','errorCaught') + end + -- clears the init function so it cant be used in runtime + data.on_init = nil + end + end + -- this could also be called runtime + 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 + end, + __tostring=function(tbl) + -- a concat of all the loaded modules + local rtn = 'Load Modules: ' + for key,value in pairs(tbl) do + rtn=rtn..key..', ' + end + return rtn:sub(1,-3) + end + } +) + +--- 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 +-- @usage Manager.error(err) -- calls all error handlers that are set or if none then prints to game and if that fails crashs game +-- @tparam err string the err string that will be passed to the handlers +-- @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 name string || fucntion the name that is given to the callback || the callback that will be used +-- @tparam[opt:type(name)=='function'] callback function if name is given as a string this will be the callback used +-- @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({ + __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 +},{ + __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 Stop','errorCaught') rawget(tbl,'__error_call')('Force Stop',2) end + -- other wise treat the call as if its been passed an err string + 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') 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') + rawget(tbl,'__error_call')(err,2) + end + end, + __index=function(tbl,key) + -- this allows the __error_handler to be called from many different names + 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' 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 +-- @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 event_name int|string index that referes to an event +-- @tparam callback function 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 +},{ + __metatable=false, + __call=function(tbl,event_name,new_callback,...) + -- 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 + if type(tbl[event_name]) == 'table' then + for module_name,callback in pairs(tbl[event_name]) do + -- loops over the call backs and which module it is from + if type(callback) ~= 'function' then error('Invalid Event Callback: "'..event_name..'/'..module_name..'"') end + local sandbox, success, err = Manager.sandbox(callback,{module_name=setupModuleName(module_name)},new_callback,...) + if not success then Manager.verbose('Event Failed: "'..tbl.names[event_name]..'/'..module_name..'" ('..err..')','errorCaught') error('Event Failed: "'..event_name..'/'..module_name..'" ('..err..')') end + -- if stop constant is returned then stop further processing + if err == rawget(tbl,'__stop') then Manager.verbose('Event Haulted By: "'..module_name..'"','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 module_name = module_name or 'FSM' + -- converts the key to a number index for the event + key = tonumber(key) or tbl.names[key] + Manager.verbose('Added Handler: "'..tbl.names[key]..'"','errorCaught') + -- 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 rawget(tbl,'__event')(key,function(...) tbl(...) end) rawset(rawget(tbl,'__events'),key,{}) end + -- adds callback to Manager.event.__events[event_id][module_name] + rawset(rawget(rawget(tbl,'__events'),key),module_name,value) + end, + __index=function(tbl,key) + -- proforms different look ups depentding weather the current module has an event handler registered + if module_name 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),module_name) + or rawget(rawget(tbl,'__events'),rawget(tbl,'names')[key]) and rawget(rawget(rawget(tbl,'__events'),rawget(tbl,'names')[key]),module_name) + 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 +-- @usage Manager.event[event_name] -- see above, can not be accessed via Manager.event.names +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 chache + if rawget(tbl,key) then return rawget(tbl,key) end + -- if it is a core event then it will simply return + if key == 'on_init' or key == 'init' then + rawset(tbl,key,-1) + elseif key == 'on_load' or key == 'load' then + rawset(tbl,key,-2) + elseif key == 'on_configuration_changed' or key == 'configuration_changed' then + rawset(tbl,key,-3) + 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 chache after being loaded in + return rawget(tbl,key) + -- if it is a string then no reverse look up is required + else return rawget(rawget(Manager.event,'events'),key) end + end +})) +--over rides for the base values; can be called though Event +Event=setmetatable({},{__index=function(tbl,key) return Manager.event[key] or script[key] or error('Invalid Index To Table Event') end}) +script.mod_name = setmetatable({},{ + __index=function(tbl,key) return _G.module_name end, + __newindex=function(tbl,key,value) error('Module Name Is Read Only') end, + __tostring=function(tbl) return _G.module_name end, + __concat=function(tbl,val) return _G.module_name..val end, + __metatable=false, +}) +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 \ No newline at end of file diff --git a/control.lua b/control.lua index 90226fbb..fe3b3285 100644 --- a/control.lua +++ b/control.lua @@ -1,3 +1,4 @@ +function _log(...) log(...) end --[[ Explosive Gaming @@ -8,78 +9,15 @@ Discord: https://discord.gg/r6dC2uK ]] --Please Only Edit Below This Line----------------------------------------------------------- --- Replaces the base error function -_error = error -error = function(err) - verbose('Error Called: '..err) - if _G.error_handle and type(error_handle) == 'function' then - verbose('Exception Caught By Error Handle') - local success, _err = pcall(error_handle,err) - if not success then _error({handle=_err,err=err}) end - elseif _G.Game and game then - verbose('Exception Caught By Game Print') - if Game.print_all(err) == 0 then - _error(err) - end - else - verbose('Failed to catch error') - _error(err) - end -end --- Replaces the base require function and verbose function -_verbose = false -- Set to true for more on the loading of the files -function verbose(str) if _verbose then log(str) print(str) end end -verbose('============================= START =============================') -require_return_err = false -- Set to false when removing files; set to true for debuging -_require = require -require = function(path) - local _path = path - if string.sub(path,1) ~= '/' then path = '/'..path end - local _return = {pcall(_require,path)} - if not table.remove(_return, 1) then - local __return = {pcall(_require,'/Addons'..path)} - if not table.remove(__return, 1) then - verbose('Failed to load: '.._path..' ('.._return[1]..')') - verbose('Also Attemped: /Addons'..path..' ('..__return[1]..')') - if require_return_err then error(unpack(_return)) end - else verbose('Loaded: '.._path) return unpack(__return) end - else verbose('Loaded: '.._path) end - return unpack(_return) -end - -verbose('Begain Base Lib Loading') -require('mod-gui') --- Loads the stdlib and allows Core Game and Event -Color, Game, Event = require('StdLib/load'){'Color','Game','Event'} - --- loads the ExpLib, functions are placed into the lua global -local ExpLib = require 'ExpLib' -verbose('ExpLib Extraction') -ExpLib._unpack_to_G(ExpLib) - -verbose('Begain Core File Loading') --- Loads the ExpCore files. These are need in order to run the other addons -Ranking, Sync, Server, Gui = require('ExpCore/load'){'Ranking','Sync','Server','Gui'} -verbose('Gui Test Initiation') -local success,err = require('ExpCore/GuiParts/test') -if success then Gui.test = err else verbose('No Test Present') end -if Gui.popup then verbose('Gui Popup Initiation') Gui.popup._load() end -if Sync._load then verbose('Sync Initiation') Sync._load() end --- Loads the ranks that Ranking uses -verbose('Base Ranks Initiation') -require('ExpCore/ranks') --- Loads any edits that are not need in core pcall as file may not be present -verbose('Extented Ranks Initiation') -require('Addons/playerRanks') --- Makes sure that all the little details are cleaned up -verbose('Ranking Initiation') -Ranking._auto_edit_ranks() --- Loads all the addons -verbose('Begain Addons Loading') -local success,err = pcall(require,'Addons/load') -if not success then error(err) end --- Loads anything that does not use ExpCore (source given in the file) -verbose('Begain Stand Alone Loading') -local success,err = pcall(require,'StandAlone/load') -if not success then error(err) end -verbose('============================== END ==============================') \ No newline at end of file +-- File Which Factorio Will Call +Manager = require("FactorioSoftmodManager") +Manager.setVerbose{ + selfInit=true, -- called while the manager is being set up + moduleLoad=true, -- when a module is required by the manager + moduleInit=true, -- when and within the initation of a module + moduleEnv=true, -- during module runtime, this is a global option set within each module for fine control + eventRegistered=true, -- when a module registers its event handlers + errorCaught=true, -- when an error is caught during runtime + output=Manager._verbose -- can be: can be: print || log || other function +} +Manager() -- can be Manager.loadModules() if called else where \ No newline at end of file diff --git a/modules/ExpGamingLib/control.lua b/modules/ExpGamingLib/control.lua new file mode 100644 index 00000000..bc691e93 --- /dev/null +++ b/modules/ExpGamingLib/control.lua @@ -0,0 +1,144 @@ +--[[ +Explosive Gaming + +This file can be used with permission but this and the credit below must remain in the file. +Contact a member of management on our discord to seek permission to use our code. +Any changes that you may make to the code are yours but that does not make the script yours. +Discord: https://discord.gg/r6dC2uK +]] +--Please Only Edit Below This Line----------------------------------------------------------- +local module_verbose = false -- there is no verbose in this file so true will do nothing +local ExpLib = {} + +--- Loads a table into _G even when sandboxed; will not overwrite values or append to tables; will not work during runtime to avoid desyncs +-- @usage unpack_to_G{key1='foo',key2='bar'} +-- @tparam table tbl -- table to be unpacked +function ExpLib.unpack_to_G(tbl) + if not type(tbl) == 'table' or game then return end + for key,value in pairs(tbl) do + if not _G[key] then rawset(_G,key,value) end + end +end + +--- Used to get the current ENV with all _G keys removed; useful when saving function to global +-- @usage get_env() -- returns current ENV with _G keys removed +-- @treturn table -- the env table with _G keys removed +function ExpLib.get_env() + local level = 2 + local env = setmetatable({},{__index=_G}) + while true do + if not debug.getinfo(level-1) then break end + local i = 1 + while true do + local name, value = debug.getlocal(level,i) + if not name then break else env[name] = value end + i=i+1 + end + level=level+1 + if debug.getinfo(level-1).namewhat == 'global' then break end + end + return env +end + +--- Compear types faster for faster valadation 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 bolean -- is v of type test_type +function ExpLib.is_type(v,test_type) + return test_type and v and type(v) == test_type or not test_type and not v 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 rtn -- 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, ingroned 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 ExpLib.player_return(rtn,colour,player) + local colour = ExpLib.is_type(colour) == 'table' and colour or defines.color[colour] + local player = player or game.player + local function _return(callback,rtn) + if ExpLib.is_type(rtn,'table') then + -- test for: userdata, locale string, table with __tostring meta method, any other table + if ExpLib.is_type(rtn.__self,'userdata') then callback('Cant Display Userdata',colour) + elseif ExpLib.is_type(rtn[1],'string') and string.find(rtn[1],'.+[.].+') and not string.find(rtn[1],'%s') then callback(rtn,colour) + elseif getmetatable(rtn) ~= nil and not tostring(rtn):find('table: 0x') then callback(tostring(rtn),colour) + else callback(table.tostring(rtn),colour) end + -- test for: function + elseif ExpLib.is_type(rtn,'function') then callback('Cant Display Functions',colour) + -- else just call tostring + else callback(tostring(rtn),colour) end + end + if player then + -- allows any vaild player identifier to be used + local player = Game.get_player(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'} + _return(player.print,rtn) + else _return(rcon.print,rtn) end +end + +--- Convert ticks to hours +-- @usage tick_to_hour(216001) -- return 1 +-- @tparam number tick -- tick to convert to hours +-- @treturn number -- the number of whole hours from this tick +function ExpLib.tick_to_hour(tick) + if not ExpLib.is_type(tick,'number') then return 0 end + return math.floor(tick/(216000*game.speed)) +end + +--- Convert ticks to minutes +-- @usage tick_to_hour(3601) -- return 1 +-- @tparam number tick -- tick to convert to minutes +-- @treturn number -- the number of whole minutes from this tick +function ExpLib.tick_to_min (tick) + if not ExpLib.is_type(tick,'number') then return 0 end + return math.floor(tick/(3600*game.speed)) +end + +--- Converts a tick into a clean format for end user +-- @usage tick_to_display_format(3600) -- return '1.00 M' +-- @usage tick_to_display_format(234000) -- return '1 H 5 M' +-- @tparam number tick -- the tick to convert +-- @treturn string -- the formated string +function ExpLib.tick_to_display_format(tick) + if not ExpLib.is_type(tick,'number') then return '0H 0M' end + if ExpLib.tick_to_min(tick) < 10 then + return string.format('%.2f M',tick/(3600*game.speed)) + else + return string.format('%d H %d M', + ExpLib.tick_to_hour(tick), + ExpLib.tick_to_min(tick)-60*ExpLib.tick_to_hour(tick) + ) + end +end + +--- Used as a way to view the structure of a gui, used for debuging +-- @usage Gui_tree(root) -- returns all children of gui recusivly +-- @tparam LuaGuiElement root -- the root to start the tree from +-- @treturn table -- the table that describes the gui +function ExpLib.gui_tree(root) + if not ExpLib.is_type(root,'table') or not root.valid then error('Invalid Gui Element given to gui_tree',2) end + local tree = {} + for _,child in pairs(root.children) do + if #child.children > 0 then + if child.name then tree[child.name] = ExpLib.gui_tree(child) + else table.insert(tree,ExpLib.gui_tree(child)) end + else + if child.name then tree[child.name] = child.type + else table.insert(tree,child.type) end + end + end + return tree +end + +-- unpacks lib to _G on module init +function ExpLib.on_init(self) + self:unpack_to_G() +end + +return ExpLib \ No newline at end of file diff --git a/modules/ExpGamingLib/softmod.json b/modules/ExpGamingLib/softmod.json new file mode 100644 index 00000000..54669d82 --- /dev/null +++ b/modules/ExpGamingLib/softmod.json @@ -0,0 +1,14 @@ +{ + "name": "ExpGamingLib", + "module": "ExpLib", + "description": "Adds some common functions used though out all ExpGaming modules", + "keywords": ["ExpGaming","Lib"], + "version": "1.0.0", + "location": "nil", + "main": "control", + "dependencies": {}, + "author": "Cooldude2606", + "contact": "Discord: Cooldude2606#5241", + "license": "https://github.com/badgamernl/explosivegaming-main/blob/master/LICENSE" +} + \ No newline at end of file diff --git a/modules/index.lua b/modules/index.lua new file mode 100644 index 00000000..da98a4a0 --- /dev/null +++ b/modules/index.lua @@ -0,0 +1,3 @@ +return { + ['ExpLib']='/modules/ExpGamingLib/control', +} \ No newline at end of file