From c798b2a33fb8711ed2e4dabed52421371ec1e0e1 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:52:07 +0000 Subject: [PATCH] Keep the local controller intact when it is not synced With the controller off, a god or ghost player was still turned into a spectator while downloading, which threw away the local state the sync is meant to leave alone. Only spectate a player without a character when the controller is synced, and skip the ghost round trip when the ghost is the local state. A ghost on an instance that syncs inventories but not the controller never received the stored character, so after respawning it uploaded an empty one over it. Respawn such a ghost straight away when there is a stored character to give it. Clear the saved local controller when the player picks a temporary inventory after a failed download, otherwise a later download restores the stale controller over the temporary character. An empty download only gives a character when the controller is synced or the player was spectated. Co-Authored-By: Claude Opus 5.5 --- plugins/player_sync/README.md | 1 + plugins/player_sync/module/download_inventory.lua | 2 +- plugins/player_sync/module/gui/dialog_failed_download.lua | 1 + plugins/player_sync/module/player_sync.lua | 3 ++- plugins/player_sync/module/serialize.lua | 8 +++++++- 5 files changed, 12 insertions(+), 3 deletions(-) diff --git a/plugins/player_sync/README.md b/plugins/player_sync/README.md index 47532e2f..d0e7ff9e 100644 --- a/plugins/player_sync/README.md +++ b/plugins/player_sync/README.md @@ -104,6 +104,7 @@ Defaults to `true`. Sync the character and god inventories, the character bonuses and the crafting queue. While the controller is not synced only the inventory of a character is synced, a player without a character keeps the inventory they have on the instance and does not change the stored one. +A player waiting to respawn is respawned straight away if there is a synced character to give them. Defaults to `true`. diff --git a/plugins/player_sync/module/download_inventory.lua b/plugins/player_sync/module/download_inventory.lua index e1d06e12..120e346a 100644 --- a/plugins/player_sync/module/download_inventory.lua +++ b/plugins/player_sync/module/download_inventory.lua @@ -34,7 +34,7 @@ local function download_inventory(player_name, data, number, total) progress_dialog.remove(player) -- Give the player a character and pretend that's the synced player data - if serialize.syncs(script_data.components, "inventories") then + if serialize.syncs(script_data.components, "controller") or player_record.local_controller then ensure_character(player) end player_record.local_controller = nil diff --git a/plugins/player_sync/module/gui/dialog_failed_download.lua b/plugins/player_sync/module/gui/dialog_failed_download.lua index 2f369833..4859e40b 100644 --- a/plugins/player_sync/module/gui/dialog_failed_download.lua +++ b/plugins/player_sync/module/gui/dialog_failed_download.lua @@ -131,6 +131,7 @@ dialog_failed_download.events = { player.gui.screen.dialog_failed_download.destroy() player_sync.players[player.name].dirty = true player_sync.players[player.name].sync = false + player_sync.players[player.name].local_controller = nil elseif event.element.name == "player_sync_failed_download_retry" then -- Retry acquiring the player from the controller diff --git a/plugins/player_sync/module/player_sync.lua b/plugins/player_sync/module/player_sync.lua index 46d72a72..4f3b381b 100644 --- a/plugins/player_sync/module/player_sync.lua +++ b/plugins/player_sync/module/player_sync.lua @@ -487,7 +487,8 @@ function player_sync.initiate_inventory_download(player, player_record, generati -- If this is a synced player turn them into a spectator while the -- player data is downloading - if player_record.sync and syncs("inventories") then + -- Without the controller synced a player without a character has nothing synced to protect + if player_record.sync and syncs("inventories") and (syncs("controller") or player.character) then -- Store original position to teleport back to if v2_remote_controller then record.surface = player.physical_surface diff --git a/plugins/player_sync/module/serialize.lua b/plugins/player_sync/module/serialize.lua index 6bd310b7..b37ce71f 100644 --- a/plugins/player_sync/module/serialize.lua +++ b/plugins/player_sync/module/serialize.lua @@ -987,10 +987,16 @@ function serialize.deserialize_player(player, serialized, components, local_cont local state = local_controller if restore_controller then state = { controller = serialized.controller, ticks_to_respawn = serialized.ticks_to_respawn } + elseif state and state.controller == "ghost" and sync_inventories and serialized.character then + -- Respawn now, a ghost would drop the synced character and upload an empty one after respawning + state = { controller = "character" } end local target_controller = state and defines.controllers[state.controller] - if state and (player.controller_type ~= target_controller or state.controller == "ghost") then + if state and ( + player.controller_type ~= target_controller + or restore_controller and state.controller == "ghost" + ) then if state.controller == "character" then -- Create a character but do not destroy an existing one ensure_character(player)