Merge pull request #1106 from ILLISIS/ps-armor-grid-bonus

Keep main inventory slots from armor equipment when restoring a character
This commit is contained in:
Cooldude2606 authored and GitHub committed 2026-10-18 20:14:02 +01:00
commit 4fd1d56b4a
3 files changed
+93 -6

No files matched your search

@@ -18,6 +18,7 @@ return function(no_early_return)
player_sync.players_waiting_for_acquire = player_sync.players_waiting_for_acquire or {}
player_sync.players_in_cutscene_to_sync = player_sync.players_in_cutscene_to_sync or {}
player_sync.failed_deserialization = player_sync.failed_deserialization or {}
player_sync.inventory_bonus_raises = player_sync.inventory_bonus_raises or {}
player_sync.active_downloads = player_sync.active_downloads or {}
player_sync.finished_downloads = player_sync.finished_downloads or {}
+37 -1
View File
@@ -60,6 +60,19 @@ local function remove_player(player)
script_data.players[player.name] = nil
end
-- Remove the temporary inventory bonus a deserialized character got to fit its main inventory
local function remove_inventory_bonus_raise(script_data, player_name)
local raises = script_data.inventory_bonus_raises
local raise = raises and raises[player_name]
if raise then
raises[player_name] = nil
local character = raise.character
if character.valid then
character.character_inventory_slots_bonus = character.character_inventory_slots_bonus - raise.bonus
end
end
end
player_sync = {}
-- This function is called by instance.js as /sc player_sync.download_inventory("danielv", Escaped JSON string, package_number, total_packages_count) with data from the controller
@@ -100,6 +113,15 @@ function player_sync.serialize_player(player, player_record)
local serialized_player = serialize.serialize_player(player, failed_deserialization, player_record.components)
serialized_player.generation = player_record.generation
-- Leaving on the tick of a download the temporary bonus is still there and must not be uploaded. Removing it
-- from the character here would shrink the main inventory before the armor's bonus applies and spill items.
local raises = get_script_data().inventory_bonus_raises
local raise = raises and raises[player.name]
local character = serialized_player.character
if raise and character and raise.character == player.character then
character.character_inventory_slots_bonus = character.character_inventory_slots_bonus - raise.bonus
end
return serialized_player
end
@@ -134,10 +156,18 @@ function player_sync.deserialize_player(player, finished_record)
-- Deserialize downloaded player data
local serialized_player = compat.json_to_table(finished_record.data)
assert(type(serialized_player) == "table", "wrong type for serialized_player")
local failed, spill =
-- An earlier raise not removed yet is replaced, the character's inventory bonus is restored with the data
script_data.inventory_bonus_raises[player.name] = nil
local failed, spill, inventory_bonus_raise =
serialize.deserialize_player(player, serialized_player, finished_record.components, local_controller)
script_data.failed_deserialization[player.name] = failed
player_record.local_controller = nil
if inventory_bonus_raise and inventory_bonus_raise > 0 then
script_data.inventory_bonus_raises[player.name] = {
character = player.character,
bonus = inventory_bonus_raise,
}
end
-- Restore player position and driving state
restore_position(player, finished_record)
@@ -396,6 +426,12 @@ player_sync.events[defines.events.on_player_joined_game] = function(event)
script_data.active_uploads[player.name] = nil
end
-- Raised for a restored armor after its equipment's inventory bonus applied, the temporary bonus is no longer needed
player_sync.events[defines.events.on_player_armor_inventory_changed] = function(event)
local player = assert(game.get_player(event.player_index))
remove_inventory_bonus_raise(get_script_data(), player.name)
end
player_sync.on_nth_tick = {}
player_sync.on_nth_tick[33] = function(event)
-- Periodically check players currently in the process of syncing
+55 -5
View File
@@ -10,6 +10,7 @@ local v2_storage_api = compat.version_ge("2.0.0")
local v2_remote_controller = compat.version_ge("2.0.0")
local v2_space_platform = compat.version_ge("2.0.0")
local v2_exit_remote_view = compat.version_ge("2.0.56")
local v2_inventory_bonus_equipment = 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")
@@ -73,6 +74,30 @@ function serialize.deserialize_character(character, serialized)
end
end
--- Index of the last slot used by a serialized inventory
local function last_slot_index(serialized)
local last = 0
for _, entry in ipairs(serialized.i) do
last = (entry.s or last + 1) + (entry.r or 0)
end
return last
end
--- Raise the inventory bonus of a character with its armor restored by the bonus of the armor's equipment which is
--- still to come, as far as a serialized main inventory needs it
--- @return integer raise Slots added to character_inventory_slots_bonus, removed again once the armor's bonus applies
local function raise_inventory_bonus(character, main)
local grid = character.grid --[[@as LuaEquipmentGrid]]
local equipment_bonus = 0
for _, equipment in pairs(grid.equipment) do
equipment_bonus = equipment_bonus + equipment.inventory_bonus
end
local delayed = equipment_bonus - grid.inventory_bonus
local raise = math.max(0, math.min(last_slot_index(main) - #character.get_main_inventory(), delayed))
character.character_inventory_slots_bonus = character.character_inventory_slots_bonus + raise
return raise
end
--- Restore the armor grid state, the grid exists after the armor inventory is restored
function serialize.deserialize_character_grid(character, serialized)
local grid = character.grid
@@ -190,15 +215,18 @@ end
--- @param destinations table<string, LuaInventory>
--- @param inventories table<string, table>?
--- @param items { name: string, count: integer, quality: string? }[]?
--- @param after_armor fun()? Called once the armor is restored, before the other inventories
--- @return table[] leftovers
function serialize.place_items(destinations, inventories, items)
function serialize.place_items(destinations, inventories, items, after_armor)
for _, destination in pairs(destinations) do
destination.clear()
end
-- Every inventory is restored before inserting into them, as restoring a slot replaces what is in it
-- Every inventory is restored before inserting into them, as restoring a slot replaces what is in it.
-- The armor first, its inventory bonus changes the size of the main inventory.
inventories = inventories or {}
local pending = {}
for name, serialized in pairs(inventories or {}) do
local function restore(name, serialized)
local destination = destinations[name]
local fitting, rest = split_inventory(serialized, destination and #destination or 0)
if destination then
@@ -208,6 +236,17 @@ function serialize.place_items(destinations, inventories, items)
table.insert(pending, { from = destination, stack = stack })
end
end
if inventories.armor then
restore("armor", inventories.armor)
end
if after_armor then
after_armor()
end
for name, serialized in pairs(inventories) do
if name ~= "armor" then
restore(name, serialized)
end
end
for _, item in pairs(items or {}) do
table.insert(pending, { stack = { n = item.name, c = item.count, q = item.quality } })
end
@@ -1226,6 +1265,7 @@ end
--- @param components SyncComponents
--- @param local_controller LocalControllerState? Controller to restore when the controller is not synced
--- @return FailedDeserializationPlayerData?, table[]? Serialized stacks which did not fit, see spill_items
--- @return integer? inventory_bonus_raise See raise_inventory_bonus
function serialize.deserialize_player(player, serialized, components, local_controller)
local failed_deserialization = {}
local sync_controller = syncs(components, "controller")
@@ -1347,14 +1387,24 @@ function serialize.deserialize_player(player, serialized, components, local_cont
-- Deserialize items, a ghost got them while it had a character above
local character = player.character
local inventory_bonus_raise
if place and not to_ghost then
local stats = character and items.character
if stats then
serialize.deserialize_character(character, stats)
end
local craft = character and queue
-- The bonus of the armor's equipment (toolbelts) only applies at the end of the tick, so once the armor is
-- restored the main inventory is raised by the part of that bonus still to come. Slots past it overflow and
-- are left over as without the raise. Only for armor with a grid, restoring it raises
-- on_player_armor_inventory_changed which removes the raise.
spill = serialize.place_items(
serialize.item_destinations(player), inventories, not craft and queue and queue.ingredients or nil
serialize.item_destinations(player), inventories, not craft and queue and queue.ingredients or nil,
function()
if v2_inventory_bonus_equipment and stats and inventories.main and character and character.grid then
inventory_bonus_raise = raise_inventory_bonus(character, inventories.main)
end
end
)
if stats then
serialize.deserialize_character_grid(character, stats)
@@ -1378,7 +1428,7 @@ function serialize.deserialize_player(player, serialized, components, local_cont
serialize.deserialize_crafting_notifications(player, serialized.recipe_notifications)
end
return next(failed_deserialization) and failed_deserialization or nil, spill
return next(failed_deserialization) and failed_deserialization or nil, spill, inventory_bonus_raise
end
return serialize