Join messages were stored in exp_legacy's PlayerData, which nothing
ever synced. The controller now keeps a players.json datastore of
per-player settings keyed by player name, starting with the join
message, and saves it on the save and shutdown hooks. Records with
nothing set are removed.
When a player joins, the instance plugin fetches their record and
prints the message to everyone over rcon in the player's colour, so no
lua is needed to show it. The in-game /set-join-message and
/remove-join-message commands send ipc which the instance forwards to
the controller; /set-join-message without a message asks the instance,
which prints the stored message back to the player.
Greetings is now a feature. Disabling it stops the greeting, the
commands, and the printed join messages. The private greeting with the
community name and Discord link is shown to every joining player, even
those with a join message, since one is a welcome for the player and
the other an announcement to everyone else.
Two controller permissions cover the web interface:
exp_scenario.join_message.set for your own message and .set_other for
anyone's, checked against the requesting user the same way
exp_discord_auth checks unlinking. The seed grants them to Supporter
and Moderator alongside the in-game commands. The user page gets a
Scenario section showing the message with an edit button for users who
may change it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Inventory sync already stores the player colour, tag, quickbar and alt
view (game view settings), so the legacy alt-view, quickbar and tag
modules and the colour persistence are removed along with
/save-quickbar and its quickbar presets.
The /tag, /tag-color and /tag-clear commands move to exp_scenario and
write player.tag directly, which inventory sync then carries between
servers. /tag-color reads the current text back out of the tag, so it
now needs a tag to be set first. It also used to concatenate the colour
table into the tag and error, it now formats it as rich text.
player-colours keeps giving new players their preset or a random
colour on creation, inventory sync replaces it with the stored one for
anyone who has played before.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Clusterio #988 added a permissions array to PluginDeclaration and a
type level Permissions registry, and made checkPermission and the web
UI hasPermission helpers take PermissionName. Move the definePermission
calls of exp_groups and exp_scenario into the declaration and add the
names to the registry so the web pages type check. The browser
tsconfigs include index.ts so the augmentation is visible to the web
bundle, matching the in-repo plugins.
The exp_scenario table keeps its tuple form and derives the name union
from it, so a new row is still one line. Seed role permissions are typed
as PermissionName, so a typo in seed.ts is now a compile error rather
than a warning at seed time. The tests register the declared
permissions through registerPluginPermissions instead of importing
permissions.ts for its side effect.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
exp_scenario reached into @expcluster/roles/dist/node/* for the message
classes and the controller plugin type. The package index now re-exports
messages and the ControllerPlugin type, so consumers import from the
package name like they do with @clusterio/lib. The controller export is
type only, which keeps node code out of the web bundle. The test still
requires the controller classes from dist since they cannot be
re-exported at runtime.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.