Address review on the role api

- Roles are objects and everything done to or with a role is a method on it:
  assign, unassign, has_player, has_permission, is_higher_than,
  is_lower_than, get_players, get_player_names, print. Assignment has one
  entry point, role:assign(player, options), with local_only as an option
  rather than a second function.
- Roles are looked up by clusterio id with get_role; get_role_by_name searches
  the list for the few places, such as configs, which only know a name. The
  name map and the ordered list are gone, get_roles sorts on demand and the
  index field is replaced by the comparison methods.
- Players are LuaPlayer objects only, with nil or index 0 for the server.
- get_higher_roles and get_lower_roles replace print_to_roles_higher and
  print_to_roles_lower, call sites loop over them with role:print.
- Permission groups are removed from roles again, exp_groups owns the mapping
  from roles to groups.
- Seeding is a SeedRolesRequest behind a button on the roles page rather than
  running on first start, and creates only the roles; the player assignments
  are dropped. The seed lists each permission once at the lowest role which
  has it and lets the parent chain carry it up.
- System commands unlock for core.admin rather than a permission of their own.
- Role metatables are registered with Storage.register_metatable so the
  methods survive save and load, which the role records in storage needed.
- The player list auth uses Roles.player_outranks directly, and the event
  carries role ids rather than names.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
bbassie
2026-08-19 15:52:50 +00:00
co-authored by Claude Fable 5
parent 78a533a6f6
commit bea8d46c82
18 changed files with 330 additions and 506 deletions
@@ -22,7 +22,7 @@ authorities.exp_permission =
end
end)
Roles.define_permission_trigger("exp_scenario.player.system_commands", function(player, state)
Roles.define_permission_trigger("core.admin", function(player, state)
if state then
Commands.unlock_system_commands(player.name)
else
+3 -2
View File
@@ -19,6 +19,7 @@ local valid, invalid = Commands.status.success, Commands.status.invalid_input
local Roles = require("modules/exp_roles")
local player_outranks = Roles.player_outranks
local player_has_permission = Roles.player_has_permission
local types = {} --- @class (partial) Commands.types
@@ -34,7 +35,7 @@ types.role =
if name == nil then
return invalid{ "exp-commands-parse.string-options", table.concat(names, ", ") }
else
return valid(Roles.get_role(name))
return valid(Roles.get_role_by_name(name))
end
end)
@@ -45,7 +46,7 @@ types.lower_role =
if not success then return status, result end
--- @cast result ExpRoles.Role
if not Roles.player_outranks_role(player, result) then
if not player_has_permission(player, "core.admin") and not Roles.get_player_highest_role(player):is_higher_than(result) then
return invalid{ "exp-commands-parse_role.lower-role" }
else
return valid(result)
@@ -218,13 +218,16 @@ SelectEntities:on_stop(on_player_selection_stop)
--- When there is a repeat offence print it in chat
local function on_repeat_violation(event)
Roles.print_to_roles_higher("Regular", {
"exp-commands_entity-protection.repeat-offence",
format_player_name(event.player_index),
event.entity.localised_name,
event.entity.position.x,
event.entity.position.y
})
local regular = Roles.get_role_by_name("Regular")
for _, role in ipairs(regular and Roles.get_higher_roles(regular) or {}) do
role:print{
"exp-commands_entity-protection.repeat-offence",
format_player_name(event.player_index),
event.entity.localised_name,
event.entity.position.x,
event.entity.position.y
}
end
end
return {
+2 -2
View File
@@ -19,7 +19,7 @@ Commands.new("assign-role", { "exp-commands_roles.description-assign" })
:register(function(player, other_player, role)
--- @cast other_player LuaPlayer
--- @cast role ExpRoles.Role
Roles.assign_player(other_player, role, player.name)
role:assign(other_player, { by_player_name = player.name })
end)
--- Unassigns a role to a player
@@ -31,7 +31,7 @@ Commands.new("unassign-role", { "exp-commands_roles.description-unassign" })
:register(function(player, other_player, role)
--- @cast other_player LuaPlayer
--- @cast role ExpRoles.Role
Roles.unassign_player(other_player, role, player.name)
role:unassign(other_player, { by_player_name = player.name })
end)
--- Lists all roles in they correct order
+5 -2
View File
@@ -68,8 +68,11 @@ do local _points_limit = {} --- @type table<number, number>
--- @param player LuaPlayer
--- @return number
function Elements.bonus_used._calculate_points_limit(player)
local role = assert(Roles.get_role(config.points.role_name), "Bonus points role does not exist")
local role_diff = role.index - Roles.get_player_highest_role(player).index
local roles = Roles.get_roles()
local positions = {}
for index, role in ipairs(roles) do positions[role.id] = index end
local role = assert(Roles.get_role_by_name(config.points.role_name), "Bonus points role does not exist")
local role_diff = positions[role.id] - positions[Roles.get_player_highest_role(player).id]
local points_limit = math.floor(config.points.base * (1 + config.points.increase_percentage_per_role_level * role_diff))
_points_limit[player.index] = points_limit
return points_limit
+1 -1
View File
@@ -396,7 +396,7 @@ define_tab(
for _, group in ipairs(groups) do
local seen = {}
for _, role_name in ipairs(group.roles) do
local role = Roles.get_role(role_name)
local role = Roles.get_role_by_name(role_name)
for _, player_name in ipairs(role and role:get_player_names() or {}) do
if not seen[player_name] then
seen[player_name] = true
-1
View File
@@ -123,7 +123,6 @@ const definitions: Definition[] = [
["exp_scenario.player.admin", "Factorio admin", "Be promoted to Factorio admin while holding a role with this permission."],
["exp_scenario.player.instant_respawn", "Instant respawn", "Respawn after two seconds instead of the default delay."],
["exp_scenario.player.spectator", "Spectator", "Remove the zoom to world noise effect, as Factorio does for spectators."],
["exp_scenario.player.system_commands", "System commands", "Unlock commands flagged system only, such as /_rcon and /_sudo."],
];
for (const [name, title, description, grantByDefault] of definitions) {