mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-14 07:24:52 +09:00
Keep main inventory slots from armor equipment when restoring a character
The inventory bonus of equipment in the armor's grid (toolbelts) is applied by the engine on the tick after the armor is inserted. The main inventory was restored in the same tick, so the items of every slot past 80 plus research plus the armor's own bonus did not fit and were put in a corpse: legendary mech armor with legendary toolbelts left 2125 of 2330 slots in the corpse. The armor is now restored first, the character's inventory bonus is raised to fit the saved main inventory, and the raise is removed again on the next tick, or before an upload if the player leaves first. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
43243eab86
commit
b32f5416a1
3 files changed
+84
-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 {}
|
||||
|
||||
@@ -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
|
||||
@@ -73,6 +86,9 @@ end
|
||||
-- This function is called internally when a player leaves the game to
|
||||
-- serialize the player for upload.
|
||||
function player_sync.serialize_player(player, player_record)
|
||||
-- Leaving on the tick of a download, the temporary bonus must not be uploaded
|
||||
remove_inventory_bonus_raise(get_script_data(), player.name)
|
||||
|
||||
-- Editor mode is not supported.
|
||||
if player.controller_type == defines.controllers.editor then
|
||||
player.toggle_map_editor()
|
||||
@@ -134,10 +150,17 @@ 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 =
|
||||
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,
|
||||
tick = game.tick,
|
||||
}
|
||||
end
|
||||
|
||||
-- Restore player position and driving state
|
||||
restore_position(player, finished_record)
|
||||
@@ -396,6 +419,20 @@ player_sync.events[defines.events.on_player_joined_game] = function(event)
|
||||
script_data.active_uploads[player.name] = nil
|
||||
end
|
||||
|
||||
-- The inventory bonus of armor equipment applies on the tick after a download, the temporary bonus is no longer needed
|
||||
player_sync.events[defines.events.on_tick] = function(event)
|
||||
local script_data = get_script_data()
|
||||
local raises = script_data.inventory_bonus_raises
|
||||
if not raises or next(raises) == nil then
|
||||
return
|
||||
end
|
||||
for player_name, raise in pairs(raises) do
|
||||
if raise.tick < event.tick then
|
||||
remove_inventory_bonus_raise(script_data, player_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
player_sync.on_nth_tick = {}
|
||||
player_sync.on_nth_tick[33] = function(event)
|
||||
-- Periodically check players currently in the process of syncing
|
||||
|
||||
@@ -73,6 +73,23 @@ 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 to fit a serialized main inventory
|
||||
--- @return integer raise Slots added to character_inventory_slots_bonus, to be removed again on the next tick
|
||||
local function raise_inventory_bonus(character, main)
|
||||
local raise = math.max(0, last_slot_index(main) - #character.get_main_inventory())
|
||||
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 +207,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 +228,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 +1257,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 +1379,22 @@ 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 on the next tick, so once the armor is
|
||||
-- restored the main inventory is raised to fit the data
|
||||
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 stats and inventories.main then
|
||||
inventory_bonus_raise = raise_inventory_bonus(character, inventories.main)
|
||||
end
|
||||
end
|
||||
)
|
||||
if stats then
|
||||
serialize.deserialize_character_grid(character, stats)
|
||||
@@ -1378,7 +1418,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
|
||||
Reference in new issue
Block a user