From 3f79cdb1a65adbf41711683da784465753fb24e8 Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Tue, 20 Oct 2026 12:28:08 +1100 Subject: [PATCH] Download when a dropped prefetched copy already took the body A copy confirmed at an earlier join has replaced the body with a spectator. If a later join drops it (another controller run) while the generations match, the join kept the local state: a spectator with no body, uploaded on the next leave. Download instead. A newer copy replacing a held one keeps where the body stood, so the replacement still counts as having taken it. Co-Authored-By: Claude Opus 5.5 (1M context) --- plugins/player_sync/module/player_sync.lua | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/plugins/player_sync/module/player_sync.lua b/plugins/player_sync/module/player_sync.lua index 35ffbe85..4a02932c 100644 --- a/plugins/player_sync/module/player_sync.lua +++ b/plugins/player_sync/module/player_sync.lua @@ -369,8 +369,12 @@ function player_sync.sync_player(acquire_response) -- synced here now. The generation alone isn't enough: a controller that crashed before saving it restarts at the -- last saved generation, and another instance's upload can then reuse the same number with other data. Otherwise -- the copy goes, and the join carries on as if it had never come: today's download or failure dialog, never the - -- restart path below, which only sees a different generation. A copy this save doesn't need goes too, keeping the - -- local data: an upload from an instance with older data can take the generation this save already holds. + -- restart path below, which only sees a different generation or epoch. A copy this save doesn't need goes too, + -- keeping the local data: an upload from an instance with older data can take the generation this save already + -- holds. + -- A copy confirmed at an earlier join already took the body (spectate keeps where it stood), and the local data + -- went with it, so a join that drops that copy downloads. + local body_taken = false for _, downloads in ipairs({ script_data.finished_downloads, script_data.active_downloads }) do local record = downloads[acquire_response.player_name] if record and record.prefetch and (not ( @@ -378,6 +382,7 @@ function player_sync.sync_player(acquire_response) and (record.epoch or 0) == epoch and record.token == acquire_response.token and serialize.same_components(record.components, script_data.components) ) or not download_needed(script_data.players[acquire_response.player_name], acquire_response, script_data)) then + body_taken = body_taken or record.position ~= nil downloads[acquire_response.player_name] = nil end end @@ -472,7 +477,7 @@ function player_sync.sync_player(acquire_response) end local stale = (player_record.epoch or 0) ~= epoch - if download_needed(player_record, acquire_response, script_data) then + if body_taken or download_needed(player_record, acquire_response, script_data) then player_sync.initiate_inventory_download(player, player_record, acquire_response.generation, epoch) else @@ -746,6 +751,9 @@ function player_sync.prefetch_inventory(player_name, generation, data, number, t rcon.print("Refused prefetch for " .. player_name .. ": generation " .. generation .. " is not newer") return end + -- A held copy (or offline-finished download) whose join took the body knows where it stood, and that the body + -- is gone (sync_player's body_taken): the copy replacing it keeps both + local held = active or finished script_data.finished_downloads[player_name] = nil script_data.active_downloads[player_name] = { started = game.ticks_played, @@ -756,6 +764,9 @@ function player_sync.prefetch_inventory(player_name, generation, data, number, t token = token, epoch = epoch, components = components and compat.json_to_table(components), + surface = held and held.surface, + position = held and held.position, + vehicle = held and held.vehicle, } end