mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-09-21 17:04:00 +00:00
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:
co-authored by
Claude Fable 5
parent
78a533a6f6
commit
bea8d46c82
@@ -8,7 +8,7 @@ return {
|
|||||||
trust_time = 3600 * 60 * 10, --- @setting trust_time The time in ticks that a player must be online for to count as trusted
|
trust_time = 3600 * 60 * 10, --- @setting trust_time The time in ticks that a player must be online for to count as trusted
|
||||||
update_time = 3600 * 30, --- @setting update_time How often in ticks the script checks for active players
|
update_time = 3600 * 30, --- @setting update_time How often in ticks the script checks for active players
|
||||||
custom_active_check = function(player)
|
custom_active_check = function(player)
|
||||||
local veteran = Roles.get_role("Veteran")
|
local veteran = Roles.get_role_by_name("Veteran")
|
||||||
return veteran ~= nil and Roles.get_player_highest_role(player).index <= veteran.index
|
return veteran ~= nil and not Roles.get_player_highest_role(player):is_lower_than(veteran)
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,6 @@ local function set_accessors(player_getter, action_setter)
|
|||||||
get_selected_player, set_selected_action = player_getter, action_setter
|
get_selected_player, set_selected_action = player_getter, action_setter
|
||||||
end
|
end
|
||||||
|
|
||||||
-- auth that will only allow when on player's of lower roles
|
|
||||||
local function auth_lower_role(player, selected_player_name)
|
|
||||||
return Roles.player_outranks(player, selected_player_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- gets the action player and a coloured name for the action to be used on
|
-- gets the action player and a coloured name for the action to be used on
|
||||||
local function get_action_player(player)
|
local function get_action_player(player)
|
||||||
local selected_player = get_selected_player(player) --[[@as LuaPlayer]]
|
local selected_player = get_selected_player(player) --[[@as LuaPlayer]]
|
||||||
@@ -98,7 +93,10 @@ local function report_player_callback(player, reason)
|
|||||||
local selected_player, selected_player_color = get_action_player(player)
|
local selected_player, selected_player_color = get_action_player(player)
|
||||||
local by_player_name_color = format_player_name(player)
|
local by_player_name_color = format_player_name(player)
|
||||||
game.print{ "exp-commands_reports.response", selected_player_color, reason }
|
game.print{ "exp-commands_reports.response", selected_player_color, reason }
|
||||||
Roles.print_to_roles_higher("Trainee", { "exp-commands_reports.response-admin", selected_player_color, by_player_name_color, reason })
|
local trainee = Roles.get_role_by_name("Trainee")
|
||||||
|
for _, role in ipairs(trainee and Roles.get_higher_roles(trainee) or {}) do
|
||||||
|
role:print{ "exp-commands_reports.response-admin", selected_player_color, by_player_name_color, reason }
|
||||||
|
end
|
||||||
Reports.report_player(selected_player.name, player.name, reason)
|
Reports.report_player(selected_player.name, player.name, reason)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -180,22 +178,22 @@ return {
|
|||||||
report_player,
|
report_player,
|
||||||
},
|
},
|
||||||
["exp_scenario.command.create_warning"] = {
|
["exp_scenario.command.create_warning"] = {
|
||||||
auth = auth_lower_role, -- warn a lower user, replaces report
|
auth = Roles.player_outranks, -- warn a lower user, replaces report
|
||||||
reason_callback = warn_player_callback,
|
reason_callback = warn_player_callback,
|
||||||
warn_player,
|
warn_player,
|
||||||
},
|
},
|
||||||
["exp_scenario.command.jail"] = {
|
["exp_scenario.command.jail"] = {
|
||||||
auth = auth_lower_role,
|
auth = Roles.player_outranks,
|
||||||
reason_callback = jail_player_callback,
|
reason_callback = jail_player_callback,
|
||||||
jail_player,
|
jail_player,
|
||||||
},
|
},
|
||||||
["exp_scenario.gui.player_list.kick"] = {
|
["exp_scenario.gui.player_list.kick"] = {
|
||||||
auth = auth_lower_role,
|
auth = Roles.player_outranks,
|
||||||
reason_callback = kick_player_callback,
|
reason_callback = kick_player_callback,
|
||||||
kick_player,
|
kick_player,
|
||||||
},
|
},
|
||||||
["exp_scenario.gui.player_list.ban"] = {
|
["exp_scenario.gui.player_list.ban"] = {
|
||||||
auth = auth_lower_role,
|
auth = Roles.player_outranks,
|
||||||
reason_callback = ban_player_callback,
|
reason_callback = ban_player_callback,
|
||||||
ban_player,
|
ban_player,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,8 +20,10 @@ local Roles = require("modules/exp_roles")
|
|||||||
|
|
||||||
local valid_player = function(p) return type(p) == "userdata" and p or game.get_player(p) end
|
local valid_player = function(p) return type(p) == "userdata" and p or game.get_player(p) end
|
||||||
|
|
||||||
--- The role which is given to jailed players, it has a higher priority than every other role
|
--- The role given to jailed players, it has a higher priority than every other role
|
||||||
local jail_role = "Jail"
|
local function jail_role()
|
||||||
|
return assert(Roles.get_role_by_name("Jail"), "The Jail role does not exist")
|
||||||
|
end
|
||||||
|
|
||||||
local Jail = {
|
local Jail = {
|
||||||
events = {
|
events = {
|
||||||
@@ -62,7 +64,8 @@ end
|
|||||||
-- @tparam LuaPlayer player the player to check if they are in jail
|
-- @tparam LuaPlayer player the player to check if they are in jail
|
||||||
-- @treturn boolean whether the player is currently in jail
|
-- @treturn boolean whether the player is currently in jail
|
||||||
function Jail.is_jailed(player)
|
function Jail.is_jailed(player)
|
||||||
return Roles.player_has_role(valid_player(player), jail_role)
|
local valid = valid_player(player)
|
||||||
|
return valid ~= nil and jail_role():has_player(valid)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Moves a player to jail, which suppresses all of their other roles
|
--- Moves a player to jail, which suppresses all of their other roles
|
||||||
@@ -77,7 +80,8 @@ function Jail.jail_player(player, by_player_name, reason)
|
|||||||
|
|
||||||
reason = reason or "Non given."
|
reason = reason or "Non given."
|
||||||
|
|
||||||
if Roles.player_has_role(player, jail_role) then return end
|
local role = jail_role()
|
||||||
|
if role:has_player(player) then return end
|
||||||
|
|
||||||
player.walking_state = { walking = false, direction = player.walking_state.direction }
|
player.walking_state = { walking = false, direction = player.walking_state.direction }
|
||||||
player.riding_state = { acceleration = defines.riding.acceleration.nothing, direction = player.riding_state.direction }
|
player.riding_state = { acceleration = defines.riding.acceleration.nothing, direction = player.riding_state.direction }
|
||||||
@@ -86,7 +90,7 @@ function Jail.jail_player(player, by_player_name, reason)
|
|||||||
player.picking_state = false
|
player.picking_state = false
|
||||||
player.repair_state = { repairing = false, position = player.repair_state.position }
|
player.repair_state = { repairing = false, position = player.repair_state.position }
|
||||||
|
|
||||||
Roles.assign_player(player, jail_role, by_player_name, true)
|
role:assign(player, { by_player_name = by_player_name, silent = true })
|
||||||
|
|
||||||
event_emit(Jail.events.on_player_jailed, player, by_player_name, reason)
|
event_emit(Jail.events.on_player_jailed, player, by_player_name, reason)
|
||||||
|
|
||||||
@@ -102,9 +106,10 @@ function Jail.unjail_player(player, by_player_name)
|
|||||||
if not player then return end
|
if not player then return end
|
||||||
if not by_player_name then return end
|
if not by_player_name then return end
|
||||||
|
|
||||||
if not Roles.player_has_role(player, jail_role) then return end
|
local role = jail_role()
|
||||||
|
if not role:has_player(player) then return end
|
||||||
|
|
||||||
Roles.unassign_player(player, jail_role, by_player_name, true)
|
role:unassign(player, { by_player_name = by_player_name, silent = true })
|
||||||
|
|
||||||
event_emit(Jail.events.on_player_unjailed, player, by_player_name)
|
event_emit(Jail.events.on_player_unjailed, player, by_player_name)
|
||||||
|
|
||||||
|
|||||||
+6
-24
@@ -1,7 +1,7 @@
|
|||||||
import { BaseControllerPlugin, InstanceRecord } from "@clusterio/controller";
|
import { BaseControllerPlugin, InstanceRecord } from "@clusterio/controller";
|
||||||
import * as lib from "@clusterio/lib";
|
import * as lib from "@clusterio/lib";
|
||||||
import * as messages from "./messages";
|
import * as messages from "./messages";
|
||||||
import { SeedRole, seedRoles, seedAssignments, flattenSeedPermissions } from "./seed";
|
import { SeedRole, seedRoles, flattenSeedPermissions } from "./seed";
|
||||||
import * as path from "node:path";
|
import * as path from "node:path";
|
||||||
|
|
||||||
export class ControllerPlugin extends BaseControllerPlugin {
|
export class ControllerPlugin extends BaseControllerPlugin {
|
||||||
@@ -17,12 +17,6 @@ export class ControllerPlugin extends BaseControllerPlugin {
|
|||||||
).bootstrap()
|
).bootstrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
// An empty datastore means the plugin has not run before, so the roles
|
|
||||||
// the scenario used to define are created on the controller
|
|
||||||
if (this.roleMeta.size === 0) {
|
|
||||||
this.seed();
|
|
||||||
}
|
|
||||||
|
|
||||||
// The datastore can be out of step with the roles, either because the
|
// The datastore can be out of step with the roles, either because the
|
||||||
// plugin was installed after they were created or because it was
|
// plugin was installed after they were created or because it was
|
||||||
// uninstalled while they were deleted
|
// uninstalled while they were deleted
|
||||||
@@ -41,6 +35,7 @@ export class ControllerPlugin extends BaseControllerPlugin {
|
|||||||
|
|
||||||
this.controller.handle(messages.RoleListRequest, this.handleRoleListRequest.bind(this));
|
this.controller.handle(messages.RoleListRequest, this.handleRoleListRequest.bind(this));
|
||||||
this.controller.handle(messages.RoleMetaUpdateRequest, this.handleRoleMetaUpdateRequest.bind(this));
|
this.controller.handle(messages.RoleMetaUpdateRequest, this.handleRoleMetaUpdateRequest.bind(this));
|
||||||
|
this.controller.handle(messages.SeedRolesRequest, this.handleSeedRolesRequest.bind(this));
|
||||||
|
|
||||||
this.controller.handle(messages.AssignmentListRequest, this.handleAssignmentListRequest.bind(this));
|
this.controller.handle(messages.AssignmentListRequest, this.handleAssignmentListRequest.bind(this));
|
||||||
this.controller.handle(messages.AssignmentUpdateRequest, this.handleAssignmentUpdateRequest.bind(this));
|
this.controller.handle(messages.AssignmentUpdateRequest, this.handleAssignmentUpdateRequest.bind(this));
|
||||||
@@ -55,18 +50,16 @@ export class ControllerPlugin extends BaseControllerPlugin {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the roles the scenario shipped with and give the players it listed
|
* Create the roles the scenario shipped with. Roles which already exist
|
||||||
* their roles. Roles which already exist by name are reused.
|
* by name are reused and only gain the seed permissions.
|
||||||
*/
|
*/
|
||||||
seed() {
|
async handleSeedRolesRequest() {
|
||||||
const idsByName = new Map<string, number>();
|
|
||||||
for (const [index, seedRole] of seedRoles.entries()) {
|
for (const [index, seedRole] of seedRoles.entries()) {
|
||||||
const role = this.seedRole(seedRole);
|
const role = this.seedRole(seedRole);
|
||||||
if (!role) {
|
if (!role) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
idsByName.set(seedRole.name, role.id);
|
|
||||||
this.roleMeta.set(new messages.RoleMetaRecord(
|
this.roleMeta.set(new messages.RoleMetaRecord(
|
||||||
role.id,
|
role.id,
|
||||||
index + 1,
|
index + 1,
|
||||||
@@ -74,23 +67,12 @@ export class ControllerPlugin extends BaseControllerPlugin {
|
|||||||
seedRole.shortHand,
|
seedRole.shortHand,
|
||||||
"",
|
"",
|
||||||
seedRole.color,
|
seedRole.color,
|
||||||
seedRole.permissionGroup,
|
|
||||||
seedRole.autoAssignHours === undefined ? null : seedRole.autoAssignHours * 3600000,
|
seedRole.autoAssignHours === undefined ? null : seedRole.autoAssignHours * 3600000,
|
||||||
seedRole.blockAutoAssign ?? false,
|
seedRole.blockAutoAssign ?? false,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [name, roleNames] of Object.entries(seedAssignments)) {
|
this.logger.info(`Seeded ${seedRoles.length} roles`);
|
||||||
const roleIds = roleNames.map(roleName => idsByName.get(roleName)).filter(id => id !== undefined);
|
|
||||||
if (roleIds.length !== roleNames.length) {
|
|
||||||
this.logger.warn(`Seed assignment for ${name} names a role which does not exist`);
|
|
||||||
}
|
|
||||||
const user = this.controller.users.getOrCreateUser(name);
|
|
||||||
user.set("roleIds", new Set([...user.roleIds, ...roleIds]));
|
|
||||||
this.controller.userPermissionsUpdated(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.info(`Seeded ${seedRoles.length} roles and ${Object.keys(seedAssignments).length} assignments`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find or create the clusterio role for a seed role, returns undefined if it has no role to use. */
|
/** Find or create the clusterio role for a seed role, returns undefined if it has no role to use. */
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export const plugin: lib.PluginDeclaration = {
|
|||||||
|
|
||||||
messages.RoleListRequest,
|
messages.RoleListRequest,
|
||||||
messages.RoleMetaUpdateRequest,
|
messages.RoleMetaUpdateRequest,
|
||||||
|
messages.SeedRolesRequest,
|
||||||
|
|
||||||
messages.AssignmentListRequest,
|
messages.AssignmentListRequest,
|
||||||
messages.AssignmentUpdateRequest,
|
messages.AssignmentUpdateRequest,
|
||||||
|
|||||||
+12
-11
@@ -51,11 +51,6 @@ export class RoleMetaRecord {
|
|||||||
public tag: string = "",
|
public tag: string = "",
|
||||||
/** Colour used for the role in game, null uses the default colour. */
|
/** Colour used for the role in game, null uses the default colour. */
|
||||||
public color: RoleColor | null = null,
|
public color: RoleColor | null = null,
|
||||||
/**
|
|
||||||
* Factorio permission group players are moved into while this is their
|
|
||||||
* most privileged role which names one, empty leaves the group alone.
|
|
||||||
*/
|
|
||||||
public permissionGroup: string = "",
|
|
||||||
/** Online time after which this role is granted, null never grants it. */
|
/** Online time after which this role is granted, null never grants it. */
|
||||||
public autoAssignOnlineTimeMs: number | null = null,
|
public autoAssignOnlineTimeMs: number | null = null,
|
||||||
/** When true holders of this role are never granted roles automatically. */
|
/** When true holders of this role are never granted roles automatically. */
|
||||||
@@ -71,7 +66,6 @@ export class RoleMetaRecord {
|
|||||||
short_hand: Type.Optional(Type.String()),
|
short_hand: Type.Optional(Type.String()),
|
||||||
tag: Type.Optional(Type.String()),
|
tag: Type.Optional(Type.String()),
|
||||||
color: Type.Optional(Type.Union([RoleColor.jsonSchema, Type.Null()])),
|
color: Type.Optional(Type.Union([RoleColor.jsonSchema, Type.Null()])),
|
||||||
permission_group: Type.Optional(Type.String()),
|
|
||||||
auto_assign_online_time_ms: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
|
auto_assign_online_time_ms: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
|
||||||
block_auto_assign: Type.Optional(Type.Boolean()),
|
block_auto_assign: Type.Optional(Type.Boolean()),
|
||||||
updated_at_ms: Type.Optional(Type.Number()),
|
updated_at_ms: Type.Optional(Type.Number()),
|
||||||
@@ -100,10 +94,6 @@ export class RoleMetaRecord {
|
|||||||
json.color = this.color.toJSON();
|
json.color = this.color.toJSON();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.permissionGroup) {
|
|
||||||
json.permission_group = this.permissionGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.autoAssignOnlineTimeMs !== null) {
|
if (this.autoAssignOnlineTimeMs !== null) {
|
||||||
json.auto_assign_online_time_ms = this.autoAssignOnlineTimeMs;
|
json.auto_assign_online_time_ms = this.autoAssignOnlineTimeMs;
|
||||||
}
|
}
|
||||||
@@ -131,7 +121,6 @@ export class RoleMetaRecord {
|
|||||||
json.short_hand ?? "",
|
json.short_hand ?? "",
|
||||||
json.tag ?? "",
|
json.tag ?? "",
|
||||||
json.color ? RoleColor.fromJSON(json.color) : null,
|
json.color ? RoleColor.fromJSON(json.color) : null,
|
||||||
json.permission_group ?? "",
|
|
||||||
json.auto_assign_online_time_ms ?? null,
|
json.auto_assign_online_time_ms ?? null,
|
||||||
json.block_auto_assign ?? false,
|
json.block_auto_assign ?? false,
|
||||||
json.updated_at_ms ?? 0,
|
json.updated_at_ms ?? 0,
|
||||||
@@ -376,6 +365,18 @@ export class RoleMetaUpdateRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Create the roles the scenario shipped with, see seed.ts. */
|
||||||
|
export class SeedRolesRequest {
|
||||||
|
declare ["constructor"]: typeof SeedRolesRequest;
|
||||||
|
static plugin = "exp_roles" as const;
|
||||||
|
static type = "request" as const;
|
||||||
|
static src = "control" as const;
|
||||||
|
static dst = "controller" as const;
|
||||||
|
static permission = "core.role.create" as const;
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Assignment requests
|
Assignment requests
|
||||||
*/
|
*/
|
||||||
|
|||||||
+212
-334
@@ -6,19 +6,20 @@ the controller. This module keeps a copy of that state and answers permission
|
|||||||
checks from it, so a check such as `Roles.player_has_permission(player,
|
checks from it, so a check such as `Roles.player_has_permission(player,
|
||||||
"exp_scenario.command.kill")` is answered from the same data the web ui shows.
|
"exp_scenario.command.kill")` is answered from the same data the web ui shows.
|
||||||
|
|
||||||
Assignments made in game are applied locally first and then sent to the
|
Roles are objects, looked up by their clusterio id, and everything done to or
|
||||||
controller, which avoids callers having to deal with the round trip. A local
|
with a role is a method on it. Assignments made in game are applied locally
|
||||||
assignment which is never sent, such as one earned from time on this map, can be
|
first and then sent to the controller, which avoids callers having to deal with
|
||||||
made with `assign_player_local`.
|
the round trip.
|
||||||
|
|
||||||
Only the roles with the highest priority a player holds are considered. Jail
|
Only the roles with the highest priority a player holds apply. Jail sits above
|
||||||
sits above every other role, so holding it suppresses the rest, including the
|
every other role, so holding it suppresses the rest, including the default role
|
||||||
default role every player has.
|
every player has.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local clusterio_api = require("modules/clusterio/api")
|
local clusterio_api = require("modules/clusterio/api")
|
||||||
local compat = require("modules/clusterio/compat") --[[@as LibCompat]]
|
local compat = require("modules/clusterio/compat") --[[@as LibCompat]]
|
||||||
local Async = require("modules/exp_util/async")
|
local Async = require("modules/exp_util/async")
|
||||||
|
local Storage = require("modules/exp_util/storage")
|
||||||
|
|
||||||
--- @class ExpRoles
|
--- @class ExpRoles
|
||||||
local ExpRoles = {
|
local ExpRoles = {
|
||||||
@@ -34,8 +35,8 @@ local ExpRoles = {
|
|||||||
--- @class EventData.ExpRoles.on_player_roles_changed : EventData
|
--- @class EventData.ExpRoles.on_player_roles_changed : EventData
|
||||||
--- @field player_index uint
|
--- @field player_index uint
|
||||||
--- @field by_player_index uint 0 when the change did not come from a player
|
--- @field by_player_index uint 0 when the change did not come from a player
|
||||||
--- @field assigned string[] Names of the roles which were assigned
|
--- @field assigned number[] Ids of the roles which were assigned
|
||||||
--- @field unassigned string[] Names of the roles which were unassigned
|
--- @field unassigned number[] Ids of the roles which were unassigned
|
||||||
|
|
||||||
--- Methods shared by every role, kept apart from the fields so that defining
|
--- Methods shared by every role, kept apart from the fields so that defining
|
||||||
--- them does not count as injecting fields into the role itself
|
--- them does not count as injecting fields into the role itself
|
||||||
@@ -43,19 +44,25 @@ local ExpRoles = {
|
|||||||
local Role = {}
|
local Role = {}
|
||||||
ExpRoles._prototype = Role
|
ExpRoles._prototype = Role
|
||||||
|
|
||||||
|
--- Registered so roles keep their methods across save and load
|
||||||
|
local role_metatable = Storage.register_metatable("Role", { __index = Role })
|
||||||
|
|
||||||
--- @class ExpRoles.Role : ExpRoles.RolePrototype
|
--- @class ExpRoles.Role : ExpRoles.RolePrototype
|
||||||
--- @field id number Clusterio role id
|
--- @field id number Clusterio role id
|
||||||
--- @field name string
|
--- @field name string
|
||||||
--- @field short_hand string
|
--- @field short_hand string
|
||||||
--- @field order number Order given by the controller, which can have gaps
|
--- @field order number Position given by the controller, compare roles with the methods rather than this
|
||||||
--- @field index number Position within the role order, with no gaps
|
|
||||||
--- @field priority number Only the highest priority roles a player holds apply
|
--- @field priority number Only the highest priority roles a player holds apply
|
||||||
--- @field tag string
|
--- @field tag string
|
||||||
--- @field color Color?
|
--- @field color Color?
|
||||||
--- @field permissions table<string, true> Permission names granted by this role
|
--- @field permissions table<string, true> Permission names granted by this role
|
||||||
--- @field permission_group string? Factorio permission group holders are moved to
|
|
||||||
--- @field block_auto_assign boolean
|
--- @field block_auto_assign boolean
|
||||||
|
|
||||||
|
--- @class ExpRoles.AssignOptions
|
||||||
|
--- @field by_player_name string? Shown in the game message, defaults to the current player or the server
|
||||||
|
--- @field silent boolean? When true no game message is printed
|
||||||
|
--- @field local_only boolean? When true the controller is not told, for roles earned on this map only
|
||||||
|
|
||||||
--- @class ExpRoles.ScriptData
|
--- @class ExpRoles.ScriptData
|
||||||
--- @field roles table<number, ExpRoles.Role> Roles indexed by clusterio id
|
--- @field roles table<number, ExpRoles.Role> Roles indexed by clusterio id
|
||||||
--- @field synced_players table<string, number[]> Role ids from the controller
|
--- @field synced_players table<string, number[]> Role ids from the controller
|
||||||
@@ -65,49 +72,13 @@ ExpRoles._prototype = Role
|
|||||||
--- @field emit_updates boolean
|
--- @field emit_updates boolean
|
||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
--- Roles in order, the most privileged first, rebuilt from script data
|
|
||||||
local ordered_roles = {} --- @type ExpRoles.Role[]
|
|
||||||
--- Roles indexed by name, rebuilt from script data
|
|
||||||
local roles_by_name = {} --- @type table<string, ExpRoles.Role>
|
|
||||||
--- Async handlers run when the state of a permission may have changed
|
--- Async handlers run when the state of a permission may have changed
|
||||||
local permission_triggers = {} --- @type table<string, Async.AsyncFunction>
|
local permission_triggers = {} --- @type table<string, Async.AsyncFunction>
|
||||||
|
|
||||||
--- Move a player into a permission group, done async because the game does not
|
|
||||||
--- allow it from within every event
|
|
||||||
local set_permission_group_async = Async.register(function(player, group)
|
|
||||||
--- @cast player LuaPlayer
|
|
||||||
--- @cast group LuaPermissionGroup
|
|
||||||
if player.valid and group.valid then
|
|
||||||
group.add_player(player)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
Role state
|
Role state
|
||||||
]]
|
]]
|
||||||
|
|
||||||
--- Rebuild the ordered and by name views of the roles
|
|
||||||
local function rebuild_role_views()
|
|
||||||
local roles = {}
|
|
||||||
for _, role in pairs(script_data.roles) do
|
|
||||||
roles[#roles + 1] = role
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Sorted on the order given by the controller, which can have gaps, the
|
|
||||||
-- index each role is given is its position within that order
|
|
||||||
table.sort(roles, function(a, b)
|
|
||||||
if a.order == b.order then return a.id < b.id end
|
|
||||||
return a.order < b.order
|
|
||||||
end)
|
|
||||||
|
|
||||||
ordered_roles, roles_by_name = {}, {}
|
|
||||||
for index, role in ipairs(roles) do
|
|
||||||
role.index = index
|
|
||||||
ordered_roles[index] = role
|
|
||||||
roles_by_name[role.name] = role
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Build a role from the record sent by the controller
|
--- Build a role from the record sent by the controller
|
||||||
--- @param record table
|
--- @param record table
|
||||||
--- @param names string[]? When given, record.permissions holds indexes into it
|
--- @param names string[]? When given, record.permissions holds indexes into it
|
||||||
@@ -126,33 +97,37 @@ local function decode_role(record, names)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local group = meta.permission_group
|
|
||||||
if group == "" then group = nil end
|
|
||||||
|
|
||||||
--- @type ExpRoles.Role
|
--- @type ExpRoles.Role
|
||||||
return setmetatable({
|
return setmetatable({
|
||||||
id = record.id,
|
id = record.id,
|
||||||
name = record.name,
|
name = record.name,
|
||||||
short_hand = meta.short_hand or record.name,
|
short_hand = meta.short_hand or record.name,
|
||||||
order = meta.order or record.id,
|
order = meta.order or record.id,
|
||||||
index = meta.order or record.id,
|
|
||||||
priority = meta.priority or 0,
|
priority = meta.priority or 0,
|
||||||
tag = meta.tag or "",
|
tag = meta.tag or "",
|
||||||
color = meta.color,
|
color = meta.color,
|
||||||
permissions = permissions,
|
permissions = permissions,
|
||||||
permission_group = group,
|
|
||||||
block_auto_assign = meta.block_auto_assign or false,
|
block_auto_assign = meta.block_auto_assign or false,
|
||||||
}, { __index = ExpRoles._prototype })
|
}, role_metatable)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Sort roles in place, the most privileged first
|
||||||
|
--- @param roles ExpRoles.Role[]
|
||||||
|
--- @return ExpRoles.Role[]
|
||||||
|
local function sort_roles(roles)
|
||||||
|
table.sort(roles, function(a, b)
|
||||||
|
if a.order == b.order then return a.id < b.id end
|
||||||
|
return a.order < b.order
|
||||||
|
end)
|
||||||
|
return roles
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Get the name of a player from a player or a name, nil for the server
|
|
||||||
--- The server is represented by nil, or by a player object with index 0
|
--- The server is represented by nil, or by a player object with index 0
|
||||||
--- @param player LuaPlayer | string | nil
|
--- @param player LuaPlayer?
|
||||||
--- @return string?
|
--- @return LuaPlayer? # The player when it is not the server
|
||||||
local function player_name_of(player)
|
local function not_server(player)
|
||||||
if type(player) == "string" then return player end
|
if player == nil or player.index == 0 then return nil end
|
||||||
if player and player.index ~= 0 then return player.name end
|
return player
|
||||||
return nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Role ids a player has been given, without the default role or priority applied
|
--- Role ids a player has been given, without the default role or priority applied
|
||||||
@@ -171,59 +146,75 @@ local function get_held_role_ids(player_name)
|
|||||||
return role_ids
|
return role_ids
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Role ids which apply to a player, with the default role and priority applied
|
--- Role ids a player has been given, as a set
|
||||||
--- @param player_name string
|
--- @param player_name string
|
||||||
--- @return table<number, true>
|
--- @return table<number, true>
|
||||||
local function get_effective_role_ids(player_name)
|
local function get_held_role_set(player_name)
|
||||||
|
local rtn = {}
|
||||||
|
for _, role_id in pairs(get_held_role_ids(player_name)) do
|
||||||
|
rtn[role_id] = true
|
||||||
|
end
|
||||||
|
return rtn
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Roles which apply to a player, with the default role and priority applied
|
||||||
|
--- @param player_name string
|
||||||
|
--- @return ExpRoles.Role[]
|
||||||
|
local function get_effective_roles(player_name)
|
||||||
local role_ids = get_held_role_ids(player_name)
|
local role_ids = get_held_role_ids(player_name)
|
||||||
if script_data.default_role_id then
|
if script_data.default_role_id then
|
||||||
role_ids[#role_ids + 1] = script_data.default_role_id
|
role_ids[#role_ids + 1] = script_data.default_role_id
|
||||||
end
|
end
|
||||||
|
|
||||||
local highest_priority
|
local roles, highest_priority = {}, nil
|
||||||
for _, role_id in pairs(role_ids) do
|
for _, role_id in pairs(role_ids) do
|
||||||
local role = script_data.roles[role_id]
|
local role = script_data.roles[role_id]
|
||||||
if role and (highest_priority == nil or role.priority > highest_priority) then
|
if role then
|
||||||
|
roles[#roles + 1] = role
|
||||||
|
if highest_priority == nil or role.priority > highest_priority then
|
||||||
highest_priority = role.priority
|
highest_priority = role.priority
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local rtn = {}
|
local rtn = {}
|
||||||
for _, role_id in pairs(role_ids) do
|
for _, role in pairs(roles) do
|
||||||
local role = script_data.roles[role_id]
|
if role.priority == highest_priority then
|
||||||
if role and role.priority == highest_priority then
|
rtn[#rtn + 1] = role
|
||||||
rtn[role_id] = true
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return rtn
|
return sort_roles(rtn)
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
Role lookup
|
Role lookup
|
||||||
]]
|
]]
|
||||||
|
|
||||||
--- Get a role from its name, its clusterio id, or a role
|
--- Get a role from its clusterio id
|
||||||
--- @param any string | number | ExpRoles.Role
|
--- @param role_id number
|
||||||
--- @return ExpRoles.Role?
|
--- @return ExpRoles.Role?
|
||||||
function ExpRoles.get_role(any)
|
function ExpRoles.get_role(role_id)
|
||||||
local t_any = type(any)
|
return script_data.roles[role_id]
|
||||||
if t_any == "string" then
|
|
||||||
return roles_by_name[any]
|
|
||||||
elseif t_any == "number" then
|
|
||||||
return script_data.roles[any]
|
|
||||||
elseif t_any == "table" then
|
|
||||||
return script_data.roles[any.id]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Get every role in order, the most privileged first
|
--- Get a role from its name, roles should be referred to by id where possible
|
||||||
|
--- @param name string
|
||||||
|
--- @return ExpRoles.Role?
|
||||||
|
function ExpRoles.get_role_by_name(name)
|
||||||
|
for _, role in pairs(script_data.roles) do
|
||||||
|
if role.name == name then return role end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get every role, the most privileged first
|
||||||
--- @return ExpRoles.Role[]
|
--- @return ExpRoles.Role[]
|
||||||
function ExpRoles.get_roles()
|
function ExpRoles.get_roles()
|
||||||
local rtn = {}
|
local rtn = {}
|
||||||
for index, role in ipairs(ordered_roles) do
|
for _, role in pairs(script_data.roles) do
|
||||||
rtn[index] = role
|
rtn[#rtn + 1] = role
|
||||||
end
|
end
|
||||||
return rtn
|
return sort_roles(rtn)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Get the role every player holds
|
--- Get the role every player holds
|
||||||
@@ -232,43 +223,60 @@ function ExpRoles.get_default_role()
|
|||||||
return script_data.default_role_id and script_data.roles[script_data.default_role_id] or nil
|
return script_data.default_role_id and script_data.roles[script_data.default_role_id] or nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Get a role and every role more privileged than it, the default role excluded
|
||||||
|
--- @param role ExpRoles.Role
|
||||||
|
--- @return ExpRoles.Role[]
|
||||||
|
function ExpRoles.get_higher_roles(role)
|
||||||
|
local rtn = {}
|
||||||
|
for _, other in pairs(script_data.roles) do
|
||||||
|
if not other:is_lower_than(role) and other.id ~= script_data.default_role_id then
|
||||||
|
rtn[#rtn + 1] = other
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return sort_roles(rtn)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get a role and every role less privileged than it, the default role excluded
|
||||||
|
--- @param role ExpRoles.Role
|
||||||
|
--- @return ExpRoles.Role[]
|
||||||
|
function ExpRoles.get_lower_roles(role)
|
||||||
|
local rtn = {}
|
||||||
|
for _, other in pairs(script_data.roles) do
|
||||||
|
if not other:is_higher_than(role) and other.id ~= script_data.default_role_id then
|
||||||
|
rtn[#rtn + 1] = other
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return sort_roles(rtn)
|
||||||
|
end
|
||||||
|
|
||||||
--- Role used when there is no player, such as for commands run by the server
|
--- Role used when there is no player, such as for commands run by the server
|
||||||
--- It has core.admin so it passes every permission check
|
--- It has core.admin so it passes every permission check
|
||||||
local server_role = setmetatable({
|
local server_role = setmetatable({
|
||||||
id = -1,
|
id = -1,
|
||||||
name = "<server>",
|
name = "<server>",
|
||||||
short_hand = "SRV",
|
short_hand = "SRV",
|
||||||
order = 0,
|
order = -math.huge,
|
||||||
index = 0,
|
|
||||||
priority = 0,
|
priority = 0,
|
||||||
tag = "",
|
tag = "",
|
||||||
color = nil,
|
color = nil,
|
||||||
permissions = { ["core.admin"] = true },
|
permissions = { ["core.admin"] = true },
|
||||||
permission_group = nil,
|
|
||||||
block_auto_assign = true,
|
block_auto_assign = true,
|
||||||
}, { __index = ExpRoles._prototype })
|
}, role_metatable)
|
||||||
|
|
||||||
--- Get the roles which apply to a player, including the default role
|
--- Get the roles which apply to a player, including the default role
|
||||||
--- Only the roles with the highest priority are returned, which lets a role
|
--- Only the roles with the highest priority are returned, which lets a role
|
||||||
--- such as Jail suppress every other role a player holds
|
--- such as Jail suppress every other role a player holds
|
||||||
--- @param player LuaPlayer | string | nil
|
--- @param player LuaPlayer? nil for the server
|
||||||
--- @return ExpRoles.Role[]
|
--- @return ExpRoles.Role[]
|
||||||
function ExpRoles.get_player_roles(player)
|
function ExpRoles.get_player_roles(player)
|
||||||
local player_name = player_name_of(player)
|
|
||||||
-- The server is not a player and is allowed to do anything
|
-- The server is not a player and is allowed to do anything
|
||||||
if player_name == nil then return { server_role } end
|
local valid = not_server(player)
|
||||||
|
if not valid then return { server_role } end
|
||||||
local rtn = {}
|
return get_effective_roles(valid.name)
|
||||||
for role_id in pairs(get_effective_role_ids(player_name)) do
|
|
||||||
rtn[#rtn + 1] = script_data.roles[role_id]
|
|
||||||
end
|
|
||||||
|
|
||||||
table.sort(rtn, function(a, b) return a.index < b.index end)
|
|
||||||
return rtn
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Get the most privileged role which applies to a player
|
--- Get the most privileged role which applies to a player
|
||||||
--- @param player LuaPlayer | string | nil
|
--- @param player LuaPlayer? nil for the server
|
||||||
--- @return ExpRoles.Role
|
--- @return ExpRoles.Role
|
||||||
function ExpRoles.get_player_highest_role(player)
|
function ExpRoles.get_player_highest_role(player)
|
||||||
local role = ExpRoles.get_player_roles(player)[1]
|
local role = ExpRoles.get_player_roles(player)[1]
|
||||||
@@ -280,7 +288,7 @@ end
|
|||||||
]]
|
]]
|
||||||
|
|
||||||
--- Check if a player has a permission through any of their roles
|
--- Check if a player has a permission through any of their roles
|
||||||
--- @param player LuaPlayer | string | nil
|
--- @param player LuaPlayer? nil for the server
|
||||||
--- @param permission string A clusterio permission such as `exp_scenario.command.kill`
|
--- @param permission string A clusterio permission such as `exp_scenario.command.kill`
|
||||||
--- @return boolean
|
--- @return boolean
|
||||||
function ExpRoles.player_has_permission(player, permission)
|
function ExpRoles.player_has_permission(player, permission)
|
||||||
@@ -294,49 +302,21 @@ function ExpRoles.player_has_permission(player, permission)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Check if a role applies to a player
|
|
||||||
--- @param player LuaPlayer | string | nil
|
|
||||||
--- @param search_role string | number | ExpRoles.Role
|
|
||||||
--- @return boolean
|
|
||||||
function ExpRoles.player_has_role(player, search_role)
|
|
||||||
local role = ExpRoles.get_role(search_role)
|
|
||||||
if not role then return false end
|
|
||||||
|
|
||||||
for _, player_role in pairs(ExpRoles.get_player_roles(player)) do
|
|
||||||
if player_role.id == role.id then return true end
|
|
||||||
end
|
|
||||||
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Check if a player is more privileged than a role
|
|
||||||
--- A player with core.admin, which includes the server, outranks every role
|
|
||||||
--- @param player LuaPlayer | string | nil
|
|
||||||
--- @param role string | number | ExpRoles.Role
|
|
||||||
--- @return boolean
|
|
||||||
function ExpRoles.player_outranks_role(player, role)
|
|
||||||
local resolved = ExpRoles.get_role(role)
|
|
||||||
if not resolved then return false end
|
|
||||||
if ExpRoles.player_has_permission(player, "core.admin") then return true end
|
|
||||||
local highest = ExpRoles.get_player_roles(player)[1]
|
|
||||||
return highest ~= nil and highest.index < resolved.index
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Check if a player is more privileged than another player
|
--- Check if a player is more privileged than another player
|
||||||
--- A player with core.admin, which includes the server, outranks every player
|
--- A player with core.admin, which includes the server, outranks every player
|
||||||
--- @param player LuaPlayer | string | nil
|
--- @param player LuaPlayer? nil for the server
|
||||||
--- @param other LuaPlayer | string | nil
|
--- @param other LuaPlayer? nil for the server
|
||||||
--- @return boolean
|
--- @return boolean
|
||||||
function ExpRoles.player_outranks(player, other)
|
function ExpRoles.player_outranks(player, other)
|
||||||
if ExpRoles.player_has_permission(player, "core.admin") then return true end
|
if ExpRoles.player_has_permission(player, "core.admin") then return true end
|
||||||
local highest = ExpRoles.get_player_roles(player)[1]
|
local highest = ExpRoles.get_player_roles(player)[1]
|
||||||
local other_highest = ExpRoles.get_player_roles(other)[1]
|
local other_highest = ExpRoles.get_player_roles(other)[1]
|
||||||
if highest == nil then return false end
|
if highest == nil then return false end
|
||||||
return other_highest == nil or highest.index < other_highest.index
|
return other_highest == nil or highest:is_higher_than(other_highest)
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
Role prototype
|
Role methods
|
||||||
]]
|
]]
|
||||||
|
|
||||||
--- Check if this role grants a permission
|
--- Check if this role grants a permission
|
||||||
@@ -348,6 +328,35 @@ function Role.has_permission(self, permission)
|
|||||||
return permissions["core.admin"] or permissions[permission] or false
|
return permissions["core.admin"] or permissions[permission] or false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Check if this role is more privileged than another
|
||||||
|
--- @param self ExpRoles.Role
|
||||||
|
--- @param other ExpRoles.Role
|
||||||
|
--- @return boolean
|
||||||
|
function Role.is_higher_than(self, other)
|
||||||
|
if self.order == other.order then return self.id < other.id end
|
||||||
|
return self.order < other.order
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check if this role is less privileged than another
|
||||||
|
--- @param self ExpRoles.Role
|
||||||
|
--- @param other ExpRoles.Role
|
||||||
|
--- @return boolean
|
||||||
|
function Role.is_lower_than(self, other)
|
||||||
|
return other:is_higher_than(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check if a player has been given this role, or it is the default role
|
||||||
|
--- A role a player holds does not always apply, see ExpRoles.get_player_roles
|
||||||
|
--- @param self ExpRoles.Role
|
||||||
|
--- @param player LuaPlayer? nil for the server
|
||||||
|
--- @return boolean
|
||||||
|
function Role.has_player(self, player)
|
||||||
|
local valid = not_server(player)
|
||||||
|
if not valid then return self == server_role end
|
||||||
|
if self.id == script_data.default_role_id then return true end
|
||||||
|
return get_held_role_set(valid.name)[self.id] == true
|
||||||
|
end
|
||||||
|
|
||||||
--- Get the names of every player who has been given this role
|
--- Get the names of every player who has been given this role
|
||||||
--- This includes players who have never joined this map, and is not affected
|
--- This includes players who have never joined this map, and is not affected
|
||||||
--- by priority, so a jailed moderator is still listed under moderator
|
--- by priority, so a jailed moderator is still listed under moderator
|
||||||
@@ -401,56 +410,6 @@ function Role.print(self, message)
|
|||||||
return #players
|
return #players
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
|
||||||
Printing to roles
|
|
||||||
]]
|
|
||||||
|
|
||||||
--- Print a message to every player holding one of the given roles
|
|
||||||
--- @param roles (string | number | ExpRoles.Role)[]
|
|
||||||
--- @param message LocalisedString
|
|
||||||
function ExpRoles.print_to_roles(roles, message)
|
|
||||||
for _, role in pairs(roles) do
|
|
||||||
local resolved = ExpRoles.get_role(role)
|
|
||||||
if resolved then resolved:print(message) end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Print a message to every player holding the given role or a more privileged one
|
|
||||||
--- The default role is never included
|
|
||||||
--- @param role string | number | ExpRoles.Role
|
|
||||||
--- @param message LocalisedString
|
|
||||||
function ExpRoles.print_to_roles_higher(role, message)
|
|
||||||
local resolved = ExpRoles.get_role(role)
|
|
||||||
if not resolved then return end
|
|
||||||
|
|
||||||
local roles = {}
|
|
||||||
for _, other in ipairs(ordered_roles) do
|
|
||||||
if other.index <= resolved.index and other.id ~= script_data.default_role_id then
|
|
||||||
roles[#roles + 1] = other
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
ExpRoles.print_to_roles(roles, message)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Print a message to every player holding the given role or a less privileged one
|
|
||||||
--- The default role is never included
|
|
||||||
--- @param role string | number | ExpRoles.Role
|
|
||||||
--- @param message LocalisedString
|
|
||||||
function ExpRoles.print_to_roles_lower(role, message)
|
|
||||||
local resolved = ExpRoles.get_role(role)
|
|
||||||
if not resolved then return end
|
|
||||||
|
|
||||||
local roles = {}
|
|
||||||
for _, other in ipairs(ordered_roles) do
|
|
||||||
if other.index >= resolved.index and other.id ~= script_data.default_role_id then
|
|
||||||
roles[#roles + 1] = other
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
ExpRoles.print_to_roles(roles, message)
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
Permission triggers
|
Permission triggers
|
||||||
]]
|
]]
|
||||||
@@ -463,23 +422,12 @@ function ExpRoles.define_permission_trigger(permission, callback)
|
|||||||
permission_triggers[permission] = Async.register(callback)
|
permission_triggers[permission] = Async.register(callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Run every permission trigger for a player and move them into the permission
|
--- Run every permission trigger for a player
|
||||||
--- group of their most privileged role which names one
|
|
||||||
--- @param player LuaPlayer
|
--- @param player LuaPlayer
|
||||||
local function apply_player_state(player)
|
local function apply_permission_triggers(player)
|
||||||
for permission, async_function in pairs(permission_triggers) do
|
for permission, async_function in pairs(permission_triggers) do
|
||||||
async_function(player, ExpRoles.player_has_permission(player, permission))
|
async_function(player, ExpRoles.player_has_permission(player, permission))
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, role in ipairs(ExpRoles.get_player_roles(player)) do
|
|
||||||
if role.permission_group then
|
|
||||||
local group = game.permissions.get_group(role.permission_group)
|
|
||||||
if group and (not player.permission_group or player.permission_group.name ~= group.name) then
|
|
||||||
set_permission_group_async(player, group)
|
|
||||||
end
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
@@ -496,22 +444,27 @@ local function emit_player_roles_changed(player, assigned, unassigned, by_player
|
|||||||
by_player_name = by_player_name or (game.player and game.player.name) or "<server>"
|
by_player_name = by_player_name or (game.player and game.player.name) or "<server>"
|
||||||
local by_player = game.get_player(by_player_name)
|
local by_player = game.get_player(by_player_name)
|
||||||
|
|
||||||
|
local assigned_ids, unassigned_ids = {}, {}
|
||||||
local assigned_names, unassigned_names = {}, {}
|
local assigned_names, unassigned_names = {}, {}
|
||||||
for index, role in ipairs(assigned) do assigned_names[index] = role.name end
|
for index, role in ipairs(assigned) do
|
||||||
for index, role in ipairs(unassigned) do unassigned_names[index] = role.name end
|
assigned_ids[index], assigned_names[index] = role.id, role.name
|
||||||
|
end
|
||||||
|
for index, role in ipairs(unassigned) do
|
||||||
|
unassigned_ids[index], unassigned_names[index] = role.id, role.name
|
||||||
|
end
|
||||||
|
|
||||||
if not silent then
|
if not silent then
|
||||||
if #assigned_names > 0 then
|
if #assigned > 0 then
|
||||||
game.print{ "exp-roles.game-message-assign", player.name, table.concat(assigned_names, ", "), by_player_name }
|
game.print{ "exp-roles.game-message-assign", player.name, table.concat(assigned_names, ", "), by_player_name }
|
||||||
end
|
end
|
||||||
if #unassigned_names > 0 then
|
if #unassigned > 0 then
|
||||||
game.print{ "exp-roles.game-message-unassign", player.name, table.concat(unassigned_names, ", "), by_player_name }
|
game.print{ "exp-roles.game-message-unassign", player.name, table.concat(unassigned_names, ", "), by_player_name }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if #assigned_names > 0 then
|
if #assigned > 0 then
|
||||||
player.play_sound{ path = "utility/achievement_unlocked" }
|
player.play_sound{ path = "utility/achievement_unlocked" }
|
||||||
elseif #unassigned_names > 0 then
|
elseif #unassigned > 0 then
|
||||||
player.play_sound{ path = "utility/game_lost" }
|
player.play_sound{ path = "utility/game_lost" }
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -520,22 +473,11 @@ local function emit_player_roles_changed(player, assigned, unassigned, by_player
|
|||||||
tick = game.tick,
|
tick = game.tick,
|
||||||
player_index = player.index,
|
player_index = player.index,
|
||||||
by_player_index = by_player and by_player.index or 0,
|
by_player_index = by_player and by_player.index or 0,
|
||||||
assigned = assigned_names,
|
assigned = assigned_ids,
|
||||||
unassigned = unassigned_names,
|
unassigned = unassigned_ids,
|
||||||
})
|
})
|
||||||
|
|
||||||
apply_player_state(player)
|
apply_permission_triggers(player)
|
||||||
end
|
|
||||||
|
|
||||||
--- Role ids a player has been given, as a set
|
|
||||||
--- @param player_name string
|
|
||||||
--- @return table<number, true>
|
|
||||||
local function get_held_role_set(player_name)
|
|
||||||
local rtn = {}
|
|
||||||
for _, role_id in pairs(get_held_role_ids(player_name)) do
|
|
||||||
rtn[role_id] = true
|
|
||||||
end
|
|
||||||
return rtn
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Compare the roles a player had been given before and after a change and
|
--- Compare the roles a player had been given before and after a change and
|
||||||
@@ -558,9 +500,7 @@ local function emit_held_diff(player_name, before, by_player_name, silent)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if #assigned > 0 or #unassigned > 0 then
|
if #assigned > 0 or #unassigned > 0 then
|
||||||
table.sort(assigned, function(a, b) return a.index < b.index end)
|
emit_player_roles_changed(player, sort_roles(assigned), sort_roles(unassigned), by_player_name, silent)
|
||||||
table.sort(unassigned, function(a, b) return a.index < b.index end)
|
|
||||||
emit_player_roles_changed(player, assigned, unassigned, by_player_name, silent)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -591,56 +531,32 @@ local function add_role_id(list, role_id)
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Convert a role, role name, or array of either into an array of roles
|
--- Apply a role change to the local state, returns true when something changed
|
||||||
--- @param roles any
|
|
||||||
--- @return ExpRoles.Role[]
|
|
||||||
local function resolve_roles(roles)
|
|
||||||
if type(roles) ~= "table" or roles.name then
|
|
||||||
roles = { roles }
|
|
||||||
end
|
|
||||||
|
|
||||||
local rtn = {}
|
|
||||||
for _, role in pairs(roles) do
|
|
||||||
local resolved = ExpRoles.get_role(role)
|
|
||||||
if resolved then rtn[#rtn + 1] = resolved end
|
|
||||||
end
|
|
||||||
|
|
||||||
return rtn
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Apply a role change to the local state
|
|
||||||
--- @param player_name string
|
--- @param player_name string
|
||||||
--- @param roles ExpRoles.Role[]
|
--- @param role ExpRoles.Role
|
||||||
--- @param change_type "assign" | "unassign"
|
--- @param assign boolean
|
||||||
--- @param sync boolean
|
--- @param sync boolean
|
||||||
--- @return ExpRoles.Role[] # The roles which actually changed
|
--- @return boolean
|
||||||
local function apply_local_change(player_name, roles, change_type, sync)
|
local function apply_local_change(player_name, role, assign, sync)
|
||||||
local local_roles = script_data.local_players[player_name] or {}
|
local local_roles = script_data.local_players[player_name] or {}
|
||||||
local pending = script_data.pending[player_name] or {}
|
local pending = script_data.pending[player_name] or {}
|
||||||
local changed = {}
|
local changed
|
||||||
|
|
||||||
for _, role in pairs(roles) do
|
if assign then
|
||||||
if change_type == "assign" then
|
|
||||||
local synced = script_data.synced_players[player_name] or {}
|
local synced = script_data.synced_players[player_name] or {}
|
||||||
local already_synced = false
|
local already_synced = false
|
||||||
for _, role_id in pairs(synced) do
|
for _, role_id in pairs(synced) do
|
||||||
if role_id == role.id then already_synced = true break end
|
if role_id == role.id then already_synced = true break end
|
||||||
end
|
end
|
||||||
if not already_synced and add_role_id(local_roles, role.id) then
|
changed = not already_synced and add_role_id(local_roles, role.id)
|
||||||
changed[#changed + 1] = role
|
if changed and sync then add_role_id(pending, role.id) end
|
||||||
if sync then add_role_id(pending, role.id) end
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
local removed = remove_role_id(local_roles, role.id)
|
changed = remove_role_id(local_roles, role.id)
|
||||||
remove_role_id(pending, role.id)
|
remove_role_id(pending, role.id)
|
||||||
-- A synced role is only taken away here when the controller is
|
-- A synced role is only taken away here when the controller is
|
||||||
-- being told as well, otherwise the next update would restore it
|
-- being told as well, otherwise the next update would restore it
|
||||||
if sync then
|
if sync then
|
||||||
removed = remove_role_id(script_data.synced_players[player_name], role.id) or removed
|
changed = remove_role_id(script_data.synced_players[player_name], role.id) or changed
|
||||||
end
|
|
||||||
if removed then
|
|
||||||
changed[#changed + 1] = role
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -652,83 +568,53 @@ end
|
|||||||
|
|
||||||
--- Send a role change to the controller
|
--- Send a role change to the controller
|
||||||
--- @param player_name string
|
--- @param player_name string
|
||||||
--- @param roles ExpRoles.Role[]
|
--- @param role ExpRoles.Role
|
||||||
--- @param change_type "assign" | "unassign"
|
--- @param assign boolean
|
||||||
local function emit_assignment_update(player_name, roles, change_type)
|
local function emit_assignment_update(player_name, role, assign)
|
||||||
if not script_data.emit_updates then return end
|
if not script_data.emit_updates then return end
|
||||||
|
|
||||||
local role_ids = {}
|
|
||||||
for index, role in ipairs(roles) do
|
|
||||||
role_ids[index] = role.id
|
|
||||||
end
|
|
||||||
|
|
||||||
clusterio_api.send_json("exp_roles:assignment_update", {
|
clusterio_api.send_json("exp_roles:assignment_update", {
|
||||||
name = player_name,
|
name = player_name,
|
||||||
assign = change_type == "assign" and role_ids or nil,
|
assign = assign and { role.id } or nil,
|
||||||
unassign = change_type == "unassign" and role_ids or nil,
|
unassign = not assign and { role.id } or nil,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Change the roles a player holds
|
--- Change whether a player holds a role
|
||||||
--- @param player LuaPlayer | string
|
--- @param role ExpRoles.Role
|
||||||
--- @param roles any A role, role name, or array of either
|
--- @param player LuaPlayer
|
||||||
--- @param change_type "assign" | "unassign"
|
--- @param assign boolean
|
||||||
--- @param by_player_name string?
|
--- @param options ExpRoles.AssignOptions?
|
||||||
--- @param silent boolean?
|
local function change_player_role(role, player, assign, options)
|
||||||
--- @param sync boolean
|
local valid = not_server(player)
|
||||||
local function change_player_roles(player, roles, change_type, by_player_name, silent, sync)
|
if not valid then return end
|
||||||
local player_name = player_name_of(player)
|
options = options or {}
|
||||||
if not player_name then return end
|
|
||||||
|
|
||||||
local role_objects = resolve_roles(roles)
|
|
||||||
if #role_objects == 0 then return end
|
|
||||||
|
|
||||||
|
local player_name = valid.name
|
||||||
local before = get_held_role_set(player_name)
|
local before = get_held_role_set(player_name)
|
||||||
local changed = apply_local_change(player_name, role_objects, change_type, sync)
|
if not apply_local_change(player_name, role, assign, not options.local_only) then return end
|
||||||
if #changed == 0 then return end
|
|
||||||
|
|
||||||
if sync then
|
if not options.local_only then
|
||||||
emit_assignment_update(player_name, changed, change_type)
|
emit_assignment_update(player_name, role, assign)
|
||||||
end
|
end
|
||||||
|
|
||||||
emit_held_diff(player_name, before, by_player_name, silent)
|
emit_held_diff(player_name, before, options.by_player_name, options.silent)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Give a player one or more roles, the change is sent to the controller
|
--- Give a player this role, the change is sent to the controller unless local_only is set
|
||||||
--- @param player LuaPlayer | string
|
--- @param self ExpRoles.Role
|
||||||
--- @param roles any A role, role name, or array of either
|
--- @param player LuaPlayer
|
||||||
--- @param by_player_name string? Shown in the game message, defaults to the current player or the server
|
--- @param options ExpRoles.AssignOptions?
|
||||||
--- @param silent boolean? When true no game message is printed
|
function Role.assign(self, player, options)
|
||||||
function ExpRoles.assign_player(player, roles, by_player_name, silent)
|
change_player_role(self, player, true, options)
|
||||||
change_player_roles(player, roles, "assign", by_player_name, silent, true)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Take one or more roles from a player, the change is sent to the controller
|
--- Take this role from a player, the change is sent to the controller unless local_only is set
|
||||||
--- @param player LuaPlayer | string
|
--- @param self ExpRoles.Role
|
||||||
--- @param roles any A role, role name, or array of either
|
--- @param player LuaPlayer
|
||||||
--- @param by_player_name string? Shown in the game message, defaults to the current player or the server
|
--- @param options ExpRoles.AssignOptions?
|
||||||
--- @param silent boolean? When true no game message is printed
|
function Role.unassign(self, player, options)
|
||||||
function ExpRoles.unassign_player(player, roles, by_player_name, silent)
|
change_player_role(self, player, false, options)
|
||||||
change_player_roles(player, roles, "unassign", by_player_name, silent, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Give a player one or more roles on this map only, the controller is not told
|
|
||||||
--- Use this for roles earned from progress which does not leave this map
|
|
||||||
--- @param player LuaPlayer | string
|
|
||||||
--- @param roles any A role, role name, or array of either
|
|
||||||
--- @param by_player_name string? Shown in the game message, defaults to the current player or the server
|
|
||||||
--- @param silent boolean? When true no game message is printed
|
|
||||||
function ExpRoles.assign_player_local(player, roles, by_player_name, silent)
|
|
||||||
change_player_roles(player, roles, "assign", by_player_name, silent, false)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Take one or more local roles from a player, the controller is not told
|
|
||||||
--- @param player LuaPlayer | string
|
|
||||||
--- @param roles any A role, role name, or array of either
|
|
||||||
--- @param by_player_name string? Shown in the game message, defaults to the current player or the server
|
|
||||||
--- @param silent boolean? When true no game message is printed
|
|
||||||
function ExpRoles.unassign_player_local(player, roles, by_player_name, silent)
|
|
||||||
change_player_roles(player, roles, "unassign", by_player_name, silent, false)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
@@ -736,7 +622,7 @@ end
|
|||||||
]]
|
]]
|
||||||
|
|
||||||
--- Stop holding a pending role locally once the controller has confirmed it
|
--- Stop holding a pending role locally once the controller has confirmed it
|
||||||
--- Roles assigned with assign_player_local are never pending so are untouched
|
--- Roles assigned with local_only are never pending so are untouched
|
||||||
--- @param player_name string
|
--- @param player_name string
|
||||||
local function drop_confirmed_local(player_name)
|
local function drop_confirmed_local(player_name)
|
||||||
local pending = script_data.pending[player_name]
|
local pending = script_data.pending[player_name]
|
||||||
@@ -758,9 +644,6 @@ end
|
|||||||
--- Restore local references to persistent script data after load
|
--- Restore local references to persistent script data after load
|
||||||
function ExpRoles.on_load()
|
function ExpRoles.on_load()
|
||||||
script_data = compat.script_data["exp_roles"]
|
script_data = compat.script_data["exp_roles"]
|
||||||
if script_data then
|
|
||||||
rebuild_role_views()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Enable or disable sending role changes back to the controller
|
--- Enable or disable sending role changes back to the controller
|
||||||
@@ -769,7 +652,7 @@ function ExpRoles.set_emit_events(enabled)
|
|||||||
script_data.emit_updates = enabled ~= false
|
script_data.emit_updates = enabled ~= false
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Apply the state of every connected player, and raise the event so guis
|
--- Apply the triggers of every connected player, and raise the event so guis
|
||||||
--- can refresh, after the roles themselves have changed
|
--- can refresh, after the roles themselves have changed
|
||||||
local function roles_changed()
|
local function roles_changed()
|
||||||
for _, player in pairs(game.connected_players) do
|
for _, player in pairs(game.connected_players) do
|
||||||
@@ -818,9 +701,7 @@ function ExpRoles.initialise(payload)
|
|||||||
end
|
end
|
||||||
script_data.pending = {}
|
script_data.pending = {}
|
||||||
|
|
||||||
rebuild_role_views()
|
|
||||||
roles_changed()
|
roles_changed()
|
||||||
|
|
||||||
script_data.emit_updates = emit_updates
|
script_data.emit_updates = emit_updates
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -843,7 +724,6 @@ function ExpRoles.receive_role_updates(records)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
rebuild_role_views()
|
|
||||||
roles_changed()
|
roles_changed()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -868,17 +748,15 @@ end
|
|||||||
--- Roll back a local assignment which the controller rejected
|
--- Roll back a local assignment which the controller rejected
|
||||||
--- @param payload { name: string, role_ids: number[] }
|
--- @param payload { name: string, role_ids: number[] }
|
||||||
function ExpRoles.reject_assignment(payload)
|
function ExpRoles.reject_assignment(payload)
|
||||||
local player_name = payload.name
|
local player = game.get_player(payload.name)
|
||||||
local roles = {}
|
|
||||||
for _, role_id in pairs(payload.role_ids) do
|
for _, role_id in pairs(payload.role_ids) do
|
||||||
local role = script_data.roles[role_id]
|
local role = script_data.roles[role_id]
|
||||||
if role then roles[#roles + 1] = role end
|
if role and player then
|
||||||
|
role:unassign(player, { by_player_name = "<server>", silent = true, local_only = true })
|
||||||
|
elseif role then
|
||||||
|
apply_local_change(payload.name, role, false, false)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if #roles == 0 then return end
|
|
||||||
|
|
||||||
-- Sync is false, so this is never sent back to the controller
|
|
||||||
change_player_roles(player_name, roles, "unassign", "<server>", true, false)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Get the current script data for debugging purposes
|
--- Get the current script data for debugging purposes
|
||||||
@@ -908,11 +786,11 @@ function ExpRoles.on_server_startup()
|
|||||||
ExpRoles.on_load()
|
ExpRoles.on_load()
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Apply the permission triggers and group for a player who just joined
|
--- Apply the permission triggers for a player who just joined
|
||||||
--- @param event EventData.on_player_joined_game
|
--- @param event EventData.on_player_joined_game
|
||||||
function ExpRoles.on_player_joined_game(event)
|
function ExpRoles.on_player_joined_game(event)
|
||||||
local player = game.get_player(event.player_index)
|
local player = game.get_player(event.player_index)
|
||||||
if player then apply_player_state(player) end
|
if player then apply_permission_triggers(player) end
|
||||||
end
|
end
|
||||||
|
|
||||||
return ExpRoles
|
return ExpRoles
|
||||||
|
|||||||
+9
-75
@@ -1,8 +1,8 @@
|
|||||||
import { RoleColor } from "./messages";
|
import { RoleColor } from "./messages";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A role created the first time the plugin runs, as the scenario defined it
|
* A role created by the seed, as the scenario defined it before roles moved to
|
||||||
* before roles moved to the controller. `parent` stands in for the inheritance
|
* the controller. `parent` stands in for the inheritance
|
||||||
* the old config had and is flattened by the seed. The default and admin roles
|
* the old config had and is flattened by the seed. The default and admin roles
|
||||||
* already exist, so those entries only provide the in game properties.
|
* already exist, so those entries only provide the in game properties.
|
||||||
*/
|
*/
|
||||||
@@ -10,8 +10,6 @@ export interface SeedRole {
|
|||||||
name: string;
|
name: string;
|
||||||
shortHand: string;
|
shortHand: string;
|
||||||
color: RoleColor | null;
|
color: RoleColor | null;
|
||||||
/** Factorio permission group, see exp_legacy/module/config/expcore/permission_groups.lua */
|
|
||||||
permissionGroup: string;
|
|
||||||
priority?: number;
|
priority?: number;
|
||||||
autoAssignHours?: number;
|
autoAssignHours?: number;
|
||||||
blockAutoAssign?: boolean;
|
blockAutoAssign?: boolean;
|
||||||
@@ -24,16 +22,11 @@ export interface SeedRole {
|
|||||||
permissions: string[];
|
permissions: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const admin = ["exp_scenario.player.admin", "exp_scenario.player.spectator", "exp_scenario.bypass.reports"];
|
|
||||||
const instantRespawn = ["exp_scenario.player.instant_respawn"];
|
|
||||||
const trusted = ["exp_scenario.player.spectator", "exp_scenario.bypass.reports", ...instantRespawn];
|
|
||||||
|
|
||||||
export const seedRoles: SeedRole[] = [
|
export const seedRoles: SeedRole[] = [
|
||||||
{
|
{
|
||||||
name: "System",
|
name: "System",
|
||||||
shortHand: "SYS",
|
shortHand: "SYS",
|
||||||
color: null,
|
color: null,
|
||||||
permissionGroup: "Default",
|
|
||||||
isAdmin: true,
|
isAdmin: true,
|
||||||
permissions: [],
|
permissions: [],
|
||||||
},
|
},
|
||||||
@@ -41,11 +34,8 @@ export const seedRoles: SeedRole[] = [
|
|||||||
name: "Senior Administrator",
|
name: "Senior Administrator",
|
||||||
shortHand: "SAdmin",
|
shortHand: "SAdmin",
|
||||||
color: new RoleColor(233, 63, 233),
|
color: new RoleColor(233, 63, 233),
|
||||||
permissionGroup: "Admin",
|
|
||||||
parent: "Administrator",
|
parent: "Administrator",
|
||||||
permissions: [
|
permissions: [
|
||||||
...admin, ...instantRespawn,
|
|
||||||
"exp_scenario.player.system_commands",
|
|
||||||
"exp_scenario.command._rcon",
|
"exp_scenario.command._rcon",
|
||||||
"exp_scenario.command.debug",
|
"exp_scenario.command.debug",
|
||||||
"exp_scenario.command.set_cheat_mode",
|
"exp_scenario.command.set_cheat_mode",
|
||||||
@@ -56,10 +46,8 @@ export const seedRoles: SeedRole[] = [
|
|||||||
name: "Administrator",
|
name: "Administrator",
|
||||||
shortHand: "Admin",
|
shortHand: "Admin",
|
||||||
color: new RoleColor(233, 63, 233),
|
color: new RoleColor(233, 63, 233),
|
||||||
permissionGroup: "Admin",
|
|
||||||
parent: "Moderator",
|
parent: "Moderator",
|
||||||
permissions: [
|
permissions: [
|
||||||
...admin, ...instantRespawn,
|
|
||||||
"exp_scenario.gui.warp_list.bypass_proximity",
|
"exp_scenario.gui.warp_list.bypass_proximity",
|
||||||
"exp_scenario.gui.warp_list.bypass_cooldown",
|
"exp_scenario.gui.warp_list.bypass_cooldown",
|
||||||
"exp_scenario.command.connect_all",
|
"exp_scenario.command.connect_all",
|
||||||
@@ -69,10 +57,9 @@ export const seedRoles: SeedRole[] = [
|
|||||||
name: "Moderator",
|
name: "Moderator",
|
||||||
shortHand: "Mod",
|
shortHand: "Mod",
|
||||||
color: new RoleColor(0, 170, 0),
|
color: new RoleColor(0, 170, 0),
|
||||||
permissionGroup: "Admin",
|
|
||||||
parent: "Trainee",
|
parent: "Trainee",
|
||||||
permissions: [
|
permissions: [
|
||||||
...admin, ...instantRespawn,
|
"exp_scenario.player.instant_respawn",
|
||||||
"exp_scenario.command.assign_role",
|
"exp_scenario.command.assign_role",
|
||||||
"exp_scenario.command.unassign_role",
|
"exp_scenario.command.unassign_role",
|
||||||
"exp_scenario.command.repair",
|
"exp_scenario.command.repair",
|
||||||
@@ -107,10 +94,11 @@ export const seedRoles: SeedRole[] = [
|
|||||||
name: "Trainee",
|
name: "Trainee",
|
||||||
shortHand: "TrMod",
|
shortHand: "TrMod",
|
||||||
color: new RoleColor(0, 170, 0),
|
color: new RoleColor(0, 170, 0),
|
||||||
permissionGroup: "Admin",
|
|
||||||
parent: "Veteran",
|
parent: "Veteran",
|
||||||
permissions: [
|
permissions: [
|
||||||
...admin,
|
"exp_scenario.player.admin",
|
||||||
|
"exp_scenario.player.spectator",
|
||||||
|
"exp_scenario.bypass.reports",
|
||||||
"exp_scenario.command.admin_chat",
|
"exp_scenario.command.admin_chat",
|
||||||
"exp_scenario.command.goto",
|
"exp_scenario.command.goto",
|
||||||
"exp_scenario.command.teleport",
|
"exp_scenario.command.teleport",
|
||||||
@@ -139,10 +127,8 @@ export const seedRoles: SeedRole[] = [
|
|||||||
name: "Board Member",
|
name: "Board Member",
|
||||||
shortHand: "Board",
|
shortHand: "Board",
|
||||||
color: new RoleColor(247, 246, 54),
|
color: new RoleColor(247, 246, 54),
|
||||||
permissionGroup: "Trusted",
|
|
||||||
parent: "Sponsor",
|
parent: "Sponsor",
|
||||||
permissions: [
|
permissions: [
|
||||||
...trusted,
|
|
||||||
"exp_scenario.command.goto",
|
"exp_scenario.command.goto",
|
||||||
"exp_scenario.command.repair",
|
"exp_scenario.command.repair",
|
||||||
"exp_scenario.command.spectate",
|
"exp_scenario.command.spectate",
|
||||||
@@ -154,20 +140,17 @@ export const seedRoles: SeedRole[] = [
|
|||||||
name: "Senior Backer",
|
name: "Senior Backer",
|
||||||
shortHand: "Backer",
|
shortHand: "Backer",
|
||||||
color: new RoleColor(238, 172, 44),
|
color: new RoleColor(238, 172, 44),
|
||||||
permissionGroup: "Trusted",
|
|
||||||
parent: "Sponsor",
|
parent: "Sponsor",
|
||||||
permissions: [
|
permissions: [],
|
||||||
...trusted,
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Sponsor",
|
name: "Sponsor",
|
||||||
shortHand: "Spon",
|
shortHand: "Spon",
|
||||||
color: new RoleColor(238, 172, 44),
|
color: new RoleColor(238, 172, 44),
|
||||||
permissionGroup: "Trusted",
|
|
||||||
parent: "Supporter",
|
parent: "Supporter",
|
||||||
permissions: [
|
permissions: [
|
||||||
...trusted,
|
"exp_scenario.bypass.reports",
|
||||||
|
"exp_scenario.player.instant_respawn",
|
||||||
"exp_scenario.gui.rocket_info.toggle_active",
|
"exp_scenario.gui.rocket_info.toggle_active",
|
||||||
"exp_scenario.gui.rocket_info.remote_launch",
|
"exp_scenario.gui.rocket_info.remote_launch",
|
||||||
"exp_scenario.gui.bonus",
|
"exp_scenario.gui.bonus",
|
||||||
@@ -182,7 +165,6 @@ export const seedRoles: SeedRole[] = [
|
|||||||
name: "Supporter",
|
name: "Supporter",
|
||||||
shortHand: "Sup",
|
shortHand: "Sup",
|
||||||
color: new RoleColor(230, 99, 34),
|
color: new RoleColor(230, 99, 34),
|
||||||
permissionGroup: "Trusted",
|
|
||||||
parent: "Veteran",
|
parent: "Veteran",
|
||||||
permissions: [
|
permissions: [
|
||||||
"exp_scenario.player.spectator",
|
"exp_scenario.player.spectator",
|
||||||
@@ -197,7 +179,6 @@ export const seedRoles: SeedRole[] = [
|
|||||||
name: "Partner",
|
name: "Partner",
|
||||||
shortHand: "Part",
|
shortHand: "Part",
|
||||||
color: new RoleColor(140, 120, 200),
|
color: new RoleColor(140, 120, 200),
|
||||||
permissionGroup: "Trusted",
|
|
||||||
parent: "Veteran",
|
parent: "Veteran",
|
||||||
permissions: [
|
permissions: [
|
||||||
"exp_scenario.player.spectator",
|
"exp_scenario.player.spectator",
|
||||||
@@ -209,7 +190,6 @@ export const seedRoles: SeedRole[] = [
|
|||||||
name: "Veteran",
|
name: "Veteran",
|
||||||
shortHand: "Vet",
|
shortHand: "Vet",
|
||||||
color: new RoleColor(140, 120, 200),
|
color: new RoleColor(140, 120, 200),
|
||||||
permissionGroup: "Trusted",
|
|
||||||
parent: "Member",
|
parent: "Member",
|
||||||
autoAssignHours: 10,
|
autoAssignHours: 10,
|
||||||
permissions: [
|
permissions: [
|
||||||
@@ -223,7 +203,6 @@ export const seedRoles: SeedRole[] = [
|
|||||||
name: "Member",
|
name: "Member",
|
||||||
shortHand: "Mem",
|
shortHand: "Mem",
|
||||||
color: new RoleColor(24, 172, 188),
|
color: new RoleColor(24, 172, 188),
|
||||||
permissionGroup: "Standard",
|
|
||||||
parent: "Regular",
|
parent: "Regular",
|
||||||
permissions: [
|
permissions: [
|
||||||
"exp_scenario.bypass.deconstruction_log",
|
"exp_scenario.bypass.deconstruction_log",
|
||||||
@@ -245,7 +224,6 @@ export const seedRoles: SeedRole[] = [
|
|||||||
name: "Regular",
|
name: "Regular",
|
||||||
shortHand: "Reg",
|
shortHand: "Reg",
|
||||||
color: new RoleColor(79, 155, 163),
|
color: new RoleColor(79, 155, 163),
|
||||||
permissionGroup: "Standard",
|
|
||||||
autoAssignHours: 3,
|
autoAssignHours: 3,
|
||||||
permissions: [
|
permissions: [
|
||||||
"exp_scenario.command.kill",
|
"exp_scenario.command.kill",
|
||||||
@@ -261,7 +239,6 @@ export const seedRoles: SeedRole[] = [
|
|||||||
name: "Jail",
|
name: "Jail",
|
||||||
shortHand: "Jail",
|
shortHand: "Jail",
|
||||||
color: new RoleColor(50, 50, 50),
|
color: new RoleColor(50, 50, 50),
|
||||||
permissionGroup: "Restricted",
|
|
||||||
priority: 1,
|
priority: 1,
|
||||||
blockAutoAssign: true,
|
blockAutoAssign: true,
|
||||||
permissions: [],
|
permissions: [],
|
||||||
@@ -270,54 +247,11 @@ export const seedRoles: SeedRole[] = [
|
|||||||
name: "Guest",
|
name: "Guest",
|
||||||
shortHand: "",
|
shortHand: "",
|
||||||
color: new RoleColor(185, 187, 160),
|
color: new RoleColor(185, 187, 160),
|
||||||
permissionGroup: "Guest",
|
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
permissions: [],
|
permissions: [],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/** Players given roles the first time the plugin runs, by role name. */
|
|
||||||
export const seedAssignments: Record<string, string[]> = {
|
|
||||||
"PHIDIAS0303": ["Moderator", "Board Member", "Member"],
|
|
||||||
"aldldl": ["Administrator", "Moderator", "Member"],
|
|
||||||
"arty714": ["Senior Administrator", "Moderator", "Member"],
|
|
||||||
"Cooldude2606": ["Senior Administrator", "Moderator", "Member"],
|
|
||||||
"Drahc_pro": ["Administrator", "Moderator", "Member"],
|
|
||||||
"mark9064": ["Administrator", "Moderator", "Member"],
|
|
||||||
"7h3w1z4rd": ["Moderator", "Member"],
|
|
||||||
"FlipHalfling90": ["Moderator", "Member"],
|
|
||||||
"hamsterbryan": ["Moderator", "Member"],
|
|
||||||
"HunterOfGames": ["Moderator", "Member"],
|
|
||||||
"NextIdea": ["Moderator", "Member"],
|
|
||||||
"TheKernel32": ["Moderator", "Member"],
|
|
||||||
"TheKernel64": ["Moderator", "Member"],
|
|
||||||
"tovernaar123": ["Moderator", "Member"],
|
|
||||||
"UUBlueFire": ["Moderator", "Member"],
|
|
||||||
"AssemblyStorm": ["Moderator", "Member"],
|
|
||||||
"banakeg": ["Moderator", "Member"],
|
|
||||||
"connormkii": ["Moderator", "Member"],
|
|
||||||
"cydes": ["Moderator", "Member"],
|
|
||||||
"darklich14": ["Moderator", "Member"],
|
|
||||||
"facere": ["Moderator", "Member"],
|
|
||||||
"freek18": ["Moderator", "Member"],
|
|
||||||
"Gizan": ["Moderator", "Member"],
|
|
||||||
"LoicB": ["Moderator", "Member"],
|
|
||||||
"M74132": ["Moderator", "Member"],
|
|
||||||
"mafisch3": ["Moderator", "Member"],
|
|
||||||
"maplesyrup01": ["Moderator", "Member"],
|
|
||||||
"ookl": ["Moderator", "Member"],
|
|
||||||
"Phoenix27833": ["Moderator", "Member"],
|
|
||||||
"porelos": ["Moderator", "Member"],
|
|
||||||
"Ruuyji": ["Moderator", "Member"],
|
|
||||||
"samy115": ["Moderator", "Member"],
|
|
||||||
"SilentLog": ["Moderator", "Member"],
|
|
||||||
"Tcheko": ["Moderator", "Member"],
|
|
||||||
"thadius856": ["Moderator", "Member"],
|
|
||||||
"whoami32": ["Moderator", "Member"],
|
|
||||||
"Windbomb": ["Moderator", "Member"],
|
|
||||||
"XenoCyber": ["Moderator", "Member"],
|
|
||||||
};
|
|
||||||
|
|
||||||
/** The permissions a seed role grants, including those of its parents. */
|
/** The permissions a seed role grants, including those of its parents. */
|
||||||
export function flattenSeedPermissions(role: SeedRole, roles = seedRoles) {
|
export function flattenSeedPermissions(role: SeedRole, roles = seedRoles) {
|
||||||
const permissions = new Set<string>();
|
const permissions = new Set<string>();
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ type RoleFormValues = {
|
|||||||
shortHand: string;
|
shortHand: string;
|
||||||
tag: string;
|
tag: string;
|
||||||
color: string | { toRgb(): { r: number, g: number, b: number } } | null;
|
color: string | { toRgb(): { r: number, g: number, b: number } } | null;
|
||||||
permissionGroup: string;
|
|
||||||
autoAssignHours: number | null;
|
autoAssignHours: number | null;
|
||||||
blockAutoAssign: boolean;
|
blockAutoAssign: boolean;
|
||||||
};
|
};
|
||||||
@@ -47,7 +46,6 @@ export default function RoleProperties(props: { plugin: WebPlugin, role?: lib.Ro
|
|||||||
shortHand: meta.shortHand,
|
shortHand: meta.shortHand,
|
||||||
tag: meta.tag,
|
tag: meta.tag,
|
||||||
color: meta.color ? `rgb(${meta.color.r}, ${meta.color.g}, ${meta.color.b})` : null,
|
color: meta.color ? `rgb(${meta.color.r}, ${meta.color.g}, ${meta.color.b})` : null,
|
||||||
permissionGroup: meta.permissionGroup,
|
|
||||||
autoAssignHours: meta.autoAssignOnlineTimeMs === null
|
autoAssignHours: meta.autoAssignOnlineTimeMs === null
|
||||||
? null
|
? null
|
||||||
: meta.autoAssignOnlineTimeMs / MS_PER_HOUR,
|
: meta.autoAssignOnlineTimeMs / MS_PER_HOUR,
|
||||||
@@ -80,7 +78,6 @@ export default function RoleProperties(props: { plugin: WebPlugin, role?: lib.Ro
|
|||||||
values.shortHand ?? "",
|
values.shortHand ?? "",
|
||||||
values.tag ?? "",
|
values.tag ?? "",
|
||||||
color,
|
color,
|
||||||
values.permissionGroup ?? "",
|
|
||||||
values.autoAssignHours === null || values.autoAssignHours === undefined
|
values.autoAssignHours === null || values.autoAssignHours === undefined
|
||||||
? null
|
? null
|
||||||
: values.autoAssignHours * MS_PER_HOUR,
|
: values.autoAssignHours * MS_PER_HOUR,
|
||||||
@@ -161,19 +158,6 @@ export default function RoleProperties(props: { plugin: WebPlugin, role?: lib.Ro
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col xs={24} sm={12} xl={6}>
|
|
||||||
<Form.Item
|
|
||||||
name="permissionGroup"
|
|
||||||
label={labelled(
|
|
||||||
"Permission group",
|
|
||||||
"Factorio permission group players are moved into while this is their "
|
|
||||||
+ "most privileged role which names one. Leave empty to not change their group"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col xs={24} sm={12} xl={6}>
|
<Col xs={24} sm={12} xl={6}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="autoAssignHours"
|
name="autoAssignHours"
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import React, { useContext, useState } from "react";
|
||||||
|
import { Button, Popconfirm } from "antd";
|
||||||
|
|
||||||
|
import { ControlContext, SectionHeader, useAccount, notifyErrorHandler } from "@clusterio/web_ui";
|
||||||
|
|
||||||
|
import { SeedRolesRequest } from "../../messages";
|
||||||
|
|
||||||
|
/** Button on the roles page which creates the roles the scenario shipped with. */
|
||||||
|
export default function SeedRoles() {
|
||||||
|
const control = useContext(ControlContext);
|
||||||
|
const account = useAccount();
|
||||||
|
const [seeding, setSeeding] = useState(false);
|
||||||
|
|
||||||
|
if (!account.hasPermission("core.role.create")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <SectionHeader
|
||||||
|
title="ExpGaming Roles"
|
||||||
|
extra={<Popconfirm
|
||||||
|
title="Create the ExpGaming roles?"
|
||||||
|
description="Roles which already exist by name are kept and only gain permissions."
|
||||||
|
onConfirm={() => {
|
||||||
|
setSeeding(true);
|
||||||
|
control.send(new SeedRolesRequest())
|
||||||
|
.catch(notifyErrorHandler("Error seeding roles"))
|
||||||
|
.finally(() => setSeeding(false));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button loading={seeding}>Seed roles</Button>
|
||||||
|
</Popconfirm>}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import * as lib from "@clusterio/lib";
|
|||||||
import * as messages from "../messages";
|
import * as messages from "../messages";
|
||||||
|
|
||||||
import RoleProperties from "./components/RoleProperties";
|
import RoleProperties from "./components/RoleProperties";
|
||||||
|
import SeedRoles from "./components/SeedRoles";
|
||||||
|
|
||||||
export class WebPlugin extends BaseWebPlugin {
|
export class WebPlugin extends BaseWebPlugin {
|
||||||
roles = new lib.MapSubscriber(messages.RoleUpdatedEvent, this.control);
|
roles = new lib.MapSubscriber(messages.RoleUpdatedEvent, this.control);
|
||||||
@@ -14,6 +15,7 @@ export class WebPlugin extends BaseWebPlugin {
|
|||||||
// does not carry in its type
|
// does not carry in its type
|
||||||
this.componentExtra = {
|
this.componentExtra = {
|
||||||
RoleViewPage: RoleProperties as React.ComponentType,
|
RoleViewPage: RoleProperties as React.ComponentType,
|
||||||
|
RolesPage: SeedRoles,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ authorities.exp_permission =
|
|||||||
end
|
end
|
||||||
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
|
if state then
|
||||||
Commands.unlock_system_commands(player.name)
|
Commands.unlock_system_commands(player.name)
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ local valid, invalid = Commands.status.success, Commands.status.invalid_input
|
|||||||
|
|
||||||
local Roles = require("modules/exp_roles")
|
local Roles = require("modules/exp_roles")
|
||||||
local player_outranks = Roles.player_outranks
|
local player_outranks = Roles.player_outranks
|
||||||
|
local player_has_permission = Roles.player_has_permission
|
||||||
|
|
||||||
local types = {} --- @class (partial) Commands.types
|
local types = {} --- @class (partial) Commands.types
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ types.role =
|
|||||||
if name == nil then
|
if name == nil then
|
||||||
return invalid{ "exp-commands-parse.string-options", table.concat(names, ", ") }
|
return invalid{ "exp-commands-parse.string-options", table.concat(names, ", ") }
|
||||||
else
|
else
|
||||||
return valid(Roles.get_role(name))
|
return valid(Roles.get_role_by_name(name))
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -45,7 +46,7 @@ types.lower_role =
|
|||||||
if not success then return status, result end
|
if not success then return status, result end
|
||||||
--- @cast result ExpRoles.Role
|
--- @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" }
|
return invalid{ "exp-commands-parse_role.lower-role" }
|
||||||
else
|
else
|
||||||
return valid(result)
|
return valid(result)
|
||||||
|
|||||||
@@ -218,13 +218,16 @@ SelectEntities:on_stop(on_player_selection_stop)
|
|||||||
|
|
||||||
--- When there is a repeat offence print it in chat
|
--- When there is a repeat offence print it in chat
|
||||||
local function on_repeat_violation(event)
|
local function on_repeat_violation(event)
|
||||||
Roles.print_to_roles_higher("Regular", {
|
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",
|
"exp-commands_entity-protection.repeat-offence",
|
||||||
format_player_name(event.player_index),
|
format_player_name(event.player_index),
|
||||||
event.entity.localised_name,
|
event.entity.localised_name,
|
||||||
event.entity.position.x,
|
event.entity.position.x,
|
||||||
event.entity.position.y
|
event.entity.position.y
|
||||||
})
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ Commands.new("assign-role", { "exp-commands_roles.description-assign" })
|
|||||||
:register(function(player, other_player, role)
|
:register(function(player, other_player, role)
|
||||||
--- @cast other_player LuaPlayer
|
--- @cast other_player LuaPlayer
|
||||||
--- @cast role ExpRoles.Role
|
--- @cast role ExpRoles.Role
|
||||||
Roles.assign_player(other_player, role, player.name)
|
role:assign(other_player, { by_player_name = player.name })
|
||||||
end)
|
end)
|
||||||
|
|
||||||
--- Unassigns a role to a player
|
--- 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)
|
:register(function(player, other_player, role)
|
||||||
--- @cast other_player LuaPlayer
|
--- @cast other_player LuaPlayer
|
||||||
--- @cast role ExpRoles.Role
|
--- @cast role ExpRoles.Role
|
||||||
Roles.unassign_player(other_player, role, player.name)
|
role:unassign(other_player, { by_player_name = player.name })
|
||||||
end)
|
end)
|
||||||
|
|
||||||
--- Lists all roles in they correct order
|
--- Lists all roles in they correct order
|
||||||
|
|||||||
@@ -68,8 +68,11 @@ do local _points_limit = {} --- @type table<number, number>
|
|||||||
--- @param player LuaPlayer
|
--- @param player LuaPlayer
|
||||||
--- @return number
|
--- @return number
|
||||||
function Elements.bonus_used._calculate_points_limit(player)
|
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 roles = Roles.get_roles()
|
||||||
local role_diff = role.index - Roles.get_player_highest_role(player).index
|
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))
|
local points_limit = math.floor(config.points.base * (1 + config.points.increase_percentage_per_role_level * role_diff))
|
||||||
_points_limit[player.index] = points_limit
|
_points_limit[player.index] = points_limit
|
||||||
return points_limit
|
return points_limit
|
||||||
|
|||||||
@@ -396,7 +396,7 @@ define_tab(
|
|||||||
for _, group in ipairs(groups) do
|
for _, group in ipairs(groups) do
|
||||||
local seen = {}
|
local seen = {}
|
||||||
for _, role_name in ipairs(group.roles) do
|
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
|
for _, player_name in ipairs(role and role:get_player_names() or {}) do
|
||||||
if not seen[player_name] then
|
if not seen[player_name] then
|
||||||
seen[player_name] = true
|
seen[player_name] = true
|
||||||
|
|||||||
@@ -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.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.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.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) {
|
for (const [name, title, description, grantByDefault] of definitions) {
|
||||||
|
|||||||
Reference in New Issue
Block a user