Files
factorio-clusterio/plugins/inventory_sync/instance.ts
T
Luciole 0b3550b472 Port remaining plugins to Typescript (#499)
* .js to .ts

* Allow access to _items entries; getItemCount return number

* add permission field to PluginPage interface

* port plugins to typescript

* update Request classes

* fix IpcFlowType
2023-08-22 16:17:16 +02:00

173 lines
5.7 KiB
TypeScript

import * as lib from "@clusterio/lib";
import { AcquireRequest, ReleaseRequest, UploadRequest, DownloadRequest } from "./messages";
type IpcPlayerName = {
player_name: string
}
export type IpcPlayerData = { //.\module\serialize.lua:serialize.serialize_player()
generation: number,
controller: string,
name: string,
color: number[],
chat_color: number[],
tag: string,
force: string,
cheat_mode: boolean,
flashlight: boolean,
ticks_to_respawn?: number,
character?: any,
inventories?: any,
hotbar?: string[],
personal_logistic_slots?: {name:string, min:number, max:number}[],
crafting_queue?: any,
}
/**
* Splits string into array of strings with max of a certain length
* @param chunkSize - Max length of each chunk
* @param string - String to split into chunks
*/
function chunkify(chunkSize: number, string: string): string[] {
return string.match(new RegExp(`.{1,${chunkSize}}`, "g")) || [];
}
export class InstancePlugin extends lib.BaseInstancePlugin {
playersToRelease!: Set<string>;
disconnecting!: boolean;
async init() {
this.playersToRelease = new Set();
this.disconnecting = false;
// Handle IPC from scenario script
this.instance.server.on("ipc-inventory_sync_acquire", (request: IpcPlayerName) => this.handleAcquire(request)
.catch(err => this.logger.error(`Error handling ipc-inventory_sync_acquire:\n${err.stack}`)));
this.instance.server.on("ipc-inventory_sync_release", (request: IpcPlayerName) => this.handleRelease(request)
.catch(err => this.logger.error(`Error handling ipc-inventory_sync_release:\n${err.stack}`)));
this.instance.server.on("ipc-inventory_sync_upload", (player_data: IpcPlayerData) => this.handleUpload(player_data)
.catch(err => this.logger.error(`Error handling ipc-inventory_sync_upload:\n${err.stack}`)));
this.instance.server.on("ipc-inventory_sync_download", (request: IpcPlayerName) => this.handleDownload(request)
.catch(err => this.logger.error(`Error handling ipc-inventory_sync_download:\n${err.stack}`)));
}
async onPrepareControllerDisconnect() {
this.disconnecting = true;
}
onControllerConnectionEvent(event: "connect" | "drop" | "resume" | "close") {
if (event === "connect") {
this.disconnecting = false;
(async () => {
for (let player_name of this.playersToRelease) {
if (!this.host.connector.connected || this.disconnecting) {
return;
}
this.playersToRelease.delete(player_name);
await this.instance.sendTo("controller", new ReleaseRequest(this.instance.id, player_name));
}
})().catch(
err => this.logger.error(`Unpexpected error releasing queued up players:\n${err.stack}`)
);
}
}
async handleAcquire(request: IpcPlayerName) {
let response = {
player_name: request.player_name,
status: "error",
message: "Controller is temporarily unavailable",
generation: undefined,
has_data: undefined,
};
if (this.host.connector.connected && !this.disconnecting) {
try {
let acquireResponse = await this.instance.sendTo(
"controller",
new AcquireRequest(this.instance.id, request.player_name),
);
response = {
player_name: request.player_name,
status: acquireResponse.status,
generation: acquireResponse.generation,
has_data: acquireResponse.hasData,
message: acquireResponse.message,
};
} catch (err: any) {
if (!(err instanceof lib.SessionLost)) {
this.logger.error(`Unexpected error sending aquire request:\n${err.stack}`);
response.message = err.message;
}
}
}
let json = lib.escapeString(JSON.stringify(response));
await this.sendRcon(`/sc inventory_sync.acquire_response("${json}")`, true);
}
async handleRelease(request: IpcPlayerName) {
if (!this.host.connector.connected) {
this.playersToRelease.add(request.player_name);
}
try {
await this.instance.sendTo("controller", new ReleaseRequest(this.instance.id, request.player_name));
} catch (err: any) {
if (err instanceof lib.SessionLost) {
this.playersToRelease.add(request.player_name);
} else {
this.logger.error(`Unexpected error releasing player ${request.player_name}:\n${err.stack}`);
}
}
}
async handleUpload(player_data: IpcPlayerData) {
if (!this.host.connector.connected || this.disconnecting) {
return;
}
this.logger.verbose(`Uploading ${player_data.name} (${JSON.stringify(player_data).length / 1000}kB)`);
try {
await this.instance.sendTo(
"controller",
new UploadRequest(this.instance.id, player_data.name, player_data),
);
} catch (err: any) {
if (!(err instanceof lib.SessionLost)) {
this.logger.error(`Unexpected error uploading inventory for ${player_data.name}:\n${err.stack}`);
}
return;
}
await this.sendRcon(
`/sc inventory_sync.confirm_upload("${player_data.name}", ${player_data.generation})`, true
);
}
async handleDownload(request: IpcPlayerName) {
const playerName = request.player_name;
this.logger.verbose(`Downloading ${playerName}`);
let response = await this.instance.sendTo("controller", new DownloadRequest(this.instance.id, playerName));
if (!response.player_data) {
await this.sendRcon(`/sc inventory_sync.download_inventory('${playerName}',nil,0,0)`, true);
return;
}
const chunkSize = this.instance.config.get("inventory_sync.rcon_chunk_size");
const chunks = chunkify(chunkSize, JSON.stringify(response.player_data));
this.logger.verbose(`Sending inventory for ${playerName} in ${chunks.length} chunks`);
for (let i = 0; i < chunks.length; i++) {
// this.logger.verbose(`Sending chunk ${i+1} of ${chunks.length}`)
const chunk = lib.escapeString(chunks[i]);
await this.sendRcon(
`/sc inventory_sync.download_inventory('${playerName}','${chunk}',${i + 1},${chunks.length})`,
true
);
}
}
}