mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 15:04:52 +09:00
Rename inventory_sync to player_sync
The plugin syncs more than inventories now (settings, appearance, recipe notifications), so name it for what it does. The package becomes @clusterio/plugin-player_sync and every inventory_sync identifier follows. A migration entrypoint renames the inventory_sync config fields, including load_plugin, and the inventory view permission. The plugin moves inventories.json in the database directory to player_sync.json and storage.inventory_sync in saves to storage.player_sync itself, and closes dialogs left open under the old GUI names on startup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e455d17bb7
commit
8363c5f0ef
39 files changed
+331
-177
No files matched your search
@@ -75,7 +75,7 @@ These are the plugins supported and maintained by the Clusterio developers:
|
||||
- [Statistics Exporter](/plugins/statistics_exporter/README.md): collect in-game statistics from all the servers and makes it available to the Prometheus endpoint on the controller.
|
||||
- [Subspace Storage](https://github.com/clusterio/subspace_storage): Provide shared storage that can transport items between servers via teleport chests.
|
||||
- [Player Auth](/plugins/player_auth/README.md): Provides authentication to the cluster via logging into a Factorio server.
|
||||
- [Inventory Sync](/plugins/inventory_sync/README.md): Synchronizes your inventory between servers.
|
||||
- [Player Sync](/plugins/player_sync/README.md): Synchronizes your inventory and player settings between servers.
|
||||
|
||||
There's also plugins developed and maintained by the community:
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@ if (process.platform === "win32") {
|
||||
|
||||
const availablePlugins = [
|
||||
{ name: "Global Chat", value: "@clusterio/plugin-global_chat" },
|
||||
{ name: "Inventory Sync", value: "@clusterio/plugin-inventory_sync" },
|
||||
{ name: "Player Auth", value: "@clusterio/plugin-player_auth" },
|
||||
{ name: "Player Sync", value: "@clusterio/plugin-player_sync" },
|
||||
{ name: "Research Sync", value: "@clusterio/plugin-research_sync" },
|
||||
{ name: "Statistics Exporter", value: "@clusterio/plugin-statistics_exporter" },
|
||||
{ name: "Subspace Storage", value: "@clusterio/plugin-subspace_storage" },
|
||||
|
||||
@@ -119,9 +119,9 @@ By default the freeplay scenario from the latest Factorio install in `host.facto
|
||||
* `--zip` Write the scenario as a zip file instead of a directory.
|
||||
Factorio does not load zipped scenarios, extract it before use.
|
||||
|
||||
For example, creating a scenario with only the inventory_sync module:
|
||||
For example, creating a scenario with only the player_sync module:
|
||||
|
||||
npx clusteriohost create-scenario ~/.factorio/scenarios/inventory_sync --plugins inventory_sync
|
||||
npx clusteriohost create-scenario ~/.factorio/scenarios/player_sync --plugins player_sync
|
||||
|
||||
|
||||
#### `config list`
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
local compat = require("modules/clusterio/compat")
|
||||
|
||||
--- @param no_early_return boolean? True to always setup data
|
||||
return function(no_early_return)
|
||||
local inventory_sync = compat.script_data.inventory_sync
|
||||
if inventory_sync and not no_early_return then
|
||||
return inventory_sync
|
||||
end
|
||||
|
||||
inventory_sync = inventory_sync or {}
|
||||
inventory_sync.players = inventory_sync.players or {}
|
||||
inventory_sync.players_waiting_for_acquire = inventory_sync.players_waiting_for_acquire or {}
|
||||
inventory_sync.players_in_cutscene_to_sync = inventory_sync.players_in_cutscene_to_sync or {}
|
||||
inventory_sync.failed_deserialization = inventory_sync.failed_deserialization or {}
|
||||
|
||||
inventory_sync.active_downloads = inventory_sync.active_downloads or {}
|
||||
inventory_sync.finished_downloads = inventory_sync.finished_downloads or {}
|
||||
inventory_sync.active_uploads = inventory_sync.active_uploads or {}
|
||||
|
||||
compat.script_data.inventory_sync = inventory_sync
|
||||
return inventory_sync
|
||||
end
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "inventory_sync",
|
||||
"load": ["inventory_sync.lua", "gui/dialog_failed_download.lua"]
|
||||
}
|
||||
File renamed without changes.
@@ -1,6 +1,6 @@
|
||||
# Clusterio inventory sync plugin
|
||||
# Clusterio player sync plugin
|
||||
|
||||
Carry over player inventory between servers
|
||||
Carry over player inventories and settings between servers
|
||||
|
||||
As a player, it mostly just works. Most important to know is that factorio data transfer is slow, which means if you have "big" items (blueprints mostly) in your inventory it will take a long time to transfer when joining servers. For this reason it is recommended to keep blueprints in the blueprint library.
|
||||
|
||||
@@ -12,11 +12,24 @@ When a server crashes while you are online there won't be time to immediately up
|
||||
|
||||
Run the following commands in the folder Clusterio is installed to:
|
||||
|
||||
npm install @clusterio/plugin-inventory_sync
|
||||
npx clusteriocontroller plugin add @clusterio/plugin-inventory_sync
|
||||
npm install @clusterio/plugin-player_sync
|
||||
npx clusteriocontroller plugin add @clusterio/plugin-player_sync
|
||||
|
||||
Substitute clusteriocontroller with clusteriohost or clusterioctl if this a dedicated host or ctl installation respectively.
|
||||
|
||||
## Upgrading from inventory_sync
|
||||
|
||||
This plugin was called inventory_sync before 2.0.0-alpha.28.
|
||||
Remove the old package and install the new one in its place:
|
||||
|
||||
npm uninstall @clusterio/plugin-inventory_sync
|
||||
npm install @clusterio/plugin-player_sync
|
||||
npx clusteriocontroller plugin remove inventory_sync
|
||||
npx clusteriocontroller plugin add @clusterio/plugin-player_sync
|
||||
|
||||
The `inventory_sync.*` config fields and the `inventory_sync.inventory.view` permission are renamed on load.
|
||||
The controller moves `inventories.json` in the database directory to `player_sync.json`, and saves keep their player data the next time they are started with the new module.
|
||||
|
||||
## Method of operation
|
||||
|
||||
This plugin does event based synchronization of inventories.
|
||||
@@ -47,7 +60,7 @@ Overall, we are able to achieve a latency between 3 and infinite ticks from serv
|
||||
|
||||
## Controller Configuration
|
||||
|
||||
### inventory_sync.player_lock_timeout
|
||||
### player_sync.player_lock_timeout
|
||||
|
||||
Time in seconds before the exclusive access an instance holds on a player's inventory expires after the instance stops or its host disconnects.
|
||||
Until it expires the player is told the inventory is in use on that instance when joining another one.
|
||||
@@ -58,7 +71,7 @@ Defaults to `60`.
|
||||
|
||||
## Instance Configuration
|
||||
|
||||
### inventory_sync.rcon_chunk_size
|
||||
### player_sync.rcon_chunk_size
|
||||
|
||||
Size in characters of the chunks the serialized inventory is split into when sent to the game over RCON.
|
||||
Smaller chunks take more commands to transfer an inventory but hold up the RCON command pipe for less time each.
|
||||
@@ -7,7 +7,20 @@ import * as lib from "@clusterio/lib";
|
||||
import * as msg from "./messages.js";
|
||||
|
||||
async function loadDatabase(config: lib.ControllerConfig, logger: lib.Logger) {
|
||||
let itemsPath = path.resolve(config.get("controller.database_directory"), "inventories.json");
|
||||
let databaseDirectory = config.get("controller.database_directory");
|
||||
let itemsPath = path.resolve(databaseDirectory, "player_sync.json");
|
||||
// migrate: inventory_sync stored its data in inventories.json before alpha 28
|
||||
let legacyPath = path.resolve(databaseDirectory, "inventories.json");
|
||||
if (!await fs.access(itemsPath).then(() => true, () => false)) {
|
||||
try {
|
||||
await fs.rename(legacyPath, itemsPath);
|
||||
logger.info(`Moved ${legacyPath} to ${itemsPath}`);
|
||||
} catch (err: any) {
|
||||
if (err.code !== "ENOENT") {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.verbose(`Loading ${itemsPath}`);
|
||||
try {
|
||||
let content = await fs.readFile(itemsPath, { encoding: "utf8" });
|
||||
@@ -28,7 +41,7 @@ async function saveDatabase(
|
||||
logger: lib.Logger,
|
||||
) {
|
||||
if (playerDatastore) {
|
||||
let file = path.resolve(controllerConfig.get("controller.database_directory"), "inventories.json");
|
||||
let file = path.resolve(controllerConfig.get("controller.database_directory"), "player_sync.json");
|
||||
logger.verbose(`writing ${file}`);
|
||||
let content = JSON.stringify(Array.from(playerDatastore));
|
||||
await lib.safeOutputFile(file, content);
|
||||
@@ -67,7 +80,7 @@ export default async function(context: ControllerPluginContext) {
|
||||
}
|
||||
|
||||
if (["unknown", "stopped"].includes(instance.status)) {
|
||||
let timeoutMs = controller.config.get("inventory_sync.player_lock_timeout") * 1000;
|
||||
let timeoutMs = controller.config.get("player_sync.player_lock_timeout") * 1000;
|
||||
for (let acquisitonRecord of acquiredPlayers.values()) {
|
||||
if (acquisitonRecord.instanceId === instanceId && !acquisitonRecord.expiresMs) {
|
||||
acquisitonRecord.expiresMs = Date.now() + timeoutMs;
|
||||
@@ -3,24 +3,25 @@ import * as messages from "./messages.js";
|
||||
|
||||
declare module "@clusterio/lib" {
|
||||
export interface InstanceConfigFields {
|
||||
"inventory_sync.rcon_chunk_size": number;
|
||||
"player_sync.rcon_chunk_size": number;
|
||||
}
|
||||
export interface ControllerConfigFields {
|
||||
"inventory_sync.player_lock_timeout": number;
|
||||
"player_sync.player_lock_timeout": number;
|
||||
}
|
||||
export interface Permissions {
|
||||
"inventory_sync.inventory.view": never;
|
||||
"player_sync.inventory.view": never;
|
||||
}
|
||||
}
|
||||
|
||||
export const plugin: lib.PluginDeclaration = {
|
||||
name: "inventory_sync",
|
||||
title: "Inventory sync",
|
||||
description: "Synchronizes players inventories between instances",
|
||||
name: "player_sync",
|
||||
title: "Player sync",
|
||||
description: "Synchronizes player inventories and settings between instances",
|
||||
migrationEntrypoint: "dist/node/migrations.js",
|
||||
|
||||
instanceEntrypoint: "dist/node/instance.js",
|
||||
instanceConfigFields: {
|
||||
"inventory_sync.rcon_chunk_size": {
|
||||
"player_sync.rcon_chunk_size": {
|
||||
title: "Rcon inventory chunk size",
|
||||
description:
|
||||
"Divide inventories into chunks of this size before sending with rcon to prevent blocking the pipe",
|
||||
@@ -31,7 +32,7 @@ export const plugin: lib.PluginDeclaration = {
|
||||
|
||||
controllerEntrypoint: "dist/node/controller.js",
|
||||
controllerConfigFields: {
|
||||
"inventory_sync.player_lock_timeout": {
|
||||
"player_sync.player_lock_timeout": {
|
||||
title: "Player Lock Timeout",
|
||||
description:
|
||||
"Time in seconds before the lock on a player inventory expires after an instance stops " +
|
||||
@@ -55,7 +56,7 @@ export const plugin: lib.PluginDeclaration = {
|
||||
],
|
||||
permissions: [
|
||||
{
|
||||
name: "inventory_sync.inventory.view",
|
||||
name: "player_sync.inventory.view",
|
||||
title: "View player inventories",
|
||||
description: "View player inventories",
|
||||
grantByDefault: true,
|
||||
@@ -136,7 +136,7 @@ export default async function(context: InstancePluginContext) {
|
||||
}
|
||||
|
||||
let json = lib.escapeString(JSON.stringify(response));
|
||||
await instance.sendRcon(`/sc inventory_sync.acquire_response("${json}")`, true, plugin.name);
|
||||
await instance.sendRcon(`/sc player_sync.acquire_response("${json}")`, true, plugin.name);
|
||||
}
|
||||
|
||||
async function handleRelease(request: IpcPlayerName) {
|
||||
@@ -178,7 +178,7 @@ export default async function(context: InstancePluginContext) {
|
||||
}
|
||||
|
||||
await instance.sendRcon(
|
||||
`/sc inventory_sync.confirm_upload("${player_data.name}", ${player_data.generation})`, true, plugin.name
|
||||
`/sc player_sync.confirm_upload("${player_data.name}", ${player_data.generation})`, true, plugin.name
|
||||
);
|
||||
}
|
||||
|
||||
@@ -193,20 +193,20 @@ export default async function(context: InstancePluginContext) {
|
||||
|
||||
if (!response.playerData) {
|
||||
await instance.sendRcon(
|
||||
`/sc inventory_sync.download_inventory('${playerName}',nil,0,0)`, true, plugin.name
|
||||
`/sc player_sync.download_inventory('${playerName}',nil,0,0)`, true, plugin.name
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await applyRecipeNotificationDelta(response.playerData, request.recipe_notifications, logger);
|
||||
|
||||
const chunkSize = instance.config.get("inventory_sync.rcon_chunk_size");
|
||||
const chunkSize = instance.config.get("player_sync.rcon_chunk_size");
|
||||
const chunks = chunkify(chunkSize, JSON.stringify(response.playerData));
|
||||
logger.verbose(`Sending inventory for ${playerName} in ${chunks.length} chunks`);
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
const chunk = lib.escapeString(chunks[i]);
|
||||
await instance.sendRcon(
|
||||
`/sc inventory_sync.download_inventory('${playerName}','${chunk}',${i + 1},${chunks.length})`,
|
||||
`/sc player_sync.download_inventory('${playerName}','${chunk}',${i + 1},${chunks.length})`,
|
||||
true,
|
||||
plugin.name,
|
||||
);
|
||||
@@ -215,27 +215,27 @@ export default async function(context: InstancePluginContext) {
|
||||
|
||||
// Handle IPC from scenario script
|
||||
instance.server.on(
|
||||
"ipc-inventory_sync_acquire",
|
||||
"ipc-player_sync_acquire",
|
||||
(request: IpcPlayerName) => handleAcquire(request).catch(
|
||||
err => logger.error(`Error handling ipc-inventory_sync_acquire:\n${err.stack}`)
|
||||
err => logger.error(`Error handling ipc-player_sync_acquire:\n${err.stack}`)
|
||||
),
|
||||
);
|
||||
instance.server.on(
|
||||
"ipc-inventory_sync_release",
|
||||
"ipc-player_sync_release",
|
||||
(request: IpcPlayerName) => handleRelease(request).catch(
|
||||
err => logger.error(`Error handling ipc-inventory_sync_release:\n${err.stack}`)
|
||||
err => logger.error(`Error handling ipc-player_sync_release:\n${err.stack}`)
|
||||
),
|
||||
);
|
||||
instance.server.on(
|
||||
"ipc-inventory_sync_upload",
|
||||
"ipc-player_sync_upload",
|
||||
(player_data: IpcPlayerData) => handleUpload(player_data).catch(
|
||||
err => logger.error(`Error handling ipc-inventory_sync_upload:\n${err.stack}`)
|
||||
err => logger.error(`Error handling ipc-player_sync_upload:\n${err.stack}`)
|
||||
),
|
||||
);
|
||||
instance.server.on(
|
||||
"ipc-inventory_sync_download",
|
||||
"ipc-player_sync_download",
|
||||
(request: IpcDownloadRequest) => handleDownload(request).catch(
|
||||
err => logger.error(`Error handling ipc-inventory_sync_download:\n${err.stack}`)
|
||||
err => logger.error(`Error handling ipc-player_sync_download:\n${err.stack}`)
|
||||
),
|
||||
);
|
||||
|
||||
@@ -53,7 +53,7 @@ export class AcquireRequest {
|
||||
static type = "request" as const;
|
||||
static src = "instance" as const;
|
||||
static dst = "controller" as const;
|
||||
static plugin = "inventory_sync" as const;
|
||||
static plugin = "player_sync" as const;
|
||||
static Response = AcquireResponse;
|
||||
|
||||
constructor(
|
||||
@@ -77,7 +77,7 @@ export class ReleaseRequest {
|
||||
static type = "request" as const;
|
||||
static src = "instance" as const;
|
||||
static dst = "controller" as const;
|
||||
static plugin = "inventory_sync" as const;
|
||||
static plugin = "player_sync" as const;
|
||||
|
||||
constructor(
|
||||
public instanceId: number,
|
||||
@@ -100,7 +100,7 @@ export class UploadRequest {
|
||||
static type = "request" as const;
|
||||
static src = "instance" as const;
|
||||
static dst = "controller" as const;
|
||||
static plugin = "inventory_sync" as const;
|
||||
static plugin = "player_sync" as const;
|
||||
|
||||
constructor(
|
||||
public instanceId: number,
|
||||
@@ -140,7 +140,7 @@ export class DownloadRequest {
|
||||
static type = "request" as const;
|
||||
static src = "instance" as const;
|
||||
static dst = "controller" as const;
|
||||
static plugin = "inventory_sync" as const;
|
||||
static plugin = "player_sync" as const;
|
||||
static Response = DownloadResponse;
|
||||
|
||||
constructor(
|
||||
@@ -186,7 +186,7 @@ export class DatabaseStatsRequest {
|
||||
static type = "request" as const;
|
||||
static src = "control" as const;
|
||||
static dst = "controller" as const;
|
||||
static plugin = "inventory_sync" as const;
|
||||
static permission = "inventory_sync.inventory.view" as const;
|
||||
static plugin = "player_sync" as const;
|
||||
static permission = "player_sync.inventory.view" as const;
|
||||
static Response = DatabaseStatsResponse;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import * as lib from "@clusterio/lib";
|
||||
|
||||
export default function(context: lib.MigrationContext) {
|
||||
const { hooks, plugin } = context;
|
||||
// migrate: inventory_sync was renamed to player_sync in 2.0.0-alpha.28
|
||||
const renameFields = (config: lib.ConfigSchema) => (
|
||||
lib.renameConfigPrefix(config, "inventory_sync.", "player_sync.")
|
||||
);
|
||||
hooks.controllerConfig.attach(plugin.name, renameFields);
|
||||
hooks.hostConfig.attach(plugin.name, renameFields);
|
||||
hooks.instanceConfig.attach(plugin.name, renameFields);
|
||||
hooks.role.attach(plugin.name, role => lib.renamePermissionPrefix(role, "inventory_sync.", "player_sync."));
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+8
-8
@@ -1,7 +1,7 @@
|
||||
local progress_dialog = require("modules/inventory_sync/gui/progress_dialog")
|
||||
local ensure_character = require("modules/inventory_sync/ensure_character")
|
||||
local restore_position = require("modules/inventory_sync/restore_position")
|
||||
local get_script_data = require("modules/inventory_sync/get_script_data")
|
||||
local progress_dialog = require("modules/player_sync/gui/progress_dialog")
|
||||
local ensure_character = require("modules/player_sync/ensure_character")
|
||||
local restore_position = require("modules/player_sync/restore_position")
|
||||
local get_script_data = require("modules/player_sync/get_script_data")
|
||||
|
||||
|
||||
local function download_inventory(player_name, data, number, total)
|
||||
@@ -21,14 +21,14 @@ local function download_inventory(player_name, data, number, total)
|
||||
local player_record = script_data.players[player_name]
|
||||
if record.restart then
|
||||
rcon.print("Restarting outdated download")
|
||||
inventory_sync.initiate_inventory_download(player, player_record, record.generation)
|
||||
player_sync.initiate_inventory_download(player, player_record, record.generation)
|
||||
return
|
||||
end
|
||||
|
||||
if total == 0 then
|
||||
-- Download was requested but the player had no data stored.
|
||||
log("ERROR: Inventory sync failed, got empty player data for " .. player.name .. " from the controller")
|
||||
player.print("ERROR: Inventory sync failed, got empty player data from the controller")
|
||||
log("ERROR: Player sync failed, got empty player data for " .. player.name .. " from the controller")
|
||||
player.print("ERROR: Player sync failed, got empty player data from the controller")
|
||||
|
||||
progress_dialog.remove(player)
|
||||
|
||||
@@ -70,7 +70,7 @@ local function download_inventory(player_name, data, number, total)
|
||||
log("Imported player data for " .. player_name .. " in " .. ticks .. " ticks")
|
||||
|
||||
if player.connected then
|
||||
inventory_sync.finish_download(player, record)
|
||||
player_sync.finish_download(player, record)
|
||||
end
|
||||
end
|
||||
|
||||
File renamed without changes.
@@ -0,0 +1,28 @@
|
||||
local compat = require("modules/clusterio/compat")
|
||||
|
||||
--- @param no_early_return boolean? True to always setup data
|
||||
return function(no_early_return)
|
||||
local player_sync = compat.script_data.player_sync
|
||||
if player_sync and not no_early_return then
|
||||
return player_sync
|
||||
end
|
||||
|
||||
-- migrate: inventory_sync was renamed to player_sync in 2.0.0-alpha.28
|
||||
if not player_sync and compat.script_data.inventory_sync then
|
||||
player_sync = compat.script_data.inventory_sync
|
||||
compat.script_data.inventory_sync = nil
|
||||
end
|
||||
|
||||
player_sync = player_sync or {}
|
||||
player_sync.players = player_sync.players or {}
|
||||
player_sync.players_waiting_for_acquire = player_sync.players_waiting_for_acquire or {}
|
||||
player_sync.players_in_cutscene_to_sync = player_sync.players_in_cutscene_to_sync or {}
|
||||
player_sync.failed_deserialization = player_sync.failed_deserialization or {}
|
||||
|
||||
player_sync.active_downloads = player_sync.active_downloads or {}
|
||||
player_sync.finished_downloads = player_sync.finished_downloads or {}
|
||||
player_sync.active_uploads = player_sync.active_uploads or {}
|
||||
|
||||
compat.script_data.player_sync = player_sync
|
||||
return player_sync
|
||||
end
|
||||
+12
-12
@@ -1,5 +1,5 @@
|
||||
local ensure_character = require("modules/inventory_sync/ensure_character")
|
||||
local get_script_data = require("modules/inventory_sync/get_script_data")
|
||||
local ensure_character = require("modules/player_sync/ensure_character")
|
||||
local get_script_data = require("modules/player_sync/get_script_data")
|
||||
|
||||
local dialog_failed_download = {}
|
||||
|
||||
@@ -21,7 +21,7 @@ function dialog_failed_download.create(player, acquire_response)
|
||||
}
|
||||
titlebar.drag_target = frame
|
||||
|
||||
local title_caption = "Inventory sync download failed"
|
||||
local title_caption = "Player sync download failed"
|
||||
if acquire_response.status == "busy" then
|
||||
title_caption = "Inventory is in use on " .. acquire_response.message
|
||||
end
|
||||
@@ -43,7 +43,7 @@ function dialog_failed_download.create(player, acquire_response)
|
||||
filler.drag_target = frame
|
||||
|
||||
local content = frame.add {
|
||||
name = "inventory_sync_failed_download_content",
|
||||
name = "player_sync_failed_download_content",
|
||||
type = "frame",
|
||||
direction = "vertical",
|
||||
style = "inside_shallow_frame_with_padding",
|
||||
@@ -90,7 +90,7 @@ function dialog_failed_download.create(player, acquire_response)
|
||||
dialog_row.drag_target = frame
|
||||
|
||||
local abort = dialog_row.add {
|
||||
name = "inventory_sync_failed_download_abort",
|
||||
name = "player_sync_failed_download_abort",
|
||||
type = "button",
|
||||
style = "back_button",
|
||||
caption = "Use temporary inventory",
|
||||
@@ -107,7 +107,7 @@ function dialog_failed_download.create(player, acquire_response)
|
||||
dialog_filler.drag_target = frame
|
||||
|
||||
local retry = dialog_row.add {
|
||||
name = "inventory_sync_failed_download_retry",
|
||||
name = "player_sync_failed_download_retry",
|
||||
type = "button",
|
||||
style = "forward_button",
|
||||
caption = "Retry",
|
||||
@@ -123,19 +123,19 @@ dialog_failed_download.events = {
|
||||
return
|
||||
end
|
||||
|
||||
if event.element.name == "inventory_sync_failed_download_abort" then
|
||||
if event.element.name == "player_sync_failed_download_abort" then
|
||||
-- Give the player a character and let them play with that
|
||||
local player = assert(game.get_player(event.player_index))
|
||||
ensure_character(player)
|
||||
local inventory_sync = get_script_data()
|
||||
local player_sync = get_script_data()
|
||||
player.gui.screen.dialog_failed_download.destroy()
|
||||
inventory_sync.players[player.name].dirty = true
|
||||
inventory_sync.players[player.name].sync = false
|
||||
player_sync.players[player.name].dirty = true
|
||||
player_sync.players[player.name].sync = false
|
||||
|
||||
elseif event.element.name == "inventory_sync_failed_download_retry" then
|
||||
elseif event.element.name == "player_sync_failed_download_retry" then
|
||||
-- Retry acquiring the player from the controller
|
||||
local player = assert(game.get_player(event.player_index))
|
||||
inventory_sync.acquire(player)
|
||||
player_sync.acquire(player)
|
||||
player.gui.screen.dialog_failed_download.destroy()
|
||||
end
|
||||
end,
|
||||
+7
-7
@@ -6,7 +6,7 @@ local progress_dialog = {}
|
||||
--- @param total number
|
||||
function progress_dialog.create(player, progress, total)
|
||||
local frame = player.gui.screen.add {
|
||||
name = "inventory_sync_progress",
|
||||
name = "player_sync_progress",
|
||||
type = "frame",
|
||||
direction = "vertical",
|
||||
}
|
||||
@@ -19,7 +19,7 @@ function progress_dialog.create(player, progress, total)
|
||||
local title = titlebar.add {
|
||||
type = "label",
|
||||
style = "frame_title",
|
||||
caption = "Inventory sync",
|
||||
caption = "Player sync",
|
||||
}
|
||||
title.drag_target = frame
|
||||
|
||||
@@ -33,7 +33,7 @@ function progress_dialog.create(player, progress, total)
|
||||
filler.drag_target = frame
|
||||
|
||||
local content = frame.add {
|
||||
name = "inventory_sync_progress_dialog_content",
|
||||
name = "player_sync_progress_dialog_content",
|
||||
type = "frame",
|
||||
direction = "vertical",
|
||||
style = "inside_shallow_frame_with_padding",
|
||||
@@ -59,8 +59,8 @@ end
|
||||
|
||||
--- @param player LuaPlayer
|
||||
function progress_dialog.remove(player)
|
||||
if player.gui.screen.inventory_sync_progress then
|
||||
player.gui.screen.inventory_sync_progress.destroy()
|
||||
if player.gui.screen.player_sync_progress then
|
||||
player.gui.screen.player_sync_progress.destroy()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -69,12 +69,12 @@ end
|
||||
--- @param total number
|
||||
function progress_dialog.update(player, progress, total)
|
||||
-- Be defensive here in case an old GUI is present
|
||||
local frame = player.gui.screen.inventory_sync_progress
|
||||
local frame = player.gui.screen.player_sync_progress
|
||||
if not frame then
|
||||
return false
|
||||
end
|
||||
|
||||
local content = frame.inventory_sync_progress_dialog_content
|
||||
local content = frame.player_sync_progress_dialog_content
|
||||
if not content then
|
||||
return false
|
||||
end
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "player_sync",
|
||||
"load": ["player_sync.lua", "gui/dialog_failed_download.lua"]
|
||||
}
|
||||
+68
-59
@@ -1,6 +1,6 @@
|
||||
--[[
|
||||
|
||||
Inventory Sync
|
||||
Player Sync
|
||||
|
||||
When a player leaves the game, serialize the player state and upload it to the controller.
|
||||
When a player joins the game, request the player data from controller and deserialize the player state from it.
|
||||
@@ -9,12 +9,12 @@ When a player joins the game, request the player data from controller and deseri
|
||||
|
||||
local compat = require("modules/clusterio/compat")
|
||||
local clusterio_api = require("modules/clusterio/api")
|
||||
local download_inventory = require("modules/inventory_sync/download_inventory")
|
||||
local serialize = require("modules/inventory_sync/serialize")
|
||||
local restore_position = require("modules/inventory_sync/restore_position")
|
||||
local get_script_data = require("modules/inventory_sync/get_script_data")
|
||||
local progress_dialog = require("modules/inventory_sync/gui/progress_dialog")
|
||||
local dialog_failed_download = require("modules/inventory_sync/gui/dialog_failed_download")
|
||||
local download_inventory = require("modules/player_sync/download_inventory")
|
||||
local serialize = require("modules/player_sync/serialize")
|
||||
local restore_position = require("modules/player_sync/restore_position")
|
||||
local get_script_data = require("modules/player_sync/get_script_data")
|
||||
local progress_dialog = require("modules/player_sync/gui/progress_dialog")
|
||||
local dialog_failed_download = require("modules/player_sync/gui/dialog_failed_download")
|
||||
|
||||
local v2_remote_controller = compat.version_ge("2.0.0")
|
||||
local recipe_notifications_api = compat.version_ge("2.0.67")
|
||||
@@ -47,13 +47,13 @@ local function remove_player(player)
|
||||
end
|
||||
|
||||
|
||||
inventory_sync = {}
|
||||
-- This function is called by instance.js as /sc inventory_sync.download_inventory("danielv", Escaped JSON string, package_number, total_packages_count) with data from the controller
|
||||
inventory_sync.download_inventory = download_inventory
|
||||
player_sync = {}
|
||||
-- This function is called by instance.js as /sc player_sync.download_inventory("danielv", Escaped JSON string, package_number, total_packages_count) with data from the controller
|
||||
player_sync.download_inventory = download_inventory
|
||||
|
||||
-- This function is called internally when a player leaves the game to
|
||||
-- serialize the player for upload.
|
||||
function inventory_sync.serialize_player(player, player_record)
|
||||
function player_sync.serialize_player(player, player_record)
|
||||
-- Editor mode is not supported.
|
||||
if player.controller_type == defines.controllers.editor then
|
||||
player.toggle_map_editor()
|
||||
@@ -84,7 +84,7 @@ function inventory_sync.serialize_player(player, player_record)
|
||||
return serialized_player
|
||||
end
|
||||
|
||||
function inventory_sync.deserialize_player(player, finished_record)
|
||||
function player_sync.deserialize_player(player, finished_record)
|
||||
-- Editor mode is not supported.
|
||||
if player.controller_type == defines.controllers.editor then
|
||||
player.toggle_map_editor()
|
||||
@@ -146,8 +146,8 @@ function inventory_sync.deserialize_player(player, finished_record)
|
||||
end
|
||||
end
|
||||
|
||||
inventory_sync.events = {}
|
||||
inventory_sync.events[clusterio_api.events.on_server_startup] = function(event)
|
||||
player_sync.events = {}
|
||||
player_sync.events[clusterio_api.events.on_server_startup] = function(event)
|
||||
local script_data = get_script_data(true)
|
||||
script_data.players_waiting_for_acquire = {}
|
||||
script_data.players_in_cutscene_to_sync = {}
|
||||
@@ -156,34 +156,43 @@ inventory_sync.events[clusterio_api.events.on_server_startup] = function(event)
|
||||
if not script_data.players[player.name] then
|
||||
create_player(player, true)
|
||||
end
|
||||
|
||||
-- migrate: dialogs created before inventory_sync was renamed to player_sync
|
||||
local screen = player.gui.screen
|
||||
if screen.inventory_sync_progress then
|
||||
screen.inventory_sync_progress.destroy()
|
||||
end
|
||||
if screen.dialog_failed_download and screen.dialog_failed_download.inventory_sync_failed_download_content then
|
||||
screen.dialog_failed_download.destroy()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Cleanup
|
||||
inventory_sync.events[defines.events.on_pre_player_removed] = function(event)
|
||||
player_sync.events[defines.events.on_pre_player_removed] = function(event)
|
||||
remove_player(game.get_player(event.player_index))
|
||||
end
|
||||
|
||||
inventory_sync.events[defines.events.on_player_created] = function(event)
|
||||
player_sync.events[defines.events.on_player_created] = function(event)
|
||||
create_player(game.get_player(event.player_index), false)
|
||||
end
|
||||
|
||||
function inventory_sync.acquire(player)
|
||||
function player_sync.acquire(player)
|
||||
local script_data = get_script_data()
|
||||
script_data.players_waiting_for_acquire[player.name] = {
|
||||
start_tick = game.ticks_played,
|
||||
}
|
||||
clusterio_api.send_json("inventory_sync_acquire", {
|
||||
clusterio_api.send_json("player_sync_acquire", {
|
||||
player_name = player.name,
|
||||
})
|
||||
end
|
||||
|
||||
function inventory_sync.check_players_waiting_for_acquire()
|
||||
function player_sync.check_players_waiting_for_acquire()
|
||||
local script_data = get_script_data()
|
||||
for player_name, record in pairs(script_data.players_waiting_for_acquire) do
|
||||
-- Check if the acquire player request timed out
|
||||
if game.ticks_played > record.start_tick + 600 then
|
||||
inventory_sync.acquire_response(compat.table_to_json({
|
||||
player_sync.acquire_response(compat.table_to_json({
|
||||
status = "timeout",
|
||||
player_name = player_name,
|
||||
}))
|
||||
@@ -191,11 +200,11 @@ function inventory_sync.check_players_waiting_for_acquire()
|
||||
end
|
||||
end
|
||||
|
||||
function inventory_sync.acquire_response(data)
|
||||
function player_sync.acquire_response(data)
|
||||
local script_data = get_script_data()
|
||||
local response = assert(compat.json_to_table(data))
|
||||
if not script_data.players_waiting_for_acquire[response.player_name] then
|
||||
clusterio_api.send_json("inventory_sync_release", { player_name = response.player_name })
|
||||
clusterio_api.send_json("player_sync_release", { player_name = response.player_name })
|
||||
return
|
||||
end
|
||||
script_data.players_waiting_for_acquire[response.player_name] = nil
|
||||
@@ -205,21 +214,21 @@ function inventory_sync.acquire_response(data)
|
||||
response.player = player
|
||||
script_data.players_in_cutscene_to_sync[response.player_name] = response
|
||||
else
|
||||
inventory_sync.sync_player(response)
|
||||
player_sync.sync_player(response)
|
||||
end
|
||||
end
|
||||
|
||||
function inventory_sync.check_players_in_cutscene()
|
||||
function player_sync.check_players_in_cutscene()
|
||||
local script_data = get_script_data()
|
||||
for player_name, response in pairs(script_data.players_in_cutscene_to_sync) do
|
||||
if not is_in_cutscene(response.player) then
|
||||
script_data.players_in_cutscene_to_sync[player_name] = nil
|
||||
inventory_sync.sync_player(response)
|
||||
player_sync.sync_player(response)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function inventory_sync.sync_player(acquire_response)
|
||||
function player_sync.sync_player(acquire_response)
|
||||
local player = assert(game.get_player(acquire_response.player_name))
|
||||
|
||||
-- Editor mode is not supported.
|
||||
@@ -228,8 +237,8 @@ function inventory_sync.sync_player(acquire_response)
|
||||
end
|
||||
|
||||
if player.controller_type == defines.controllers.editor then
|
||||
log("ERROR: Inventory sync failed, unable to switch " .. player.name .. " out of editor mode")
|
||||
player.print("ERROR: Inventory sync failed, unable to switch out of editor mode")
|
||||
log("ERROR: Player sync failed, unable to switch " .. player.name .. " out of editor mode")
|
||||
player.print("ERROR: Player sync failed, unable to switch out of editor mode")
|
||||
return
|
||||
end
|
||||
|
||||
@@ -237,7 +246,7 @@ function inventory_sync.sync_player(acquire_response)
|
||||
local finished_record = script_data.finished_downloads[acquire_response.player_name]
|
||||
if finished_record then
|
||||
if acquire_response.status == "acquired" and finished_record.generation == acquire_response.generation then
|
||||
inventory_sync.finish_download(player, finished_record)
|
||||
player_sync.finish_download(player, finished_record)
|
||||
return
|
||||
end
|
||||
|
||||
@@ -298,7 +307,7 @@ function inventory_sync.sync_player(acquire_response)
|
||||
acquire_response.has_data
|
||||
and (not player_record.sync or acquire_response.generation > player_record.generation)
|
||||
then
|
||||
inventory_sync.initiate_inventory_download(player, player_record, acquire_response.generation)
|
||||
player_sync.initiate_inventory_download(player, player_record, acquire_response.generation)
|
||||
|
||||
else
|
||||
if acquire_response.generation == 0 then
|
||||
@@ -314,12 +323,12 @@ function inventory_sync.sync_player(acquire_response)
|
||||
end
|
||||
end
|
||||
|
||||
function inventory_sync.finish_download(player, finished_record)
|
||||
function player_sync.finish_download(player, finished_record)
|
||||
local script_data = get_script_data()
|
||||
script_data.finished_downloads[player.name] = nil
|
||||
local player_record = script_data.players[player.name]
|
||||
|
||||
local status, result = xpcall(inventory_sync.deserialize_player, debug.traceback, player, finished_record)
|
||||
local status, result = xpcall(player_sync.deserialize_player, debug.traceback, player, finished_record)
|
||||
if not status then
|
||||
log("ERROR: Deserializing player " .. player.name .. " failed: " .. result)
|
||||
player.print("ERROR: Deserializing player data failed: " .. result)
|
||||
@@ -335,43 +344,43 @@ function inventory_sync.finish_download(player, finished_record)
|
||||
end
|
||||
|
||||
-- Download inventory from controller
|
||||
inventory_sync.events[defines.events.on_player_joined_game] = function(event)
|
||||
player_sync.events[defines.events.on_player_joined_game] = function(event)
|
||||
local player = assert(game.get_player(event.player_index))
|
||||
local script_data = get_script_data()
|
||||
|
||||
if not game.is_multiplayer() then
|
||||
return player.print("Inventory sync deactivated: game is single player")
|
||||
return player.print("Player sync deactivated: game is single player")
|
||||
end
|
||||
|
||||
-- Send acquire request even if an active download is currently in progress
|
||||
inventory_sync.acquire(player)
|
||||
player_sync.acquire(player)
|
||||
|
||||
-- Clear active upload if it exists
|
||||
script_data.active_uploads[player.name] = nil
|
||||
end
|
||||
|
||||
inventory_sync.on_nth_tick = {}
|
||||
inventory_sync.on_nth_tick[33] = function(event)
|
||||
player_sync.on_nth_tick = {}
|
||||
player_sync.on_nth_tick[33] = function(event)
|
||||
-- Periodically check players currently in the process of syncing
|
||||
inventory_sync.check_players_waiting_for_acquire()
|
||||
inventory_sync.check_players_in_cutscene()
|
||||
inventory_sync.check_active_downloads()
|
||||
inventory_sync.check_active_uploads()
|
||||
player_sync.check_players_waiting_for_acquire()
|
||||
player_sync.check_players_in_cutscene()
|
||||
player_sync.check_active_downloads()
|
||||
player_sync.check_active_uploads()
|
||||
end
|
||||
|
||||
inventory_sync.events[defines.events.on_cutscene_waypoint_reached] = function(event)
|
||||
inventory_sync.check_players_in_cutscene()
|
||||
player_sync.events[defines.events.on_cutscene_waypoint_reached] = function(event)
|
||||
player_sync.check_players_in_cutscene()
|
||||
end
|
||||
|
||||
inventory_sync.events[defines.events.on_cutscene_cancelled] = function(event)
|
||||
inventory_sync.check_players_in_cutscene()
|
||||
player_sync.events[defines.events.on_cutscene_cancelled] = function(event)
|
||||
player_sync.check_players_in_cutscene()
|
||||
end
|
||||
|
||||
-- Upload inventory when a player leaves the game. Triggers on restart after crash if player was online during crash.
|
||||
inventory_sync.events[defines.events.on_pre_player_left_game] = function(event)
|
||||
player_sync.events[defines.events.on_pre_player_left_game] = function(event)
|
||||
-- for some reason, on_pre_player_left_game gets called before on_server_startup so script_data isn't ready yet
|
||||
if not compat.script_data.inventory_sync then
|
||||
log("ERROR: script data for inventory sync is not defined")
|
||||
if not compat.script_data.player_sync then
|
||||
log("ERROR: script data for player sync is not defined")
|
||||
return
|
||||
end
|
||||
|
||||
@@ -381,18 +390,18 @@ inventory_sync.events[defines.events.on_pre_player_left_game] = function(event)
|
||||
|
||||
-- If player has an active download release our acquisition but let the download continue
|
||||
if script_data.active_downloads[player.name] then
|
||||
clusterio_api.send_json("inventory_sync_release", { player_name = player.name })
|
||||
clusterio_api.send_json("player_sync_release", { player_name = player.name })
|
||||
return
|
||||
end
|
||||
|
||||
if not player_record.dirty or not player_record.sync then
|
||||
-- Nothing to upload, release the acquisition so other instances are not blocked
|
||||
clusterio_api.send_json("inventory_sync_release", { player_name = player.name })
|
||||
clusterio_api.send_json("player_sync_release", { player_name = player.name })
|
||||
return
|
||||
end
|
||||
|
||||
player_record.generation = player_record.generation + 1
|
||||
local status, result = xpcall(inventory_sync.serialize_player, debug.traceback, player, player_record)
|
||||
local status, result = xpcall(player_sync.serialize_player, debug.traceback, player, player_record)
|
||||
if not status then
|
||||
log("ERROR: Serializing player " .. player.name .. " failed: " .. result)
|
||||
player.print("ERROR: Serializing player data failed: " .. result)
|
||||
@@ -404,11 +413,11 @@ inventory_sync.events[defines.events.on_pre_player_left_game] = function(event)
|
||||
timeout = math.random(600, 1200), -- Start with 10-20 seconds for the timeout
|
||||
}
|
||||
|
||||
clusterio_api.send_json("inventory_sync_upload", result)
|
||||
clusterio_api.send_json("player_sync_upload", result)
|
||||
end
|
||||
|
||||
-- Invoked by the instance code to confirm the controller has received the uploaded data
|
||||
function inventory_sync.confirm_upload(player_name, generation)
|
||||
function player_sync.confirm_upload(player_name, generation)
|
||||
local player = game.get_player(player_name)
|
||||
if not player or player.connected then
|
||||
log("no player, or player connected")
|
||||
@@ -431,7 +440,7 @@ function inventory_sync.confirm_upload(player_name, generation)
|
||||
player_record.dirty = false
|
||||
end
|
||||
|
||||
function inventory_sync.initiate_inventory_download(player, player_record, generation)
|
||||
function player_sync.initiate_inventory_download(player, player_record, generation)
|
||||
player.print("Initiating inventory download...")
|
||||
local record = {
|
||||
started = game.ticks_played,
|
||||
@@ -448,7 +457,7 @@ function inventory_sync.initiate_inventory_download(player, player_record, gener
|
||||
recipe_notifications = serialize.serialize_crafting_notifications(player)
|
||||
end
|
||||
|
||||
clusterio_api.send_json("inventory_sync_download", {
|
||||
clusterio_api.send_json("player_sync_download", {
|
||||
player_name = player.name,
|
||||
recipe_notifications = recipe_notifications,
|
||||
})
|
||||
@@ -479,7 +488,7 @@ function inventory_sync.initiate_inventory_download(player, player_record, gener
|
||||
progress_dialog.display(player, 0, 1)
|
||||
end
|
||||
|
||||
function inventory_sync.check_active_downloads()
|
||||
function player_sync.check_active_downloads()
|
||||
-- Used for detecting the inventory download failing, like when the map is saved and
|
||||
-- re-loaded in the middle of a download. The player will be returned to a playable state.
|
||||
local script_data = get_script_data()
|
||||
@@ -503,7 +512,7 @@ function inventory_sync.check_active_downloads()
|
||||
end
|
||||
end
|
||||
|
||||
function inventory_sync.check_active_uploads()
|
||||
function player_sync.check_active_uploads()
|
||||
local script_data = get_script_data()
|
||||
for player_name, record in pairs(script_data.active_uploads) do
|
||||
if record.last_attempt + record.timeout < game.ticks_played then
|
||||
@@ -515,10 +524,10 @@ function inventory_sync.check_active_uploads()
|
||||
log("Retrying upload for " .. player_name)
|
||||
record.last_attempt = game.ticks_played
|
||||
record.timeout = math.random(record.timeout, record.timeout * 2)
|
||||
clusterio_api.send_json("inventory_sync_upload", record.serialized)
|
||||
clusterio_api.send_json("player_sync_upload", record.serialized)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return inventory_sync
|
||||
return player_sync
|
||||
File renamed without changes.
+3
-3
@@ -1,8 +1,8 @@
|
||||
local compat = require("modules/clusterio/compat")
|
||||
local clusterio_serialize = require("modules/clusterio/serialize")
|
||||
local character_inventories = require("modules/inventory_sync/define_player_inventories")
|
||||
local character_stat_keys = require("modules/inventory_sync/define_player_stat_keys")
|
||||
local game_view_settings_keys = require("modules/inventory_sync/define_game_view_settings")
|
||||
local character_inventories = require("modules/player_sync/define_player_inventories")
|
||||
local character_stat_keys = require("modules/player_sync/define_player_stat_keys")
|
||||
local game_view_settings_keys = require("modules/player_sync/define_game_view_settings")
|
||||
local serialize = {}
|
||||
|
||||
local v2_logistic_api = compat.version_ge("2.0.0")
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@clusterio/plugin-inventory_sync",
|
||||
"name": "@clusterio/plugin-player_sync",
|
||||
"version": "2.0.0-alpha.27",
|
||||
"description": "Clusterio plugin forwarding between Factorio servers",
|
||||
"homepage": "https://github.com/clusterio/clusterio#readme",
|
||||
@@ -0,0 +1,53 @@
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
import * as lib from "@clusterio/lib";
|
||||
import * as mock from "../../../test/mock.js";
|
||||
import controllerEntrypoint from "../dist/node/controller.js";
|
||||
import { plugin as info } from "../dist/node/index.js";
|
||||
import { DatabaseStatsRequest } from "../dist/node/messages.js";
|
||||
|
||||
describe("player_sync controller", function() {
|
||||
const databaseDir = path.join("temp", "test", "player_sync");
|
||||
|
||||
before(function() {
|
||||
try {
|
||||
lib.registerPluginMessages([info]);
|
||||
} catch (err) {
|
||||
// Already registered by the full test suite
|
||||
}
|
||||
});
|
||||
beforeEach(async function() {
|
||||
await fs.rm(databaseDir, { recursive: true, force: true });
|
||||
await fs.mkdir(databaseDir, { recursive: true });
|
||||
});
|
||||
|
||||
async function load() {
|
||||
return await mock.loadControllerPlugin(async (context) => {
|
||||
context.controller.mockConfigEntries.set("controller.database_directory", databaseDir);
|
||||
await controllerEntrypoint(context);
|
||||
}, info);
|
||||
}
|
||||
|
||||
it("should move inventories.json to player_sync.json", async function() { // Alpha 28
|
||||
const playerData = { name: "test", generation: 1 };
|
||||
await fs.writeFile(path.join(databaseDir, "inventories.json"), JSON.stringify([["test", playerData]]));
|
||||
|
||||
const controller = await load();
|
||||
const stats = await mock.getHandler(controller, DatabaseStatsRequest)();
|
||||
assert.equal(stats.databaseEntries, 1);
|
||||
assert.equal(stats.largestEntry.name, "test");
|
||||
await assert.rejects(fs.access(path.join(databaseDir, "inventories.json")), { code: "ENOENT" });
|
||||
await fs.access(path.join(databaseDir, "player_sync.json"));
|
||||
});
|
||||
it("should not replace an existing player_sync.json", async function() {
|
||||
await fs.writeFile(path.join(databaseDir, "inventories.json"), JSON.stringify([["old", { name: "old" }]]));
|
||||
await fs.writeFile(path.join(databaseDir, "player_sync.json"), JSON.stringify([["new", { name: "new" }]]));
|
||||
|
||||
const controller = await load();
|
||||
const stats = await mock.getHandler(controller, DatabaseStatsRequest)();
|
||||
assert.equal(stats.databaseEntries, 1);
|
||||
assert.equal(stats.largestEntry.name, "new");
|
||||
});
|
||||
});
|
||||
@@ -19,7 +19,7 @@ async function decode(encoded) {
|
||||
return JSON.parse((await inflate(Buffer.from(encoded, "base64"))).toString());
|
||||
}
|
||||
|
||||
describe("inventory_sync", function() {
|
||||
describe("player_sync", function() {
|
||||
describe("recipeNotificationDelta()", function() {
|
||||
it("should be empty when the lists match", function() {
|
||||
assert.deepEqual(recipeNotificationDelta(["a", "b"], ["b", "a"]), {});
|
||||
@@ -71,7 +71,7 @@ describe("inventory_sync", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("ipc-inventory_sync_download", function() {
|
||||
describe("ipc-player_sync_download", function() {
|
||||
let instance;
|
||||
before(async function() {
|
||||
try {
|
||||
@@ -80,7 +80,7 @@ describe("inventory_sync", function() {
|
||||
// Already registered by the full test suite
|
||||
}
|
||||
({ instance } = await mock.loadInstancePlugin(loadInstancePlugin, info));
|
||||
instance.mockConfigEntries.set("inventory_sync.rcon_chunk_size", 100000);
|
||||
instance.mockConfigEntries.set("player_sync.rcon_chunk_size", 100000);
|
||||
});
|
||||
beforeEach(function() {
|
||||
instance.server.reset();
|
||||
@@ -94,7 +94,7 @@ describe("inventory_sync", function() {
|
||||
});
|
||||
}
|
||||
async function download(request) {
|
||||
instance.server.emit("ipc-inventory_sync_download", request);
|
||||
instance.server.emit("ipc-player_sync_download", request);
|
||||
// zlib runs on the threadpool, so poll on timers rather than setImmediate
|
||||
for (let i = 0; i < 1000 && !instance.server.rconCommands.length; i++) {
|
||||
await lib.wait(1);
|
||||
@@ -102,7 +102,7 @@ describe("inventory_sync", function() {
|
||||
}
|
||||
function downloadCommand(playerData) {
|
||||
const json = lib.escapeString(JSON.stringify(playerData));
|
||||
return `/sc inventory_sync.download_inventory('test','${json}',1,1)`;
|
||||
return `/sc player_sync.download_inventory('test','${json}',1,1)`;
|
||||
}
|
||||
|
||||
it("should send an empty download when the controller has no data", async function() {
|
||||
@@ -110,7 +110,7 @@ describe("inventory_sync", function() {
|
||||
await download({ player_name: "test" });
|
||||
assert.deepEqual(
|
||||
instance.server.rconCommands,
|
||||
["/sc inventory_sync.download_inventory('test',nil,0,0)"]
|
||||
["/sc player_sync.download_inventory('test',nil,0,0)"]
|
||||
);
|
||||
});
|
||||
it("should send everything as a delta when no snapshot was given", async function() {
|
||||
@@ -0,0 +1,46 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import * as lib from "@clusterio/lib";
|
||||
import { Controller } from "@clusterio/controller";
|
||||
|
||||
import { plugin as info } from "../dist/node/index.js";
|
||||
|
||||
describe("player_sync migrations", function() {
|
||||
before(async function() {
|
||||
lib.addPluginConfigFields([info]);
|
||||
const packagePath = fileURLToPath(new URL("../package.json", import.meta.url));
|
||||
await lib.loadPluginMigrations([{ ...info, packagePath }]);
|
||||
});
|
||||
after(function() {
|
||||
for (const hook of [
|
||||
lib.ControllerConfig.pluginMigrations, lib.HostConfig.pluginMigrations,
|
||||
lib.InstanceConfig.pluginMigrations, lib.Role.pluginMigrations,
|
||||
]) {
|
||||
hook.detach(info.name);
|
||||
}
|
||||
});
|
||||
|
||||
it("should rename inventory_sync config fields", function() { // Alpha 28
|
||||
const controllerConfig = lib.ControllerConfig.fromJSON({
|
||||
"inventory_sync.load_plugin": false,
|
||||
"inventory_sync.player_lock_timeout": 120,
|
||||
}, "controller");
|
||||
assert.equal(controllerConfig.get("player_sync.load_plugin"), false);
|
||||
assert.equal(controllerConfig.get("player_sync.player_lock_timeout"), 120);
|
||||
assert.equal(controllerConfig.toJSON()["inventory_sync.player_lock_timeout"], undefined);
|
||||
|
||||
const hostConfig = lib.HostConfig.fromJSON({ "inventory_sync.load_plugin": false }, "host");
|
||||
assert.equal(hostConfig.get("player_sync.load_plugin"), false);
|
||||
|
||||
const instanceConfig = lib.InstanceConfig.fromJSON({
|
||||
"inventory_sync.rcon_chunk_size": 500,
|
||||
}, "controller");
|
||||
assert.equal(instanceConfig.get("player_sync.rcon_chunk_size"), 500);
|
||||
});
|
||||
it("should rename the inventory_sync permission in roles", function() { // Alpha 28
|
||||
const result = Controller.migrateRoles([
|
||||
{ permissions: ["core.admin", "inventory_sync.inventory.view"] },
|
||||
]);
|
||||
assert.deepEqual(result, [{ permissions: ["core.admin", "player_sync.inventory.view"] }]);
|
||||
});
|
||||
});
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -17,8 +17,8 @@ function InventoryPage() {
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return <PageLayout nav={[{ name: "Inventory sync" }]}>
|
||||
<PageHeader title="Inventory sync" />
|
||||
return <PageLayout nav={[{ name: "Player sync" }]}>
|
||||
<PageHeader title="Player sync" />
|
||||
{statsData && <>
|
||||
<p>Database size: {Math.round(statsData.databaseSize / 1000)}kB</p>
|
||||
<p>Database entries: {statsData.databaseEntries}</p>
|
||||
@@ -33,8 +33,8 @@ export default async function(context: WebPluginContext) {
|
||||
control.hooks.pages.attach(plugin.name, () => [
|
||||
{
|
||||
path: "/inventory",
|
||||
sidebarName: "Inventory sync",
|
||||
permission: "inventory_sync.inventory.view",
|
||||
sidebarName: "Player sync",
|
||||
permission: "player_sync.inventory.view",
|
||||
content: <InventoryPage />,
|
||||
},
|
||||
]);
|
||||
File renamed without changes.
@@ -13,8 +13,8 @@ module.exports = (env = {}, argv = {}) => merge(common(env, argv), {
|
||||
},
|
||||
plugins: [
|
||||
new webpack.container.ModuleFederationPlugin({
|
||||
name: "inventory_sync",
|
||||
library: { type: "var", name: "plugin_inventory_sync" },
|
||||
name: "player_sync",
|
||||
library: { type: "var", name: "plugin_player_sync" },
|
||||
exposes: {
|
||||
"./": "./index.ts",
|
||||
"./package.json": "./package.json",
|
||||
@@ -9,13 +9,13 @@ describe("ctl/config_ops", function() {
|
||||
"# Clusterio configuration",
|
||||
"",
|
||||
"instance.name = Example",
|
||||
"inventory_sync.load_plugin = false",
|
||||
"player_sync.load_plugin = false",
|
||||
"",
|
||||
].join(newline);
|
||||
|
||||
assert.deepEqual(await configToKeyVal(data), {
|
||||
"instance.name": "Example",
|
||||
"inventory_sync.load_plugin": "false",
|
||||
"player_sync.load_plugin": "false",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -909,7 +909,7 @@ describe("Integration of Clusterio", function() {
|
||||
describe("instance send-rcon", function() {
|
||||
requiresFactorio(this);
|
||||
const plugins = [
|
||||
"global_chat", "inventory_sync", "player_auth",
|
||||
"global_chat", "player_sync", "player_auth",
|
||||
"research_sync", "statistics_exporter", "subspace_storage",
|
||||
];
|
||||
this.beforeAll(async function() {
|
||||
@@ -1276,7 +1276,7 @@ describe("Integration of Clusterio", function() {
|
||||
await execCtl("instance config set 44 research_sync.load_plugin false");
|
||||
await execCtl("instance config set 44 statistics_exporter.load_plugin false");
|
||||
await execCtl("instance config set 44 subspace_storage.load_plugin false");
|
||||
await execCtl("instance config set 44 inventory_sync.load_plugin false");
|
||||
await execCtl("instance config set 44 player_sync.load_plugin false");
|
||||
await execCtl("instance config set 44 factorio.enable_save_patching false");
|
||||
|
||||
await execCtl("instance start 44");
|
||||
|
||||
@@ -345,7 +345,7 @@ before(async function() {
|
||||
await execCtlProcess("plugin add ../../plugins/research_sync");
|
||||
await execCtlProcess("plugin add ../../plugins/statistics_exporter");
|
||||
await execCtlProcess("plugin add ../../plugins/subspace_storage");
|
||||
await execCtlProcess("plugin add ../../plugins/inventory_sync");
|
||||
await execCtlProcess("plugin add ../../plugins/player_sync");
|
||||
await execCtlProcess("plugin add ../../plugins/player_auth");
|
||||
|
||||
console.log("Bootstrapping");
|
||||
|
||||
@@ -43,7 +43,7 @@ describe("Clusterio Instance", function() {
|
||||
await execCtl(`instance config set ${instName} instance.exclude_from_start_all true`);
|
||||
|
||||
for (const plugin of [
|
||||
"global_chat", "research_sync", "statistics_exporter", "subspace_storage", "player_auth", "inventory_sync",
|
||||
"global_chat", "research_sync", "statistics_exporter", "subspace_storage", "player_auth", "player_sync",
|
||||
]) {
|
||||
await execCtl(`instance config set ${instName} ${plugin}.load_plugin false`);
|
||||
}
|
||||
@@ -255,7 +255,7 @@ describe("start-all and stop-all commands", function() {
|
||||
for (const instanceName of [testInstName1, testInstName2, testInstName3]) {
|
||||
for (const plugin of [
|
||||
"global_chat", "research_sync", "statistics_exporter",
|
||||
"subspace_storage", "player_auth", "inventory_sync",
|
||||
"subspace_storage", "player_auth", "player_sync",
|
||||
]) {
|
||||
await execCtl(`instance config set ${instanceName} ${plugin}.load_plugin false`);
|
||||
}
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
{ "path": "./packages/ctl/" },
|
||||
{ "path": "./packages/web_ui/" },
|
||||
{ "path": "./plugins/global_chat/" },
|
||||
{ "path": "./plugins/inventory_sync/" },
|
||||
{ "path": "./plugins/player_sync/" },
|
||||
{ "path": "./plugins/player_auth/" },
|
||||
{ "path": "./plugins/research_sync/" },
|
||||
{ "path": "./plugins/statistics_exporter/" },
|
||||
|
||||
Reference in new issue
Block a user