mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-11-13 08:04:53 +09:00
Player statistics move out of exp_legacy into their own plugin, with the totals kept on the controller instead of the PlayerData datastore, which was never synced anywhere. The lua module tracks the same statistics with the same rules. Gains are kept per player as unsent in storage and sent over IPC every 5 minutes for every player with changes, and for one player when they leave. Sent values move into the totals the controller gave when the player joined, so get() is the total on every server either way. Nothing is resent. The instance plugin sends everything left when the server stops and drops anything unsent when it starts. A leftover can only come from a save made before it was sent, such as the autosave loaded after a crash, so dropping it loses a few minutes at worst but never counts twice. The controller adds the deltas to a record per player name in database/exp_statistics/players.json. Instances fetch a player's totals on join and hand them to lua over rcon. The web UI gets a sortable table of every player at /exp_statistics, behind exp_statistics.view. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
61 lines
2.3 KiB
TypeScript
61 lines
2.3 KiB
TypeScript
import type { ControllerPluginContext } from "@clusterio/controller";
|
|
import * as lib from "@clusterio/lib";
|
|
import * as messages from "./messages.js";
|
|
import * as path from "node:path";
|
|
|
|
/** Keeps the statistics totals of every player, instances send what was gained since their last update. */
|
|
export default async function (context: ControllerPluginContext) {
|
|
const { controller, plugin } = context;
|
|
const databaseDirectory = controller.config.get("controller.database_directory");
|
|
const players = new lib.SubscribableDatastore(
|
|
...await new lib.JsonIdDatastoreProvider(
|
|
path.join(databaseDirectory, "exp_statistics", "players.json"),
|
|
messages.PlayerStatistics.fromJSON.bind(messages.PlayerStatistics),
|
|
).bootstrap()
|
|
);
|
|
|
|
function addDeltas(deltas: messages.StatisticsDelta[]) {
|
|
const changed = new Map<string, messages.PlayerStatistics>();
|
|
for (const delta of deltas) {
|
|
const record = changed.get(delta.name)
|
|
?? players.getMutable(delta.name)
|
|
?? new messages.PlayerStatistics(delta.name);
|
|
for (const [name, value] of Object.entries(delta.stats)) {
|
|
if (Number.isFinite(value) && value !== 0) {
|
|
record.stats[name] = (record.stats[name] ?? 0) + value;
|
|
}
|
|
}
|
|
changed.set(record.id, record);
|
|
}
|
|
if (changed.size) {
|
|
players.setMany([...changed.values()]);
|
|
}
|
|
}
|
|
|
|
players.on("update", updates => {
|
|
controller.subscriptions.broadcast(new messages.PlayerStatisticsUpdatedEvent(updates));
|
|
});
|
|
|
|
controller.subscriptions.handle(messages.PlayerStatisticsUpdatedEvent, async request => {
|
|
const updates = [...players.values()].filter(player => player.updatedAtMs > request.lastRequestTimeMs);
|
|
return updates.length ? new messages.PlayerStatisticsUpdatedEvent(updates) : null;
|
|
});
|
|
|
|
controller.handle(messages.StatisticsUpdateEvent, async (event: messages.StatisticsUpdateEvent) => {
|
|
addDeltas(event.deltas);
|
|
});
|
|
|
|
controller.handle(messages.StatisticsGetRequest, async (request: messages.StatisticsGetRequest) => (
|
|
players.get(request.name) ?? new messages.PlayerStatistics(request.name)
|
|
));
|
|
|
|
controller.handle(messages.StatisticsListRequest, async () => [...players.values()]);
|
|
|
|
controller.hooks.save.attach(plugin.name, async () => {
|
|
await players.save();
|
|
});
|
|
controller.hooks.shutdown.attach(plugin.name, async () => {
|
|
await players.save();
|
|
});
|
|
}
|