diff --git a/plugins/player_sync/module/player_sync.lua b/plugins/player_sync/module/player_sync.lua index b2df31b6..58c6a64c 100644 --- a/plugins/player_sync/module/player_sync.lua +++ b/plugins/player_sync/module/player_sync.lua @@ -634,6 +634,7 @@ player_sync.on_nth_tick[33] = function(event) player_sync.check_players_in_cutscene() player_sync.check_active_downloads() player_sync.check_active_uploads() + player_sync.check_missing_quick_bar_records() end player_sync.events[defines.events.on_cutscene_waypoint_reached] = function(event) @@ -781,6 +782,20 @@ function player_sync.check_active_downloads() end end +-- Quick bar slots pointing into the blueprint library are set once the save has the player's library +function player_sync.check_missing_quick_bar_records() + local script_data = get_script_data() + for player_name, failed in pairs(script_data.failed_deserialization) do + local player = failed.quick_bar and game.get_player(player_name) + if player and player.connected then + failed.quick_bar = serialize.restore_missing_quick_bar_records(player, failed.quick_bar) + if not next(failed) then + script_data.failed_deserialization[player_name] = nil + end + end + end +end + function player_sync.check_active_uploads() local script_data = get_script_data() for player_name, record in pairs(script_data.active_uploads) do diff --git a/plugins/player_sync/module/serialize.lua b/plugins/player_sync/module/serialize.lua index ee082e85..bf5fffda 100644 --- a/plugins/player_sync/module/serialize.lua +++ b/plugins/player_sync/module/serialize.lua @@ -494,6 +494,27 @@ local function find_item_path(inventory, item_number) return nil end +--- The record a quick bar slot entry points at, if the player's library has it at that path +--- @param player LuaPlayer +--- @param entry QuickBarSlotEncoded.record +--- @return LuaRecord? +local function find_record(player, entry) + -- The record must still be what was stored, a shifted library must not set the wrong one + local records = player.blueprints + local record --[[@as LuaRecord?]] + for _, index in ipairs(entry.p) do + record = records[index] + if not (record and record.valid) then + return nil + end + records = record.type == "blueprint-book" and record.contents or {} + end + if record and record.type == entry.y and (record.label or "") == (entry.l or "") then + return record + end + return nil +end + --- @param filter ItemFilter --- @return QuickBarSlotEncoded local function serialize_filter(filter) @@ -611,18 +632,8 @@ function serialize.deserialize_quick_bar_slot(entry, player) end if entry.t == "r" then - -- The record must still be what was stored, a shifted library must not set the wrong one - local records = player.blueprints - local record --[[@as LuaRecord?]] - for _, index in ipairs(entry.p) do - record = records[index] - if not (record and record.valid) then - record = nil - break - end - records = record.type == "blueprint-book" and record.contents or {} - end - if record and record.valid and record.type == entry.y and (record.label or "") == (entry.l or "") then + local record = find_record(player, entry) + if record then return { type = "record", record = record } --[[@as QuickBarSlot]] end @@ -670,8 +681,9 @@ local function quick_bar_slot_count(player) end --- @param player LuaPlayer +--- @param missing table? Entries deserialize_quick_bar could not find the record of --- @return table? -function serialize.serialize_quick_bar(player) +function serialize.serialize_quick_bar(player, missing) local serialized local width = v2_1_quick_bar_api and player.quick_bar_width @@ -696,19 +708,63 @@ function serialize.serialize_quick_bar(player) end end + -- A record still missing here is stored again as it came, unless the player put something else in its slot + for index, entry in pairs(missing or {}) do + serialized = serialized or {} + serialized[index] = serialized[index] or entry + end + return serialized end +--- Slot index to page and slot on it +--- @param player LuaPlayer +--- @param i integer +local function quick_bar_location(player, i) + local width = player.quick_bar_width + return math.floor((i - 1) / width) + 1, ((i - 1) % width) + 1 +end + +--- Set the slots whose records were not in the player's library when the data was applied. A save gets the library +--- of a player from the client some seconds after the join, later than the data of a player new to it is applied. +--- @param player LuaPlayer +--- @param missing table +--- @return table? missing Entries whose record is still not there +function serialize.restore_missing_quick_bar_records(player, missing) + for index, entry in pairs(missing) do + local page, page_slot = quick_bar_location(player, tonumber(index) --[[@as integer]]) + if player.get_quick_bar_slot(page, page_slot) ~= nil then + -- The player put something else there + missing[index] = nil + else + local record = find_record(player, entry) + if record then + player.set_quick_bar_slot(page, page_slot, { type = "record", record = record }) + missing[index] = nil + end + end + end + return next(missing) and missing or nil +end + --- @param player LuaPlayer --- @param serialized table? +--- @return table? missing Entries whose record is not in the player's library function serialize.deserialize_quick_bar(player, serialized) -- Every slot is set, and the ones not in the data are cleared, so a slot moved or cleared on the -- previous server does not come back from this server's old copy serialized = serialized or {} local width = v2_1_quick_bar_api and player.quick_bar_width + local missing for i = 1, quick_bar_slot_count(player) do local entry = serialized[tostring(i)] local slot = entry ~= nil and serialize.deserialize_quick_bar_slot(entry, player) or nil + if slot == nil and v2_1_quick_bar_api and type(entry) == "table" and entry.t == "r" then + -- ponytail: kept until the record shows up or the slot is used for something else, so the entry of a + -- record deleted from the library is carried along. Drop them by age if that ever matters. + missing = missing or {} + missing[tostring(i)] = entry + end if v2_1_quick_bar_api then local page = math.floor((i - 1) / width) + 1 local page_slot = ((i - 1) % width) + 1 @@ -718,6 +774,7 @@ function serialize.deserialize_quick_bar(player, serialized) player.set_quick_bar_slot(i, slot) end end + return missing end --- Quick bar page shown on each quick bar row of the screen, kept by every save on its own @@ -1212,7 +1269,7 @@ function serialize.serialize_player(player, failed_deserialization, components) -- Serialize quick bar if syncs(components, "quick_bar") then - serialized.quick_bar = serialize.serialize_quick_bar(player) + serialized.quick_bar = serialize.serialize_quick_bar(player, failed_deserialization.quick_bar) serialized.quick_bar_pages = serialize.serialize_quick_bar_pages(player) end @@ -1333,6 +1390,7 @@ end --- @class FailedDeserializationPlayerData --- @field recipe_notifications string[]? +--- @field quick_bar table? Slots whose record is not in the library (yet) --- @param player LuaPlayer --- @param serialized SerializedPlayerData @@ -1495,7 +1553,9 @@ function serialize.deserialize_player(player, serialized, components, local_cont -- Deserialize quick bar (named hotbar in old data) if syncs(components, "quick_bar") then - serialize.deserialize_quick_bar(player, serialized.quick_bar or serialized.hotbar) + failed_deserialization.quick_bar = serialize.deserialize_quick_bar( + player, serialized.quick_bar or serialized.hotbar + ) serialize.deserialize_quick_bar_pages(player, serialized.quick_bar_pages) end