From e9a43822773aa10a5b625bebd80252825272a1d3 Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Tue, 9 Apr 2019 16:16:05 +0100 Subject: [PATCH 01/15] Changed add_tag to set_flag on commands --- expcore/commands.lua | 16 ++++++++-------- modules/commands/admin-chat.lua | 2 +- modules/commands/cheat-mode.lua | 4 ++-- modules/commands/help.lua | 2 +- modules/commands/interface.lua | 2 +- modules/commands/kill.lua | 4 ++-- modules/commands/tag.lua | 2 +- modules/commands/teleport.lua | 6 +++--- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/expcore/commands.lua b/expcore/commands.lua index a3c90991..e096cc19 100644 --- a/expcore/commands.lua +++ b/expcore/commands.lua @@ -60,8 +60,8 @@ 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 + :set_defaults{smiley=false} -- adds false as the default for smiley + :set_flag('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 @@ -119,8 +119,8 @@ return false end end) - :add_defaults{smiley=false} - :add_tag('admin_only',true) + :set_defaults{smiley=false} + :set_flag('admin_only',true) :add_alias('name','rname') :register(function(player,repeat_count,smiley,raw) game.print(player.name..' used a command with input: '..raw) @@ -147,8 +147,8 @@ 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:set_defaults(defaults) --- Adds default values to params only matters if the param is optional + Commands._prototype:set_flag(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 @@ -399,7 +399,7 @@ end -- @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) +function Commands._prototype:set_defaults(defaults) for name,value in pairs(defaults) do if self.params[name] then self.params[name].default = value @@ -413,7 +413,7 @@ end -- @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) +function Commands._prototype:set_flag(name,value) if not value then -- value not given so name is the value table.insert(self.tags,name) diff --git a/modules/commands/admin-chat.lua b/modules/commands/admin-chat.lua index 2f9df730..c3fb6e5f 100644 --- a/modules/commands/admin-chat.lua +++ b/modules/commands/admin-chat.lua @@ -4,7 +4,7 @@ require 'config.command_parse_general' 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) +:set_flag('admin_only',true) :add_alias('ac') :register(function(player,message,raw) local pcc = player and player.chat_color or {r=255,g=255,b=255} diff --git a/modules/commands/cheat-mode.lua b/modules/commands/cheat-mode.lua index dbbdc54d..6aef5fca 100644 --- a/modules/commands/cheat-mode.lua +++ b/modules/commands/cheat-mode.lua @@ -3,10 +3,10 @@ require 'config.command_parse_general' 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) +:set_defaults{player=function(player) return player -- default is the user using the command end} -:add_tag('admin_only',true) +:set_flag('admin_only',true) :register(function(player,action_player,raw) action_player.cheat_mode = not action_player.cheat_mode end) \ No newline at end of file diff --git a/modules/commands/help.lua b/modules/commands/help.lua index 9d8a2183..c4a78567 100644 --- a/modules/commands/help.lua +++ b/modules/commands/help.lua @@ -12,7 +12,7 @@ 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} +:set_defaults{keyword='',page=1} :register(function(player,keyword,page,raw) local player_index = player and player.index or 0 -- if keyword is a number then treat it as page number diff --git a/modules/commands/interface.lua b/modules/commands/interface.lua index da03fdd1..6ee924c0 100644 --- a/modules/commands/interface.lua +++ b/modules/commands/interface.lua @@ -48,7 +48,7 @@ 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) +:set_flag('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 diff --git a/modules/commands/kill.lua b/modules/commands/kill.lua index d1e4de25..24dc16bc 100644 --- a/modules/commands/kill.lua +++ b/modules/commands/kill.lua @@ -3,13 +3,13 @@ require 'config.command_parse_general' 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) +:set_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) +:set_flag('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 diff --git a/modules/commands/tag.lua b/modules/commands/tag.lua index 97ae6c21..4105d92a 100644 --- a/modules/commands/tag.lua +++ b/modules/commands/tag.lua @@ -10,7 +10,7 @@ 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) +:set_defaults{player=function(player) return player -- default is the user using the command end} :register(function(player,action_player,raw) diff --git a/modules/commands/teleport.lua b/modules/commands/teleport.lua index 7275f56a..dc87b024 100644 --- a/modules/commands/teleport.lua +++ b/modules/commands/teleport.lua @@ -14,7 +14,7 @@ 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) +:set_flag('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 @@ -28,7 +28,7 @@ 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) +:set_flag('admin_only',true) :register(function(player,from_player,raw) if from_player.index == player.index then -- return if attempting to teleport to self @@ -43,7 +43,7 @@ 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) +:set_flag('admin_only',true) :register(function(player,to_player,raw) if to_player.index == player.index then -- return if attempting to teleport to self From f4426058668064fa242875e38b04e5931cab8120 Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Tue, 9 Apr 2019 18:54:39 +0100 Subject: [PATCH 02/15] Added Roles --- config/file_loader.lua | 1 + config/roles.lua | 1 + expcore/locale/en.cfg | 8 +- expcore/roles.lua | 421 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 430 insertions(+), 1 deletion(-) create mode 100644 config/roles.lua create mode 100644 expcore/roles.lua diff --git a/config/file_loader.lua b/config/file_loader.lua index 3a85a462..0daa5df4 100644 --- a/config/file_loader.lua +++ b/config/file_loader.lua @@ -26,4 +26,5 @@ return { 'config.command_auth_admin', -- commands tagged with admin_only are blocked for non admins 'config.command_auth_runtime_disable', -- allows commands to be enabled and disabled during runtime 'config.permission_groups', -- loads some predefined permission groups + 'config.roles', -- loads some predefined roles } \ No newline at end of file diff --git a/config/roles.lua b/config/roles.lua new file mode 100644 index 00000000..bd2e69d1 --- /dev/null +++ b/config/roles.lua @@ -0,0 +1 @@ +local Roles = require 'expcore.roles' \ No newline at end of file diff --git a/expcore/locale/en.cfg b/expcore/locale/en.cfg index b33e7ee3..2a9b5451 100644 --- a/expcore/locale/en.cfg +++ b/expcore/locale/en.cfg @@ -14,4 +14,10 @@ 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__ \ No newline at end of file +command-error-log-format=[ERROR] command/__1__ :: __2__ + +[expcore-roles] +error-log-format-flag=[ERROR] roleFlag/__1__ :: __2__ +error-log-format-promote=[ERROR] rolePromote/__1__ :: __2__ +game-message-assign=__1__ has been assigned to __2__ by __3__ +game-message-unassign=__1__ has been unassigned from __2__ by __3__ \ No newline at end of file diff --git a/expcore/roles.lua b/expcore/roles.lua new file mode 100644 index 00000000..be3f834b --- /dev/null +++ b/expcore/roles.lua @@ -0,0 +1,421 @@ +local Game = require 'utils.game' +local Global = require 'utils.global' +local Event = require 'utils.event' +local Groups = require 'expcore.permission_groups' +local Colours = require 'resources.color_presets' + +local Roles = { + config={ + order={}, -- Contains the order of the roles, lower index is better + roles={}, -- Contains the raw info for the roles, indexed by role name + flags={}, -- Contains functions that run when a flag is added/removed from a player + internal={}, -- Contains all internally accessed roles, such as root, default + players={} + }, + player_role_assigned=script.generate_event_name(), + player_role_unassigned=script.generate_event_name(), + _prototype={} +} + +local function emit_player_roles_updated(player,type,roles,by_player_name) + local event = Roles.player_role_assigned + if type == 'unassign' then + event = Roles.player_role_unassigned + end + local by_player = Game.get_player_from_any(by_player_name) + local by_player_index = by_player and by_player.index or 0 + local role_names = {} + for _,role in pairs(roles) do + table.insert(role_names,role.name) + end + game.print({'expcore-roles.game-message-'..type,player.name,role_names.join(', '),by_player_name or ''},Colours.cyan) + script.raise_event(event,{ + name=Roles.player_roles_updated, + tick=game.tick, + player_index=player.index, + by_player_index=by_player_index, + roles=roles + }) + game.write_file('log/roles.log',game.table_to_json{ + player_name=player.name, + by_player_name=by_player_name or '', + type=type, + roles_changed=roles + }..'\n',true,0) +end + +Global.register(Roles.config,function(tbl) + Roles.config = tbl + for _,role in pairs(Roles.config.roles) do + setmetatable(role,{__index=Roles._prototype}) + local parent = Roles.config.roles[role.parent] + if parent then + setmetatable(role.allow, {__index=parent.allow}) + end + end +end) + +function Roles.get_role_by_name(name) + return Roles.config.roles[name] +end + +function Roles.get_role_by_order(index) + local name = Roles.config.order[index] + return Roles.config.roles[name] +end + +function Roles.get_role_from_any(any) + local tany = type(any) + if tany == 'number' or tonumber(any) then + any = tonumber(any) + return Roles.get_role_by_order(any) + elseif tany == 'string' then + return Roles.get_role_by_name(any) + elseif tany == 'table' then + return Roles.get_role_by_name(any.name) + end +end + +function Roles.get_player_roles(player) + player = Game.get_player_from_any(player) + if not player then return end + local roles = Roles.config.players[player.name] or {} + table.insert(roles,Roles.config.internal.default) + for index,role_name in pairs(roles) do + roles[index] = Roles.config.roles[role_name] + end + return roles +end + +function Roles.assign_player(player,roles,by_player_name) + player = Game.get_player_from_any(player) + if not player then return end + if type(roles) ~= 'table' or roles.name then + roles = {roles} + end + for _,role in pairs(roles) do + role = Roles.get_role_from_any(role) + if role then + role:add_player(player,false,true) + end + end + emit_player_roles_updated(player,'assign',roles,by_player_name) +end + +function Roles.unassign_player(player,roles,by_player_name) + player = Game.get_player_from_any(player) + if not player then return end + if type(roles) ~= 'table' or roles.name then + roles = {roles} + end + for _,role in pairs(roles) do + role = Roles.get_role_from_any(role) + if role then + role:remove_player(player,false,true) + end + end + emit_player_roles_updated(player,'unassign',roles,by_player_name) +end + +function Roles.override_player_roles(roles) + Roles.config.players = roles +end + +function Roles.player_has_role(player,search_role) + local roles = Roles.get_player_roles(player) + if not roles then return end + search_role = Roles.get_role_from_any(search_role) + if not search_role then return end + for _,role in pairs(roles) do + if role == search_role.name then return true end + end + return false +end + +function Roles.player_has_flag(player,flag_name) + local roles = Roles.get_player_roles(player) + if not roles then return end + for _,role in pairs(roles) do + if role:has_flag(flag_name) then + return true + end + end + return false +end + +function Roles.player_allowed(player,action) + local roles = Roles.get_player_roles(player) + if not roles then return end + for _,role in pairs(roles) do + if role:allowed(action) then + return true + end + end + return false +end + +function Roles.define_role_order(order) + local sanitized = {} + for _,role in ipairs(order) do + if type(role) == 'table' and role.name then + table.insert(sanitized,role.name) + else + table.insert(sanitized,role) + end + end + Roles.config.order = sanitized + for index,role in pairs(sanitized) do + Roles.config.roles[role].index = index + end +end + +function Roles.define_flag_trigger(name,callback) + Roles.config.flags[name] = callback -- this can desync if there are upvalues +end + +function Roles.set_default(name) + local role = Roles.config.roles[name] + if not role then return end + Roles.config.internal.default = name +end + +function Roles.set_root(name) + local role = Roles.config.roles[name] + if not role then return end + Roles.config.internal.root = name +end + +function Roles.new_role(name,short_hand) + if Roles.config.roles[name] then return error('Role name is non unique') end + local role = setmetatable({ + name=name, + short_hand=short_hand or name, + allow={}, + allow_all_actions=false, + flags={} + },{__index=Roles._prototype}) + Roles.config.roles[name] = role + return role +end + +function Roles._prototype:set_allow_all(state) + self.allow_all_actions = not not state + return self +end + +function Roles._prototype:allow(actions) + if type(actions) ~= 'table' then + actions = {actions} + end + for _,action in pairs(actions) do + self.allow[action]=true + end + return self +end + +function Roles._prototype:disallow(actions) + if type(actions) ~= 'table' then + actions = {actions} + end + for _,action in pairs(actions) do + self.allow[action]=false + end + return self +end + +function Roles._prototype:is_allowed(action) + local is_root = Roles.config.internal.root.name == self.name + return self.allowed[action] or self.allow_all_actions or is_root +end + +function Roles._prototype:set_flag(name,value) + self.flags[name] = value + return self +end + +function Roles._prototype:clear_flags() + self.flags = {} + return self +end + +function Roles._prototype:has_flag(name) + return self.flags[name] or false +end + +function Roles._prototype:set_custom_tag(tag) + self.custom_tag = tag + return self +end + +function Roles._prototype:set_permission_group(name,use_factorio_api) + if not use_factorio_api then + self.permission_group = {true,name} + else + local group = Groups.get_group_by_name(name) + if not group then return end + self.permission_group = name + end + return self +end + +function Roles._prototype:set_parent(role) + role = Role.get_role_from_any(role) + if not role then return end + self.parent = role.name + setmetatable(self.allow, {__index=role.allow}) + return self +end + +function Roles._prototype:set_auto_promote_condition(callback) + self.auto_promote_condition = callback + return self +end + +function Roles._prototype:add_player(player,skip_check,skip_event) + player = Game.get_player_from_any(player) + -- Check the player is valid, can be skipped but a name must be given + if not player then + if skip_check then + player = {name=player} + else + return false + end + end + -- Add the role name to the player's roles + local player_roles = Roles.config.players[player.name] + if player_roles then + for _,role_name in pairs(player_roles) do + if role_name == self.name then return false end + end + table.insert(player_roles,self.name) + else + Roles.config.players[player.name] = {self.name} + end + -- Emits event if required + if not skip_event then + local by_player_name + if game.player then by_player_name = game.player.name end + emit_player_roles_updated(player,'assign',{self},by_player_name) + end + return true +end + +function Roles._prototype:remove_player(player,skip_check,skip_event) + player = Game.get_player_from_any(player) + -- Check the player is valid, can be skipped but a name must be given + if not player then + if skip_check then + player = {name=player} + else + return false + end + end + -- Remove the role from the players roles + local player_roles = Roles.config.players[player.name] + local rtn = false + if player_roles then + for index,role_name in pairs(player_roles) do + if role_name == self.name then + table.remove(player_roles,index) + rtn = true + break + end + end + if #player_roles == 0 then + Roles.config.players[player.name] = nil + end + end + -- Emits event if required + if not skip_event then + local by_player_name + if game.player then by_player_name = game.player.name end + emit_player_roles_updated(player,'unassign',{self},by_player_name) + end + return rtn +end + +function Roles._prototype:get_players(online) + local players = {} + -- Gets all players that have this role + for player_name,player_roles in pairs(Roles.config.players) do + for _,role_name in pairs(player_roles) do + if role_name == self.name then + table.insert(players,player_name) + end + end + end + -- Convert the player names to LuaPlayer + for index,player_name in pairs(players) do + players[index] = Game.get_player_from_any(player_name) + end + -- Filter by online if param is defined + if online == nil then + return players + else + local filtered = {} + for _,player in pairs(players) do + if player.connected == online then + table.insert(filtered,player) + end + end + return filtered + end +end + +function Roles._prototype:print(message) + local players = self:get_players(true) + for _,player in pairs(players) do + player.print(message) + end + return #players +end + +local function role_update(event) + local player = Game.get_player_by_idnex(event.player_index) + -- Updates flags given to the player + for flag,callback in pairs(Role.config.flags) do + local state = Role.player_has_flag(player,flag) + local success,err = pcall(callback,player,state) + if not success then + log{'expcore-roles.error-log-format-flag',flag,err} + end + end + -- Updates the players permission group + local highest + local roles = Roles.get_player_roles(player) + for _,role in pairs(roles) do + if not highest or role.index < highest.index then + highest = role + end + end + Groups.set_player_group(player,highest.permission_group) +end + +Event.add(Roles.player_role_assigned,role_update) +Event.add(Roles.player_role_unassigned,role_update) +Event.on_nth_tick(300,function() + local promotes = {} + for _,player in pairs(game.connected_players) do + for _,role in pairs(Roles.config.roles) do + if role.auto_promote_condition then + local success,err = pcall(role.auto_promote_condition,player) + if not success then + log{'expcore-roles.error-log-format-promote',role.name,err} + else + if err == true then + if promotes[player.name] then + table.insert(promotes[player.name],role.name) + else + promotes[player.name] = {role.name} + end + end + end + end + end + end + for player_name,roles in pairs(promotes) do + Roles.assign(player_name,roles) + end +end) + +return Roles \ No newline at end of file From d7474947f083634600c7ae4878297d645a211fb0 Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Tue, 9 Apr 2019 19:09:02 +0100 Subject: [PATCH 03/15] Added Test role config --- config/permission_groups.lua | 4 ++-- config/roles.lua | 45 +++++++++++++++++++++++++++++++++++- expcore/roles.lua | 7 +++++- 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/config/permission_groups.lua b/config/permission_groups.lua index b14fa9e9..b895e6ed 100644 --- a/config/permission_groups.lua +++ b/config/permission_groups.lua @@ -100,7 +100,7 @@ Permission_Groups.new_group('Restricted') :disallow_all() :allow('write_to_console') ---- These events are used until a role system is added to make it easier for our admins +--[[ These events are used until a role system is added to make it easier for our admins local trusted_time = 60*60*60*10 -- 10 hour local standard_time = 60*60*60*3 -- 3 hour @@ -137,4 +137,4 @@ Event.on_nth_tick(check_interval,function(event) for _,player in pairs(game.connected_players) do assign_group(player) end -end) \ No newline at end of file +end)]] \ No newline at end of file diff --git a/config/roles.lua b/config/roles.lua index bd2e69d1..7d685e13 100644 --- a/config/roles.lua +++ b/config/roles.lua @@ -1 +1,44 @@ -local Roles = require 'expcore.roles' \ No newline at end of file +local Roles = require 'expcore.roles' + +-- Use these to adjust for ticks ie game.tick < 5*minutes +local seconds, minutes, hours = 60, 3600, 216000 + +local function playtime(time_required) + return function(player) + if player.online_time > time_required then + return true + end + end +end + +Roles.define_flag_trigger('admin',function(player,state) + player.admin = state +end) + +Roles.new_role('Server','SYS') +:set_allow_all(true) + +Roles.new_role('Senior Admin','SAdmin') +:set_allow_all(false) +:set_flag('admin',true) +:set_parent('Regular') +:set_permission_group('Admin') + +Roles.new_role('Regular','Reg') +:set_allow_all(false) +:set_auto_promote_condition(playtime(5*minutes)) +:set_parent('Guest') +:set_permission_group('Trusted') + +Roles.new_role('Guest','') +:set_allow_all(false) +:set_permission_group('Standard') + +Roles.set_root('Server') +Roles.set_default('Guest') + +Roles.define_role_order{ + 'Server', + 'Senior Admin', + 'Guest' +} \ No newline at end of file diff --git a/expcore/roles.lua b/expcore/roles.lua index be3f834b..d5fb08df 100644 --- a/expcore/roles.lua +++ b/expcore/roles.lua @@ -166,6 +166,9 @@ function Roles.define_role_order(order) Roles.config.order = sanitized for index,role in pairs(sanitized) do Roles.config.roles[role].index = index + if role.parent then + setmetatable(role.allow, Roles.config.roles[role.parent].allow) + end end end @@ -388,7 +391,9 @@ local function role_update(event) highest = role end end - Groups.set_player_group(player,highest.permission_group) + if highest.permission_group then + Groups.set_player_group(player,highest.permission_group) + end end Event.add(Roles.player_role_assigned,role_update) From 73a319b220550e77785db6426375ab896b722d7d Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Tue, 9 Apr 2019 20:53:17 +0100 Subject: [PATCH 04/15] Debuged all functions --- config/command_auth_roles.lua | 11 ++++ config/file_loader.lua | 1 + config/roles.lua | 22 ++++++- expcore/commands.lua | 2 +- expcore/roles.lua | 107 +++++++++++++++++++++------------ locale/en/expcore.cfg | 9 +-- modules/addons/spawn-area.lua | 2 +- modules/commands/interface.lua | 3 +- utils/game.lua | 2 +- 9 files changed, 109 insertions(+), 50 deletions(-) create mode 100644 config/command_auth_roles.lua diff --git a/config/command_auth_roles.lua b/config/command_auth_roles.lua new file mode 100644 index 00000000..8a87f047 --- /dev/null +++ b/config/command_auth_roles.lua @@ -0,0 +1,11 @@ +--- This will make commands only work if the role has been allowed it in the role config +local Commands = require 'expcore.commands' +local Roles = require 'expcore.roles' + +Commands.add_authenticator(function(player,command,tags,reject) + if Roles.player_allowed(player,command) then + return true + else + return reject() + end +end) \ No newline at end of file diff --git a/config/file_loader.lua b/config/file_loader.lua index 0daa5df4..993582b8 100644 --- a/config/file_loader.lua +++ b/config/file_loader.lua @@ -24,6 +24,7 @@ return { 'modules.addons.worn-paths', -- Config Files 'config.command_auth_admin', -- commands tagged with admin_only are blocked for non admins + 'config.command_auth_roles', -- commands must be allowed via the role config 'config.command_auth_runtime_disable', -- allows commands to be enabled and disabled during runtime 'config.permission_groups', -- loads some predefined permission groups 'config.roles', -- loads some predefined roles diff --git a/config/roles.lua b/config/roles.lua index 7d685e13..b6472d72 100644 --- a/config/roles.lua +++ b/config/roles.lua @@ -17,21 +17,32 @@ end) Roles.new_role('Server','SYS') :set_allow_all(true) +:set_flag('admin',true) Roles.new_role('Senior Admin','SAdmin') -:set_allow_all(false) +:set_flag('admin',true) +:set_parent('Admin') +:set_permission_group('Admin') +:set_custom_color('blue') +:allow{ + 'interface' +} + +Roles.new_role('Admin','Admin') :set_flag('admin',true) :set_parent('Regular') :set_permission_group('Admin') +:allow{ + 'tp' +} Roles.new_role('Regular','Reg') -:set_allow_all(false) :set_auto_promote_condition(playtime(5*minutes)) :set_parent('Guest') +:set_custom_tag('Reg') :set_permission_group('Trusted') Roles.new_role('Guest','') -:set_allow_all(false) :set_permission_group('Standard') Roles.set_root('Server') @@ -40,5 +51,10 @@ Roles.set_default('Guest') Roles.define_role_order{ 'Server', 'Senior Admin', + 'Regular', 'Guest' +} + +Roles.override_player_roles{ + Cooldude2606={'Server','Senior Admin'} } \ No newline at end of file diff --git a/expcore/commands.lua b/expcore/commands.lua index e096cc19..efc45a87 100644 --- a/expcore/commands.lua +++ b/expcore/commands.lua @@ -516,7 +516,7 @@ end -- @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 + if value ~= nil then player_return(value) end player_return({'expcore-commands.command-ran'},'cyan') return Commands.defines.success end diff --git a/expcore/roles.lua b/expcore/roles.lua index d5fb08df..98ebc989 100644 --- a/expcore/roles.lua +++ b/expcore/roles.lua @@ -18,6 +18,7 @@ local Roles = { } local function emit_player_roles_updated(player,type,roles,by_player_name) + by_player_name = game.player and game.player.name or by_player_name or '' local event = Roles.player_role_assigned if type == 'unassign' then event = Roles.player_role_unassigned @@ -26,9 +27,12 @@ local function emit_player_roles_updated(player,type,roles,by_player_name) local by_player_index = by_player and by_player.index or 0 local role_names = {} for _,role in pairs(roles) do - table.insert(role_names,role.name) + role = Roles.get_role_from_any(role) + if role then + table.insert(role_names,role.name) + end end - game.print({'expcore-roles.game-message-'..type,player.name,role_names.join(', '),by_player_name or ''},Colours.cyan) + game.print({'expcore-roles.game-message-'..type,player.name,table.concat(role_names,', '),by_player_name},Colours.cyan) script.raise_event(event,{ name=Roles.player_roles_updated, tick=game.tick, @@ -38,7 +42,7 @@ local function emit_player_roles_updated(player,type,roles,by_player_name) }) game.write_file('log/roles.log',game.table_to_json{ player_name=player.name, - by_player_name=by_player_name or '', + by_player_name=by_player_name, type=type, roles_changed=roles }..'\n',true,0) @@ -50,11 +54,22 @@ Global.register(Roles.config,function(tbl) setmetatable(role,{__index=Roles._prototype}) local parent = Roles.config.roles[role.parent] if parent then - setmetatable(role.allow, {__index=parent.allow}) + setmetatable(role.allowed_actions, {__index=parent.allowed_actions}) end end end) +function Roles.debug() + local output = '' + for index,role_name in pairs(Roles.config.order) do + local role = Roles.config.roles[role_name] + local color = role.custom_color or Colours.white + color = string.format('[color=%d,%d,%d]',color.r,color.g,color.b) + output = output..string.format('\n%s %s) %s',color,index,serpent.line(role)) + end + return output +end + function Roles.get_role_by_name(name) return Roles.config.roles[name] end @@ -80,11 +95,12 @@ function Roles.get_player_roles(player) player = Game.get_player_from_any(player) if not player then return end local roles = Roles.config.players[player.name] or {} - table.insert(roles,Roles.config.internal.default) - for index,role_name in pairs(roles) do - roles[index] = Roles.config.roles[role_name] + local default = Roles.config.roles[Roles.config.internal.default] + local rtn = {default} + for _,role_name in pairs(roles) do + table.insert(rtn,Roles.config.roles[role_name]) end - return roles + return rtn end function Roles.assign_player(player,roles,by_player_name) @@ -127,7 +143,7 @@ function Roles.player_has_role(player,search_role) search_role = Roles.get_role_from_any(search_role) if not search_role then return end for _,role in pairs(roles) do - if role == search_role.name then return true end + if role.name == search_role.name then return true end end return false end @@ -147,7 +163,7 @@ function Roles.player_allowed(player,action) local roles = Roles.get_player_roles(player) if not roles then return end for _,role in pairs(roles) do - if role:allowed(action) then + if role:is_allowed(action) then return true end end @@ -155,19 +171,20 @@ function Roles.player_allowed(player,action) end function Roles.define_role_order(order) - local sanitized = {} + Roles.config.order = {} for _,role in ipairs(order) do if type(role) == 'table' and role.name then - table.insert(sanitized,role.name) + table.insert(Roles.config.order,role.name) else - table.insert(sanitized,role) + table.insert(Roles.config.order,role) end end - Roles.config.order = sanitized - for index,role in pairs(sanitized) do - Roles.config.roles[role].index = index - if role.parent then - setmetatable(role.allow, Roles.config.roles[role.parent].allow) + for index,role in pairs(Roles.config.order) do + role = Roles.config.roles[role] + role.index = index + local parent = Roles.config.roles[role.parent] + if parent then + setmetatable(role.allowed_actions,{__index=parent.allowed_actions}) end end end @@ -193,7 +210,7 @@ function Roles.new_role(name,short_hand) local role = setmetatable({ name=name, short_hand=short_hand or name, - allow={}, + allowed_actions={}, allow_all_actions=false, flags={} },{__index=Roles._prototype}) @@ -211,7 +228,7 @@ function Roles._prototype:allow(actions) actions = {actions} end for _,action in pairs(actions) do - self.allow[action]=true + self.allowed_actions[action]=true end return self end @@ -221,14 +238,14 @@ function Roles._prototype:disallow(actions) actions = {actions} end for _,action in pairs(actions) do - self.allow[action]=false + self.allowed_actions[action]=false end return self end function Roles._prototype:is_allowed(action) local is_root = Roles.config.internal.root.name == self.name - return self.allowed[action] or self.allow_all_actions or is_root + return self.allowed_actions[action] or self.allow_all_actions or is_root end function Roles._prototype:set_flag(name,value) @@ -250,8 +267,16 @@ function Roles._prototype:set_custom_tag(tag) return self end +function Roles._prototype:set_custom_color(color) + if type(color) ~= 'table' then + color = Colours[color] + end + self.custom_color = color + return self +end + function Roles._prototype:set_permission_group(name,use_factorio_api) - if not use_factorio_api then + if use_factorio_api then self.permission_group = {true,name} else local group = Groups.get_group_by_name(name) @@ -262,10 +287,10 @@ function Roles._prototype:set_permission_group(name,use_factorio_api) end function Roles._prototype:set_parent(role) - role = Role.get_role_from_any(role) - if not role then return end - self.parent = role.name - setmetatable(self.allow, {__index=role.allow}) + self.parent = role + role = Roles.get_role_from_any(role) + if not role then return self end + setmetatable(self.allowed_actions, {__index=role.allowed_actions}) return self end @@ -296,9 +321,7 @@ function Roles._prototype:add_player(player,skip_check,skip_event) end -- Emits event if required if not skip_event then - local by_player_name - if game.player then by_player_name = game.player.name end - emit_player_roles_updated(player,'assign',{self},by_player_name) + emit_player_roles_updated(player,'assign',{self}) end return true end @@ -330,9 +353,7 @@ function Roles._prototype:remove_player(player,skip_check,skip_event) end -- Emits event if required if not skip_event then - local by_player_name - if game.player then by_player_name = game.player.name end - emit_player_roles_updated(player,'unassign',{self},by_player_name) + emit_player_roles_updated(player,'unassign',{self}) end return rtn end @@ -374,10 +395,10 @@ function Roles._prototype:print(message) end local function role_update(event) - local player = Game.get_player_by_idnex(event.player_index) + local player = Game.get_player_by_index(event.player_index) -- Updates flags given to the player - for flag,callback in pairs(Role.config.flags) do - local state = Role.player_has_flag(player,flag) + for flag,callback in pairs(Roles.config.flags) do + local state = Roles.player_has_flag(player,flag) local success,err = pcall(callback,player,state) if not success then log{'expcore-roles.error-log-format-flag',flag,err} @@ -392,12 +413,20 @@ local function role_update(event) end end if highest.permission_group then - Groups.set_player_group(player,highest.permission_group) + if highest.permission_group[1] then + local group = game.permissions.get_group(highest.permission_group[2]) + if group then + group.add_player(player) + end + else + Groups.set_player_group(player,highest.permission_group) + end end end Event.add(Roles.player_role_assigned,role_update) Event.add(Roles.player_role_unassigned,role_update) +Event.add(defines.events.on_player_joined_game,role_update) Event.on_nth_tick(300,function() local promotes = {} for _,player in pairs(game.connected_players) do @@ -407,7 +436,7 @@ Event.on_nth_tick(300,function() if not success then log{'expcore-roles.error-log-format-promote',role.name,err} else - if err == true then + if err == true and not Roles.player_has_role(player,role) then if promotes[player.name] then table.insert(promotes[player.name],role.name) else @@ -419,7 +448,7 @@ Event.on_nth_tick(300,function() end end for player_name,roles in pairs(promotes) do - Roles.assign(player_name,roles) + Roles.assign_player(player_name,roles) end end) diff --git a/locale/en/expcore.cfg b/locale/en/expcore.cfg index e5572501..ee6f558d 100644 --- a/locale/en/expcore.cfg +++ b/locale/en/expcore.cfg @@ -18,7 +18,8 @@ command-ran=Command Complete command-fail=Command failed to run: __1__ command-error-log-format=[ERROR] command/__1__ :: __2__ -[time-format] -simple-format-none=__1__ -simple-format-div=__1__:__2__ -simple-format-tagged=__1__ __2__ +[expcore-roles] +error-log-format-flag=[ERROR] roleFlag/__1__ :: __2__ +error-log-format-promote=[ERROR] rolePromote/__1__ :: __2__ +game-message-assign=__1__ has been assigned to __2__ by __3__ +game-message-unassign=__1__ has been unassigned from __2__ by __3__ \ No newline at end of file diff --git a/modules/addons/spawn-area.lua b/modules/addons/spawn-area.lua index 6e6ed21f..04c341fa 100644 --- a/modules/addons/spawn-area.lua +++ b/modules/addons/spawn-area.lua @@ -140,5 +140,5 @@ Event.add(defines.events.on_player_created, function(event) spawn_entities(s,p) spawn_belts(s,p) spawn_turrets() - player.teleport(s,p) + player.teleport(p,s) end) \ No newline at end of file diff --git a/modules/commands/interface.lua b/modules/commands/interface.lua index 6ee924c0..5f1b2b42 100644 --- a/modules/commands/interface.lua +++ b/modules/commands/interface.lua @@ -8,7 +8,8 @@ local interface_modules = { ['_C']=Common, ['Commands']=Commands, ['output']=Common.player_return, - ['Group']='expcore.permission_groups' + ['Group']='expcore.permission_groups', + ['Roles']='expcore.roles' } -- loads all the modules given in the above table diff --git a/utils/game.lua b/utils/game.lua index 7a151c17..54cda4da 100644 --- a/utils/game.lua +++ b/utils/game.lua @@ -50,7 +50,7 @@ end function Game.get_player_from_any(obj) local o_type = type(obj) local p - if type == 'number' then + if o_type == 'number' then p = Game.get_player_by_index(obj) elseif o_type == 'string' then p = game.players[obj] From 0e18002375bb3a43ae21e9245de882649aa495dc Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Tue, 9 Apr 2019 21:22:07 +0100 Subject: [PATCH 05/15] Added Role config --- config/command_auth_roles.lua | 2 +- config/roles.lua | 155 +++++++++++++++++++++++++++++----- expcore/roles.lua | 5 ++ 3 files changed, 139 insertions(+), 23 deletions(-) diff --git a/config/command_auth_roles.lua b/config/command_auth_roles.lua index 8a87f047..3011166e 100644 --- a/config/command_auth_roles.lua +++ b/config/command_auth_roles.lua @@ -3,7 +3,7 @@ local Commands = require 'expcore.commands' local Roles = require 'expcore.roles' Commands.add_authenticator(function(player,command,tags,reject) - if Roles.player_allowed(player,command) then + if Roles.player_allowed(player,'command/'..command) then return true else return reject() diff --git a/config/roles.lua b/config/roles.lua index b6472d72..e8baf7b9 100644 --- a/config/roles.lua +++ b/config/roles.lua @@ -11,50 +11,161 @@ local function playtime(time_required) end end -Roles.define_flag_trigger('admin',function(player,state) +Roles.define_flag_trigger('is_admin',function(player,state) player.admin = state end) +Roles.define_flag_trigger('is_spectator',function(player,state) + player.spectator = state +end) +Roles.define_flag_trigger('is_jail',function(player,state) + if player.character then + player.character.active = not state + end +end) -Roles.new_role('Server','SYS') +Roles.new_role('System','SYS') :set_allow_all(true) -:set_flag('admin',true) - -Roles.new_role('Senior Admin','SAdmin') -:set_flag('admin',true) -:set_parent('Admin') +:set_flag('is_admin',true) +:set_flag('is_spectator',true) :set_permission_group('Admin') -:set_custom_color('blue') + +Roles.new_role('Senior Administrator','SAdmin') +:set_flag('is_admin',true) +:set_flag('is_spectator',true) +:set_permission_group('Admin') +:set_parent('Administrator') :allow{ - 'interface' + 'command/interface', + 'command/toggle-cheat-mode' } -Roles.new_role('Admin','Admin') -:set_flag('admin',true) -:set_parent('Regular') +Roles.new_role('Administrator','Admin') +:set_flag('is_admin',true) +:set_flag('is_spectator',true) +:set_custom_color{r=233,g=63,b=233} :set_permission_group('Admin') +:set_parent('Moderator') +:allow{ +} + +Roles.new_role('Moderator','Mod') +:set_flag('is_admin',true) +:set_flag('is_spectator',true) +:set_custom_color{r=0,g=170,b=0} +:set_permission_group('Admin') +:set_parent('Trainee') +:allow{ +} + +Roles.new_role('Trainee','TrMod') +:set_flag('is_admin',true) +:set_flag('is_spectator',true) +:set_custom_color{r=0,g=170,b=0} +:set_permission_group('Admin') +:set_parent('Donator') +:allow{ + 'command/admin-chat', + 'command/teleport', + 'command/bring', + 'command/goto' +} + +Roles.new_role('Sponsor','Spon') +:set_flag('is_spectator',true) +:set_custom_color{r=247,g=246,b=54} +:set_permission_group('Trusted') +:set_parent('Pay to Win') +:allow{ +} + +Roles.new_role('Pay to Win','P2W') +:set_flag('is_spectator',true) +:set_custom_color{r=238,g=172,b=44} +:set_permission_group('Trusted') +:set_parent('Donator') +:allow{ +} + +Roles.new_role('Donator','Don') +:set_flag('is_spectator',true) +:set_custom_color{r=230,g=99,b=34} +:set_permission_group('Trusted') +:set_parent('Veteran') +:allow{ +} + +Roles.new_role('Partner','Part') +:set_flag('is_spectator',true) +:set_custom_color{r=140,g=120,b=200} +:set_permission_group('Trusted') +:set_parent('Veteran') +:allow{ +} + +Roles.new_role('Veteran','Vet') +:set_custom_color{r=140,g=120,b=200} +:set_permission_group('Trusted') +:set_parent('Member') +:allow{ +} +:set_auto_promote_condition(playtime(10*hours)) + +Roles.new_role('Member','Mem') +:set_custom_color{r=24,g=172,b=188} +:set_permission_group('Standard') +:set_parent('Regular') :allow{ - 'tp' } Roles.new_role('Regular','Reg') -:set_auto_promote_condition(playtime(5*minutes)) +:set_custom_color{r=79,g=155,b=163} +:set_permission_group('Standard') :set_parent('Guest') -:set_custom_tag('Reg') -:set_permission_group('Trusted') +:allow{ +} +:set_auto_promote_condition(playtime(3*hours)) Roles.new_role('Guest','') -:set_permission_group('Standard') +:set_custom_color{r=185,g=187,b=160} +:set_permission_group('Guest') +:allow{ + 'command/me', + 'command/tag', + 'command/tag-clear', + 'command/chelp' +} -Roles.set_root('Server') +Roles.new_role('Jail') +:set_custom_color{r=50,g=50,b=50} +:set_permission_group('Restricted') +:set_block_auto_promote(true) +:allow{ +} + +Roles.set_root('System') Roles.set_default('Guest') Roles.define_role_order{ - 'Server', - 'Senior Admin', + 'System', + 'Senior Administrator', + 'Administrator', + 'Moderator', + 'Trainee', + 'Sponsor', + 'Pay to Win', + 'Donator', + 'Partner', + 'Veteran', + 'Member', 'Regular', - 'Guest' + 'Guest', + 'Jail' } Roles.override_player_roles{ - Cooldude2606={'Server','Senior Admin'} + Cooldude2606={'Senior Administrator','Administrator','Moderator','Member'}, + arty714={'Senior Administrator','Administrator','Moderator','Member'}, + mark9064={'Administrator','Moderator','Member'}, + Drahc_pro={'Administrator','Moderator','Member'}, + aldldl={'Sponsor','Administrator','Moderator','Member'}, } \ No newline at end of file diff --git a/expcore/roles.lua b/expcore/roles.lua index 98ebc989..d865122c 100644 --- a/expcore/roles.lua +++ b/expcore/roles.lua @@ -299,6 +299,11 @@ function Roles._prototype:set_auto_promote_condition(callback) return self end +function Roles._prototype:set_block_auto_promote(state) + self.block_auto_promote = state + return self +end + function Roles._prototype:add_player(player,skip_check,skip_event) player = Game.get_player_from_any(player) -- Check the player is valid, can be skipped but a name must be given From 9df2a958f82d2ad85d93a45ea846089241dd7df9 Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Tue, 9 Apr 2019 21:33:11 +0100 Subject: [PATCH 06/15] Changed kill and tag to use role system --- config/roles.lua | 5 ++++- expcore/locale/en.cfg | 3 ++- expcore/roles.lua | 20 +++++++++++++------- locale/en/expcore.cfg | 3 ++- modules/commands/kill.lua | 16 ++++++++++++++-- modules/commands/tag.lua | 11 +++++++++-- 6 files changed, 44 insertions(+), 14 deletions(-) diff --git a/config/roles.lua b/config/roles.lua index e8baf7b9..4de02f5c 100644 --- a/config/roles.lua +++ b/config/roles.lua @@ -67,7 +67,9 @@ Roles.new_role('Trainee','TrMod') 'command/admin-chat', 'command/teleport', 'command/bring', - 'command/goto' + 'command/goto', + 'command/kill/always', + 'command/tag-clear/always', } Roles.new_role('Sponsor','Spon') @@ -122,6 +124,7 @@ Roles.new_role('Regular','Reg') :set_permission_group('Standard') :set_parent('Guest') :allow{ + 'command/kill' } :set_auto_promote_condition(playtime(3*hours)) diff --git a/expcore/locale/en.cfg b/expcore/locale/en.cfg index 2a9b5451..f064aa41 100644 --- a/expcore/locale/en.cfg +++ b/expcore/locale/en.cfg @@ -20,4 +20,5 @@ command-error-log-format=[ERROR] command/__1__ :: __2__ error-log-format-flag=[ERROR] roleFlag/__1__ :: __2__ error-log-format-promote=[ERROR] rolePromote/__1__ :: __2__ game-message-assign=__1__ has been assigned to __2__ by __3__ -game-message-unassign=__1__ has been unassigned from __2__ by __3__ \ No newline at end of file +game-message-unassign=__1__ has been unassigned from __2__ by __3__ +command-error-higher-role=Unauthorized, __1__ has a higher role than you. \ No newline at end of file diff --git a/expcore/roles.lua b/expcore/roles.lua index d865122c..f380c58b 100644 --- a/expcore/roles.lua +++ b/expcore/roles.lua @@ -103,6 +103,18 @@ function Roles.get_player_roles(player) return rtn end +function Roles.get_player_highest_role(player) + local roles = Roles.get_player_roels(player) + if not roles then return end + local highest + for _,role in pairs(roles) do + if not highest or role.index < highest.index then + highest = role + end + end + return highest +end + function Roles.assign_player(player,roles,by_player_name) player = Game.get_player_from_any(player) if not player then return end @@ -410,13 +422,7 @@ local function role_update(event) end end -- Updates the players permission group - local highest - local roles = Roles.get_player_roles(player) - for _,role in pairs(roles) do - if not highest or role.index < highest.index then - highest = role - end - end + local highest = Roles.get_player_highest_role(player) if highest.permission_group then if highest.permission_group[1] then local group = game.permissions.get_group(highest.permission_group[2]) diff --git a/locale/en/expcore.cfg b/locale/en/expcore.cfg index ee6f558d..68a1f9f9 100644 --- a/locale/en/expcore.cfg +++ b/locale/en/expcore.cfg @@ -22,4 +22,5 @@ command-error-log-format=[ERROR] command/__1__ :: __2__ error-log-format-flag=[ERROR] roleFlag/__1__ :: __2__ error-log-format-promote=[ERROR] rolePromote/__1__ :: __2__ game-message-assign=__1__ has been assigned to __2__ by __3__ -game-message-unassign=__1__ has been unassigned from __2__ by __3__ \ No newline at end of file +game-message-unassign=__1__ has been unassigned from __2__ by __3__ +command-error-higher-role=Unauthorized, __1__ has a higher role than you. \ No newline at end of file diff --git a/modules/commands/kill.lua b/modules/commands/kill.lua index 24dc16bc..2467f5a7 100644 --- a/modules/commands/kill.lua +++ b/modules/commands/kill.lua @@ -1,4 +1,5 @@ local Commands = require 'expcore.commands' +local Roles = require 'expcore.roles' require 'config.command_parse_general' Commands.new_command('kill','Kills yourself or another player.') @@ -9,11 +10,22 @@ Commands.new_command('kill','Kills yourself or another player.') return player end end} -:set_flag('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() + if player == action_player then + action_player.character.die() + elseif Roles.player_allowed(player,'command/kill/always') then + local player_highest = Roles.get_player_highest_role(player) + local action_player_highest = Roles.get_player_highest_role(action_player) + if player_highest.index < action_player_highest.index then + action_player.character.die() + else + return Commands.error{'expcore-roles.command-error-higher-role',action_player.name} + end + else + return Commands.error{'expcore-commands.unauthorized'} + end end) \ No newline at end of file diff --git a/modules/commands/tag.lua b/modules/commands/tag.lua index 4105d92a..c335e892 100644 --- a/modules/commands/tag.lua +++ b/modules/commands/tag.lua @@ -1,4 +1,5 @@ local Commands = require 'expcore.commands' +local Roles = require 'expcore.roles' require 'config.command_parse_general' Commands.new_command('tag','Sets your player tag.') @@ -17,9 +18,15 @@ end} if action_player.index == player.index then -- no player given so removes your tag action_player.tag = '' - elseif player.admin then + elseif Roles.player_allowed(player,'command/clear-tag/always') then -- player given and user is admin so clears that player's tag - action_player.tag = '' + local player_highest = Roles.get_player_highest_role(player) + local action_player_highest = Roles.get_player_highest_role(action_player) + if player_highest.index < action_player_highest.index then + action_player.tag = '' + else + return Commands.error{'expcore-roles.command-error-higher-role',action_player.name} + end else -- user is not admin and tried to clear another users tag return Commands.error{'expcore-commands.unauthorized'} From 7cfcedd97bb2e8abded58bf19d9c7650d789b66e Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Tue, 9 Apr 2019 21:48:09 +0100 Subject: [PATCH 07/15] Added command parse for roles --- config/command_parase_roles.lua | 38 +++++++++++++++++++++++++++++++++ expcore/locale/en.cfg | 11 ++++++---- locale/en/expcore.cfg | 9 ++++---- modules/commands/kill.lua | 11 +++------- modules/commands/tag.lua | 11 +++------- 5 files changed, 56 insertions(+), 24 deletions(-) create mode 100644 config/command_parase_roles.lua diff --git a/config/command_parase_roles.lua b/config/command_parase_roles.lua new file mode 100644 index 00000000..e717cbea --- /dev/null +++ b/config/command_parase_roles.lua @@ -0,0 +1,38 @@ +--- Adds some parse functions that can be used with the role system +local Commands = require 'expcore.commands' +local Roles = require 'expcore.roles' +require 'config.command_parse_general' + +Commands.add_parse('role',function(input,player,reject) + if not role then return end + local role = Roles.get_role_from_any(input) + if not role then + return reject{'expcore-role.reject-role'} + else + return role + end +end) + +Commands.add_parse('player-role',function(input,player,reject) + local input_player = Commands.parse('player',input,player,reject) + if not input_player then return end -- nil check + local player_highest = Roles.get_player_highest_role(player) + local action_player_highest = Roles.get_player_highest_role(input_player) + if player_highest.index < action_player_highest.index then + return input_player + else + return reject{'expcore-roles.reject-player-role'} + end +end) + +Commands.add_parse('player-role-online',function(input,player,reject) + local input_player = Commands.parse('player-rank',input,player,reject) + if not input_player then return end -- nil check + return Commands.parse('player-online',input_player,player,reject) +end) + +Commands.add_parse('player-role-alive',function(input,player,reject) + local input_player = Commands.parse('player-rank',input,player,reject) + if not input_player then return end -- nil check + return Commands.parse('player-alive',input_player,player,reject) +end) \ No newline at end of file diff --git a/expcore/locale/en.cfg b/expcore/locale/en.cfg index f064aa41..82d68681 100644 --- a/expcore/locale/en.cfg +++ b/expcore/locale/en.cfg @@ -1,14 +1,16 @@ +time-symbol-days-short=__1__d + [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=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 +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__ @@ -21,4 +23,5 @@ error-log-format-flag=[ERROR] roleFlag/__1__ :: __2__ error-log-format-promote=[ERROR] rolePromote/__1__ :: __2__ game-message-assign=__1__ has been assigned to __2__ by __3__ game-message-unassign=__1__ has been unassigned from __2__ by __3__ -command-error-higher-role=Unauthorized, __1__ has a higher role than you. \ No newline at end of file +reject-role=Invalid Role Name. +reject-player-role=Player has a higher role. \ No newline at end of file diff --git a/locale/en/expcore.cfg b/locale/en/expcore.cfg index 68a1f9f9..82d68681 100644 --- a/locale/en/expcore.cfg +++ b/locale/en/expcore.cfg @@ -4,13 +4,13 @@ time-symbol-days-short=__1__d 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=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 +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__ @@ -23,4 +23,5 @@ error-log-format-flag=[ERROR] roleFlag/__1__ :: __2__ error-log-format-promote=[ERROR] rolePromote/__1__ :: __2__ game-message-assign=__1__ has been assigned to __2__ by __3__ game-message-unassign=__1__ has been unassigned from __2__ by __3__ -command-error-higher-role=Unauthorized, __1__ has a higher role than you. \ No newline at end of file +reject-role=Invalid Role Name. +reject-player-role=Player has a higher role. \ No newline at end of file diff --git a/modules/commands/kill.lua b/modules/commands/kill.lua index 2467f5a7..5b4391eb 100644 --- a/modules/commands/kill.lua +++ b/modules/commands/kill.lua @@ -1,9 +1,10 @@ local Commands = require 'expcore.commands' local Roles = require 'expcore.roles' require 'config.command_parse_general' +require 'config.command_parse_role' 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_param('player',true,'player-role-alive') -- the player to kill, must be alive to be valid :set_defaults{player=function(player) -- default is the player unless they are dead if player.character and player.character.health > 0 then @@ -18,13 +19,7 @@ end} if player == action_player then action_player.character.die() elseif Roles.player_allowed(player,'command/kill/always') then - local player_highest = Roles.get_player_highest_role(player) - local action_player_highest = Roles.get_player_highest_role(action_player) - if player_highest.index < action_player_highest.index then - action_player.character.die() - else - return Commands.error{'expcore-roles.command-error-higher-role',action_player.name} - end + action_player.character.die() else return Commands.error{'expcore-commands.unauthorized'} end diff --git a/modules/commands/tag.lua b/modules/commands/tag.lua index c335e892..177fbd4b 100644 --- a/modules/commands/tag.lua +++ b/modules/commands/tag.lua @@ -1,6 +1,7 @@ local Commands = require 'expcore.commands' local Roles = require 'expcore.roles' require 'config.command_parse_general' +require 'config.command_parse_role' Commands.new_command('tag','Sets your player tag.') :add_param('tag',false,'string-max-length',20) -- new tag for your player max 20 char @@ -10,7 +11,7 @@ Commands.new_command('tag','Sets your player 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_param('player',true,'player-role') -- player to remove the tag of, nil to apply to self :set_defaults{player=function(player) return player -- default is the user using the command end} @@ -20,13 +21,7 @@ end} action_player.tag = '' elseif Roles.player_allowed(player,'command/clear-tag/always') then -- player given and user is admin so clears that player's tag - local player_highest = Roles.get_player_highest_role(player) - local action_player_highest = Roles.get_player_highest_role(action_player) - if player_highest.index < action_player_highest.index then - action_player.tag = '' - else - return Commands.error{'expcore-roles.command-error-higher-role',action_player.name} - end + action_player.tag = '' else -- user is not admin and tried to clear another users tag return Commands.error{'expcore-commands.unauthorized'} From 33f58996c4ca25675d9dacf3706e0a66fd5f44c6 Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Tue, 9 Apr 2019 22:04:02 +0100 Subject: [PATCH 08/15] Debuged Command Parse for Roles --- ...ommand_parase_roles.lua => command_parse_roles.lua} | 10 +++++----- expcore/commands.lua | 2 +- expcore/roles.lua | 2 +- modules/commands/kill.lua | 2 +- modules/commands/tag.lua | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) rename config/{command_parase_roles.lua => command_parse_roles.lua} (79%) diff --git a/config/command_parase_roles.lua b/config/command_parse_roles.lua similarity index 79% rename from config/command_parase_roles.lua rename to config/command_parse_roles.lua index e717cbea..82992a9c 100644 --- a/config/command_parase_roles.lua +++ b/config/command_parse_roles.lua @@ -4,7 +4,7 @@ local Roles = require 'expcore.roles' require 'config.command_parse_general' Commands.add_parse('role',function(input,player,reject) - if not role then return end + if not input then return end local role = Roles.get_role_from_any(input) if not role then return reject{'expcore-role.reject-role'} @@ -17,8 +17,8 @@ Commands.add_parse('player-role',function(input,player,reject) local input_player = Commands.parse('player',input,player,reject) if not input_player then return end -- nil check local player_highest = Roles.get_player_highest_role(player) - local action_player_highest = Roles.get_player_highest_role(input_player) - if player_highest.index < action_player_highest.index then + local input_player_highest = Roles.get_player_highest_role(input_player) + if player_highest.index < input_player_highest.index then return input_player else return reject{'expcore-roles.reject-player-role'} @@ -26,13 +26,13 @@ Commands.add_parse('player-role',function(input,player,reject) end) Commands.add_parse('player-role-online',function(input,player,reject) - local input_player = Commands.parse('player-rank',input,player,reject) + local input_player = Commands.parse('player-role',input,player,reject) if not input_player then return end -- nil check return Commands.parse('player-online',input_player,player,reject) end) Commands.add_parse('player-role-alive',function(input,player,reject) - local input_player = Commands.parse('player-rank',input,player,reject) + local input_player = Commands.parse('player-role',input,player,reject) if not input_player then return end -- nil check return Commands.parse('player-alive',input_player,player,reject) end) \ No newline at end of file diff --git a/expcore/commands.lua b/expcore/commands.lua index efc45a87..3a3baeea 100644 --- a/expcore/commands.lua +++ b/expcore/commands.lua @@ -643,7 +643,7 @@ function Commands.run_command(command_event) 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 param_data.optional == true and raw_params[index] == 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 diff --git a/expcore/roles.lua b/expcore/roles.lua index f380c58b..284b9b01 100644 --- a/expcore/roles.lua +++ b/expcore/roles.lua @@ -104,7 +104,7 @@ function Roles.get_player_roles(player) end function Roles.get_player_highest_role(player) - local roles = Roles.get_player_roels(player) + local roles = Roles.get_player_roles(player) if not roles then return end local highest for _,role in pairs(roles) do diff --git a/modules/commands/kill.lua b/modules/commands/kill.lua index 5b4391eb..0c36e327 100644 --- a/modules/commands/kill.lua +++ b/modules/commands/kill.lua @@ -1,7 +1,7 @@ local Commands = require 'expcore.commands' local Roles = require 'expcore.roles' require 'config.command_parse_general' -require 'config.command_parse_role' +require 'config.command_parse_roles' Commands.new_command('kill','Kills yourself or another player.') :add_param('player',true,'player-role-alive') -- the player to kill, must be alive to be valid diff --git a/modules/commands/tag.lua b/modules/commands/tag.lua index 177fbd4b..18c1f0e5 100644 --- a/modules/commands/tag.lua +++ b/modules/commands/tag.lua @@ -1,7 +1,7 @@ local Commands = require 'expcore.commands' local Roles = require 'expcore.roles' require 'config.command_parse_general' -require 'config.command_parse_role' +require 'config.command_parse_roles' Commands.new_command('tag','Sets your player tag.') :add_param('tag',false,'string-max-length',20) -- new tag for your player max 20 char From 01facf16786dbef31800659d0669b3061529d43a Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Tue, 9 Apr 2019 23:13:22 +0100 Subject: [PATCH 09/15] Added Comments --- config/roles.lua | 8 +++ expcore/roles.lua | 132 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 137 insertions(+), 3 deletions(-) diff --git a/config/roles.lua b/config/roles.lua index 4de02f5c..b40940af 100644 --- a/config/roles.lua +++ b/config/roles.lua @@ -1,3 +1,4 @@ +--- This is the main config file for the role system; file includes defines for roles and role flags and default values local Roles = require 'expcore.roles' -- Use these to adjust for ticks ie game.tick < 5*minutes @@ -11,6 +12,7 @@ local function playtime(time_required) end end +--- Role flags that will run when a player changes roles Roles.define_flag_trigger('is_admin',function(player,state) player.admin = state end) @@ -23,6 +25,7 @@ Roles.define_flag_trigger('is_jail',function(player,state) end end) +--- Admin Roles Roles.new_role('System','SYS') :set_allow_all(true) :set_flag('is_admin',true) @@ -72,6 +75,7 @@ Roles.new_role('Trainee','TrMod') 'command/tag-clear/always', } +--- Trusted Roles Roles.new_role('Sponsor','Spon') :set_flag('is_spectator',true) :set_custom_color{r=247,g=246,b=54} @@ -112,6 +116,7 @@ Roles.new_role('Veteran','Vet') } :set_auto_promote_condition(playtime(10*hours)) +--- Standard User Roles Roles.new_role('Member','Mem') :set_custom_color{r=24,g=172,b=188} :set_permission_group('Standard') @@ -128,6 +133,7 @@ Roles.new_role('Regular','Reg') } :set_auto_promote_condition(playtime(3*hours)) +--- Guest/Default role Roles.new_role('Guest','') :set_custom_color{r=185,g=187,b=160} :set_permission_group('Guest') @@ -138,6 +144,7 @@ Roles.new_role('Guest','') 'command/chelp' } +--- Jail role Roles.new_role('Jail') :set_custom_color{r=50,g=50,b=50} :set_permission_group('Restricted') @@ -145,6 +152,7 @@ Roles.new_role('Jail') :allow{ } +--- System defaults which are required to be set Roles.set_root('System') Roles.set_default('Guest') diff --git a/expcore/roles.lua b/expcore/roles.lua index 284b9b01..d5690744 100644 --- a/expcore/roles.lua +++ b/expcore/roles.lua @@ -17,6 +17,7 @@ local Roles = { _prototype={} } +--- Internal function used to trigger a few different things when roles are changed local function emit_player_roles_updated(player,type,roles,by_player_name) by_player_name = game.player and game.player.name or by_player_name or '' local event = Roles.player_role_assigned @@ -48,6 +49,7 @@ local function emit_player_roles_updated(player,type,roles,by_player_name) }..'\n',true,0) end +--- When global is loaded it will have the metatable re-assigned to the roles Global.register(Roles.config,function(tbl) Roles.config = tbl for _,role in pairs(Roles.config.roles) do @@ -59,6 +61,8 @@ Global.register(Roles.config,function(tbl) end end) +--- Returns a string which contains all roles in index order displaying all data for them +-- @treturn string the debug output string function Roles.debug() local output = '' for index,role_name in pairs(Roles.config.order) do @@ -70,15 +74,25 @@ function Roles.debug() return output end +--- Get a role for the given name +-- @tparam name string the name of the role to get +-- @treturn Roles._prototype the role with that name or nil function Roles.get_role_by_name(name) return Roles.config.roles[name] end +--- Get a role with the given order index +-- @tparam index number the place in the oder list of the role to get +-- @treturn Roles._prototype the role with that index in the order list or nil function Roles.get_role_by_order(index) local name = Roles.config.order[index] return Roles.config.roles[name] end +--- Gets a role from a name,index or role object (where it is just returned) +-- nb: this function is used for the input for most outward facing functions +-- @tparam any ?number|string|table the value used to find the role +-- @treturn Roles._prototype the role that was found or nil see above function Roles.get_role_from_any(any) local tany = type(any) if tany == 'number' or tonumber(any) then @@ -91,6 +105,9 @@ function Roles.get_role_from_any(any) end end +--- Gets all the roles of the given player, this will always contain the default role +-- @tparam player LuaPlayer the player to get the roles of +-- @treturn table a table where the values are the roles which the player has function Roles.get_player_roles(player) player = Game.get_player_from_any(player) if not player then return end @@ -103,6 +120,9 @@ function Roles.get_player_roles(player) return rtn end +--- Gets the highest role which the player has, can be used to compeer one player to another +-- @tparam player LuaPlayer the player to get the highest role of +-- @treturn the role with the highest order index which this player has function Roles.get_player_highest_role(player) local roles = Roles.get_player_roles(player) if not roles then return end @@ -115,6 +135,10 @@ function Roles.get_player_highest_role(player) return highest end +--- Gives a player the given role(s) with an option to pass a by player name used in the log +-- @tparam player LuaPlayer the player that will be assigned the roles +-- @tparam role table a table of roles that the player will be given, can be one role and can be role names +-- @tparam[opt=] by_player_name string the name of the player that will be shown in the log function Roles.assign_player(player,roles,by_player_name) player = Game.get_player_from_any(player) if not player then return end @@ -130,6 +154,10 @@ function Roles.assign_player(player,roles,by_player_name) emit_player_roles_updated(player,'assign',roles,by_player_name) end +--- Removes a player from the given role(s) with an option to pass a by player name used in the log +-- @tparam player LuaPlayer the player that will have the roles removed +-- @tparam roles table a table of roles to be removed from the player, can be one role and can be role names +-- @tparam[opt=] by_player_name string the name of the player that will be shown in the logs function Roles.unassign_player(player,roles,by_player_name) player = Game.get_player_from_any(player) if not player then return end @@ -145,10 +173,16 @@ function Roles.unassign_player(player,roles,by_player_name) emit_player_roles_updated(player,'unassign',roles,by_player_name) end +--- Overrides all player roles with the given table of roles, useful to mass set roles on game start +-- @tparam roles table a table which is indexed by case sensitive player names and has the value of a table of role names function Roles.override_player_roles(roles) Roles.config.players = roles end +--- A test for weather a player has the given role +-- @tparam player LuaPlayer the player to test the roles of +-- @tparam search_role ?string|number|table a pointer to the role that is being searched for +-- @treturn boolean true if the player has the role, false otherwise, nil for errors function Roles.player_has_role(player,search_role) local roles = Roles.get_player_roles(player) if not roles then return end @@ -160,6 +194,10 @@ function Roles.player_has_role(player,search_role) return false end +--- A test for weather a player has the given flag true for at least one of they roles +-- @tparam player LuaPlayer the player to test the roles of +-- @tparam flag_name string the name of the flag that is being looked for +-- @treturn boolean true if the player has at least one role which has the flag set to true, false otherwise, nil for errors function Roles.player_has_flag(player,flag_name) local roles = Roles.get_player_roles(player) if not roles then return end @@ -171,6 +209,10 @@ function Roles.player_has_flag(player,flag_name) return false end +--- A test for weather a player has at least one role which is allowed the given action +-- @tparam player LuaPlayer the player to test the roles of +-- @tparam action string the name of the action that is being tested for +-- @treturn boolean true if the player has at least one role which is allowed this action, false otherwise, nil for errors function Roles.player_allowed(player,action) local roles = Roles.get_player_roles(player) if not roles then return end @@ -182,7 +224,11 @@ function Roles.player_allowed(player,action) return false end +--- Used to set the role order, higher in the list is better, must be called at least once in config +-- nb: function also re links parents due to expected position in the config file +-- @tparam order table a table which is keyed only by numbers (start 1) and values are roles in order with highest first function Roles.define_role_order(order) + -- Clears and then rebuilds the order table Roles.config.order = {} for _,role in ipairs(order) do if type(role) == 'table' and role.name then @@ -191,6 +237,7 @@ function Roles.define_role_order(order) table.insert(Roles.config.order,role) end end + -- Re-links roles to they parents as this is called at the end of the config for index,role in pairs(Roles.config.order) do role = Roles.config.roles[role] role.index = index @@ -201,22 +248,36 @@ function Roles.define_role_order(order) end end +--- Defines a new trigger for when a tag is added or removed from a player +-- @tparam name string the name of the flag which the roles will have +-- @tparam callback function the function that is called when roles are assigned +-- flag param - player - the player that has had they roles changed +-- flag param - state - the state of the flag, aka if the flag is present function Roles.define_flag_trigger(name,callback) Roles.config.flags[name] = callback -- this can desync if there are upvalues end +--- Sets the default role which every player will have, this needs to be called at least once +-- @tparam name string the name of the default role function Roles.set_default(name) local role = Roles.config.roles[name] if not role then return end Roles.config.internal.default = name end +--- Sets the root role which will always have all permissions, any server actions act from this role +-- @tparam name string the name of the root role function Roles.set_root(name) local role = Roles.config.roles[name] if not role then return end + role:set_allow_all(true) Roles.config.internal.root = name end +--- Defines a new role and returns the prototype to allow configuration +-- @tparam name string the name of the new role, must be unique +-- @tparam[opt=name] shirt_hand string the shortened version of the name +-- @treturn Roles._prototype the start of the config chain for this role function Roles.new_role(name,short_hand) if Roles.config.roles[name] then return error('Role name is non unique') end local role = setmetatable({ @@ -230,11 +291,18 @@ function Roles.new_role(name,short_hand) return role end +--- Sets the default allow state of the role, true will allow all actions +-- @tparam[opt=true] strate boolean true will allow all actions +-- @treturn Roles._prototype allows chaining function Roles._prototype:set_allow_all(state) - self.allow_all_actions = not not state + if state == nil then state = true end + self.allow_all_actions = not not state -- not not forces a boolean value return self end +--- Sets the allow actions for this role, actions in this list will be allowed for this role +-- @tparam actions table indexed with numbers and is an array of action names, order has no effect +-- @treturn Roles._prototype allows chaining function Roles._prototype:allow(actions) if type(actions) ~= 'table' then actions = {actions} @@ -245,6 +313,9 @@ function Roles._prototype:allow(actions) return self end +--- Sets the disallow actions for this role, will prevent actions from being allowed regardless of inheritance +-- @tparam actions table indexed with numbers and is an array of action names, order has no effect +-- @treturn Roles._prototype allows chaining function Roles._prototype:disallow(actions) if type(actions) ~= 'table' then actions = {actions} @@ -255,30 +326,49 @@ function Roles._prototype:disallow(actions) return self end +--- Test for if a role is allowed the given action, mostly internal see Roles.player_allowed +-- @tparam action string the name of the action to test if it is allowed +-- @treturn boolean true if action is allowed, false otherwise function Roles._prototype:is_allowed(action) local is_root = Roles.config.internal.root.name == self.name return self.allowed_actions[action] or self.allow_all_actions or is_root end +--- Sets the state of a flag for a role, flags can be used to apply effects to players +-- @tparam name string the name of the flag to set the value of +-- @tparam[opt=true] value boolean the state to set the flag to +-- @treturn Roles._prototype allows chaining function Roles._prototype:set_flag(name,value) - self.flags[name] = value + if value == nil then value = true end + self.flags[name] = not not value -- not not forces a boolean value return self end +--- Clears all flags from this role, individual flags can be removed with set_flag(name,false) +-- @treturn Roles._prototype allows chaining function Roles._prototype:clear_flags() self.flags = {} return self end +--- A test for if the role has a flag set +-- @tparam name string the name of the flag to test for +-- @treturn boolean true if the flag is set, false otherwise function Roles._prototype:has_flag(name) return self.flags[name] or false end +--- Sets a custom player tag for the role, can be accessed by other code +-- @tparam tag string the value that the tag will be +-- @treturn Roles._prototype allows chaining function Roles._prototype:set_custom_tag(tag) self.custom_tag = tag return self end +--- Sets a custom colour for the role, can be accessed by other code +-- @tparam color ?string|table can either be and rgb colour table or the name of a colour defined in the presets +-- @treturn Roles._prototype allows chaining function Roles._prototype:set_custom_color(color) if type(color) ~= 'table' then color = Colours[color] @@ -287,6 +377,10 @@ function Roles._prototype:set_custom_color(color) return self end +--- Sets the permission group for this role, players will be moved to the group of they highest role +-- @tparam name string the name of the permission group to have players moved to +-- @tparam[opt=false] use_factorio_api boolean when true the custom permission group module is ignored +-- @treturn Roles._prototype allows chaining function Roles._prototype:set_permission_group(name,use_factorio_api) if use_factorio_api then self.permission_group = {true,name} @@ -298,6 +392,10 @@ function Roles._prototype:set_permission_group(name,use_factorio_api) return self end +--- Sets the parent for a role, any action not in allow or disallow will be looked for in its parents +-- nb: this is a recursive action, and changing the allows and disallows will effect all children roles +-- @tparam role string the name of the role that will be the parent; has imminent effect if role is already defined +-- @treturn Roles._prototype allows chaining function Roles._prototype:set_parent(role) self.parent = role role = Roles.get_role_from_any(role) @@ -306,16 +404,29 @@ function Roles._prototype:set_parent(role) return self end +--- Sets an auto promote condition that is checked every 5 seconds, if true is returned then the player will recive the role +-- nb: this is one way, failing false after already gaining the role will not revoke the role +-- @tparam callback function receives only one param which is player to promote, return true to promote the player +-- @treturn Roles._prototype allows chaining function Roles._prototype:set_auto_promote_condition(callback) self.auto_promote_condition = callback return self end +--- Sets the role to not allow players to have auto promote effect them, useful to keep people locked to a punishment +-- @tparam[opt=true] state boolean when true the players with this role will not be auto promoted +-- @treturn Roles._prototype allows chaining function Roles._prototype:set_block_auto_promote(state) - self.block_auto_promote = state + if state == nil then state = true end + self.block_auto_promote = not not state -- forces a boolean value return self end +--- Adds a player to this role, players can have more than one role at a time, used internally see Roles.assign +-- @tparam player LuaPlayer the player that will be given this role +-- @tparam skip_check boolean when true player will be taken as the player name (use when player has not yet joined) +-- @tparam skip_event boolean when true the event emit will be skipped, this is used internally with Roles.assign +-- @treturn boolean true if the player was added successfully function Roles._prototype:add_player(player,skip_check,skip_event) player = Game.get_player_from_any(player) -- Check the player is valid, can be skipped but a name must be given @@ -343,6 +454,11 @@ function Roles._prototype:add_player(player,skip_check,skip_event) return true end +--- Removes a player from this role, players can have more than one role at a time, used internally see Roles.unassign +-- @tparam player LuaPlayer the player that will lose this role +-- @tparam skip_check boolean when true player will be taken as the player name (use when player has not yet joined) +-- @tparam skip_event boolean when true the event emit will be skipped, this is used internally with Roles.unassign +-- @treturn boolean true if the player was removed successfully function Roles._prototype:remove_player(player,skip_check,skip_event) player = Game.get_player_from_any(player) -- Check the player is valid, can be skipped but a name must be given @@ -375,6 +491,9 @@ function Roles._prototype:remove_player(player,skip_check,skip_event) return rtn end +--- Returns an array of all the players who have this role, can be filtered by online status +-- @tparam[opt=nil] online boolean when given will filter by this online state, nil will return all players +-- @treturn table all the players who have this role, indexed order is meaningless function Roles._prototype:get_players(online) local players = {} -- Gets all players that have this role @@ -403,6 +522,9 @@ function Roles._prototype:get_players(online) end end +--- Will print a message to all players with this role +-- @tparam message string the message that will be printed to the players +-- @treturn number the number of players who received the message function Roles._prototype:print(message) local players = self:get_players(true) for _,player in pairs(players) do @@ -411,6 +533,7 @@ function Roles._prototype:print(message) return #players end +--- Used internally to be the first trigger on an event change, would be messy to include this in 4 different places local function role_update(event) local player = Game.get_player_by_index(event.player_index) -- Updates flags given to the player @@ -435,9 +558,11 @@ local function role_update(event) end end +--- When a player joined or has a role change then the update is triggered Event.add(Roles.player_role_assigned,role_update) Event.add(Roles.player_role_unassigned,role_update) Event.add(defines.events.on_player_joined_game,role_update) +-- Every 5 seconds the auto promote check is preformed Event.on_nth_tick(300,function() local promotes = {} for _,player in pairs(game.connected_players) do @@ -463,4 +588,5 @@ Event.on_nth_tick(300,function() end end) +-- Return Roles return Roles \ No newline at end of file From 3b2336a0560d05ca9c3a1c0b2b3b5c6c604e004e Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Wed, 10 Apr 2019 01:27:03 +0100 Subject: [PATCH 10/15] Added Header Comment --- config/roles.lua | 54 ++++++++-------- expcore/roles.lua | 153 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 180 insertions(+), 27 deletions(-) diff --git a/config/roles.lua b/config/roles.lua index b40940af..4cead3dd 100644 --- a/config/roles.lua +++ b/config/roles.lua @@ -27,15 +27,15 @@ end) --- Admin Roles Roles.new_role('System','SYS') -:set_allow_all(true) -:set_flag('is_admin',true) -:set_flag('is_spectator',true) :set_permission_group('Admin') +:set_flag('is_admin') +:set_flag('is_spectator') +:set_allow_all() Roles.new_role('Senior Administrator','SAdmin') -:set_flag('is_admin',true) -:set_flag('is_spectator',true) :set_permission_group('Admin') +:set_flag('is_admin') +:set_flag('is_spectator') :set_parent('Administrator') :allow{ 'command/interface', @@ -43,28 +43,28 @@ Roles.new_role('Senior Administrator','SAdmin') } Roles.new_role('Administrator','Admin') -:set_flag('is_admin',true) -:set_flag('is_spectator',true) -:set_custom_color{r=233,g=63,b=233} :set_permission_group('Admin') +:set_custom_color{r=233,g=63,b=233} +:set_flag('is_admin') +:set_flag('is_spectator') :set_parent('Moderator') :allow{ } Roles.new_role('Moderator','Mod') -:set_flag('is_admin',true) -:set_flag('is_spectator',true) -:set_custom_color{r=0,g=170,b=0} :set_permission_group('Admin') +:set_custom_color{r=0,g=170,b=0} +:set_flag('is_admin') +:set_flag('is_spectator') :set_parent('Trainee') :allow{ } Roles.new_role('Trainee','TrMod') -:set_flag('is_admin',true) -:set_flag('is_spectator',true) -:set_custom_color{r=0,g=170,b=0} :set_permission_group('Admin') +:set_custom_color{r=0,g=170,b=0} +:set_flag('is_admin') +:set_flag('is_spectator') :set_parent('Donator') :allow{ 'command/admin-chat', @@ -77,40 +77,40 @@ Roles.new_role('Trainee','TrMod') --- Trusted Roles Roles.new_role('Sponsor','Spon') -:set_flag('is_spectator',true) -:set_custom_color{r=247,g=246,b=54} :set_permission_group('Trusted') +:set_custom_color{r=247,g=246,b=54} +:set_flag('is_spectator') :set_parent('Pay to Win') :allow{ } Roles.new_role('Pay to Win','P2W') -:set_flag('is_spectator',true) -:set_custom_color{r=238,g=172,b=44} :set_permission_group('Trusted') +:set_custom_color{r=238,g=172,b=44} +:set_flag('is_spectator') :set_parent('Donator') :allow{ } Roles.new_role('Donator','Don') -:set_flag('is_spectator',true) -:set_custom_color{r=230,g=99,b=34} :set_permission_group('Trusted') +:set_custom_color{r=230,g=99,b=34} +:set_flag('is_spectator') :set_parent('Veteran') :allow{ } Roles.new_role('Partner','Part') -:set_flag('is_spectator',true) -:set_custom_color{r=140,g=120,b=200} :set_permission_group('Trusted') +:set_custom_color{r=140,g=120,b=200} +:set_flag('is_spectator') :set_parent('Veteran') :allow{ } Roles.new_role('Veteran','Vet') -:set_custom_color{r=140,g=120,b=200} :set_permission_group('Trusted') +:set_custom_color{r=140,g=120,b=200} :set_parent('Member') :allow{ } @@ -118,15 +118,15 @@ Roles.new_role('Veteran','Vet') --- Standard User Roles Roles.new_role('Member','Mem') -:set_custom_color{r=24,g=172,b=188} :set_permission_group('Standard') +:set_custom_color{r=24,g=172,b=188} :set_parent('Regular') :allow{ } Roles.new_role('Regular','Reg') -:set_custom_color{r=79,g=155,b=163} :set_permission_group('Standard') +:set_custom_color{r=79,g=155,b=163} :set_parent('Guest') :allow{ 'command/kill' @@ -135,8 +135,8 @@ Roles.new_role('Regular','Reg') --- Guest/Default role Roles.new_role('Guest','') -:set_custom_color{r=185,g=187,b=160} :set_permission_group('Guest') +:set_custom_color{r=185,g=187,b=160} :allow{ 'command/me', 'command/tag', @@ -146,8 +146,8 @@ Roles.new_role('Guest','') --- Jail role Roles.new_role('Jail') -:set_custom_color{r=50,g=50,b=50} :set_permission_group('Restricted') +:set_custom_color{r=50,g=50,b=50} :set_block_auto_promote(true) :allow{ } diff --git a/expcore/roles.lua b/expcore/roles.lua index d5690744..6b24ed44 100644 --- a/expcore/roles.lua +++ b/expcore/roles.lua @@ -1,3 +1,156 @@ +--- Factorio role system to manage custom permissions +-- @author Cooldude2606 +-- @module Commands +--[[ +>>>>Using Role System (Frontend): + When a map first starts you will want to define on mass all the players you expect to join and the roles to give them: + Roles.override_player_roles{ + Cooldude2606 = {'Owner','Admin','Member'}, + NotCooldude2606 = {'Member'} + } + + Once the game is running you still want to be able to give role and remove them which is when you would use: + Roles.assign_player(player,'Admin',by_player_name) -- this will give the "Admin" role to the player + Roles.unassign_player(player,{'Admin','Moderator'},by_player_name) -- this will remove "Admin" and "Moderator" role in one go + +>>>>Using Role System (Backend): + To comparer two players you can comparer the index of they highest roles, can be used when you want to allow a "write" down type system: + Roles.get_player_highest_role(playerOne).index < Roles.get_player_highest_role(playerTwo).index -- remember that less means a higher role + + Listing all of a players roles can also be useful which is when you would want to use: + Roles.get_player_roles(player) -- the return is an array that can be looped over however this is not in particular order + + Finally you may want to test if a player has a certain role, flag or action allowed which is when you would use: + Roles.player_has_role(player,'Admin') -- you can provide a role name if you only want a name based system + Roles.player_has_flag(player,'is_donator') -- your roles can be grouped together with flags such as is_donator + Roles.player_allowed(player,'game modifiers') -- or you can have an action based system where each action is something the player can do + +>>>>Example Flag Define: + Flags can be used to group multiple roles and actions under one catch all, for example if you want a piece of code to only + be active for your donators then you would add a "is_donator" flag to all your donator roles and then in the code test if + a player has that tag present: + + -- give you donators a speed boost when they join; these functions aren't required but can be useful + Roles.define_flag_trigger('is_donator',function(player,state) + if state then + player.character_running_speed_modifier = 1.5 + else + player.character_running_speed_modifier = 1 + end + end) + + -- then on all your donator roles you would add + Roles.new_role('Donator') + :set_flag('is_donator') + + -- and in your code you would test for + if Roles.player_has_flag(player,'is_donator') then + -- some donator only code + end + +>>>>Example Role Define: + You can't use a role system without any roles so first you must define your roles; each role has a minimum of a name with + the option for a shorthand: + Roles.new_role('Administrator','Admin') + + Next you will want to add any extras you want to have, such as a tag, colour, permission group or any custom flags: + Roles.new_role('Administrator','Admin') + :set_custom_tag('[Admin]') + :set_custom_color('red') -- this can be {r=0,g=0,b=0} or a predefined value + :set_permission_group('Staff') -- a second argument can be added if you have not used the custom permission group config + :set_flag('is_admin') + + You will then want to decide if you want to allow all actions, this should of course be used sparely: + Roles.new_role('Administrator','Admin') + ...extras... + :set_allow_all() + + If you dont do this want this as i would advise you do then you will want to define what the role can do; this comes with + an optional inheritance system if you like those sort of things in which case disallow may also be of some use to you: + Roles.new_role('Administrator','Admin') + ...extras... + :set_parent('Moderator') -- the admin can do anything that a moderator can do + :allow{ -- these actions can be anything just try to keep them without conflicts + 'command/kill', + 'gui/game settings' + } + + Here is what the finished admin role would look like: + Roles.new_role('Administrator','Admin') + :set_custom_tag('[Admin]') + :set_custom_color('red') + :set_permission_group('Staff') + :set_flag('is_admin') + :set_parent('Moderator') + :allow{ + 'command/kill', + 'gui/game settings' + } + +>>>>Example System Define: + Once all roles are defined these steps must be done to ensure the system is ready to use, this includes setting a default + role, assigning a root (all permission) role that the server/system will use and the linier order that the roles fall into: + + Roles.set_default('Guest') + Roles.set_root('System') + + Roles.define_role_order{ + 'System', + 'Administrator', + 'Moderator', + 'Donator', + 'Guest' + } + + Just remember that in this example all these roles have not been defined; so make sure all your roles that are used are defined + before hand; a config file on load is useful for this to ensure that its loaded before the first player even joins. + +>>>>Functions List (see function for more detail): + Roles.debug() --- Returns a string which contains all roles in index order displaying all data for them + + Roles.get_role_by_name(name) --- Get a role for the given name + Roles.get_role_by_order(index) --- Get a role with the given order index + Roles.get_role_from_any(any) --- Gets a role from a name,index or role object (where it is just returned) + Roles.get_player_roles(player) --- Gets all the roles of the given player, this will always contain the default role + Roles.get_player_highest_role(player) --- Gets the highest role which the player has, can be used to compeer one player to another + + Roles.assign_player(player,roles,by_player_name) --- Gives a player the given role(s) with an option to pass a by player name used in the log + Roles.unassign_player(player,roles,by_player_name) --- Removes a player from the given role(s) with an option to pass a by player name used in the log + Roles.override_player_roles(roles) --- Overrides all player roles with the given table of roles, useful to mass set roles on game start + + Roles.player_has_role(player,search_role) --- A test for weather a player has the given role + Roles.player_has_flag(player,flag_name) --- A test for weather a player has the given flag true for at least one of they roles + Roles.player_allowed(player,action) --- A test for weather a player has at least one role which is allowed the given action + + Roles.define_role_order(order) --- Used to set the role order, higher in the list is better, must be called at least once in config + Roles.define_flag_trigger(name,callback) --- Defines a new trigger for when a tag is added or removed from a player + Roles.set_default(name) --- Sets the default role which every player will have, this needs to be called at least once + Roles.set_root(name) --- Sets the root role which will always have all permissions, any server actions act from this role + + Roles.new_role(name,short_hand) --- Defines a new role and returns the prototype to allow configuration + + Roles._prototype:set_allow_all(state) --- Sets the default allow state of the role, true will allow all actions + Roles._prototype:allow(actions) --- Sets the allow actions for this role, actions in this list will be allowed for this role + Roles._prototype:disallow(actions) --- Sets the disallow actions for this role, will prevent actions from being allowed regardless of inheritance + Roles._prototype:is_allowed(action) --- Test for if a role is allowed the given action, mostly internal see Roles.player_allowed + + Roles._prototype:set_flag(name,value) --- Sets the state of a flag for a role, flags can be used to apply effects to players + Roles._prototype:clear_flags() --- Clears all flags from this role, individual flags can be removed with set_flag(name,false) + Roles._prototype:has_flag(name) --- A test for if the role has a flag set + + Roles._prototype:set_custom_tag(tag) --- Sets a custom player tag for the role, can be accessed by other code + Roles._prototype:set_custom_color(color) --- Sets a custom colour for the role, can be accessed by other code + Roles._prototype:set_permission_group(name,use_factorio_api) --- Sets the permission group for this role, players will be moved to the group of they highest role + Roles._prototype:set_parent(role) --- Sets the parent for a role, any action not in allow or disallow will be looked for in its parents + Roles._prototype:set_auto_promote_condition(callback) --- Sets an auto promote condition that is checked every 5 seconds, if true is returned then the player will recive the role + Roles._prototype:set_block_auto_promote(state) --- Sets the role to not allow players to have auto promote effect them, useful to keep people locked to a punishment + + Roles._prototype:add_player(player,skip_check,skip_event) --- Adds a player to this role, players can have more than one role at a time, used internally see Roles.assign + Roles._prototype:remove_player(player,skip_check,skip_event) --- Removes a player from this role, players can have more than one role at a time, used internally see Roles.unassign + Roles._prototype:get_players(online) --- Returns an array of all the players who have this role, can be filtered by online status + Roles._prototype:print(message) --- Will print a message to all players with this role +]] + local Game = require 'utils.game' local Global = require 'utils.global' local Event = require 'utils.event' From 247461d21dee6ca52d4b5e808b2ff9e8c3c62d95 Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Wed, 10 Apr 2019 15:25:47 +0100 Subject: [PATCH 11/15] Improved comment in commands --- expcore/commands.lua | 263 ++++++++++++++++++++++++++----------------- 1 file changed, 159 insertions(+), 104 deletions(-) diff --git a/expcore/commands.lua b/expcore/commands.lua index 3a3baeea..b9eb5186 100644 --- a/expcore/commands.lua +++ b/expcore/commands.lua @@ -3,115 +3,170 @@ -- @module Commands --[[ >>>>Example Authenticator: + The command system is most useful when you can control who can use commands; to do this would would need to + define an authenticator which is ran every time a command is run; in this example I will show a simple one + that requires some commands to require the user to be a game admin: - -- 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 + When the authenticator is called be the command handler it will be passed 4 vales: + 1) the player who used the command + 2) the name of the command that is being used + 3) any flags which have been set for this command, this is a table of values set using :set_flag(name,value) + 4) the reject function which is the preferred method to prevent execution of the command + + For our admin only example we will set a flag to true when we want it do be admin only so when we define the + command will will use :set_flag('admin_only',true) and then inside the authenticator we will test if the flag + is present using: if flags.admin_only then + + Although no return is required to allow the command to execute it is best practice to return true; we do this in + two cases in our authenticator: + 1) when the "admin_only" flag is not set, which we take to mean any one can use it + 2) when the "admin_only" flag is set, and the player is admin + + Now when the user is not an admin and the command requires you to be an admin then we must reject the request: + 1) return false -- this is the most basic block and should only be used while testing + 2) return reject -- returning the reject function is only an option as a fail safe, same as returning false + 3) reject() -- this will block execution without returning to allow further code to be ran in the authenticator + 4) reject('This command is for admins only!') -- Using reject as a function allows a error message to be returned + 5) return reject() -- using return on either case above is best practice as you should execute all code before rejecting + + Example Code: + Commands.add_authenticator(function(player,command,flags,reject) + if flags.admin_only then -- our test for the "admin_only" flag + if player.admin then + return true -- true return 2 + else + return reject('This command is for admins only!') -- reject return 5 with a custom error message end - else -- command does not require admin - return true -- no return is needed for success but is useful to include + else + return true -- true return 1 end end) >>>>Example Parse: + Before you go making commands it is important to understand the most powerful feature of this command handler, + when you define a command you are able to type the params and have then be parsed by an handler so before your + command is ever executed you can be sure that all the params are valid. This module should be paired with a general + command parse but you may want to create your own: - -- 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 + For our example we will create a parse to accept only integer numbers in a given range: + 1) we will give it the name "number-range-int" this is the "type" that the input is expected to be + 2) when we define the type we will also define the min and max of the range so we can use the function more than once + Example parse usage: + :add_param('repeat_count',false,'number-range-int',5,10) -- range 5 to 10 inclusive + + The command parse will be passed 3 params and any other you define, in our case: + 1) the input that has been given by the user for this param, the role of this function is to transform this value + nb: the input is a string but can be nil if the param is marked as optional + 2) the player who is using the command, this is always present + 3) the reject function to throw an error to the user, this is always present + 4) the range min, this is user defined and has the value given when the param is defined + 5) the range max, this is user defined and has the value given when the param is defined + + When returning from the param parse you again have a few options with how to do this: + 1) you return the new value for the param (any non nil value) this value is then passed to the command callback + 2) not returning will cause a generic invalid error and the command callback is blocked, not recommenced + 3) return reject -- this is just a failsafe in case the function is not called, same as no return + 4) return reject() -- will give a shorter error message as you pass a nil custom error + 5) return reject('Number entered is not in range: '..range_min..', '..range_max) -- returns a custom error the the user + nb: if you do not return reject after you call it then you are still returning nil so there will be a duplicate message + + It should be noted that if you want to expand on an existing parse you can use Commands.parse(type,input,player,reject) + and this value will either return a new value for the input or nil, if it is nil you should return nil to prevent dobble + messages to the user: + input = Commands.parse('number-int',input,player,reject) + if not input then return end -- nil check + + Example Code: 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) - :set_defaults{smiley=false} -- adds false as the default for smiley - :set_flag('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 + -- the input is either not a number or is outside the range return reject('Number entered is not in range: '..range_min..', '..range_max) else + -- returns the input as a number value rather than a string, thus the param is now the correct type return rtn end end) +>>>>Example Command: + How for the fun part making the commands, the commands can be set up with any number of params and flags that you want, + you can add aliases for the commands and set default values for optional params and of course register your command callback + in our example we will just have a command that will repeat the users name in chat X amount of times and only allow admins to use it. + + First we create the new command, nb this will not register the command to the game this is done at the end, we will call + the command "repeat-name" and set the help message as follows: 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) + + Now for our first param we will call "repeat-count" and it will be a required value between 1 and 5 inclusive: + :add_param('repeat-count',false,'number-range-int',1,5) + + Our second param we need a custom parse for but we have not defined it, this is an option for when it is unlikely for + any other command to use the same input type; however in our case it will just be a boolean which should be noted as being + included in the general command parse config. As for the param its self it will be called "smiley" and will be optional with + a default value of false: :add_param('smiley',true,function(input,player,reject) + -- since it is optional the input can be nil, in which case we just return + if not input then return end + -- if it is not nil then we check for a truthy value + if input:lower() == 'true' or input:lower() == 'yes' then + return true + else + -- note that because we did not return nil or reject then false will be passed to command callback, see example parse + return false + end + end) + + Once all params are defined you can now define some default values if you have optional params, the default value will be used only + when no value is given as input, if an invalid value is given then the command will still fail and this value will not be used, the + default can also be a function which is passed the player using the command and returns a value. Here we set the default for "smiley" to false: + :set_defaults{smiley=false} + + Another example of defaults if we have: item, amount[opt], player[opt] + :set_defaults{ + amount = 50, -- more than one value can be set at a time + player = function(player) + return player -- default is the player using the command + end + } + + Now the params are set up we can alter how the command works, we can set auth flags, add aliases to this command or enable "auto concat" + which is when you want all extra words to be concatenated onto the end of the last param, useful for reason or messages: + :set_flag('admin_only',true) -- in our case we want "admin_only" to be set to true so only admins can use the command + :add_alias('name','rname') -- we also add two aliases here: "name" and "rname" which point to this command + -- :enable_auto_concat() we do not use this in our case but this can also be used to enable the "auto concat" feature + + And finally we want to register a callback to this command, the callback is what defines what the command does, can be as complex as you + want it to be to as simple as our example; the command receives two params plus all that you have defines: + 1) the player who used the command + 2) in our case repeat_count which will be a number + 3) in our case smiley which will be a boolean + 4) the raw input; this param is always last as is always present as a catch all + :register(function(player,repeat_count,smiley,raw) + -- this is to show the value for raw as this is an example command, the log file will also show this + game.print(player.name..' used a command with input: '..raw) + local msg = ') '..player.name + if smiley then + -- this is where that smiley param is used + msg = ':'..msg + end + for 1 = 1,repeat_count do + -- this print function will return ANY value to the user in a desync safe manor, this includes if the command was used through rcon + Command.print(1..msg) + end + -- see below for what else can be used here + end) + + Some other useful functions that can be used are: + Commands.print(any,colour[opt]) -- this will return any value value to the user including if it is ran through rcon console + Commands.error(message[opt]) -- this returns a warning to the user, aka an error that does not prevent execution of the command + return Commands.error(message[opt]) -- this returns an error to the user, and will halt the command execution, ie no success message is returned + Commands.success(message[opt]) -- used to return a success message however dont use this method see below + return Commands.success(message[opt]) -- will return the success message to the user and your given message, halts execution + + Example Code: + 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) -- required int in range 1 to 5 inclusive + :add_param('smiley',true,function(input,player,reject) -- optional boolean default false if not input then return end if input:lower() == 'true' or input:lower() == 'yes' then return true @@ -120,16 +175,16 @@ end end) :set_defaults{smiley=false} - :set_flag('admin_only',true) - :add_alias('name','rname') + :set_flag('admin_only',true) -- command is admin only + :add_alias('name','rname') -- allow alias: name and 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) + for 1 = 1,repeat_count do + Command.print(1..msg) end end) @@ -148,7 +203,7 @@ 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:set_defaults(defaults) --- Adds default values to params only matters if the param is optional - Commands._prototype:set_flag(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:set_flag(name,value) --- Adds a tag to the command which is passed via the flags 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 @@ -183,7 +238,7 @@ local Commands = { -- @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 - flags: table - any flags 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) @@ -241,8 +296,8 @@ function Commands.authorize(player,command_name) -- 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) + -- callback(player: LuaPlayer, command: string, flags: table, reject: function(error_message?: string)) + local success, rtn = pcall(callback,player,command_name,command_data.flags,auth_fail) -- error handler if not success then -- the callback failed to run @@ -360,7 +415,7 @@ function Commands.new_command(name,help) auto_concat=false, min_param_count=0, max_param_count=0, - tags={}, -- stores tags that can be used by auth + flags={}, -- stores flags that can be used by auth aliases={}, -- n = name: string params={}, -- [param_name] = {optional: boolean, default: any, parse: function, parse_args: table} }, { @@ -408,18 +463,18 @@ function Commands._prototype:set_defaults(defaults) 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 +--- Adds a tag to the command which is passed via the flags 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 flags 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:set_flag(name,value) if not value then -- value not given so name is the value - table.insert(self.tags,name) + table.insert(self.flags,name) else -- name is given so its key: value - self.tags[name] = value + self.flags[name] = value end return self end From 7925ad3444b8e12ff2a505d6c6cd5bb061962cc6 Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Wed, 10 Apr 2019 19:38:03 +0100 Subject: [PATCH 12/15] Renamed worn paths to scorched earth --- config/{worn_paths.lua => scorched_earth.lua} | 0 modules/addons/{worn-paths.lua => scorched-earth.lua} | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename config/{worn_paths.lua => scorched_earth.lua} (100%) rename modules/addons/{worn-paths.lua => scorched-earth.lua} (99%) diff --git a/config/worn_paths.lua b/config/scorched_earth.lua similarity index 100% rename from config/worn_paths.lua rename to config/scorched_earth.lua diff --git a/modules/addons/worn-paths.lua b/modules/addons/scorched-earth.lua similarity index 99% rename from modules/addons/worn-paths.lua rename to modules/addons/scorched-earth.lua index 2d301a3d..15660669 100644 --- a/modules/addons/worn-paths.lua +++ b/modules/addons/scorched-earth.lua @@ -2,7 +2,7 @@ local Event = require 'utils.event' local Game = require 'utils.game' local Global = require 'utils.global' local print_grid_value, clear_flying_text = ext_require('expcore.common','print_grid_value','clear_flying_text') -local config = require 'config.worn_paths' +local config = require 'config.scorched_earth' -- Loops over the config and finds the wile which has the highest value for strength local max_strength = 0 From d4f9161cdf1cbbc2ce0fc7b2d0910790bdff4f06 Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Wed, 10 Apr 2019 20:00:26 +0100 Subject: [PATCH 13/15] Added Pollution Grading --- config/pollution_grading.lua | 7 +++++++ modules/addons/pollution-grading.lua | 13 +++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 config/pollution_grading.lua create mode 100644 modules/addons/pollution-grading.lua diff --git a/config/pollution_grading.lua b/config/pollution_grading.lua new file mode 100644 index 00000000..b19fd3ef --- /dev/null +++ b/config/pollution_grading.lua @@ -0,0 +1,7 @@ +-- This controls how pollution is viewed on the map +return { + reference_point = {x=0,y=0}, -- where pollution is read from + max_scalar = 0.5, -- the scale between true max and max + min_scalar = 0.17, -- the scale between the lowest max and min + update_delay = 15 -- time in minutes between view updates +} \ No newline at end of file diff --git a/modules/addons/pollution-grading.lua b/modules/addons/pollution-grading.lua new file mode 100644 index 00000000..8166cebd --- /dev/null +++ b/modules/addons/pollution-grading.lua @@ -0,0 +1,13 @@ +local Event = require 'utils.event' +local config = require 'config.pollution_grading' + +local delay = config.update_delay * 3600 -- convert from minutes to ticks +Event.on_nth_tick(delay,function() + local surface = game.surfaces[1] + local true_max = surface.get_pollution(config.reference_point) + local max = true_max*config.max_scalar + local min = max*config.min_scalar + local settings = game.map_settings.pollution + settings.expected_max_per_chunk = max + settings.min_to_show_per_chunk = min +end) \ No newline at end of file From eaec5f438f40bf7c1ccb664391001aa0acdcee60 Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Wed, 10 Apr 2019 20:06:32 +0100 Subject: [PATCH 14/15] Fixed invalid compilatron bug --- modules/addons/compilatron.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/addons/compilatron.lua b/modules/addons/compilatron.lua index f7142b3d..f184baf5 100644 --- a/modules/addons/compilatron.lua +++ b/modules/addons/compilatron.lua @@ -20,6 +20,8 @@ Global.register( end ) +local Public = {} + --- This will re-create the speech bubble after it de-spawns called with set_timeout local callback = Token.register( @@ -38,6 +40,9 @@ local callback = --- This will move the messages onto the next message in the loop local function circle_messages() for name, ent in pairs(compilatrons) do + if not ent.valid then + Public.spawn_compilatron(game.players[1].surface,name) + end local current_message = current_messages[name] local msg_number local message @@ -58,8 +63,6 @@ end Event.on_nth_tick(config.message_cycle, circle_messages) -local Public = {} - --- This will add a compilatron to the global and start his message cycle -- @tparam entity LuaEntity the compilatron entity that moves around -- @tparam name string the name of the location that the complitron is at From ee2344acdbdf350bcca2b2c9526bc2e688b7a601 Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Thu, 11 Apr 2019 16:55:10 +0100 Subject: [PATCH 15/15] Added Role Commands --- config/file_loader.lua | 1 + config/roles.lua | 5 ++++- locale/en/commands.cfg | 5 ++++- modules/commands/roles.lua | 45 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 modules/commands/roles.lua diff --git a/config/file_loader.lua b/config/file_loader.lua index 993582b8..caa0a1a8 100644 --- a/config/file_loader.lua +++ b/config/file_loader.lua @@ -14,6 +14,7 @@ return { 'modules.commands.cheat-mode', 'modules.commands.interface', 'modules.commands.help', + 'modules.commands.roles', -- QoL Addons 'modules.addons.chat-popups', 'modules.addons.damage-popups', diff --git a/config/roles.lua b/config/roles.lua index 4cead3dd..81cf5b7d 100644 --- a/config/roles.lua +++ b/config/roles.lua @@ -58,6 +58,8 @@ Roles.new_role('Moderator','Mod') :set_flag('is_spectator') :set_parent('Trainee') :allow{ + 'command/assign-role', + 'command/unassign-role' } Roles.new_role('Trainee','TrMod') @@ -141,7 +143,8 @@ Roles.new_role('Guest','') 'command/me', 'command/tag', 'command/tag-clear', - 'command/chelp' + 'command/chelp', + 'command/list-roles' } --- Jail role diff --git a/locale/en/commands.cfg b/locale/en/commands.cfg index ddf4213b..e1cff318 100644 --- a/locale/en/commands.cfg +++ b/locale/en/commands.cfg @@ -7,4 +7,7 @@ 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. \ No newline at end of file +chelp-out-of-range=__1__ is an invalid page number. +roles-higher-role=The role you tried to assign is higher than your highest. +roles-list=All active roles are: +roles-list-element=__1__, [color=__2__]__3__ \ No newline at end of file diff --git a/modules/commands/roles.lua b/modules/commands/roles.lua new file mode 100644 index 00000000..64bdb74e --- /dev/null +++ b/modules/commands/roles.lua @@ -0,0 +1,45 @@ +local Commands = require 'expcore.commands' +local Roles = require 'expcore.roles' +local Colours = require 'resources.color_presets' + +Commands.new_command('assign-role','Assigns a role to a player') +:add_param('player',false,'player-role') +:add_param('role',false,'role') +:set_flag('admin-only',true) +:add_alias('rpromote','assign','role','add-role') +:register(function(player,action_player,role,raw) + local player_highest = Roles.get_player_highest(player) + if player_highest.index < role.index then + Roles.assign_player(action_player,role,player.name) + else + return Commands.error{'exp-commands.roles-higher-role'} + end +end) + +Commands.new_command('unassign-role','Unassigns a role from a player') +:add_param('player',false,'player-role') +:add_param('role',false,'role') +:set_flag('admin-only',true) +:add_alias('rdemote','unassign','remove-role') +:register(function(player,action_player,role,raw) + local player_highest = Roles.get_player_highest(player) + if player_highest.index < role.index then + Roles.unassign_player(action_player,role,player.name) + else + return Commands.error{'exp-commands.roles-higher-role'} + end +end) + +Commands.new_command('list-roles','Lists all roles in they correct order') +:add_alias('lroles','roles') +:register(function(player,raw) + local roles = Roles.config.order + local message = {'exp-commands.roles-list'} + for _,role_name in pairs(roles) do + local role = Roles.get_role_by_name(role_name) + local colour = role.custom_color or Colours.white + colour = string.format('%d,%d,%d',colour.r,colour.g,colour.b) + message = {'exp-commands.roles-list-element',message,colour,role_name} + end + return Commands.success(message) +end) \ No newline at end of file