From 255cc4c32549ae6f9de74698f5a20adda786f7ef Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Sun, 12 Apr 2026 22:15:24 +0100 Subject: [PATCH 1/7] Fix accessing main inventory when in map view --- plugins/inventory_sync/module/serialize.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/inventory_sync/module/serialize.lua b/plugins/inventory_sync/module/serialize.lua index 28f4fa06..bb7ab439 100644 --- a/plugins/inventory_sync/module/serialize.lua +++ b/plugins/inventory_sync/module/serialize.lua @@ -203,7 +203,7 @@ function serialize.serialize_crafting_queue(player) player.character_inventory_slots_bonus = player.character_inventory_slots_bonus + 1000 -- Save current items - local inventory = player.get_main_inventory() + local inventory = player.character.get_main_inventory() local old_items = inventory.get_contents() local crafting_queue_progress = player.crafting_queue_progress From 2219b58000ba7f299fc3ae501c23492f27747dcf Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:31:26 +0100 Subject: [PATCH 2/7] Fix two missed cases of get_main_inventory --- plugins/inventory_sync/module/inventory_sync.lua | 4 ++-- plugins/inventory_sync/module/serialize.lua | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/inventory_sync/module/inventory_sync.lua b/plugins/inventory_sync/module/inventory_sync.lua index 9805601c..4604e223 100644 --- a/plugins/inventory_sync/module/inventory_sync.lua +++ b/plugins/inventory_sync/module/inventory_sync.lua @@ -116,9 +116,9 @@ function inventory_sync.deserialize_player(player, finished_record) -- Transfer items from stashed inventory if stashed_corpse then - local main = player.get_main_inventory() local stash = stashed_corpse.get_inventory(defines.inventory.character_corpse) - if main then + if player.character then + local main = player.character.get_main_inventory() for i = 1, #stash do -- Try transferring a stack local source = stash[i] diff --git a/plugins/inventory_sync/module/serialize.lua b/plugins/inventory_sync/module/serialize.lua index bb7ab439..64b92e53 100644 --- a/plugins/inventory_sync/module/serialize.lua +++ b/plugins/inventory_sync/module/serialize.lua @@ -317,7 +317,7 @@ function serialize.serialize_crafting_queue(player) end function serialize.deserialize_crafting_queue(player, serialized) - local inventory = player.get_main_inventory() + local inventory = player.character.get_main_inventory() -- Give player some more inventory space to avoid duplicating items player.character_inventory_slots_bonus = player.character_inventory_slots_bonus + 1000 From 037f06808e256f97d68b38f842c95fce3ae6d544 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Wed, 22 Apr 2026 00:45:31 +0100 Subject: [PATCH 3/7] Support remote controller type within deserialize --- plugins/inventory_sync/module/serialize.lua | 82 +++++++++++++-------- 1 file changed, 51 insertions(+), 31 deletions(-) diff --git a/plugins/inventory_sync/module/serialize.lua b/plugins/inventory_sync/module/serialize.lua index 64b92e53..e67e8ade 100644 --- a/plugins/inventory_sync/module/serialize.lua +++ b/plugins/inventory_sync/module/serialize.lua @@ -510,6 +510,26 @@ function serialize.serialize_player(player, failed_deserialization) return serialized end +--- Ensure a player has a character, works from any controller type +--- @param player LuaPlayer +--- @return LuaEntity +local function ensure_character(player) + -- Do nothing if the player has a valid character + local character = player.character + if character and character.valid then + return character + end + + -- Switch to god controller if create_character would fail + if player.controller_type ~= defines.controllers.god then + player.set_controller{ type = defines.controllers.god } + end + + -- Create and return the character + assert(player.create_character(), "Failed to create character") + return player.character +end + --- @class FailedDeserializationPlayerData --- @field recipe_notifications string[]? @@ -519,45 +539,45 @@ end function serialize.deserialize_player(player, serialized) local failed_deserialization = {} - if player.controller_type ~= defines.controllers[serialized.controller] or serialized.controller == "ghost" then - -- If targeting the character or ghost controller then create a character - if serialized.controller == "character" or serialized.controller == "ghost" then - if player.controller_type == defines.controllers.ghost or player.controller_type == defines.controllers.spectator then - player.set_controller({ type = defines.controllers.god }) - end - if player.controller_type == defines.controllers.god then - player.create_character() + local target_controller = defines.controllers[serialized.controller] + if player.controller_type ~= target_controller or serialized.controller == "ghost" then + if serialized.controller == "character" or serialized.controller == "remote" then + -- Character and Remote both require a character, but must not destroy an existing one + ensure_character(player) + if player.controller_type ~= target_controller then + player.set_controller{ type = target_controller } end - -- The ghost state stores hidden logistic and filters which are only accessible in the character controller - if serialized.controller == "ghost" then + elseif serialized.controller == "ghost" then + -- Ghost state stores hidden logistic and filters which are only accessible in the character controller + local character = ensure_character(player) + if serialized.personal_logistic_slots then serialize.deserialize_personal_logistic_slots(player, serialized.personal_logistic_slots) + end + if serialized.inventories then serialize.deserialize_inventories(player, serialized.inventories, character_inventories) - local character = player.character - if serialized.ticks_to_respawn then - player.ticks_to_respawn = serialized.ticks_to_respawn - else - -- We have to set ticks to respawn to save the hidden state into the player but we - -- can't unset tick_to_respawn by setting it back to nil as that triggers a respawn. - player.ticks_to_respawn = 0 - player.set_controller({ type = defines.controllers.god }) - player.set_controller({ type = defines.controllers.ghost }) - end - if character and character.valid then - character.destroy() - end + end + if serialized.ticks_to_respawn then + player.ticks_to_respawn = serialized.ticks_to_respawn + else + -- We have to set ticks to respawn to save the hidden state into the player but we + -- can't unset tick_to_respawn by setting it back to nil as that triggers a respawn. + player.ticks_to_respawn = 0 + player.set_controller{ type = defines.controllers.god } + player.set_controller{ type = defines.controllers.ghost } + end + if character and character.valid then + character.destroy() end else - -- Targeting the god or spectator controller, if coming from the - -- character controller then destroy the character - if player.controller_type == defines.controllers.character then - player.character.destroy() + -- All other controllers should not have a character + local character = player.character + if character and character.valid then + character.destroy() end - - -- Switching to god or spectator is a matter of setting the controller - if player.controller_type ~= defines.controllers[serialized.controller] then - player.set_controller({ type = defines.controllers[serialized.controller] }) + if player.controller_type ~= target_controller then + player.set_controller{ type = target_controller } end end end From add00887042c4f792de62335f1ef3d29bb23d0c7 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Wed, 22 Apr 2026 00:49:45 +0100 Subject: [PATCH 4/7] Switch to xpcall for better trace backs --- plugins/inventory_sync/module/inventory_sync.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/inventory_sync/module/inventory_sync.lua b/plugins/inventory_sync/module/inventory_sync.lua index 4604e223..440152d2 100644 --- a/plugins/inventory_sync/module/inventory_sync.lua +++ b/plugins/inventory_sync/module/inventory_sync.lua @@ -312,7 +312,7 @@ function inventory_sync.sync_player(acquire_response) end function inventory_sync.finish_download(player, finished_record) - local status, result = pcall(inventory_sync.deserialize_player, player, finished_record) + local status, result = xpcall(inventory_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) @@ -382,7 +382,7 @@ inventory_sync.events[defines.events.on_pre_player_left_game] = function(event) end player_record.generation = player_record.generation + 1 - local status, result = pcall(inventory_sync.serialize_player, player, player_record) + local status, result = xpcall(inventory_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) From 8be95c140af29e5d433c9aca9ca0842a7b12fc23 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Wed, 22 Apr 2026 22:37:42 +0100 Subject: [PATCH 5/7] Fix edge cases with physical location and controller --- .../inventory_sync/module/inventory_sync.lua | 16 ++++++++++-- .../module/restore_position.lua | 7 +++++ plugins/inventory_sync/module/serialize.lua | 26 ++++++++++++++++--- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/plugins/inventory_sync/module/inventory_sync.lua b/plugins/inventory_sync/module/inventory_sync.lua index 440152d2..cf4305b5 100644 --- a/plugins/inventory_sync/module/inventory_sync.lua +++ b/plugins/inventory_sync/module/inventory_sync.lua @@ -16,6 +16,8 @@ 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 v2_remote_controller = compat.version_ge("2.0.0") + -- Returns true if the player is currently in a cutscene local function is_in_cutscene(player) return player.controller_type == defines.controllers.cutscene @@ -114,6 +116,11 @@ function inventory_sync.deserialize_player(player, finished_record) -- Restore player position and driving state restore_position(player, finished_record) + -- Return to remote if needed (restore position may exit remote view) + if serialized_player.remote then + player.set_controller{ type = defines.controllers.remote } + end + -- Transfer items from stashed inventory if stashed_corpse then local stash = stashed_corpse.get_inventory(defines.inventory.character_corpse) @@ -440,8 +447,13 @@ function inventory_sync.initiate_inventory_download(player, player_record, gener -- player data is downloading if player_record.sync then -- Store original position to teleport back to - record.surface = player.surface - record.position = player.position + if v2_remote_controller then + record.surface = player.physical_surface + record.position = player.physical_position + else + record.surface = player.surface + record.position = player.position + end if player.driving then record.vehicle = player.vehicle end diff --git a/plugins/inventory_sync/module/restore_position.lua b/plugins/inventory_sync/module/restore_position.lua index 5192bafa..c0cf479b 100644 --- a/plugins/inventory_sync/module/restore_position.lua +++ b/plugins/inventory_sync/module/restore_position.lua @@ -1,9 +1,14 @@ +local compat = require("modules/clusterio/compat") + local can_enter_vehicle = { [defines.controllers.character] = true, [defines.controllers.god] = true, [defines.controllers.editor] = true, + [defines.controllers.remote] = true, } +local v2_surface_platform = compat.version_ge("2.0.0") + --- @param player LuaPlayer --- @param record table return function(player, record) @@ -20,6 +25,8 @@ return function(player, record) player.teleport(safe_position, player.surface) end end + elseif v2_surface_platform and record.surface and record.surface.platform and can_enter_vehicle[player.controller_type] then + player.enter_space_platform(record.surface.platform) elseif record.surface and record.position then player.teleport(record.position, record.surface) end diff --git a/plugins/inventory_sync/module/serialize.lua b/plugins/inventory_sync/module/serialize.lua index e67e8ade..e7b7db63 100644 --- a/plugins/inventory_sync/module/serialize.lua +++ b/plugins/inventory_sync/module/serialize.lua @@ -6,6 +6,7 @@ local serialize = {} 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") function serialize.serialize_inventories(source, inventories) @@ -428,6 +429,7 @@ end --- @field generation number --- @field name string --- @field controller string +--- @field remote boolean --- @field color Color --- @field chat_color Color --- @field tag string @@ -450,6 +452,7 @@ function serialize.serialize_player(player, failed_deserialization) local serialized = { generation = 0, -- Gets replaced later controller = controller_to_name[player.controller_type], + remote = false, name = player.name, color = player.color, chat_color = player.chat_color, @@ -460,6 +463,11 @@ function serialize.serialize_player(player, failed_deserialization) ticks_to_respawn = player.ticks_to_respawn, } + if v2_remote_controller then + serialized.remote = player.controller_type == defines.controllers.remote + serialized.controller = controller_to_name[player.physical_controller_type] + end + -- For the waiting to respawn state the inventory logistic requests and filters are hidden on the player if player.controller_type == defines.controllers.ghost and player.ticks_to_respawn then player.ticks_to_respawn = nil -- Respawn now @@ -482,7 +490,9 @@ function serialize.serialize_player(player, failed_deserialization) end -- Serialize non-character inventories - if player.controller_type == defines.controllers.god then + if player.controller_type == defines.controllers.god + or v2_remote_controller and player.physical_controller_type == defines.controllers.god + then serialized.inventories = serialize.serialize_inventories(player, { main = defines.inventory.god_main }) end @@ -541,8 +551,8 @@ function serialize.deserialize_player(player, serialized) local target_controller = defines.controllers[serialized.controller] if player.controller_type ~= target_controller or serialized.controller == "ghost" then - if serialized.controller == "character" or serialized.controller == "remote" then - -- Character and Remote both require a character, but must not destroy an existing one + if serialized.controller == "character" then + -- Create a character but do not destroy an existing one ensure_character(player) if player.controller_type ~= target_controller then player.set_controller{ type = target_controller } @@ -570,7 +580,7 @@ function serialize.deserialize_player(player, serialized) character.destroy() end - else + elseif serialized.controller ~= "remote" then -- All other controllers should not have a character local character = player.character if character and character.valid then @@ -579,6 +589,9 @@ function serialize.deserialize_player(player, serialized) if player.controller_type ~= target_controller then player.set_controller{ type = target_controller } end + + else + error("Remote should not be 'serialized.controller'") end end @@ -624,6 +637,11 @@ function serialize.deserialize_player(player, serialized) serialize.deserialize_crafting_notifications(player, serialized.recipe_notifications) end + -- Return to remote view after physical controller has been synced + if serialized.remote then + player.set_controller{ type = defines.controllers.remote } + end + return next(failed_deserialization) and failed_deserialization or nil end From 41f8f493aa34f6c0994bea84126f025f9559c1df Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Thu, 23 Apr 2026 00:19:20 +0100 Subject: [PATCH 6/7] Fix nil table key error in factorio 1.1 --- plugins/inventory_sync/module/restore_position.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/inventory_sync/module/restore_position.lua b/plugins/inventory_sync/module/restore_position.lua index c0cf479b..f2513133 100644 --- a/plugins/inventory_sync/module/restore_position.lua +++ b/plugins/inventory_sync/module/restore_position.lua @@ -4,9 +4,13 @@ local can_enter_vehicle = { [defines.controllers.character] = true, [defines.controllers.god] = true, [defines.controllers.editor] = true, - [defines.controllers.remote] = true, } +if defines.controllers.remote then + -- Does not exist in 1.1, so we need this nil check + can_enter_vehicle[defines.controllers.remote] = true +end + local v2_surface_platform = compat.version_ge("2.0.0") --- @param player LuaPlayer From 1af672aa1df458031b6227a924262be47ba5f1ac Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Thu, 23 Apr 2026 17:25:13 +0100 Subject: [PATCH 7/7] Always exit remote view on sync --- plugins/inventory_sync/module/inventory_sync.lua | 5 ----- plugins/inventory_sync/module/serialize.lua | 9 +-------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/plugins/inventory_sync/module/inventory_sync.lua b/plugins/inventory_sync/module/inventory_sync.lua index cf4305b5..ca452d4c 100644 --- a/plugins/inventory_sync/module/inventory_sync.lua +++ b/plugins/inventory_sync/module/inventory_sync.lua @@ -116,11 +116,6 @@ function inventory_sync.deserialize_player(player, finished_record) -- Restore player position and driving state restore_position(player, finished_record) - -- Return to remote if needed (restore position may exit remote view) - if serialized_player.remote then - player.set_controller{ type = defines.controllers.remote } - end - -- Transfer items from stashed inventory if stashed_corpse then local stash = stashed_corpse.get_inventory(defines.inventory.character_corpse) diff --git a/plugins/inventory_sync/module/serialize.lua b/plugins/inventory_sync/module/serialize.lua index e7b7db63..567289d0 100644 --- a/plugins/inventory_sync/module/serialize.lua +++ b/plugins/inventory_sync/module/serialize.lua @@ -429,7 +429,6 @@ end --- @field generation number --- @field name string --- @field controller string ---- @field remote boolean --- @field color Color --- @field chat_color Color --- @field tag string @@ -452,7 +451,6 @@ function serialize.serialize_player(player, failed_deserialization) local serialized = { generation = 0, -- Gets replaced later controller = controller_to_name[player.controller_type], - remote = false, name = player.name, color = player.color, chat_color = player.chat_color, @@ -463,8 +461,8 @@ function serialize.serialize_player(player, failed_deserialization) ticks_to_respawn = player.ticks_to_respawn, } + -- In 2.0 we want to sync the physical controller to ignore remote view if v2_remote_controller then - serialized.remote = player.controller_type == defines.controllers.remote serialized.controller = controller_to_name[player.physical_controller_type] end @@ -637,11 +635,6 @@ function serialize.deserialize_player(player, serialized) serialize.deserialize_crafting_notifications(player, serialized.recipe_notifications) end - -- Return to remote view after physical controller has been synced - if serialized.remote then - player.set_controller{ type = defines.controllers.remote } - end - return next(failed_deserialization) and failed_deserialization or nil end