diff --git a/config/expcore/roles.lua b/config/expcore/roles.lua index 3f6dd512..20feeb14 100644 --- a/config/expcore/roles.lua +++ b/config/expcore/roles.lua @@ -267,6 +267,7 @@ Roles.new_role('Jail') :set_permission_group('Restricted') :set_custom_color{r=50,g=50,b=50} :set_block_auto_assign(true) +:set_flag("defer_role_changes") :disallow(default.allowed) --- System defaults which are required to be set diff --git a/expcore/roles.lua b/expcore/roles.lua index 2028cd23..b895897b 100644 --- a/expcore/roles.lua +++ b/expcore/roles.lua @@ -117,14 +117,15 @@ local Colours = require 'utils.color_presets' --- @dep utils.color_presets local write_json = _C.write_json --- @dep expcore.common local Roles = { - _prototype={}, - 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 = {}, -- Contains the roles that players have - auto_assign = {} -- Contains references to all roles which have auto assign conditions + _prototype = {}, + 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 = {}, -- Contains the roles that players have + auto_assign = {}, -- Contains references to all roles which have auto assign conditions + deferred_roles = {}, -- Contains the roles that are to be assigned to players when they are unjailed }, events = { on_role_assigned = script.generate_event_name(), @@ -133,8 +134,9 @@ local Roles = { } --- When global is loaded it will have the metatable re-assigned to the roles -Global.register(Roles.config.players, function(tbl) - Roles.config.players = tbl +Global.register({ Roles.config.players, Roles.config.deferred_roles }, function(tbl) + Roles.config.players = tbl[1] + Roles.config.deferred_roles = tbl[2] end) --- Getter. @@ -145,7 +147,7 @@ end) -- this is the raw internal trigger as the other function is called at other times -- there is a second half called role_update which triggers after the event call, it also is called when a player joins local function emit_player_roles_updated(player, type, roles, by_player_name, skip_game_print) - by_player_name = game.player and game.player.name or by_player_name or '' + by_player_name = by_player_name or game.player and game.player.name or '' local by_player = game.players[by_player_name] local by_player_index = by_player and by_player.index or 0 -- get the event id from the type of emit @@ -153,14 +155,10 @@ local function emit_player_roles_updated(player, type, roles, by_player_name, sk if type == 'unassign' then event = Roles.events.on_role_unassigned end - -- convert the roles to objects and get the names of the roles - local index, role_names = 0, {} - for _, role in ipairs(roles) do - role = Roles.get_role_from_any(role) - if role then - index = index + 1 - role_names[index] = role.name - end + -- Get the names of the roles + local role_names = {} + for index, role in ipairs(roles) do + role_names[index] = role.name end -- output to all the different locations: game print, player sound, event trigger and role log if not skip_game_print then @@ -367,17 +365,46 @@ function Roles.assign_player(player, roles, by_player_name, skip_checks, silent) local valid_player = Game.get_player_from_any(player) if not skip_checks and not valid_player then return end if not player then return end + + -- Convert the roles into a table (allows for optional array) if type(roles) ~= 'table' or roles.name then - roles = {roles} + roles = { roles } end + + -- Convert to role objects + local role_objects = {} for _, role in ipairs(roles) do - role = Roles.get_role_from_any(role) - if role then - role:add_player(valid_player or player, valid_player == nil, true) - end + local role_object = Roles.get_role_from_any(role) + if role_object then table.insert(role_objects, role_object) end end + + -- If the player has a role that needs to defer the role changes, save the roles that need to be assigned later into a table + if valid_player and Roles.player_has_flag(player, "defer_role_changes") then + local assign_later = Roles.config.deferred_roles[valid_player.name] or {} + for _, role in ipairs(role_objects) do + local role_change = assign_later[role.name] + if role_change then + role_change.count = role_change.count + 1 + if role_change.count == 1 then + role_change.by_player_name = by_player_name or "" + role_change.silent = silent + end + else + assign_later[role.name] = { + count = 1, by_player_name = by_player_name or "", silent = silent + } + end + end + Roles.config.deferred_roles[valid_player.name] = assign_later + return + end + + for _, role in ipairs(role_objects) do + role:add_player(valid_player or player, valid_player == nil, true) + end + if valid_player then - emit_player_roles_updated(valid_player, 'assign', roles, by_player_name, silent) + emit_player_roles_updated(valid_player, 'assign', role_objects, by_player_name, silent) end end @@ -399,17 +426,78 @@ function Roles.unassign_player(player, roles, by_player_name, skip_checks, silen local valid_player = Game.get_player_from_any(player) if not skip_checks and not valid_player then return end if not player then return end + + -- Convert the roles into a table (allows for optional array) if type(roles) ~= 'table' or roles.name then - roles = {roles} + roles = { roles } end + + -- Convert to role objects + local role_objects = {} for _, role in ipairs(roles) do - role = Roles.get_role_from_any(role) - if role then + local role_object = Roles.get_role_from_any(role) + if role_object then table.insert(role_objects, role_object) end + end + + -- If the player has a role that needs to defer the role changes, save the roles that need to be unassigned later into a table + local defer_changes = Roles.player_has_flag(player, "defer_role_changes") + if defer_changes then + local assign_later = Roles.config.deferred_roles[valid_player.name] or {} + for _, role in ipairs(role_objects) do + local role_change = assign_later[role.name] + if role_change then + role_change.count = role_change.count - 1 + if role_change.count == -1 then + role_change.by_player_name = by_player_name or "" + role_change.silent = silent + end + else + assign_later[role.name] = { + count = -1, by_player_name = by_player_name or "", silent = silent + } + end + end + Roles.config.deferred_roles[valid_player.name] = assign_later + end + + -- Remove the player from roles + local role_changes = {} + for _, role in ipairs(role_objects) do + if not defer_changes or role:has_flag("defer_role_changes") then role:remove_player(valid_player or player, valid_player == nil, true) + table.insert(role_changes, role) end end - if valid_player then - emit_player_roles_updated(valid_player, 'unassign', roles, by_player_name, silent) + + -- If there are deferred role changes, apply them now + if defer_changes and not Roles.player_has_flag(valid_player, "defer_role_changes") then + local assign_later = Roles.config.deferred_roles[player.name] or {} + local assigns, unassigns = {}, {} + for role_name, details in pairs(assign_later) do + local role = Roles.get_role_from_any(role_name) + if role then + if details.count > 0 then + role:add_player(valid_player or player, valid_player == nil, true) + if not assigns[details.by_player_name] then assigns[details.by_player_name] = {} end + if not details.silent then table.insert(assigns[details.by_player_name], role) end + elseif details.count < 0 then + role:remove_player(valid_player or player, valid_player == nil, true) + if not unassigns[details.by_player_name] then unassigns[details.by_player_name] = {} end + if not details.silent then table.insert(unassigns[details.by_player_name], role) end + end + end + end + for assign_by_player_name, assign_roles in pairs(assigns) do + if #assign_roles > 0 then emit_player_roles_updated(valid_player, 'assign', assign_roles, assign_by_player_name) end + end + for unassign_by_player_name, unassign_roles in pairs(unassigns) do + if #unassign_roles > 0 then emit_player_roles_updated(valid_player, 'unassign', unassign_roles, unassign_by_player_name) end + end + Roles.config.deferred_roles[player.name] = nil + end + + if valid_player and #role_changes > 0 then + emit_player_roles_updated(valid_player, 'unassign', role_changes, by_player_name, silent) end end diff --git a/modules/control/jail.lua b/modules/control/jail.lua index 1e37fbfa..b5bf7df4 100644 --- a/modules/control/jail.lua +++ b/modules/control/jail.lua @@ -18,7 +18,6 @@ local Roles = require 'expcore.roles' --- @dep expcore.roles local Game = require 'utils.game' --- @dep utils.game -local Global = require 'utils.global' --- @dep utils.global local valid_player = Game.get_player_from_any local assign_roles = Roles.assign_player @@ -43,12 +42,6 @@ local Jail = { } } -local old_roles = Jail.old_roles -Global.register(old_roles, function(tbl) - Jail.old_roles = tbl - old_roles = tbl -end) - --- Used to emit the jail related events -- @tparam number event the name of the event that will be emited -- @tparam LuaPlayer player the player who is being acted on @@ -89,7 +82,6 @@ function Jail.jail_player(player, by_player_name, reason) if has_role(player, 'Jail') then return end local roles = get_roles(player) - old_roles[player.name] = roles player.walking_state = { walking = false } player.riding_state = { acceleration = defines.riding.acceleration.nothing, direction = player.riding_state.direction } @@ -98,8 +90,9 @@ function Jail.jail_player(player, by_player_name, reason) player.picking_state = false player.repair_state = { repairing = false } - assign_roles(player, 'Jail', by_player_name, nil, true) unassign_roles(player, roles, by_player_name, nil, true) + assign_roles(player, 'Jail', by_player_name, nil, true) + assign_roles(player, roles, by_player_name, nil, true) event_emit(Jail.events.on_player_jailed, player, by_player_name, reason) @@ -116,9 +109,7 @@ function Jail.unjail_player(player, by_player_name) if not by_player_name then return end if not has_role(player, 'Jail') then return end - local roles = old_roles[player.name] or {} - assign_roles(player, roles, by_player_name, nil, true) unassign_roles(player, 'Jail', by_player_name, nil, true) event_emit(Jail.events.on_player_unjailed, player, by_player_name) @@ -126,4 +117,4 @@ function Jail.unjail_player(player, by_player_name) return true end -return Jail \ No newline at end of file +return Jail