From 4775a5fd06b7e679c8a37cc2208cffc6ae19f481 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Mon, 29 Jun 2026 01:33:22 +0100 Subject: [PATCH 1/3] Add `encode_string` and `decode_string` to compat --- packages/host/modules/clusterio/compat.lua | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/host/modules/clusterio/compat.lua b/packages/host/modules/clusterio/compat.lua index 96cd444a..cafee324 100644 --- a/packages/host/modules/clusterio/compat.lua +++ b/packages/host/modules/clusterio/compat.lua @@ -17,6 +17,8 @@ local lib_json = require("json") --- @field prototypes LuaPrototypes --- @field active_mods table --- @field write_file fun(filename: string, data: LocalisedString, append: boolean?, for_player: uint?) +--- @field encode_string fun(string: string): string +--- @field decode_string fun(string: string): string --- @field table_to_json fun(data: table): string --- @field json_to_table fun(json: string): AnyBasic? local compat = {} @@ -177,6 +179,24 @@ function runtime_properties.write_file() end end +--- Deflates and base64 encodes the given string. +function runtime_properties.encode_string() + if (v0_18 and compat.version_ge("0.18.7")) or v1 then + return game.encode_string + elseif v2 then + return helpers.encode_string + end +end + +--- Base64 decodes and inflates the given string. +function runtime_properties.decode_string() + if (v0_18 and compat.version_ge("0.18.7")) or v1 then + return game.decode_string + elseif v2 then + return helpers.decode_string + end +end + --- Convert tables to and from json, we have backported this to work before runtime if v0 or v1 then compat.table_to_json = function(tbl) From 46ebb13fdae151d741bfce10e57430551b309455 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Mon, 29 Jun 2026 02:17:03 +0100 Subject: [PATCH 2/3] Update quick bar to support 2.1 and quality --- plugins/inventory_sync/module/serialize.lua | 186 ++++++++++++++++++-- 1 file changed, 168 insertions(+), 18 deletions(-) diff --git a/plugins/inventory_sync/module/serialize.lua b/plugins/inventory_sync/module/serialize.lua index 567289d0..2aa7f9a2 100644 --- a/plugins/inventory_sync/module/serialize.lua +++ b/plugins/inventory_sync/module/serialize.lua @@ -8,6 +8,8 @@ local v2_logistic_api = compat.version_ge("2.0.0") local v2_storage_api = compat.version_ge("2.0.0") local v2_remote_controller = compat.version_ge("2.0.0") local recipe_notifications_api = compat.version_ge("2.0.67") +local v2_0_quick_bar_api = compat.version_ge("2.0.0") +local v2_1_quick_bar_api = compat.version_ge("2.1.0") function serialize.serialize_inventories(source, inventories) local serialized = {} @@ -194,6 +196,167 @@ function serialize.deserialize_personal_logistic_slots(player, serialized) end end +-- name is a custom type where quality is "normal" and comparator is "=" (most common case) +--- @alias QuickBarSlotEncoded.name { t: "n", n: string } +--- @alias QuickBarSlotEncoded.filter { t: "f", n: string, q: string, c: string } +--- @alias QuickBarSlotEncoded QuickBarSlotEncoded.name | QuickBarSlotEncoded.filter + +--- @param filter ItemFilter +--- @return QuickBarSlotEncoded +local function serialize_filter(filter) + if filter.quality == "normal" and filter.comparator == "=" then + return { t = "n", n = filter.name } + end + + return { + t = "f", + n = filter.name, + q = filter.quality, + c = filter.comparator, + } +end + +--- @param slot QuickBarSlot | ItemFilter | LuaItemPrototype +--- @return QuickBarSlotEncoded | nil +function serialize.serialize_quick_bar_slot(slot) + if v2_1_quick_bar_api then + -- 2.1 has a dedicated type for quick bar slots + --- @cast slot QuickBarSlot + if slot.type == "filter" then + return serialize_filter(slot.filter) + end + + print("Warning: Unsupported quick bar slot type '" .. slot.type .. "'") + return nil + end + + if v2_0_quick_bar_api then + -- 2.0 gives us an item filter which supports quality + --- @cast slot ItemFilter + return serialize_filter(slot) + end + + -- pre 2.0 quality did not exist + --- @cast slot LuaItemPrototype + return { + t = "n", + n = slot.name + } +end + +--- @param entry string | QuickBarSlotEncoded +--- @return QuickBarSlot | ItemWithQualityID | nil +function serialize.deserialize_quick_bar_slot(entry) + if type(entry) == "string" then + -- Migration, old data was just the name, so convert to QuickBarSlotEncoded.name + entry = { t = "n", n = entry } + end + + if v2_1_quick_bar_api then + -- Return a quick bar slot + if entry.t == "n" then + return { + type = "filter", + filter = { + name = entry.n, + quality = "normal", + comparator = "=", + }, + } + end + + if entry.t == "f" then + return { + type = "filter", + filter = { + name = entry.n, + quality = entry.q, + comparator = entry.c, + }, + } + end + + print("Warning: Unsupported serialized quick bar slot type '" .. tostring(entry.t) .. "'") + return nil + end + + if v2_0_quick_bar_api then + -- Return a ItemIDAndQualityIDPair (member of ItemWithQualityID) + if entry.t == "n" then + return { + name = entry.n, + quality = "normal", + } + end + + -- entry.t == "f" is only remaining case for 2.0 + return { + name = entry.n, + quality = entry.q, + } + end + + -- Return a string (member of ItemPrototypeIdentification) + -- entry.t == "n" is the only case for pre 2.0 + return entry.n +end + +--- @param player LuaPlayer +--- @return table? +function serialize.serialize_quick_bar(player) + local serialized + local width = v2_1_quick_bar_api and player.quick_bar_width + + for i = 1, 100 do + local slot + + if v2_1_quick_bar_api then + local page = math.floor((i - 1) / width) + 1 + local page_slot = ((i - 1) % width) + 1 + slot = player.get_quick_bar_slot(page, page_slot) + else + --- @diagnostic disable-next-line: missing-parameter + slot = player.get_quick_bar_slot(i) + end + + if slot ~= nil then + local entry = serialize.serialize_quick_bar_slot(slot) + if entry ~= nil then + serialized = serialized or {} + serialized[tostring(i)] = entry + end + end + end + + return serialized +end + +--- @param player LuaPlayer +--- @param serialized table? +function serialize.deserialize_quick_bar(player, serialized) + if not serialized then + return + end + + local width = v2_1_quick_bar_api and player.quick_bar_width + for i = 1, 100 do + local entry = serialized[tostring(i)] + if entry ~= nil then + local slot = serialize.deserialize_quick_bar_slot(entry) + if slot ~= nil then + if v2_1_quick_bar_api then + local page = math.floor((i - 1) / width) + 1 + local page_slot = ((i - 1) % width) + 1 + player.set_quick_bar_slot(page, page_slot, slot) + else + --- @diagnostic disable-next-line: param-type-mismatch + player.set_quick_bar_slot(i, slot) + end + end + end + end +end + -- Crafting queue is a table with the following fields: -- crafting_queue -- ingredients @@ -439,6 +602,7 @@ end --- @field character table? --- @field inventories table? --- @field hotbar table? +--- @field quick_bar table? --- @field personal_logistic_slots table? --- @field crafting_queue table? --- @field recipe_notifications string? @@ -494,16 +658,8 @@ function serialize.serialize_player(player, failed_deserialization) serialized.inventories = serialize.serialize_inventories(player, { main = defines.inventory.god_main }) end - -- Serialize hotbar - for i = 1, 100 do - local slot = player.get_quick_bar_slot(i) - if slot ~= nil and slot.name ~= nil then - if not serialized.hotbar then - serialized.hotbar = {} - end - serialized.hotbar[tostring(i)] = slot.name --[[@as string]] - end - end + -- Serialize quick bar + serialized.quick_bar = serialize.serialize_quick_bar(player) -- Serialize crafting queue if player.character then @@ -615,14 +771,8 @@ function serialize.deserialize_player(player, serialized) serialize.deserialize_inventories(player, serialized.inventories, { main = defines.inventory.god_main }) end - -- Deserialize hotbar - if serialized.hotbar then - for i = 1, 100 do - if serialized.hotbar[tostring(i)] ~= nil then - player.set_quick_bar_slot(i, serialized.hotbar[tostring(i)]) - end - end - end + -- Deserialize quick bar (named hotbar in old data) + serialize.deserialize_quick_bar(player, serialized.quick_bar or serialized.hotbar) -- Deserialize crafting queue if player.character and serialized.crafting_queue then From 2559c0478c113fabf779523ae927d56e4f6deb85 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Fri, 10 Jul 2026 20:59:06 +0100 Subject: [PATCH 3/3] Use item name only for better size usage --- plugins/inventory_sync/module/serialize.lua | 26 +++++++-------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/plugins/inventory_sync/module/serialize.lua b/plugins/inventory_sync/module/serialize.lua index 2aa7f9a2..93e11343 100644 --- a/plugins/inventory_sync/module/serialize.lua +++ b/plugins/inventory_sync/module/serialize.lua @@ -197,7 +197,7 @@ function serialize.deserialize_personal_logistic_slots(player, serialized) end -- name is a custom type where quality is "normal" and comparator is "=" (most common case) ---- @alias QuickBarSlotEncoded.name { t: "n", n: string } +--- @alias QuickBarSlotEncoded.name string --- @alias QuickBarSlotEncoded.filter { t: "f", n: string, q: string, c: string } --- @alias QuickBarSlotEncoded QuickBarSlotEncoded.name | QuickBarSlotEncoded.filter @@ -205,7 +205,7 @@ end --- @return QuickBarSlotEncoded local function serialize_filter(filter) if filter.quality == "normal" and filter.comparator == "=" then - return { t = "n", n = filter.name } + return filter.name end return { @@ -238,27 +238,19 @@ function serialize.serialize_quick_bar_slot(slot) -- pre 2.0 quality did not exist --- @cast slot LuaItemPrototype - return { - t = "n", - n = slot.name - } + return slot.name end --- @param entry string | QuickBarSlotEncoded --- @return QuickBarSlot | ItemWithQualityID | nil function serialize.deserialize_quick_bar_slot(entry) - if type(entry) == "string" then - -- Migration, old data was just the name, so convert to QuickBarSlotEncoded.name - entry = { t = "n", n = entry } - end - if v2_1_quick_bar_api then -- Return a quick bar slot - if entry.t == "n" then + if type(entry) == "string" then return { type = "filter", filter = { - name = entry.n, + name = entry, quality = "normal", comparator = "=", }, @@ -282,9 +274,9 @@ function serialize.deserialize_quick_bar_slot(entry) if v2_0_quick_bar_api then -- Return a ItemIDAndQualityIDPair (member of ItemWithQualityID) - if entry.t == "n" then + if type(entry) == "string" then return { - name = entry.n, + name = entry, quality = "normal", } end @@ -297,8 +289,8 @@ function serialize.deserialize_quick_bar_slot(entry) end -- Return a string (member of ItemPrototypeIdentification) - -- entry.t == "n" is the only case for pre 2.0 - return entry.n + -- Pre 2.0 this was the only method of encoding used + return entry end --- @param player LuaPlayer