mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 15:04:52 +09:00
153 lines
4.9 KiB
TypeScript
153 lines
4.9 KiB
TypeScript
import * as lib from "@clusterio/lib";
|
|
import type { InstancePluginContext } from "@clusterio/host";
|
|
|
|
import {
|
|
PlaceEvent,
|
|
RemoveRequest,
|
|
GetStorageRequest,
|
|
UpdateStorageEvent,
|
|
Item,
|
|
} from "./messages.js";
|
|
|
|
type IpcItems = [name: string, count: number, quality: string][];
|
|
|
|
export default async function(context: InstancePluginContext) {
|
|
const { instance, host, logger, plugin } = context;
|
|
const pendingTasks = new Set<Promise<unknown>>();
|
|
let pingId: ReturnType<typeof setInterval> | undefined;
|
|
let timeUpdateId: ReturnType<typeof setInterval> | undefined;
|
|
let cachedInventoryItems: Item[] = [];
|
|
|
|
function unexpectedError(err: Error) {
|
|
logger.error(`Unexpected error:\n${err.stack}`);
|
|
}
|
|
|
|
// provide items --------------------------------------------------------------
|
|
async function provideItems(items: IpcItems) {
|
|
if (!host.connector.hasSession) {
|
|
// For now the items are voided if the controller connection is
|
|
// down, which is no different from the previous behaviour.
|
|
if (instance.config.get("subspace_storage.log_item_transfers")) {
|
|
logger.verbose("Voided the following items:");
|
|
logger.verbose(JSON.stringify(items));
|
|
}
|
|
return;
|
|
}
|
|
|
|
const fromIpcItems = items.map(item => new Item(item[0], item[1], item[2]));
|
|
instance.sendTo("controller", new PlaceEvent(fromIpcItems));
|
|
|
|
if (instance.config.get("subspace_storage.log_item_transfers")) {
|
|
logger.verbose("Exported the following to controller:");
|
|
logger.verbose(JSON.stringify(items));
|
|
}
|
|
}
|
|
|
|
// request items --------------------------------------------------------------
|
|
async function requestItems(orders: IpcItems) {
|
|
logger.info(`Requesting items: ${JSON.stringify(orders)}`);
|
|
// Request the items all at once
|
|
const fromIpcItems = orders.map(item => new Item(item[0], item[1], item[2]));
|
|
let items = await instance.sendTo("controller", new RemoveRequest(fromIpcItems));
|
|
|
|
if (!items.length) {
|
|
return;
|
|
}
|
|
|
|
if (instance.config.get("subspace_storage.log_item_transfers")) {
|
|
logger.verbose("Imported following from controller:");
|
|
logger.verbose(JSON.stringify(items));
|
|
}
|
|
|
|
let itemsJson = lib.escapeString(JSON.stringify(items));
|
|
await instance.sendRcon(`/sc __subspace_storage__ Import("${itemsJson}")`, true, plugin.name);
|
|
}
|
|
|
|
instance.server.on("ipc-subspace_storage:output", (output: IpcItems) => {
|
|
logger.info("Received output items:");
|
|
logger.info(JSON.stringify(output));
|
|
provideItems(output).catch(err => unexpectedError(err));
|
|
});
|
|
instance.server.on("ipc-subspace_storage:orders", (orders: IpcItems) => {
|
|
if (instance.status !== "running" || !host.connected) {
|
|
return;
|
|
}
|
|
|
|
let task = requestItems(orders).catch(err => unexpectedError(err));
|
|
pendingTasks.add(task);
|
|
task.finally(() => { pendingTasks.delete(task); });
|
|
});
|
|
|
|
// combinator signals ---------------------------------------------------------
|
|
instance.handle(UpdateStorageEvent, async (event: UpdateStorageEvent) => {
|
|
if (instance.status !== "running") {
|
|
return;
|
|
}
|
|
// Cache latest inventory from controller
|
|
cachedInventoryItems = event.items;
|
|
});
|
|
|
|
function sendPing() {
|
|
instance.sendRcon(
|
|
"/sc __subspace_storage__ global.ticksSinceMasterPinged = 0", true, plugin.name
|
|
).catch(err => unexpectedError(err));
|
|
}
|
|
|
|
function sendInventory() {
|
|
const payloadItems = [
|
|
...cachedInventoryItems,
|
|
new Item("signal-unixtime", Math.floor(Date.now() / 1000), "normal"),
|
|
];
|
|
const payloadJson = lib.escapeString(JSON.stringify(payloadItems));
|
|
const task = instance.sendRcon(
|
|
`/sc __subspace_storage__ UpdateInvData("${payloadJson}")`, true, plugin.name
|
|
).catch(err => unexpectedError(err));
|
|
pendingTasks.add(task);
|
|
task.finally(() => { pendingTasks.delete(task); });
|
|
}
|
|
|
|
instance.hooks.start.attach(plugin.name, async () => {
|
|
// Each command runs a tick on an auto paused game.
|
|
pingId = setInterval(() => {
|
|
// Only ping if we are actually connected to the controller.
|
|
if (host.connected && !instance.autoPaused) {
|
|
sendPing();
|
|
}
|
|
}, 5000);
|
|
|
|
let items = await instance.sendTo("controller", new GetStorageRequest());
|
|
|
|
// Send the full inventory before a delta update can replace the cached items.
|
|
const itemsJson = lib.escapeString(JSON.stringify(items));
|
|
await instance.sendRcon(`/sc __subspace_storage__ UpdateInvData("${itemsJson}", true)`, true, plugin.name);
|
|
|
|
// Ensure a payload with updated time is sent every second
|
|
timeUpdateId = setInterval(() => {
|
|
if (instance.status === "running" && !instance.autoPaused) {
|
|
sendInventory();
|
|
}
|
|
}, 1000);
|
|
});
|
|
|
|
instance.hooks.autoPauseChanged.attach(plugin.name, async (paused: boolean) => {
|
|
if (paused || instance.status !== "running") {
|
|
return;
|
|
}
|
|
if (host.connected) {
|
|
sendPing();
|
|
}
|
|
sendInventory();
|
|
});
|
|
|
|
instance.hooks.stop.attach(plugin.name, async () => {
|
|
clearInterval(pingId);
|
|
clearInterval(timeUpdateId);
|
|
await Promise.all(pendingTasks);
|
|
});
|
|
|
|
instance.hooks.exit.attach(plugin.name, () => {
|
|
clearInterval(pingId);
|
|
clearInterval(timeUpdateId);
|
|
});
|
|
}
|