mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-13 15:14:52 +09:00
Admins had no way to look at or fix the player data the controller stores when a sync went wrong. The web page now lists every stored entry with its generation, size and the instance holding the lock, and opens a drawer per player with an inventory summary, JSON export and import, delete and lock release. The same operations are exposed as clusterioctl player-sync commands. Import bumps the generation past the stored one so instances holding an older copy download the new data on the next join. Import and delete are refused while an instance holds the lock, since its upload on leave would undo the change. A new player_sync.inventory.modify permission gates the write operations. Closes #1003 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
351 lines
8.8 KiB
TypeScript
351 lines
8.8 KiB
TypeScript
import { Type, Static } from "@sinclair/typebox";
|
|
import { StringEnum, jsonArray } from "@clusterio/lib";
|
|
|
|
// .\module\serialize.lua:serialize.serialize_player()
|
|
export type IpcPlayerData = {
|
|
generation: number,
|
|
name: string,
|
|
controller?: string,
|
|
color?: number[],
|
|
chat_color?: number[],
|
|
tag?: string,
|
|
force?: string,
|
|
cheat_mode?: boolean,
|
|
flashlight?: boolean,
|
|
shortcuts?: Record<string, boolean>,
|
|
game_view_settings?: Record<string, boolean>,
|
|
ticks_to_respawn?: number,
|
|
character?: any,
|
|
inventories?: any,
|
|
quick_bar?: any,
|
|
hotbar?: string[],
|
|
personal_logistic_slots?: {name:string, min:number, max:number}[],
|
|
crafting_queue?: any,
|
|
recipe_notifications?: string,
|
|
// Components the upload was serialized with, all when missing
|
|
components?: Record<string, boolean> | unknown[],
|
|
}
|
|
|
|
const jsonPlayerData = Type.Object({
|
|
generation: Type.Number(),
|
|
name: Type.String(),
|
|
});
|
|
|
|
export class AcquireResponse {
|
|
constructor(
|
|
public status: string,
|
|
public generation?: number,
|
|
public hasData?: boolean,
|
|
public message?: string,
|
|
) {
|
|
}
|
|
|
|
static jsonSchema = Type.Object({
|
|
"status": StringEnum(["acquired", "error", "busy"]),
|
|
"generation": Type.Optional(Type.Integer()),
|
|
"hasData": Type.Optional(Type.Boolean()),
|
|
"message": Type.Optional(Type.String()),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof AcquireResponse.jsonSchema>): AcquireResponse {
|
|
return new this(json.status, json.generation, json.hasData, json.message);
|
|
}
|
|
};
|
|
export class AcquireRequest {
|
|
declare ["constructor"]: typeof AcquireRequest;
|
|
static type = "request" as const;
|
|
static src = "instance" as const;
|
|
static dst = "controller" as const;
|
|
static plugin = "player_sync" as const;
|
|
static Response = AcquireResponse;
|
|
|
|
constructor(
|
|
public instanceId: number,
|
|
public playerName: string,
|
|
) {
|
|
}
|
|
|
|
static jsonSchema = Type.Object({
|
|
"instanceId": Type.Number(),
|
|
"playerName": Type.String(),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof AcquireRequest.jsonSchema>): AcquireRequest {
|
|
return new this(json.instanceId, json.playerName);
|
|
}
|
|
}
|
|
|
|
export class ReleaseRequest {
|
|
declare ["constructor"]: typeof ReleaseRequest;
|
|
static type = "request" as const;
|
|
static src = "instance" as const;
|
|
static dst = "controller" as const;
|
|
static plugin = "player_sync" as const;
|
|
|
|
constructor(
|
|
public instanceId: number,
|
|
public playerName: string
|
|
) {
|
|
}
|
|
|
|
static jsonSchema = Type.Object({
|
|
"instanceId": Type.Integer(),
|
|
"playerName": Type.String(),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof ReleaseRequest.jsonSchema>) {
|
|
return new this(json.instanceId, json.playerName);
|
|
}
|
|
}
|
|
|
|
export class UploadRequest {
|
|
declare ["constructor"]: typeof UploadRequest;
|
|
static type = "request" as const;
|
|
static src = "instance" as const;
|
|
static dst = "controller" as const;
|
|
static plugin = "player_sync" as const;
|
|
|
|
constructor(
|
|
public instanceId: number,
|
|
public playerName: string,
|
|
public playerData: IpcPlayerData,
|
|
) {
|
|
}
|
|
|
|
static jsonSchema = Type.Object({
|
|
"instanceId": Type.Integer(),
|
|
"playerName": Type.String(),
|
|
"playerData": jsonPlayerData,
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof UploadRequest.jsonSchema>) {
|
|
return new this(json.instanceId, json.playerName, json.playerData as IpcPlayerData);
|
|
}
|
|
}
|
|
|
|
export class DownloadResponse {
|
|
constructor(
|
|
public playerData: IpcPlayerData | null
|
|
) {
|
|
}
|
|
|
|
static jsonSchema = Type.Object({
|
|
"playerData": Type.Optional(jsonPlayerData),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof DownloadResponse.jsonSchema>): DownloadResponse {
|
|
return new this(json.playerData as IpcPlayerData);
|
|
}
|
|
}
|
|
|
|
export class DownloadRequest {
|
|
declare ["constructor"]: typeof DownloadRequest;
|
|
static type = "request" as const;
|
|
static src = "instance" as const;
|
|
static dst = "controller" as const;
|
|
static plugin = "player_sync" as const;
|
|
static Response = DownloadResponse;
|
|
|
|
constructor(
|
|
public instanceId: number,
|
|
public playerName: string,
|
|
) {
|
|
}
|
|
|
|
static jsonSchema = Type.Object({
|
|
"instanceId": Type.Integer(),
|
|
"playerName": Type.String(),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof DownloadRequest.jsonSchema>) {
|
|
return new this(json.instanceId, json.playerName);
|
|
}
|
|
}
|
|
|
|
export class DatabaseStatsResponse {
|
|
constructor(
|
|
public databaseSize: number,
|
|
public databaseEntries: number,
|
|
public largestEntry: { name: string, size: number },
|
|
) {
|
|
}
|
|
|
|
static jsonSchema = Type.Object({
|
|
"databaseSize": Type.Integer(),
|
|
"databaseEntries": Type.Integer(),
|
|
"largestEntry": Type.Object({
|
|
"name": Type.String(),
|
|
"size": Type.Number(),
|
|
}),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof DatabaseStatsResponse.jsonSchema>): DatabaseStatsResponse {
|
|
return new this(json.databaseSize, json.databaseEntries, json.largestEntry);
|
|
}
|
|
}
|
|
|
|
export class DatabaseStatsRequest {
|
|
declare ["constructor"]: typeof DatabaseStatsRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static plugin = "player_sync" as const;
|
|
static permission = "player_sync.inventory.view" as const;
|
|
static Response = DatabaseStatsResponse;
|
|
}
|
|
|
|
export class PlayerEntry {
|
|
constructor(
|
|
public name: string,
|
|
public generation?: number,
|
|
public size?: number,
|
|
public instanceId?: number,
|
|
) {
|
|
}
|
|
|
|
static jsonSchema = Type.Object({
|
|
"name": Type.String(),
|
|
"generation": Type.Optional(Type.Integer()),
|
|
"size": Type.Optional(Type.Integer()),
|
|
"instanceId": Type.Optional(Type.Integer()),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof PlayerEntry.jsonSchema>): PlayerEntry {
|
|
return new this(json.name, json.generation, json.size, json.instanceId);
|
|
}
|
|
}
|
|
|
|
export class ListPlayersRequest {
|
|
declare ["constructor"]: typeof ListPlayersRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static plugin = "player_sync" as const;
|
|
static permission = "player_sync.inventory.view" as const;
|
|
static Response = jsonArray(PlayerEntry);
|
|
}
|
|
|
|
export class GetPlayerDataResponse {
|
|
constructor(
|
|
public playerData: IpcPlayerData | null,
|
|
public instanceId?: number,
|
|
) {
|
|
}
|
|
|
|
static jsonSchema = Type.Object({
|
|
"playerData": Type.Union([jsonPlayerData, Type.Null()]),
|
|
"instanceId": Type.Optional(Type.Integer()),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof GetPlayerDataResponse.jsonSchema>): GetPlayerDataResponse {
|
|
return new this(json.playerData as IpcPlayerData | null, json.instanceId);
|
|
}
|
|
}
|
|
|
|
export class GetPlayerDataRequest {
|
|
declare ["constructor"]: typeof GetPlayerDataRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static plugin = "player_sync" as const;
|
|
static permission = "player_sync.inventory.view" as const;
|
|
static Response = GetPlayerDataResponse;
|
|
|
|
constructor(
|
|
public playerName: string,
|
|
) {
|
|
}
|
|
|
|
static jsonSchema = Type.Object({
|
|
"playerName": Type.String(),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof GetPlayerDataRequest.jsonSchema>) {
|
|
return new this(json.playerName);
|
|
}
|
|
}
|
|
|
|
export class SetPlayerDataResponse {
|
|
constructor(
|
|
public generation: number,
|
|
) {
|
|
}
|
|
|
|
static jsonSchema = Type.Object({
|
|
"generation": Type.Integer(),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof SetPlayerDataResponse.jsonSchema>): SetPlayerDataResponse {
|
|
return new this(json.generation);
|
|
}
|
|
}
|
|
|
|
export class SetPlayerDataRequest {
|
|
declare ["constructor"]: typeof SetPlayerDataRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static plugin = "player_sync" as const;
|
|
static permission = "player_sync.inventory.modify" as const;
|
|
static Response = SetPlayerDataResponse;
|
|
|
|
constructor(
|
|
public playerName: string,
|
|
public playerData: IpcPlayerData,
|
|
) {
|
|
}
|
|
|
|
static jsonSchema = Type.Object({
|
|
"playerName": Type.String(),
|
|
"playerData": jsonPlayerData,
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof SetPlayerDataRequest.jsonSchema>) {
|
|
return new this(json.playerName, json.playerData as IpcPlayerData);
|
|
}
|
|
}
|
|
|
|
export class DeletePlayerDataRequest {
|
|
declare ["constructor"]: typeof DeletePlayerDataRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static plugin = "player_sync" as const;
|
|
static permission = "player_sync.inventory.modify" as const;
|
|
|
|
constructor(
|
|
public playerName: string,
|
|
) {
|
|
}
|
|
|
|
static jsonSchema = Type.Object({
|
|
"playerName": Type.String(),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof DeletePlayerDataRequest.jsonSchema>) {
|
|
return new this(json.playerName);
|
|
}
|
|
}
|
|
|
|
export class ForceReleaseRequest {
|
|
declare ["constructor"]: typeof ForceReleaseRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static plugin = "player_sync" as const;
|
|
static permission = "player_sync.inventory.modify" as const;
|
|
|
|
constructor(
|
|
public playerName: string,
|
|
) {
|
|
}
|
|
|
|
static jsonSchema = Type.Object({
|
|
"playerName": Type.String(),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof ForceReleaseRequest.jsonSchema>) {
|
|
return new this(json.playerName);
|
|
}
|
|
}
|