Players on the watch list are announced to every online admin on every
instance when they join, and the join is posted to the Discord and JSON
webhooks. The Reports page has a second table for the watch list with an
Add button and a Delete button per row.
Reports can be marked read or unread from the web interface. Each report
row also has an Add to watch list button which opens the add form filled
in with the player and reason. Setting exp_reports.watch_unread_reports
treats players with unread reports as watched.
Reports now store created_at_ms because marking them read changes
updated_at_ms. Old reports.json files migrate on load with updated_at_ms
as the creation time. The report webhook moved from the datastore update
listener to the create handler so reading a report is not posted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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.