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>
ExpGaming - Reports
Lets players report each other in game or from the web interface. Reports are kept on the controller in database/exp_reports/reports.json, so a report made on one instance can be seen and cleared from any other.
Each report has the reported player, the player who made it, a reason, the instance it was made on and the time. A player can only report the same player once. Reports can't be edited, only marked as read or unread from the web interface, and deleted. Nothing is stored in the Lua state, every command sends a request to the controller and the answer is printed once it arrives.
The plugin also keeps a watch list in database/exp_reports/watch_list.json. When a player on it joins any instance, every admin online on every instance is told who joined where and why they are watched, and the join is posted to the webhooks. With exp_reports.watch_unread_reports set, players with unread reports are treated as if they were on the watch list, and their unread reports are listed with the join.
Config
All fields are on the controller.
| Field | Default | Description |
|---|---|---|
exp_reports.discord_webhook_url |
Discord channel webhook which new reports and watched player joins are posted to as an embed. | |
exp_reports.json_webhook_url |
URL which new reports and watched player joins are posted to as JSON. | |
exp_reports.watch_unread_reports |
false |
Treat players with unread reports as if they were on the watch list. |
Only new reports and watched player joins are posted. Deleting or reading a report and editing the watch list are not. The JSON webhook gets { "type": "report_created", "report": { ... } } with the report in the same form as reports.json, or { "type": "watched_player_joined", "player_name", "instance_name", "watch_list_entry", "unread_reports" }. watch_list_entry is left out when the player is only watched for unread reports, and unread_reports is empty unless exp_reports.watch_unread_reports is set. A webhook that fails is logged as a warning and the report is still made.
Permissions
| Permission | Default | Allows |
|---|---|---|
exp_reports.report.get |
granted | Get a report by id. |
exp_reports.report.list |
granted | List the reports against every player, or one player. |
exp_reports.report.subscribe |
granted | Receive updates when reports are made or deleted. |
exp_reports.report.create |
not granted | Report a player from the web interface. |
exp_reports.report.delete |
not granted | Delete reports from the web interface. |
exp_reports.report.mark_read |
not granted | Mark reports as read or unread. |
exp_reports.watch_list.subscribe |
granted | See the watch list and receive updates to it. |
exp_reports.watch_list.add |
not granted | Add a player to the watch list. |
exp_reports.watch_list.remove |
not granted | Remove a player from the watch list. |
These only apply to the web interface and control connections. In game, players with the exp_scenario.bypass.reports role permission can't be reported.
Commands
| Command | Description |
|---|---|
/create-report <player> <reason> |
Report a player. Alias /report. |
/get-reports [player] |
List the reports against a player, or the number against every player. Alias /reports. Admin only. |
/clear-reports <player> [from-player] |
Delete every report against a player, or only the one from from-player. Admin only. |
When a report is made, admins on that instance see who made it and the reason. Other players see the reported player and the reason. Clearing reports is announced to everyone on the instance.
Web
The Reports page lists every report. The player, reporter and reason columns are searchable, and the instance column can be filtered. Reports made from the web interface show "Web UI" as the instance. The status column shows whether a report has been read and can be filtered. Users with exp_reports.report.create get a Create button. Each row has buttons for the permissions the user has: Mark read or Mark unread with exp_reports.report.mark_read, Add to watch list with exp_reports.watch_list.add, and Delete with exp_reports.report.delete. Add to watch list opens the add form with the player and the report's reason filled in, and is disabled once the player is on the watch list. A report made from the web interface is made by the logged in user.
Below the reports is the watch list, with the player, reason, who added them and when. Users with exp_reports.watch_list.add get an Add button, and users with exp_reports.watch_list.remove get a Delete button on each row. A reason is required. Players watched only for unread reports are not listed here.
Lua
local ExpReports = require("modules/exp_reports")
ExpReports.create_report(player, reported_player, reason)
ExpReports.list_reports(player, reported_player) -- Every player when reported_player is nil
ExpReports.delete_reports(player, reported_player, by_player_name) -- Only those from by_player_name when given
[ExpReports.on_player_reported] = function(event) end, -- { player_index, by_player_name, reason, report_count, by_player_names }
[ExpReports.on_reports_deleted] = function(event) end, -- { player_index, by_player_name, count }
The functions only send the request, the outcome is printed to player. The events are raised on the instance the command was used on, and only when the reported player is online there.
Node
ReportCreateRequest, ReportListRequest, ReportGetRequest and ReportDeleteRequest can be sent from instances and control connections. ReportSetReadRequest, WatchListAddRequest and WatchListRemoveRequest can only be sent from control connections. Web plugins and control connections can subscribe to ReportUpdatedEvent with the exp_reports.report.subscribe permission, and to WatchListUpdatedEvent with exp_reports.watch_list.subscribe. The controller sends WatchedPlayerJoinedEvent to every instance when a watched player joins.