mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-11-14 00:14:52 +09:00
The plugin is now exp_player_statistics (@expcluster/player-statistics), with the module, global, permission, route and database directory renamed to match. The exp-statistics locale section keeps its name. Every statistic is tracked unconditionally, so the enable flags are gone from config.lua. Playtime and JoinCount are no longer counted in game: clusterio already keeps online time and joins for each user, so the controller fills them in from the user when a player's totals are requested, and the web page does the same from the user list. They are not stored or adjusted by the plugin. The web table has a column picker, a multiple select with check boxes, Select all and Reset buttons, showing "N statistics selected". The choice is kept in local storage. The user page shows the statistics of the player in a grid like the core player stats. The readme statistics table drops its second title and uses the short time format. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
161 lines
4.2 KiB
TypeScript
161 lines
4.2 KiB
TypeScript
import * as lib from "@clusterio/lib";
|
|
import { Type, Static } from "@sinclair/typebox";
|
|
|
|
const StatsSchema = Type.Record(Type.String(), Type.Number());
|
|
|
|
/** Totals for one player across every instance, keyed by player name. */
|
|
export class PlayerStatistics {
|
|
constructor(
|
|
public id: string,
|
|
/** Statistic name to total, missing statistics are zero */
|
|
public stats: Record<string, number> = {},
|
|
public updatedAtMs: number = 0,
|
|
public isDeleted: boolean = false,
|
|
) {}
|
|
|
|
static jsonSchema = Type.Object({
|
|
id: Type.String(),
|
|
stats: StatsSchema,
|
|
updated_at_ms: Type.Optional(Type.Number()),
|
|
is_deleted: Type.Optional(Type.Boolean()),
|
|
});
|
|
|
|
toJSON() {
|
|
const json: Static<typeof PlayerStatistics.jsonSchema> = {
|
|
id: this.id,
|
|
stats: this.stats,
|
|
};
|
|
|
|
if (this.updatedAtMs) {
|
|
json.updated_at_ms = this.updatedAtMs;
|
|
}
|
|
|
|
if (this.isDeleted) {
|
|
json.is_deleted = true;
|
|
}
|
|
|
|
return json;
|
|
}
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.id, { ...json.stats }, json.updated_at_ms ?? 0, json.is_deleted ?? false);
|
|
}
|
|
}
|
|
|
|
/** Statistics clusterio already keeps for every user, shown with the others but never added to */
|
|
export function userStatistics(user?: Pick<lib.UserDetails, "playerStats">): Record<string, number> {
|
|
return {
|
|
Playtime: Math.floor((user?.playerStats.onlineTimeMs ?? 0) / 60e3),
|
|
JoinCount: user?.playerStats.joinCount ?? 0,
|
|
};
|
|
}
|
|
|
|
/** Statistics gained by one player since the last update */
|
|
export class StatisticsDelta {
|
|
constructor(
|
|
public name: string,
|
|
public stats: Record<string, number>,
|
|
) {}
|
|
|
|
static jsonSchema = Type.Object({
|
|
name: Type.String(),
|
|
stats: StatsSchema,
|
|
});
|
|
|
|
toJSON() {
|
|
return { name: this.name, stats: this.stats };
|
|
}
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.name, json.stats);
|
|
}
|
|
}
|
|
|
|
/** Sent by an instance to add to the totals of its players. */
|
|
export class StatisticsUpdateEvent {
|
|
declare ["constructor"]: typeof StatisticsUpdateEvent;
|
|
static plugin = "exp_player_statistics" as const;
|
|
static type = "event" as const;
|
|
static src = "instance" as const;
|
|
static dst = "controller" as const;
|
|
static permission = null;
|
|
|
|
constructor(
|
|
public deltas: StatisticsDelta[],
|
|
) {}
|
|
|
|
static jsonSchema = Type.Object({
|
|
deltas: Type.Array(StatisticsDelta.jsonSchema),
|
|
});
|
|
|
|
toJSON() {
|
|
return { deltas: this.deltas.map(delta => delta.toJSON()) };
|
|
}
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.deltas.map(delta => StatisticsDelta.fromJSON(delta)));
|
|
}
|
|
}
|
|
|
|
export class PlayerStatisticsUpdatedEvent {
|
|
declare ["constructor"]: typeof PlayerStatisticsUpdatedEvent;
|
|
static plugin = "exp_player_statistics" as const;
|
|
static type = "event" as const;
|
|
static src = "controller" as const;
|
|
static dst = "control" as const;
|
|
static permission = "exp_player_statistics.view" as const;
|
|
|
|
constructor(
|
|
public updates: PlayerStatistics[],
|
|
) {}
|
|
|
|
static jsonSchema = Type.Object({
|
|
updates: Type.Array(PlayerStatistics.jsonSchema),
|
|
});
|
|
|
|
toJSON() {
|
|
return { updates: this.updates.map(player => player.toJSON()) };
|
|
}
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.updates.map(player => PlayerStatistics.fromJSON(player)));
|
|
}
|
|
}
|
|
|
|
/** Totals for one player, with Playtime and JoinCount from their clusterio user. */
|
|
export class StatisticsGetRequest {
|
|
declare ["constructor"]: typeof StatisticsGetRequest;
|
|
static plugin = "exp_player_statistics" as const;
|
|
static type = "request" as const;
|
|
static src = ["control", "instance"] as const;
|
|
static dst = "controller" as const;
|
|
static permission = "exp_player_statistics.view" as const;
|
|
static Response = PlayerStatistics;
|
|
|
|
constructor(
|
|
public name: string,
|
|
) {}
|
|
|
|
static jsonSchema = Type.Object({
|
|
name: Type.String(),
|
|
});
|
|
|
|
toJSON() {
|
|
return { name: this.name };
|
|
}
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.name);
|
|
}
|
|
}
|
|
|
|
export class StatisticsListRequest {
|
|
declare ["constructor"]: typeof StatisticsListRequest;
|
|
static plugin = "exp_player_statistics" as const;
|
|
static type = "request" as const;
|
|
static src = ["control", "instance"] as const;
|
|
static dst = "controller" as const;
|
|
static permission = "exp_player_statistics.view" as const;
|
|
static Response = lib.jsonArray(PlayerStatistics);
|
|
}
|