Files
factorio-clusterio/packages/host/modules/clusterio/serialize.lua
T

290 lines
8.4 KiB
Lua

-- Library for serializing items in Factorio
-- Based on code from playerManager and trainTeleports
local compat = require("modules/clusterio/compat")
local serialize = {}
-- 0.17 compatibility
local supports_bar, get_bar, set_bar
if compat.version_ge("1.0.0") then
supports_bar = "supports_bar"
get_bar = "get_bar"
set_bar = "set_bar"
else
supports_bar = "hasbar"
get_bar = "getbar"
set_bar = "setbar"
end
local has_create_grid = compat.version_ge("1.1.7")
local has_quality = compat.version_ge("2.0.0")
-- Equipment Grids are serialized into an array of equipment entries
-- where ench entry is a table with the following fields:
-- n: name
-- p: position (array of 2 numbers corresponding to x and y)
-- s: shield (optional)
-- e: energy (optional)
-- If the equipment is a burner the following is also present:
-- i: burner inventory
-- r: result inventory
-- b: currently burning (optional)
-- f: remaining_burning_fuel (optional)
function serialize.serialize_equipment_grid(grid)
local serialized = {}
local processed = {}
for y = 0, grid.height - 1 do
for x = 0, grid.width - 1 do
local equipment = grid.get({x, y})
if equipment ~= nil then
local pos = equipment.position
local combined_pos = pos.x + pos.y * grid.width + 1
if not processed[combined_pos] then
processed[combined_pos] = true
local entry = {
n = equipment.name,
p = {pos.x, pos.y},
}
if equipment.shield > 0 then entry.s = equipment.shield end
if equipment.energy > 0 then entry.e = equipment.energy end
if equipment.burner then
local burner = equipment.burner
entry.i = serialize.serialize_inventory(burner.inventory)
entry.r = serialize.serialize_inventory(burner.burnt_result_inventory)
if burner.currently_burning then
entry.b = burner.currently_burning.name
entry.f = burner.remaining_burning_fuel
end
end
table.insert(serialized, entry)
end
end
end
end
return serialized
end
function serialize.deserialize_equipment_grid(grid, serialized)
grid.clear()
for _, entry in ipairs(serialized) do
local equipment = grid.put({
name = entry.n,
position = entry.p,
})
if equipment then
if entry.s then equipment.shield = entry.s end
if entry.e then equipment.energy = entry.e end
if entry.i then
local burner = equipment.burner
if entry.b then burner.currently_burning = entry.b end
if entry.f then burner.remaining_burning_fuel = entry.f end
serialize.deserialize_inventory(burner.burnt_result_inventory, entry.r)
serialize.deserialize_inventory(burner.inventory, entry.i)
end
end
end
end
-- Item stacks are serialized into a table with the following fields:
-- n: name
-- c: count
-- q: quality (optional)
-- h: health (optional)
-- d: durability (optional)
-- a: ammo count (optional)
-- l: label (optional)
-- g: equipment grid (optional)
-- i: item inventory (optional)
-- If the item stack is exportable it has the following property instead
-- e: export string
-- Label is a table with the following fields:
-- t: label text (optional)
-- c: color (optional)
-- a: allow manual label change
function serialize.serialize_item_stack(slot, entry)
if
slot.is_blueprint
or slot.is_blueprint_book
or slot.is_upgrade_item
or slot.is_deconstruction_item
or slot.is_item_with_tags
then
local call_success, call_return = pcall(slot.export_stack)
if not call_success then
print("Error: '" .. call_return .. "' thrown exporting '" .. slot.name .. "'")
else
entry.e = call_return
end
return
end
entry.n = slot.name
entry.c = slot.count
if has_quality and slot.quality.level ~= 0 then entry.q = slot.quality.name end
if slot.health < 1 then entry.h = slot.health end
if slot.type == "tool" and slot.durability then entry.d = slot.durability end
if slot.type == "ammo" then entry.a = slot.ammo end
if slot.is_item_with_label then
local label = {}
if slot.label then label.t = slot.label end
if slot.label_color then label.c = slot.label_color end
label.a = slot.allow_manual_label_change
entry.l = label
end
if slot.grid then
entry.g = serialize.serialize_equipment_grid(slot.grid)
end
if slot.is_item_with_inventory then
local sub_inventory = slot.get_inventory(defines.inventory.item_main)
entry.i = serialize.serialize_inventory(sub_inventory)
end
end
function serialize.deserialize_item_stack(slot, entry)
if entry.e then
local success = slot.import_stack(entry.e)
if success == 1 then
print("Error: import of '" .. entry.e .. "' succeeded with errors")
elseif success == -1 then
print("Error: import of '" .. entry.e .. "' failed")
end
return
end
local item_stack = {
name = entry.n,
count = entry.c,
}
if entry.q then item_stack.quality = entry.q end
if entry.h then item_stack.health = entry.h end
if entry.d then item_stack.durability = entry.d end
if entry.a then item_stack.ammo = entry.a end
local call_success, call_return = pcall(slot.set_stack, item_stack)
if not call_success then
print("Error: '" .. call_return .. "' thrown setting stack ".. serpent.line(entry))
elseif not call_return then
print("Error: Failed to set stack " .. serpent.line(entry))
else
if entry.l then
-- TODO test this with AAI's unit-remote-control
local label = entry.l
if label.t then slot.label = label.t end
if label.c then slot.label_color = label.c end
slot.allow_manual_label_change = label.a
end
if entry.g then
if slot.grid then
serialize.deserialize_equipment_grid(slot.grid, entry.g)
elseif slot.type == "item-with-entity-data" and has_create_grid then
slot.create_grid()
serialize.deserialize_equipment_grid(slot.grid, entry.g)
else
print("Error: Attempt to deserialize equipment grid on an unsupported entity")
end
end
if entry.i then
local sub_inventory = slot.get_inventory(defines.inventory.item_main)
serialize.deserialize_inventory(sub_inventory, entry.i)
end
end
end
-- Inventories are serialized into a table with the following fields:
-- i: array of item stack or exportable item entries
-- b: bar position (optional)
-- Each item entry has the following fields
-- s: index (optional, equals to previous plus one if not present)
-- r: repeat count (optional)
-- f: slot filter (optional)
-- Pluss all the fields for item stacks (see deserialize_item_stack)
-- It's also possible that the slot is empty but has a slot filter.
function serialize.serialize_inventory(inventory)
local serialized = {}
if inventory[supports_bar]() and inventory[get_bar]() <= #inventory then
serialized.b = inventory[get_bar]()
end
serialized.i = {}
local previous_index = 0
local previous_serialized = nil
for i = 1, #inventory do
local item = {}
local slot = inventory[i]
if inventory.supports_filters() then
item.f = inventory.get_filter(i)
end
if slot.valid_for_read then
serialize.serialize_item_stack(slot, item)
end
if item.n or item.f or item.e then
local item_serialized = compat.table_to_json(item)
if item_serialized == previous_serialized then
local previous_item = serialized.i[#serialized.i]
previous_item.r = (previous_item.r or 0) + 1
previous_index = i
else
if i ~= previous_index + 1 then
item.s = i
end
previous_index = i
previous_serialized = item_serialized
table.insert(serialized.i, item)
end
else
-- Either an empty slot or serilization failed
previous_index = 0
previous_serialized = nil
end
end
return serialized
end
function serialize.deserialize_inventory(inventory, serialized)
if serialized.b and inventory[supports_bar]() then
inventory[set_bar](serialized.b)
end
local last_slot_index = 0
for _, entry in ipairs(serialized.i) do
local base_index = entry.s or last_slot_index + 1
local repeat_count = entry.r or 0
for offset = 0, repeat_count do
local index = base_index + offset
if index > #inventory then
print("Error: Inventory index out of bounds deserializing inventory, voiding excess slots")
break
end
local slot = inventory[index]
if entry.f then
local call_success, call_return = pcall(inventory.set_filter, index, entry.f)
if not call_success then
print("Error: '" .. call_return .. "' thrown setting filter " .. entry.f)
elseif not call_return then
print("Error: Failed to set filter " .. entry.f)
end
end
if entry.n or entry.e then
serialize.deserialize_item_stack(slot, entry)
end
end
last_slot_index = base_index + repeat_count
end
end
return serialize