mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-14 23:24:52 +09:00
Place synced items from a list of stacks instead of script inventories
place_items no longer creates script inventories for what does not fit. Each serialized inventory is split at the size of its destination: the slots that fit are restored slot for slot and the rest become serialized stacks, which are inserted into the free space of the same inventory and then main, guns, ammo and armor. What is still left over is returned as a list and spill_items puts it into a corpse sized to hold it. This keeps 1.0 out of reach of game.create_inventory. The armor-first fill order is gone, as the bonus slots from armor equipment only apply on the next tick anyway (#1106). Filters are no longer stripped for destinations, they are the same inventory type the data came from. deserialize_inventory concatenated the filter table in its error print on 2.0, which errored out the whole restore when an inventory did not support filters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
923ba10d2a
commit
a1a57b8c97
2 files changed
+125
-120
No files matched your search
@@ -81,36 +81,88 @@ function serialize.deserialize_character_grid(character, serialized)
|
||||
end
|
||||
end
|
||||
|
||||
-- Inventories by the names used in serialized data in the order they are filled, armor first as it adds slots to main
|
||||
local fill_order = { "armor", "guns", "ammo", "main", "trash" }
|
||||
-- Inventories items which do not fit into the inventory they came from are inserted into, in order
|
||||
local overflow_order = { "main", "guns", "ammo", "armor" }
|
||||
|
||||
--- Number of slots a serialized inventory uses
|
||||
--- @param serialized table
|
||||
--- @return number
|
||||
local function serialized_size(serialized)
|
||||
local last = 0
|
||||
for _, entry in ipairs(serialized.i) do
|
||||
last = (entry.s or last + 1) + (entry.r or 0)
|
||||
--- Copy of a serialized item entry without the slot index, repeat count and filter
|
||||
--- @param entry table
|
||||
--- @return table
|
||||
local function stack_entry(entry)
|
||||
local stack = {}
|
||||
for key, value in pairs(entry) do
|
||||
stack[key] = value
|
||||
end
|
||||
return last
|
||||
stack.s = nil
|
||||
stack.r = nil
|
||||
stack.f = nil
|
||||
return stack
|
||||
end
|
||||
|
||||
--- Copy of a serialized inventory without slot filters, for inventories which do not support them
|
||||
--- Split a serialized inventory into the slots which fit into an inventory of size and the stacks of those which do not
|
||||
--- @param serialized table
|
||||
--- @return table
|
||||
local function without_filters(serialized)
|
||||
local copy = { i = {} }
|
||||
--- @param size number
|
||||
--- @return table fitting, table[] rest
|
||||
local function split_inventory(serialized, size)
|
||||
local fitting = { b = serialized.b, i = {} }
|
||||
local rest = {}
|
||||
local last = 0
|
||||
for _, entry in ipairs(serialized.i) do
|
||||
local item = {}
|
||||
for key, value in pairs(entry) do
|
||||
item[key] = value
|
||||
local first = entry.s or last + 1
|
||||
last = first + (entry.r or 0)
|
||||
if last <= size then
|
||||
table.insert(fitting.i, entry)
|
||||
else
|
||||
if first <= size then
|
||||
local part = stack_entry(entry)
|
||||
part.s = first
|
||||
part.r = size > first and size - first or nil
|
||||
part.f = entry.f
|
||||
table.insert(fitting.i, part)
|
||||
end
|
||||
if entry.n or entry.e then
|
||||
for _ = math.max(first, size + 1), last do
|
||||
table.insert(rest, stack_entry(entry))
|
||||
end
|
||||
end
|
||||
end
|
||||
item.f = nil
|
||||
table.insert(copy.i, item)
|
||||
end
|
||||
return copy
|
||||
return fitting, rest
|
||||
end
|
||||
|
||||
local stack_definition_keys = { n = true, c = true, q = true, h = true, p = true, d = true, a = true }
|
||||
|
||||
--- Insert as much of a serialized stack as possible into an inventory, reducing its count by what was inserted
|
||||
--- @param inventory LuaInventory
|
||||
--- @param stack table
|
||||
--- @return boolean placed true if all of it was inserted
|
||||
local function insert_stack(inventory, stack)
|
||||
local plain = true
|
||||
for key in pairs(stack) do
|
||||
plain = plain and stack_definition_keys[key] or false
|
||||
end
|
||||
|
||||
-- Stacks without labels, grids, inventories or export strings can be merged with what is in the inventory
|
||||
if plain then
|
||||
local inserted = inventory.insert({
|
||||
name = stack.n, count = stack.c, quality = stack.q, health = stack.h,
|
||||
spoil_percent = stack.p, durability = stack.d, ammo = stack.a,
|
||||
})
|
||||
stack.c = stack.c - inserted
|
||||
return stack.c <= 0
|
||||
end
|
||||
|
||||
-- Export strings have no name, and 1.1 does not take nil for it
|
||||
local slot
|
||||
if stack.n then
|
||||
slot = inventory.find_empty_stack(stack.n)
|
||||
else
|
||||
slot = inventory.find_empty_stack()
|
||||
end
|
||||
if not slot or stack.n and not slot.can_set_stack({ name = stack.n, count = stack.c, quality = stack.q }) then
|
||||
return false
|
||||
end
|
||||
clusterio_serialize.deserialize_item_stack(slot, stack)
|
||||
return slot.valid_for_read
|
||||
end
|
||||
|
||||
--- Inventories the items of a player go into, by the names used in serialized data
|
||||
@@ -131,131 +183,84 @@ function serialize.item_destinations(player)
|
||||
return destinations
|
||||
end
|
||||
|
||||
--- Insert as much of a stack as possible, starting with the inventory it came from
|
||||
--- @param stack LuaItemStack
|
||||
--- @param destinations table<string, LuaInventory>
|
||||
--- @param from string?
|
||||
local function insert_stack(stack, destinations, from)
|
||||
local function insert(destination)
|
||||
if destination and stack.valid_for_read then
|
||||
local inserted = destination.insert(stack)
|
||||
if inserted >= stack.count then
|
||||
stack.clear()
|
||||
elseif inserted > 0 then
|
||||
stack.count = stack.count - inserted
|
||||
end
|
||||
end
|
||||
end
|
||||
if from then
|
||||
insert(destinations[from])
|
||||
end
|
||||
for _, name in ipairs(overflow_order) do
|
||||
insert(destinations[name])
|
||||
end
|
||||
end
|
||||
|
||||
--- Replace the items in destinations with the serialized inventories and loose items
|
||||
---
|
||||
--- Each inventory goes slot for slot into the destination of the same name when it fits, otherwise its items are
|
||||
--- inserted where there is space. Returns a script inventory with what did not fit, to be put into a corpse.
|
||||
--- Each inventory goes slot for slot into the destination of the same name as far as it fits, the slots past its
|
||||
--- size are inserted where there is space. Returns the serialized stacks which did not fit, see spill_items.
|
||||
--- @param destinations table<string, LuaInventory>
|
||||
--- @param inventories table<string, table>?
|
||||
--- @param items { name: string, count: integer, quality: string? }[]?
|
||||
--- @return LuaInventory?
|
||||
--- @return table[] leftovers
|
||||
function serialize.place_items(destinations, inventories, items)
|
||||
for _, destination in pairs(destinations) do
|
||||
destination.clear()
|
||||
end
|
||||
|
||||
local leftovers = {}
|
||||
for _, name in ipairs(fill_order) do
|
||||
local serialized = inventories and inventories[name]
|
||||
local size = serialized and serialized_size(serialized) or 0
|
||||
-- Every inventory is restored before inserting into them, as restoring a slot replaces what is in it
|
||||
local pending = {}
|
||||
for name, serialized in pairs(inventories or {}) do
|
||||
local destination = destinations[name]
|
||||
if serialized and destination and size <= #destination then
|
||||
if not destination.supports_filters() then
|
||||
serialized = without_filters(serialized)
|
||||
end
|
||||
clusterio_serialize.deserialize_inventory(destination, serialized)
|
||||
elseif serialized and size > 0 then
|
||||
-- Slot filters belong to the slot they were set on
|
||||
local inventory = game.create_inventory(size)
|
||||
clusterio_serialize.deserialize_inventory(inventory, without_filters(serialized))
|
||||
table.insert(leftovers, { from = name, inventory = inventory })
|
||||
local fitting, rest = split_inventory(serialized, destination and #destination or 0)
|
||||
if destination then
|
||||
clusterio_serialize.deserialize_inventory(destination, fitting)
|
||||
end
|
||||
for _, stack in ipairs(rest) do
|
||||
table.insert(pending, { from = destination, stack = stack })
|
||||
end
|
||||
end
|
||||
for _, item in pairs(items or {}) do
|
||||
table.insert(pending, { stack = { n = item.name, c = item.count, q = item.quality } })
|
||||
end
|
||||
|
||||
if items and next(items) then
|
||||
local inventory = game.create_inventory(#items)
|
||||
for _, item in pairs(items) do
|
||||
local remaining = item.count
|
||||
while remaining > 0 do
|
||||
local inserted = inventory.insert({ name = item.name, count = remaining, quality = item.quality })
|
||||
remaining = remaining - inserted
|
||||
if remaining > 0 then
|
||||
if inserted == 0 and not inventory.is_full() then
|
||||
log("ERROR: Unable to place " .. remaining .. " " .. item.name .. ", voiding it")
|
||||
break
|
||||
end
|
||||
inventory.resize(#inventory * 2)
|
||||
end
|
||||
end
|
||||
local leftovers = {}
|
||||
for _, entry in ipairs(pending) do
|
||||
local stack = entry.stack
|
||||
local placed = entry.from and insert_stack(entry.from, stack)
|
||||
for _, name in ipairs(overflow_order) do
|
||||
placed = placed or destinations[name] and insert_stack(destinations[name], stack)
|
||||
end
|
||||
table.insert(leftovers, { inventory = inventory })
|
||||
end
|
||||
|
||||
local spill
|
||||
local spilled = 0
|
||||
for _, leftover in ipairs(leftovers) do
|
||||
local inventory = leftover.inventory
|
||||
for i = 1, #inventory do
|
||||
local stack = inventory[i]
|
||||
if stack.valid_for_read then
|
||||
insert_stack(stack, destinations, leftover.from)
|
||||
end
|
||||
if stack.valid_for_read then
|
||||
spill = spill or game.create_inventory(1)
|
||||
if spilled == #spill then
|
||||
spill.resize(#spill * 2)
|
||||
end
|
||||
spilled = spilled + 1
|
||||
spill[spilled].transfer_stack(stack)
|
||||
end
|
||||
if not placed then
|
||||
table.insert(leftovers, stack)
|
||||
end
|
||||
inventory.destroy()
|
||||
end
|
||||
return spill
|
||||
return leftovers
|
||||
end
|
||||
|
||||
--- Put items which did not fit into a corpse below the player
|
||||
--- Put serialized stacks which did not fit into a corpse below the player
|
||||
--- @param player LuaPlayer
|
||||
--- @param spill LuaInventory?
|
||||
function serialize.spill_items(player, spill)
|
||||
if not spill then
|
||||
--- @param stacks table[]?
|
||||
function serialize.spill_items(player, stacks)
|
||||
if not stacks or not next(stacks) then
|
||||
return
|
||||
end
|
||||
spill.sort_and_merge()
|
||||
local count = #spill - spill.count_empty_stacks()
|
||||
|
||||
-- Plain stacks can hold more than a stack's worth
|
||||
local slots = 0
|
||||
for _, stack in ipairs(stacks) do
|
||||
local prototype = stack.n and compat.prototypes.item[stack.n]
|
||||
slots = slots + (prototype and math.ceil((stack.c or 1) / prototype.stack_size) or 1)
|
||||
end
|
||||
|
||||
local corpse = player.surface.create_entity({
|
||||
name = "character-corpse",
|
||||
position = player.position,
|
||||
inventory_size = count,
|
||||
inventory_size = slots,
|
||||
player_index = player.index,
|
||||
})
|
||||
if corpse then
|
||||
local inventory = assert(corpse.get_inventory(defines.inventory.character_corpse))
|
||||
for i = 1, #spill do
|
||||
if spill[i].valid_for_read then
|
||||
assert(inventory.find_empty_stack()).transfer_stack(spill[i])
|
||||
end
|
||||
end
|
||||
player.print(
|
||||
"Some of your synced items do not fit into your inventory here and have been placed in a corpse below you."
|
||||
)
|
||||
else
|
||||
log("ERROR: Unable to create a corpse for " .. player.name .. ", voiding " .. count .. " stacks of items")
|
||||
if not corpse then
|
||||
log("ERROR: Unable to create a corpse for " .. player.name .. ", voiding " .. #stacks .. " stacks of items")
|
||||
return
|
||||
end
|
||||
spill.destroy()
|
||||
|
||||
local inventory = assert(corpse.get_inventory(defines.inventory.character_corpse))
|
||||
for _, stack in ipairs(stacks) do
|
||||
if not insert_stack(inventory, stack) then
|
||||
log("ERROR: Unable to place " .. (stack.n or "an exported item") .. " into the corpse of " .. player.name)
|
||||
end
|
||||
end
|
||||
player.print(
|
||||
"Some of your synced items do not fit into your inventory here and have been placed in a corpse below you."
|
||||
)
|
||||
end
|
||||
|
||||
-- Personal logistic slots is a table mapping string indexes to a table with the following fields:
|
||||
@@ -1173,7 +1178,7 @@ end
|
||||
--- @param serialized SerializedPlayerData
|
||||
--- @param components SyncComponents
|
||||
--- @param local_controller LocalControllerState? Controller to restore when the controller is not synced
|
||||
--- @return FailedDeserializationPlayerData?, LuaInventory? Items which did not fit, see spill_items
|
||||
--- @return FailedDeserializationPlayerData?, table[]? Serialized stacks which did not fit, see spill_items
|
||||
function serialize.deserialize_player(player, serialized, components, local_controller)
|
||||
local failed_deserialization = {}
|
||||
local sync_controller = syncs(components, "controller")
|
||||
|
||||
Reference in new issue
Block a user