From f4426058668064fa242875e38b04e5931cab8120 Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Tue, 9 Apr 2019 18:54:39 +0100 Subject: [PATCH] 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