The create-report, get-reports and clear-reports commands now live in the
plugin's own module next to the functions they call, with their locale.
The permissions keep their exp_scenario.command names because the command
authority derives them from the command name.
The Discord report alert in exp_scenario is gone since exp_reports posts to
its own webhook. The shared add_player stub takes an options table with
connected and admin defaulting to true and false, replacing the copy in
the exp_reports test environment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The virtual layer was a legacy control module and a legacy gui built on
the legacy event system. The control module moves to
exp_scenario/module/control/vlayer.lua in the event_handler shape, with the
data typed and the allowed item table built locally rather than by
rewriting the config. The gui moves to exp_scenario/module/gui/vlayer.lua
in the shape of the other scenario guis: statistics as label and bar
defines refreshed from display data computed once, and the interface
controls as a define which owns its dropdowns.
Api changes while moving: interfaces are removed by entity rather than by
searching a position, the dead circuit wiring parameter is gone since
connect_neighbour no longer exists, and remove_item now takes only the
unallocated amount out of storage and counts that production back out of
the total, which the legacy module got wrong.
The locale moves to [exp-gui_vlayer] in en, zh-CN and zh-TW and the legacy
[vlayer] sections, modules and file loader entries are removed.
Co-authored-by: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com>
The warp system was split across a legacy control module and a legacy gui,
both built on expcore.datastore. They are now one exp_scenario gui,
exp_scenario/module/gui/warp_list.lua, in the shape of the task list: per
force warps and per player cooldown and proximity state live on the
container's element data, rows are element defines which carry their warp in
element data, and refreshes compute the player's status once and apply it to
every row. Warps are referenced as objects rather than string ids, the last
editor is a LuaPlayer, and the cooldown counts ticks.
The locale moves to [exp-gui_warp-list] in en, zh-CN and zh-TW with keys
named by region and use, and the legacy [warp-list] sections are removed
along with the two legacy modules and the file loader entry. The config
stays in exp_legacy/config/gui/warps.lua for now like the other gui configs.
The seed roles were owned by exp_roles, but they describe the scenario, so
they now live in exp_scenario/seed.ts next to the permissions they grant.
Each seed role names the permission group its holders belong to, and the
five groups the legacy config defined (Admin, Trusted, Standard, Guest,
Restricted) are seeded through exp_groups along with one role mapping per
role. Mapping priorities follow how exp_roles ranks a player's highest
role, so Jail lands in Restricted regardless of other roles.
SeedRolesRequest becomes exp_scenario's SeedRequest behind a new
exp_scenario.seed permission, and the button moves to the scenario's web
plugin, which gets a web entrypoint for it. exp_roles no longer depends on
exp_scenario, and exp_scenario gains controller tests around the seed.
With groups seeded from the controller the legacy expcore.permission_groups
module and its config are removed, along with the Group rcon static.
Reports were the last legacy control module, kept in the lua state of
each instance. They are now an exp_reports plugin which stores them on
the controller: a report is immutable and holds the reported player, who
made it, the reason, the instance name and when it was made, and the only
operations are get, list, create and delete.
In game nothing is stored. The lua module sends each request over ipc,
the instance plugin asks the controller and hands the answer back with
rcon, and the module prints it to whoever asked if they are still online.
Answers are dropped if the instance stopped meanwhile. The module raises
on_player_reported with the count and reporters of every report against
the player, which report_jail and discord_alerts use, and the commands
and player list button call into it.
The web ui gets a /reports page: a table with search on the player,
reporter and reason, a filter on the instance, delete per row and a
create form which reports in the name of the web user.
New reports are posted to two optional webhooks configured on the
controller, a Discord channel hook as an embed and a generic JSON hook.
Failures are logged rather than failing the report.
Tests cover the messages, the controller against a real Controller, the
instance plugin against a real Instance, and the lua module through the
shared harness.
Warnings were a moderator tool layered on top of reports and had no
callers left outside their own command and the player list button. The
legacy module, its config and locale, the exp_scenario command and its
permissions, the discord alert block and the player list button are all
removed. Reporting from the player list no longer depends on whether the
player could give warnings, anyone can report a player who is not immune.
With the role system finished the jail module has nothing legacy left in
it, so it moves to exp_scenario/module/control/jail.lua and the copy in
exp_legacy is removed. The module takes LuaPlayer only, requires a reason,
returns false rather than nil when there is nothing to do, and exposes its
event ids as Jail.on_player_jailed and Jail.on_player_unjailed in the same
shape as exp_util's selection events.
- Roles are objects and everything done to or with a role is a method on it:
assign, unassign, has_player, has_permission, is_higher_than,
is_lower_than, get_players, get_player_names, print. Assignment has one
entry point, role:assign(player, options), with local_only as an option
rather than a second function.
- Roles are looked up by clusterio id with get_role; get_role_by_name searches
the list for the few places, such as configs, which only know a name. The
name map and the ordered list are gone, get_roles sorts on demand and the
index field is replaced by the comparison methods.
- Players are LuaPlayer objects only, with nil or index 0 for the server.
- get_higher_roles and get_lower_roles replace print_to_roles_higher and
print_to_roles_lower, call sites loop over them with role:print.
- Permission groups are removed from roles again, exp_groups owns the mapping
from roles to groups.
- Seeding is a SeedRolesRequest behind a button on the roles page rather than
running on first start, and creates only the roles; the player assignments
are dropped. The seed lists each permission once at the lowest role which
has it and lets the parent chain carry it up.
- System commands unlock for core.admin rather than a permission of their own.
- Role metatables are registered with Storage.register_metatable so the
methods survive save and load, which the role records in storage needed.
- The player list auth uses Roles.player_outranks directly, and the event
carries role ids rather than names.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every call site of expcore.roles now uses exp_roles, and the legacy module,
its config, and the glue which refreshed guis on role events are deleted.
Where a file only renamed the require and the permission strings the change is
mechanical; the rest:
- Jail is now "give the Jail role" and unjail "take it away". The role has a
higher priority than every other so holding it suppresses them, which is
what stashing and restoring the roles was for.
- The command role authority derives exp_scenario.command.<name> from the
command name, and the role parsers use player_outranks rather than comparing
indexes with their own root check.
- The admin and spectator triggers, and the gui refresh on role changes, live
in exp_scenario/control/roles.lua; the system commands trigger stays with
the command authority.
- The player list warn button is keyed on create_warning, the permission
the command behind it already required, and report on create_report. Both
were keyed on names no role held, so only root ever saw them.
- The warps and tasks configs say exp_roles where they said expcore.roles.
- The role tables the readme and player list read are replaced by
get_player_names and get_roles.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`assert` returns every argument it is given, so `return assert(x, msg)`
was leaking the message as a second return value.
`get_tile` is documented as taking x and y. The async function class
declared `@operator call` with no parameters, which made every async
call look over supplied.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The api documents both as a union with a positional array because both
are accepted as input, which makes every read of `.x` or `.left_top`
optional. Everything read back from the game uses the named form, so
the positional variant is removed and we now always write it that way
too.
The spawn area config offsets and the mine depletion search areas were
the only positional writes left, and apply_offset no longer needs its
fallback.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The player list action setter runs during control setup, before any of
the callbacks that use it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Locals declared ahead of an assignment inside a callback were inferred
as nil at every use site.
The role event handlers are given a class so the table is not unified
with the other module handler tables.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
emmylua only parses the inline cast as `--[[@as T]]`, the spaced form
`--[[ @as T ]]` is treated as a plain comment, so all 149 of them were
doing nothing.
LuaGuiElement.style is a union because a style name can be assigned to
it, so reading it back needs the cast.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The gui element locals are declared at the top and assigned far below,
so emmylua inferred them as nil at every use site.
circuit_oc was reassigned from a control behaviour to a logistic
section, which hid every section method behind the wrong type.
allowed_items entries carry heterogeneous optional properties, now
described by a class.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Migrate player list gui to scenario
Move modules/gui/player-list into exp_scenario/gui/player_list using the
new exp_gui element API, matching the other scenario guis.
- Store the selected player and action on the container's player data
instead of a datastore, folding the datastore on_update reactivity
into explicit setter functions
- Switch the actions config to injected accessor functions and the
exp-gui_player-list locale namespace
- Add the locale section, register in control.lua, and disable the
legacy module in the file loader
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Align names and call patterns to best practice
Apply the conventions from the rocket info gui review to the player list,
and remove the now migrated legacy module.
- Move row building onto the table def as calculate_row_data, and refresh
via refresh_player which computes the row data once per player
- Store the selection on the container with private/public accessor
methods instead of poking element data directly
- Store the selected player as a LuaPlayer rather than a string and update
the actions config to match
- Use :style{} on the player table and make the action and reason bars
their own element definitions
- Delete the migrated legacy gui and its file loader entry
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Address player list review feedback
- Rename the player table element data class to element_data
- Remove the redundant offline selection clear; on_player_left_game
already clears any selection pointing at a player who disconnects
- Move the action bar and reason bar visibility into their own refresh
methods so refresh_player just delegates to them
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Style and locale fixes
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com>
Apply the conventions from the rocket info gui review to the player list,
and remove the now migrated legacy module.
- Move row building onto the table def as calculate_row_data, and refresh
via refresh_player which computes the row data once per player
- Store the selection on the container with private/public accessor
methods instead of poking element data directly
- Store the selected player as a LuaPlayer rather than a string and update
the actions config to match
- Use :style{} on the player table and make the action and reason bars
their own element definitions
- Delete the migrated legacy gui and its file loader entry
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move modules/gui/player-list into exp_scenario/gui/player_list using the
new exp_gui element API, matching the other scenario guis.
- Store the selected player and action on the container's player data
instead of a datastore, folding the datastore on_update reactivity
into explicit setter functions
- Switch the actions config to injected accessor functions and the
exp-gui_player-list locale namespace
- Add the locale section, register in control.lua, and disable the
legacy module in the file loader
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The rocket info gui and its control module now live in
exp_scenario/gui/rocket_info, so delete the orphaned legacy files and
drop the disabled file loader entry.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port modules/gui/rocket-info into exp_scenario/gui/rocket_info using the
new exp_gui element API, matching the other scenario guis. The per-force
stats, launch times and silo tracking from the legacy control module are
folded into the gui module.
- Drop the removed auto/remote launch controls and count launches via
on_cargo_pod_finished_ascending instead of the old launch event
- Add the exp-gui_rocket-info locale section
- Disable the legacy module in the file loader and re-enable the
gui/rocket-info role permission
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Refactor server ups
* Use catalogs
* Move to own plugin
* Use web config
* Remove External.get_server_ups
* Update workspace version requirement
* Remove need for storage
* Add locale
* Fix CI