mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-09-21 17:04:00 +00:00
.
This commit is contained in:
@@ -2,3 +2,4 @@ dist/
|
|||||||
node_modules/
|
node_modules/
|
||||||
.vscode
|
.vscode
|
||||||
.luarc.json
|
.luarc.json
|
||||||
|
.tap/
|
||||||
|
|||||||
@@ -22,10 +22,8 @@ return {
|
|||||||
--- GUI
|
--- GUI
|
||||||
"modules.gui.warp-list",
|
"modules.gui.warp-list",
|
||||||
"modules.gui.vlayer",
|
"modules.gui.vlayer",
|
||||||
"modules.gui._role_updates",
|
|
||||||
|
|
||||||
"modules.graftorio.require", -- graftorio
|
"modules.graftorio.require", -- graftorio
|
||||||
--- Config Files
|
--- Config Files
|
||||||
"config.expcore.permission_groups", -- loads some predefined permission groups
|
"config.expcore.permission_groups", -- loads some predefined permission groups
|
||||||
"config.expcore.roles", -- loads some predefined roles
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
local Roles = require("modules.exp_legacy.expcore.roles")
|
local Roles = require("modules/exp_roles")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
admin_as_active = true, --- @setting admin_as_active When true admins will be treated as active regardless of afk time
|
admin_as_active = true, --- @setting admin_as_active When true admins will be treated as active regardless of afk time
|
||||||
@@ -8,6 +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)
|
||||||
return Roles.get_player_highest_role(player).index <= Roles.get_role_from_any("Veteran").index
|
local veteran = Roles.get_role_by_name("Veteran")
|
||||||
|
return veteran ~= nil and not Roles.get_player_highest_role(player):is_lower_than(veteran)
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ return {
|
|||||||
},
|
},
|
||||||
allow_command_prefix_for_messages = true, --- @setting allow_command_prefix_for_messages when true any message trigger will print to all player when prefixed
|
allow_command_prefix_for_messages = true, --- @setting allow_command_prefix_for_messages when true any message trigger will print to all player when prefixed
|
||||||
command_admin_only = false, --- @setting command_admin_only when true will only allow chat commands for admins
|
command_admin_only = false, --- @setting command_admin_only when true will only allow chat commands for admins
|
||||||
command_permission = "command/chat-commands", --- @setting command_permission the permission used to allow command prefixes
|
command_permission = "exp_scenario.chat.commands", --- @setting command_permission the permission used to allow command prefixes
|
||||||
command_prefix = "!", --- @setting command_prefix prefix used for commands below and to print to all players (if enabled above)
|
command_prefix = "!", --- @setting command_prefix prefix used for commands below and to print to all players (if enabled above)
|
||||||
--- @type table<string, LocalisedString | fun(player: LuaPlayer, is_command: boolean): LocalisedString>
|
--- @type table<string, LocalisedString | fun(player: LuaPlayer, is_command: boolean): LocalisedString>
|
||||||
commands = { --- @setting commands will trigger only when command prefix is given
|
commands = { --- @setting commands will trigger only when command prefix is given
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
local ExpUtil = require("modules/exp_util")
|
local ExpUtil = require("modules/exp_util")
|
||||||
local Gui = require("modules/exp_gui")
|
local Gui = require("modules/exp_gui")
|
||||||
local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
|
local Roles = require("modules/exp_roles")
|
||||||
local Reports = require("modules.exp_legacy.modules.control.reports") --- @dep modules.control.reports
|
local Reports = require("modules.exp_legacy.modules.control.reports") --- @dep modules.control.reports
|
||||||
local Warnings = require("modules.exp_legacy.modules.control.warnings") --- @dep modules.control.warnings
|
local Warnings = require("modules.exp_legacy.modules.control.warnings") --- @dep modules.control.warnings
|
||||||
local Jail = require("modules.exp_legacy.modules.control.jail") --- @dep modules.control.jail
|
local Jail = require("modules.exp_legacy.modules.control.jail") --- @dep modules.control.jail
|
||||||
@@ -21,15 +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)
|
|
||||||
local player_highest = Roles.get_player_highest_role(player)
|
|
||||||
local action_player_highest = Roles.get_player_highest_role(selected_player_name)
|
|
||||||
if player_highest.index < action_player_highest.index then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
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]]
|
||||||
@@ -94,7 +85,7 @@ local report_player = new_button("utility/spawn_flag", { "exp-gui_player-list.re
|
|||||||
if Reports.is_reported(selected_player.name, player.name) then
|
if Reports.is_reported(selected_player.name, player.name) then
|
||||||
player.print({ "exp-commands_report.already-reported" }, Colors.orange_red)
|
player.print({ "exp-commands_report.already-reported" }, Colors.orange_red)
|
||||||
else
|
else
|
||||||
set_selected_action(player, "command/report")
|
set_selected_action(player, "exp_scenario.command.create_report")
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -102,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
|
||||||
|
|
||||||
@@ -110,7 +104,7 @@ end
|
|||||||
-- @element warn_player
|
-- @element warn_player
|
||||||
local warn_player = new_button("utility/spawn_flag", { "exp-gui_player-list.warn-player" })
|
local warn_player = new_button("utility/spawn_flag", { "exp-gui_player-list.warn-player" })
|
||||||
:on_click(function(def, player, element)
|
:on_click(function(def, player, element)
|
||||||
set_selected_action(player, "command/give-warning")
|
set_selected_action(player, "exp_scenario.command.create_warning")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local function warn_player_callback(player, reason)
|
local function warn_player_callback(player, reason)
|
||||||
@@ -128,7 +122,7 @@ local jail_player = new_button("utility/multiplayer_waiting_icon", { "exp-gui_pl
|
|||||||
if Jail.is_jailed(selected_player.name) then
|
if Jail.is_jailed(selected_player.name) then
|
||||||
player.print({ "exp-commands_jail.already-jailed", selected_player_color }, Colors.orange_red)
|
player.print({ "exp-commands_jail.already-jailed", selected_player_color }, Colors.orange_red)
|
||||||
else
|
else
|
||||||
set_selected_action(player, "command/jail")
|
set_selected_action(player, "exp_scenario.command.jail")
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -143,7 +137,7 @@ end
|
|||||||
-- @element kick_player
|
-- @element kick_player
|
||||||
local kick_player = new_button("utility/warning_icon", { "exp-gui_player-list.kick-player" })
|
local kick_player = new_button("utility/warning_icon", { "exp-gui_player-list.kick-player" })
|
||||||
:on_click(function(def, player, element)
|
:on_click(function(def, player, element)
|
||||||
set_selected_action(player, "command/kick")
|
set_selected_action(player, "exp_scenario.gui.player_list.kick")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local function kick_player_callback(player, reason)
|
local function kick_player_callback(player, reason)
|
||||||
@@ -155,7 +149,7 @@ end
|
|||||||
-- @element ban_player
|
-- @element ban_player
|
||||||
local ban_player = new_button("utility/danger_icon", { "exp-gui_player-list.ban-player" })
|
local ban_player = new_button("utility/danger_icon", { "exp-gui_player-list.ban-player" })
|
||||||
:on_click(function(def, player, element)
|
:on_click(function(def, player, element)
|
||||||
set_selected_action(player, "command/ban")
|
set_selected_action(player, "exp_scenario.gui.player_list.ban")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local function ban_player_callback(player, reason)
|
local function ban_player_callback(player, reason)
|
||||||
@@ -166,40 +160,40 @@ end
|
|||||||
return {
|
return {
|
||||||
set_accessors = set_accessors,
|
set_accessors = set_accessors,
|
||||||
buttons = {
|
buttons = {
|
||||||
["command/teleport"] = {
|
["exp_scenario.command.teleport"] = {
|
||||||
auth = function(player, selected_player)
|
auth = function(player, selected_player)
|
||||||
return player.name ~= selected_player.name
|
return player.name ~= selected_player.name
|
||||||
end, -- cant teleport to your self
|
end, -- cant teleport to your self
|
||||||
goto_player,
|
goto_player,
|
||||||
bring_player,
|
bring_player,
|
||||||
},
|
},
|
||||||
["command/report"] = {
|
["exp_scenario.command.create_report"] = {
|
||||||
auth = function(player, selected_player)
|
auth = function(player, selected_player)
|
||||||
if player == selected_player then return false end
|
if player == selected_player then return false end
|
||||||
if not Roles.player_allowed(player, "command/give-warning") then
|
if not Roles.player_has_permission(player, "exp_scenario.command.create_warning") then
|
||||||
return not Roles.player_has_flag(selected_player, "report-immune")
|
return not Roles.player_has_permission(selected_player, "exp_scenario.bypass.reports")
|
||||||
end
|
end
|
||||||
end, -- can report any player that isn't immune and you aren't able to give warnings
|
end, -- can report any player that isn't immune and you aren't able to give warnings
|
||||||
reason_callback = report_player_callback,
|
reason_callback = report_player_callback,
|
||||||
report_player,
|
report_player,
|
||||||
},
|
},
|
||||||
["command/give-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,
|
||||||
},
|
},
|
||||||
["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,
|
||||||
},
|
},
|
||||||
["command/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,
|
||||||
},
|
},
|
||||||
["command/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,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ return {
|
|||||||
allow_zoom_to_map = true, --- @setting allow_zoom_to_map false will disable the zoom to map feature
|
allow_zoom_to_map = true, --- @setting allow_zoom_to_map false will disable the zoom to map feature
|
||||||
allow_remote_launch = true, --- @setting allow_remote_launch false removes the remote launch button for all players
|
allow_remote_launch = true, --- @setting allow_remote_launch false removes the remote launch button for all players
|
||||||
remote_launch_admins_only = false, --- @setting remote_launch_admins_only true will remove the remote launch button for all non (game) admins
|
remote_launch_admins_only = false, --- @setting remote_launch_admins_only true will remove the remote launch button for all non (game) admins
|
||||||
remote_launch_role_permission = "gui/rocket-info/remote_launch", --- @setting remote_launch_role_permission value used by custom permission system to allow or disallow the button
|
remote_launch_role_permission = "exp_scenario.gui.rocket_info.remote_launch", --- @setting remote_launch_role_permission value used by custom permission system to allow or disallow the button
|
||||||
allow_toggle_active = true, --- @setting allow_toggle_active false removes the remote toggle auto launch button for all players
|
allow_toggle_active = true, --- @setting allow_toggle_active false removes the remote toggle auto launch button for all players
|
||||||
toggle_active_admins_only = false, --- @setting toggle_active_admins_only true will remove the toggle auto launch button for all non (game) admins
|
toggle_active_admins_only = false, --- @setting toggle_active_admins_only true will remove the toggle auto launch button for all non (game) admins
|
||||||
toggle_active_role_permission = "gui/rocket-info/toggle-active", --- @setting toggle_active_role_permission value used by custom permission system to allow or disallow the button
|
toggle_active_role_permission = "exp_scenario.gui.rocket_info.toggle_active", --- @setting toggle_active_role_permission value used by custom permission system to allow or disallow the button
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
-- Adding tasks
|
-- Adding tasks
|
||||||
allow_add_task = "all", --- @setting allow_add_task dictates who is allowed to add new tasks; values: all, admin, expcore.roles, none
|
allow_add_task = "all", --- @setting allow_add_task dictates who is allowed to add new tasks; values: all, admin, exp_roles, none
|
||||||
expcore_roles_allow_add_task = "gui/task-list/add", --- @setting expcore_roles_allow_add_task if expcore.roles is used then this is the required permission
|
exp_roles_allow_add_task = "exp_scenario.gui.task_list.add", --- @setting exp_roles_allow_add_task if exp_roles is used then this is the required permission
|
||||||
|
|
||||||
-- Editing tasks
|
-- Editing tasks
|
||||||
allow_edit_task = "expcore.roles", --- @setting allow_edit_task dictates who is allowed to edit existing tasks; values: all, admin, expcore.roles, none
|
allow_edit_task = "exp_roles", --- @setting allow_edit_task dictates who is allowed to edit existing tasks; values: all, admin, exp_roles, none
|
||||||
expcore_roles_allow_edit_task = "gui/task-list/edit", --- @setting expcore_roles_allow_edit_task if expcore.roles is used then this is the required permission
|
exp_roles_allow_edit_task = "exp_scenario.gui.task_list.edit", --- @setting exp_roles_allow_edit_task if exp_roles is used then this is the required permission
|
||||||
user_can_edit_own_tasks = true, --- @settings if true then the user who made the task can edit it regardless of the allow_edit_task setting
|
user_can_edit_own_tasks = true, --- @settings if true then the user who made the task can edit it regardless of the allow_edit_task setting
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,23 +8,23 @@ return {
|
|||||||
default_icon = { type = "item", name = "discharge-defense-equipment" }, --- @setting default_icon the default icon that will be used for warps
|
default_icon = { type = "item", name = "discharge-defense-equipment" }, --- @setting default_icon the default icon that will be used for warps
|
||||||
|
|
||||||
-- Warp cooldowns
|
-- Warp cooldowns
|
||||||
bypass_warp_cooldown = "expcore.roles", --- @setting bypass_warp_cooldown dictates who the warp cooldown is applied to; values: all, admin, expcore.roles, none
|
bypass_warp_cooldown = "exp_roles", --- @setting bypass_warp_cooldown dictates who the warp cooldown is applied to; values: all, admin, exp_roles, none
|
||||||
expcore_roles_bypass_warp_cooldown = "gui/warp-list/bypass-cooldown", --- @setting expcore_roles_bypass_warp_cooldown if expcore.roles is used then this is the required permission
|
exp_roles_bypass_warp_cooldown = "exp_scenario.gui.warp_list.bypass_cooldown", --- @setting exp_roles_bypass_warp_cooldown if exp_roles is used then this is the required permission
|
||||||
cooldown_duration = 60, --- @setting cooldown_duration the duration of the warp cooldown in seconds
|
cooldown_duration = 60, --- @setting cooldown_duration the duration of the warp cooldown in seconds
|
||||||
|
|
||||||
-- Warp proximity
|
-- Warp proximity
|
||||||
bypass_warp_proximity = "expcore.roles", --- @setting bypass_warp_proximity dictates who the warp proximity is applied to; values: all, admin, expcore.roles, none
|
bypass_warp_proximity = "exp_roles", --- @setting bypass_warp_proximity dictates who the warp proximity is applied to; values: all, admin, exp_roles, none
|
||||||
expcore_roles_bypass_warp_proximity = "gui/warp-list/bypass-proximity", --- @setting expcore_roles_bypass_warp_proximity if expcore.roles is used then this is the required permission
|
exp_roles_bypass_warp_proximity = "exp_scenario.gui.warp_list.bypass_proximity", --- @setting exp_roles_bypass_warp_proximity if exp_roles is used then this is the required permission
|
||||||
standard_proximity_radius = 4, --- @setting standard_proximity_radius the minimum distance a player is allowed to be to a warp in order to use it
|
standard_proximity_radius = 4, --- @setting standard_proximity_radius the minimum distance a player is allowed to be to a warp in order to use it
|
||||||
spawn_proximity_radius = 20, --- @setting spawn_proximity_radius the minimum distance a player is allowed to be from they spawn point to use warps
|
spawn_proximity_radius = 20, --- @setting spawn_proximity_radius the minimum distance a player is allowed to be from they spawn point to use warps
|
||||||
|
|
||||||
-- Adding warps
|
-- Adding warps
|
||||||
allow_add_warp = "expcore.roles", --- @setting allow_add_warp dictates who is allowed to add warps; values: all, admin, expcore.roles, none
|
allow_add_warp = "exp_roles", --- @setting allow_add_warp dictates who is allowed to add warps; values: all, admin, exp_roles, none
|
||||||
expcore_roles_allow_add_warp = "gui/warp-list/add", --- @setting expcore_roles_allow_add_warp if expcore.roles is used then this is the required permission
|
exp_roles_allow_add_warp = "exp_scenario.gui.warp_list.add", --- @setting exp_roles_allow_add_warp if exp_roles is used then this is the required permission
|
||||||
|
|
||||||
-- Editing warps
|
-- Editing warps
|
||||||
allow_edit_warp = "expcore.roles", --- @setting allow_edit_warp dictates who is allowed to edit warps; values: all, admin, expcore.roles, none
|
allow_edit_warp = "exp_roles", --- @setting allow_edit_warp dictates who is allowed to edit warps; values: all, admin, exp_roles, none
|
||||||
expcore_roles_allow_edit_warp = "gui/warp-list/edit", --- @setting expcore_roles_allow_edit_warp if expcore.roles is used then this is the required permission
|
exp_roles_allow_edit_warp = "exp_scenario.gui.warp_list.edit", --- @setting exp_roles_allow_edit_warp if exp_roles is used then this is the required permission
|
||||||
user_can_edit_own_warps = false, --- @settings user_can_edit_own_warps if true then the user who made the warp can edit it regardless of the allow_edit_warp setting
|
user_can_edit_own_warps = false, --- @settings user_can_edit_own_warps if true then the user who made the warp can edit it regardless of the allow_edit_warp setting
|
||||||
|
|
||||||
-- Warp area generation
|
-- Warp area generation
|
||||||
|
|||||||
@@ -25,6 +25,6 @@ return {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ignore_permission = "bypass-nukeprotect", -- @setting ignore_permission The permission that nukeprotect will ignore
|
ignore_permission = "exp_scenario.bypass.nuke_protection", -- @setting ignore_permission The permission that nukeprotect will ignore
|
||||||
ignore_admins = true, -- @setting ignore_admins Ignore admins, true by default. Allows usage outside of the roles module
|
ignore_admins = true, -- @setting ignore_admins Ignore admins, true by default. Allows usage outside of the roles module
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
return {
|
return {
|
||||||
ignore_admins = true, --- @setting ignore_admins If admins are ignored by the protection filter
|
ignore_admins = true, --- @setting ignore_admins If admins are ignored by the protection filter
|
||||||
ignore_permission = "bypass-entity-protection", --- @setting ignore_permission Players with this permission will be ignored by the protection filter, leave nil if expcore.roles is not used
|
ignore_permission = "exp_scenario.bypass.entity_protection", --- @setting ignore_permission Players with this permission will be ignored by the protection filter, leave nil to protect against every player
|
||||||
repeat_count = 5, --- @setting repeat_count Number of protected entities that must be removed within repeat_lifetime in order to trigger repeated removal protection
|
repeat_count = 5, --- @setting repeat_count Number of protected entities that must be removed within repeat_lifetime in order to trigger repeated removal protection
|
||||||
repeat_lifetime = 3600 * 20, --- @setting repeat_lifetime The length of time, in ticks, that protected removals will be remembered for
|
repeat_lifetime = 3600 * 20, --- @setting repeat_lifetime The length of time, in ticks, that protected removals will be remembered for
|
||||||
refresh_rate = 3600 * 5, --- @setting refresh_rate How often the age of protected removals are checked against repeat_lifetime
|
refresh_rate = 3600 * 5, --- @setting refresh_rate How often the age of protected removals are checked against repeat_lifetime
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,3 @@
|
|||||||
[expcore-roles]
|
|
||||||
error-log-format-flag=[ERROR] roleFlag/__1__ :: __2__
|
|
||||||
error-log-format-assign=[ERROR] rolePromote/__1__ :: __2__
|
|
||||||
game-message-assign=__1__ has been assigned to __2__ by __3__
|
|
||||||
game-message-unassign=__1__ has been unassigned from __2__ by __3__
|
|
||||||
reject-role=Invalid Role Name.
|
|
||||||
reject-player-role=Player has a higher role.
|
|
||||||
|
|
||||||
[expcore-data]
|
[expcore-data]
|
||||||
description-preference=Allows you to set/get your data saving preference.
|
description-preference=Allows you to set/get your data saving preference.
|
||||||
description-data=Writes all your player data to a file on your computer.
|
description-data=Writes all your player data to a file on your computer.
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
[expcore-roles]
|
|
||||||
error-log-format-flag=[ERROR] roleFlag/__1__ :: __2__
|
|
||||||
error-log-format-assign=[ERROR] rolePromote/__1__ :: __2__
|
|
||||||
game-message-assign=__1__ 已被 __3__ 指派到身分組 __2__
|
|
||||||
game-message-unassign=__1__ 已被 __3__ 取消指派到身分組 __2__
|
|
||||||
reject-role=無效的身分組。
|
|
||||||
reject-player-role=用戶已有更高的身分組。
|
|
||||||
|
|
||||||
[expcore-data]
|
[expcore-data]
|
||||||
description-preference=允許寫入資料。
|
description-preference=允許寫入資料。
|
||||||
description-data=把用戶資料寫入電腦
|
description-data=把用戶資料寫入電腦
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
[expcore-roles]
|
|
||||||
error-log-format-flag=[ERROR] roleFlag/__1__ :: __2__
|
|
||||||
error-log-format-assign=[ERROR] rolePromote/__1__ :: __2__
|
|
||||||
game-message-assign=__1__ 已被 __3__ 指派到身分組 __2__
|
|
||||||
game-message-unassign=__1__ 已被 __3__ 取消指派到身分組 __2__
|
|
||||||
reject-role=無效的身分組。
|
|
||||||
reject-player-role=用戶已有更高的身分組。
|
|
||||||
|
|
||||||
[expcore-data]
|
[expcore-data]
|
||||||
description-preference=允許寫入資料。
|
description-preference=允許寫入資料。
|
||||||
description-data=把用戶資料寫入電腦
|
description-data=把用戶資料寫入電腦
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"clusterio": "*",
|
"clusterio": "*",
|
||||||
"exp_util": "*",
|
"exp_util": "*",
|
||||||
|
"exp_roles": "*",
|
||||||
"exp_gui": "*"
|
"exp_gui": "*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,25 +7,25 @@
|
|||||||
-- import the module from the control modules
|
-- import the module from the control modules
|
||||||
local Jail = require("modules.exp_legacy.modules.control.jail") --- @dep modules.control.jail
|
local Jail = require("modules.exp_legacy.modules.control.jail") --- @dep modules.control.jail
|
||||||
|
|
||||||
-- This will move 'MrBiter' to the jail role and remove all other roles from them
|
-- This will give 'MrBiter' the jail role, which suppresses all of their other roles
|
||||||
-- the player name and reason are only so they can be included in the event for user feedback
|
-- the player name and reason are only so they can be included in the event for user feedback
|
||||||
Jail.jail_player('MrBiter', 'Cooldude2606', 'Likes biters too much')
|
Jail.jail_player('MrBiter', 'Cooldude2606', 'Likes biters too much')
|
||||||
|
|
||||||
-- This will give 'MrBiter' all his roles back and remove him from jail
|
-- This will remove the jail role from 'MrBiter', restoring their other roles
|
||||||
-- again as above the player name is only used in the event for user feedback
|
-- again as above the player name is only used in the event for user feedback
|
||||||
Jail.unjail_player('MrBiter', 'Cooldude2606')
|
Jail.unjail_player('MrBiter', 'Cooldude2606')
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local Roles = require("modules.exp_legacy.expcore.roles")
|
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
|
||||||
local assign_roles = Roles.assign_player
|
|
||||||
local unassign_roles = Roles.unassign_player
|
--- The role given to jailed players, it has a higher priority than every other role
|
||||||
local has_role = Roles.player_has_role
|
local function jail_role()
|
||||||
local get_roles = Roles.get_player_roles
|
return assert(Roles.get_role_by_name("Jail"), "The Jail role does not exist")
|
||||||
|
end
|
||||||
|
|
||||||
local Jail = {
|
local Jail = {
|
||||||
old_roles = {},
|
|
||||||
events = {
|
events = {
|
||||||
--- When a player is assigned to jail
|
--- When a player is assigned to jail
|
||||||
-- @event on_player_jailed
|
-- @event on_player_jailed
|
||||||
@@ -64,10 +64,11 @@ 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 has_role(valid_player(player), "Jail")
|
local valid = valid_player(player)
|
||||||
|
return valid ~= nil and jail_role():has_player(valid)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Moves a player to jail and removes all other roles
|
--- Moves a player to jail, which suppresses all of their other roles
|
||||||
-- @tparam LuaPlayer player the player who will be jailed
|
-- @tparam LuaPlayer player the player who will be jailed
|
||||||
-- @tparam string by_player_name the name of the player who is doing the jailing
|
-- @tparam string by_player_name the name of the player who is doing the jailing
|
||||||
-- @tparam[opt='Non given.'] string reason the reason that the player is being jailed
|
-- @tparam[opt='Non given.'] string reason the reason that the player is being jailed
|
||||||
@@ -79,8 +80,8 @@ function Jail.jail_player(player, by_player_name, reason)
|
|||||||
|
|
||||||
reason = reason or "Non given."
|
reason = reason or "Non given."
|
||||||
|
|
||||||
if has_role(player, "Jail") then return end
|
local role = jail_role()
|
||||||
local roles = get_roles(player)
|
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 }
|
||||||
@@ -89,16 +90,14 @@ 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 }
|
||||||
|
|
||||||
unassign_roles(player, roles, by_player_name, nil, true)
|
role:assign(player, { by_player_name = by_player_name, silent = true })
|
||||||
assign_roles(player, "Jail", by_player_name, nil, true)
|
|
||||||
assign_roles(player, roles, by_player_name, nil, true)
|
|
||||||
|
|
||||||
event_emit(Jail.events.on_player_jailed, player, by_player_name, reason)
|
event_emit(Jail.events.on_player_jailed, player, by_player_name, reason)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Moves a player out of jail and restores all roles previously removed
|
--- Moves a player out of jail, which restores all of their other roles
|
||||||
-- @tparam LuaPlayer player the player that will be unjailed
|
-- @tparam LuaPlayer player the player that will be unjailed
|
||||||
-- @tparam string by_player_name the name of the player that is doing the unjail
|
-- @tparam string by_player_name the name of the player that is doing the unjail
|
||||||
-- @treturn boolean whether the player was unjailed successfully
|
-- @treturn boolean whether the player was unjailed successfully
|
||||||
@@ -107,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 has_role(player, "Jail") then return end
|
local role = jail_role()
|
||||||
|
if not role:has_player(player) then return end
|
||||||
|
|
||||||
unassign_roles(player, "Jail", by_player_name, nil, 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)
|
||||||
|
|
||||||
|
|||||||
@@ -33,11 +33,7 @@ for _, config_key in ipairs{ "always_protected_names", "always_protected_types",
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Require roles if a permission is assigned in the config
|
local Roles = require("modules/exp_roles")
|
||||||
local Roles --- @type table<string, any>
|
|
||||||
if config.ignore_permission then
|
|
||||||
Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
|
|
||||||
end
|
|
||||||
|
|
||||||
----- Storage Variables -----
|
----- Storage Variables -----
|
||||||
--- Variables stored in the global table
|
--- Variables stored in the global table
|
||||||
@@ -159,7 +155,7 @@ Event.add(defines.events.on_pre_player_mined_item, function(event)
|
|||||||
-- Check if the player should be ignored
|
-- Check if the player should be ignored
|
||||||
if config.ignore_admins and player.admin then return end
|
if config.ignore_admins and player.admin then return end
|
||||||
if entity.last_user == nil or entity.last_user.index == player.index then return end
|
if entity.last_user == nil or entity.last_user.index == player.index then return end
|
||||||
if config.ignore_permission and Roles.player_allowed(player, config.ignore_permission) then return end
|
if config.ignore_permission and Roles.player_has_permission(player, config.ignore_permission) then return end
|
||||||
|
|
||||||
-- Check if the entity is protected
|
-- Check if the entity is protected
|
||||||
if EntityProtection.is_entity_protected(entity)
|
if EntityProtection.is_entity_protected(entity)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ local Commands = require("modules/exp_commands")
|
|||||||
local PlayerData = require("modules.exp_legacy.expcore.player_data") --- @dep expcore.player_data
|
local PlayerData = require("modules.exp_legacy.expcore.player_data") --- @dep expcore.player_data
|
||||||
local CustomMessages = PlayerData.Settings:combine("JoinMessage")
|
local CustomMessages = PlayerData.Settings:combine("JoinMessage")
|
||||||
CustomMessages:set_metadata{
|
CustomMessages:set_metadata{
|
||||||
permission = "command/join-message",
|
permission = "exp_scenario.command.set_join_message",
|
||||||
}
|
}
|
||||||
|
|
||||||
--- When a players data loads show their message
|
--- When a players data loads show their message
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ local config = require("modules.exp_legacy.config.preset_player_quickbar") --- @
|
|||||||
local PlayerData = require("modules.exp_legacy.expcore.player_data") --- @dep expcore.player_data
|
local PlayerData = require("modules.exp_legacy.expcore.player_data") --- @dep expcore.player_data
|
||||||
local PlayerFilters = PlayerData.Settings:combine("QuickbarFilters")
|
local PlayerFilters = PlayerData.Settings:combine("QuickbarFilters")
|
||||||
PlayerFilters:set_metadata{
|
PlayerFilters:set_metadata{
|
||||||
permission = "command/save-quickbar",
|
permission = "exp_scenario.command.save_quickbar",
|
||||||
stringify = function(value)
|
stringify = function(value)
|
||||||
if not value then return "No filters set" end
|
if not value then return "No filters set" end
|
||||||
local count = 0
|
local count = 0
|
||||||
|
|||||||
@@ -4,17 +4,17 @@
|
|||||||
]]
|
]]
|
||||||
|
|
||||||
local Commands = require("modules/exp_commands")
|
local Commands = require("modules/exp_commands")
|
||||||
local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
|
local Roles = require("modules/exp_roles")
|
||||||
|
|
||||||
--- Stores the tag for a player
|
--- Stores the tag for a player
|
||||||
local PlayerData = require("modules.exp_legacy.expcore.player_data") --- @dep expcore.player_data
|
local PlayerData = require("modules.exp_legacy.expcore.player_data") --- @dep expcore.player_data
|
||||||
local PlayerTags = PlayerData.Settings:combine("Tag")
|
local PlayerTags = PlayerData.Settings:combine("Tag")
|
||||||
local PlayerTagColors = PlayerData.Settings:combine("TagColor")
|
local PlayerTagColors = PlayerData.Settings:combine("TagColor")
|
||||||
PlayerTags:set_metadata{
|
PlayerTags:set_metadata{
|
||||||
permission = "command/tag",
|
permission = "exp_scenario.command.tag",
|
||||||
}
|
}
|
||||||
PlayerTagColors:set_metadata{
|
PlayerTagColors:set_metadata{
|
||||||
permission = "command/tag-color",
|
permission = "exp_scenario.command.tag_color",
|
||||||
}
|
}
|
||||||
|
|
||||||
local set_tag = function(player, tag, color)
|
local set_tag = function(player, tag, color)
|
||||||
@@ -72,7 +72,7 @@ Commands.new("tag-clear", "Clears your tag. Or another player if you are admin."
|
|||||||
if other_player == player then
|
if other_player == player then
|
||||||
-- No player given so removes your tag
|
-- No player given so removes your tag
|
||||||
PlayerTags:remove(other_player)
|
PlayerTags:remove(other_player)
|
||||||
elseif Roles.player_allowed(player, "command/clear-tag/always") then
|
elseif Roles.player_has_permission(player, "exp_scenario.command.tag_clear.always") then
|
||||||
-- Player given and user is admin so clears that player's tag
|
-- Player given and user is admin so clears that player's tag
|
||||||
PlayerTags:remove(other_player)
|
PlayerTags:remove(other_player)
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
local Gui = require("modules/exp_gui")
|
|
||||||
local Roles = require("modules.exp_legacy.expcore.roles")
|
|
||||||
local Event = require("modules/exp_legacy/utils/event")
|
|
||||||
|
|
||||||
--- @diagnostic disable: access-invisible
|
|
||||||
Event.add(Roles.events.on_role_assigned, Gui._ensure_consistency)
|
|
||||||
Event.add(Roles.events.on_role_unassigned, Gui._ensure_consistency)
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
]]
|
]]
|
||||||
|
|
||||||
local Gui = require("modules/exp_gui")
|
local Gui = require("modules/exp_gui")
|
||||||
local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
|
local Roles = require("modules/exp_roles")
|
||||||
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
|
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
|
||||||
local format_number = require("util").format_number --- @dep util
|
local format_number = require("util").format_number --- @dep util
|
||||||
local config = require("modules.exp_legacy.config.vlayer") --- @dep config.vlayer
|
local config = require("modules.exp_legacy.config.vlayer") --- @dep config.vlayer
|
||||||
@@ -457,7 +457,7 @@ local vlayer_control_set = Gui.define("vlayer_control_set")
|
|||||||
vlayer_gui_control_see(disp)
|
vlayer_gui_control_see(disp)
|
||||||
local b = vlayer_gui_control_build(disp)
|
local b = vlayer_gui_control_build(disp)
|
||||||
local r = vlayer_gui_control_remove(disp)
|
local r = vlayer_gui_control_remove(disp)
|
||||||
local v = Roles.player_allowed(player, "gui/vlayer-edit")
|
local v = Roles.player_has_permission(player, "exp_scenario.gui.vlayer_edit")
|
||||||
b.visible = v
|
b.visible = v
|
||||||
r.visible = v
|
r.visible = v
|
||||||
|
|
||||||
@@ -484,22 +484,21 @@ Gui.toolbar.create_button{
|
|||||||
sprite = "entity/solar-panel",
|
sprite = "entity/solar-panel",
|
||||||
tooltip = { "vlayer.main-tooltip" },
|
tooltip = { "vlayer.main-tooltip" },
|
||||||
visible = function(player, element)
|
visible = function(player, element)
|
||||||
return Roles.player_allowed(player, "gui/vlayer")
|
return Roles.player_has_permission(player, "exp_scenario.gui.vlayer")
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
--- Update the visibly of the buttons based on a players roles
|
--- Update the visibly of the buttons based on a players roles
|
||||||
local function role_update_event(event)
|
local function role_update_event(event)
|
||||||
local player = game.players[event.player_index]
|
local player = game.players[event.player_index]
|
||||||
local visible = Roles.player_allowed(player, "gui/vlayer-edit")
|
local visible = Roles.player_has_permission(player, "exp_scenario.gui.vlayer_edit")
|
||||||
local container = Gui.get_left_element(vlayer_container, player)
|
local container = Gui.get_left_element(vlayer_container, player)
|
||||||
local disp = container.frame["vlayer_st_2"].disp.table
|
local disp = container.frame["vlayer_st_2"].disp.table
|
||||||
disp[vlayer_gui_control_build.name].visible = visible
|
disp[vlayer_gui_control_build.name].visible = visible
|
||||||
disp[vlayer_gui_control_remove.name].visible = visible
|
disp[vlayer_gui_control_remove.name].visible = visible
|
||||||
end
|
end
|
||||||
|
|
||||||
Event.add(Roles.events.on_role_assigned, role_update_event)
|
Event.add(Roles.events.on_player_roles_changed, role_update_event)
|
||||||
Event.add(Roles.events.on_role_unassigned, role_update_event)
|
|
||||||
|
|
||||||
Event.on_nth_tick(config.update_tick_gui, function(_)
|
Event.on_nth_tick(config.update_tick_gui, function(_)
|
||||||
local stats = vlayer.get_statistics()
|
local stats = vlayer.get_statistics()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ local Gui = require("modules/exp_gui")
|
|||||||
local Datastore = require("modules.exp_legacy.expcore.datastore") --- @dep expcore.datastore
|
local Datastore = require("modules.exp_legacy.expcore.datastore") --- @dep expcore.datastore
|
||||||
local Storage = require("modules/exp_util/storage")
|
local Storage = require("modules/exp_util/storage")
|
||||||
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
|
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
|
||||||
local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
|
local Roles = require("modules/exp_roles")
|
||||||
local Colors = require("modules/exp_util/include/color")
|
local Colors = require("modules/exp_util/include/color")
|
||||||
local config = require("modules.exp_legacy.config.gui.warps") --- @dep config.gui.warps
|
local config = require("modules.exp_legacy.config.gui.warps") --- @dep config.gui.warps
|
||||||
local Warps = require("modules.exp_legacy.modules.control.warps") --- @dep modules.control.warps
|
local Warps = require("modules.exp_legacy.modules.control.warps") --- @dep modules.control.warps
|
||||||
@@ -68,8 +68,8 @@ local function check_player_permissions(player, action, warp)
|
|||||||
return true
|
return true
|
||||||
elseif action_config == "admin" then
|
elseif action_config == "admin" then
|
||||||
return player.admin
|
return player.admin
|
||||||
elseif action_config == "expcore.roles" then
|
elseif action_config == "exp_roles" then
|
||||||
return Roles.player_allowed(player, config["expcore_roles_" .. action])
|
return Roles.player_has_permission(player, config["exp_roles_" .. action])
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Return false as all other conditions have not been met
|
-- Return false as all other conditions have not been met
|
||||||
@@ -708,7 +708,7 @@ Gui.toolbar.create_button{
|
|||||||
sprite = config.default_icon.type .. "/" .. config.default_icon.name,
|
sprite = config.default_icon.type .. "/" .. config.default_icon.name,
|
||||||
tooltip = { "warp-list.main-tooltip" },
|
tooltip = { "warp-list.main-tooltip" },
|
||||||
visible = function(player, element)
|
visible = function(player, element)
|
||||||
return Roles.player_allowed(player, "gui/warp-list")
|
return Roles.player_has_permission(player, "exp_scenario.gui.warp_list")
|
||||||
end
|
end
|
||||||
}:on_click(function(def, player, element)
|
}:on_click(function(def, player, element)
|
||||||
-- Set gui keep open state for player that clicked the button: true if visible, false if invisible
|
-- Set gui keep open state for player that clicked the button: true if visible, false if invisible
|
||||||
@@ -885,8 +885,7 @@ local function role_update_event(event)
|
|||||||
add_new_warp_element.visible = allow_add_warp
|
add_new_warp_element.visible = allow_add_warp
|
||||||
end
|
end
|
||||||
|
|
||||||
Event.add(Roles.events.on_role_assigned, role_update_event)
|
Event.add(Roles.events.on_player_roles_changed, role_update_event)
|
||||||
Event.add(Roles.events.on_role_unassigned, role_update_event)
|
|
||||||
|
|
||||||
--- When a chart tag is removed or edited make sure it is not one that belongs to a warp
|
--- When a chart tag is removed or edited make sure it is not one that belongs to a warp
|
||||||
local function maintain_tag(event)
|
local function maintain_tag(event)
|
||||||
|
|||||||
@@ -1,6 +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, 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 {
|
||||||
@@ -34,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));
|
||||||
@@ -43,6 +45,68 @@ export class ControllerPlugin extends BaseControllerPlugin {
|
|||||||
await this.roleMeta.save();
|
await this.roleMeta.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Seeding
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the roles the scenario shipped with. Roles which already exist
|
||||||
|
* by name are reused and only gain the seed permissions.
|
||||||
|
*/
|
||||||
|
async handleSeedRolesRequest() {
|
||||||
|
for (const [index, seedRole] of seedRoles.entries()) {
|
||||||
|
const role = this.seedRole(seedRole);
|
||||||
|
if (!role) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.roleMeta.set(new messages.RoleMetaRecord(
|
||||||
|
role.id,
|
||||||
|
index + 1,
|
||||||
|
seedRole.priority ?? 0,
|
||||||
|
seedRole.shortHand,
|
||||||
|
"",
|
||||||
|
seedRole.color,
|
||||||
|
seedRole.autoAssignHours === undefined ? null : seedRole.autoAssignHours * 3600000,
|
||||||
|
seedRole.blockAutoAssign ?? false,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.info(`Seeded ${seedRoles.length} roles`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Find or create the clusterio role for a seed role, returns undefined if it has no role to use. */
|
||||||
|
seedRole(seedRole: SeedRole) {
|
||||||
|
const roles = this.controller.roles;
|
||||||
|
if (seedRole.isAdmin) {
|
||||||
|
return roles.get(lib.Role.DefaultAdminRoleId);
|
||||||
|
}
|
||||||
|
if (seedRole.isDefault) {
|
||||||
|
const defaultRoleId = this.controller.config.get("controller.default_role_id");
|
||||||
|
return defaultRoleId !== null ? roles.get(defaultRoleId) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const permissions = flattenSeedPermissions(seedRole);
|
||||||
|
for (const permission of permissions) {
|
||||||
|
if (!lib.permissions.has(permission)) {
|
||||||
|
this.logger.warn(`Seed role ${seedRole.name} grants unknown permission ${permission}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let role = [...roles.valuesMutable()].find(other => other.name === seedRole.name);
|
||||||
|
if (role) {
|
||||||
|
for (const permission of permissions) {
|
||||||
|
role.permissions.add(permission);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const id = Math.max(5, ...[...roles.keys()].map(other => other + 1));
|
||||||
|
role = new lib.Role(id, seedRole.name, "", permissions);
|
||||||
|
this.logger.info(`Created role ${seedRole.name}`);
|
||||||
|
}
|
||||||
|
roles.set(role);
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Role properties
|
Role properties
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ export class InstancePlugin extends BaseInstancePlugin {
|
|||||||
this.instance.sendTo("controller", new messages.AssignmentListRequest()),
|
this.instance.sendTo("controller", new messages.AssignmentListRequest()),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (!roles.some(role => role.isDefault)) {
|
||||||
|
this.logger.warn("No default role is set on the controller, players will hold no roles in game");
|
||||||
|
}
|
||||||
|
|
||||||
await this.luaSend("initialise", {
|
await this.luaSend("initialise", {
|
||||||
...messages.encodeRolesForLua(roles),
|
...messages.encodeRolesForLua(roles),
|
||||||
assignments: assignments.map(assignment => assignment.toJSON()),
|
assignments: assignments.map(assignment => assignment.toJSON()),
|
||||||
|
|||||||
@@ -365,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
|
||||||
*/
|
*/
|
||||||
|
|||||||
+421
-507
File diff suppressed because it is too large
Load Diff
@@ -2,8 +2,8 @@
|
|||||||
Wires the role sync into the factorio event handler.
|
Wires the role sync into the factorio event handler.
|
||||||
|
|
||||||
This is kept apart from control.lua because `ExpRoles.events` holds the ids of
|
This is kept apart from control.lua because `ExpRoles.events` holds the ids of
|
||||||
the events this module raises, which is the interface the legacy role system
|
the events this module raises, while event_handler expects `events` to be the
|
||||||
had, and event_handler expects `events` to be the handlers to register.
|
handlers to register.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local clusterio_api = require("modules/clusterio/api")
|
local clusterio_api = require("modules/clusterio/api")
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[exp-roles]
|
||||||
|
game-message-assign=__1__ 已被 __3__ 指派到身分組 __2__
|
||||||
|
game-message-unassign=__1__ 已被 __3__ 取消指派到身分組 __2__
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[exp-roles]
|
||||||
|
game-message-assign=__1__ 已被 __3__ 指派到身分組 __2__
|
||||||
|
game-message-unassign=__1__ 已被 __3__ 取消指派到身分組 __2__
|
||||||
@@ -7,7 +7,9 @@
|
|||||||
"repository": "explosivegaming/ExpCluster",
|
"repository": "explosivegaming/ExpCluster",
|
||||||
"main": "dist/node/index.js",
|
"main": "dist/node/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "tsc --build && webpack-cli --env production"
|
"prepare": "tsc --build && webpack-cli --env production",
|
||||||
|
"test": "tap --disable-coverage --allow-empty-coverage test/*.test.js",
|
||||||
|
"coverage": "tap --coverage-report=text test/*.test.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@@ -24,6 +26,7 @@
|
|||||||
"@clusterio/host": "workspace:^",
|
"@clusterio/host": "workspace:^",
|
||||||
"@clusterio/lib": "workspace:^",
|
"@clusterio/lib": "workspace:^",
|
||||||
"@clusterio/web_ui": "workspace:^",
|
"@clusterio/web_ui": "workspace:^",
|
||||||
|
"@expcluster/scenario": "workspace:^",
|
||||||
"@types/node": "catalog:",
|
"@types/node": "catalog:",
|
||||||
"@types/react": "catalog:",
|
"@types/react": "catalog:",
|
||||||
"antd": "catalog:",
|
"antd": "catalog:",
|
||||||
@@ -32,6 +35,8 @@
|
|||||||
"react-router-dom": "catalog:",
|
"react-router-dom": "catalog:",
|
||||||
"typescript": "catalog:",
|
"typescript": "catalog:",
|
||||||
"webpack": "catalog:",
|
"webpack": "catalog:",
|
||||||
|
"fengari": "^0.1.5",
|
||||||
|
"tap": "^21.1.0",
|
||||||
"webpack-cli": "catalog:",
|
"webpack-cli": "catalog:",
|
||||||
"webpack-merge": "catalog:"
|
"webpack-merge": "catalog:"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,268 @@
|
|||||||
|
import { RoleColor } from "./messages";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A role created by the seed, as the scenario defined it before roles moved to
|
||||||
|
* the controller. `parent` stands in for the inheritance
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
export interface SeedRole {
|
||||||
|
name: string;
|
||||||
|
shortHand: string;
|
||||||
|
color: RoleColor | null;
|
||||||
|
priority?: number;
|
||||||
|
autoAssignHours?: number;
|
||||||
|
blockAutoAssign?: boolean;
|
||||||
|
/** Uses the controller's default role rather than creating one. */
|
||||||
|
isDefault?: boolean;
|
||||||
|
/** Uses the controller's admin role rather than creating one. */
|
||||||
|
isAdmin?: boolean;
|
||||||
|
/** Name of the role whose permissions are also granted, applied recursively. */
|
||||||
|
parent?: string;
|
||||||
|
permissions: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const seedRoles: SeedRole[] = [
|
||||||
|
{
|
||||||
|
name: "System",
|
||||||
|
shortHand: "SYS",
|
||||||
|
color: null,
|
||||||
|
isAdmin: true,
|
||||||
|
permissions: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Senior Administrator",
|
||||||
|
shortHand: "SAdmin",
|
||||||
|
color: new RoleColor(233, 63, 233),
|
||||||
|
parent: "Administrator",
|
||||||
|
permissions: [
|
||||||
|
"exp_scenario.command._rcon",
|
||||||
|
"exp_scenario.command.debug",
|
||||||
|
"exp_scenario.command.set_cheat_mode",
|
||||||
|
"exp_scenario.command.research_all",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Administrator",
|
||||||
|
shortHand: "Admin",
|
||||||
|
color: new RoleColor(233, 63, 233),
|
||||||
|
parent: "Moderator",
|
||||||
|
permissions: [
|
||||||
|
"exp_scenario.gui.warp_list.bypass_proximity",
|
||||||
|
"exp_scenario.gui.warp_list.bypass_cooldown",
|
||||||
|
"exp_scenario.command.connect_all",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Moderator",
|
||||||
|
shortHand: "Mod",
|
||||||
|
color: new RoleColor(0, 170, 0),
|
||||||
|
parent: "Trainee",
|
||||||
|
permissions: [
|
||||||
|
"exp_scenario.player.instant_respawn",
|
||||||
|
"exp_scenario.command.assign_role",
|
||||||
|
"exp_scenario.command.unassign_role",
|
||||||
|
"exp_scenario.command.repair",
|
||||||
|
"exp_scenario.command.kill.always",
|
||||||
|
"exp_scenario.command.tag_clear.always",
|
||||||
|
"exp_scenario.command.spawn.always",
|
||||||
|
"exp_scenario.command.clear_reports",
|
||||||
|
"exp_scenario.command.clear_warnings",
|
||||||
|
"exp_scenario.command.clear_script_warnings",
|
||||||
|
"exp_scenario.command.clear_last_warnings",
|
||||||
|
"exp_scenario.command.clear_inventory",
|
||||||
|
"exp_scenario.command.kill_enemies",
|
||||||
|
"exp_scenario.command.remove_enemies",
|
||||||
|
"exp_scenario.command.home",
|
||||||
|
"exp_scenario.command.set_home",
|
||||||
|
"exp_scenario.command.get_home",
|
||||||
|
"exp_scenario.command.return",
|
||||||
|
"exp_scenario.command.connect_player",
|
||||||
|
"exp_scenario.command.set_bot_queue",
|
||||||
|
"exp_scenario.command.set_game_speed",
|
||||||
|
"exp_scenario.command.set_friendly_fire",
|
||||||
|
"exp_scenario.command.set_always_day",
|
||||||
|
"exp_scenario.command.set_pollution_enabled",
|
||||||
|
"exp_scenario.command.clear_pollution",
|
||||||
|
"exp_scenario.gui.rocket_info.toggle_active",
|
||||||
|
"exp_scenario.gui.rocket_info.remote_launch",
|
||||||
|
"exp_scenario.gui.bonus",
|
||||||
|
"exp_scenario.decon.fast_trees",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Trainee",
|
||||||
|
shortHand: "TrMod",
|
||||||
|
color: new RoleColor(0, 170, 0),
|
||||||
|
parent: "Veteran",
|
||||||
|
permissions: [
|
||||||
|
"exp_scenario.player.admin",
|
||||||
|
"exp_scenario.player.spectator",
|
||||||
|
"exp_scenario.bypass.reports",
|
||||||
|
"exp_scenario.command.admin_chat",
|
||||||
|
"exp_scenario.command.goto",
|
||||||
|
"exp_scenario.command.teleport",
|
||||||
|
"exp_scenario.command.bring",
|
||||||
|
"exp_scenario.command.create_warning",
|
||||||
|
"exp_scenario.command.get_warnings",
|
||||||
|
"exp_scenario.command.get_reports",
|
||||||
|
"exp_scenario.command.protect_entity",
|
||||||
|
"exp_scenario.command.protect_area",
|
||||||
|
"exp_scenario.command.protect_tag",
|
||||||
|
"exp_scenario.command.jail",
|
||||||
|
"exp_scenario.command.unjail",
|
||||||
|
"exp_scenario.gui.player_list.kick",
|
||||||
|
"exp_scenario.gui.player_list.ban",
|
||||||
|
"exp_scenario.command.spectate",
|
||||||
|
"exp_scenario.command.follow",
|
||||||
|
"exp_scenario.command.search",
|
||||||
|
"exp_scenario.command.search_online",
|
||||||
|
"exp_scenario.command.search_amount",
|
||||||
|
"exp_scenario.command.search_recent",
|
||||||
|
"exp_scenario.command.clear_blueprints_surface",
|
||||||
|
"exp_scenario.gui.playerdata",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Board Member",
|
||||||
|
shortHand: "Board",
|
||||||
|
color: new RoleColor(247, 246, 54),
|
||||||
|
parent: "Sponsor",
|
||||||
|
permissions: [
|
||||||
|
"exp_scenario.command.goto",
|
||||||
|
"exp_scenario.command.repair",
|
||||||
|
"exp_scenario.command.spectate",
|
||||||
|
"exp_scenario.command.follow",
|
||||||
|
"exp_scenario.gui.playerdata",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Senior Backer",
|
||||||
|
shortHand: "Backer",
|
||||||
|
color: new RoleColor(238, 172, 44),
|
||||||
|
parent: "Sponsor",
|
||||||
|
permissions: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Sponsor",
|
||||||
|
shortHand: "Spon",
|
||||||
|
color: new RoleColor(238, 172, 44),
|
||||||
|
parent: "Supporter",
|
||||||
|
permissions: [
|
||||||
|
"exp_scenario.bypass.reports",
|
||||||
|
"exp_scenario.player.instant_respawn",
|
||||||
|
"exp_scenario.gui.rocket_info.toggle_active",
|
||||||
|
"exp_scenario.gui.rocket_info.remote_launch",
|
||||||
|
"exp_scenario.gui.bonus",
|
||||||
|
"exp_scenario.command.home",
|
||||||
|
"exp_scenario.command.set_home",
|
||||||
|
"exp_scenario.command.get_home",
|
||||||
|
"exp_scenario.command.return",
|
||||||
|
"exp_scenario.decon.fast_trees",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Supporter",
|
||||||
|
shortHand: "Sup",
|
||||||
|
color: new RoleColor(230, 99, 34),
|
||||||
|
parent: "Veteran",
|
||||||
|
permissions: [
|
||||||
|
"exp_scenario.player.spectator",
|
||||||
|
"exp_scenario.command.tag_color",
|
||||||
|
"exp_scenario.command.jail",
|
||||||
|
"exp_scenario.command.unjail",
|
||||||
|
"exp_scenario.command.set_join_message",
|
||||||
|
"exp_scenario.command.remove_join_message",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Partner",
|
||||||
|
shortHand: "Part",
|
||||||
|
color: new RoleColor(140, 120, 200),
|
||||||
|
parent: "Veteran",
|
||||||
|
permissions: [
|
||||||
|
"exp_scenario.player.spectator",
|
||||||
|
"exp_scenario.command.jail",
|
||||||
|
"exp_scenario.command.unjail",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Veteran",
|
||||||
|
shortHand: "Vet",
|
||||||
|
color: new RoleColor(140, 120, 200),
|
||||||
|
parent: "Member",
|
||||||
|
autoAssignHours: 10,
|
||||||
|
permissions: [
|
||||||
|
"exp_scenario.chat.commands",
|
||||||
|
"exp_scenario.command.clear_ground_items",
|
||||||
|
"exp_scenario.command.clear_blueprints",
|
||||||
|
"exp_scenario.command.set_trains_to_automatic",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Member",
|
||||||
|
shortHand: "Mem",
|
||||||
|
color: new RoleColor(24, 172, 188),
|
||||||
|
parent: "Regular",
|
||||||
|
permissions: [
|
||||||
|
"exp_scenario.bypass.deconstruction_log",
|
||||||
|
"exp_scenario.gui.task_list.add",
|
||||||
|
"exp_scenario.gui.task_list.edit",
|
||||||
|
"exp_scenario.gui.warp_list.add",
|
||||||
|
"exp_scenario.gui.warp_list.edit",
|
||||||
|
"exp_scenario.gui.surveillance",
|
||||||
|
"exp_scenario.gui.vlayer_edit",
|
||||||
|
"exp_scenario.gui.tool",
|
||||||
|
"exp_scenario.command.save_quickbar",
|
||||||
|
"exp_scenario.command.vlayer_info",
|
||||||
|
"exp_scenario.command.lawnmower",
|
||||||
|
"exp_scenario.command.waterfill",
|
||||||
|
"exp_scenario.command.artillery",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Regular",
|
||||||
|
shortHand: "Reg",
|
||||||
|
color: new RoleColor(79, 155, 163),
|
||||||
|
autoAssignHours: 3,
|
||||||
|
permissions: [
|
||||||
|
"exp_scenario.command.kill",
|
||||||
|
"exp_scenario.command.rainbow",
|
||||||
|
"exp_scenario.command.spawn",
|
||||||
|
"exp_scenario.command.me",
|
||||||
|
"exp_scenario.decon.standard",
|
||||||
|
"exp_scenario.bypass.entity_protection",
|
||||||
|
"exp_scenario.bypass.nuke_protection",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Jail",
|
||||||
|
shortHand: "Jail",
|
||||||
|
color: new RoleColor(50, 50, 50),
|
||||||
|
priority: 1,
|
||||||
|
blockAutoAssign: true,
|
||||||
|
permissions: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Guest",
|
||||||
|
shortHand: "",
|
||||||
|
color: new RoleColor(185, 187, 160),
|
||||||
|
isDefault: true,
|
||||||
|
permissions: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/** The permissions a seed role grants, including those of its parents. */
|
||||||
|
export function flattenSeedPermissions(role: SeedRole, roles = seedRoles) {
|
||||||
|
const permissions = new Set<string>();
|
||||||
|
const seen = new Set<string>();
|
||||||
|
let current: SeedRole | undefined = role;
|
||||||
|
while (current && !seen.has(current.name)) {
|
||||||
|
seen.add(current.name);
|
||||||
|
for (const permission of current.permissions) {
|
||||||
|
permissions.add(permission);
|
||||||
|
}
|
||||||
|
current = roles.find(other => other.name === current!.parent);
|
||||||
|
}
|
||||||
|
return permissions;
|
||||||
|
}
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
"use strict";
|
||||||
|
const t = require("tap");
|
||||||
|
const lib = require("@clusterio/lib");
|
||||||
|
const { Controller } = require("@clusterio/controller");
|
||||||
|
const { ControllerPlugin } = require("../dist/node/controller");
|
||||||
|
const messages = require("../dist/node/messages");
|
||||||
|
const { seedRoles } = require("../dist/node/seed");
|
||||||
|
|
||||||
|
// Importing this defines the exp_scenario permissions the seed grants
|
||||||
|
require("@expcluster/scenario/dist/node/permissions");
|
||||||
|
|
||||||
|
// The controller validates message classes against the link registry
|
||||||
|
lib.Link.register(messages.RoleUpdatedEvent);
|
||||||
|
lib.Link.register(messages.AssignmentUpdatedEvent);
|
||||||
|
lib.Link.register(messages.RoleListRequest);
|
||||||
|
lib.Link.register(messages.RoleMetaUpdateRequest);
|
||||||
|
lib.Link.register(messages.SeedRolesRequest);
|
||||||
|
lib.Link.register(messages.AssignmentListRequest);
|
||||||
|
lib.Link.register(messages.AssignmentUpdateRequest);
|
||||||
|
|
||||||
|
const logger = { child: () => logger, info: () => {}, warn: () => {}, error: () => {}, verbose: () => {} };
|
||||||
|
|
||||||
|
// Silence the singleton logger to avoid polluting the test output
|
||||||
|
lib.logger.silent = true;
|
||||||
|
t.after(() => {
|
||||||
|
lib.logger.silent = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Build a plugin around a real controller, which is side effect free while not started. */
|
||||||
|
async function startPlugin(t2, { roles = [] } = {}) {
|
||||||
|
const controllerConfig = new lib.ControllerConfig("controller", {
|
||||||
|
"controller.database_directory": t2.testdir(),
|
||||||
|
"controller.default_role_id": lib.Role.DefaultPlayerRoleId,
|
||||||
|
});
|
||||||
|
const controller = new Controller(logger, [], controllerConfig);
|
||||||
|
for (const role of roles) {
|
||||||
|
controller.roles.set(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spies which record and then defer to the real behaviour
|
||||||
|
const state = { broadcasts: [], permissionUpdates: [] };
|
||||||
|
const broadcast = controller.subscriptions.broadcast.bind(controller.subscriptions);
|
||||||
|
controller.subscriptions.broadcast = event => {
|
||||||
|
state.broadcasts.push(event);
|
||||||
|
broadcast(event);
|
||||||
|
};
|
||||||
|
const permissionsUpdated = controller.userPermissionsUpdated.bind(controller);
|
||||||
|
controller.userPermissionsUpdated = user => {
|
||||||
|
state.permissionUpdates.push(user.name);
|
||||||
|
permissionsUpdated(user);
|
||||||
|
};
|
||||||
|
|
||||||
|
const plugin = new ControllerPlugin({ name: "exp_roles" }, controller, undefined, logger);
|
||||||
|
await plugin.init();
|
||||||
|
return { plugin, controller, state };
|
||||||
|
}
|
||||||
|
|
||||||
|
const role = (id, name, permissions = []) => new lib.Role(id, name, "", new Set(permissions));
|
||||||
|
|
||||||
|
t.test("class ControllerPlugin", t2 => {
|
||||||
|
t2.test(".init() and .sweepRoleMeta() manage the role properties", async t3 => {
|
||||||
|
const { plugin } = await startPlugin(t3, { roles: [role(0, "Cluster Admin"), role(5, "Moderator")] });
|
||||||
|
|
||||||
|
t3.strictSame(plugin.roleMeta.get(0).order, 1, "the first role is ordered first");
|
||||||
|
t3.strictSame(plugin.roleMeta.get(5).order, 2, "the next role is ordered after it");
|
||||||
|
|
||||||
|
plugin.roleMeta.set(new messages.RoleMetaRecord(99, 3));
|
||||||
|
plugin.sweepRoleMeta();
|
||||||
|
t3.notOk(plugin.roleMeta.get(99), "properties without a role are removed");
|
||||||
|
t3.ok(plugin.roleMeta.get(5), "properties with a role are kept");
|
||||||
|
});
|
||||||
|
|
||||||
|
t2.test(".buildRoleRecord() combines the role with its properties", async t3 => {
|
||||||
|
const { plugin, controller } = await startPlugin(t3, {
|
||||||
|
roles: [role(1, "Player"), role(5, "Moderator", ["exp_scenario.command.kill"])],
|
||||||
|
});
|
||||||
|
plugin.roleMeta.set(new messages.RoleMetaRecord(5, 2, 1, "Mod"));
|
||||||
|
|
||||||
|
const record = plugin.buildRoleRecord(controller.roles.get(5));
|
||||||
|
t3.strictSame(record.name, "Moderator", "the name comes from the role");
|
||||||
|
t3.strictSame(record.permissions, ["exp_scenario.command.kill"], "the permissions come from the role");
|
||||||
|
t3.strictSame(record.meta.shortHand, "Mod", "the properties come from the datastore");
|
||||||
|
t3.notOk(record.isDefault, "not the default role");
|
||||||
|
t3.ok(plugin.buildRoleRecord(controller.roles.get(1)).isDefault, "the default role is marked");
|
||||||
|
});
|
||||||
|
|
||||||
|
t2.test(".rolesUpdated() and .roleMetaUpdated() broadcast to subscribers", async t3 => {
|
||||||
|
const { plugin, controller, state } = await startPlugin(t3, { roles: [role(1, "Player")] });
|
||||||
|
|
||||||
|
state.broadcasts.length = 0;
|
||||||
|
controller.roles.set(role(5, "Moderator"));
|
||||||
|
const updated = state.broadcasts
|
||||||
|
.filter(event => event instanceof messages.RoleUpdatedEvent)
|
||||||
|
.flatMap(event => event.updates.map(update => update.name));
|
||||||
|
t3.ok(updated.includes("Moderator"), "a new role is broadcast");
|
||||||
|
|
||||||
|
state.broadcasts.length = 0;
|
||||||
|
plugin.roleMeta.set(new messages.RoleMetaRecord(5, 2, 0, "Mod"));
|
||||||
|
t3.ok(state.broadcasts.some(
|
||||||
|
event => event instanceof messages.RoleUpdatedEvent && event.updates[0].meta.shortHand === "Mod"
|
||||||
|
), "a property change is broadcast as its role");
|
||||||
|
|
||||||
|
state.broadcasts.length = 0;
|
||||||
|
await plugin.onControllerConfigFieldChanged("controller.default_role_id");
|
||||||
|
t3.ok(
|
||||||
|
state.broadcasts.some(event => event instanceof messages.RoleUpdatedEvent),
|
||||||
|
"a default role change rebroadcasts",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
t2.test(".handleRoleSubscription() replays only newer records", async t3 => {
|
||||||
|
const { plugin, controller } = await startPlugin(t3, { roles: [role(1, "Player")] });
|
||||||
|
controller.roles.set(role(5, "Moderator"));
|
||||||
|
const updatedAtMs = plugin.buildRoleRecord(controller.roles.get(5)).updatedAtMs;
|
||||||
|
|
||||||
|
const all = await plugin.handleRoleSubscription({ lastRequestTimeMs: 0 });
|
||||||
|
t3.strictSame(all.updates.length, 2, "everything is replayed from the start");
|
||||||
|
const none = await plugin.handleRoleSubscription({ lastRequestTimeMs: updatedAtMs });
|
||||||
|
t3.strictSame(none, null, "nothing is replayed when up to date");
|
||||||
|
});
|
||||||
|
|
||||||
|
t2.test(".listAssignmentRecords() mirrors users without the default role", async t3 => {
|
||||||
|
const { plugin, controller } = await startPlugin(t3, { roles: [role(1, "Player"), role(5, "Moderator")] });
|
||||||
|
controller.users.createUser("alice").set("roleIds", new Set([lib.Role.DefaultPlayerRoleId, 5]));
|
||||||
|
controller.users.createUser("bob");
|
||||||
|
|
||||||
|
const records = plugin.listAssignmentRecords();
|
||||||
|
t3.strictSame(records.length, 1, "users with only the default role are left out");
|
||||||
|
t3.strictSame(records[0].name, "alice");
|
||||||
|
t3.strictSame([...records[0].roleIds], [5], "the default role is not listed");
|
||||||
|
});
|
||||||
|
|
||||||
|
t2.test(".handleAssignmentUpdateRequest() validates the user and roles", async t3 => {
|
||||||
|
const { plugin, controller, state } = await startPlugin(t3, {
|
||||||
|
roles: [role(1, "Player"), role(5, "Moderator"), role(6, "Regular")],
|
||||||
|
});
|
||||||
|
controller.users.createUser("alice").set("roleIds", new Set([5]));
|
||||||
|
|
||||||
|
await t3.rejects(
|
||||||
|
plugin.handleAssignmentUpdateRequest(new messages.AssignmentUpdateRequest("nobody", [], [])),
|
||||||
|
{ message: /does not exist/ }, "an unknown user is refused",
|
||||||
|
);
|
||||||
|
await t3.rejects(
|
||||||
|
plugin.handleAssignmentUpdateRequest(new messages.AssignmentUpdateRequest("alice", [99], [])),
|
||||||
|
{ message: /does not exist/ }, "an unknown role is refused",
|
||||||
|
);
|
||||||
|
|
||||||
|
await plugin.handleAssignmentUpdateRequest(new messages.AssignmentUpdateRequest("alice", [6], [5]));
|
||||||
|
t3.strictSame([...controller.users.getByName("alice").roleIds], [6], "roles are added and removed");
|
||||||
|
t3.ok(state.permissionUpdates.includes("alice"), "permission changes are pushed to the user");
|
||||||
|
});
|
||||||
|
|
||||||
|
t2.test(".applyAutoAssign() and .onPlayerEvent() grant roles from online time", async t3 => {
|
||||||
|
const { plugin, controller } = await startPlugin(t3, {
|
||||||
|
roles: [role(1, "Player"), role(6, "Regular"), role(7, "Jail")],
|
||||||
|
});
|
||||||
|
const onlineTime = ms => new Map([[1, lib.PlayerStats.fromJSON({ online_time_ms: ms })]]);
|
||||||
|
controller.users.createUser("newcomer").set("instanceStats", onlineTime(3600000));
|
||||||
|
controller.users.createUser("veteran").set("instanceStats", onlineTime(7200000));
|
||||||
|
const jailed = controller.users.createUser("jailed");
|
||||||
|
jailed.set("roleIds", new Set([7]));
|
||||||
|
jailed.set("instanceStats", onlineTime(7200000));
|
||||||
|
const roleIds = name => controller.users.getByName(name).roleIds;
|
||||||
|
|
||||||
|
// The blocking role is marked first, since setting properties applies auto assignment
|
||||||
|
plugin.roleMeta.set(new messages.RoleMetaRecord(7, 3, 1, "", "", null, null, true));
|
||||||
|
plugin.roleMeta.set(new messages.RoleMetaRecord(6, 2, 0, "", "", null, 7200000));
|
||||||
|
t3.ok(roleIds("veteran").has(6), "enough online time grants the role");
|
||||||
|
t3.notOk(roleIds("newcomer").has(6), "not enough online time does not");
|
||||||
|
t3.notOk(roleIds("jailed").has(6), "a blocking role prevents the grant");
|
||||||
|
|
||||||
|
// Any user update also applies auto assignment, so the leave path is
|
||||||
|
// observed directly rather than through a role change
|
||||||
|
const applied = [];
|
||||||
|
plugin.applyAutoAssign = names => applied.push(names);
|
||||||
|
await plugin.onPlayerEvent(undefined, { type: "leave", name: "veteran" });
|
||||||
|
t3.strictSame(applied, [["veteran"]], "a leave applies auto assignment for that user");
|
||||||
|
await plugin.onPlayerEvent(undefined, { type: "join", name: "veteran" });
|
||||||
|
t3.strictSame(applied.length, 1, "other player events do not");
|
||||||
|
});
|
||||||
|
|
||||||
|
t2.test(".handleRoleListRequest() returns the current roles", async t3 => {
|
||||||
|
const { plugin } = await startPlugin(t3, {
|
||||||
|
roles: [role(1, "Player"), role(5, "Moderator")],
|
||||||
|
});
|
||||||
|
plugin.roleMeta.set(new messages.RoleMetaRecord(5, 2, 0, "Mod"));
|
||||||
|
|
||||||
|
const response = await plugin.handleRoleListRequest(new messages.RoleListRequest());
|
||||||
|
t3.strictSame(response.length, 2, "the current roles are returned");
|
||||||
|
t3.ok(response.some(record => record.name === "Moderator"));
|
||||||
|
t3.ok(response.some(record => record.name === "Player"));
|
||||||
|
});
|
||||||
|
|
||||||
|
t2.test(".handleRoleMetaUpdateRequest() validates the role", async t3 => {
|
||||||
|
const { plugin } = await startPlugin(t3, { roles: [role(1, "Player"), role(5, "Moderator")] });
|
||||||
|
|
||||||
|
await t3.rejects(
|
||||||
|
plugin.handleRoleMetaUpdateRequest(new messages.RoleMetaUpdateRequest(new messages.RoleMetaRecord(99, 1))),
|
||||||
|
{ message: /does not exist/ }, "properties for an unknown role are refused",
|
||||||
|
);
|
||||||
|
|
||||||
|
await plugin.handleRoleMetaUpdateRequest(new messages.RoleMetaUpdateRequest(
|
||||||
|
new messages.RoleMetaRecord(5, 2, 0, "Mod"),
|
||||||
|
));
|
||||||
|
t3.strictSame(plugin.roleMeta.get(5).shortHand, "Mod", "the properties are stored");
|
||||||
|
});
|
||||||
|
|
||||||
|
t2.test(".handleAssignmentListRequest() returns the current assignments", async t3 => {
|
||||||
|
const { plugin, controller } = await startPlugin(t3, {
|
||||||
|
roles: [role(1, "Player"), role(5, "Moderator")],
|
||||||
|
});
|
||||||
|
controller.users.createUser("alice").set("roleIds", new Set([5]));
|
||||||
|
controller.users.createUser("bob");
|
||||||
|
|
||||||
|
const response = await plugin.handleAssignmentListRequest(new messages.AssignmentListRequest());
|
||||||
|
t3.strictSame(response.length, 1, "the current assignments are returned");
|
||||||
|
t3.strictSame(response[0].name, "alice");
|
||||||
|
t3.strictSame(response[0].roleIds, new Set([5]), "the default role is not listed");
|
||||||
|
});
|
||||||
|
|
||||||
|
t2.test(".handleAssignmentSubscription() replays only newer records", async t3 => {
|
||||||
|
const { plugin, controller } = await startPlugin(t3, { roles: [role(1, "Player"), role(5, "Moderator")] });
|
||||||
|
controller.users.createUser("alice").set("roleIds", new Set([5]));
|
||||||
|
const updatedAtMs = plugin.listAssignmentRecords()[0].updatedAtMs;
|
||||||
|
|
||||||
|
const all = await plugin.handleAssignmentSubscription({ lastRequestTimeMs: 0 });
|
||||||
|
t3.strictSame(all.updates.length, 1, "everything is replayed from the start");
|
||||||
|
const none = await plugin.handleAssignmentSubscription({ lastRequestTimeMs: updatedAtMs });
|
||||||
|
t3.strictSame(none, null, "nothing is replayed when up to date");
|
||||||
|
});
|
||||||
|
|
||||||
|
t2.test(".handleSeedRolesRequest() creates the roles and reuses them by name", async t3 => {
|
||||||
|
const { plugin, controller } = await startPlugin(t3, {
|
||||||
|
roles: [role(0, "Cluster Admin", ["core.admin"]), role(1, "Player")],
|
||||||
|
});
|
||||||
|
|
||||||
|
await plugin.handleSeedRolesRequest();
|
||||||
|
t3.strictSame(controller.roles.size, seedRoles.length, "every seed role exists");
|
||||||
|
|
||||||
|
const moderator = [...controller.roles.values()].find(other => other.name === "Moderator");
|
||||||
|
t3.ok(moderator.permissions.has("exp_scenario.command.jail"), "parent permissions are flattened in");
|
||||||
|
t3.ok(plugin.roleMeta.get(moderator.id), "the role properties are created");
|
||||||
|
t3.strictSame(plugin.roleMeta.get(moderator.id).shortHand, "Mod", "the properties match the seed");
|
||||||
|
|
||||||
|
await plugin.handleSeedRolesRequest();
|
||||||
|
t3.strictSame(controller.roles.size, seedRoles.length, "seeding again reuses the roles");
|
||||||
|
});
|
||||||
|
|
||||||
|
t2.end();
|
||||||
|
});
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
"use strict";
|
||||||
|
const t = require("tap");
|
||||||
|
const lib = require("@clusterio/lib");
|
||||||
|
const { Instance } = require("@clusterio/host");
|
||||||
|
const { InstancePlugin } = require("../dist/node/instance");
|
||||||
|
const { plugin: pluginDeclaration } = require("../dist/node/index");
|
||||||
|
const messages = require("../dist/node/messages");
|
||||||
|
|
||||||
|
// The instance validates message classes against the link registry, and the
|
||||||
|
// plugin's config fields must be defined before an InstanceConfig can set them
|
||||||
|
lib.Link.register(messages.RoleUpdatedEvent);
|
||||||
|
lib.Link.register(messages.AssignmentUpdatedEvent);
|
||||||
|
lib.Link.register(messages.RoleListRequest);
|
||||||
|
lib.Link.register(messages.AssignmentListRequest);
|
||||||
|
lib.Link.register(messages.AssignmentUpdateRequest);
|
||||||
|
lib.addPluginConfigFields([pluginDeclaration]);
|
||||||
|
|
||||||
|
const logger = { child: () => logger, info: () => {}, warn: () => {}, error: () => {}, verbose: () => {} };
|
||||||
|
|
||||||
|
class TestConnector extends lib.BaseConnector {
|
||||||
|
constructor() {
|
||||||
|
super(lib.Address.fromShorthand({ instanceId: 1 }), lib.Address.fromShorthand({ hostId: 1 }));
|
||||||
|
this.valid = true;
|
||||||
|
this.connected = true;
|
||||||
|
this.hasSession = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
send() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sampleRoles = () => [
|
||||||
|
new messages.RoleRecord(1, "Player", ["exp_scenario.gui.readme"], new messages.RoleMetaRecord(1, 2), true),
|
||||||
|
new messages.RoleRecord(5, "Moderator", ["exp_scenario.gui.readme", "core.admin"], new messages.RoleMetaRecord(5, 1)),
|
||||||
|
];
|
||||||
|
const sampleAssignments = () => [new messages.AssignmentRecord("alice", new Set([5]))];
|
||||||
|
|
||||||
|
/** Build a plugin around a real instance with spies on what leaves it, started on first use by each test. */
|
||||||
|
async function startPlugin(t2, { syncMode = "bidirectional", roles = sampleRoles() } = {}) {
|
||||||
|
const instanceConfig = new lib.InstanceConfig("host");
|
||||||
|
instanceConfig.set("instance.id", 1);
|
||||||
|
instanceConfig.set("instance.name", "test");
|
||||||
|
instanceConfig.set("exp_roles.sync_mode", syncMode);
|
||||||
|
|
||||||
|
const instance = new Instance(
|
||||||
|
{ assignGamePort: () => 1 }, new TestConnector(), t2.testdir(), "factorioDir", instanceConfig
|
||||||
|
);
|
||||||
|
|
||||||
|
// Spies which record the messages and commands leaving the instance
|
||||||
|
const state = { sent: [], rcons: [], warnings: [], failNext: null };
|
||||||
|
instance.server = {
|
||||||
|
handle: () => {},
|
||||||
|
sendRcon: async command => {
|
||||||
|
state.rcons.push(command);
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
};
|
||||||
|
instance.sendTo = async (dst, request) => {
|
||||||
|
state.sent.push(request);
|
||||||
|
if (state.failNext) {
|
||||||
|
const error = state.failNext;
|
||||||
|
state.failNext = null;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
if (request instanceof messages.RoleListRequest) {
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
if (request instanceof messages.AssignmentListRequest) {
|
||||||
|
return sampleAssignments();
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const plugin = new InstancePlugin({ name: "exp_roles" }, instance, {});
|
||||||
|
plugin.logger = { ...logger, warn: message => state.warnings.push(message) };
|
||||||
|
await plugin.init();
|
||||||
|
return { plugin, state };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The lua receiver and payload of a recorded rcon command. */
|
||||||
|
function decodeRcon(command) {
|
||||||
|
const match = command.match(/^\/sc exp_roles\.(\w+)\(helpers\.json_to_table\[=\[(.*)\]=\]\)$/s);
|
||||||
|
return { receiver: match[1], payload: JSON.parse(match[2]) };
|
||||||
|
}
|
||||||
|
|
||||||
|
t.test("class InstancePlugin", t2 => {
|
||||||
|
t2.test(".onStart() subscribes and initialises the lua module", async t3 => {
|
||||||
|
const { plugin, state } = await startPlugin(t3, { syncMode: "enabled" });
|
||||||
|
await plugin.onStart();
|
||||||
|
|
||||||
|
const subscribed = state.sent.filter(request => request instanceof lib.SubscriptionRequest);
|
||||||
|
t3.strictSame(subscribed.length, 2, "roles and assignments are subscribed to");
|
||||||
|
t3.ok(state.sent.some(request => request instanceof messages.RoleListRequest), "the roles are requested");
|
||||||
|
t3.ok(
|
||||||
|
state.sent.some(request => request instanceof messages.AssignmentListRequest),
|
||||||
|
"the assignments are requested",
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialise = decodeRcon(state.rcons[0]);
|
||||||
|
t3.strictSame(initialise.receiver, "initialise", "the lua module is initialised");
|
||||||
|
t3.strictSame(initialise.payload.permission_names, ["exp_scenario.gui.readme", "core.admin"],
|
||||||
|
"each permission name is sent once");
|
||||||
|
t3.strictSame(initialise.payload.roles[1].permissions, [0, 1], "roles reference permissions by index");
|
||||||
|
t3.strictSame(initialise.payload.assignments, [{ name: "alice", role_ids: [5] }], "the assignments are sent");
|
||||||
|
|
||||||
|
const emit = decodeRcon(state.rcons[1]);
|
||||||
|
t3.strictSame([emit.receiver, emit.payload], ["set_emit_events", false], "enabled does not emit changes back");
|
||||||
|
t3.strictSame(state.warnings.length, 0, "no warnings with a default role");
|
||||||
|
});
|
||||||
|
|
||||||
|
t2.test(".onStart() with bidirectional emits changes back", async t3 => {
|
||||||
|
const { plugin, state } = await startPlugin(t3);
|
||||||
|
await plugin.onStart();
|
||||||
|
const emit = decodeRcon(state.rcons[state.rcons.length - 1]);
|
||||||
|
t3.strictSame([emit.receiver, emit.payload], ["set_emit_events", true]);
|
||||||
|
});
|
||||||
|
|
||||||
|
t2.test(".onStart() with disabled does nothing", async t3 => {
|
||||||
|
const { plugin, state } = await startPlugin(t3, { syncMode: "disabled" });
|
||||||
|
await plugin.onStart();
|
||||||
|
t3.strictSame(state.sent.length, 0, "nothing is sent");
|
||||||
|
t3.strictSame(state.rcons.length, 0, "no commands are run");
|
||||||
|
});
|
||||||
|
|
||||||
|
t2.test(".onStart() warns when no default role is set", async t3 => {
|
||||||
|
const roles = sampleRoles().map(record => { record.isDefault = false; return record; });
|
||||||
|
const { plugin, state } = await startPlugin(t3, { roles });
|
||||||
|
await plugin.onStart();
|
||||||
|
t3.ok(state.warnings.some(message => /default role/.test(message)), "the warning names the default role");
|
||||||
|
});
|
||||||
|
|
||||||
|
t2.test(".handleRoleUpdatedEvent() and .handleAssignmentUpdatedEvent() forward to lua", async t3 => {
|
||||||
|
const { plugin, state } = await startPlugin(t3);
|
||||||
|
await plugin.handleRoleUpdatedEvent(new messages.RoleUpdatedEvent(sampleRoles()));
|
||||||
|
const roles = decodeRcon(state.rcons[0]);
|
||||||
|
t3.strictSame(roles.receiver, "receive_role_updates", "role updates are forwarded");
|
||||||
|
t3.strictSame(roles.payload[0].name, "Player", "the records are sent in plain form");
|
||||||
|
|
||||||
|
await plugin.handleAssignmentUpdatedEvent(new messages.AssignmentUpdatedEvent(sampleAssignments()));
|
||||||
|
const assignments = decodeRcon(state.rcons[1]);
|
||||||
|
t3.strictSame(assignments.receiver, "receive_assignment_updates", "assignment updates are forwarded");
|
||||||
|
});
|
||||||
|
|
||||||
|
t2.test(".handleRoleUpdatedEvent() and .handleAssignmentUpdatedEvent() are gated while disabled", async t3 => {
|
||||||
|
const { plugin, state } = await startPlugin(t3, { syncMode: "disabled" });
|
||||||
|
await plugin.handleRoleUpdatedEvent(new messages.RoleUpdatedEvent(sampleRoles()));
|
||||||
|
await plugin.handleAssignmentUpdatedEvent(new messages.AssignmentUpdatedEvent(sampleAssignments()));
|
||||||
|
t3.strictSame(state.rcons.length, 0, "no commands are run");
|
||||||
|
});
|
||||||
|
|
||||||
|
t2.test(".handleAssignmentUpdateIPC() sends the change and rolls back a refusal", async t3 => {
|
||||||
|
const { plugin, state } = await startPlugin(t3);
|
||||||
|
await plugin.handleAssignmentUpdateIPC({ name: "alice", assign: [5], unassign: undefined });
|
||||||
|
const request = state.sent[0];
|
||||||
|
t3.ok(request instanceof messages.AssignmentUpdateRequest, "the change is sent as a request");
|
||||||
|
t3.strictSame([request.name, request.assign, request.unassign], ["alice", [5], []], "the request names the change");
|
||||||
|
|
||||||
|
await plugin.handleAssignmentUpdateIPC({ name: "alice", assign: undefined, unassign: [5] });
|
||||||
|
t3.strictSame(state.sent[1].unassign, [5], "removals are sent the same way");
|
||||||
|
|
||||||
|
state.failNext = new Error("User 'alice' does not exist");
|
||||||
|
await plugin.handleAssignmentUpdateIPC({ name: "alice", assign: [5], unassign: undefined });
|
||||||
|
const rollback = decodeRcon(state.rcons[0]);
|
||||||
|
t3.strictSame(rollback.receiver, "reject_assignment", "the refusal is rolled back in lua");
|
||||||
|
t3.strictSame(rollback.payload, { name: "alice", role_ids: [5] }, "the rollback names the refused roles");
|
||||||
|
|
||||||
|
state.failNext = new Error("User 'alice' does not exist");
|
||||||
|
await plugin.handleAssignmentUpdateIPC({ name: "alice", assign: undefined, unassign: [5] });
|
||||||
|
t3.strictSame(state.rcons.length, 1, "a refused removal has nothing to roll back");
|
||||||
|
});
|
||||||
|
|
||||||
|
t2.test(".handleAssignmentUpdateIPC() is gated unless bidirectional", async t3 => {
|
||||||
|
const { plugin, state } = await startPlugin(t3, { syncMode: "enabled" });
|
||||||
|
await plugin.handleAssignmentUpdateIPC({ name: "alice", assign: [5], unassign: undefined });
|
||||||
|
t3.strictSame(state.sent.length, 0, "nothing is sent");
|
||||||
|
});
|
||||||
|
|
||||||
|
t2.test(".onInstanceConfigFieldChanged() toggles emit with the sync mode", async t3 => {
|
||||||
|
const { plugin, state } = await startPlugin(t3);
|
||||||
|
await plugin.onInstanceConfigFieldChanged("exp_roles.sync_mode", "enabled", "bidirectional");
|
||||||
|
const emit = decodeRcon(state.rcons[0]);
|
||||||
|
t3.strictSame([emit.receiver, emit.payload], ["set_emit_events", false], "leaving bidirectional stops emitting");
|
||||||
|
});
|
||||||
|
|
||||||
|
t2.end();
|
||||||
|
});
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
"use strict";
|
||||||
|
const t = require("tap");
|
||||||
|
const messages = require("../dist/node/messages");
|
||||||
|
const { testMatrix, testRoundTripJsonSerialisable } = require("../../test/common");
|
||||||
|
|
||||||
|
const fullMeta = new messages.RoleMetaRecord(
|
||||||
|
7, 3, 1, "Mod", "[Mod]", new messages.RoleColor(1, 2, 3), 3600000, true, 12345, false,
|
||||||
|
);
|
||||||
|
|
||||||
|
t.test("class RoleColor", t2 => {
|
||||||
|
testRoundTripJsonSerialisable(t2, messages.RoleColor, testMatrix(
|
||||||
|
[0, 255], // r
|
||||||
|
[0, 128], // g
|
||||||
|
[0, 1], // b
|
||||||
|
));
|
||||||
|
|
||||||
|
t2.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test("class RoleMetaRecord", t2 => {
|
||||||
|
testRoundTripJsonSerialisable(t2, messages.RoleMetaRecord, testMatrix(
|
||||||
|
[7], // id
|
||||||
|
[3], // order
|
||||||
|
[0, 1], // priority
|
||||||
|
["", "Mod"], // shortHand
|
||||||
|
["", "[Mod]"], // tag
|
||||||
|
[null, new messages.RoleColor(1, 2, 3)], // color
|
||||||
|
[null, 3600000], // autoAssignOnlineTimeMs
|
||||||
|
[false, true], // blockAutoAssign
|
||||||
|
[0, 12345], // updatedAtMs
|
||||||
|
[false, true], // isDeleted
|
||||||
|
));
|
||||||
|
|
||||||
|
t2.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test("class RoleRecord", t2 => {
|
||||||
|
testRoundTripJsonSerialisable(t2, messages.RoleRecord, testMatrix(
|
||||||
|
[7], // id
|
||||||
|
["Moderator"], // name
|
||||||
|
[[], ["a.b", "c.d"]], // permissions
|
||||||
|
[new messages.RoleMetaRecord(7, 3), fullMeta], // meta
|
||||||
|
[false, true], // isDefault
|
||||||
|
[0, 12345], // updatedAtMs
|
||||||
|
[false, true], // isDeleted
|
||||||
|
));
|
||||||
|
|
||||||
|
t2.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test("class AssignmentRecord", t2 => {
|
||||||
|
testRoundTripJsonSerialisable(t2, messages.AssignmentRecord, testMatrix(
|
||||||
|
["alice"], // name
|
||||||
|
[new Set(), new Set([5, 6])], // roleIds
|
||||||
|
[0, 12345], // updatedAtMs
|
||||||
|
[false, true], // isDeleted
|
||||||
|
));
|
||||||
|
|
||||||
|
t2.test("get id()", t3 => {
|
||||||
|
const record = new messages.AssignmentRecord("alice", new Set([5]), 12345);
|
||||||
|
t3.equal(record.id, "alice");
|
||||||
|
t3.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t2.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
const sampleRecord = new messages.RoleRecord(7, "Moderator", ["a.b"], fullMeta, true, 12345);
|
||||||
|
const sampleAssignment = new messages.AssignmentRecord("alice", new Set([5]), 12345);
|
||||||
|
|
||||||
|
t.test("class RoleUpdatedEvent", t2 => {
|
||||||
|
testRoundTripJsonSerialisable(t2, messages.RoleUpdatedEvent, testMatrix(
|
||||||
|
[[], [sampleRecord]], // updates
|
||||||
|
));
|
||||||
|
|
||||||
|
t2.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test("class AssignmentUpdatedEvent", t2 => {
|
||||||
|
testRoundTripJsonSerialisable(t2, messages.AssignmentUpdatedEvent, testMatrix(
|
||||||
|
[[], [sampleAssignment]], // updates
|
||||||
|
));
|
||||||
|
|
||||||
|
t2.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test("class RoleMetaUpdateRequest", t2 => {
|
||||||
|
testRoundTripJsonSerialisable(t2, messages.RoleMetaUpdateRequest, testMatrix(
|
||||||
|
[new messages.RoleMetaRecord(7, 3), fullMeta], // meta
|
||||||
|
));
|
||||||
|
|
||||||
|
t2.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test("class AssignmentUpdateRequest", t2 => {
|
||||||
|
testRoundTripJsonSerialisable(t2, messages.AssignmentUpdateRequest, testMatrix(
|
||||||
|
["alice"], // name
|
||||||
|
[[], [5]], // assign
|
||||||
|
[[], [6]], // unassign
|
||||||
|
));
|
||||||
|
|
||||||
|
t2.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test("encodeRolesForLua() sends each permission name once", t2 => {
|
||||||
|
const meta = id => new messages.RoleMetaRecord(id, id);
|
||||||
|
const encoded = messages.encodeRolesForLua([
|
||||||
|
new messages.RoleRecord(1, "A", ["p.one", "p.two"], meta(1)),
|
||||||
|
new messages.RoleRecord(2, "B", ["p.two", "p.three"], meta(2)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
t2.strictSame(encoded.permission_names, ["p.one", "p.two", "p.three"], "names are deduplicated");
|
||||||
|
t2.strictSame(encoded.roles[0].permissions, [0, 1], "indexes are zero based");
|
||||||
|
t2.strictSame(encoded.roles[1].permissions, [1, 2], "shared names reuse their index");
|
||||||
|
t2.end();
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
"use strict";
|
||||||
|
const path = require("node:path");
|
||||||
|
const t = require("tap");
|
||||||
|
const { reportLuaTests } = require("../../test/lua/runner");
|
||||||
|
|
||||||
|
const envFile = path.join(__dirname, "module", "env.lua");
|
||||||
|
|
||||||
|
// Each file runs in its own lua state, and each test in a fresh environment
|
||||||
|
t.test("control.lua", t2 => {
|
||||||
|
for (const file of ["lookup.lua", "players.lua", "assignment.lua", "sync.lua", "holders.lua"]) {
|
||||||
|
t2.test(file, t3 => reportLuaTests(t3, envFile, path.join(__dirname, "module", file)));
|
||||||
|
}
|
||||||
|
t2.end();
|
||||||
|
});
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
--- Tests for the assignment methods of module/control.lua
|
||||||
|
local Suite = ... --- @type Suite<ExpRoles.TestEnv> The suite this file adds its tests to, see env.lua
|
||||||
|
local test, check, eq, empty = Suite.test, Suite.check, Suite.eq, Suite.empty
|
||||||
|
|
||||||
|
--- alice is a moderator and bob a regular, with changes sent to the controller
|
||||||
|
local function setup(env)
|
||||||
|
local players = {
|
||||||
|
alice = env.add_player("alice"),
|
||||||
|
bob = env.add_player("bob"),
|
||||||
|
}
|
||||||
|
env.initialise{
|
||||||
|
env.assignment("alice", { "Moderator" }),
|
||||||
|
env.assignment("bob", { "Regular" }),
|
||||||
|
}
|
||||||
|
env.Roles.set_emit_events(true)
|
||||||
|
env.reset_log()
|
||||||
|
return players
|
||||||
|
end
|
||||||
|
|
||||||
|
test(".assign() applies locally and is sent to the controller", function(env)
|
||||||
|
local players = setup(env)
|
||||||
|
local moderator = env.R("Moderator")
|
||||||
|
moderator:assign(players.bob, { by_player_name = "alice" })
|
||||||
|
eq(env.sent, {
|
||||||
|
{ channel = "exp_roles:assignment_update", data = { name = "bob", assign = { moderator.id } } },
|
||||||
|
}, "the assignment is sent to the controller")
|
||||||
|
check(moderator:has_player(players.bob), "the role applies before confirmation")
|
||||||
|
eq(env.events, {
|
||||||
|
{
|
||||||
|
name = env.Roles.events.on_player_roles_changed,
|
||||||
|
tick = 1,
|
||||||
|
player_index = players.bob.index,
|
||||||
|
by_player_index = players.alice.index,
|
||||||
|
assigned = { moderator.id },
|
||||||
|
unassigned = {},
|
||||||
|
},
|
||||||
|
}, "the event carries the change")
|
||||||
|
eq(env.printed, {
|
||||||
|
{ "exp-roles.game-message-assign", "bob", "Moderator", "alice" },
|
||||||
|
}, "the change is announced")
|
||||||
|
eq(env.sounds, { "bob:utility/achievement_unlocked" }, "the assign sound plays")
|
||||||
|
|
||||||
|
local sd = env.script_data()
|
||||||
|
eq(sd.local_players.bob, { moderator.id }, "the role is held locally")
|
||||||
|
eq(sd.pending.bob, { moderator.id }, "the role is pending")
|
||||||
|
|
||||||
|
env.reset_log()
|
||||||
|
moderator:assign(players.bob)
|
||||||
|
empty(env.sent, "assigning a held role sends nothing")
|
||||||
|
empty(env.events, "assigning a held role raises nothing")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test(".assign() defaults the by player to game.player", function(env)
|
||||||
|
local players = setup(env)
|
||||||
|
game.player = players.alice
|
||||||
|
env.R("Jail"):assign(players.bob, { silent = true })
|
||||||
|
eq(env.events, {
|
||||||
|
{
|
||||||
|
name = env.Roles.events.on_player_roles_changed,
|
||||||
|
tick = 1,
|
||||||
|
player_index = players.bob.index,
|
||||||
|
by_player_index = players.alice.index,
|
||||||
|
assigned = { env.R("Jail").id },
|
||||||
|
unassigned = {},
|
||||||
|
},
|
||||||
|
}, "the acting player is game.player")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("receive_assignment_updates() releases the local hold once confirmed", function(env)
|
||||||
|
local players = setup(env)
|
||||||
|
env.R("Moderator"):assign(players.bob)
|
||||||
|
env.reset_log()
|
||||||
|
env.Roles.receive_assignment_updates{ env.assignment("bob", { "Regular", "Moderator" }) }
|
||||||
|
empty(env.events, "a confirmation raises no events")
|
||||||
|
empty(env.printed, "a confirmation announces nothing")
|
||||||
|
|
||||||
|
local sd = env.script_data()
|
||||||
|
check(sd.local_players.bob == nil and sd.pending.bob == nil, "the role is no longer held locally")
|
||||||
|
check(env.R("Moderator"):has_player(players.bob), "the role is still held after confirmation")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test(".unassign() removes a synced role", function(env)
|
||||||
|
local players = setup(env)
|
||||||
|
local regular = env.R("Regular")
|
||||||
|
regular:unassign(players.bob, { by_player_name = "alice", silent = true })
|
||||||
|
eq(env.sent, {
|
||||||
|
{ channel = "exp_roles:assignment_update", data = { name = "bob", unassign = { regular.id } } },
|
||||||
|
}, "the unassignment is sent to the controller")
|
||||||
|
check(not regular:has_player(players.bob), "the role is removed locally")
|
||||||
|
eq(env.events, {
|
||||||
|
{
|
||||||
|
name = env.Roles.events.on_player_roles_changed,
|
||||||
|
tick = 1,
|
||||||
|
player_index = players.bob.index,
|
||||||
|
by_player_index = players.alice.index,
|
||||||
|
assigned = {},
|
||||||
|
unassigned = { regular.id },
|
||||||
|
},
|
||||||
|
}, "the event carries the change")
|
||||||
|
empty(env.printed, "silent suppresses the announcement")
|
||||||
|
eq(env.sounds, { "bob:utility/game_lost" }, "the unassign sound plays")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("reject_assignment() rolls the assignment back", function(env)
|
||||||
|
local players = setup(env)
|
||||||
|
local jail = env.R("Jail")
|
||||||
|
jail:assign(players.alice)
|
||||||
|
check(jail:has_player(players.alice), "the role applies before rejection")
|
||||||
|
|
||||||
|
env.reset_log()
|
||||||
|
env.Roles.reject_assignment{ name = "alice", role_ids = { jail.id } }
|
||||||
|
check(not jail:has_player(players.alice), "the rejected role is rolled back")
|
||||||
|
empty(env.sent, "the rollback is not sent to the controller")
|
||||||
|
eq(env.events, {
|
||||||
|
{
|
||||||
|
name = env.Roles.events.on_player_roles_changed,
|
||||||
|
tick = 1,
|
||||||
|
player_index = players.alice.index,
|
||||||
|
by_player_index = 0,
|
||||||
|
assigned = {},
|
||||||
|
unassigned = { jail.id },
|
||||||
|
},
|
||||||
|
}, "the rollback raises the event")
|
||||||
|
|
||||||
|
local sd = env.script_data()
|
||||||
|
check(sd.local_players.alice == nil and sd.pending.alice == nil, "the rollback clears the local state")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test(".assign() with local_only never reaches the controller", function(env)
|
||||||
|
local players = setup(env)
|
||||||
|
local regular = env.R("Regular")
|
||||||
|
regular:assign(players.alice, { silent = true, local_only = true })
|
||||||
|
empty(env.sent, "the assignment is not sent")
|
||||||
|
check(regular:has_player(players.alice), "the role applies")
|
||||||
|
|
||||||
|
local sd = env.script_data()
|
||||||
|
check(sd.pending.alice == nil, "the role is not pending")
|
||||||
|
eq(sd.local_players.alice, { regular.id }, "the role is held locally")
|
||||||
|
|
||||||
|
env.Roles.receive_assignment_updates{ env.assignment("alice", { "Moderator" }) }
|
||||||
|
check(regular:has_player(players.alice), "the role survives controller updates")
|
||||||
|
|
||||||
|
env.reset_log()
|
||||||
|
regular:unassign(players.alice, { local_only = true })
|
||||||
|
check(not regular:has_player(players.alice), "the role is removed")
|
||||||
|
empty(env.sent, "the removal is not sent")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test(".assign() of a higher priority role suppresses the rest", function(env)
|
||||||
|
local players = setup(env)
|
||||||
|
local jail = env.R("Jail")
|
||||||
|
jail:assign(players.alice, { by_player_name = "<server>", silent = true })
|
||||||
|
eq(Suite.names(env.Roles.get_player_roles(players.alice)), { "Jail" }, "only the jail role applies")
|
||||||
|
check(env.R("Moderator"):has_player(players.alice), "a suppressed role is still held")
|
||||||
|
check(not env.Roles.player_has_permission(players.alice, "exp_scenario.command.kill"), "permissions are lost")
|
||||||
|
check(not env.Roles.player_has_permission(players.alice, "exp_scenario.gui.readme"), "the default role is lost")
|
||||||
|
check(not env.Roles.player_outranks(players.alice, players.bob), "a jailed player no longer outranks")
|
||||||
|
eq(env.events, {
|
||||||
|
{
|
||||||
|
name = env.Roles.events.on_player_roles_changed,
|
||||||
|
tick = 1,
|
||||||
|
player_index = players.alice.index,
|
||||||
|
by_player_index = 0,
|
||||||
|
assigned = { jail.id },
|
||||||
|
unassigned = {},
|
||||||
|
},
|
||||||
|
}, "the event lists only the jail role")
|
||||||
|
|
||||||
|
env.reset_log()
|
||||||
|
jail:unassign(players.alice, { by_player_name = "<server>", silent = true })
|
||||||
|
eq(Suite.sorted(Suite.names(env.Roles.get_player_roles(players.alice))), { "Moderator", "Player" },
|
||||||
|
"unjail restores the roles")
|
||||||
|
eq(env.events, {
|
||||||
|
{
|
||||||
|
name = env.Roles.events.on_player_roles_changed,
|
||||||
|
tick = 1,
|
||||||
|
player_index = players.alice.index,
|
||||||
|
by_player_index = 0,
|
||||||
|
assigned = {},
|
||||||
|
unassigned = { jail.id },
|
||||||
|
},
|
||||||
|
}, "the unjail event lists only the jail role")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test(".assign() ignores the server", function(env)
|
||||||
|
setup(env)
|
||||||
|
env.R("Moderator"):assign(env.server)
|
||||||
|
empty(env.sent, "assigning to the server sends nothing")
|
||||||
|
empty(env.events, "assigning to the server raises nothing")
|
||||||
|
end)
|
||||||
|
|
||||||
|
return Suite.run()
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
--[[-- Test environment for module/control.lua
|
||||||
|
The module specific extension between the shared stubs and the test files: it
|
||||||
|
extends each environment with a fresh copy of the roles module and the
|
||||||
|
fixtures below, and hands each test file a suite built around that.
|
||||||
|
|
||||||
|
Run as a chunk by test/lua/runner.js, receiving the shared folder. The suite
|
||||||
|
returned here becomes `...` in each test file.
|
||||||
|
]]
|
||||||
|
|
||||||
|
local shared_root = ... --- @type string
|
||||||
|
local source = assert(debug.getinfo(1, "S")).source:gsub("\\", "/")
|
||||||
|
local plugin_root = assert(source:match("^@(.*)/test/module/env%.lua$"))
|
||||||
|
|
||||||
|
local Framework = assert(loadfile(shared_root .. "/framework.lua"))() --- @type Framework
|
||||||
|
|
||||||
|
--- Standard fixture: permission names sent once and referenced by zero based index
|
||||||
|
local permission_names = {
|
||||||
|
"core.admin", -- 0
|
||||||
|
"exp_scenario.command.kill", -- 1
|
||||||
|
"exp_scenario.command.jail", -- 2
|
||||||
|
"exp_scenario.player.admin", -- 3
|
||||||
|
"exp_scenario.gui.readme", -- 4
|
||||||
|
"exp_scenario.command.assign_role", -- 5
|
||||||
|
}
|
||||||
|
|
||||||
|
local function meta(order, extra)
|
||||||
|
local m = { id = 0, order = order }
|
||||||
|
for k, v in pairs(extra or {}) do m[k] = v end
|
||||||
|
return m
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Standard fixture: the roles as the controller would send them on initialise
|
||||||
|
local function role_records()
|
||||||
|
return {
|
||||||
|
{ id = 0, name = "Cluster Admin", permissions = { 0 }, meta = meta(1, { short_hand = "SYS" }) },
|
||||||
|
{ id = 5, name = "Moderator", permissions = { 1, 2, 3, 5 }, meta = meta(2, { color = { r = 0, g = 170, b = 0 } }) },
|
||||||
|
{ id = 6, name = "Regular", permissions = { 1 }, meta = meta(3) },
|
||||||
|
{ id = 7, name = "Jail", permissions = {}, meta = meta(4, { priority = 1, block_auto_assign = true }) },
|
||||||
|
{ id = 1, name = "Player", permissions = { 4 }, meta = meta(5), is_default = true },
|
||||||
|
{ id = 9, name = "Gone", permissions = {}, meta = meta(6), is_deleted = true },
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Fixture role ids by name, avoiding a search of the roles on every lookup
|
||||||
|
local role_ids = {}
|
||||||
|
for _, record in ipairs(role_records()) do
|
||||||
|
role_ids[record.name] = record.id
|
||||||
|
end
|
||||||
|
|
||||||
|
--- The environment given to each test: the stubs extended with a fresh copy
|
||||||
|
--- of the roles module and the fixture helpers
|
||||||
|
--- @class ExpRoles.TestEnv : Stubs
|
||||||
|
--- @field Roles ExpRoles A fresh copy of the roles module
|
||||||
|
--- @field R fun(name: string): ExpRoles.Role Get a fixture role by name
|
||||||
|
--- @field assignment fun(player_name: string, role_names: string[]): { name: string, role_ids: number[], is_deleted: boolean? }
|
||||||
|
--- @field script_data fun(): ExpRoles.ScriptData The module's script data, for asserting on internal state
|
||||||
|
--- @field initialise fun(assignments: table[]?) Load the standard fixture and the given assignments
|
||||||
|
|
||||||
|
return Framework.suite(function(env)
|
||||||
|
--- @cast env ExpRoles.TestEnv
|
||||||
|
env.Roles = assert(loadfile(plugin_root .. "/module/control.lua"))() --- @type ExpRoles
|
||||||
|
env.Roles.on_server_startup()
|
||||||
|
|
||||||
|
--- Get a fixture role by name, failing the test when it does not exist
|
||||||
|
--- Roles added by a test are found by a search instead
|
||||||
|
function env.R(name)
|
||||||
|
local role_id = role_ids[name]
|
||||||
|
if role_id then
|
||||||
|
return (assert(env.Roles.get_role(role_id), "No role with id " .. role_id))
|
||||||
|
end
|
||||||
|
return (assert(env.Roles.get_role_by_name(name), "No role named " .. name))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- An assignment record as the controller would send it, roles by name
|
||||||
|
function env.assignment(player_name, role_names)
|
||||||
|
local ids = {}
|
||||||
|
for index, role_name in ipairs(role_names) do
|
||||||
|
ids[index] = assert(role_ids[role_name], "No fixture role named " .. role_name)
|
||||||
|
end
|
||||||
|
return { name = player_name, role_ids = ids }
|
||||||
|
end
|
||||||
|
|
||||||
|
--- The module's script data, for asserting on internal state
|
||||||
|
function env.script_data()
|
||||||
|
--- @diagnostic disable-next-line: access-invisible
|
||||||
|
return env.Roles._script_data()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Load the standard fixture and the given assignments
|
||||||
|
function env.initialise(assignments)
|
||||||
|
env.Roles.initialise{
|
||||||
|
permission_names = permission_names,
|
||||||
|
roles = role_records(),
|
||||||
|
assignments = assignments or {},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
return env
|
||||||
|
end)
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
--- Tests for listing and printing to the players who hold a role
|
||||||
|
local Suite = ... --- @type Suite<ExpRoles.TestEnv> The suite this file adds its tests to, see test/lua/framework.lua
|
||||||
|
local test, check, eq = Suite.test, Suite.check, Suite.eq
|
||||||
|
|
||||||
|
--- alice and dave are moderators with dave offline, zed has never joined
|
||||||
|
local function setup(env)
|
||||||
|
local players = {
|
||||||
|
alice = env.add_player("alice"),
|
||||||
|
bob = env.add_player("bob"),
|
||||||
|
dave = env.add_player("dave", false),
|
||||||
|
}
|
||||||
|
env.initialise{
|
||||||
|
env.assignment("alice", { "Moderator" }),
|
||||||
|
env.assignment("bob", { "Regular" }),
|
||||||
|
env.assignment("dave", { "Moderator" }),
|
||||||
|
env.assignment("zed", { "Moderator" }),
|
||||||
|
}
|
||||||
|
env.Roles.set_emit_events(true)
|
||||||
|
return players
|
||||||
|
end
|
||||||
|
|
||||||
|
test(".get_player_names() includes players not on this map", function(env)
|
||||||
|
setup(env)
|
||||||
|
eq(Suite.sorted(env.R("Moderator"):get_player_names()), { "alice", "dave", "zed" },
|
||||||
|
"every player given the role is listed")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test(".get_player_names() counts a player in both lists once", function(env)
|
||||||
|
local players = setup(env)
|
||||||
|
env.R("Regular"):assign(players.alice, { silent = true, local_only = true })
|
||||||
|
env.Roles.receive_assignment_updates{ env.assignment("alice", { "Moderator", "Regular" }) }
|
||||||
|
|
||||||
|
local sd = env.script_data()
|
||||||
|
check(sd.local_players.alice ~= nil and sd.synced_players.alice ~= nil, "the role is held in both lists")
|
||||||
|
eq(Suite.sorted(env.R("Regular"):get_player_names()), { "alice", "bob" }, "the player is counted once")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test(".get_players() is limited to this map and can be filtered", function(env)
|
||||||
|
setup(env)
|
||||||
|
local moderator = env.R("Moderator")
|
||||||
|
eq(Suite.sorted(Suite.names(moderator:get_players())), { "alice", "dave" }, "players are limited to this map")
|
||||||
|
eq(Suite.names(moderator:get_players(true)), { "alice" }, "filtered to connected players")
|
||||||
|
eq(Suite.names(moderator:get_players(false)), { "dave" }, "filtered to offline players")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test(".print() reaches online holders", function(env)
|
||||||
|
setup(env)
|
||||||
|
env.reset_log()
|
||||||
|
eq(env.R("Moderator"):print("hello"), 1, "print returns the number of players reached")
|
||||||
|
eq(env.printed, { { "hello", to = "alice" } }, "only online holders receive the message")
|
||||||
|
eq(env.R("Jail"):print("hello"), 0, "a role with no online holders reaches nobody")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test(".print() over get_higher_roles() can repeat", function(env)
|
||||||
|
local players = setup(env)
|
||||||
|
env.R("Regular"):assign(players.alice, { silent = true, local_only = true })
|
||||||
|
env.reset_log()
|
||||||
|
for _, role in ipairs(env.Roles.get_higher_roles(env.R("Regular"))) do
|
||||||
|
role:print("hello")
|
||||||
|
end
|
||||||
|
|
||||||
|
local got = {}
|
||||||
|
for _, entry in ipairs(env.printed) do got[#got + 1] = entry.to end
|
||||||
|
-- alice holds two of the roles, so like the legacy system the message can repeat
|
||||||
|
eq(Suite.sorted(got), { "alice", "alice", "bob" }, "the online holders of each role are reached")
|
||||||
|
end)
|
||||||
|
|
||||||
|
return Suite.run()
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
--- Tests for the role lookup functions of module/control.lua
|
||||||
|
local Suite = ... --- @type Suite<ExpRoles.TestEnv> The suite this file adds its tests to, see test/lua/framework.lua
|
||||||
|
local test, check, eq = Suite.test, Suite.check, Suite.eq
|
||||||
|
|
||||||
|
test("get_role() returns the role with the given clusterio id", function(env)
|
||||||
|
env.initialise{}
|
||||||
|
check(env.Roles.get_role(env.R("Regular").id) == env.R("Regular"), "the role is found by id")
|
||||||
|
eq(env.Roles.get_role(9), nil, "deleted roles are not known")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("get_role_by_name() searches the roles", function(env)
|
||||||
|
env.initialise{}
|
||||||
|
check(env.Roles.get_role_by_name("Moderator") == env.R("Moderator"), "the role is found by name")
|
||||||
|
eq(env.Roles.get_role_by_name("Gone"), nil, "deleted roles are not known")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("get_roles() returns every role", function(env)
|
||||||
|
env.initialise{}
|
||||||
|
eq(Suite.sorted(Suite.names(env.Roles.get_roles())),
|
||||||
|
{ "Cluster Admin", "Jail", "Moderator", "Player", "Regular" },
|
||||||
|
"every role is returned with deleted roles skipped")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("get_ordered_roles() returns the most privileged first", function(env)
|
||||||
|
env.initialise{}
|
||||||
|
eq(Suite.names(env.Roles.get_ordered_roles()), { "Cluster Admin", "Moderator", "Regular", "Jail", "Player" },
|
||||||
|
"roles follow their order")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("sort_roles() orders most privileged first and breaks ties on the id", function(env)
|
||||||
|
env.initialise{}
|
||||||
|
local sorted = env.Roles.sort_roles{ env.R("Player"), env.R("Cluster Admin"), env.R("Regular") }
|
||||||
|
eq(Suite.names(sorted), { "Cluster Admin", "Regular", "Player" }, "the list is sorted in place")
|
||||||
|
|
||||||
|
-- A role with the same order as Regular but a higher id sorts after it
|
||||||
|
env.Roles.receive_role_updates{
|
||||||
|
{ id = 10, name = "Twin", permissions = {}, meta = { id = 0, order = env.R("Regular").order } },
|
||||||
|
}
|
||||||
|
local tied = env.Roles.sort_roles{ env.R("Twin"), env.R("Regular") }
|
||||||
|
eq(Suite.names(tied), { "Regular", "Twin" }, "order ties break on the id")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("get_default_role() follows is_default", function(env)
|
||||||
|
env.initialise{}
|
||||||
|
check(env.Roles.get_default_role() == env.R("Player"), "the default role is known")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("get_higher_roles() and get_lower_roles() include the role and exclude the default", function(env)
|
||||||
|
env.initialise{}
|
||||||
|
eq(Suite.sorted(Suite.names(env.Roles.get_higher_roles(env.R("Regular")))),
|
||||||
|
{ "Cluster Admin", "Moderator", "Regular" }, "higher roles")
|
||||||
|
eq(Suite.sorted(Suite.names(env.Roles.get_lower_roles(env.R("Regular")))),
|
||||||
|
{ "Jail", "Regular" }, "lower roles")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test(".is_higher_than() and .is_lower_than() compare on order", function(env)
|
||||||
|
env.initialise{}
|
||||||
|
check(env.R("Moderator"):is_higher_than(env.R("Player")), "a lower order is more privileged")
|
||||||
|
check(env.R("Player"):is_lower_than(env.R("Moderator")), "is_lower_than is the reverse")
|
||||||
|
check(not env.R("Moderator"):is_higher_than(env.R("Moderator")), "a role is not higher than itself")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("initialise() decodes the role records", function(env)
|
||||||
|
env.initialise{}
|
||||||
|
eq(env.R("Cluster Admin").short_hand, "SYS", "short hand decoded")
|
||||||
|
eq(env.R("Moderator").short_hand, "Moderator", "short hand falls back to the name")
|
||||||
|
eq(env.R("Moderator").color, { r = 0, g = 170, b = 0 }, "color decoded")
|
||||||
|
check(env.R("Jail").priority == 1 and env.R("Jail").block_auto_assign, "priority and block auto assign decoded")
|
||||||
|
end)
|
||||||
|
|
||||||
|
return Suite.run()
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
--- Tests for the player checks of module/control.lua
|
||||||
|
local Suite = ... --- @type Suite<ExpRoles.TestEnv> The suite this file adds its tests to, see test/lua/framework.lua
|
||||||
|
local test, check, eq = Suite.test, Suite.check, Suite.eq
|
||||||
|
|
||||||
|
--- alice is a moderator, bob is a regular, and carol has only the default role
|
||||||
|
local function setup(env)
|
||||||
|
local players = {
|
||||||
|
alice = env.add_player("alice"),
|
||||||
|
bob = env.add_player("bob"),
|
||||||
|
carol = env.add_player("carol"),
|
||||||
|
}
|
||||||
|
env.initialise{
|
||||||
|
env.assignment("alice", { "Moderator" }),
|
||||||
|
env.assignment("bob", { "Regular" }),
|
||||||
|
}
|
||||||
|
return players
|
||||||
|
end
|
||||||
|
|
||||||
|
test("get_player_roles() includes the default role", function(env)
|
||||||
|
local players = setup(env)
|
||||||
|
eq(Suite.sorted(Suite.names(env.Roles.get_player_roles(players.alice))), { "Moderator", "Player" },
|
||||||
|
"held roles and the default role")
|
||||||
|
eq(Suite.names(env.Roles.get_player_roles(players.carol)), { "Player" },
|
||||||
|
"a player with no roles has the default role")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("get_player_roles() treats nil and index 0 as the server", function(env)
|
||||||
|
setup(env)
|
||||||
|
eq(Suite.names(env.Roles.get_player_roles(nil)), { "<server>" }, "nil is the server")
|
||||||
|
eq(Suite.names(env.Roles.get_player_roles(env.server)), { "<server>" }, "index 0 is the server")
|
||||||
|
check(env.Roles.player_has_permission(nil, "anything.at.all"), "the server has every permission")
|
||||||
|
check(not env.R("Player"):has_player(nil), "the server does not have the default role")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("get_player_highest_role() returns the most privileged role", function(env)
|
||||||
|
local players = setup(env)
|
||||||
|
check(env.Roles.get_player_highest_role(players.alice) == env.R("Moderator"), "the highest held role")
|
||||||
|
check(env.Roles.get_player_highest_role(players.carol) == env.R("Player"), "the default role when none are held")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("get_player_highest_role() errors when a player has no roles", function(env)
|
||||||
|
local players = setup(env)
|
||||||
|
env.Roles.receive_role_updates{
|
||||||
|
{ id = env.R("Player").id, name = "Player", permissions = {}, meta = { id = 0, order = 5 }, is_deleted = true },
|
||||||
|
}
|
||||||
|
Suite.throws(function()
|
||||||
|
env.Roles.get_player_highest_role(players.carol)
|
||||||
|
end, "Player has no roles", "no default role and no held roles errors")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("player_has_permission()", function(env)
|
||||||
|
local players = setup(env)
|
||||||
|
check(env.Roles.player_has_permission(players.alice, "exp_scenario.command.kill"), "granted by a held role")
|
||||||
|
check(env.Roles.player_has_permission(players.alice, "exp_scenario.gui.readme"), "granted by the default role")
|
||||||
|
check(not env.Roles.player_has_permission(players.bob, "exp_scenario.command.jail"), "not granted")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("player_has_any_permission() and player_has_all_permission()", function(env)
|
||||||
|
local players = setup(env)
|
||||||
|
local jail, kill = "exp_scenario.command.jail", "exp_scenario.command.kill"
|
||||||
|
check(env.Roles.player_has_any_permission(players.bob, jail, kill), "any passes when one is granted")
|
||||||
|
check(not env.Roles.player_has_any_permission(players.bob, jail, "exp_scenario.command.assign_role"),
|
||||||
|
"any fails when none are granted")
|
||||||
|
check(not env.Roles.player_has_any_permission(players.bob), "any of none is false")
|
||||||
|
check(env.Roles.player_has_all_permission(players.alice, jail, kill), "all passes when every one is granted")
|
||||||
|
check(not env.Roles.player_has_all_permission(players.bob, jail, kill), "all fails when one is missing")
|
||||||
|
check(env.Roles.player_has_all_permission(players.bob), "all of none is true")
|
||||||
|
check(env.Roles.player_has_any_permission(nil, "anything.at.all"), "the server passes any")
|
||||||
|
check(env.Roles.player_has_all_permission(nil, "anything.at.all", "and.this.too"), "the server passes all")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test(".has_permission()", function(env)
|
||||||
|
setup(env)
|
||||||
|
check(env.R("Moderator"):has_permission("exp_scenario.command.kill"), "granted")
|
||||||
|
check(not env.R("Regular"):has_permission("exp_scenario.command.jail"), "not granted")
|
||||||
|
check(env.R("Cluster Admin"):has_permission("anything.at.all"), "core.admin grants everything")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test(".has_player()", function(env)
|
||||||
|
local players = setup(env)
|
||||||
|
check(env.R("Moderator"):has_player(players.alice), "a held role")
|
||||||
|
check(not env.R("Moderator"):has_player(players.bob), "a role not held")
|
||||||
|
check(env.R("Player"):has_player(players.carol), "everyone has the default role")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("player_outranks()", function(env)
|
||||||
|
local players = setup(env)
|
||||||
|
check(env.Roles.player_outranks(players.alice, players.bob), "a higher role outranks a lower one")
|
||||||
|
check(not env.Roles.player_outranks(players.bob, players.alice), "a lower role does not outrank a higher one")
|
||||||
|
check(not env.Roles.player_outranks(players.alice, players.alice), "nobody outranks themselves")
|
||||||
|
check(env.Roles.player_outranks(nil, players.alice), "the server outranks everyone")
|
||||||
|
check(not env.Roles.player_outranks(players.alice, nil), "nobody outranks the server")
|
||||||
|
end)
|
||||||
|
|
||||||
|
return Suite.run()
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
--- Tests for the sync entry points of module/control.lua
|
||||||
|
local Suite = ... --- @type Suite<ExpRoles.TestEnv> The suite this file adds its tests to, see test/lua/framework.lua
|
||||||
|
local test, check, eq, empty = Suite.test, Suite.check, Suite.eq, Suite.empty
|
||||||
|
|
||||||
|
--- The state given by the admin trigger, reset by setup for every test
|
||||||
|
local admin_state = {} --- @type table<string, boolean>
|
||||||
|
|
||||||
|
--- alice is a moderator and carol has only the default role, both connected
|
||||||
|
local function setup(env)
|
||||||
|
local players = {
|
||||||
|
alice = env.add_player("alice"),
|
||||||
|
carol = env.add_player("carol"),
|
||||||
|
}
|
||||||
|
admin_state = {}
|
||||||
|
env.Roles.define_permission_trigger("exp_scenario.player.admin", function(player, state)
|
||||||
|
admin_state[player.name] = state
|
||||||
|
end)
|
||||||
|
env.initialise{
|
||||||
|
env.assignment("alice", { "Moderator" }),
|
||||||
|
env.assignment("zed", { "Moderator", "Regular" }), -- has never joined this map
|
||||||
|
}
|
||||||
|
env.Roles.set_emit_events(true)
|
||||||
|
return players
|
||||||
|
end
|
||||||
|
|
||||||
|
test("initialise() applies triggers and raises empty events", function(env)
|
||||||
|
setup(env)
|
||||||
|
eq(admin_state, { alice = true, carol = false }, "triggers run for connected players")
|
||||||
|
eq(#env.events, 2, "the event is raised once per connected player")
|
||||||
|
for _, event in ipairs(env.events) do
|
||||||
|
empty(event.assigned, "the events assign nothing")
|
||||||
|
empty(event.unassigned, "the events unassign nothing")
|
||||||
|
end
|
||||||
|
empty(env.printed, "initialise is silent")
|
||||||
|
empty(env.sent, "initialise sends nothing")
|
||||||
|
empty(env.sounds, "initialise plays no sounds")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("initialise() skips deleted assignments", function(env)
|
||||||
|
local players = setup(env)
|
||||||
|
local record = env.assignment("carol", { "Moderator" })
|
||||||
|
record.is_deleted = true
|
||||||
|
env.initialise{ record }
|
||||||
|
check(not env.R("Moderator"):has_player(players.carol), "a deleted assignment is ignored")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("initialise() is authoritative for pending roles", function(env)
|
||||||
|
local players = setup(env)
|
||||||
|
env.R("Moderator"):assign(players.carol)
|
||||||
|
env.R("Jail"):assign(players.carol, { silent = true, local_only = true })
|
||||||
|
|
||||||
|
local sd = env.script_data()
|
||||||
|
eq(sd.pending.carol, { env.R("Moderator").id }, "the role is pending before initialise")
|
||||||
|
|
||||||
|
env.reset_log()
|
||||||
|
env.initialise{ env.assignment("alice", { "Moderator" }) }
|
||||||
|
eq(sd.pending.carol, nil, "pending roles are cleared")
|
||||||
|
check(not env.R("Moderator"):has_player(players.carol), "an unconfirmed role is given up")
|
||||||
|
eq(sd.local_players.carol, { env.R("Jail").id }, "local only roles are kept")
|
||||||
|
empty(env.sent, "initialise sends nothing")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("receive_assignment_updates() applies controller changes", function(env)
|
||||||
|
local players = setup(env)
|
||||||
|
env.reset_log()
|
||||||
|
env.Roles.receive_assignment_updates{ env.assignment("carol", { "Moderator" }) }
|
||||||
|
check(env.R("Moderator"):has_player(players.carol), "the assignment applies")
|
||||||
|
eq(env.events, {
|
||||||
|
{
|
||||||
|
name = env.Roles.events.on_player_roles_changed,
|
||||||
|
tick = 1,
|
||||||
|
player_index = players.carol.index,
|
||||||
|
by_player_index = 0,
|
||||||
|
assigned = { env.R("Moderator").id },
|
||||||
|
unassigned = {},
|
||||||
|
},
|
||||||
|
}, "the event carries the change with no by player")
|
||||||
|
eq(env.printed, {
|
||||||
|
{ "exp-roles.game-message-assign", "carol", "Moderator", "<server>" },
|
||||||
|
}, "the change is announced by the server")
|
||||||
|
check(admin_state.carol == true, "triggers follow the assignment")
|
||||||
|
|
||||||
|
env.reset_log()
|
||||||
|
env.Roles.receive_assignment_updates{ { name = "carol", role_ids = {}, is_deleted = true } }
|
||||||
|
check(not env.R("Moderator"):has_player(players.carol), "a removal applies")
|
||||||
|
eq(env.events, {
|
||||||
|
{
|
||||||
|
name = env.Roles.events.on_player_roles_changed,
|
||||||
|
tick = 1,
|
||||||
|
player_index = players.carol.index,
|
||||||
|
by_player_index = 0,
|
||||||
|
assigned = {},
|
||||||
|
unassigned = { env.R("Moderator").id },
|
||||||
|
},
|
||||||
|
}, "the removal raises the event")
|
||||||
|
check(admin_state.carol == false, "triggers follow the removal")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("receive_assignment_updates() for players not on this map raises nothing", function(env)
|
||||||
|
setup(env)
|
||||||
|
env.reset_log()
|
||||||
|
env.Roles.receive_assignment_updates{ env.assignment("zed", { "Moderator" }) }
|
||||||
|
empty(env.events, "no event is raised")
|
||||||
|
empty(env.printed, "nothing is announced")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("receive_role_updates() applies to the holders", function(env)
|
||||||
|
setup(env)
|
||||||
|
env.reset_log()
|
||||||
|
local regular_id = env.R("Regular").id
|
||||||
|
env.Roles.receive_role_updates{
|
||||||
|
{ id = regular_id, name = "Regular", permissions = { "exp_scenario.command.jail" }, meta = { id = 0, order = 3 } },
|
||||||
|
}
|
||||||
|
check(env.R("Regular"):has_permission("exp_scenario.command.jail"), "the permission change applies")
|
||||||
|
eq(#env.events, 2, "a role change raises for every connected player")
|
||||||
|
for _, event in ipairs(env.events) do
|
||||||
|
empty(event.assigned, "role change events are empty")
|
||||||
|
end
|
||||||
|
|
||||||
|
env.Roles.receive_role_updates{
|
||||||
|
{ id = regular_id, name = "Regular", permissions = {}, meta = { id = 0, order = 3 }, is_deleted = true },
|
||||||
|
}
|
||||||
|
eq(env.Roles.get_role(regular_id), nil, "a deleted role is removed")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("receive_role_updates() moves the default role", function(env)
|
||||||
|
local players = setup(env)
|
||||||
|
env.Roles.receive_role_updates{
|
||||||
|
{ id = env.R("Player").id, name = "Player", permissions = {}, meta = { id = 0, order = 5 } },
|
||||||
|
{ id = 8, name = "Guest", permissions = {}, meta = { id = 0, order = 7 }, is_default = true },
|
||||||
|
}
|
||||||
|
check(env.Roles.get_default_role() == env.R("Guest"), "the new default role is known")
|
||||||
|
eq(Suite.names(env.Roles.get_player_roles(players.carol)), { "Guest" }, "players pick up the new default")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("set_emit_events() gates what is sent and defaults to enabled", function(env)
|
||||||
|
local players = setup(env)
|
||||||
|
env.Roles.set_emit_events(false)
|
||||||
|
env.reset_log()
|
||||||
|
env.R("Regular"):assign(players.alice)
|
||||||
|
empty(env.sent, "nothing is sent while disabled")
|
||||||
|
check(env.R("Regular"):has_player(players.alice), "the assignment still applies locally")
|
||||||
|
|
||||||
|
env.Roles.set_emit_events()
|
||||||
|
env.R("Jail"):assign(players.alice, { silent = true })
|
||||||
|
eq(env.sent, {
|
||||||
|
{ channel = "exp_roles:assignment_update", data = { name = "alice", assign = { env.R("Jail").id } } },
|
||||||
|
}, "no argument enables sending")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("on_load() restores the state after a save and load", function(env)
|
||||||
|
local players = setup(env)
|
||||||
|
env.R("Jail"):assign(players.carol, { silent = true, local_only = true })
|
||||||
|
env.save_load()
|
||||||
|
env.Roles.on_load()
|
||||||
|
|
||||||
|
check(env.R("Moderator"):is_higher_than(env.R("Regular")), "role methods survive through the registered metatable")
|
||||||
|
check(env.R("Moderator"):has_player(players.alice), "synced roles survive")
|
||||||
|
check(env.R("Jail"):has_player(players.carol), "local roles survive")
|
||||||
|
check(env.Roles.player_has_permission(players.alice, "exp_scenario.command.kill"), "permission checks still answer")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("on_player_joined_game() runs the triggers", function(env)
|
||||||
|
local players = setup(env)
|
||||||
|
admin_state.alice = nil
|
||||||
|
env.Roles.on_player_joined_game{
|
||||||
|
name = defines.events.on_player_joined_game,
|
||||||
|
tick = game.tick,
|
||||||
|
player_index = players.alice.index,
|
||||||
|
}
|
||||||
|
check(admin_state.alice ~= nil, "triggers run when a player joins")
|
||||||
|
end)
|
||||||
|
|
||||||
|
return Suite.run()
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
"use strict";
|
||||||
|
const t = require("tap");
|
||||||
|
const lib = require("@clusterio/lib");
|
||||||
|
const { seedRoles, flattenSeedPermissions } = require("../dist/node/seed");
|
||||||
|
|
||||||
|
// Importing this defines the exp_scenario permissions the seed grants
|
||||||
|
require("@expcluster/scenario/dist/node/permissions");
|
||||||
|
|
||||||
|
t.test("seedRoles[] grant only defined permissions", t2 => {
|
||||||
|
for (const role of seedRoles) {
|
||||||
|
for (const permission of role.permissions) {
|
||||||
|
t2.ok(lib.permissions.has(permission), `${role.name} grants defined permission ${permission}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t2.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test("seedRoles[] are unique with one default and one admin", t2 => {
|
||||||
|
const names = seedRoles.map(role => role.name);
|
||||||
|
t2.strictSame(names.length, new Set(names).size, "names are unique");
|
||||||
|
t2.strictSame(seedRoles.filter(role => role.isDefault).length, 1, "one default role");
|
||||||
|
t2.strictSame(seedRoles.filter(role => role.isAdmin).length, 1, "one admin role");
|
||||||
|
t2.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test("flattenSeedPermissions() inherits permissions from parent roles", t2 => {
|
||||||
|
const byName = new Map(seedRoles.map(role => [role.name, role]));
|
||||||
|
for (const role of seedRoles) {
|
||||||
|
if (role.parent === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = byName.get(role.parent);
|
||||||
|
t2.ok(parent, `${role.name} has parent ${role.parent}`);
|
||||||
|
|
||||||
|
const flattened = flattenSeedPermissions(role);
|
||||||
|
for (const permission of flattenSeedPermissions(parent)) {
|
||||||
|
t2.ok(flattened.has(permission), `${role.name} inherits ${permission}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t2.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test("flattenSeedPermissions() does not inherit permissions when parent is undefined", t2 => {
|
||||||
|
for (const role of seedRoles) {
|
||||||
|
if (role.parent !== undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const flattened = flattenSeedPermissions(role);
|
||||||
|
for (const permission of role.permissions) {
|
||||||
|
t2.ok(flattened.has(permission), `${role.name} contains ${permission}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
t2.equal(
|
||||||
|
flattened.size,
|
||||||
|
role.permissions.length,
|
||||||
|
`${role.name} has no inherited permissions`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
t2.end();
|
||||||
|
});
|
||||||
@@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,28 @@
|
|||||||
--[[-- Command Authorities - Roles
|
--[[-- Command Authorities - Roles
|
||||||
Adds a permission authority for exp roles
|
Adds a permission authority which checks the clusterio permission for a command
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local Commands = require("modules/exp_commands")
|
local Commands = require("modules/exp_commands")
|
||||||
local add, allow, deny = Commands.add_permission_authority, Commands.status.success, Commands.status.unauthorised
|
local add, allow, deny = Commands.add_permission_authority, Commands.status.success, Commands.status.unauthorised
|
||||||
|
|
||||||
local Roles = require("modules/exp_legacy/expcore/roles")
|
local Roles = require("modules/exp_roles")
|
||||||
local player_allowed = Roles.player_allowed
|
local player_has_permission = Roles.player_has_permission
|
||||||
|
|
||||||
local authorities = {}
|
local authorities = {}
|
||||||
|
|
||||||
--- If a command has the flag "character_only" then the command can only be used outside of remote view
|
--- Every command requires the permission `exp_scenario.command.<name>`, with
|
||||||
|
--- hyphens replaced by underscores, see permissions.ts for their definitions
|
||||||
authorities.exp_permission =
|
authorities.exp_permission =
|
||||||
add(function(player, command)
|
add(function(player, command)
|
||||||
if not player_allowed(player, command.flags.exp_permission or ("command/" .. command.name)) then
|
local permission = "exp_scenario.command." .. command.name:gsub("%-", "_")
|
||||||
|
if not player_has_permission(player, permission) then
|
||||||
return deny{ "exp-commands-authorities_role.deny" }
|
return deny{ "exp-commands-authorities_role.deny" }
|
||||||
else
|
else
|
||||||
return allow()
|
return allow()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
Roles.define_flag_trigger("is_system", 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
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ local add_static, add_dynamic = Commands.add_rcon_static, Commands.add_rcon_dyna
|
|||||||
add_static("Gui", require("modules/exp_gui"))
|
add_static("Gui", require("modules/exp_gui"))
|
||||||
|
|
||||||
add_static("Group", require("modules.exp_legacy.expcore.permission_groups"))
|
add_static("Group", require("modules.exp_legacy.expcore.permission_groups"))
|
||||||
add_static("Roles", require("modules.exp_legacy.expcore.roles"))
|
add_static("Roles", require("modules/exp_roles"))
|
||||||
add_static("Datastore", require("modules.exp_legacy.expcore.datastore"))
|
add_static("Datastore", require("modules.exp_legacy.expcore.datastore"))
|
||||||
add_static("External", require("modules.exp_legacy.expcore.external"))
|
add_static("External", require("modules.exp_legacy.expcore.external"))
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
--[[-- Command Types - Roles
|
--[[-- Command Types - Roles
|
||||||
The data types that are used with exp_roles
|
The data types that are used with exp_roles
|
||||||
A lower role index indicates it is more privileged
|
A lower role index indicates it is more privileged
|
||||||
@@ -11,28 +10,43 @@ Adds parsers for:
|
|||||||
lower_role_player_alive
|
lower_role_player_alive
|
||||||
]]
|
]]
|
||||||
|
|
||||||
|
local ExpUtil = require("modules/exp_util")
|
||||||
|
local auto_complete = ExpUtil.auto_complete
|
||||||
|
|
||||||
local Commands = require("modules/exp_commands")
|
local Commands = require("modules/exp_commands")
|
||||||
local add, parse = Commands.add_data_type, Commands.parse_input
|
local add, parse = Commands.add_data_type, Commands.parse_input
|
||||||
local valid, invalid = Commands.status.success, Commands.status.invalid_input
|
local valid, invalid = Commands.status.success, Commands.status.invalid_input
|
||||||
|
|
||||||
local Roles = require("modules.exp_legacy.expcore.roles")
|
local Roles = require("modules/exp_roles")
|
||||||
local highest_role = Roles.get_player_highest_role
|
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
|
||||||
|
|
||||||
--- A role defined by exp roles
|
--- A role known to exp roles, matched on its name
|
||||||
types.role = add("role", Commands.types.key_of(Roles.config.roles))
|
types.role =
|
||||||
|
add("role", function(input)
|
||||||
|
local names = {}
|
||||||
|
for index, role in ipairs(Roles.get_ordered_roles()) do
|
||||||
|
names[index] = role.name
|
||||||
|
end
|
||||||
|
|
||||||
|
local name = auto_complete(names, input)
|
||||||
|
if name == nil then
|
||||||
|
return invalid{ "exp-commands-parse.string-options", table.concat(names, ", ") }
|
||||||
|
else
|
||||||
|
return valid(Roles.get_role_by_name(name))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
--- A role which is lower than the players highest role
|
--- A role which is lower than the players highest role
|
||||||
types.lower_role =
|
types.lower_role =
|
||||||
add("lower_role", function(input, player)
|
add("lower_role", function(input, player)
|
||||||
local success, status, result = parse(input, player, types.role)
|
local success, status, result = parse(input, player, types.role)
|
||||||
if not success then return status, result end
|
if not success then return status, result end
|
||||||
--- @cast result any TODO role is not a defined type
|
--- @cast result ExpRoles.Role
|
||||||
|
|
||||||
local player_highest = highest_role(player)
|
if not player_has_permission(player, "core.admin") and not Roles.get_player_highest_role(player):is_higher_than(result) then
|
||||||
local is_root = Roles.config.internal.root == player_highest.name
|
|
||||||
if not is_root and player_highest.index >= result.index 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)
|
||||||
@@ -46,10 +60,7 @@ types.lower_role_player =
|
|||||||
if not success then return status, result end
|
if not success then return status, result end
|
||||||
--- @cast result LuaPlayer
|
--- @cast result LuaPlayer
|
||||||
|
|
||||||
local other_highest = highest_role(result)
|
if not player_outranks(player, result) then
|
||||||
local player_highest = highest_role(player)
|
|
||||||
local is_root = Roles.config.internal.root == player_highest.name
|
|
||||||
if not is_root and player_highest.index >= other_highest.index then
|
|
||||||
return invalid{ "exp-commands-parse_role.lower-role-player" }
|
return invalid{ "exp-commands-parse_role.lower-role-player" }
|
||||||
else
|
else
|
||||||
return valid(result)
|
return valid(result)
|
||||||
@@ -63,10 +74,7 @@ types.lower_role_player_online =
|
|||||||
if not success then return status, result end
|
if not success then return status, result end
|
||||||
--- @cast result LuaPlayer
|
--- @cast result LuaPlayer
|
||||||
|
|
||||||
local other_highest = highest_role(result)
|
if not player_outranks(player, result) then
|
||||||
local player_highest = highest_role(player)
|
|
||||||
local is_root = Roles.config.internal.root == player_highest.name
|
|
||||||
if not is_root and player_highest.index >= other_highest.index then
|
|
||||||
return invalid{ "exp-commands-parse_role.lower-role-player" }
|
return invalid{ "exp-commands-parse_role.lower-role-player" }
|
||||||
else
|
else
|
||||||
return valid(result)
|
return valid(result)
|
||||||
@@ -80,10 +88,7 @@ types.lower_role_player_alive =
|
|||||||
if not success then return status, result end
|
if not success then return status, result end
|
||||||
--- @cast result LuaPlayer
|
--- @cast result LuaPlayer
|
||||||
|
|
||||||
local other_highest = highest_role(result)
|
if not player_outranks(player, result) then
|
||||||
local player_highest = highest_role(player)
|
|
||||||
local is_root = Roles.config.internal.root == player_highest.name
|
|
||||||
if not is_root and player_highest.index >= other_highest.index then
|
|
||||||
return invalid{ "exp-commands-parse_role.lower-role-player" }
|
return invalid{ "exp-commands-parse_role.lower-role-player" }
|
||||||
else
|
else
|
||||||
return valid(result)
|
return valid(result)
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ Adds a command that allows players to kill themselves and others
|
|||||||
|
|
||||||
local Commands = require("modules/exp_commands")
|
local Commands = require("modules/exp_commands")
|
||||||
|
|
||||||
local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
|
local Roles = require("modules/exp_roles")
|
||||||
local highest_role = Roles.get_player_highest_role
|
local player_outranks = Roles.player_outranks
|
||||||
|
|
||||||
--- Kills yourself or another player.
|
--- Kills yourself or another player.
|
||||||
Commands.new("kill", { "exp-commands_kill.description" })
|
Commands.new("kill", { "exp-commands_kill.description" })
|
||||||
@@ -20,7 +20,7 @@ Commands.new("kill", { "exp-commands_kill.description" })
|
|||||||
if other_player == nil then
|
if other_player == nil then
|
||||||
-- Can only be nil if the target is the player and they are already dead
|
-- Can only be nil if the target is the player and they are already dead
|
||||||
return Commands.status.error{ "exp-commands_kill.already-dead" }
|
return Commands.status.error{ "exp-commands_kill.already-dead" }
|
||||||
elseif (other_player == player) or (highest_role(player).index < highest_role(other_player).index) then
|
elseif other_player == player or player_outranks(player, other_player) then
|
||||||
-- You can always kill yourself or can kill lower role players
|
-- You can always kill yourself or can kill lower role players
|
||||||
if script.active_mods["space-age"] then
|
if script.active_mods["space-age"] then
|
||||||
other_player.surface.create_entity{ name = "lightning", position = { other_player.position.x, other_player.position.y - 16 }, target = other_player.character }
|
other_player.surface.create_entity{ name = "lightning", position = { other_player.position.x, other_player.position.y - 16 }, target = other_player.character }
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ local expand_area = AABB.expand
|
|||||||
local Commands = require("modules/exp_commands")
|
local Commands = require("modules/exp_commands")
|
||||||
local format_player_name = Commands.format_player_name_locale
|
local format_player_name = Commands.format_player_name_locale
|
||||||
|
|
||||||
local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
|
local Roles = require("modules/exp_roles")
|
||||||
local EntityProtection = require("modules.exp_legacy.modules.control.protection") --- @dep modules.control.protection
|
local EntityProtection = require("modules.exp_legacy.modules.control.protection") --- @dep modules.control.protection
|
||||||
|
|
||||||
local format_string = string.format
|
local format_string = string.format
|
||||||
@@ -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")
|
||||||
"exp-commands_entity-protection.repeat-offence",
|
for _, role in ipairs(regular and Roles.get_higher_roles(regular) or {}) do
|
||||||
format_player_name(event.player_index),
|
role:print{
|
||||||
event.entity.localised_name,
|
"exp-commands_entity-protection.repeat-offence",
|
||||||
event.entity.position.x,
|
format_player_name(event.player_index),
|
||||||
event.entity.position.y
|
event.entity.localised_name,
|
||||||
})
|
event.entity.position.x,
|
||||||
|
event.entity.position.y
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ local Commands = require("modules/exp_commands")
|
|||||||
local format_player_name = Commands.format_player_name_locale
|
local format_player_name = Commands.format_player_name_locale
|
||||||
local parse_input = Commands.parse_input
|
local parse_input = Commands.parse_input
|
||||||
|
|
||||||
local Roles = require("modules.exp_legacy.expcore.roles")
|
local Roles = require("modules/exp_roles")
|
||||||
local player_has_flag = Roles.player_has_flag
|
local player_has_permission = Roles.player_has_permission
|
||||||
|
|
||||||
local Reports = require("modules.exp_legacy.modules.control.reports") --- @dep modules.control.reports
|
local Reports = require("modules.exp_legacy.modules.control.reports") --- @dep modules.control.reports
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ local function reportable_player(input, player)
|
|||||||
if not success then return status, result end
|
if not success then return status, result end
|
||||||
--- @cast result LuaPlayer
|
--- @cast result LuaPlayer
|
||||||
|
|
||||||
if player_has_flag(input, "report-immune") then
|
if player_has_permission(input, "exp_scenario.bypass.reports") then
|
||||||
return Commands.status.invalid_input{ "exp-commands_reports.player-immune" }
|
return Commands.status.invalid_input{ "exp-commands_reports.player-immune" }
|
||||||
elseif player == input then
|
elseif player == input then
|
||||||
return Commands.status.invalid_input{ "exp-commands_reports.self-report" }
|
return Commands.status.invalid_input{ "exp-commands_reports.self-report" }
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ local Commands = require("modules/exp_commands")
|
|||||||
local format_player_name = Commands.format_player_name_locale
|
local format_player_name = Commands.format_player_name_locale
|
||||||
local format_text = Commands.format_rich_text_color_locale
|
local format_text = Commands.format_rich_text_color_locale
|
||||||
|
|
||||||
local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
|
local Roles = require("modules/exp_roles")
|
||||||
local get_roles_ordered = Roles.get_roles_ordered
|
local get_ordered_roles = Roles.get_ordered_roles
|
||||||
local get_player_roles = Roles.get_player_roles
|
local get_player_roles = Roles.get_player_roles
|
||||||
|
|
||||||
--- Assigns a role to a player
|
--- Assigns a role to a player
|
||||||
@@ -18,8 +18,8 @@ Commands.new("assign-role", { "exp-commands_roles.description-assign" })
|
|||||||
:add_flags{ "admin_only" }
|
:add_flags{ "admin_only" }
|
||||||
:register(function(player, other_player, role)
|
:register(function(player, other_player, role)
|
||||||
--- @cast other_player LuaPlayer
|
--- @cast other_player LuaPlayer
|
||||||
--- @cast role any -- TODO
|
--- @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
|
||||||
@@ -30,8 +30,8 @@ Commands.new("unassign-role", { "exp-commands_roles.description-unassign" })
|
|||||||
:add_flags{ "admin_only" }
|
:add_flags{ "admin_only" }
|
||||||
:register(function(player, other_player, role)
|
:register(function(player, other_player, role)
|
||||||
--- @cast other_player LuaPlayer
|
--- @cast other_player LuaPlayer
|
||||||
--- @cast role any -- TODO
|
--- @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
|
||||||
@@ -40,17 +40,17 @@ Commands.new("get-roles", { "exp-commands_roles.description-get" })
|
|||||||
:add_aliases{ "roles" }
|
:add_aliases{ "roles" }
|
||||||
:register(function(player, other_player)
|
:register(function(player, other_player)
|
||||||
--- @cast other_player LuaPlayer?
|
--- @cast other_player LuaPlayer?
|
||||||
local roles = get_roles_ordered()
|
local roles = get_ordered_roles()
|
||||||
local roles_formatted = { "" } --- @type LocalisedString
|
local roles_formatted = { "" } --- @type LocalisedString
|
||||||
local response = { "exp-commands_roles.list-roles", roles_formatted } --[[@as LocalisedString]]
|
local response = { "exp-commands_roles.list-roles", roles_formatted } --[[@as LocalisedString]]
|
||||||
if other_player then
|
if other_player then
|
||||||
roles = get_player_roles(other_player)
|
roles = Roles.sort_roles(get_player_roles(other_player))
|
||||||
response[1] = "exp-commands_roles.list-player"
|
response[1] = "exp-commands_roles.list-player"
|
||||||
response[3] = format_player_name(other_player)
|
response[3] = format_player_name(other_player)
|
||||||
end
|
end
|
||||||
|
|
||||||
for index, role in ipairs(roles) do
|
for index, role in ipairs(roles) do
|
||||||
local role_name = format_text(role.name, role.custom_color or Commands.color.white)
|
local role_name = format_text(role.name, role.color or Commands.color.white)
|
||||||
roles_formatted[index + 1] = { "exp-commands_roles.list-element", role_name }
|
roles_formatted[index + 1] = { "exp-commands_roles.list-element", role_name }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ local teleport_player = ExpUtil.teleport_player
|
|||||||
|
|
||||||
local Commands = require("modules/exp_commands")
|
local Commands = require("modules/exp_commands")
|
||||||
|
|
||||||
local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
|
local Roles = require("modules/exp_roles")
|
||||||
local player_allowed = Roles.player_allowed
|
local player_has_permission = Roles.player_has_permission
|
||||||
|
|
||||||
--- @class ExpCommands_Teleport.commands
|
--- @class ExpCommands_Teleport.commands
|
||||||
local commands = {}
|
local commands = {}
|
||||||
@@ -85,7 +85,7 @@ commands.spawn = Commands.new("spawn", { "exp-commands_teleport.description-spaw
|
|||||||
if not teleport_player(player, game.surfaces.nauvis, { 0, 0 }, "dismount") then
|
if not teleport_player(player, game.surfaces.nauvis, { 0, 0 }, "dismount") then
|
||||||
return Commands.status.error{ "exp-commands_teleport.unavailable" }
|
return Commands.status.error{ "exp-commands_teleport.unavailable" }
|
||||||
end
|
end
|
||||||
elseif player_allowed(player, "command/spawn/always") then
|
elseif player_has_permission(player, "exp_scenario.command.spawn.always") then
|
||||||
if not teleport_player(other_player, game.surfaces.nauvis, { 0, 0 }, "dismount") then
|
if not teleport_player(other_player, game.surfaces.nauvis, { 0, 0 }, "dismount") then
|
||||||
return Commands.status.error{ "exp-commands_teleport.unavailable" }
|
return Commands.status.error{ "exp-commands_teleport.unavailable" }
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ add(require("modules/exp_scenario/control/pollution_grading"))
|
|||||||
add(require("modules/exp_scenario/control/protection_jail"))
|
add(require("modules/exp_scenario/control/protection_jail"))
|
||||||
add(require("modules/exp_scenario/control/report_jail"))
|
add(require("modules/exp_scenario/control/report_jail"))
|
||||||
add(require("modules/exp_scenario/control/research"))
|
add(require("modules/exp_scenario/control/research"))
|
||||||
|
add(require("modules/exp_scenario/control/roles"))
|
||||||
add(require("modules/exp_scenario/control/spawn_area"))
|
add(require("modules/exp_scenario/control/spawn_area"))
|
||||||
add(require("modules/exp_scenario/control/station_auto_name"))
|
add(require("modules/exp_scenario/control/station_auto_name"))
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Various bonus related event handlers
|
|||||||
TODO Refactor this fully, this is temp to get it out of the player bonus gui file
|
TODO Refactor this fully, this is temp to get it out of the player bonus gui file
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local Roles = require("modules/exp_legacy/expcore/roles")
|
local Roles = require("modules/exp_roles")
|
||||||
local config = require("modules/exp_legacy/config/bonus")
|
local config = require("modules/exp_legacy/config/bonus")
|
||||||
|
|
||||||
--- @param event EventData.on_force_created
|
--- @param event EventData.on_force_created
|
||||||
@@ -26,7 +26,7 @@ end
|
|||||||
--- @param event EventData.on_player_died
|
--- @param event EventData.on_player_died
|
||||||
local function fast_respawn(event)
|
local function fast_respawn(event)
|
||||||
local player = assert(game.get_player(event.player_index))
|
local player = assert(game.get_player(event.player_index))
|
||||||
if Roles.player_has_flag(player, "instant-respawn") then
|
if Roles.player_has_permission(player, "exp_scenario.player.instant_respawn") then
|
||||||
player.ticks_to_respawn = 120
|
player.ticks_to_respawn = 120
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
Adds auto replies to chat messages, as well as chat commands
|
Adds auto replies to chat messages, as well as chat commands
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local Roles = require("modules.exp_legacy.expcore.roles")
|
local Roles = require("modules/exp_roles")
|
||||||
local config = require("modules.exp_legacy.config.chat_reply")
|
local config = require("modules.exp_legacy.config.chat_reply")
|
||||||
local prefix = config.command_prefix
|
local prefix = config.command_prefix
|
||||||
local prefix_len = string.len(prefix)
|
local prefix_len = string.len(prefix)
|
||||||
@@ -20,7 +20,7 @@ local function on_console_chat(event)
|
|||||||
-- Check if the player can chat commands
|
-- Check if the player can chat commands
|
||||||
local commands_allowed = true
|
local commands_allowed = true
|
||||||
if config.command_admin_only and not player.admin then commands_allowed = false end
|
if config.command_admin_only and not player.admin then commands_allowed = false end
|
||||||
if config.command_permission and not Roles.player_allowed(player, config.command_permission) then commands_allowed = false end
|
if config.command_permission and not Roles.player_has_permission(player, config.command_permission) then commands_allowed = false end
|
||||||
|
|
||||||
-- Check if a key word appears in the message
|
-- Check if a key word appears in the message
|
||||||
for key_word, reply in pairs(config.messages) do
|
for key_word, reply in pairs(config.messages) do
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Log certain actions into a file when events are triggered
|
|||||||
]]
|
]]
|
||||||
|
|
||||||
local ExpUtil = require("modules/exp_util")
|
local ExpUtil = require("modules/exp_util")
|
||||||
local Roles = require("modules.exp_legacy.expcore.roles")
|
local Roles = require("modules/exp_roles")
|
||||||
local config = require("modules.exp_legacy.config.deconlog")
|
local config = require("modules.exp_legacy.config.deconlog")
|
||||||
|
|
||||||
local seconds_time_format = ExpUtil.format_time_factory{ format = "short", hours = true, minutes = true, seconds = true }
|
local seconds_time_format = ExpUtil.format_time_factory{ format = "short", hours = true, minutes = true, seconds = true }
|
||||||
@@ -79,7 +79,7 @@ end
|
|||||||
local function get_log_player(event)
|
local function get_log_player(event)
|
||||||
local player = assert(game.get_player(event.player_index))
|
local player = assert(game.get_player(event.player_index))
|
||||||
|
|
||||||
if Roles.player_has_flag(player, "deconlog-bypass") then
|
if Roles.player_has_permission(player, "exp_scenario.bypass.deconstruction_log") then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Makes trees which are marked for decon "decay" quickly to allow faster building
|
|||||||
|
|
||||||
local Gui = require("modules/exp_gui")
|
local Gui = require("modules/exp_gui")
|
||||||
local Async = require("modules/exp_util/async")
|
local Async = require("modules/exp_util/async")
|
||||||
local Roles = require("modules.exp_legacy.expcore.roles")
|
local Roles = require("modules/exp_roles")
|
||||||
|
|
||||||
local PlayerData = require("modules.exp_legacy.expcore.player_data")
|
local PlayerData = require("modules.exp_legacy.expcore.player_data")
|
||||||
local HasEnabledDecon = PlayerData.Settings:combine("HasEnabledDecon")
|
local HasEnabledDecon = PlayerData.Settings:combine("HasEnabledDecon")
|
||||||
@@ -57,9 +57,9 @@ local remove_trees_async =
|
|||||||
--- @param player LuaPlayer
|
--- @param player LuaPlayer
|
||||||
--- @return "fast" | "allow" | "disallow"
|
--- @return "fast" | "allow" | "disallow"
|
||||||
local function get_permission(player)
|
local function get_permission(player)
|
||||||
if Roles.player_allowed(player, "fast-tree-decon") then
|
if Roles.player_has_permission(player, "exp_scenario.decon.fast_trees") then
|
||||||
return HasEnabledDecon:get(player) and "fast" or "allow"
|
return HasEnabledDecon:get(player) and "fast" or "allow"
|
||||||
elseif Roles.player_allowed(player, "standard-decon") then
|
elseif Roles.player_has_permission(player, "exp_scenario.decon.standard") then
|
||||||
return "allow"
|
return "allow"
|
||||||
else
|
else
|
||||||
return "disallow"
|
return "disallow"
|
||||||
@@ -101,7 +101,7 @@ Gui.toolbar.create_button{
|
|||||||
tooltip = { "exp_fast-decon.tooltip-main" },
|
tooltip = { "exp_fast-decon.tooltip-main" },
|
||||||
auto_toggle = true,
|
auto_toggle = true,
|
||||||
visible = function(player, _)
|
visible = function(player, _)
|
||||||
return Roles.player_allowed(player, "fast-tree-decon")
|
return Roles.player_has_permission(player, "exp_scenario.decon.fast_trees")
|
||||||
end
|
end
|
||||||
}:on_click(function(def, player, element)
|
}:on_click(function(def, player, element)
|
||||||
local state = Gui.toolbar.get_button_toggled_state(def, player)
|
local state = Gui.toolbar.get_button_toggled_state(def, player)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Disable new players from having certain items in their inventory, most commonly
|
|||||||
]]
|
]]
|
||||||
|
|
||||||
local ExpUtil = require("modules/exp_util")
|
local ExpUtil = require("modules/exp_util")
|
||||||
local Roles = require("modules.exp_legacy.expcore.roles")
|
local Roles = require("modules/exp_roles")
|
||||||
local config = require("modules.exp_legacy.config.nukeprotect")
|
local config = require("modules.exp_legacy.config.nukeprotect")
|
||||||
|
|
||||||
--- Check all items in the given inventory
|
--- Check all items in the given inventory
|
||||||
@@ -12,14 +12,14 @@ local config = require("modules.exp_legacy.config.nukeprotect")
|
|||||||
--- @param banned_items string[]
|
--- @param banned_items string[]
|
||||||
local function check_items(player, type, banned_items)
|
local function check_items(player, type, banned_items)
|
||||||
-- If the player has perms to be ignored, then they should be
|
-- If the player has perms to be ignored, then they should be
|
||||||
if config.ignore_permission and Roles.player_allowed(player, config.ignore_permission) then return end
|
if config.ignore_permission and Roles.player_has_permission(player, config.ignore_permission) then return end
|
||||||
if config.ignore_admins and player.admin then return end
|
if config.ignore_admins and player.admin then return end
|
||||||
|
|
||||||
local items = {} --- @type LuaItemStack[]
|
local items = {} --- @type LuaItemStack[]
|
||||||
local inventory = assert(player.get_inventory(type))
|
local inventory = assert(player.get_inventory(type))
|
||||||
-- Check what items the player has
|
-- Check what items the player has
|
||||||
for i = 1, #inventory do
|
for i = 1, #inventory do
|
||||||
local item = inventory[i]
|
local item = inventory[i --[[@as uint]]]
|
||||||
if item.valid_for_read and banned_items[item.name] then
|
if item.valid_for_read and banned_items[item.name] then
|
||||||
player.print{ "exp_nuke-protection.chat-found", item.prototype.localised_name }
|
player.print{ "exp_nuke-protection.chat-found", item.prototype.localised_name }
|
||||||
items[#items + 1] = item
|
items[#items + 1] = item
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
--[[-- Control - Roles
|
||||||
|
Applies the in game effects of the player permissions defined by the scenario
|
||||||
|
]]
|
||||||
|
|
||||||
|
local Gui = require("modules/exp_gui")
|
||||||
|
local Roles = require("modules/exp_roles")
|
||||||
|
|
||||||
|
Roles.define_permission_trigger("exp_scenario.player.admin", function(player, state)
|
||||||
|
player.admin = state
|
||||||
|
end)
|
||||||
|
|
||||||
|
Roles.define_permission_trigger("exp_scenario.player.spectator", function(player, state)
|
||||||
|
player.spectator = state
|
||||||
|
end)
|
||||||
|
|
||||||
|
return {
|
||||||
|
events = {
|
||||||
|
--- @diagnostic disable-next-line: access-invisible
|
||||||
|
[Roles.events.on_player_roles_changed] = Gui._ensure_consistency,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,8 +5,8 @@ Adds a custom spawn area with chests and afk turrets
|
|||||||
local config = require("modules.exp_legacy.config.spawn_area")
|
local config = require("modules.exp_legacy.config.spawn_area")
|
||||||
|
|
||||||
--- Apply an offset to a LuaPosition
|
--- Apply an offset to a LuaPosition
|
||||||
--- @param position MapPosition
|
--- @param position MapPosition.struct
|
||||||
--- @param offset MapPosition
|
--- @param offset MapPosition.struct
|
||||||
--- @return MapPosition.struct
|
--- @return MapPosition.struct
|
||||||
local function apply_offset(position, offset)
|
local function apply_offset(position, offset)
|
||||||
return {
|
return {
|
||||||
@@ -17,7 +17,7 @@ end
|
|||||||
|
|
||||||
--- Apply offset to an array of positions
|
--- Apply offset to an array of positions
|
||||||
--- @param positions table
|
--- @param positions table
|
||||||
--- @param offset MapPosition
|
--- @param offset MapPosition.struct
|
||||||
--- @param x_index number
|
--- @param x_index number
|
||||||
--- @param y_index number
|
--- @param y_index number
|
||||||
local function apply_offset_to_array(positions, offset, x_index, y_index)
|
local function apply_offset_to_array(positions, offset, x_index, y_index)
|
||||||
@@ -97,14 +97,19 @@ local belt_details = {
|
|||||||
|
|
||||||
--- Makes a 2x2 afk belt at the locations in the config
|
--- Makes a 2x2 afk belt at the locations in the config
|
||||||
--- @param surface LuaSurface
|
--- @param surface LuaSurface
|
||||||
--- @param offset MapPosition
|
--- @param offset MapPosition.struct
|
||||||
local function create_belts(surface, offset)
|
local function create_belts(surface, offset)
|
||||||
local belt_type = config.afk_belts.belt_type
|
local belt_type = config.afk_belts.belt_type
|
||||||
|
|
||||||
|
local entity_pos = { x = 0, y = 0 }
|
||||||
|
local group_offset = { x = 0, y = 0 }
|
||||||
for _, position in pairs(config.afk_belts.locations) do
|
for _, position in pairs(config.afk_belts.locations) do
|
||||||
position = apply_offset(position, offset)
|
group_offset.x = position[1] + offset.x
|
||||||
|
group_offset.y = position[2] + offset.y
|
||||||
for _, belt in pairs(belt_details) do
|
for _, belt in pairs(belt_details) do
|
||||||
local pos = apply_offset(position, belt)
|
entity_pos.x = belt[1]
|
||||||
|
entity_pos.y = belt[2]
|
||||||
|
local pos = apply_offset(entity_pos, group_offset)
|
||||||
local entity = surface.create_entity{ name = belt_type, position = pos, force = "neutral", direction = belt[3] }
|
local entity = surface.create_entity{ name = belt_type, position = pos, force = "neutral", direction = belt[3] }
|
||||||
if entity and config.afk_belts.protected then
|
if entity and config.afk_belts.protected then
|
||||||
protect_entity(entity)
|
protect_entity(entity)
|
||||||
@@ -115,13 +120,16 @@ end
|
|||||||
|
|
||||||
-- Generates extra tiles in a set pattern as defined in the config
|
-- Generates extra tiles in a set pattern as defined in the config
|
||||||
--- @param surface LuaSurface
|
--- @param surface LuaSurface
|
||||||
--- @param offset MapPosition
|
--- @param offset MapPosition.struct
|
||||||
local function create_pattern_tiles(surface, offset)
|
local function create_pattern_tiles(surface, offset)
|
||||||
local tiles_to_make = {}
|
local tiles_to_make = {}
|
||||||
local pattern_tile = config.pattern.pattern_tile
|
local pattern_tile = config.pattern.pattern_tile
|
||||||
|
|
||||||
|
local tile_pos = { x = 0, y = 0 }
|
||||||
for index, position in pairs(config.pattern.locations) do
|
for index, position in pairs(config.pattern.locations) do
|
||||||
tiles_to_make[index] = { name = pattern_tile, position = apply_offset(position, offset) }
|
tile_pos.x = position[1]
|
||||||
|
tile_pos.y = position[2]
|
||||||
|
tiles_to_make[index] = { name = pattern_tile, position = apply_offset(tile_pos, offset) }
|
||||||
end
|
end
|
||||||
|
|
||||||
surface.set_tiles(tiles_to_make)
|
surface.set_tiles(tiles_to_make)
|
||||||
@@ -129,13 +137,16 @@ end
|
|||||||
|
|
||||||
-- Generates extra water as defined in the config
|
-- Generates extra water as defined in the config
|
||||||
--- @param surface LuaSurface
|
--- @param surface LuaSurface
|
||||||
--- @param offset MapPosition
|
--- @param offset MapPosition.struct
|
||||||
local function create_water_tiles(surface, offset)
|
local function create_water_tiles(surface, offset)
|
||||||
local tiles_to_make = {}
|
local tiles_to_make = {}
|
||||||
local water_tile = config.water.water_tile
|
local water_tile = config.water.water_tile
|
||||||
|
|
||||||
|
local tile_pos = { x = 0, y = 0 }
|
||||||
for _, position in pairs(config.water.locations) do
|
for _, position in pairs(config.water.locations) do
|
||||||
table.insert(tiles_to_make, { name = water_tile, position = apply_offset(position, offset) })
|
tile_pos.x = position[1]
|
||||||
|
tile_pos.y = position[2]
|
||||||
|
table.insert(tiles_to_make, { name = water_tile, position = apply_offset(tile_pos, offset) })
|
||||||
end
|
end
|
||||||
|
|
||||||
surface.set_tiles(tiles_to_make)
|
surface.set_tiles(tiles_to_make)
|
||||||
@@ -143,11 +154,17 @@ end
|
|||||||
|
|
||||||
--- Generates the entities that are in the config
|
--- Generates the entities that are in the config
|
||||||
--- @param surface LuaSurface
|
--- @param surface LuaSurface
|
||||||
--- @param offset MapPosition
|
--- @param offset MapPosition.struct
|
||||||
local function create_entities(surface, offset)
|
local function create_entities(surface, offset)
|
||||||
|
local entity_pos = { x = 0, y = 0 }
|
||||||
for _, entity_details in pairs(config.entities.locations) do
|
for _, entity_details in pairs(config.entities.locations) do
|
||||||
local pos = apply_offset({ entity_details[2], entity_details[3] }, offset)
|
entity_pos.x = entity_details[2]
|
||||||
local entity = surface.create_entity{ name = entity_details[1], position = pos, force = "neutral" }
|
entity_pos.y = entity_details[3]
|
||||||
|
local entity = surface.create_entity{
|
||||||
|
name = entity_details[1],
|
||||||
|
position = apply_offset(entity_pos, offset),
|
||||||
|
force = "neutral"
|
||||||
|
}
|
||||||
|
|
||||||
if entity then
|
if entity then
|
||||||
if config.entities.protected then
|
if config.entities.protected then
|
||||||
@@ -161,7 +178,7 @@ end
|
|||||||
|
|
||||||
--- Generates an area with no water or entities, no water area is larger
|
--- Generates an area with no water or entities, no water area is larger
|
||||||
--- @param surface LuaSurface
|
--- @param surface LuaSurface
|
||||||
--- @param offset MapPosition
|
--- @param offset MapPosition.struct
|
||||||
local function clear_spawn_area(surface, offset)
|
local function clear_spawn_area(surface, offset)
|
||||||
local get_tile = surface.get_tile
|
local get_tile = surface.get_tile
|
||||||
|
|
||||||
@@ -182,7 +199,7 @@ local function clear_spawn_area(surface, offset)
|
|||||||
for y = -fill_radius, fill_radius do -- loop over y
|
for y = -fill_radius, fill_radius do -- loop over y
|
||||||
local y_sqr = (y + 0.5) ^ 2
|
local y_sqr = (y + 0.5) ^ 2
|
||||||
local dst = x_sqr + y_sqr
|
local dst = x_sqr + y_sqr
|
||||||
local pos = apply_offset({ x, y }, offset)
|
local pos = apply_offset({ x = x, y = y }, offset)
|
||||||
if dst < tile_radius_sqr then
|
if dst < tile_radius_sqr then
|
||||||
-- If it is inside the decon radius always set the tile
|
-- If it is inside the decon radius always set the tile
|
||||||
tiles_to_make[#tiles_to_make + 1] = { name = decon_tile, position = pos }
|
tiles_to_make[#tiles_to_make + 1] = { name = decon_tile, position = pos }
|
||||||
@@ -204,14 +221,14 @@ end
|
|||||||
|
|
||||||
--- Spawn the resource tiles
|
--- Spawn the resource tiles
|
||||||
--- @param surface LuaSurface
|
--- @param surface LuaSurface
|
||||||
--- @param offset MapPosition
|
--- @param offset MapPosition.struct
|
||||||
local function create_resources_tiles(surface, offset)
|
local function create_resources_tiles(surface, offset)
|
||||||
for _, resource in ipairs(config.resource_tiles.resources) do
|
for _, resource in ipairs(config.resource_tiles.resources) do
|
||||||
if resource.enabled then
|
if resource.enabled then
|
||||||
local pos = apply_offset(resource.offset, offset)
|
local pos = apply_offset(resource.offset, offset)
|
||||||
for x = pos.x, pos.x + resource.size[1] do
|
for x = pos.x, pos.x + resource.size[1] do
|
||||||
for y = pos.y, pos.y + resource.size[2] do
|
for y = pos.y, pos.y + resource.size[2] do
|
||||||
surface.create_entity{ name = resource.name, amount = resource.amount, position = { x, y } }
|
surface.create_entity{ name = resource.name, amount = resource.amount, position = { x = x, y = y } }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -220,7 +237,7 @@ end
|
|||||||
|
|
||||||
--- Spawn the resource entities
|
--- Spawn the resource entities
|
||||||
--- @param surface LuaSurface
|
--- @param surface LuaSurface
|
||||||
--- @param offset MapPosition
|
--- @param offset MapPosition.struct
|
||||||
local function create_resource_patches(surface, offset)
|
local function create_resource_patches(surface, offset)
|
||||||
for _, resource in ipairs(config.resource_patches.resources) do
|
for _, resource in ipairs(config.resource_patches.resources) do
|
||||||
if resource.enabled then
|
if resource.enabled then
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Adds a config menu for setting autofill of placed entities
|
|||||||
]]
|
]]
|
||||||
|
|
||||||
local Gui = require("modules/exp_gui")
|
local Gui = require("modules/exp_gui")
|
||||||
local Roles = require("modules.exp_legacy.expcore.roles")
|
local Roles = require("modules/exp_roles")
|
||||||
local config = require("modules.exp_legacy.config.gui.autofill")
|
local config = require("modules.exp_legacy.config.gui.autofill")
|
||||||
local FlyingText = require("modules/exp_util/flying_text")
|
local FlyingText = require("modules/exp_util/flying_text")
|
||||||
|
|
||||||
@@ -330,7 +330,7 @@ Gui.toolbar.create_button{
|
|||||||
sprite = config.icon,
|
sprite = config.icon,
|
||||||
tooltip = { "exp-gui_autofill.tooltip-main" },
|
tooltip = { "exp-gui_autofill.tooltip-main" },
|
||||||
visible = function(player, element)
|
visible = function(player, element)
|
||||||
return Roles.player_allowed(player, "gui/autofill")
|
return Roles.player_has_permission(player, "exp_scenario.gui.autofill")
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Adds a Gui which creates an selection planner to insert modules into buildings
|
|||||||
|
|
||||||
local Gui = require("modules/exp_gui")
|
local Gui = require("modules/exp_gui")
|
||||||
local AABB = require("modules/exp_util/aabb")
|
local AABB = require("modules/exp_util/aabb")
|
||||||
local Roles = require("modules/exp_legacy/expcore/roles")
|
local Roles = require("modules/exp_roles")
|
||||||
|
|
||||||
local Selection = require("modules/exp_util/selection")
|
local Selection = require("modules/exp_util/selection")
|
||||||
local SelectArea = Selection.connect("ModuleArea")
|
local SelectArea = Selection.connect("ModuleArea")
|
||||||
@@ -259,7 +259,7 @@ Gui.toolbar.create_button{
|
|||||||
sprite = "item/productivity-module-3",
|
sprite = "item/productivity-module-3",
|
||||||
tooltip = { "exp-gui_module-inserter.tooltip-main" },
|
tooltip = { "exp-gui_module-inserter.tooltip-main" },
|
||||||
visible = function(player, element)
|
visible = function(player, element)
|
||||||
return Roles.player_allowed(player, "gui/module")
|
return Roles.player_has_permission(player, "exp_scenario.gui.module")
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,7 +459,7 @@ local function on_entity_settings_pasted(event)
|
|||||||
-- Get the modules and add them to the planner
|
-- Get the modules and add them to the planner
|
||||||
local all_modules = {}
|
local all_modules = {}
|
||||||
for i = 1, #module_inventory do
|
for i = 1, #module_inventory do
|
||||||
local slot = module_inventory[i]
|
local slot = module_inventory[i --[[@as uint]]]
|
||||||
if slot.valid_for_read and slot.count > 0 then
|
if slot.valid_for_read and slot.count > 0 then
|
||||||
all_modules[i] = { name = slot.name, quality = slot.quality.name }
|
all_modules[i] = { name = slot.name, quality = slot.quality.name }
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Adds a gui that allows players to apply various bonuses
|
|||||||
]]
|
]]
|
||||||
|
|
||||||
local Gui = require("modules/exp_gui")
|
local Gui = require("modules/exp_gui")
|
||||||
local Roles = require("modules/exp_legacy/expcore/roles")
|
local Roles = require("modules/exp_roles")
|
||||||
local config = require("modules/exp_legacy/config/bonus")
|
local config = require("modules/exp_legacy/config/bonus")
|
||||||
local vlayer = require("modules/exp_legacy/modules/control/vlayer")
|
local vlayer = require("modules/exp_legacy/modules/control/vlayer")
|
||||||
local format_number = require("util").format_number
|
local format_number = require("util").format_number
|
||||||
@@ -68,7 +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_diff = Roles.get_role_by_name(config.points.role_name).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))
|
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
|
||||||
@@ -458,19 +462,19 @@ Gui.toolbar.create_button{
|
|||||||
sprite = "item/exoskeleton-equipment",
|
sprite = "item/exoskeleton-equipment",
|
||||||
tooltip = { "exp-gui_player-bonus.tooltip-main" },
|
tooltip = { "exp-gui_player-bonus.tooltip-main" },
|
||||||
visible = function(player, element)
|
visible = function(player, element)
|
||||||
return Roles.player_allowed(player, "gui/bonus")
|
return Roles.player_has_permission(player, "exp_scenario.gui.bonus")
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
--- Recalculate and apply the bonus for a player
|
--- Recalculate and apply the bonus for a player
|
||||||
local function recalculate_bonus(event)
|
local function recalculate_bonus(event)
|
||||||
local player = Gui.get_player(event)
|
local player = Gui.get_player(event)
|
||||||
if event.name == Roles.events.on_role_assigned or event.name == Roles.events.on_role_unassigned then
|
if event.name == Roles.events.on_player_roles_changed then
|
||||||
-- If the player's roles changed then we will need to recalculate their limit
|
-- If the player's roles changed then we will need to recalculate their limit
|
||||||
Elements.bonus_used._clear_points_limit_cache(player)
|
Elements.bonus_used._clear_points_limit_cache(player)
|
||||||
local bonus_cost = Elements.container.calculate_cost(player)
|
local bonus_cost = Elements.container.calculate_cost(player)
|
||||||
local within_limit = Elements.bonus_used.refresh_player(player, bonus_cost)
|
local within_limit = Elements.bonus_used.refresh_player(player, bonus_cost)
|
||||||
if not within_limit or not Roles.player_allowed(player, "gui/bonus") then
|
if not within_limit or not Roles.player_has_permission(player, "exp_scenario.gui.bonus") then
|
||||||
Elements.container.clear_player_bonus(player)
|
Elements.container.clear_player_bonus(player)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -512,7 +516,7 @@ end
|
|||||||
--- Apply the periodic bonus to all players
|
--- Apply the periodic bonus to all players
|
||||||
local function apply_periodic_bonus_online()
|
local function apply_periodic_bonus_online()
|
||||||
for _, player in pairs(game.connected_players) do
|
for _, player in pairs(game.connected_players) do
|
||||||
if player.character and Roles.player_allowed(player, "gui/bonus") then
|
if player.character and Roles.player_has_permission(player, "exp_scenario.gui.bonus") then
|
||||||
apply_personal_battery_recharge(player)
|
apply_personal_battery_recharge(player)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -524,8 +528,7 @@ return {
|
|||||||
elements = Elements,
|
elements = Elements,
|
||||||
events = {
|
events = {
|
||||||
[e.on_player_respawned] = recalculate_bonus,
|
[e.on_player_respawned] = recalculate_bonus,
|
||||||
[Roles.events.on_role_assigned] = recalculate_bonus,
|
[Roles.events.on_player_roles_changed] = recalculate_bonus,
|
||||||
[Roles.events.on_role_unassigned] = recalculate_bonus,
|
|
||||||
},
|
},
|
||||||
on_nth_tick = {
|
on_nth_tick = {
|
||||||
[config.periodic_bonus_rate] = apply_periodic_bonus_online,
|
[config.periodic_bonus_rate] = apply_periodic_bonus_online,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Adds a player list to show names and play time; also includes action buttons whi
|
|||||||
|
|
||||||
local ExpUtil = require("modules/exp_util")
|
local ExpUtil = require("modules/exp_util")
|
||||||
local Gui = require("modules/exp_gui")
|
local Gui = require("modules/exp_gui")
|
||||||
local Roles = require("modules/exp_legacy/expcore/roles")
|
local Roles = require("modules/exp_roles")
|
||||||
local config = require("modules/exp_legacy/config/gui/player_list_actions")
|
local config = require("modules/exp_legacy/config/gui/player_list_actions")
|
||||||
|
|
||||||
--- @class ExpGui_PlayerList.elements
|
--- @class ExpGui_PlayerList.elements
|
||||||
@@ -203,7 +203,8 @@ function Elements.player_table.calculate_row_data()
|
|||||||
-- Flatten the roles into a single ordered list
|
-- Flatten the roles into a single ordered list
|
||||||
local count = 0
|
local count = 0
|
||||||
local row_data = {}
|
local row_data = {}
|
||||||
for _, role_name in pairs(Roles.config.order) do
|
for _, role in ipairs(Roles.get_ordered_roles()) do
|
||||||
|
local role_name = role.name
|
||||||
if players[role_name] then
|
if players[role_name] then
|
||||||
for _, player in pairs(players[role_name]) do
|
for _, player in pairs(players[role_name]) do
|
||||||
count = count + 1
|
count = count + 1
|
||||||
@@ -360,7 +361,7 @@ function Elements.action_bar.refresh(action_bar, player, selected_player)
|
|||||||
if buttons.auth and not buttons.auth(player, selected_player) then
|
if buttons.auth and not buttons.auth(player, selected_player) then
|
||||||
flow.visible = false
|
flow.visible = false
|
||||||
else
|
else
|
||||||
flow.visible = Roles.player_allowed(player, action_name)
|
flow.visible = Roles.player_has_permission(player, action_name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -492,7 +493,7 @@ Gui.toolbar.create_button{
|
|||||||
sprite = "entity/character",
|
sprite = "entity/character",
|
||||||
tooltip = { "exp-gui_player-list.main-tooltip" },
|
tooltip = { "exp-gui_player-list.main-tooltip" },
|
||||||
visible = function(player, element)
|
visible = function(player, element)
|
||||||
return Roles.player_allowed(player, "gui/player-list")
|
return Roles.player_has_permission(player, "exp_scenario.gui.player_list")
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -534,8 +535,7 @@ return {
|
|||||||
events = {
|
events = {
|
||||||
[e.on_player_joined_game] = redraw_player_list,
|
[e.on_player_joined_game] = redraw_player_list,
|
||||||
[e.on_player_left_game] = on_player_left_game,
|
[e.on_player_left_game] = on_player_left_game,
|
||||||
[Roles.events.on_role_assigned] = redraw_player_list,
|
[Roles.events.on_player_roles_changed] = redraw_player_list,
|
||||||
[Roles.events.on_role_unassigned] = redraw_player_list,
|
|
||||||
},
|
},
|
||||||
on_nth_tick = {
|
on_nth_tick = {
|
||||||
[1800] = refresh_player_times,
|
[1800] = refresh_player_times,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Displays the player data for a player
|
|||||||
local ExpUtil = require("modules/exp_util")
|
local ExpUtil = require("modules/exp_util")
|
||||||
local Gui = require("modules/exp_gui")
|
local Gui = require("modules/exp_gui")
|
||||||
local ElementsExtra = require("modules/exp_scenario/gui/elements")
|
local ElementsExtra = require("modules/exp_scenario/gui/elements")
|
||||||
local Roles = require("modules/exp_legacy/expcore/roles")
|
local Roles = require("modules/exp_roles")
|
||||||
|
|
||||||
require("modules/exp_legacy/modules/data/statistics")
|
require("modules/exp_legacy/modules/data/statistics")
|
||||||
local PlayerData = require("modules/exp_legacy/expcore/player_data")
|
local PlayerData = require("modules/exp_legacy/expcore/player_data")
|
||||||
@@ -214,7 +214,7 @@ Gui.toolbar.create_button{
|
|||||||
tooltip = { "exp-gui_player-stats.tooltip-main" },
|
tooltip = { "exp-gui_player-stats.tooltip-main" },
|
||||||
left_element = Elements.container,
|
left_element = Elements.container,
|
||||||
visible = function(player, element)
|
visible = function(player, element)
|
||||||
return Roles.player_allowed(player, "gui/playerdata")
|
return Roles.player_has_permission(player, "exp_scenario.gui.playerdata")
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Adds a Gui for displaying item production stats
|
|||||||
]]
|
]]
|
||||||
|
|
||||||
local Gui = require("modules/exp_gui")
|
local Gui = require("modules/exp_gui")
|
||||||
local Roles = require("modules/exp_legacy/expcore/roles")
|
local Roles = require("modules/exp_roles")
|
||||||
|
|
||||||
--- @class ExpGui_ProductionStats.elements
|
--- @class ExpGui_ProductionStats.elements
|
||||||
local Elements = {}
|
local Elements = {}
|
||||||
@@ -250,7 +250,7 @@ Gui.toolbar.create_button{
|
|||||||
sprite = "entity/assembling-machine-3",
|
sprite = "entity/assembling-machine-3",
|
||||||
tooltip = { "exp-gui_production-stats.tooltip-main" },
|
tooltip = { "exp-gui_production-stats.tooltip-main" },
|
||||||
visible = function(player, element)
|
visible = function(player, element)
|
||||||
return Roles.player_allowed(player, "gui/production")
|
return Roles.player_has_permission(player, "exp_scenario.gui.production")
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Adds a few buttons for common actions
|
|||||||
|
|
||||||
local Gui = require("modules/exp_gui")
|
local Gui = require("modules/exp_gui")
|
||||||
local Commands = require("modules/exp_commands")
|
local Commands = require("modules/exp_commands")
|
||||||
local Roles = require("modules/exp_legacy/expcore/roles")
|
local Roles = require("modules/exp_roles")
|
||||||
|
|
||||||
local addon_artillery = require("modules/exp_scenario/commands/artillery")
|
local addon_artillery = require("modules/exp_scenario/commands/artillery")
|
||||||
local addon_trains = require("modules/exp_scenario/commands/trains")
|
local addon_trains = require("modules/exp_scenario/commands/trains")
|
||||||
@@ -116,7 +116,7 @@ Gui.toolbar.create_button{
|
|||||||
sprite = "item/repair-pack",
|
sprite = "item/repair-pack",
|
||||||
tooltip = { "exp-gui_quick-actions.tooltip-main" },
|
tooltip = { "exp-gui_quick-actions.tooltip-main" },
|
||||||
visible = function(player, element)
|
visible = function(player, element)
|
||||||
return Roles.player_allowed(player, "gui/tool")
|
return Roles.player_has_permission(player, "exp_scenario.gui.tool")
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +129,6 @@ end
|
|||||||
return {
|
return {
|
||||||
elements = Elements,
|
elements = Elements,
|
||||||
events = {
|
events = {
|
||||||
[Roles.events.on_role_assigned] = on_role_changed,
|
[Roles.events.on_player_roles_changed] = on_role_changed,
|
||||||
[Roles.events.on_role_unassigned] = on_role_changed,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Adds a main gui that contains important information about the server.
|
|||||||
|
|
||||||
local ExpUtil = require("modules/exp_util")
|
local ExpUtil = require("modules/exp_util")
|
||||||
local Gui = require("modules/exp_gui")
|
local Gui = require("modules/exp_gui")
|
||||||
local Roles = require("modules.exp_legacy.expcore.roles")
|
local Roles = require("modules/exp_roles")
|
||||||
local Commands = require("modules/exp_commands")
|
local Commands = require("modules/exp_commands")
|
||||||
local PlayerData = require("modules.exp_legacy.expcore.player_data")
|
local PlayerData = require("modules.exp_legacy.expcore.player_data")
|
||||||
local External = require("modules.exp_legacy.expcore.external")
|
local External = require("modules.exp_legacy.expcore.external")
|
||||||
@@ -214,7 +214,7 @@ define_tab(
|
|||||||
container.add{ type = "flow" }.style.height = 4
|
container.add{ type = "flow" }.style.height = 4
|
||||||
|
|
||||||
local role_names = {}
|
local role_names = {}
|
||||||
for i, role in ipairs(Roles.get_player_roles(player)) do
|
for i, role in ipairs(Roles.sort_roles(Roles.get_player_roles(player))) do
|
||||||
role_names[i] = role.name
|
role_names[i] = role.name
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -392,14 +392,16 @@ define_tab(
|
|||||||
|
|
||||||
local done = {}
|
local done = {}
|
||||||
|
|
||||||
-- Fill groups from configured roles
|
-- Fill groups from the players who hold the roles, a player can be in more than one group
|
||||||
for player_name, player_roles in pairs(Roles.config.players) do
|
for _, group in ipairs(groups) do
|
||||||
for _, group in ipairs(groups) do
|
local seen = {}
|
||||||
for _, role_name in ipairs(group.roles) do
|
for _, role_name in ipairs(group.roles) do
|
||||||
if table.contains(player_roles, role_name) then
|
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
|
||||||
done[player_name] = true
|
done[player_name] = true
|
||||||
group.players[#group.players + 1] = player_name
|
group.players[#group.players + 1] = player_name
|
||||||
break
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -463,7 +465,7 @@ local function render_data_category(opts)
|
|||||||
for name, child in pairs(opts.children) do
|
for name, child in pairs(opts.children) do
|
||||||
local metadata = child.metadata
|
local metadata = child.metadata
|
||||||
|
|
||||||
if not metadata.permission or Roles.player_allowed(opts.player, metadata.permission) then
|
if not metadata.permission or Roles.player_has_permission(opts.player, metadata.permission) then
|
||||||
local value = child:get(opts.player_name)
|
local value = child:get(opts.player_name)
|
||||||
|
|
||||||
if value ~= nil or metadata.show_always then
|
if value ~= nil or metadata.show_always then
|
||||||
@@ -663,7 +665,7 @@ Elements.toggle_button = Gui.toolbar.create_button{
|
|||||||
sprite = "virtual-signal/signal-info",
|
sprite = "virtual-signal/signal-info",
|
||||||
tooltip = { "exp-gui_readme.main-tooltip" },
|
tooltip = { "exp-gui_readme.main-tooltip" },
|
||||||
visible = function(player)
|
visible = function(player)
|
||||||
return Roles.player_allowed(player, "gui/readme")
|
return Roles.player_has_permission(player, "exp_scenario.gui.readme")
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
:on_click(function(_, player)
|
:on_click(function(_, player)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Adds a gui for tracking research milestones
|
|||||||
|
|
||||||
local ExpUtil = require("modules/exp_util")
|
local ExpUtil = require("modules/exp_util")
|
||||||
local Gui = require("modules/exp_gui")
|
local Gui = require("modules/exp_gui")
|
||||||
local Roles = require("modules/exp_legacy/expcore/roles")
|
local Roles = require("modules/exp_roles")
|
||||||
local config = require("modules/exp_legacy/config/research")
|
local config = require("modules/exp_legacy/config/research")
|
||||||
|
|
||||||
local table_to_json = helpers.table_to_json
|
local table_to_json = helpers.table_to_json
|
||||||
@@ -294,9 +294,9 @@ end
|
|||||||
--- Get the achieved time for a force
|
--- Get the achieved time for a force
|
||||||
--- @param force LuaForce
|
--- @param force LuaForce
|
||||||
--- @param research_index number
|
--- @param research_index number
|
||||||
--- @return number
|
--- @return number?
|
||||||
function Elements.container.get_achieved_time(force, research_index)
|
function Elements.container.get_achieved_time(force, research_index)
|
||||||
return assert(Elements.container.data[force][research_index])
|
return assert(Elements.container.data[force])[research_index]
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Calculate the starting research index for a force
|
--- Calculate the starting research index for a force
|
||||||
@@ -339,7 +339,7 @@ Gui.toolbar.create_button{
|
|||||||
sprite = "item/space-science-pack",
|
sprite = "item/space-science-pack",
|
||||||
tooltip = { "exp-gui_research-milestones.tooltip-main" },
|
tooltip = { "exp-gui_research-milestones.tooltip-main" },
|
||||||
visible = function(player, element)
|
visible = function(player, element)
|
||||||
return Roles.player_allowed(player, "gui/research")
|
return Roles.player_has_permission(player, "exp_scenario.gui.research")
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Adds a rocket information gui which shows general stats, milestones and build pr
|
|||||||
|
|
||||||
local ExpUtil = require("modules/exp_util")
|
local ExpUtil = require("modules/exp_util")
|
||||||
local Gui = require("modules/exp_gui")
|
local Gui = require("modules/exp_gui")
|
||||||
local Roles = require("modules/exp_legacy/expcore/roles")
|
local Roles = require("modules/exp_roles")
|
||||||
local config = require("modules/exp_legacy/config/gui/rockets")
|
local config = require("modules/exp_legacy/config/gui/rockets")
|
||||||
|
|
||||||
--- @class ExpGui_RocketInfo.elements
|
--- @class ExpGui_RocketInfo.elements
|
||||||
@@ -651,7 +651,7 @@ Gui.toolbar.create_button{
|
|||||||
sprite = "item/rocket-silo",
|
sprite = "item/rocket-silo",
|
||||||
tooltip = { "exp-gui_rocket-info.tooltip-main" },
|
tooltip = { "exp-gui_rocket-info.tooltip-main" },
|
||||||
visible = function(player, element)
|
visible = function(player, element)
|
||||||
return Roles.player_allowed(player, "gui/rocket-info")
|
return Roles.player_has_permission(player, "exp_scenario.gui.rocket_info")
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Adds a science info gui that shows production usage and net for the different sc
|
|||||||
local ExpUtil = require("modules/exp_util")
|
local ExpUtil = require("modules/exp_util")
|
||||||
local Gui = require("modules/exp_gui")
|
local Gui = require("modules/exp_gui")
|
||||||
local Colors = require("modules/exp_util/include/color")
|
local Colors = require("modules/exp_util/include/color")
|
||||||
local Roles = require("modules/exp_legacy/expcore/roles")
|
local Roles = require("modules/exp_roles")
|
||||||
local config = require("modules/exp_legacy/config/gui/science")
|
local config = require("modules/exp_legacy/config/gui/science")
|
||||||
local _format_number = require("util").format_number
|
local _format_number = require("util").format_number
|
||||||
|
|
||||||
@@ -571,7 +571,7 @@ Gui.toolbar.create_button{
|
|||||||
sprite = "entity/lab",
|
sprite = "entity/lab",
|
||||||
tooltip = { "exp-gui_science-production.tooltip-main" },
|
tooltip = { "exp-gui_science-production.tooltip-main" },
|
||||||
visible = function(player, element)
|
visible = function(player, element)
|
||||||
return Roles.player_allowed(player, "gui/science-info")
|
return Roles.player_has_permission(player, "exp_scenario.gui.science_info")
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Adds cameras which can be used to view players and locations
|
|||||||
|
|
||||||
local Gui = require("modules/exp_gui")
|
local Gui = require("modules/exp_gui")
|
||||||
local ElementsExtra = require("modules/exp_scenario/gui/elements")
|
local ElementsExtra = require("modules/exp_scenario/gui/elements")
|
||||||
local Roles = require("modules/exp_legacy/expcore/roles")
|
local Roles = require("modules/exp_roles")
|
||||||
|
|
||||||
--- @class ExpGui_Surveillance.elements
|
--- @class ExpGui_Surveillance.elements
|
||||||
local Elements = {}
|
local Elements = {}
|
||||||
@@ -234,7 +234,7 @@ Gui.toolbar.create_button{
|
|||||||
sprite = "entity/radar",
|
sprite = "entity/radar",
|
||||||
tooltip = { "exp-gui_surveillance.tooltip-main" },
|
tooltip = { "exp-gui_surveillance.tooltip-main" },
|
||||||
visible = function(player, element)
|
visible = function(player, element)
|
||||||
return Roles.player_allowed(player, "gui/surveillance")
|
return Roles.player_has_permission(player, "exp_scenario.gui.surveillance")
|
||||||
end
|
end
|
||||||
}:on_click(function(def, player, element, event)
|
}:on_click(function(def, player, element, event)
|
||||||
Elements.container(player.gui.screen)
|
Elements.container(player.gui.screen)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Adds a task list to the game which players can add, remove and edit items on
|
|||||||
]]
|
]]
|
||||||
|
|
||||||
local Gui = require("modules/exp_gui")
|
local Gui = require("modules/exp_gui")
|
||||||
local Roles = require("modules.exp_legacy.expcore.roles")
|
local Roles = require("modules/exp_roles")
|
||||||
local config = require("modules.exp_legacy.config.gui.tasks")
|
local config = require("modules.exp_legacy.config.gui.tasks")
|
||||||
|
|
||||||
local ExpUtil = require("modules/exp_util")
|
local ExpUtil = require("modules/exp_util")
|
||||||
@@ -43,8 +43,8 @@ local function has_permission_create_task(player)
|
|||||||
return true
|
return true
|
||||||
elseif allow_add_task == "admin" then
|
elseif allow_add_task == "admin" then
|
||||||
return player.admin
|
return player.admin
|
||||||
elseif allow_add_task == "expcore.roles" then
|
elseif allow_add_task == "exp_roles" then
|
||||||
return Roles.player_allowed(player, config.expcore_roles_allow_add_task)
|
return Roles.player_has_permission(player, config.exp_roles_allow_add_task)
|
||||||
end
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
@@ -67,8 +67,8 @@ local function has_permission_edit_task(player, task)
|
|||||||
return true
|
return true
|
||||||
elseif allow_edit_task == "admin" then
|
elseif allow_edit_task == "admin" then
|
||||||
return player.admin
|
return player.admin
|
||||||
elseif allow_edit_task == "expcore.roles" then
|
elseif allow_edit_task == "exp_roles" then
|
||||||
return Roles.player_allowed(player, config.expcore_roles_allow_edit_task)
|
return Roles.player_has_permission(player, config.exp_roles_allow_edit_task)
|
||||||
end
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
@@ -922,7 +922,7 @@ Gui.toolbar.create_button{
|
|||||||
sprite = "utility/not_enough_repair_packs_icon",
|
sprite = "utility/not_enough_repair_packs_icon",
|
||||||
tooltip = { "exp-gui_task-list.tooltip-main" },
|
tooltip = { "exp-gui_task-list.tooltip-main" },
|
||||||
visible = function(player, element)
|
visible = function(player, element)
|
||||||
return Roles.player_allowed(player, "gui/task-list")
|
return Roles.player_has_permission(player, "exp_scenario.gui.task_list")
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -950,7 +950,6 @@ return {
|
|||||||
events = {
|
events = {
|
||||||
[e.on_player_joined_game] = refresh_player_tasks,
|
[e.on_player_joined_game] = refresh_player_tasks,
|
||||||
[e.on_player_changed_force] = refresh_player_tasks,
|
[e.on_player_changed_force] = refresh_player_tasks,
|
||||||
[Roles.events.on_role_assigned] = refresh_player_permissions,
|
[Roles.events.on_player_roles_changed] = refresh_player_permissions,
|
||||||
[Roles.events.on_role_unassigned] = refresh_player_permissions,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"clusterio": "*",
|
"clusterio": "*",
|
||||||
"exp_util": "*",
|
"exp_util": "*",
|
||||||
|
"exp_roles": "*",
|
||||||
"exp_gui": "*",
|
"exp_gui": "*",
|
||||||
"exp_commands": "*"
|
"exp_commands": "*"
|
||||||
}
|
}
|
||||||
|
|||||||
+121
-151
@@ -1,160 +1,130 @@
|
|||||||
import * as lib from "@clusterio/lib";
|
import * as lib from "@clusterio/lib";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a legacy role action into its clusterio permission name.
|
* Permissions checked by the scenario in game through exp_roles.
|
||||||
*
|
*
|
||||||
* The exp_roles lua module applies the same transform, so existing call sites such as
|
* Commands are checked as `exp_scenario.command.<name>` with hyphens replaced by
|
||||||
* `Roles.player_allowed(player, "gui/warp-list/add")` keep working unchanged.
|
* underscores, see module/commands/_authorities.lua. Everything else is checked
|
||||||
*
|
* by name at its call site.
|
||||||
* @param action - Legacy action such as `gui/warp-list/add` or `fast-tree-decon`.
|
|
||||||
* @returns Permission name such as `exp_scenario.gui.warp_list.add`.
|
|
||||||
*/
|
*/
|
||||||
export function permissionFromAction(action: string) {
|
type Definition = [name: string, title: string, description: string, grantByDefault?: boolean];
|
||||||
const body = action.replace(/-/g, "_").replace(/\//g, ".");
|
|
||||||
return `exp_scenario.${body.includes(".") ? body : `action.${body}`}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
const definitions: Definition[] = [
|
||||||
* Convert a legacy role flag into its clusterio permission name.
|
["exp_scenario.bypass.deconstruction_log", "Deconstruction log bypass", "Be excluded from the deconstruction log."],
|
||||||
*
|
["exp_scenario.bypass.entity_protection", "Bypass entity protection", "Remove entities that the protection filter would block."],
|
||||||
* @param flag - Legacy flag such as `report-immune`.
|
["exp_scenario.bypass.nuke_protection", "Bypass nuke protection", "Use nukes without the nuke protection restrictions."],
|
||||||
* @returns Permission name such as `exp_scenario.flag.report_immune`.
|
["exp_scenario.bypass.reports", "Report immune", "Be immune to being reported by other players."],
|
||||||
*/
|
["exp_scenario.chat.commands", "Chat commands", "Use the chat command prefix to trigger auto replies."],
|
||||||
export function permissionFromFlag(flag: string) {
|
["exp_scenario.command._ipc", "/_ipc", "Send an IPC message on the selected channel."],
|
||||||
return `exp_scenario.flag.${flag.replace(/-/g, "_")}`;
|
["exp_scenario.command._rcon", "/_rcon", "Execute arbitrary code within a custom environment."],
|
||||||
}
|
["exp_scenario.command._sudo", "/_sudo", "Run a command as another player."],
|
||||||
|
["exp_scenario.command.admin_chat", "/admin-chat", "Sends a message in chat that only admins can see."],
|
||||||
/** Legacy actions which map onto core clusterio permissions rather than scenario ones. */
|
["exp_scenario.command.artillery", "/artillery", "Automaticly select enemy target with artillery."],
|
||||||
export const coreActionPermissions: Record<string, string> = {
|
["exp_scenario.command.assign_role", "/assign-role", "Assigns a role lower than your own to a player."],
|
||||||
"command/assign-role": "core.user.update_roles",
|
["exp_scenario.command.bring", "/bring", "Teleports a player to you."],
|
||||||
"command/unassign-role": "core.user.update_roles",
|
["exp_scenario.command.clear_blueprints", "/clear-blueprints", "Clear all blueprints."],
|
||||||
"command/get-roles": "core.role.list",
|
["exp_scenario.command.clear_blueprints_surface", "/clear-blueprints-surface", "Clear all blueprints on the current surface."],
|
||||||
};
|
["exp_scenario.command.clear_ground_items", "/clear-ground-items", "Clear all items on the ground."],
|
||||||
|
["exp_scenario.command.clear_inventory", "/clear-inventory", "Clear a player's inventory, moving all items to spawn."],
|
||||||
/** Legacy action, user facing title, description, and whether every player gets it. */
|
["exp_scenario.command.clear_last_warnings", "/clear-last-warnings", "Clears the last warning from a player."],
|
||||||
type ActionDefinition = [action: string, title: string, description: string, grantByDefault?: boolean];
|
["exp_scenario.command.clear_pollution", "/clear-pollution", "Clear pollution from your current surface, or another surface."],
|
||||||
|
["exp_scenario.command.clear_reports", "/clear-reports", "Clears all reports from a player or just the report from one player."],
|
||||||
const actions: ActionDefinition[] = [
|
["exp_scenario.command.clear_script_warnings", "/clear-script-warnings", "Clears all script warnings from a player."],
|
||||||
["bypass-entity-protection", "Bypass entity protection", "Remove entities that the protection filter would block."],
|
["exp_scenario.command.clear_warnings", "/clear-warnings", "Clears all warnings (and script warnings) from a player."],
|
||||||
["bypass-nukeprotect", "Bypass nuke protection", "Use nukes without the nuke protection restrictions."],
|
["exp_scenario.command.collectdata", "/collectdata", "Collect data for RCON usage."],
|
||||||
["command/_rcon", "/_rcon", "Execute arbitrary code within a custom environment."],
|
["exp_scenario.command.commands", "/commands", "List and search all commands for a keyword.", true],
|
||||||
["command/admin-chat", "/admin-chat", "Sends a message in chat that only admins can see."],
|
["exp_scenario.command.connect", "/connect", "Connect to another server.", true],
|
||||||
["command/artillery", "/artillery", "Automaticly select enemy target with artillery."],
|
["exp_scenario.command.connect_all", "/connect-all", "Connect all players to another server."],
|
||||||
["command/ban", "/ban", "Ban a player from the cluster via the player list."],
|
["exp_scenario.command.connect_player", "/connect-player", "Connect a player to a different server."],
|
||||||
["command/bring", "/bring", "Teleports a player to you."],
|
["exp_scenario.command.create_report", "/create-report", "Reports a player and notifies moderators.", true],
|
||||||
["command/chat-commands", "Chat commands", "Use the chat command prefix to trigger auto replies."],
|
["exp_scenario.command.create_warning", "/create-warning", "Gives a warning to a player; may lead to automatic script action."],
|
||||||
["command/clear-blueprints", "/clear-blueprints", "Clear all blueprints."],
|
["exp_scenario.command.data_preference", "/data-preference", "Allows you to set/get your data saving preference.", true],
|
||||||
["command/clear-blueprints-surface", "/clear-blueprints-surface", "Clear all blueprints on the current surface."],
|
["exp_scenario.command.debug", "/debug", "Opens the debug gui."],
|
||||||
["command/clear-ground-items", "/clear-ground-items", "Clear all items on the ground."],
|
["exp_scenario.command.follow", "/follow", "Start following a player in spectator."],
|
||||||
["command/clear-inventory", "/clear-inventory", "Clear a player's inventory, moving all items to spawn."],
|
["exp_scenario.command.get_home", "/get-home", "Returns your current home location."],
|
||||||
["command/clear-last-warnings", "/clear-last-warnings", "Clears the last warning from a player."],
|
["exp_scenario.command.get_reports", "/get-reports", "List the reports against a player, or against every player."],
|
||||||
["command/clear-pollution", "/clear-pollution", "Clear pollution from your current surface, or another surface."],
|
["exp_scenario.command.get_roles", "/get-roles", "Get all roles that a player has, if no player provided it lists all roles.", true],
|
||||||
["command/clear-reports", "/clear-reports", "Clears all reports from a player or just the report from one player."],
|
["exp_scenario.command.get_warnings", "/get-warnings", "List the warnings against a player, or against every player."],
|
||||||
["command/clear-script-warnings", "/clear-script-warnings", "Clears all script warnings from a player."],
|
["exp_scenario.command.goto", "/goto", "Teleports you to a player."],
|
||||||
["command/clear-tag/always", "/clear-tag (any player)", "Clear the tag of any player, not just your own."],
|
["exp_scenario.command.home", "/home", "Teleports you to your home location."],
|
||||||
["command/clear-warnings", "/clear-warnings", "Clears all warnings (and script warnings) from a player."],
|
["exp_scenario.command.jail", "/jail", "Puts a player into jail, which suppresses all of their other roles."],
|
||||||
["command/collectdata", "/collectdata", "Collect data for RCON usage."],
|
["exp_scenario.command.kill", "/kill", "Kills yourself or another player."],
|
||||||
["command/commands", "/commands", "List and search all commands for a keyword.", true],
|
["exp_scenario.command.kill.always", "/kill (any player)", "Kill any player, not just yourself."],
|
||||||
["command/connect", "/connect", "Connect to another server.", true],
|
["exp_scenario.command.kill_enemies", "/kill-enemies", "Kill all enemy units."],
|
||||||
["command/connect-all", "/connect-all", "Connect all players to another server."],
|
["exp_scenario.command.lawnmower", "/lawnmower", "Clean up biter corpse, decoratives and nuclear hole."],
|
||||||
["command/connect-player", "/connect-player", "Connect a player to a different server."],
|
["exp_scenario.command.locate", "/locate", "Opens remote view at the location of the player's last location.", true],
|
||||||
["command/create-report", "/create-report", "Reports a player and notifies moderators.", true],
|
["exp_scenario.command.me", "/me", "Sends an action message in the chat."],
|
||||||
["command/create-warning", "/create-warning", "Gives a warning to a player; may lead to automatic script action."],
|
["exp_scenario.command.protect_area", "/protect-area", "Toggles area protection selection, hold shift to remove protection."],
|
||||||
["command/data-preference", "/data-preference", "Allows you to set/get your data saving preference.", true],
|
["exp_scenario.command.protect_entity", "/protect-entity", "Toggles entity protection selection, hold shift to remove protection."],
|
||||||
["command/debug", "/debug", "Opens the debug gui."],
|
["exp_scenario.command.protect_tag", "/protect-tag", "Toggles protected tag mode, edit and create protected map tags."],
|
||||||
["command/follow", "/follow", "Start following a player in spectator."],
|
["exp_scenario.command.rainbow", "/rainbow", "Sends an rainbow message in the chat."],
|
||||||
["command/get-home", "/get-home", "Returns your current home location."],
|
["exp_scenario.command.ratio", "/ratio", "Get the input and output ratios of the selected machine.", true],
|
||||||
["command/get-reports", "/get-reports", "List the reports against a player, or against every player."],
|
["exp_scenario.command.remove_enemies", "/remove-enemies", "Remove all enemy spawners."],
|
||||||
["command/get-warnings", "/get-warnings", "List the warnings against a player, or against every player."],
|
["exp_scenario.command.remove_join_message", "/remove-join-message", "Removes you custom join message."],
|
||||||
["command/give-warning", "/give-warning", "Give a player a warning via the player list."],
|
["exp_scenario.command.repair", "/repair", "Repairs entities on your force around you."],
|
||||||
["command/goto", "/goto", "Teleports you to a player."],
|
["exp_scenario.command.research_all", "/research-all", "Research all technology for your force, or another force."],
|
||||||
["command/home", "/home", "Teleports you to your home location."],
|
["exp_scenario.command.return", "/return", "Teleports you to previous location."],
|
||||||
["command/jail", "/jail", "Puts a player into jail and removes all other roles."],
|
["exp_scenario.command.save_data", "/save-data", "Writes all your player data to a file on your computer.", true],
|
||||||
["command/kick", "/kick", "Kick a player from the server via the player list."],
|
["exp_scenario.command.save_quickbar", "/save-quickbar", "Saves your Quickbar preset items to file."],
|
||||||
["command/kill", "/kill", "Kills yourself or another player."],
|
["exp_scenario.command.search", "/search", "Display players sorted by the quantity of an item held and playtime."],
|
||||||
["command/kill-enemies", "/kill-enemies", "Kill all enemy units."],
|
["exp_scenario.command.search_amount", "/search-amount", "Display players sorted by the quantity of an item held."],
|
||||||
["command/kill/always", "/kill (any player)", "Kill any player, not just yourself."],
|
["exp_scenario.command.search_online", "/search-online", "Display online players sorted by item count and playtime."],
|
||||||
["command/lawnmower", "/lawnmower", "Clean up biter corpse, decoratives and nuclear hole."],
|
["exp_scenario.command.search_recent", "/search-recent", "Display players who hold an item sorted by join time."],
|
||||||
["command/locate", "/locate", "Opens remote view at the location of the player's last location.", true],
|
["exp_scenario.command.server_ups", "/server-ups", "Toggle the server UPS display.", true],
|
||||||
["command/me", "/me", "Sends an action message in the chat."],
|
["exp_scenario.command.set_always_day", "/set-always-day", "Set always day for your current surface, or another surface."],
|
||||||
["command/protect-area", "/protect-area", "Toggles area protection selection, hold shift to remove protection."],
|
["exp_scenario.command.set_bot_queue", "/set-bot-queue", "Get / Set the construction bot queue limits."],
|
||||||
["command/protect-entity", "/protect-entity", "Toggles entity protection selection, hold shift to remove protection."],
|
["exp_scenario.command.set_cheat_mode", "/set-cheat-mode", "Set cheat mode for your player, or another player."],
|
||||||
["command/protect-tag", "/protect-tag", "Toggles protected tag mode, edit and create protected map tags."],
|
["exp_scenario.command.set_friendly_fire", "/set-friendly-fire", "Set friendly fire for your force, or another force."],
|
||||||
["command/rainbow", "/rainbow", "Sends an rainbow message in the chat."],
|
["exp_scenario.command.set_game_speed", "/set-game-speed", "Set or get the current game speed."],
|
||||||
["command/ratio", "/ratio", "Get the input and output ratios of the selected machine.", true],
|
["exp_scenario.command.set_home", "/set-home", "Sets your home location to your current position."],
|
||||||
["command/remove-enemies", "/remove-enemies", "Remove all enemy spawners."],
|
["exp_scenario.command.set_join_message", "/set-join-message", "Sets / Gets your custom join message."],
|
||||||
["command/remove-join-message", "/remove-join-message", "Removes you custom join message."],
|
["exp_scenario.command.set_pollution_enabled", "/set-pollution-enabled", "Set polution enabled state for this game."],
|
||||||
["command/repair", "/repair", "Repairs entities on your force around you."],
|
["exp_scenario.command.set_trains_to_automatic", "/set-trains-to-automatic", "Set all trains without passengers to automatic."],
|
||||||
["command/research-all", "/research-all", "Research all technology for your force, or another force."],
|
["exp_scenario.command.spawn", "/spawn", "Teleport to spawn."],
|
||||||
["command/return", "/return", "Teleports you to previous location."],
|
["exp_scenario.command.spawn.always", "/spawn (any player)", "Teleport any player to spawn, not just yourself."],
|
||||||
["command/save-data", "/save-data", "Writes all your player data to a file on your computer.", true],
|
["exp_scenario.command.spectate", "/spectate", "Toggles spectator mode."],
|
||||||
["command/save-quickbar", "/save-quickbar", "Saves your Quickbar preset items to file."],
|
["exp_scenario.command.tag", "/tag", "Sets your player tag.", true],
|
||||||
["command/search", "/search", "Display players sorted by the quantity of an item held and playtime."],
|
["exp_scenario.command.tag_clear", "/tag-clear", "Clears your tag. Or another player if you are admin.", true],
|
||||||
["command/search-amount", "/search-amount", "Display players sorted by the quantity of an item held."],
|
["exp_scenario.command.tag_clear.always", "/tag-clear (any player)", "Clear the tag of any player, not just your own."],
|
||||||
["command/search-online", "/search-online", "Display online players sorted by item count and playtime."],
|
["exp_scenario.command.tag_color", "/tag-color", "Sets your player tag color."],
|
||||||
["command/search-recent", "/search-recent", "Display players who hold an item sorted by join time."],
|
["exp_scenario.command.teleport", "/teleport", "Teleports a player to another player."],
|
||||||
["command/server-ups", "/server-ups", "Toggle the server UPS display.", true],
|
["exp_scenario.command.unassign_role", "/unassign-role", "Unassigns a role lower than your own from a player."],
|
||||||
["command/set-always-day", "/set-always-day", "Set always day for your current surface, or another surface."],
|
["exp_scenario.command.unjail", "/unjail", "Removes a player from jail, restoring their other roles."],
|
||||||
["command/set-bot-queue", "/set-bot-queue", "Get / Set the construction bot queue limits."],
|
["exp_scenario.command.vlayer_info", "/vlayer-info", "Print all vlayer information."],
|
||||||
["command/set-cheat-mode", "/set-cheat-mode", "Set cheat mode for your player, or another player."],
|
["exp_scenario.command.waterfill", "/waterfill", "Replace tiles with shallow water."],
|
||||||
["command/set-friendly-fire", "/set-friendly-fire", "Set friendly fire for your force, or another force."],
|
["exp_scenario.decon.fast_trees", "Fast tree deconstruction", "Trees and rocks marked for deconstruction are removed instantly, when enabled from the toolbar."],
|
||||||
["command/set-game-speed", "/set-game-speed", "Set or get the current game speed."],
|
["exp_scenario.decon.standard", "Standard deconstruction", "Mark entities placed by other players for deconstruction."],
|
||||||
["command/set-home", "/set-home", "Sets your home location to your current position."],
|
["exp_scenario.gui.autofill", "Autofill", "Open the autofill GUI, which fills placed entities from your inventory.", true],
|
||||||
["command/set-join-message", "/set-join-message", "Sets / Gets your custom join message."],
|
["exp_scenario.gui.bonus", "Bonus", "Open the bonus GUI to adjust your personal bonuses."],
|
||||||
["command/set-pollution-enabled", "/set-pollution-enabled", "Set polution enabled state for this game."],
|
["exp_scenario.gui.module", "Module inserter", "Open the module inserter GUI.", true],
|
||||||
["command/set-trains-to-automatic", "/set-trains-to-automatic", "Set all trains without passengers to automatic."],
|
["exp_scenario.gui.player_list", "Player list", "Open the player list GUI.", true],
|
||||||
["command/spawn", "/spawn", "Teleport to spawn."],
|
["exp_scenario.gui.player_list.ban", "Player list: ban", "Ban a player from the cluster via the player list."],
|
||||||
["command/spawn/always", "/spawn (any player)", "Teleport any player to spawn, not just yourself."],
|
["exp_scenario.gui.player_list.kick", "Player list: kick", "Kick a player from the server via the player list."],
|
||||||
["command/spectate", "/spectate", "Toggles spectator mode."],
|
["exp_scenario.gui.playerdata", "Player statistics", "Open the player statistics GUI."],
|
||||||
["command/tag", "/tag", "Sets your player tag.", true],
|
["exp_scenario.gui.production", "Production statistics", "Open the production statistics GUI.", true],
|
||||||
["command/tag-clear", "/tag-clear", "Clears your tag. Or another player if you are admin.", true],
|
["exp_scenario.gui.readme", "Readme", "Open the server readme GUI.", true],
|
||||||
["command/tag-color", "/tag-color", "Sets your player tag color."],
|
["exp_scenario.gui.research", "Research milestones", "Open the research milestones GUI.", true],
|
||||||
["command/teleport", "/teleport", "Teleports a player to another player."],
|
["exp_scenario.gui.rocket_info", "Rocket info", "Open the rocket info GUI.", true],
|
||||||
["command/unjail", "/unjail", "Removes a player from jail and restores their previous roles."],
|
["exp_scenario.gui.rocket_info.remote_launch", "Rocket info: remote launch", "Launch rockets remotely from the rocket info GUI."],
|
||||||
["command/vlayer-info", "/vlayer-info", "Print all vlayer information."],
|
["exp_scenario.gui.rocket_info.toggle_active", "Rocket info: toggle active", "Toggle whether a silo launches automatically."],
|
||||||
["command/waterfill", "/waterfill", "Replace tiles with shallow water."],
|
["exp_scenario.gui.science_info", "Science production", "Open the science production GUI.", true],
|
||||||
["fast-tree-decon", "Fast tree deconstruction", "Deconstruct trees and rocks instantly over a large area."],
|
["exp_scenario.gui.surveillance", "Surveillance", "Open the surveillance GUI showing remote camera views."],
|
||||||
["gui/autofill", "Autofill", "Open the autofill GUI, which fills placed entities from your inventory.", true],
|
["exp_scenario.gui.task_list", "Task list", "Open the task list GUI.", true],
|
||||||
["gui/bonus", "Bonus", "Open the bonus GUI to adjust your personal bonuses."],
|
["exp_scenario.gui.task_list.add", "Task list: add", "Add new tasks to the task list."],
|
||||||
["gui/module", "Module inserter", "Open the module inserter GUI.", true],
|
["exp_scenario.gui.task_list.edit", "Task list: edit", "Edit and remove existing tasks on the task list."],
|
||||||
["gui/player-list", "Player list", "Open the player list GUI.", true],
|
["exp_scenario.gui.tool", "Quick actions", "Open the quick actions GUI."],
|
||||||
["gui/playerdata", "Player statistics", "Open the player statistics GUI."],
|
["exp_scenario.gui.vlayer", "Virtual layer", "Open the virtual layer GUI.", true],
|
||||||
["gui/production", "Production statistics", "Open the production statistics GUI.", true],
|
["exp_scenario.gui.vlayer_edit", "Virtual layer: edit", "Use the edit controls in the virtual layer GUI."],
|
||||||
["gui/readme", "Readme", "Open the server readme GUI.", true],
|
["exp_scenario.gui.warp_list", "Warp list", "Open the warp list GUI and warp between locations.", true],
|
||||||
["gui/research", "Research milestones", "Open the research milestones GUI.", true],
|
["exp_scenario.gui.warp_list.add", "Warp list: add", "Add new warp points to the warp list."],
|
||||||
["gui/rocket-info", "Rocket info", "Open the rocket info GUI.", true],
|
["exp_scenario.gui.warp_list.bypass_cooldown", "Warp list: bypass cooldown", "Warp without waiting for the warp cooldown."],
|
||||||
["gui/rocket-info/remote_launch", "Rocket info: remote launch", "Launch rockets remotely from the rocket info GUI."],
|
["exp_scenario.gui.warp_list.bypass_proximity", "Warp list: bypass proximity", "Warp without standing near a warp point."],
|
||||||
["gui/rocket-info/toggle-active", "Rocket info: toggle active", "Toggle whether a silo launches automatically."],
|
["exp_scenario.gui.warp_list.edit", "Warp list: edit", "Edit and remove existing warp points."],
|
||||||
["gui/science-info", "Science production", "Open the science production GUI.", true],
|
["exp_scenario.player.admin", "Factorio admin", "Be promoted to Factorio admin while holding a role with this permission."],
|
||||||
["gui/surveillance", "Surveillance", "Open the surveillance GUI showing remote camera views."],
|
["exp_scenario.player.instant_respawn", "Instant respawn", "Respawn after two seconds instead of the default delay."],
|
||||||
["gui/task-list", "Task list", "Open the task list GUI.", true],
|
["exp_scenario.player.spectator", "Spectator", "Remove the zoom to world noise effect, as Factorio does for spectators."],
|
||||||
["gui/task-list/add", "Task list: add", "Add new tasks to the task list."],
|
|
||||||
["gui/task-list/edit", "Task list: edit", "Edit and remove existing tasks on the task list."],
|
|
||||||
["gui/tool", "Quick actions", "Open the quick actions GUI."],
|
|
||||||
["gui/vlayer", "Virtual layer", "Open the virtual layer GUI.", true],
|
|
||||||
["gui/vlayer-edit", "Virtual layer: edit", "Use the edit controls in the virtual layer GUI."],
|
|
||||||
["gui/warp-list", "Warp list", "Open the warp list GUI and warp between locations.", true],
|
|
||||||
["gui/warp-list/add", "Warp list: add", "Add new warp points to the warp list."],
|
|
||||||
["gui/warp-list/bypass-cooldown", "Warp list: bypass cooldown", "Warp without waiting for the warp cooldown."],
|
|
||||||
["gui/warp-list/bypass-proximity", "Warp list: bypass proximity", "Warp without standing near a warp point."],
|
|
||||||
["gui/warp-list/edit", "Warp list: edit", "Edit and remove existing warp points."],
|
|
||||||
["standard-decon", "Standard deconstruction", "Deconstruct trees and rocks."],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const flags: ActionDefinition[] = [
|
for (const [name, title, description, grantByDefault] of definitions) {
|
||||||
["deconlog-bypass", "Deconstruction log bypass", "Be excluded from the deconstruction log."],
|
lib.definePermission({ name, title, description, grantByDefault });
|
||||||
["defer_role_changes", "Defer role changes", "Hold back role changes until this role is removed; used by Jail."],
|
|
||||||
["instant-respawn", "Instant respawn", "Respawn after two seconds instead of the default delay."],
|
|
||||||
["is_admin", "Factorio admin", "Be promoted to Factorio admin while holding a role with this flag."],
|
|
||||||
["is_spectator", "Spectator", "Be placed into Factorio spectator mode."],
|
|
||||||
["is_system", "System commands", "Unlock system commands such as /_rcon and /_sudo."],
|
|
||||||
["report-immune", "Report immune", "Be immune to being reported by other players."],
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const [action, title, description, grantByDefault] of actions) {
|
|
||||||
lib.definePermission({ name: permissionFromAction(action), title, description, grantByDefault });
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [flag, title, description] of flags) {
|
|
||||||
lib.definePermission({ name: permissionFromFlag(flag), title, description });
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ local PlayerData = require("modules/exp_legacy/expcore/player_data")
|
|||||||
local UsesServerUps = PlayerData.Settings:combine("UsesServerUps")
|
local UsesServerUps = PlayerData.Settings:combine("UsesServerUps")
|
||||||
UsesServerUps:set_default(false)
|
UsesServerUps:set_default(false)
|
||||||
UsesServerUps:set_metadata{
|
UsesServerUps:set_metadata{
|
||||||
permission = "command/server-ups",
|
permission = "exp_scenario.command.server_ups",
|
||||||
stringify = function(value) return value and "Visible" or "Hidden" end,
|
stringify = function(value) return value and "Visible" or "Hidden" end,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
"use strict";
|
||||||
|
const { compile } = require("@clusterio/lib");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a flat array of tests from a matrix of inputs.
|
||||||
|
*
|
||||||
|
* @template {any[][]} T
|
||||||
|
* @param {T} arrays - A list of arrays representing the different values for each argument
|
||||||
|
* @returns {Array<{ [K in keyof T]: T[K][number] }>}
|
||||||
|
* An array of tuples, each containing one value from each input array.
|
||||||
|
*/
|
||||||
|
function testMatrix(...arrays) {
|
||||||
|
return arrays.reduce((acc, curr) => acc.flatMap(a => curr.map(b => [...a, b])), [[]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that a class is round trip JSON serialisable across multiple test cases.
|
||||||
|
*
|
||||||
|
* @template {any[]} T - Constructor arguments
|
||||||
|
* @param {import("tap").Test} t - Parent test.
|
||||||
|
* @param {{new(...args: T): object}} Class - The class which has toJSON and fromJSON methods.
|
||||||
|
* @param {T[]} tests - The test inputs to pass to the class constructor.
|
||||||
|
*/
|
||||||
|
function testRoundTripJsonSerialisable(t, Class, tests) {
|
||||||
|
const validate = compile(Class.jsonSchema);
|
||||||
|
|
||||||
|
for (const test of tests) {
|
||||||
|
const name = JSON.stringify(test);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const original = new Class(...test);
|
||||||
|
const jsonObject = JSON.parse(JSON.stringify(original));
|
||||||
|
const reconstructed = Class.fromJSON(jsonObject);
|
||||||
|
|
||||||
|
if (!validate(jsonObject)) {
|
||||||
|
t.fail(`schema validation: ${name}`, {
|
||||||
|
diagnostic: {
|
||||||
|
json: jsonObject,
|
||||||
|
errors: validate.errors,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
t.strictSame(reconstructed, original, `round trip: ${name}`);
|
||||||
|
} catch (error) {
|
||||||
|
t.fail(`round trip: ${name}`, { error });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
testMatrix,
|
||||||
|
testRoundTripJsonSerialisable,
|
||||||
|
};
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
--[[-- Test framework for plugin module tests
|
||||||
|
A suite is a collection of named tests. Test files declare them with
|
||||||
|
`Suite.test(name, function(env) ... end)`, naming each test after the function
|
||||||
|
or method it covers. Every test function receives a fresh environment: the
|
||||||
|
stubs extended by the function the suite was created with, so tests are
|
||||||
|
independent and can not leak state into each other.
|
||||||
|
]]
|
||||||
|
|
||||||
|
local source = assert(debug.getinfo(1, "S")).source
|
||||||
|
local shared_root = assert(source:match("^@(.*)/framework%.lua$"))
|
||||||
|
local Stubs = assert(loadfile(shared_root .. "/stubs.lua"))()
|
||||||
|
|
||||||
|
--- @class Framework
|
||||||
|
local Framework = {}
|
||||||
|
|
||||||
|
--- Turn a value into a string for failure messages
|
||||||
|
local function repr(value, depth)
|
||||||
|
if type(value) ~= "table" then return tostring(value) end
|
||||||
|
if depth > 3 then return "{...}" end
|
||||||
|
|
||||||
|
local parts = {}
|
||||||
|
for key, entry in pairs(value) do
|
||||||
|
parts[#parts + 1] = tostring(key) .. " = " .. repr(entry, depth + 1)
|
||||||
|
end
|
||||||
|
return "{ " .. table.concat(parts, ", ") .. " }"
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Create a suite of tests, one is made per test file
|
||||||
|
--- @generic T : Stubs
|
||||||
|
--- @param extend_env fun(env: Stubs): T Extends the stubs given to each test
|
||||||
|
--- @return Suite<T>
|
||||||
|
function Framework.suite(extend_env)
|
||||||
|
--- @class Suite<T>
|
||||||
|
local Suite = {
|
||||||
|
tests = {}, --- @type { name: string, fn: fun(env: T) }[] In declaration order
|
||||||
|
results = {}, --- @type { name: string, ok: boolean, detail: string? }[] Accumulated across tests
|
||||||
|
}
|
||||||
|
|
||||||
|
local current_test --- @type string?
|
||||||
|
|
||||||
|
--- Declare a named test, its function receives a fresh environment
|
||||||
|
--- @param name string
|
||||||
|
--- @param fn fun(env: T)
|
||||||
|
function Suite.test(name, fn)
|
||||||
|
Suite.tests[#Suite.tests + 1] = { name = name, fn = fn }
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Record a passing check within the current test
|
||||||
|
--- @param name string
|
||||||
|
function Suite.pass(name)
|
||||||
|
Suite.results[#Suite.results + 1] = {
|
||||||
|
name = current_test and (current_test .. ": " .. name) or name,
|
||||||
|
ok = true,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Record a failing check within the current test
|
||||||
|
--- @param name string
|
||||||
|
--- @param detail string?
|
||||||
|
function Suite.fail(name, detail)
|
||||||
|
Suite.results[#Suite.results + 1] = {
|
||||||
|
name = current_test and (current_test .. ": " .. name) or name,
|
||||||
|
ok = false,
|
||||||
|
detail = detail,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check a condition
|
||||||
|
--- @param ok any Truthy when the check passed
|
||||||
|
--- @param name string
|
||||||
|
--- @param detail string?
|
||||||
|
function Suite.check(ok, name, detail)
|
||||||
|
if ok then
|
||||||
|
Suite.pass(name)
|
||||||
|
else
|
||||||
|
Suite.fail(name, detail)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Recursive table equality, keys checked from both sides
|
||||||
|
local function deep_equal(a, b)
|
||||||
|
if type(a) ~= "table" or type(b) ~= "table" then return a == b end
|
||||||
|
for key, value in pairs(a) do
|
||||||
|
if not deep_equal(value, b[key]) then return false end
|
||||||
|
end
|
||||||
|
for key in pairs(b) do
|
||||||
|
if a[key] == nil then return false end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check two values are equal, comparing tables recursively
|
||||||
|
function Suite.eq(a, b, name)
|
||||||
|
Suite.check(deep_equal(a, b), name, ("%s ~= %s"):format(repr(a, 1), repr(b, 1)))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check a table has no entries
|
||||||
|
function Suite.empty(value, name)
|
||||||
|
Suite.check(next(value) == nil, name, repr(value, 1))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check a function raises an error containing the message
|
||||||
|
--- @param fn fun()
|
||||||
|
--- @param message string
|
||||||
|
--- @param name string
|
||||||
|
function Suite.throws(fn, message, name)
|
||||||
|
local ok, err = pcall(fn)
|
||||||
|
if ok then
|
||||||
|
Suite.fail(name, "did not error")
|
||||||
|
elseif not tostring(err):find(message, 1, true) then
|
||||||
|
Suite.fail(name, ("%s does not contain %s"):format(tostring(err), message))
|
||||||
|
else
|
||||||
|
Suite.pass(name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Names of an array of values with a name property, such as roles,
|
||||||
|
--- players, forces, or events
|
||||||
|
--- @param values { name: string }[]
|
||||||
|
--- @return string[]
|
||||||
|
function Suite.names(values)
|
||||||
|
local rtn = {}
|
||||||
|
for index, value in ipairs(values) do
|
||||||
|
rtn[index] = value.name
|
||||||
|
end
|
||||||
|
return rtn
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Sorted copy of an array of strings
|
||||||
|
function Suite.sorted(list)
|
||||||
|
local rtn = {}
|
||||||
|
for index, value in ipairs(list) do rtn[index] = value end
|
||||||
|
table.sort(rtn)
|
||||||
|
return rtn
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Run every declared test, each against a fresh environment, and return
|
||||||
|
--- the results as JSON for the javascript side
|
||||||
|
function Suite.run()
|
||||||
|
for _, test in ipairs(Suite.tests) do
|
||||||
|
current_test = test.name
|
||||||
|
local ok, err = pcall(function()
|
||||||
|
test.fn(extend_env(Stubs.new()))
|
||||||
|
end)
|
||||||
|
if not ok then
|
||||||
|
Suite.fail("did not error", tostring(err))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
current_test = nil
|
||||||
|
|
||||||
|
local function escape(value)
|
||||||
|
return (value:gsub('[%c"\\]', function(c)
|
||||||
|
return string.format("\\u%04x", c:byte())
|
||||||
|
end))
|
||||||
|
end
|
||||||
|
|
||||||
|
local parts = {}
|
||||||
|
for index, result in ipairs(Suite.results) do
|
||||||
|
local detail = result.detail and string.format(',"detail":"%s"', escape(result.detail)) or ""
|
||||||
|
parts[index] = string.format('{"name":"%s","ok":%s%s}', escape(result.name), tostring(result.ok), detail)
|
||||||
|
end
|
||||||
|
return "[" .. table.concat(parts, ",") .. "]"
|
||||||
|
end
|
||||||
|
|
||||||
|
return Suite
|
||||||
|
end
|
||||||
|
|
||||||
|
return Framework
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
"use strict";
|
||||||
|
const path = require("node:path");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const { lua, lauxlib, lualib, to_luastring, to_jsstring } = require("fengari");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run one lua test file in its own lua state and return its results.
|
||||||
|
*
|
||||||
|
* The state is created here, on first use by the caller, so every test file
|
||||||
|
* gets an independent set of stubs. Three chunks run in order, each taking one
|
||||||
|
* argument and returning one value:
|
||||||
|
*
|
||||||
|
* 1. The plugin's env.lua, receiving this directory so it can load the shared
|
||||||
|
* stubs and framework, returning its environment module.
|
||||||
|
* 2. The test file, receiving the environment module, declaring its tests.
|
||||||
|
* 3. The tests then run, each against a fresh environment, and the results
|
||||||
|
* are returned as JSON.
|
||||||
|
*
|
||||||
|
* @param {string} envFile - Absolute path of the plugin's test/lua/env.lua.
|
||||||
|
* @param {string} testFile - Absolute path of the lua test file to run.
|
||||||
|
* @returns {{ name: string, ok: boolean, detail?: string }[]}
|
||||||
|
*/
|
||||||
|
function runLuaTests(envFile, testFile) {
|
||||||
|
const L = lauxlib.luaL_newstate();
|
||||||
|
lualib.luaL_openlibs(L);
|
||||||
|
|
||||||
|
const load = (file) => {
|
||||||
|
const code = fs.readFileSync(file);
|
||||||
|
const status = lauxlib.luaL_loadbuffer(L, code, code.length, to_luastring(`@${file}`));
|
||||||
|
if (status !== lua.LUA_OK) {
|
||||||
|
throw new Error(to_jsstring(lua.lua_tostring(L, -1)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const call = () => {
|
||||||
|
if (lua.lua_pcall(L, 1, 1, 0) !== lua.LUA_OK) {
|
||||||
|
throw new Error(to_jsstring(lua.lua_tostring(L, -1)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
load(envFile);
|
||||||
|
lua.lua_pushstring(L, to_luastring(__dirname));
|
||||||
|
call();
|
||||||
|
|
||||||
|
// The environment stays on the stack and is passed to the test chunk
|
||||||
|
load(testFile);
|
||||||
|
lua.lua_insert(L, -2);
|
||||||
|
call();
|
||||||
|
|
||||||
|
const json = to_jsstring(lua.lua_tostring(L, -1));
|
||||||
|
return JSON.parse(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Report the results of a lua test file through a tap test object. */
|
||||||
|
function reportLuaTests(t, envFile, testFile) {
|
||||||
|
const results = runLuaTests(envFile, testFile);
|
||||||
|
t.ok(results.length > 0, `${path.basename(testFile)} produced results`);
|
||||||
|
for (const result of results) {
|
||||||
|
t.ok(result.ok, result.name, result.detail ? { detail: result.detail } : {});
|
||||||
|
}
|
||||||
|
t.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { runLuaTests, reportLuaTests };
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
--[[-- Generic factorio stubs for plugin module tests
|
||||||
|
Provides the surface a clusterio module touches, recording what the module does
|
||||||
|
to it. Every stub raises an error when an unimplemented property is read, which
|
||||||
|
mirrors the game api and catches mistakes in tests early.
|
||||||
|
|
||||||
|
Plugins compose these from their own test/lua/env.lua, adding their own stubs
|
||||||
|
with the extend methods rather than by mutating the tables directly.
|
||||||
|
]]
|
||||||
|
|
||||||
|
local Stubs = {}
|
||||||
|
|
||||||
|
--- Give a table an index metamethod which raises on unknown properties
|
||||||
|
--- @generic T : table
|
||||||
|
--- @param name string Shown in the error message
|
||||||
|
--- @param tbl T
|
||||||
|
--- @param allowed_nil string[]? Properties which may be read while unset
|
||||||
|
--- @return T
|
||||||
|
function Stubs.strict(name, tbl, allowed_nil)
|
||||||
|
local allowed = {}
|
||||||
|
for _, key in pairs(allowed_nil or {}) do
|
||||||
|
allowed[key] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
return setmetatable(tbl, {
|
||||||
|
__index = function(_, key)
|
||||||
|
if allowed[key] then return nil end
|
||||||
|
error(name .. " does not implement: " .. tostring(key), 2)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Merge extra into target recursively, raising when a value already exists
|
||||||
|
--- @generic T : table
|
||||||
|
--- @param target T
|
||||||
|
--- @param extra table
|
||||||
|
--- @return T
|
||||||
|
function Stubs.extend(target, extra)
|
||||||
|
for key, value in pairs(extra) do
|
||||||
|
local existing = rawget(target, key)
|
||||||
|
if existing == nil then
|
||||||
|
rawset(target, key, value)
|
||||||
|
elseif type(existing) == "table" and type(value) == "table" then
|
||||||
|
Stubs.extend(existing, value)
|
||||||
|
else
|
||||||
|
error("Stub already implements: " .. tostring(key), 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return target
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Create an independent set of stubs, installed as the lua globals
|
||||||
|
function Stubs.new()
|
||||||
|
--- @class Stubs
|
||||||
|
--- @field events table[] Events raised through script.raise_event, name and tick filled in
|
||||||
|
--- @field printed (LocalisedString | { to: string, [1]: LocalisedString })[] Messages from game.print and player.print
|
||||||
|
--- @field sent { channel: string, data: table }[] Payloads sent through the clusterio api
|
||||||
|
--- @field sounds string[] Sounds played to players, as "player:path"
|
||||||
|
local stubs = {
|
||||||
|
events = {},
|
||||||
|
printed = {},
|
||||||
|
sent = {},
|
||||||
|
sounds = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
local next_event_id = 100
|
||||||
|
local registered_metatables = {} --- @type table<table, true>
|
||||||
|
script = Stubs.strict("LuaBootstrap", {
|
||||||
|
generate_event_name = function()
|
||||||
|
next_event_id = next_event_id + 1
|
||||||
|
return next_event_id
|
||||||
|
end,
|
||||||
|
raise_event = function(id, data)
|
||||||
|
-- Factorio fills in the name and tick of the event
|
||||||
|
data.name = id
|
||||||
|
data.tick = game.tick
|
||||||
|
stubs.events[#stubs.events + 1] = data
|
||||||
|
end,
|
||||||
|
register_metatable = function(_, metatable)
|
||||||
|
registered_metatables[metatable] = true
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
defines = Stubs.strict("defines", {
|
||||||
|
events = Stubs.strict("defines.events", {
|
||||||
|
on_player_joined_game = 1,
|
||||||
|
on_multiplayer_init = 2,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Stored by name, with an index metamethod so players are also found by
|
||||||
|
-- their player index, the same way game.players works
|
||||||
|
local players_by_index = {}
|
||||||
|
local players = setmetatable({}, { __index = players_by_index })
|
||||||
|
local connected = {}
|
||||||
|
game = Stubs.strict("LuaGameScript", {
|
||||||
|
tick = 1,
|
||||||
|
players = players,
|
||||||
|
connected_players = connected,
|
||||||
|
print = function(message) stubs.printed[#stubs.printed + 1] = message end,
|
||||||
|
get_player = function(key) return players[key] end,
|
||||||
|
}, { "player" })
|
||||||
|
|
||||||
|
--- Add a player to the stubbed game, indexes are assigned in join order
|
||||||
|
--- @param name string
|
||||||
|
--- @param is_connected boolean? Defaults to connected
|
||||||
|
--- @return Stubs.Player
|
||||||
|
function stubs.add_player(name, is_connected)
|
||||||
|
local index = #players_by_index + 1
|
||||||
|
--- @class Stubs.Player
|
||||||
|
local player = Stubs.strict("LuaPlayer " .. name, {
|
||||||
|
name = name,
|
||||||
|
index = index,
|
||||||
|
connected = is_connected ~= false,
|
||||||
|
valid = true,
|
||||||
|
play_sound = function(opts)
|
||||||
|
stubs.sounds[#stubs.sounds + 1] = name .. ":" .. opts.path
|
||||||
|
end,
|
||||||
|
print = function(message)
|
||||||
|
stubs.printed[#stubs.printed + 1] = { to = name, message }
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
rawset(players, name, player)
|
||||||
|
players_by_index[index] = player
|
||||||
|
if player.connected then connected[#connected + 1] = player end
|
||||||
|
return player
|
||||||
|
end
|
||||||
|
|
||||||
|
--- A player object which represents the server
|
||||||
|
--- @type Stubs.Player
|
||||||
|
stubs.server = Stubs.strict("LuaPlayer <server>", { index = 0, name = "<server>" })
|
||||||
|
|
||||||
|
--- Modules resolved by the stubbed require, add to them with extend_requires
|
||||||
|
local requires = {
|
||||||
|
["modules/clusterio/api"] = Stubs.strict("clusterio api", {
|
||||||
|
send_json = function(channel, data)
|
||||||
|
stubs.sent[#stubs.sent + 1] = { channel = channel, data = data }
|
||||||
|
end,
|
||||||
|
}),
|
||||||
|
["modules/clusterio/compat"] = Stubs.strict("clusterio compat", { script_data = {} }),
|
||||||
|
["modules/exp_util/async"] = Stubs.strict("exp_util async", {
|
||||||
|
register = function(callback)
|
||||||
|
return function(...) return callback(...) end
|
||||||
|
end,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
require = function(name)
|
||||||
|
return assert(rawget(requires, name), "Unexpected require: " .. name)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Add modules or properties to the stubbed require
|
||||||
|
function stubs.extend_requires(extra) return Stubs.extend(requires, extra) end
|
||||||
|
|
||||||
|
--- Add properties to the script, game, and defines globals
|
||||||
|
function stubs.extend_script(extra) return Stubs.extend(script, extra) end
|
||||||
|
function stubs.extend_game(extra) return Stubs.extend(game, extra) end
|
||||||
|
function stubs.extend_defines(extra) return Stubs.extend(defines, extra) end
|
||||||
|
|
||||||
|
--- Copy a value the way factorio saves script data: functions are refused
|
||||||
|
--- and only metatables registered with script.register_metatable survive
|
||||||
|
local function save_load_copy(value, copies)
|
||||||
|
if type(value) == "function" then
|
||||||
|
error("Functions can not be stored in script data", 0)
|
||||||
|
end
|
||||||
|
if type(value) ~= "table" then return value end
|
||||||
|
if copies[value] then return copies[value] end
|
||||||
|
|
||||||
|
local copy = {}
|
||||||
|
copies[value] = copy
|
||||||
|
for key, entry in pairs(value) do
|
||||||
|
copy[save_load_copy(key, copies)] = save_load_copy(entry, copies)
|
||||||
|
end
|
||||||
|
|
||||||
|
local metatable = getmetatable(value)
|
||||||
|
if metatable ~= nil and registered_metatables[metatable] then
|
||||||
|
setmetatable(copy, metatable)
|
||||||
|
end
|
||||||
|
return copy
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Replace the script data with a copy of itself as if the map was saved
|
||||||
|
--- and loaded, the module's on_load handler should be called afterwards
|
||||||
|
function stubs.save_load()
|
||||||
|
local compat = requires["modules/clusterio/compat"]
|
||||||
|
compat.script_data = save_load_copy(compat.script_data, {})
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Forget everything recorded so far
|
||||||
|
function stubs.reset_log()
|
||||||
|
stubs.events, stubs.printed, stubs.sent, stubs.sounds = {}, {}, {}, {}
|
||||||
|
end
|
||||||
|
|
||||||
|
return stubs
|
||||||
|
end
|
||||||
|
|
||||||
|
return Stubs
|
||||||
Reference in New Issue
Block a user