mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-11-11 23:54:52 +09:00
Merge branch 'explosivegaming:main' into aperx
This commit is contained in:
14 files changed
+586
-7
No files matched your search
@@ -38,6 +38,27 @@ This module is currently not published and therefore can not be installed via `n
|
||||
3) Install the package dev dependencies: `pnpm install` (`pnpm` is recommended over `npm`)
|
||||
4) Add supported plugins will be loaded on next controller / host restart.
|
||||
|
||||
## Plugins
|
||||
|
||||
Each plugin has a readme in its folder with its config, permissions and API.
|
||||
|
||||
| Plugin | Description |
|
||||
|---|---|
|
||||
| [Scenario (@expcluster/scenario)](exp_scenario/README.md) | The ExpGaming scenario. Adds the commands, guis and control modules, each feature configurable from the web interface. |
|
||||
| [Roles (@expcluster/roles)](exp_roles/README.md) | Mirrors clusterio roles into the game, and grants roles by online time. |
|
||||
| [Permission Groups (@expcluster/permission-groups)](exp_groups/README.md) | Syncs Factorio permission groups, and puts players in a group based on their roles. |
|
||||
| [Player Statistics (@expcluster/player-statistics)](exp_player_statistics/README.md) | Counts statistics such as kills and machines built for each player, across every server. |
|
||||
| [Reports (@expcluster/reports)](exp_reports/README.md) | Lets players report each other in game or from the web interface, with optional Discord and JSON webhooks. |
|
||||
| [Server List (@expcluster/server-list)](exp_server_list/README.md) | Shares the details of every instance with the others, for the server list in game. |
|
||||
| [Discord Auth (@expcluster/discord-auth)](exp_discord_auth/README.md) | Log in to the web interface with Discord, and link Factorio accounts to a Discord account. |
|
||||
| [Auto Restart (@expcluster/auto-restart)](exp_auto_restart/README.md) | Restarts hosts, and optionally the controller, that need a restart once nobody is playing on them. |
|
||||
| [Server UPS (@expcluster/server-ups)](exp_server_ups/README.md) | Shows the UPS the server is running at, toggled with `/server-ups`. |
|
||||
| [Commands (@expcluster/lib_commands)](exp_commands/README.md) | Lua library for commands with typed arguments, aliases and permission checks. |
|
||||
| [GUI (@expcluster/lib_gui)](exp_gui/readme.md) | Lua library for defining guis and the toolbar. |
|
||||
| [Module Utilities (@expcluster/lib_util)](exp_util/README.md) | Lua library with storage, tasks spread over ticks, selection tools and other helpers. |
|
||||
|
||||
The last three are libraries the other plugins use. The scenario needs Roles, Reports, Server List, Player Statistics and the three libraries, and seeding its roles also needs Permission Groups.
|
||||
|
||||
## Contributing
|
||||
|
||||
See [Contributing](CONTRIBUTING.md) for how to make pull requests and issues.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Restarts hosts, and optionally the controller, that need a restart once nobody is playing on them.
|
||||
|
||||
A host needs a restart after a plugin or clusterio update, and after a config change that only applies on restart. The web UI shows this as "Restart Required". This plugin waits until the host has had no players online for a while and then sends it the same restart request the web UI button does.
|
||||
A host needs a restart after a plugin is installed or updated, after a clusterio update, and after a config change that only applies on restart. The web UI shows this as "Restart Required". This plugin waits until the host has had no players online for a while and then sends it the same restart request the web UI button does.
|
||||
|
||||
Restarting a host stops every instance on it. Once the host is back, the instances that were running are started again. Instances with auto start enabled are started by the host itself.
|
||||
|
||||
@@ -10,7 +10,8 @@ Restarting a host stops every instance on it. Once the host is back, the instanc
|
||||
|
||||
- Hosts must be run with `--can-restart` so their process monitor starts them again. The systemd units and run scripts written by the installer do this.
|
||||
- `controller.system_metrics_interval` must be above 0. That poll is how the controller learns a host needs a restart.
|
||||
- Installing a plugin on a host does not yet mark it as needing a restart, only updates and config changes do. Until clusterio does, restart the host by hand after an install.
|
||||
- Plugins added outside clusterio, for example checked out into `external_plugins`, do not mark the host as needing a restart. Use "Mark Restart Required" on the host page, or `clusterioctl host mark-restart-required <host>`, so this plugin picks it up.
|
||||
- With `restart_controller` enabled, the controller must also be run with `--can-restart`. The controller is not restarted if the installed clusterio version is older than the running one.
|
||||
|
||||
## Config
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# ExpGaming - Commands
|
||||
|
||||
Lua library for adding Factorio commands with typed arguments, aliases and permission checks. The other ExpCluster plugins add all their commands through it. It has no config, and the Node side is empty so the module gets patched into the save.
|
||||
|
||||
Input is split on spaces, and double quotes group words into one argument. Each argument goes through its parser before the callback runs, and every permission authority must allow the player. From RCON the player is `Commands.server`, which passes every permission check and raises an error if the callback reads a field it does not have. Denied and failed attempts, and commands run by players, are appended as JSON lines to `script-output/log/commands.log`.
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Description |
|
||||
|---|---|
|
||||
| `/commands [keyword] [page]` | Lists custom commands the player can use and game commands matching the keyword, 5 per page. Aliases `/chelp` and `/helpp`. |
|
||||
| `/_rcon <code>` | Runs Lua code. Globals it sets are kept in storage between runs. |
|
||||
| `/_sudo <player> <command> [arguments]` | Runs a command as another player, with their permissions. |
|
||||
| `/_ipc <channel> <json>` | Sends the JSON to Clusterio on the given IPC channel. |
|
||||
|
||||
The `_` commands have the `system_only` flag. Only RCON and players unlocked with `Commands.unlock_system_commands` can use them.
|
||||
|
||||
`/_rcon` has `Commands`, `ExpUtil`, `Async`, `Clustorio`, `ipc` and `print` as globals, and `player`, `surface`, `force`, `position`, `entity` and `tile` for the player running it. `entity` is the selected entity.
|
||||
|
||||
## Lua
|
||||
|
||||
```lua
|
||||
local Commands = require("modules/exp_commands")
|
||||
|
||||
Commands.new("repeat", { "my-module.description" })
|
||||
:argument("count", { "my-module.arg-count" }, Commands.types.integer_range(1, 10))
|
||||
:optional("message", nil, Commands.types.string)
|
||||
:defaults{ message = "Hello" } -- A function default is called with the player
|
||||
:add_aliases{ "repeat-message" }
|
||||
:add_flags{ "admin_only" }
|
||||
:enable_auto_concatenation() -- The last argument takes the rest of the input
|
||||
:register(function(player, count, message)
|
||||
for i = 1, count do Commands.print(i .. " " .. message) end
|
||||
return Commands.status.success() -- Returning nothing also counts as success
|
||||
end)
|
||||
|
||||
Commands.status.success(msg) -- Or error, unauthorised, invalid_input
|
||||
Commands.print(value) -- To the player running the command, or to RCON
|
||||
Commands.error(value)
|
||||
Commands.add_data_type("name", parser) -- Adds Commands.types.name, parser(input, player) returns a status
|
||||
Commands.add_permission_authority(function(player, command) end) -- Return a boolean or a status
|
||||
Commands.player_has_permission(player, command)
|
||||
Commands.list_for_player(player)
|
||||
Commands.search_for_player(keyword, player)
|
||||
Commands.disable_command(name) -- Or enable_command, kept in storage
|
||||
Commands.unlock_system_commands(player_name) -- Or lock_system_commands, kept in storage
|
||||
Commands.add_rcon_static(name, value) -- Global in /_rcon, control stage only
|
||||
Commands.add_rcon_dynamic(name, function(player) end) -- Called on each /_rcon
|
||||
```
|
||||
|
||||
Types in `Commands.types`: `boolean`, `string`, `number`, `integer`, `player`, `player_online`, `player_alive`, `force`, `surface`, `planet` and `color`. These take arguments and return a parser: `enum(options)`, `key_of(map)`, `string_max_length(max)`, `number_range(min, max)` and `integer_range(min, max)`. `integer` floors the number. `boolean` is true for yes, y, true and 1, and false for anything else.
|
||||
|
||||
Flags checked by the built in permission authorities:
|
||||
|
||||
| Flag | Allows |
|
||||
|---|---|
|
||||
| `admin_only` | Game admins. |
|
||||
| `system_only` | RCON and unlocked players. |
|
||||
| `character_only` | Players controlling their character, not in remote view. |
|
||||
| `remote_only` | Players in remote view. |
|
||||
|
||||
A command disabled with `Commands.disable_command` works only for RCON and unlocked players.
|
||||
|
||||
`/commands` searches the translated descriptions in the player's locale. Translations are requested when a player joins or changes locale, so a search finds nothing in a locale no player has joined with yet.
|
||||
@@ -32,12 +32,24 @@ All fields are on the controller.
|
||||
There are two ways to link a Factorio account:
|
||||
|
||||
- On the user page, a logged in user clicks Link Discord and approves it on Discord.
|
||||
- On the login page, a player logs in with Discord and gets a code, then types `/link-discord CODE` on any server. This is how a player who can't log in to the web interface yet links their account.
|
||||
- On the login page, a player logs in with Discord and gets a code, then types `/link-discord CODE` on any server within 10 minutes. This is how a player who can't log in to the web interface yet links their account.
|
||||
|
||||
Linking a Factorio account that is already linked to another Discord account moves it. `/link-discord` without a code shows the current link, and `/unlink-discord` removes it. The user page has an Unlink button too.
|
||||
Linking a Factorio account that is already linked to another Discord account moves it. `/link-discord` without a code shows the current link, and `/unlink-discord` removes it. `/link` and `/unlink` are aliases. The user page has an Unlink button too.
|
||||
|
||||
Users can always see and remove their own link. The `exp_discord_auth.link.get` and `exp_discord_auth.link.delete` permissions allow it for other users.
|
||||
|
||||
Once linked, the Discord tab on the login page has a "Log in as" button for each linked account. Banned users can't log in this way.
|
||||
|
||||
Links are stored in `database/exp_discord_auth/accounts.json` on the controller.
|
||||
|
||||
## Lua
|
||||
|
||||
Each call goes to the controller, and the player gets the answer printed once it arrives.
|
||||
|
||||
```lua
|
||||
local ExpDiscordAuth = require("modules/exp_discord_auth")
|
||||
|
||||
ExpDiscordAuth.link(player, code)
|
||||
ExpDiscordAuth.unlink(player)
|
||||
ExpDiscordAuth.get_linked(player) -- Prints the Discord account the player is linked to
|
||||
```
|
||||
@@ -0,0 +1,67 @@
|
||||
# ExpGaming - Permission Groups
|
||||
|
||||
Syncs Factorio permission groups between the controller and instances, and puts players in a group based on their clusterio roles.
|
||||
|
||||
The controller stores groups, role mappings and assignments in `database/exp_groups/`. On start the instance fetches every group and replaces the groups in the save with them. Groups the controller does not know are deleted and their players moved to Default. After that the instance gets updates as groups change.
|
||||
|
||||
When a player joins, and while they are online, the controller picks their group from the first of:
|
||||
|
||||
- An assignment for the player.
|
||||
- The enabled role mapping with the highest priority whose roles the player all holds. These are clusterio roles.
|
||||
- Default.
|
||||
|
||||
Deleting a group deletes the role mappings and assignments that point to it and moves its players to Default.
|
||||
|
||||
## Config
|
||||
|
||||
On the instance:
|
||||
|
||||
| Field | Default | Description |
|
||||
|---|---|---|
|
||||
| `exp_groups.sync_mode` | bidirectional | `disabled` does not sync. `enabled` applies groups and player groups from the controller. `bidirectional` also sends changes made in game to the controller. |
|
||||
|
||||
With `bidirectional`, groups created or edited in game are sent to the controller every 300 ticks, and deleted groups right away. Moving a player to another group in game updates their assignment, if they have one.
|
||||
|
||||
## Permissions
|
||||
|
||||
| Permission | Default | Allows |
|
||||
|---|---|---|
|
||||
| `exp_groups.group.get` | granted | Viewing a group. |
|
||||
| `exp_groups.group.list` | granted | Listing groups. |
|
||||
| `exp_groups.group.subscribe` | granted | Getting group updates. |
|
||||
| `exp_groups.group.create` | | Creating groups. |
|
||||
| `exp_groups.group.update` | | Renaming groups and changing their permissions. |
|
||||
| `exp_groups.group.delete` | | Deleting groups. |
|
||||
| `exp_groups.assignment.get` | granted | Viewing the assignment for a player. |
|
||||
| `exp_groups.assignment.list` | granted | Listing assignments. |
|
||||
| `exp_groups.assignment.subscribe` | granted | Getting assignment updates. |
|
||||
| `exp_groups.assignment.create` | | Assigning a player to a group. |
|
||||
| `exp_groups.assignment.update` | | Changing the group of an assignment. |
|
||||
| `exp_groups.assignment.delete` | | Removing an assignment. |
|
||||
| `exp_groups.role_mapping.get` | granted | Viewing a role mapping. |
|
||||
| `exp_groups.role_mapping.list` | granted | Listing role mappings. |
|
||||
| `exp_groups.role_mapping.subscribe` | granted | Getting role mapping updates. |
|
||||
| `exp_groups.role_mapping.create` | | Creating role mappings. |
|
||||
| `exp_groups.role_mapping.update` | | Editing role mappings. |
|
||||
| `exp_groups.role_mapping.delete` | | Deleting role mappings. |
|
||||
|
||||
## Web
|
||||
|
||||
The Permission Groups page lists groups, role mappings and assignments, each with a Create button. Clicking a group opens its page at `/permission_groups/:id/view`. There you can rename it, tick the input actions it allows, or delete it. The list of input actions comes from the defines exported for the default mod pack, so the page needs a mod pack export to show them.
|
||||
|
||||
## Lua
|
||||
|
||||
```lua
|
||||
local ExpGroups = require("modules/exp_groups")
|
||||
|
||||
ExpGroups.set_emit_events(true) -- Send changes made in game to the instance
|
||||
ExpGroups.initialise_groups(groups) -- Replace every group in the save
|
||||
ExpGroups.receive_group_update(group)
|
||||
ExpGroups.receive_assignment_update(assignment) -- Move the player to the group
|
||||
```
|
||||
|
||||
These are called by the instance over RCON. Other modules should use the Factorio permission groups directly.
|
||||
|
||||
## Node
|
||||
|
||||
Every request has a matching permission above. Groups use `GroupCreateRequest`, `GroupUpdateRequest`, `GroupDeleteRequest`, `GroupGetRequest` and `GroupListRequest`. Assignments and role mappings have the same five. `AssignmentGetRequest` with `resolve` set returns the group the player ends up in, not only an assignment. Control connections can subscribe to `GroupUpdatedEvent`, `ManualAssignmentUpdatedEvent` and `RoleMappingUpdatedEvent`. Instances subscribe to `ResolvedAssignmentUpdatedEvent` for each player online.
|
||||
@@ -442,7 +442,7 @@ If a state argument is not given then this becomes `toggle_button_toggled_state`
|
||||
This method does not raise `on_click`; instead, it raises `on_button_toggled`
|
||||
|
||||
```lua
|
||||
Gui.toolbar.set_button_toggled_state(Elements.toggle_me, true)
|
||||
Gui.toolbar.set_button_toggled_state(Elements.toggle_me, player, true)
|
||||
```
|
||||
|
||||
### `Gui.toolbar.set_left_element_visible_state`
|
||||
@@ -453,5 +453,5 @@ If a state argument is not given then this becomes `toggle_left_element_visible_
|
||||
When a toolbar button is linked, this method also raises `on_button_toggled` for that button to reflect the change.
|
||||
|
||||
```lua
|
||||
Gui.toolbar.set_button_toggled_state(Elements.my_frame, true)
|
||||
Gui.toolbar.set_left_element_visible_state(Elements.my_frame, player, true)
|
||||
```
|
||||
@@ -180,7 +180,7 @@ The returned tupple is `LuaPlayer, LuaGuiElement`.
|
||||
This method is named "filtered" because it is expected that the player list provided has been filtered on some condition and only elements for players in this list are returned.
|
||||
The optional third argument can be provided to filter to online only if you have not done so yourself.
|
||||
|
||||
### `GuiIter.all_element`
|
||||
### `GuiIter.all_elements`
|
||||
|
||||
Iterates all elements for all players in a given scope.
|
||||
The returned tupple is `LuaPlayer, LuaGuiElement`.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# ExpGui Framework Guide
|
||||
|
||||
ExpGui is a Clusterio plugin that patches the `exp_gui` module into the save. The module is a Lua library for defining Factorio GUIs, and other modules load it with `require("modules/exp_gui")`. It depends on the `exp_util` module. The plugin has no config, permissions or Node messages.
|
||||
|
||||
This guide presents best practices for creating GUIs in Factorio and demonstrates how the ExpGui framework can help streamline development.
|
||||
If you’re new to GUI creation in Factorio, we recommend starting with [Therenas' helpful tutorial](https://github.com/ClaudeMetz/UntitledGuiGuide/wiki) to build a solid foundation.
|
||||
We also recommend [Raiguard's style guide](https://man.sr.ht/~raiguard/factorio-gui-style-guide/) if you are looking to mimic the design principles employed by the game's own guis.
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# ExpGaming - Player Statistics
|
||||
|
||||
Keeps statistics for every player across every instance, such as kills, deaths and machines built. The totals live on the controller in `database/exp_player_statistics/players.json`, keyed by player name.
|
||||
|
||||
The Lua module counts what each player gains on an instance and sends it to the controller as a delta every 5 minutes, when the player leaves and when the instance stops. When a player joins, the instance fetches their totals from the controller so in-game code can show the total across every server. Anything left unsent when an instance starts came from a save made before it was sent, so it is dropped rather than counted twice.
|
||||
|
||||
Playtime and JoinCount are not stored by this plugin. They come from the online time and join count clusterio keeps for each user, and are added whenever the statistics are requested.
|
||||
|
||||
## Statistics
|
||||
|
||||
Times are in minutes.
|
||||
|
||||
| Name | Counts |
|
||||
|---|---|
|
||||
| `Playtime` | Time online, from the clusterio user. |
|
||||
| `AfkTime` | Minutes a player has been AFK for more than 5 minutes. |
|
||||
| `MapsPlayed` | Maps the player has joined, counted the first time they join each map. |
|
||||
| `JoinCount` | Times the player joined, from the clusterio user. |
|
||||
| `ChatMessages` | Chat messages sent. |
|
||||
| `CommandsUsed` | Commands used. |
|
||||
| `RocketsLaunched` | Rockets launched by the player's force while they were online. |
|
||||
| `ResearchCompleted` | Research finished by the player's force while they were online. Research finished by script is not counted. |
|
||||
| `MachinesBuilt` | Entities built. |
|
||||
| `MachinesRemoved` | Entities of the player's own force mined or marked for deconstruction. |
|
||||
| `TilesBuilt` | Tiles placed. |
|
||||
| `TilesRemoved` | Tiles mined. |
|
||||
| `TreesDestroyed` | Trees mined or marked for deconstruction. |
|
||||
| `OreMined` | Resource entities mined. |
|
||||
| `ItemsCrafted` | Items hand crafted. |
|
||||
| `ItemsPickedUp` | Items picked up from the ground. |
|
||||
| `Kills` | Entities of another, non-neutral force killed. |
|
||||
| `Deaths` | Times the player died. |
|
||||
| `DamageDealt` | Damage dealt to entities of another, non-neutral force. |
|
||||
| `DistanceTravelled` | Tiles moved while not AFK. |
|
||||
| `CapsulesUsed` | Capsules used. |
|
||||
| `EntityRepaired` | Times the player repaired an entity. |
|
||||
| `DeconstructionPlannerUsed` | Areas selected with a deconstruction planner. |
|
||||
| `MapTagsMade` | Map tags added. |
|
||||
|
||||
Damage and kills from a car, spidertron or locomotive count for the driver, or the passenger when there is no driver. The simple counters and the display order are in `module/config.lua`.
|
||||
|
||||
## Permissions
|
||||
|
||||
`exp_player_statistics.view` is granted by default. It allows viewing the statistics of every player and subscribing to their updates.
|
||||
|
||||
## Web
|
||||
|
||||
The Statistics page lists every player with a stored record, one column per statistic. Columns are sortable and the player column is searchable. A picker in the header chooses which statistics are shown, and the choice is saved in the browser's local storage.
|
||||
|
||||
The user page gets a Statistics block with every statistic for that player.
|
||||
|
||||
## Lua
|
||||
|
||||
```lua
|
||||
local ExpPlayerStatistics = require("modules/exp_player_statistics")
|
||||
|
||||
ExpPlayerStatistics.increment(player, "Kills", amount) -- amount defaults to 1
|
||||
ExpPlayerStatistics.get(player, "Kills") -- Total on every server, player or name
|
||||
ExpPlayerStatistics.format("Playtime", value, short) -- LocalisedString as shown to players
|
||||
ExpPlayerStatistics.display_order -- Statistic names in the order they are shown
|
||||
ExpPlayerStatistics.units -- Suffix shown after the value, by statistic
|
||||
```
|
||||
|
||||
`get` returns the totals received when the player joined plus what they gained since. Playtime and JoinCount are those values at the time the player joined.
|
||||
|
||||
## Node
|
||||
|
||||
`StatisticsGetRequest` returns the totals of one player with Playtime and JoinCount added. `StatisticsListRequest` returns every stored record without them. Web plugins and control connections can subscribe to `PlayerStatisticsUpdatedEvent` with the `exp_player_statistics.view` permission.
|
||||
@@ -0,0 +1,61 @@
|
||||
# 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 deleted. Nothing is stored in the Lua state, every command sends a request to the controller and the answer is printed once it arrives.
|
||||
|
||||
## Config
|
||||
|
||||
All fields are on the controller.
|
||||
|
||||
| Field | Default | Description |
|
||||
|---|---|---|
|
||||
| `exp_reports.discord_webhook_url` | | Discord channel webhook which new reports are posted to as an embed. |
|
||||
| `exp_reports.json_webhook_url` | | URL which new reports are posted to as JSON. |
|
||||
|
||||
Only new reports are posted, deletions are not. The JSON webhook gets `{ "type": "report_created", "report": { ... } }` with the report in the same form as `reports.json`. 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. |
|
||||
|
||||
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. Users with `exp_reports.report.create` get a Create button, and users with `exp_reports.report.delete` get a Delete button on each row. A report made from the web interface is made by the logged in user.
|
||||
|
||||
## Lua
|
||||
|
||||
```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. Web plugins and control connections can subscribe to `ReportUpdatedEvent` with the `exp_reports.report.subscribe` permission.
|
||||
@@ -0,0 +1,73 @@
|
||||
# ExpGaming - Roles
|
||||
|
||||
Mirrors clusterio roles into the game. Each clusterio role is a role in game with the same id, name and permissions, and a player holds the roles of the clusterio user with the same name. Permission checks in game use clusterio permission names, so `exp_scenario.command.kill` is granted in game by the same role that grants it in the web UI.
|
||||
|
||||
The controller is the source of truth. It stores the in game properties of each role in `database/exp_roles/role_meta.json`, keyed by role id, and creates them for any role that has none. New roles go after every existing role in the order. On start the instance fetches every role and assignment and replaces the state in the save. After that it gets updates as roles and users change. The default role, `controller.default_role_id`, is held by every player and is not stored per player.
|
||||
|
||||
Only the roles with the highest priority a player holds apply. A Jail role with a higher priority than the rest suppresses every other role, the default role included.
|
||||
|
||||
The controller grants roles by online time across the cluster. It checks on start, when a role or user changes, and when a player leaves an instance. Roles granted this way are never taken away. Players holding a role with block auto assign set are never granted roles this way.
|
||||
|
||||
## Config
|
||||
|
||||
On the instance:
|
||||
|
||||
| Field | Default | Description |
|
||||
|---|---|---|
|
||||
| `exp_roles.sync_mode` | bidirectional | `disabled` does not sync. `enabled` applies roles from the controller. `bidirectional` also sends roles assigned or unassigned in game to the controller. |
|
||||
|
||||
A role assigned in game applies right away. If the controller refuses it the instance takes it back off the player. Changing the mode from `disabled` applies on the next instance start.
|
||||
|
||||
## Permissions
|
||||
|
||||
The plugin has no permissions of its own. Listing roles needs `core.role.list`, subscribing to role updates needs `core.role.subscribe`, and editing the in game properties needs `core.role.update`.
|
||||
|
||||
## Web
|
||||
|
||||
The role page gets an In Game Properties section with these fields:
|
||||
|
||||
| Field | Default | Description |
|
||||
|---|---|---|
|
||||
| Order | after the last role | A lower value is a more privileged role. Ties go to the lower id. |
|
||||
| Priority | 0 | Only the roles with the highest priority a player holds apply. |
|
||||
| Short hand | | Short form of the name, the name when empty. |
|
||||
| Tag | | Shown next to the names of players with this role. |
|
||||
| Colour | | Used for the role name and tag in game. |
|
||||
| Auto assign after (hours) | | Online time after which the role is granted, never when empty. |
|
||||
| Block auto assign | false | Holders of this role are never granted roles automatically. |
|
||||
|
||||
## Lua
|
||||
|
||||
```lua
|
||||
local ExpRoles = require("modules/exp_roles")
|
||||
|
||||
ExpRoles.get_role(role_id) -- Roles are looked up by clusterio id
|
||||
ExpRoles.get_role_by_name("Moderator")
|
||||
ExpRoles.get_roles() -- In no particular order
|
||||
ExpRoles.get_ordered_roles() -- Most privileged first
|
||||
ExpRoles.get_default_role()
|
||||
ExpRoles.get_higher_roles(role) -- The role and those above it, default role excluded
|
||||
ExpRoles.get_lower_roles(role) -- The role and those below it, default role excluded
|
||||
ExpRoles.get_player_roles(player) -- Roles that apply, nil is the server which has core.admin
|
||||
ExpRoles.get_player_highest_role(player)
|
||||
ExpRoles.player_has_permission(player, "exp_scenario.command.kill")
|
||||
ExpRoles.player_has_any_permission(player, ...)
|
||||
ExpRoles.player_has_all_permission(player, ...)
|
||||
ExpRoles.player_outranks(player, other)
|
||||
ExpRoles.define_permission_trigger(permission, function(player, state) end) -- Run on join and role changes
|
||||
|
||||
role:has_permission(permission) -- core.admin grants every permission
|
||||
role:is_higher_than(other)
|
||||
role:has_player(player) -- Ignores priority
|
||||
role:get_player_names() -- Includes players who never joined this map
|
||||
role:get_players(online)
|
||||
role:print(message) -- Returns how many online players got it
|
||||
role:assign(player, { by_player_name, silent, local_only }) -- local_only keeps it to this map
|
||||
role:unassign(player, { by_player_name, silent, local_only })
|
||||
|
||||
[ExpRoles.events.on_player_roles_changed] = function(event) end, -- { player_index, by_player_index, assigned, unassigned }
|
||||
```
|
||||
|
||||
## Node
|
||||
|
||||
`RoleListRequest` returns every role combined with its in game properties. Control connections can subscribe to `RoleUpdatedEvent`. `RoleMetaUpdateRequest` sets the in game properties of a role.
|
||||
@@ -0,0 +1,115 @@
|
||||
# ExpGaming - Scenario
|
||||
|
||||
The Explosive Gaming scenario. It adds the in-game commands, guis and control modules players see on ExpGaming servers, and loads them into every instance as the `exp_scenario` save patch module.
|
||||
|
||||
Most parts of the scenario are features that can be turned on and off and configured from the web interface while the game is running. Permissions in game are clusterio permissions checked through exp_roles, so the plugin needs exp_roles, exp_groups, exp_reports, exp_server_list, exp_player_statistics, exp_commands, exp_gui and exp_util loaded as well.
|
||||
|
||||
## Features
|
||||
|
||||
Each feature is registered in Lua with `Feature.register` from `module/features.lua` and declared again in `features.ts` with a title, description and the fields the web interface can edit. The defaults on both sides must match, `test/features.test.js` checks this.
|
||||
|
||||
The controller keeps one record per feature in `database/exp_scenario/features.json` with its enabled state and the values that differ from the default. On start it adds new features, deletes removed ones and drops stored values that no longer fit a field, logging a warning for each.
|
||||
|
||||
Edits are made on the Scenario Features page of the web interface. When a record changes the controller broadcasts it, and each running instance sends every value of the feature over RCON to `exp_scenario.features.receive_update`. Instances also fetch the full list when they start and when they reconnect to the controller. The Lua side keeps the last values in `storage`, so they survive saving and loading the map.
|
||||
|
||||
While a feature is disabled its event handlers do nothing, its commands are denied and its toolbar buttons are hidden.
|
||||
|
||||
| Feature | Description |
|
||||
|---|---|
|
||||
| `afk_kick` | Kick everyone when nobody online is active. |
|
||||
| `autofill` | Fill ammo and fuel into entities when they are built. |
|
||||
| `chat_auto_reply` | Reply to key words in chat and run chat commands. |
|
||||
| `community_details` | Community name and links, see below. |
|
||||
| `custom_start` | Map settings applied when the map is created, and the items new players start with. |
|
||||
| `deconstruction_log` | Write actions of players to `log/deconstruction.log`. |
|
||||
| `degrading_tiles` | Tiles wear down as players walk and build on them. |
|
||||
| `death_markers` | Map markers, lights and lines to corpses, and collecting expired corpses into chests. |
|
||||
| `discord_alerts` | Write alerts for the Discord bot on bans, mutes, kicks, jails and logged commands. |
|
||||
| `extra_logging` | Write rockets, deaths, research, joins and leaves to a log file. |
|
||||
| `greetings` | Greet players when they join and show their join message. |
|
||||
| `help_bubbles` | A compilatron at spawn that cycles through tips and the community links. |
|
||||
| `inventory_clear` | Move the items of banned and kicked players to a chest at spawn. |
|
||||
| `lawnmower` | The `/lawnmower` command. |
|
||||
| `mine_depletion` | Deconstruct mining drills when their resources run out. |
|
||||
| `module_inserter` | A selection tool that fills machines with modules. |
|
||||
| `nuke_protection` | Remove banned items from players without the bypass permission. |
|
||||
| `player_bonus` | Points players spend on personal bonuses. |
|
||||
| `pollution_grading` | Scale the pollution overlay. |
|
||||
| `popups` | Flying text for chat messages and damage. |
|
||||
| `protection` | Stop players removing protected entities placed by others. |
|
||||
| `repair` | The `/repair` command. |
|
||||
| `research` | Research effects such as bonus inventory slots. |
|
||||
| `research_milestones` | A gui tracking research against target times. |
|
||||
| `rocket_info` | A gui with rocket launch statistics and silo progress. |
|
||||
| `science_production` | A gui with the production of each science pack. |
|
||||
| `spawn_area` | Build the spawn area when the first player joins. |
|
||||
| `station_auto_name` | Name train stops when they are built. |
|
||||
| `task_list` | A shared list of tasks. |
|
||||
| `warp_list` | Warp points players can travel between. |
|
||||
| `vlayer` | A shared virtual surface for solar panels, accumulators and storage. |
|
||||
|
||||
`community_details` holds the community name and the Discord, website, status page, GitHub and Patreon links. Chat replies, the readme gui, the help bubbles and the greeting read them. They are always used, the enabled switch has no effect.
|
||||
|
||||
Some settings are not features. Data tables such as the bonuses in `config/player_bonus.lua` and the research targets in `config/research_data.lua` are only changed by editing the module.
|
||||
|
||||
### Adding a feature
|
||||
|
||||
Register it in the Lua file that uses it and guard the handlers:
|
||||
|
||||
```lua
|
||||
local Feature = require("modules/exp_scenario/features")
|
||||
local feature, config = Feature.register("my_feature", {
|
||||
show_message = true,
|
||||
role_id = Feature.optional("number"),
|
||||
item_names = Feature.set{ "iron-plate" },
|
||||
section = { count = 1 }, -- edited as "section.count"
|
||||
})
|
||||
|
||||
return feature:guard{ events = { ... } }
|
||||
```
|
||||
|
||||
Commands take `:add_flags{ feature = feature }`. Read `config` where it is used, the table is updated in place. Then add an entry with the same name and defaults to `features` in `features.ts`, or the web interface can not configure it. Values read while the file loads, such as `on_nth_tick` periods, can not be changed at runtime and are left out of `features.ts`.
|
||||
|
||||
## Seed
|
||||
|
||||
The Seed roles and groups button on the Roles page creates the roles and Factorio permission groups the scenario used before roles moved to the controller. It needs the `exp_scenario.seed` permission and the exp_roles and exp_groups plugins.
|
||||
|
||||
It creates 13 roles, Senior Administrator down to Regular plus Jail. The controller's admin role becomes System and its default role becomes Guest, the seed sets their short hand and colour but not their permissions. Each role also gets the permissions of its parent in the seed, the way the old config inherited them. Regular is auto assigned after 3 hours played and Veteran after 10. Jail has priority 1 and blocks auto assignment.
|
||||
|
||||
It creates the permission groups Admin, Trusted, Standard, Guest and Restricted, and maps each role to one of them, higher roles getting a higher mapping priority.
|
||||
|
||||
It can be run again. Roles that already exist by name keep their permissions and gain the seeded ones. Groups that already exist by name are reset to the seeded input actions.
|
||||
|
||||
## Player settings
|
||||
|
||||
The controller keeps some settings per player in `database/exp_scenario/players.json`, so they follow a player between servers:
|
||||
|
||||
- Join message. Printed to everyone when the player joins, unless `greetings` is disabled. Set in game with `/set-join-message` and removed with `/remove-join-message`, or edited on the user page of the web interface. Up to 255 characters.
|
||||
- Locale. Sent when the player joins and when they change language, shown on the user page.
|
||||
- Toolbar. The order and state of the player's toolbar buttons, sent when they leave and restored when they join.
|
||||
|
||||
## Permissions
|
||||
|
||||
| Permission | Description |
|
||||
|---|---|
|
||||
| `exp_scenario.config.view` | View the Scenario Features page. |
|
||||
| `exp_scenario.config.edit` | Edit features on the Scenario Features page. |
|
||||
| `exp_scenario.seed` | Seed the roles and permission groups. |
|
||||
| `exp_scenario.join_message.set` | Set your own join message from the web interface. |
|
||||
| `exp_scenario.join_message.set_other` | Set the join message of other users. |
|
||||
|
||||
Users can always see their own player settings on the user page, `core.user.get` allows it for other users.
|
||||
|
||||
`permissions.ts` defines another 109 permissions checked in game through exp_roles. They are grouped by prefix: `exp_scenario.command.*` for each command, `exp_scenario.gui.*` for guis and their actions, `exp_scenario.bypass.*`, `exp_scenario.decon.*`, `exp_scenario.player.*` and `exp_scenario.chat.commands`. Every command checks `exp_scenario.command.<name>` with hyphens replaced by underscores, including commands added by other ExpGaming plugins such as `/server-ups`. 24 of them, mostly guis and informational commands, are granted by default.
|
||||
|
||||
## Lua
|
||||
|
||||
The module is split into:
|
||||
|
||||
- `commands/` the chat commands, plus `_authorities.lua` for the permission and feature checks, `_rcon.lua` for the `/_rcon` environment and `_types.lua` for argument types.
|
||||
- `control/` event handlers that run without a gui, such as afk kick, protection and the spawn area.
|
||||
- `gui/` the guis and toolbar buttons, with the debug gui in `gui/debug/`.
|
||||
- `config/` data tables and some feature registrations, such as `community_details.lua` and `spawn_area.lua`.
|
||||
- `features.lua` the feature registry described above.
|
||||
|
||||
`exp_scenario.features` and `exp_scenario.player_settings` are globals so the instance plugin can call them with `/sc`.
|
||||
@@ -0,0 +1,28 @@
|
||||
# ExpGaming - Server UPS
|
||||
|
||||
Shows the UPS the server is running at in the top right of the screen, below the game's own UPS and clock. The label reads like `59.8 (99.7%)`, the second number being the UPS as a percentage of 60.
|
||||
|
||||
The label starts hidden. Players toggle it with `/server-ups`, also available as `/sups` and `/ups`. With exp_scenario loaded the command needs the `exp_scenario.command.server_ups` permission, which is granted by default.
|
||||
|
||||
The instance asks the game for the current tick over RCON once every update interval and divides the ticks passed by the time passed, averaged over the last `average_interval` updates. Each request also sends the last result, which updates the label for every online player. If `auto_pause` is enabled in `factorio.settings`, updates start when a player joins and stop when the last player leaves, otherwise they run from when the instance starts.
|
||||
|
||||
## Config
|
||||
|
||||
All fields are on the instance.
|
||||
|
||||
| Field | Default | Description |
|
||||
|---|---|---|
|
||||
| `exp_server_ups.update_interval` | 1000 | Time between updates in milliseconds. |
|
||||
| `exp_server_ups.average_interval` | 60 | Number of updates the UPS is averaged over. |
|
||||
|
||||
## Lua
|
||||
|
||||
```lua
|
||||
local ServerUps = require("modules/exp_server_ups")
|
||||
|
||||
ServerUps.refresh_online(ups) -- Set the label of every online player
|
||||
ServerUps.get_main_label(player) -- The label of a player, created hidden if missing
|
||||
ServerUps.set_visible(player, visible)
|
||||
```
|
||||
|
||||
The instance calls `/_rcon return exp_server_ups.refresh(ups)`, which updates the labels and returns `game.tick`.
|
||||
@@ -0,0 +1,67 @@
|
||||
# ExpGaming - Module Utilities
|
||||
|
||||
Lua library the other ExpCluster plugins build on. It adds to the `math` and `table` globals, guards `require` at runtime, and has modules for storage, tasks spread over ticks, selection tools, flying text and bounding boxes. It has no config, and the Node side is empty so the module gets patched into the save.
|
||||
|
||||
Loading the module adds `math.round`, `math.clamp`, `table.get_keys`, `table.inspect`, `table.deep_copy` and more to the globals. `package.lifecycle` tracks the current stage. Once the first player joins, or on singleplayer or multiplayer init, the stage is runtime. From then on `require` only returns files that were already loaded and raises an error for anything else. Functions marked control stage only below raise an error at runtime.
|
||||
|
||||
## Lua
|
||||
|
||||
```lua
|
||||
local ExpUtil = require("modules/exp_util")
|
||||
local Storage = require("modules/exp_util/storage")
|
||||
local Async = require("modules/exp_util/async")
|
||||
local Selection = require("modules/exp_util/selection")
|
||||
local FlyingText = require("modules/exp_util/flying_text")
|
||||
local AABB = require("modules/exp_util/aabb")
|
||||
local Colors = require("modules/exp_util/include/color") -- Also ExpUtil.color
|
||||
local FlowPrecision = require("modules/exp_util/include/flow_precision")
|
||||
```
|
||||
|
||||
Storage gives each file its own table in `storage.exp_storage`, keyed by the file path. Register once per file, in the control stage:
|
||||
|
||||
```lua
|
||||
local data = { players = {} }
|
||||
Storage.register(data, function(tbl) data = tbl end) -- Called on init and load
|
||||
Storage.register_metatable("MyClass", metatable) -- Prefixed with the module name
|
||||
```
|
||||
|
||||
Async runs a function on a later tick. Its calls can be kept in storage. Register in the control stage. The id is the file and line the function is defined on, so moving it breaks calls queued in an existing save.
|
||||
|
||||
```lua
|
||||
local say = Async.register(function(name) game.print("Hello " .. name) end)
|
||||
say("Dave") -- Next tick, same as say:start_soon("Dave")
|
||||
say:start_after(60, "Dave")
|
||||
say:start_now("Dave") -- This tick
|
||||
say:start_task("Dave") -- Only if no call to it is queued
|
||||
local pending = say("Dave") -- pending:cancel() or pending:return_to(other_async)
|
||||
|
||||
return Async.status.continue(...) -- From inside, run again next tick with new arguments
|
||||
return Async.status.delay(ticks, ...)
|
||||
return Async.status.complete(...) -- Or return nothing
|
||||
```
|
||||
|
||||
Selection gives a player a `selection-tool` and sends the area events to the active selection. It stops when the tool leaves the cursor, or the player leaves or dies.
|
||||
|
||||
```lua
|
||||
local MySelection = Selection.connect("my_selection") -- Control stage only
|
||||
MySelection:on_selection(function(event, ...) end) -- Also on_alt_selection, on_start, on_stop
|
||||
MySelection:start(player, ...) -- Extra arguments are passed to the handlers
|
||||
MySelection:stop(player)
|
||||
```
|
||||
|
||||
Other functions used often. `transfer_inventory_to_surface` moves the items into matching entities on the surface and creates more when `allow_creation` is set.
|
||||
|
||||
```lua
|
||||
ExpUtil.assert_not_runtime()
|
||||
ExpUtil.assert_argument_type(value, "table", 1, "name")
|
||||
ExpUtil.write_json(path, tbl) -- Appends one JSON line to script-output on the server
|
||||
ExpUtil.optional_require(path) -- nil if the file does not exist
|
||||
ExpUtil.auto_complete(options, input) -- Best match, prefers matches at the start of a word
|
||||
ExpUtil.format_any(value) -- Readable string or locale string for any value
|
||||
ExpUtil.format_time(ticks, "short", { hours = true, minutes = true }) -- Also "long" and "clock"
|
||||
ExpUtil.format_player_name(player) -- Name in the player's chat color
|
||||
ExpUtil.teleport_player(player, surface, position, "dismount") -- Or "allow", "disallow"
|
||||
ExpUtil.transfer_inventory_to_surface{ inventory = inv, surface = surface, name = "iron-chest", allow_creation = true }
|
||||
FlyingText.create_above_player{ target_player = player, text = "Hi" }
|
||||
AABB.contains_point(aabb, point)
|
||||
```
|
||||
Reference in new issue
Block a user