From b8b514b1334d67071bb582cd2bfd155af4368a6a Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Sat, 2 Jun 2018 21:08:33 +0100 Subject: [PATCH] Added module: ExpGamingCore.Ranking --- FactorioSoftmodManager.lua | 7 +- config.ld | 4 +- modules/ExpGamingCore/Ranking/control.lua | 535 ++++++++++-------- .../{config_ranks.lua => src/config.lua} | 40 +- .../Ranking/{base_ranks.lua => src/core.lua} | 113 +--- modules/ExpGamingCore/Ranking/src/server.lua | 43 ++ modules/ExpGamingCore/Server/control.lua | 6 + modules/ExpGamingCore/Sync/control.lua | 1 + modules/ExpGamingCore/Sync/src/ranking.lua | 23 +- modules/ExpGamingCore/softmod.json | 9 +- modules/ExpGamingLib/control.lua | 2 +- modules/index.lua | 2 +- 12 files changed, 432 insertions(+), 353 deletions(-) rename modules/ExpGamingCore/Ranking/{config_ranks.lua => src/config.lua} (80%) rename modules/ExpGamingCore/Ranking/{base_ranks.lua => src/core.lua} (53%) create mode 100644 modules/ExpGamingCore/Ranking/src/server.lua diff --git a/FactorioSoftmodManager.lua b/FactorioSoftmodManager.lua index 93382980..863f2a5c 100644 --- a/FactorioSoftmodManager.lua +++ b/FactorioSoftmodManager.lua @@ -150,7 +150,8 @@ Manager.verbose('Current state is now: "selfInit"; The verbose state is: '..tost -- @usage global[key] -- used like the normal global table -- @usage global{'foo','bar'} -- sets the default value -- @usage global(true) -- restores global to default --- @tparam[opt={}] ?table|true default the default value of global, if true then default is restored +-- @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, @@ -167,8 +168,10 @@ Manager.global=setmetatable({__defaults={},__global={ }},{ __call=function(tbl,default) local global = _G.global + local module_name = type(default) == 'string' and default or module_name + local module_path = type(default) == 'string' and moduleIndex[default] or module_path if not module_path or not module_name then return _G.global end - if default then rawset(rawget(tbl,'__defaults'),tostring(module_name),default) end + if type(default) == 'table' then rawset(rawget(tbl,'__defaults'),tostring(module_name),default) end local path = 'global' local new_dir = false for dir in module_path:gmatch('%a+') do diff --git a/config.ld b/config.ld index 9efa37a4..e8a714be 100644 --- a/config.ld +++ b/config.ld @@ -1 +1,3 @@ -new_type('command','Commands',false,'param') \ No newline at end of file +new_type('command','Commands',false,'param') +new_type('event','Commands',false,'field') +new_type('gui','Commands') \ No newline at end of file diff --git a/modules/ExpGamingCore/Ranking/control.lua b/modules/ExpGamingCore/Ranking/control.lua index 142ac0a3..4e578f53 100644 --- a/modules/ExpGamingCore/Ranking/control.lua +++ b/modules/ExpGamingCore/Ranking/control.lua @@ -1,104 +1,168 @@ ---[[ -Explosive Gaming +--- A full ranking system for factorio. +-- @module ExpGamingCore.Ranking +-- @alias Ranking +-- @author Cooldude2606 +-- @license https://github.com/explosivegaming/scenario/blob/master/LICENSE -This file can be used with permission but this and the credit below must remain in the file. -Contact a member of management on our discord to seek permission to use our code. -Any changes that you may make to the code are yours but that does not make the script yours. -Discord: https://discord.gg/r6dC2uK -]] ---Please Only Edit Below This Line----------------------------------------------------------- local Ranking = {} -defines.events.rank_change = script.generate_event_name() -Ranking._rank = {} -Ranking._group = {} --- this is just for debuging when setting you rank powers +local module_verbose = false --true|false + +--- Global Table +-- @table global +-- @field old contains the previous rank a use had before a rank change +-- @field preset contains the preset ranks that users will recive apon joining +-- @filed last_change contains the name of the player who last had there rank chagned +local global = global{old={},preset={},last_change=nil} + +--- Called when there is a rank change for a user +-- @event rank_change +-- @field name the rank id +-- @field tick the tick which the event was raised on +-- @field player_index the player whos rank was changed +-- @field by_player_index the player who changed the rank, 0 means server +-- @field new_rank the name of the rank that was given +-- @field old_rank the name of the rank the player had +script.generate_event_name('rank_change') + +--- Outputs as a string all the ranks and the loaded order +-- @usage Ranking.output_ranks(player) -- prints to player +-- @tparam[opt=server] ?player_name|player_index|LuaPlayer player the player that the info will be printed to, nil will print to server +-- @todo show inheritance of ranks function Ranking.output_ranks(player) local player = Game.get_player(player) or game.player or nil - if not player then return end - for power,rank in pairs(Ranking._ranks()) do - local output = power..') '..rank.name - output=output..' '..rank.tag + local function output(rank) local admin = 'No'; if rank.is_root then admin = 'Root' elseif rank.is_admin then admin = 'Yes' end - output=output..' Admin: '..admin - output=output..' Group: '..rank.group.name - output=output..' AFK: '..tostring(rank.base_afk_time) - player_return(output,rank.colour,player) + local rtn = string.format('%s) %q %s > Admin: %s Group: %q AFK: %s Time: %s', + rank.power,rank.name,rank.tag,admin,rank.group,tostring(rank.base_afk_time),tostring(rank.time)) + player_return(rtn,rank.colour,player) end + local function recur(_rank) + for name,rank in pairs(_rank.children) do output(Ranking.ranks[rank]) end + for name,rank in pairs(_rank.children) do recur(Ranking.ranks[rank]) end + end + local root = Ranking.get_rank(Ranking.meta.root) + output(root) + recur(root) end --- this function is to avoid errors - see /ranks.lua -function Ranking._ranks(names) - return {} -end +--- Contains the location of all the ranks, readonly during runtime +-- @table Ranking.ranks +Ranking.ranks = setmetatable({},{ + __metatable=false, + __index=table.autokey, + __newindex=function(tbl,key,value) if game then error('Can not create new ranks during runtime',2) else rawset(tbl,key,value) end end, + __len=function(tbl) + local rtn = 0 + for name,rank in pairs(tbl) do + rtn=rtn+1 + end + return rtn + end +}) --- this function is to avoid errors - see /ranks.lua -function Ranking._groups(names) - return {} -end +--- Contains the location of all the rank groups, readonly during runtime +-- @table Ranking.ranks +Ranking.groups = setmetatable({},{ + __metatable=false, + __index=table.autokey, + __newindex=function(tbl,key,value) if game then error('Can not create new rank groups during runtime',2) else rawset(tbl,key,value) end end, + __len=function(tbl) + local rtn = 0 + for name,rank in pairs(tbl) do + rtn=rtn+1 + end + return rtn + end +}) --- this function is to avoid errors - see /ranks.lua -function Ranking._meta() - return {} -end +--- Contains some meta data about the ranks +-- @table Ranking.meta +-- @field default this is the name of the default rank +-- @filed root this is the name of the root rank +-- @field time_ranks a list of all ranks which have a time requirement +-- @field time_highest the power of the highest rank that has a time requirement +-- @field time_lowest the lowest amount of time required for a time rank +Ranking.meta = setmetatable({},{ + __metatable=false, + __call=function(tbl) + rawset(tbl,'time_ranks',{}) + for name,rank in pairs(Ranking.ranks) do + if not rawget(tbl,'default') and rank.is_default then rawset(tbl,'default',rank.name) end + if not rawget(tbl,'root') and rank.is_root then rawset(tbl,'root',rank.name) end + if rank.time then + table.insert(tbl.time_ranks,rank.name) + if not rawget(tbl,'time_highest') or rank.power < tbl.time_highest then if rank.power then rawset(tbl,'time_highest',rank.power) end end + if not rawget(tbl,'time_lowest') or rank.time < tbl.time_lowest then rawset(tbl,'time_lowest',rank.time) end + end + end + if not rawget(tbl,'default') then error('No default rank') end + if not rawget(tbl,'root') then error('No root rank') end + end, + __index=function(tbl,key) + tbl() + return rawget(tbl,key) + end, + __newindex=function() error('Ranking metadata is read only',2) end +}) --- this function is to avoid errors - see addons/playerRanks.lua -function Ranking._base_preset(table) - Ranking._presets().current = table -end - --- this returns a global list -function Ranking._presets() - if not global.exp_core then global.exp_core = {} end - if not global.exp_core.ranking then global.exp_core.ranking = {meta=Ranking._meta(),old={},current={},last_jail=nil} end - return global.exp_core.ranking +--- Used to set the prset ranks that will be given to players +-- @usage Ranking._base_preset{name=rank_name,nameTwo=rank_name_two} -- sets player name to have rank rank_name on join +-- @tparam table ranks table of player names with the player name as the key and rank name as the value +function Ranking._base_preset(ranks) + if not is_type(ranks,'table') then error('Ranking._base_preset was not given a table',2) end + global.preset = ranks end --- Returns a rank object given a player or rank name --- @usage Ranking.get_rank(game.player) --- Ranking.get_rank('admin') --- @param mixed player|player index|player name|rank name|rank|'server'|'root' what rank to get --- @treturn table the rank that is linked to mixed +-- @usage Ranking.get_rank(game.player) -- returns player's rank +-- @usage Ranking.get_rank('admin') -- returns rank by the name of admin +-- @tparam ?player|player_index|player_name|rank_name|Ranking._rank|'server'|'root' mixed what rank to get +-- @treturn[1] table the rank that is linked to mixed +-- @trrturn[2] nil there was no rank found function Ranking.get_rank(mixed) - if not mixed then return false end - local ranks = Ranking._ranks(true) + if not mixed then return error('Ranking.get_rank recived no paramerters') end + local ranks = Ranking.ranks local _return = false if is_type(mixed,'table') then - if mixed.index then - _return = game.players[mixed.index] and ranks[mixed.permission_group.name] or nil - else - _return = mixed.group and mixed or nil - end + -- is it a player, then get player rank; if it is a rank then return the rank + if mixed.index then _return = game.players[mixed.index] and ranks[mixed.permission_group.name] or error('Invalid player name to Ranking.get_rank',2) + else _return = mixed.group and mixed or nil end else - _return = game.players[mixed] and ranks[game.players[mixed].permission_group.name] + -- if it is a player name/index, then get player rank; if it is a rank name, get that rank; if it is server or root; return root rank; else nil + _return = game and game.players[mixed] and ranks[game.players[mixed].permission_group.name] or table.autokey(ranks,mixed) and table.autokey(ranks,mixed) - or string.contains(mixed,'server') and Ranking.get_rank(Ranking._presets().meta.root) - or string.contains(mixed,'root') and Ranking.get_rank(Ranking._presets().meta.root) + or string.contains(mixed,'server') and Ranking.get_rank(Ranking.meta.root) + or string.contains(mixed,'root') and Ranking.get_rank(Ranking.meta.root) or nil end return _return end ---- Returns the group object used to sort ranks given group name or see Ranking.get_rank --- @usage Ranking.get_group(game.player) --- Ranking.get_group('root') --- @param mixed player|player index|player name|rank name|rank|'server'|'root'|group name|group what group to get --- @treturn table the group that is linked to mixed +--- Returns the group object used to sort ranks given group name or rank +-- @usage Ranking.get_group(game.player) -- returns player's rank group +-- @usage Ranking.get_group('root') -- returns group by name of root +-- @tparam ?player|player_index|player_name|rank_name|rank|'server'|'root'|group name|group mixed what group to get +-- @see Ranking.get_rank +-- @treturn[1] table the group that is linked to mixed +-- @trrturn[2] nil there was no rank group found function Ranking.get_group(mixed) - if not mixed then return false end - local groups = Ranking._groups(true) + if not mixed then return error('Ranking.get_group recived no paramerters') end + local groups = Ranking.groups local rank = Ranking.get_rank(mixed) - return rank and rank.group - or is_type(mixed,'table') and mixed.ranks and mixed - or is_type(mixed,'string') and table.autokey(groups,mixed) and table.autokey(groups,mixed) - or false + -- if it is a table see if it is a group, return the group; if it is a string, return group by that name; if there is a rank found, return the ranks group + return is_type(mixed,'table') and not mixed.__self and mixed.ranks and mixed + or is_type(mixed,'string') and table.autokey(groups,mixed) + or rank and rank.group + or nil end --- Prints to all rank of greater/lower power of the rank given -- @usage Ranking.print('admin','We got a grifer') --- @param rank_base the rank that acts as the cut off point (rank is always included) +-- @todo change to use parent and child ranks rather than power +-- @tparam ?Ranking._rank|pointerToRank rank_base the rank that acts as the cut off point (rank is always included) -- @param rtn what do you want to return to the players --- @tparam defines.color colour the colour that will be used to print --- @tparam bolean below if true rank below base are printed to +-- @tparam[opt=defines.color.white] defines.color colour the colour that will be used to print +-- @tparam[opt=false] boolean below if true print to children rather than parents function Ranking.print(rank_base,rtn,colour,below) local colour = colour or defines.color.white local rank_base = Ranking.get_rank(rank_base) @@ -116,89 +180,83 @@ end --- Gives a user a rank -- @usage Ranking.give_rank(1,'admin') --- @param player the player to give the rank to --- @param rank the rank to give to the player --- @param[opt='server'] by_player the player who is giving the rank --- @param[opt=game.tick] tick the tick that the rank is being given on +-- @tparam ?LuaPlayer|pointerToPlayer player the player to give the rank to +-- @tparam[opt=default] ?Ranking._rank|pointerToRank rank the rank to give to the player +-- @tparam[opt='server'] ?LuaPlayer|pointerToPlayer by_player the player who is giving the rank +-- @tparam[opt=game.tick] number tick the tick that the rank is being given on, used as pass though function Ranking.give_rank(player,rank,by_player,tick) local print_colour = defines.textcolor.info local tick = tick or game.tick local by_player_name = Game.get_player(by_player) and Game.get_player(by_player).name or game.player and game.player.name or is_type(by_player,'string') and by_player or 'server' - local rank = Ranking.get_rank(rank) or Ranking.get_rank(Ranking._presets().meta.default) - local player = Game.get_player(player) or error('No Player To Give Rank') - local old_rank = Ranking.get_rank(player) or Ranking.get_rank(Ranking._presets().meta.default) + local rank = Ranking.get_rank(rank) or Ranking.get_rank(Ranking.meta.default) + local player = Game.get_player(player) or error('No player given to Ranking.give_rank',2) + local old_rank = Ranking.get_rank(player) or Ranking.get_rank(Ranking.meta.default) local message = 'ranking.rank-down' -- messaging if old_rank.name == rank.name then return end if rank.power < old_rank.power then message = 'ranking.rank-up' player.play_sound{path='utility/achievement_unlocked'} else player.play_sound{path='utility/game_lost'} end if player.online_time > 60 or by_player_name ~= 'server' then game.print({message,player.name,rank.name,by_player_name},print_colour) end - if rank.group.name ~= 'User' then player_return({'ranking.rank-given',rank.name},print_colour,player) end + if rank.group ~= 'User' then player_return({'ranking.rank-given',rank.name},print_colour,player) end if player.tag ~= old_rank.tag then player_return({'ranking.tag-reset'},print_colour,player) end -- rank change player.permission_group = game.permissions.get_group(rank.name) player.tag = rank.tag - if old_rank.group.name ~= 'Jail' then Ranking._presets().old[player.index] = old_rank.name end + if old_rank.group ~= 'Jail' then global.old[player.index] = old_rank.name end player.admin = rank.is_admin or false player.spectator = rank.is_spectator or false - if defines.events.rank_change then - script.raise_event(defines.events.rank_change,{ - name=defines.events.rank_change, - tick=tick, - player_index=player.index, - by_player_name=by_player_name, - new_rank=rank, - old_rank=old_rank - }) + local by_player_index = by_player_name == 'server' and 0 or Game.get_player(by_player_name).index + script.raise_event(defines.events.rank_change,{ + name=defines.events.rank_change, + tick=tick, + player_index=player.index, + by_player_index=by_player_index, + new_rank=rank.name, + old_rank=old_rank.name + }) + -- logs to file if rank is chagned after first join + if player.online_time > 60 then + game.write_file('ranking-change.json', + table.json({ + tick=tick, + play_time=player.online_time, + player_name=player.name, + by_player_name=by_player_name, + new_rank=rank.name, + old_rank=old_rank.name + })..'\n' + , true, 0) end - if rank.group.name == 'Jail' and Ranking._presets().last_jail ~= player.name then - Sync.emit_embeded{ - title='Player Jail', - color=Color.to_hex(defines.textcolor.med), - description='There was a player jailed.', - ['Player:']=player.name, - ['By:']='<>'..by_player_name, - ['Reason:']='No Reason' - } - end - game.write_file('ranking-change.json', - table.json({ - tick=tick, - play_time=player.online_time, - player_name=player.name, - by_player_name=by_player_name, - new_rank=rank.name, - old_rank=old_rank.name, - power_increase=(old_rank.power-rank.power) - })..'\n' - , true, 0) end --- Revert the last change to a players rank --- @usage Ranking.revert(1) --- @param player the player to revert the rank of +-- @usage Ranking.revert(1) -- reverts the rank of player with index 1 +-- @tparam ?LuaPlayer|pointerToPlayer player the player to revert the rank of -- @param[opt=nil] by_player the player who is doing the revert function Ranking.revert(player,by_player) local player = Game.get_player(player) - Ranking.give_rank(player,Ranking._presets().old[player.index],by_player) + Ranking.give_rank(player,global.old[player.index],by_player) end ---- Given the player has a rank in the preset table it is given --- @usage Ranking.find_preset(1) --- @param player the player to test for an auto rank +--- Given that the player has a rank in the preset table it is given; also will attempt to promote players if a time requirement is met +-- @usage Ranking.find_preset(1) -- attemps to find the preset for player with index 1 +-- @tparam ?LuaPlayer|pointerToPlayer player the player to test for an auto rank -- @tparam[opt=nil] number tick the tick it happens on function Ranking.find_preset(player,tick) - local presets = Ranking._presets().current - local meta_data = Ranking._presets().meta + local presets = global.preset + local meta_data = Ranking.meta local default = Ranking.get_rank(meta_data.default) local player = Game.get_player(player) - local current_rank = Ranking.get_rank(player) or {power=-1,group={name='not jail'}} + local current_rank = Ranking.get_rank(player) or {power=-1,group='not jail'} local ranks = {default} - if current_rank.group.name == 'Jail' then return end + -- users in rank group jail are ingroned + if current_rank.group == 'Jail' then return end + -- looks in preset table for player name if presets[string.lower(player.name)] then local rank = Ranking.get_rank(presets[string.lower(player.name)]) table.insert(ranks,rank) end + -- if the player mets check requirements then play time is checked if current_rank.power > meta_data.time_highest and tick_to_min(player.online_time) > meta_data.time_lowest then for _,rank_name in pairs(meta_data.time_ranks) do local rank = Ranking.get_rank(rank_name) @@ -207,186 +265,213 @@ function Ranking.find_preset(player,tick) end end end + -- if the new rank is closer to root then it is the new rank local _rank = current_rank for _,rank in pairs(ranks) do if rank.power < _rank.power or _rank.power == -1 then _rank = rank end end - if _rank then - if _rank.name == default.name then - player.tag = _rank.tag - player.permission_group = game.permissions.get_group(_rank.name) - else - Ranking.give_rank(player,_rank,nil,tick) - end + -- this new rank is given to the player + if _rank.name == current_rank.name then return end + if _rank.name == default.name then + player.tag = _rank.tag + player.permission_group = game.permissions.get_group(_rank.name) + else + Ranking.give_rank(player,_rank,nil,tick) end end --- this is the base rank object, do not store in global - +--- The class for the ranks +-- @type Rank +-- @alias Ranking._rank +-- @field name the name that is given to the rank, must be unique +-- @field short_hand the shorter way of displaying this rank, can be used by other modules +-- @field tag the tag that player in this rank will be given +-- @field colour the colour that modules should display this rank as in guis +-- @field parent the name of the rank that permissions are inherited from, allow comes from children, disallow given to children +-- @field base_afk_time a relative number that the rank should be given that other modules can use for relitive importance +-- @field time the time that is requied for this rank to be given, can be nil for manal only +-- @field allow a list of permissions that this rank is allowed +-- @field disallow a list of acctions that is blocked by the ingame permission system +-- @field is_default will be given to all players if no other rank is set for them +-- @field is_admin will promote player to ingame admin if flag set (will auto demote if not set) +-- @field is_spectator will auto set the spectator option for the player (will cleat option if not set) +-- @field is_root rank is always allowed all action, when present in root group will become the root child that all ranks are indexed from +Ranking._rank = {} + --- Is this rank allowed to open this gui or use this command etc. --- @usage rank:allowed('server-interface') +-- @usage rank:allowed('interface') -- does the rank have permision for 'interface' -- @tparam teh action to test for --- @treturn bolean is it allowed +-- @treturn boolean is it allowed function Ranking._rank:allowed(action) return self.allow[action] or self.is_root or false end --- Get all the players in this rank -- @usage rank:get_players() --- @tparam bolean online get only online players +-- @tparam[opt=false] boolean online get only online players -- @treturn table a table of all players in this rank function Ranking._rank:get_players(online) local players = game.permissions.get_group(self.name).players local _return = {} if online then - for _,player in pairs(players) do - if player.connected then table.insert(_return,player) end - end - else - _return = players - end + for _,player in pairs(players) do if player.connected then table.insert(_return,player) end end + else _return = players end return _return end --- Print a message to all players of this rank --- @usage rank:print('foo') +-- @usage rank:print('foo') -- prints to all members of this rank -- @param rtn any value you want to return --- @tparam define.color colour the colour that will be used to print --- @tparam boolean show_default weather to use the default rank name for the print +-- @tparam[opt=defines.color.white] define.color colour the colour that will be used to print +-- @tparam[opt=false] boolean show_default weather to use the default rank name for the print, used as a pass though function Ranking._rank:print(rtn,colour,show_default) local colour = colour or defines.color.white - local meta_data = Ranking._presets().meta - local default = Ranking.get_rank(meta_data.default) - if not Server or not Server._thread then - for _,player in pairs(self:get_players(true)) do - if self.name == default.name or show_default then - player_return({'ranking.all-rank-print',rtn},colour,player) - else - player_return({'ranking.rank-print',self.name,rtn},colour,player) - end - end - else - -- using threads to make less lag - Server.new_thread{ - data={rank=self,rtn=rtn,default=default.name,all=show_default} - }:on_event('resolve',function(thread) - return thread.data.rank:get_players(true) - end):on_event('success',function(thread,players) - for _,player in pairs(players) do - if thread.data.rank.name == thread.data.default or thread.data.all then - player_return({'ranking.all-rank-print',thread.data.rtn},colour,player) - else - player_return({'ranking.rank-print',thread.data.rank.name,thread.data.rtn},colour,player) - end - end - end):queue() + local default = Ranking.get_rank(Ranking.meta.default) + for _,player in pairs(self:get_players(true)) do + if self.name == default.name or show_default then player_return({'ranking.all-rank-print',rtn},colour,player) + else player_return({'ranking.rank-print',self.name,rtn},colour,player) end end end --- this is used to edit a group once made key is what is being edited and set_value makes it over ride the current value --- see Addons/playerRanks for examples -function Ranking._rank:edit(key,set_value,value) +--- Allows for a clean way to edit rank objects +-- @usage rank:edit('allow',{'interface'}) -- allows this rank to use 'interface' +-- @tparam string key the key to edit, often allow or disallow +-- @param value the new value to be set +function Ranking._rank:edit(key,value) if game then return end - verbose('Edited Rank: '..self.name..'/'..key) - if set_value then self[key] = value return end - if key == 'disallow' then - self.disallow = table.merge(self.disallow,value,true) - elseif key == 'allow' then - self.allow = table.merge(self.allow,value) - end - Ranking._update_rank(self) + verbose('Edited Rank: '..self.group..'/'..self.name..'/'..key) + if key == 'disallow' then self.disallow = table.merge(self.disallow,value,true) + elseif key == 'allow' then self.allow = table.merge(self.allow,value) + else self[key] = value end end --- this is the base group object, do not store in global, these cant be used in game +--- The class for the rank groups, the way to allow modules to idex a group that is always present, ranks will always look to there group as a parent +-- @type Group +-- @alias Ranking._group +-- @field name the name that is given to the rank group, must be unique +-- @field parent the name of the group that permissions are inherited from +-- @field allow a list of permissions that this rank is allowed +-- @field disallow a list of acctions that is blocked by the ingame permission system +Ranking._group = {} --- this makes a new group --- {name='root',allow={},disallow={}} +--- Creates a new group +-- @usage Ranking._group:create{name='root'} -- returns group with name root +-- @tparam table obj the fields for this object +-- @treturn Ranking._group returns the object to allow chaining function Ranking._group:create(obj) if game then return end - if not is_type(obj.name,'string') then return end + if not is_type(obj.name,'string') then error('Group creationg is invalid',2) end verbose('Created Group: '..obj.name) setmetatable(obj,{__index=Ranking._group}) - self.index = #Ranking._groups(names)+1 obj.ranks = {} obj.allow = obj.allow or {} obj.disallow = obj.disallow or {} - Ranking._add_group(obj) + Ranking.groups[obj.name] = obj return obj end --- this makes a new rank in side this group --- {name='Root',short_hand='Root',tag='[Root]',time=nil,colour=defines.colors.white,allow={},disallow={}} --- if the rank is given power then it is given that place realative to the highest rank in that group,if no power then it is added to the end +--- Creats a new rank with this group as its group +-- @usage group:add_rank{name='root'} -- returns self +-- @tparam table obj the fields for this object +-- @treturn Ranking._group returns the object to allow chaining function Ranking._group:add_rank(obj) if game then return end if not is_type(obj.name,'string') or not is_type(obj.short_hand,'string') or not is_type(obj.tag,'string') or - not is_type(obj.colour,'table') then return end + not is_type(obj.colour,'table') then error('Rank creation is invalid',2) end verbose('Created Rank: '..obj.name) setmetatable(obj,{__index=Ranking._rank}) - obj.group = self + obj.group = self.name + obj.children = {} obj.allow = obj.allow or {} obj.disallow = obj.disallow or {} - obj.power = obj.power and self.highest and self.highest.power+obj.power or obj.power or self.lowest and self.lowest.power+1 or nil - setmetatable(obj.allow,{__index=self.allow}) - setmetatable(obj.disallow,{__index=self.disallow}) - Ranking._add_rank(obj,obj.power) - Ranking._set_rank_power() - table.insert(self.ranks,obj) - if not self.highest or obj.power < self.highest.power then self.highest = obj end - if not self.lowest or obj.power > self.lowest.power then self.lowest = obj end + table.insert(self.ranks,obj.name) + Ranking.ranks[obj.name] = obj + return self end --- this is used to edit a group once made key is what is being edited and set_value makes it over ride the current value --- see Addons/playerRanks for examples -function Ranking._group:edit(key,set_value,value) +--- Allows for a clean way to edit rank group objects +-- @usage group:edit('allow',{'interface'}) -- allows this rank to use 'interface' +-- @tparam string key the key to edit, often allow or disallow +-- @param value the new value to be set +function Ranking._group:edit(key,value) if game then return end verbose('Edited Group: '..self.name..'/'..key) - if set_value then self[key] = value return end - if key == 'disallow' then - self.disallow = table.merge(self.disallow,value,true) - elseif key == 'allow' then - self.allow = table.merge(self.allow,value) - end - Ranking._update_group(self) + if key == 'disallow' then self.disallow = table.merge(self.disallow,value,true) + elseif key == 'allow' then self.allow = table.merge(self.allow,value) + else self[key] = value end end -Event.register(defines.events.on_player_joined_game,function(event) +script.on_event('on_player_joined_game',function(event) Ranking.find_preset(event.player_index) end) -Event.register(-1,function(event) - for power,rank in pairs(Ranking._ranks()) do - local perm = game.permissions.create_group(rank.name) +script.on_event('on_init',function(event) + for name,rank in pairs(Ranking.ranks) do + local perm = game.permissions.create_group(name) for _,toRemove in pairs(rank.disallow) do perm.set_allows_action(defines.input_action[toRemove],false) end end end) -Event.register(defines.events.on_tick,function(event) +script.on_event('on_tick',function(event) if (((event.tick+10)/(3600*game.speed))+(15/2))% 15 == 0 then -- this is the system to auto rank players - if not Server or not Server._thread then - for _,player in pairs(game.connected_players) do - Ranking.find_preset(player,tick) - end - else - Server.new_thread{ - data={players=game.connected_players} - }:on_event('tick',function(thread) - if #thread.data.players == 0 then thread:close() return end - local player = table.remove(thread.data.players,1) - Ranking.find_preset(player,tick) - end):open() + for _,player in pairs(game.connected_players) do + Ranking.find_preset(player,tick) end - end + end end) Ranking.on_init=function(self) - require(module_path.."/base_ranks") - require(module_path.."/config_ranks") + if loaded_modules.Server then require(module_path..'/src/server') end + require(module_path..'/src/core') + require(module_path..'/src/config') + -- sets up the power system, the lower the power the closer to root, root is 0 + -- there must be a rank with is_root flag set and one rank with is_default flag set, if multiple found then first found is used + local root = Ranking.get_rank(Ranking.meta.root) + root:edit('power',0) + -- asigning of children + verbose('Creating Rank Tree') + for name,rank in pairs(Ranking.ranks) do + if rank ~= root then + if not rank.parent then error('Rank has no parent: "'..name..'"') end + if not Ranking.ranks[rank.parent] then error('Invalid parent rank: "'..rank.parent..'"') end + table.insert(Ranking.ranks[rank.parent].children,name) + Ranking.ranks[rank.parent]:edit('allow',rank.allow) + rank:edit('disallow',Ranking.ranks[rank.parent].disallow) + end + end + -- asigning of powers + verbose('Assigning Rank Powers') + local power = 1 + local function set_powers(rank) + for _,name in pairs(rank.children) do + Ranking.ranks[name]:edit('power',power) + power=power+1 + end + for _,name in pairs(rank.children) do set_powers(Ranking.ranks[name]) end + end + set_powers(root) + -- asigning group meta data + verbose('Creating Rank-Group Relationship') + for name,group in pairs(Ranking.groups) do + if name ~= 'Root' then + if not group.parent then error('Group has no parent: "'..name..'"') end + if not Ranking.groups[group.parent] then error('Invalid parent rank: "'..group.parent..'"') end + Ranking.groups[group.parent]:edit('allow',group.allow) + group:edit('disallow',Ranking.groups[group.parent].disallow) + end + for _,name in pairs(group.ranks) do + local rank = Ranking.ranks[name] + rank:edit('disallow',group.disallow) + rank:edit('allow',group.allow) + if not group.highest or Ranking.ranks[group.highest].power > rank.power then group.highest = rank.name end + if not group.lowest or Ranking.ranks[group.highest].power < rank.power then group.lowest = rank.name end + end + end end return Ranking \ No newline at end of file diff --git a/modules/ExpGamingCore/Ranking/config_ranks.lua b/modules/ExpGamingCore/Ranking/src/config.lua similarity index 80% rename from modules/ExpGamingCore/Ranking/config_ranks.lua rename to modules/ExpGamingCore/Ranking/src/config.lua index b475c0e3..2a50707d 100644 --- a/modules/ExpGamingCore/Ranking/config_ranks.lua +++ b/modules/ExpGamingCore/Ranking/src/config.lua @@ -1,12 +1,8 @@ ---[[ -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----------------------------------------------------------- +--- Description - A small description that will be displayed on the doc +-- @submodule ExpGamingCore.Ranking +-- @alias Ranking +-- @author Cooldude2606 +-- @license https://github.com/explosivegaming/scenario/blob/master/LICENSE --[[ How to use groups: @@ -34,18 +30,14 @@ Example: defines.input_action.drop_item -> 'drop_item' http://lua-api.factorio.com/latest/defines.html#defines.input_action --]] --- See ExpCore/ranks.lua for examples - you add your own and edit pre-made ones here. - -local groups = Ranking._groups(true) - -groups['Root']:edit('allow',false,{ - ['testing']=true -}) +local groups = Ranking.groups +local ranks = Ranking.ranks groups['Root']:add_rank{ name='Owner', short_hand='Owner', tag='[Owner]', + parent='Root', time=nil, colour={r=170,g=0,b=0}, is_admin = true, @@ -56,6 +48,7 @@ groups['Root']:add_rank{ name='Community Manager', short_hand='Com Mngr', tag='[Com Mngr]', + parent='Root', colour={r=150,g=68,b=161}, is_admin = true, is_spectator=true, @@ -65,16 +58,19 @@ groups['Root']:add_rank{ name='Developer', short_hand='Dev', tag='[Dev]', + parent='Root', colour={r=179,g=125,b=46}, is_admin = true, is_spectator=true, base_afk_time=false } +ranks['Admin']:edit('parent','Developer') groups['Admin']:add_rank{ name='Mod', short_hand='Mod', tag='[Mod]', + parent='Admin', colour={r=0,g=170,b=0}, disallow={ 'server_command' @@ -88,8 +84,8 @@ groups['User']:add_rank{ name='Donator', short_hand='P2W', tag='[P2W]', + parent='Mod', colour={r=233,g=63,b=233}, - power=0, is_spectator=true, base_afk_time=120 } @@ -97,24 +93,22 @@ groups['User']:add_rank{ name='Veteran', short_hand='Vet', tag='[Veteran]', + parent='Donator', time=600, colour={r=140,g=120,b=200}, - power=1, base_afk_time=60 } +ranks['Member']:edit('parent','Veteran') groups['User']:add_rank{ name='Regular', short_hand='Reg', tag='[Regular]', + parent='Member', time=180, colour={r=24,g=172,b=188}, - power=3, base_afk_time=30 } - -local ranks = Ranking._ranks(true) - -ranks['Root']:edit('test',true,'testing') +ranks['Guest']:edit('parent','Regular') Ranking._base_preset{ ['badgamernl']='Owner', diff --git a/modules/ExpGamingCore/Ranking/base_ranks.lua b/modules/ExpGamingCore/Ranking/src/core.lua similarity index 53% rename from modules/ExpGamingCore/Ranking/base_ranks.lua rename to modules/ExpGamingCore/Ranking/src/core.lua index b1ba5252..d8ad5c81 100644 --- a/modules/ExpGamingCore/Ranking/base_ranks.lua +++ b/modules/ExpGamingCore/Ranking/src/core.lua @@ -1,20 +1,8 @@ ---[[ -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 groups = {} -local ranks = {} - -function Ranking._add_group(group) if game then return end table.insert(groups,group) end -function Ranking._add_rank(rank,pos) if game then return end if pos then table.insert(ranks,pos,rank) else table.insert(ranks,rank) end end -function Ranking._set_rank_power() if game then return end for power,rank in pairs(ranks) do rank.power = power end end -function Ranking._update_rank(rank) if game then return end ranks[rank.power] = rank end -function Ranking._update_group(group) if game then return end groups[group.index] = group end +--- Description - A small description that will be displayed on the doc +-- @submodule ExpGamingCore.Ranking +-- @alias Ranking +-- @author Cooldude2606 +-- @license https://github.com/explosivegaming/scenario/blob/master/LICENSE --[[ How to use groups: @@ -42,11 +30,10 @@ Example: defines.input_action.drop_item -> 'drop_item' http://lua-api.factorio.com/latest/defines.html#defines.input_action --]] --- If you wish to add more groups please use addons/playerRanks.lua --- If you wish to add to these rank groups use addons/playerRanks.lua --- Ranks will inherite from each other ie higher ranks can do everything lower ranks can +-- If you wish to add more groups please use src/config or add during your own module +-- If you wish to add to these rank groups use src/config or add during your own module -- But groups do not inherite from each other --- DO NOT REMOVE ANY OF THESE GROUPS +-- DO NOT REMOVE ANY OF THESE GROUPS!!! local root = Ranking._group:create{ name='Root', @@ -57,9 +44,9 @@ local root = Ranking._group:create{ } local admin = Ranking._group:create{ name='Admin', + parent='Root', allow={}, disallow={ - 'set_allow_commands', 'edit_permission_group', 'delete_permission_group', 'add_permission_group' @@ -67,22 +54,15 @@ local admin = Ranking._group:create{ } local user = Ranking._group:create{ name='User', + parent='Admin', allow={}, - disallow={ - 'set_allow_commands', - 'edit_permission_group', - 'delete_permission_group', - 'add_permission_group' - } + disallow={} } local jail = Ranking._group:create{ name='Jail', + parent='User', allow={}, disallow={ - 'set_allow_commands', - 'edit_permission_group', - 'delete_permission_group', - 'add_permission_group', 'open_character_gui', 'begin_mining', 'start_walking', @@ -110,8 +90,8 @@ local jail = Ranking._group:create{ } } --- If you wish to add more ranks please use addons/playerRanks.lua --- If you wish to add to these rank use addons/playerRanks.lua +-- If you wish to add more ranks please use src/config or add during your own module +-- If you wish to add to these rank use src/config or add during your own module root:add_rank{ name='Root', short_hand='Root', @@ -127,6 +107,7 @@ admin:add_rank{ name='Admin', short_hand='Admin', tag='[Admin]', + parent='Root', colour={r=233,g=63,b=233}, is_admin=true, is_spectator=true, @@ -137,6 +118,7 @@ user:add_rank{ name='Member', short_hand='Mem', tag='[Member]', + parent='Admin', colour={r=24,g=172,b=188}, disallow={ 'set_auto_launch_rocket', @@ -149,6 +131,7 @@ user:add_rank{ name='Guest', short_hand='', tag='', + parent='Member', colour={r=255,g=159,b=27}, is_default=true, disallow={ @@ -165,66 +148,8 @@ jail:add_rank{ name='Jail', short_hand='Jail', tag='[Jail]', + parent='Guest', colour={r=50,g=50,b=50}, disallow={}, base_afk_time=false -} - -function Ranking._auto_edit_ranks() - for power,rank in pairs(ranks) do - if ranks[power-1] then - rank:edit('disallow',false,ranks[power-1].disallow) - end - end - for power = #ranks, 1, -1 do - local rank = ranks[power] - rank:edit('disallow',false,rank.group.disallow) - if ranks[power+1] then - rank:edit('allow',false,ranks[power+1].allow) - end - end -end --- used to force rank to be read-only -function Ranking._groups(name) - if name then - if name then - local _return = {} - for power,group in pairs(groups) do - _return[group.name] = group - end - return _return - end - end - return groups -end - -function Ranking._ranks(name) - if name then - local _return = {} - for power,rank in pairs(ranks) do - _return[rank.name] = rank - end - return _return - end - return ranks -end - --- used to save lag by doing some calculation at the start -function Ranking._meta() - local meta = {time_ranks={}} - for power,rank in pairs(ranks) do - meta.rank_count = power - if rank.is_default then - meta.default = rank.name - end - if rank.is_root then - meta.root = rank.name - end - if rank.time then - table.insert(meta.time_ranks,rank.name) - if not meta.time_highest or power < meta.time_highest then meta.time_highest = power end - if not meta.time_lowest or rank.time < meta.time_lowest then meta.time_lowest = rank.time end - end - end - return meta -end \ No newline at end of file +} \ No newline at end of file diff --git a/modules/ExpGamingCore/Ranking/src/server.lua b/modules/ExpGamingCore/Ranking/src/server.lua new file mode 100644 index 00000000..22b66ef7 --- /dev/null +++ b/modules/ExpGamingCore/Ranking/src/server.lua @@ -0,0 +1,43 @@ +--- Description - A small description that will be displayed on the doc +-- @submodule ExpGamingCore.Ranking +-- @alias Ranking +-- @author Cooldude2606 +-- @license https://github.com/explosivegaming/scenario/blob/master/LICENSE + +--- This file will be loaded when ExpGamingCore.Server is present +-- @function _comment + +--- Print a message to all players of this rank +-- @usage rank:print('foo') -- prints to all members of this rank +-- @param rtn any value you want to return +-- @tparam[opt=defines.color.white] define.color colour the colour that will be used to print +-- @tparam[opt=false] boolean show_default weather to use the default rank name for the print, used as a pass though +function Ranking._rank:print(rtn,colour,show_default) + local colour = colour or defines.color.white + local default = Ranking.get_rank(Ranking.meta.default) + Server.new_thread{ + data={rank=self,rtn=rtn,default=default.name,all=show_default} + }:on_event('resolve',function(thread) + return thread.data.rank:get_players(true) + end):on_event('success',function(thread,players) + for _,player in pairs(players) do + if thread.data.rank.name == thread.data.default or thread.data.all then + player_return({'ranking.all-rank-print',thread.data.rtn},colour,player) + else + player_return({'ranking.rank-print',thread.data.rank.name,thread.data.rtn},colour,player) + end + end + end):queue() +end + +script.on_event('on_tick',function(event) + if (((event.tick+10)/(3600*game.speed))+(15/2))% 15 == 0 then + Server.new_thread{ + data={players=game.connected_players} + }:on_event('tick',function(thread) + if #thread.data.players == 0 then thread:close() return end + local player = table.remove(thread.data.players,1) + Ranking.find_preset(player,tick) + end):open() + end +end) \ No newline at end of file diff --git a/modules/ExpGamingCore/Server/control.lua b/modules/ExpGamingCore/Server/control.lua index 15a9114a..9b5716d0 100644 --- a/modules/ExpGamingCore/Server/control.lua +++ b/modules/ExpGamingCore/Server/control.lua @@ -5,6 +5,7 @@ -- @license https://github.com/explosivegaming/scenario/blob/master/LICENSE local Server = {} +local module_verbose = false --true|false --- Global Table -- @table global @@ -45,6 +46,7 @@ end) -- @treturn[1] number the number of threads -- @treturn[2] table table of all threads Server.threads = setmetatable({},{ + __metatable=false, __call=function(tbl) return global.all._n end, __index=function(tbl,key) return rawget(global.all,key) end, __newindex=function(tbl,key,value) rawset(global.all,key,value) end, @@ -231,6 +233,10 @@ end --- The class for the server threads, allows abbilty to run async function -- @type Thread -- @alias Server._thread +-- @field name the name that is given to the thread, use for easy later indexing +-- @field timeout the time in ticks that the thread will have before it times out +-- @field reopen when true the thread will reopen itself untill set to false, combine with timeout to create a long on_nth_tick wait +-- @field data any data that the thread will beable to access Server._thread = {} --- Returns a new thread object diff --git a/modules/ExpGamingCore/Sync/control.lua b/modules/ExpGamingCore/Sync/control.lua index 5158bff9..b36ff928 100644 --- a/modules/ExpGamingCore/Sync/control.lua +++ b/modules/ExpGamingCore/Sync/control.lua @@ -6,6 +6,7 @@ local Sync = {} local Sync_updates = {} +local module_verbose = false --true|false --- Global Table -- @table global diff --git a/modules/ExpGamingCore/Sync/src/ranking.lua b/modules/ExpGamingCore/Sync/src/ranking.lua index fa719f2e..a15944db 100644 --- a/modules/ExpGamingCore/Sync/src/ranking.lua +++ b/modules/ExpGamingCore/Sync/src/ranking.lua @@ -18,9 +18,8 @@ end -- @treturn table contains the ranks and the players in that rank function Sync.count_ranks() if not game then return {'Offline'} end - if not Ranking then return {'Ranking module not installed'} end local _ranks = {} - for power,rank in pairs(Ranking._ranks()) do + for name,rank in pairs(Ranking.ranks) do local players = rank:get_players() for k,player in pairs(players) do players[k] = player.name end local online = rank:get_players(true) @@ -35,4 +34,22 @@ if Sync.add_to_gui then Sync.add_to_gui(function(player,frame) return 'You have been assigned the rank \''..Ranking.get_rank(player).name..'\'' end) -end \ No newline at end of file +end + +-- adds a discord emit for rank chaning +script.on_event('rank_change',function(event) + local rank = Ranking.get_rank(event.new_rank) + local player = Game.get_player(event) + local by_player_name = Game.get_player(event.by_player_index) or '' + local global = global('Ranking') + if rank.group.name == 'Jail' and global.last_change ~= player.name then + Sync.emit_embeded{ + title='Player Jail', + color=Color.to_hex(defines.textcolor.med), + description='There was a player jailed.', + ['Player:']='<>'..player.name, + ['By:']='<>'..by_player_name, + ['Reason:']='No Reason' + } + end +end) \ No newline at end of file diff --git a/modules/ExpGamingCore/softmod.json b/modules/ExpGamingCore/softmod.json index 752d42c8..98e4a8ec 100644 --- a/modules/ExpGamingCore/softmod.json +++ b/modules/ExpGamingCore/softmod.json @@ -40,7 +40,10 @@ "version": "3.4.0", "location": "url", "dependencies": { - "ExpGamingLib": ">=3.0.0" + "ExpGamingLib": ">=3.0.0", + "FactorioStdLib.Color": ">=0.8.0", + "FactorioStdLib.Table": ">=0.8.0", + "ExpGamingCore.Server": "?>=3.0.0" } }, "Server": { @@ -53,8 +56,8 @@ "dependencies": { "ExpGamingLib": ">=3.0.0", "FactorioStdLib.Table": ">=0.8.0", - "ExpGamingCore/Ranking": "?>=3.0.0", - "ExpGamingCore/Commands": "?>=3.0.0" + "ExpGamingCore.Ranking": "?>=3.0.0", + "ExpGamingCore.Commands": "?>=3.0.0" } }, "Sync": { diff --git a/modules/ExpGamingLib/control.lua b/modules/ExpGamingLib/control.lua index b44bf297..2c0e5721 100644 --- a/modules/ExpGamingLib/control.lua +++ b/modules/ExpGamingLib/control.lua @@ -72,7 +72,7 @@ end -- @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.textcolor[colour] ~= defines.color.white and defines.textcolor[colour] or defines.color[colour] + local colour = ExpLib.is_type(colour,'table') and colour or defines.textcolor[colour] ~= defines.color.white and defines.textcolor[colour] or defines.color[colour] local player = player or game.player local function _return(callback,rtn) if ExpLib.is_type(rtn,'table') then diff --git a/modules/index.lua b/modules/index.lua index 08e83cdb..8ecaf4c6 100644 --- a/modules/index.lua +++ b/modules/index.lua @@ -9,7 +9,7 @@ return { ['Color']='/modules/FactorioStdLib/Color', ['table']='/modules/FactorioStdLib/Table', ['string']='/modules/FactorioStdLib/String', - --['Ranking']='/modules/ExpGamingCore/Ranking', + ['Ranking']='/modules/ExpGamingCore/Ranking', --['commands']='/modules/ExpGamingCore/Commands', --['Gui']='/modules/ExpGamingCore/Gui', ['Server']='/modules/ExpGamingCore/Server',