mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-12 15:04:52 +09:00
The controller merged uploads using the uploading instance's current config. An upload serialized under different settings, for example one sent from the save right after an admin turned on sync_inventories and restarted the instance, was then merged as if it held a character and wiped the stored one. Uploads now carry the components they were serialized with, the controller merges by those and drops the key before storing. The scenario keeps the components a player's synced session started with and serializes their upload with them, so changing the options while a player is online takes effect the next time they join. A player whose session lacks a component that is now synced downloads again even if the generation matches. Downloads snapshot the components when they start, so a change during a download can't mix two sets. A failed acquire unsyncs the player again regardless of the inventories setting, as before this branch. The god and dead player inventories move from the inventories component to the controller one, matching what the scenario syncs, which removes the special case in mergePlayerData. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
196 lines
4.9 KiB
TypeScript
196 lines
4.9 KiB
TypeScript
import { Type, Static } from "@sinclair/typebox";
|
|
import { StringEnum } 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;
|
|
}
|