The entity protection module moves to exp_scenario/module/control/protection.lua
in the event_handler shape the other control modules use, and the copy in
exp_legacy is removed. The config lookups are built as local sets instead of
rewriting the config tables in place, the two mined handlers become one, and
the entity and area key helpers are exposed so the protection command no
longer keeps its own copies. Event ids are exposed as
Protection.on_player_mined_protected and Protection.on_repeat_violation.
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.
- player_has_any_permission and player_has_all_permission, matching the
account checks the web ui has.
- Roles are no longer sorted on the way out of every lookup. get_ordered_roles
and the public sort_roles cover the two guis and the command which present
roles in order, and the highest role is found with a single scan.
- get_held_role_ids returns the list and the set it already built, rather than
a second function rebuilding the set from the list.
- get_player_names collects into a set before listing, so a player holding the
role in both the synced and the local list is counted once.
- The role metatable is registered directly under the plugin name, dropping
the storage import.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 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>
The nil check burn down added guards which turned out redundant once
the surrounding annotations landed. `_has_handlers` is set to true when
the first handler registers, so it is a boolean rather than the literal
false it was inferred as.
Co-Authored-By: Claude Opus 5 (1M context) <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>
Guards now cover the statements that follow them, and a couple of reads
are restructured so the narrowing survives.
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>
spawn_area accepts both position forms on purpose, so it asserts that
one of the two is present rather than narrowing the type.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Color.struct leaves every channel optional, so the rainbow command
declares its own fully populated colour.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rows and selectors are created alongside the machine selector, so they
exist for anything already in the table.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A union of table types makes every lookup optional. Separate index
signatures on one class say the same thing without the nil.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Values read back from the game always use the named members, so they
are narrowed once where they enter a function rather than at each use.
`LuaControl.force` is a ForceID union, so reading a force only method
off it needs the cast the rest of the repo already uses.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`Roles.new_role` returned the result of `error`, which made it nullable,
and `set_permission_group` returned nil for an unknown group, which
broke the config chain with an index error rather than saying so.
Positions and bounding boxes read back from the game always use the
named members, so they are narrowed once where they enter a function.
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>
`loadstring` was removed in Lua 5.2, `load` takes a string the same way.
Warp gui child lookups are optional, so they use the same assert the
rest of the repo does.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`LogisticFilter.signal` became `value` in 2.0, so the clear loop broke
on the first slot and outdated signals were left behind.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Autofill stores the whole per entity map against a player.
The readme container is called as a define, so it needs the overload
the other element classes carry.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`_display_data` is a cache keyed by force name, not a single record.
The auto complete accumulator starts empty, so its fields are optional.
Game commands have no usage beyond their own help text.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`config.temp_warning_limit` does not exist, the warnings config defines
`script_warning_limit`, and the locale expects it as `__3__/__4__`, so
the command was printing nil.
map_tags is keyed by the force name joined to the tag number, and
custom_color is the Color union.
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>
`data` on an element is the GuiData store keyed by element, not the
value being stored.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The prune loop shadowed its own parameter, so `silo_data[unit_number] =
nil` wrote into the entry instead of removing it from the table.
`unit_number` is optional on LuaEntity but always set for a silo. Note
that `--[[ @as ]]` does not narrow for an index expression, only
`@cast` does.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
emmylua deserialises an unknown diagnostic code to a catch all that
matches no checker, so every `invisible`, `nil-check` and
`global-element` suppression was silently doing nothing. Renamed to the
emmylua codes. `name-style-check` has no equivalent and is dropped, it
was already disabled under luals.
The `get_tile` suppressions referenced an api typedef bug from 2024
which no longer reproduces.
need-check-nil is deferred rather than disabled on merit: a third of the
407 findings come from MapPosition and BoundingBox being aliased as
`struct|[double, double]`, so every `entity.position.x` reads as
possibly nil. The rest need per site knowledge of runtime invariants.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The aabb functions all index the named members, so passing the
shorthand [MapPosition, MapPosition] form would error at runtime. The
annotations now say so.
emmylua requires a type argument on a generic alias, luals did not.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Classes contributed to by more than one file now carry the `partial`
attribute. trains.lua declared its command as `ExpCommand_Artillery`,
which was a copy paste.
`Color.0` and `MapPosition.0` became `.struct` in fmtk 2.1.6.
emmylua rejects `@type` on a function statement or a `do` block, so
those are moved onto the local or replaced by `@param` and `@return`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The role system is moving onto clusterio's own roles and permissions, which
requires every in game action to exist as a real permission rather than a bare
string held only by the lua config.
Adds a permission for each of the 104 actions and 7 flags used by the legacy
role config. Names are derived from the legacy action by a deterministic
transform, exported so the lua side can apply the same mapping and keep existing
call sites working. Command descriptions are taken from the existing locale
entries so they match what players already see.
Actions held by the Guest role are marked grantByDefault, since the in game
default role and clusterio's default role are the same concept.
Role management actions have no scenario permission; they map onto
core.user.update_roles and core.role.list instead.
Also corrects the /unjail description, which was a copy of /jail.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>