492 lines
17 KiB
Lua
492 lines
17 KiB
Lua
--[[ Copyright (c) 2020 robot256 (MIT License)
|
|
* Project: Robot256's Library
|
|
* File: save_restore.lua
|
|
* Description: Functions for converting between inventory, burner, and grid objects and native Lua table structures.
|
|
* Functions handle items in arrays of SimpleItemStack tables, with the extra "data" field to store blueprints etc.
|
|
* Functions include nil checking for both objects and arrays.
|
|
* Functions to insert items into objects return list of stacks representing the items that could not be inserted.
|
|
-]]
|
|
|
|
require("util")
|
|
|
|
local __saveGridStacks__ = nil
|
|
local __saveGrid__ = nil
|
|
|
|
local exportable = {["blueprint"]=true,
|
|
["blueprint-book"]=true,
|
|
["upgrade-planner"]=true,
|
|
["deconstruction-planner"]=true,
|
|
["item-with-tags"]=true}
|
|
|
|
local function mergeStackLists(stacks1, stacks2)
|
|
if not stacks2 then
|
|
return stacks1
|
|
end
|
|
if not stacks1 then
|
|
return stacks2
|
|
end
|
|
for _,s in pairs(stacks2) do
|
|
if not s.count then s.count = 1 end
|
|
if s.data or s.health or s.durability or s.ammo then
|
|
table.insert(stacks1, s)
|
|
else
|
|
local found = false
|
|
for _,t in pairs(stacks1) do
|
|
if not t.count then t.count = 1 end
|
|
if s.name == t.name and not (t.ammo or t.data or t.durability) then
|
|
t.count = t.count + s.count
|
|
found = true
|
|
break
|
|
end
|
|
end
|
|
if not found then
|
|
table.insert(stacks1, s)
|
|
end
|
|
end
|
|
end
|
|
return stacks1
|
|
end
|
|
|
|
|
|
local function itemsToStacks(items)
|
|
local stacks = {}
|
|
if items then
|
|
for name, count in pairs(items) do
|
|
table.insert(stacks, {name=name, count=count})
|
|
end
|
|
if #stacks == 0 then stacks = nil end
|
|
return stacks
|
|
end
|
|
end
|
|
|
|
|
|
---------------------------------------------------------------
|
|
-- Insert Stack Structure into Inventory.
|
|
-- Arguments: source -> LuaInventory to save contents of
|
|
-- Returns: stacks -> Dictionary [slot#] -> SimpleItemStack with extra optional field "data" storing blueprint export string
|
|
---------------------------------------------------------------
|
|
local function saveInventoryStacks(source)
|
|
if source and source.valid and not source.is_empty() then
|
|
local stacks = {}
|
|
for slot = 1, #source do
|
|
local stack = source[slot]
|
|
if stack and stack.valid_for_read then
|
|
if exportable[stack.name] then
|
|
table.insert(stacks, {name=stack.name, count=1, data=stack.export_stack()})
|
|
else
|
|
local s = {name=stack.name, count = stack.count}
|
|
if stack.prototype.magazine_size then
|
|
if stack.ammo < stack.prototype.magazine_size then
|
|
s.ammo = stack.ammo
|
|
end
|
|
end
|
|
if stack.prototype.durability then
|
|
if stack.durability < stack.prototype.durability then
|
|
s.durability = stack.durability
|
|
end
|
|
end
|
|
if stack.health < 1 then
|
|
s.health = stack.health
|
|
end
|
|
-- Merge with existing stacks to avoid duplicates
|
|
mergeStackLists(stacks, {s})
|
|
|
|
-- Can't restore equipment to an item's grid, have to unpack it to the inventory
|
|
if stack.grid and stack.grid.valid then
|
|
local equipStacks, fuelStacks = __saveGridStacks__(__saveGrid__(stack.grid))
|
|
mergeStackLists(stacks, equipStacks)
|
|
mergeStackLists(stacks, fuelStacks)
|
|
end
|
|
|
|
end
|
|
end
|
|
end
|
|
return stacks
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------------
|
|
-- Insert Stack Structure into Inventory.
|
|
-- Arguments: target -> LuaInventory to insert items into
|
|
-- stack -> SimpleItemStack with extra optional field "data" storing blueprint export string.
|
|
-- stack_limit (optional) -> integer maximum number of items to insert from the given stack.
|
|
-- Returns: remainder -> SimpleItemStack with extra field "data", representing all the items that could not be inserted at this time.
|
|
---------------------------------------------------------------
|
|
local function insertStack(target, stack, stack_limit)
|
|
local proto = game.item_prototypes[stack.name]
|
|
if not stack.count then stack.count = 1 end
|
|
local remainder = table.deepcopy(stack)
|
|
if proto then
|
|
if target.can_insert(stack) then
|
|
if stack.data then
|
|
-- Insert bp item, find ItemStack, import data string
|
|
for i = 1, #target do
|
|
if not target[i].valid_for_read then
|
|
-- this stack is empty, set it to blueprint
|
|
target[i].set_stack(stack)
|
|
target[i].import_stack(stack.data)
|
|
return nil -- no remainders after insertion
|
|
end
|
|
end
|
|
else
|
|
-- Handle normal item, break into chunks if need be, correct for oversized stacks
|
|
if not stack_limit then
|
|
stack_limit = math.huge
|
|
end
|
|
local d = 0
|
|
if stack.count > stack_limit then
|
|
-- This time we limit ourselves to part of the given stack.
|
|
d = target.insert({name=stack.name, count=stack_limit})
|
|
else
|
|
-- Only the last part gets assigned ammo and durability ratings of the original stack
|
|
d = target.insert(stack)
|
|
end
|
|
remainder.count = stack.count - d
|
|
if remainder.count == 0 then
|
|
return nil -- All items inserted, no remainder
|
|
else
|
|
return remainder -- Not all items inserted, return remainder with original ammo/durability ratings
|
|
end
|
|
end
|
|
else
|
|
-- Can't insert this stack, entire thing is remainder.
|
|
return remainder
|
|
end
|
|
else
|
|
-- Prototype for this item was removed from the game, don't give a remainder.
|
|
return nil
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------------
|
|
-- Spill Stack Structure onto ground.
|
|
-- Arguments: target -> LuaInventory to insert items into
|
|
-- stack -> SimpleItemStack with extra optional field "data" storing blueprint export string.
|
|
-- stack_limit (optional) -> integer maximum number of items to insert from the given stack.
|
|
-- Returns: remainder -> SimpleItemStack with extra field "data", representing all the items that could not be inserted at this time.
|
|
---------------------------------------------------------------
|
|
local function spillStack(stack, surface, position)
|
|
if stack then
|
|
surface.spill_item_stack(position, stack)
|
|
if stack.data then
|
|
-- This is a bp item, find it on the surface and restore data
|
|
for _,entity in pairs(surface.find_entities_filtered{name="item-on-ground",position=position,radius=1000}) do
|
|
-- Check if these are the droids we are looking for
|
|
if entity.stack.valid_for_read then
|
|
local es = entity.stack
|
|
if es.name == stack.name then
|
|
-- TODO: Handle detection of empty deconstruction_planner, upgrade_planner, item_with_tags
|
|
if es.is_blueprint and not es.is_blueprint_setup() then
|
|
-- New empty blueprint, let's import into it
|
|
es.import_stack(stack.data)
|
|
break
|
|
elseif es.is_blueprint_book then
|
|
local estring = es.export_stack()
|
|
-- Compare export string to empty blueprint book
|
|
if estring == "0eNqrVkrKKU0tKMrMK4lPys/PVrKqVsosSc1VskJI6IIldJQSk0syy1LjM/NSUiuUrAx0lMpSi4oz8/OUrIwsDE3MLY3MDc3MzS0tjWprAVWnHQo=" then
|
|
es.import_stack(stack.data)
|
|
break
|
|
end
|
|
elseif es.is_upgrade_item then
|
|
local estring = es.export_stack()
|
|
-- Compare export string to empty upgrade planner
|
|
if estring == "0eNo1yk0KgCAQBtC7fGtbKNGklwmhQQSbxJ824t1b+dZvoOdQ/M1XTl6EC9xA5daihAonPSWF2PiBW3NbU+HjUuMrcObUO1lD+iCy1sz5A4aSHcM=" then
|
|
es.import_stack(stack.data)
|
|
break
|
|
end
|
|
elseif es.is_deconstruction_item then
|
|
local estring = es.export_stack()
|
|
-- Compare export string to empty deconstruction planner
|
|
if estring == "0eNpdy8EKgCAMANB/2dkOSmTuZyJshGAz3Owi/XtdunR98DpsFAuL1hY1FV7OvDJTBewgpJp4F0BuORtISgfgLwxfMHBRlVcA3WxHH5y3k/chuPt+ALDXI9s=" then
|
|
es.import_stack(stack.data)
|
|
break
|
|
end
|
|
elseif es.is_item_with_tags then
|
|
if not es.tags or table_size(es.tags) == 0 then
|
|
es.import_stack(stack.data)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function spillStacks(stacks, surface, position)
|
|
if stacks then
|
|
for _,s in pairs(stacks) do
|
|
spillStack(s, surface, position)
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
---------------------------------------------------------------
|
|
-- Restore Inventory Stack List to Inventory.
|
|
-- Arguments: target -> LuaInventory to insert items into
|
|
-- stacks -> List of SimpleItemStack with extra optional field "data" storing blueprint export string.
|
|
-- Returns: remainders -> List of SimpleItemStacks representing all the items that could not be inserted at this time.
|
|
---------------------------------------------------------------
|
|
local function insertInventoryStacks(target, stacks)
|
|
local remainders = {}
|
|
if target and target.valid and stacks then
|
|
for _,stack in pairs(stacks) do
|
|
local r = insertStack(target, stack)
|
|
if r then
|
|
table.insert(remainders, r)
|
|
end
|
|
end
|
|
elseif stacks then
|
|
-- If inventory invalid, return entire contents
|
|
return stacks
|
|
end
|
|
if #remainders > 0 then
|
|
return remainders
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
|
|
local function saveBurner(burner)
|
|
if burner and burner.valid then
|
|
local saved = {heat = burner.heat}
|
|
if burner.currently_burning then
|
|
saved.currently_burning = burner.currently_burning.name
|
|
saved.remaining_burning_fuel = burner.remaining_burning_fuel
|
|
end
|
|
if burner.inventory and burner.inventory.valid then
|
|
saved.inventory = itemsToStacks(burner.inventory.get_contents())
|
|
end
|
|
if burner.burnt_result_inventory and burner.burnt_result_inventory.valid then
|
|
saved.burnt_result_inventory = itemsToStacks(burner.burnt_result_inventory.get_contents())
|
|
end
|
|
return saved
|
|
end
|
|
end
|
|
|
|
local function restoreBurner(target, saved)
|
|
if target and target.valid and saved then
|
|
-- Only restore burner heat if the fuel prototype still exists and is valid in this burner.
|
|
if (saved.currently_burning and
|
|
game.item_prototypes[saved.currently_burning] and
|
|
target.inventory.can_insert({name=saved.currently_burning, count=1})) then
|
|
target.currently_burning = game.item_prototypes[saved.currently_burning]
|
|
target.remaining_burning_fuel = saved.remaining_burning_fuel
|
|
target.heat = saved.heat
|
|
end
|
|
local r1 = insertInventoryStacks(target.inventory, saved.inventory)
|
|
local r2 = insertInventoryStacks(target.burnt_result_inventory, saved.burnt_result_inventory)
|
|
return mergeStackLists(r1, r2)
|
|
elseif saved then
|
|
-- Return entire contents if target invalid
|
|
local r = mergeStackLists({}, saved.burnt_result_inventory)
|
|
r = mergeStackLists(r, saved.inventory)
|
|
return r
|
|
end
|
|
end
|
|
|
|
|
|
local function saveGrid(grid)
|
|
if grid and grid.valid then
|
|
local gridContents = {}
|
|
for _,v in pairs(grid.equipment) do
|
|
local item = {name=v.name,position=v.position}
|
|
local burner = saveBurner(v.burner)
|
|
local energy, shield
|
|
if v.energy > 0 then
|
|
energy = v.energy
|
|
end
|
|
if v.shield > 0 then
|
|
shield = v.shield
|
|
end
|
|
table.insert(gridContents, {item=item,
|
|
energy=energy,
|
|
shield=shield,
|
|
burner=burner})
|
|
end
|
|
return gridContents
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
__saveGrid__ = saveGrid
|
|
|
|
local function restoreGrid(grid, savedGrid)
|
|
local r_stacks = {}
|
|
if grid and grid.valid and savedGrid then
|
|
-- Insert as much as possible into this grid, return items not inserted as remainder stacks
|
|
for _,v in pairs(savedGrid) do
|
|
if game.equipment_prototypes[v.item.name] then
|
|
local e = grid.put(v.item)
|
|
if e then
|
|
if v.energy then
|
|
e.energy = v.energy
|
|
end
|
|
if v.shield and v.shield > 0 then
|
|
e.shield = v.shield
|
|
end
|
|
if v.burner then
|
|
local r1 = restoreBurner(e.burner,v.burner)
|
|
r_stacks = mergeStackLists(r_stacks, r1)
|
|
end
|
|
else
|
|
r_stacks = mergeStackLists(r_stacks, {{name=v.item.name, count=1}})
|
|
if v.burner then
|
|
r_stacks = mergeStackLists(r_stacks, v.burner.inventory)
|
|
r_stacks = mergeStackLists(r_stacks, v.burner.burnt_result_inventory)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if #r_stacks > 0 then
|
|
return r_stacks
|
|
end
|
|
elseif savedGrid then
|
|
-- If grid is invalid but we have saved items, return the whole grid as a remainder
|
|
local e,f = __saveGridStacks__(savedGrid)
|
|
r_stacks = mergeStackLists(r_stacks, e)
|
|
r_stacks = mergeStackLists(r_stacks, f)
|
|
return r_stacks
|
|
end
|
|
end
|
|
|
|
|
|
local function removeStackFromSavedGrid(savedGrid, stack)
|
|
if savedGrid and stack then
|
|
if not stack.count then stack.count = 1 end
|
|
for i,e in pairs(savedGrid) do
|
|
if e.item.name == stack.name then
|
|
savedGrid[i] = nil
|
|
stack.count = stack.count - 1
|
|
elseif e.burner then
|
|
if e.burner.inventory then
|
|
for k,s in pairs(e.burner.inventory) do
|
|
if s.name == stack.name then
|
|
if s.count <= stack.count then
|
|
stack.count = stack.count - s.count
|
|
e.burner.inventory[k] = nil
|
|
else
|
|
s.count = s.count - stack.count
|
|
stack.count = 0
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if stack.count == 0 then
|
|
return
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
---------------------------------------------------------------
|
|
-- Convert Grid Contents to SimpleItemStack array.
|
|
-- Arguments: grid -> table result of saveGrid()
|
|
-- dest (optional) -> SimpleItemStack array to insert stacks into
|
|
-- Returns: stacks -> List of SimpleItemStacks or reference to dest
|
|
---------------------------------------------------------------
|
|
local function saveGridStacks(savedGrid)
|
|
-- Count item numbers so we can add stacks of equipment and fuel properly
|
|
local items = {}
|
|
local fuel_items = {}
|
|
if savedGrid then
|
|
for _,v in pairs(savedGrid) do
|
|
if v.burner then
|
|
fuel_items = mergeStackLists(fuel_items, v.burner.inventory)
|
|
fuel_items = mergeStackLists(fuel_items, v.burner.burnt_result_inventory)
|
|
end
|
|
items = mergeStackLists(items, {{name=v.item.name, count=1}})
|
|
end
|
|
return items, fuel_items
|
|
end
|
|
end
|
|
|
|
__saveGridStacks__ = saveGridStacks
|
|
|
|
|
|
local function saveFilters(source)
|
|
local filters = nil
|
|
if source and source.valid then
|
|
if source.is_filtered() then
|
|
filters = {}
|
|
for f = 1, #source do
|
|
filters[f] = source.get_filter(f)
|
|
end
|
|
end
|
|
if source.supports_bar() and source.get_bar() <= #source then
|
|
filters = filters or {}
|
|
filters.bar = source.get_bar()
|
|
end
|
|
end
|
|
return filters
|
|
end
|
|
|
|
local function restoreFilters(target, filters)
|
|
if target and target.valid and filters then
|
|
if target.supports_filters() then
|
|
for f = 1, #target do
|
|
target.set_filter(f, filters[f])
|
|
end
|
|
end
|
|
if target.supports_bar() then
|
|
if not filters.bar then
|
|
target.set_bar()
|
|
else
|
|
target.set_bar(filters.bar) -- if filters.bar is nil, will clear bar setting
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local function saveItemRequestProxy(target)
|
|
-- Search for item_request_proxy ghosts targeting this entity
|
|
local proxies = target.surface.find_entities_filtered{
|
|
name = "item-request-proxy",
|
|
force = target.force,
|
|
position = target.position
|
|
}
|
|
for _, proxy in pairs(proxies) do
|
|
if proxy.proxy_target == target and proxy.valid then
|
|
local items = {}
|
|
local exists = false
|
|
for k,v in pairs(proxy.item_requests) do
|
|
items[k] = v
|
|
exists = true
|
|
end
|
|
if exists then
|
|
return items
|
|
else
|
|
return nil
|
|
end
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
end
|
|
|
|
return {
|
|
saveBurner = saveBurner,
|
|
restoreBurner = restoreBurner,
|
|
saveGrid = saveGrid,
|
|
restoreGrid = restoreGrid,
|
|
saveGridStacks = saveGridStacks,
|
|
removeStackFromSavedGrid = removeStackFromSavedGrid,
|
|
saveInventoryStacks = saveInventoryStacks,
|
|
insertStack = insertStack,
|
|
insertInventoryStacks = insertInventoryStacks,
|
|
mergeStackLists = mergeStackLists,
|
|
itemsToStacks = itemsToStacks,
|
|
spillStack = spillStack,
|
|
spillStacks = spillStacks,
|
|
saveFilters = saveFilters,
|
|
restoreFilters = restoreFilters,
|
|
saveItemRequestProxy = saveItemRequestProxy,
|
|
}
|