logistica-cd2025/logic/mass_storage.lua
2023-11-28 14:47:35 +00:00

273 lines
10 KiB
Lua

local META_IMG_PIC = "logimgpick"
local META_RES_VAL = "logresval"
local META_UPGRADE_ADD = "logstorupgr"
local VALID_RESERVE_VALUES = {}
for i = 0,5120,128 do VALID_RESERVE_VALUES[i/128 + 1] = i end
local BASE_TRANSFER_RATE = 10
local function mass_storage_room_for_item(pos, meta, stack)
local stackName = stack:get_name()
local maxNum = logistica.get_mass_storage_max_size(pos)
local filterList = meta:get_inventory():get_list("filter")
local storageList = meta:get_inventory():get_list("storage")
local roomForItems = 0
for i, storageStack in ipairs(filterList) do
if storageStack:get_name() == stackName then
roomForItems = roomForItems + maxNum - storageList[i]:get_count()
end
end
return roomForItems
end
local function show_deposited_item_popup(player, numDeposited, name)
logistica.show_popup(player:get_player_name(), "Stored "..numDeposited.." "..name, 1.5)
end
--------------------------------
-- public functions
--------------------------------
function logistica.get_mass_storage_max_size(pos)
local node = minetest.get_node(pos)
if not node then return 0 end
local def = minetest.registered_nodes[node.name]
if def and def.logistica and def.logistica.maxItems then
local meta = minetest.get_meta(pos)
local storageUpgrade = meta:get_int(META_UPGRADE_ADD)
return def.logistica.maxItems + storageUpgrade
end
return 0
end
function logistica.get_mass_storage_num_slots(pos)
local node = minetest.get_node(pos)
if not node then return 0 end
local def = minetest.registered_nodes[node.name]
if def and def.logistica and def.logistica.numSlots then
-- TODO: account for upgrades
return def.logistica.numSlots
end
return 0
end
-- returns the transfer rate of given mass storage node
function logistica.get_supplier_transfer_rate(meta)
-- TODO: account for speed upgrade
return BASE_TRANSFER_RATE
end
-- Returns a stack of how many items remain
function logistica.insert_item_into_mass_storage(pos, inv, inputStack, dryRun)
local maxItems = logistica.get_mass_storage_max_size(pos)
local numSlots = #(inv:get_list("filter"))
local inputStackName = inputStack:get_name()
local indices = {}
for i = 1, numSlots do
local v = inv:get_stack("filter", i)
if v:get_name() == inputStackName then
table.insert(indices, i)
end
end
local remainingStack = ItemStack(inputStack)
for _, index in ipairs(indices) do
local storageStack = inv:get_stack("storage", index)
local canInsert = logistica.clamp(maxItems - storageStack:get_count(), 0, remainingStack:get_count())
if canInsert > 0 then
local toInsert = ItemStack(inputStackName)
toInsert:set_count(storageStack:get_count() + canInsert)
if not dryRun then
inv:set_stack("storage", index, toInsert)
end
if canInsert >= remainingStack:get_count() then
remainingStack:set_count(0)
return remainingStack -- nothing more to check, return early
else
remainingStack:set_count(remainingStack:get_count() - canInsert)
end
end
end
return remainingStack
end
function logistica.pull_items_from_network_into_mass_storage(pos)
local network = logistica.get_network_or_nil(pos)
if not network then return false end -- don't run timer when no nework - connecting to network will start it
local meta = minetest.get_meta(pos)
local stackPos = logistica.get_next_filled_item_slot(meta, "filter")
if stackPos <= 0 then return true end
local filterStack = meta:get_inventory():get_stack("filter", stackPos)
local spaceForItems = mass_storage_room_for_item(pos, meta, filterStack)
if spaceForItems == 0 then return true end
spaceForItems = math.min(spaceForItems, logistica.get_supplier_transfer_rate(meta))
local requestStack = ItemStack(filterStack)
requestStack:set_count(spaceForItems)
local numTaken = 0
for hash, _ in pairs(network.supplier_cache[requestStack:get_name()] or {}) do
local taken = logistica.take_item_from_supplier_simple(minetest.get_position_from_hash(hash), requestStack)
numTaken = numTaken + taken:get_count()
logistica.insert_item_into_mass_storage(pos, meta:get_inventory(), taken)
if numTaken >= spaceForItems then return true end -- everything isnerted, return
requestStack:set_count(spaceForItems - numTaken)
end
return true
end
function logistica.start_mass_storage_timer(pos)
logistica.start_node_timer(pos, 1)
end
function logistica.on_mass_storage_timer(pos, _)
return logistica.pull_items_from_network_into_mass_storage(pos)
end
function logistica.try_to_add_player_wield_item_to_mass_storage(pos, player)
if not pos or not player or not player:is_player() then return end
local wieldStack = player:get_wielded_item()
if wieldStack:get_count() == 0 or wieldStack:get_stack_max() <= 1 then return end
local numDesposited = 0
local inv = minetest.get_meta(pos):get_inventory()
local newStack = logistica.insert_item_into_mass_storage(pos, inv, wieldStack)
if newStack:get_count() ~= wieldStack:get_count() then
player:set_wielded_item(newStack)
numDesposited = wieldStack:get_count() - newStack:get_count()
end
if newStack:get_count() > 0 or not player:get_player_control().sneak then
show_deposited_item_popup(player, numDesposited, wieldStack:get_short_description())
return
end
-- else, storage potentially has more space, and player was holding sneak
-- try to deposit as many items ouf of their inventory as possible
local pInv = player:get_inventory()
local pListName = player:get_wield_list()
local pList = pInv:get_list(pListName)
for i, pInvStack in ipairs(pList) do
if pInvStack:get_name() == wieldStack:get_name() then
newStack = logistica.insert_item_into_mass_storage(pos, inv, pInvStack)
numDesposited = numDesposited + pInvStack:get_count() - newStack:get_count()
pInv:set_stack(pListName, i, newStack)
if newStack:get_count() > 0 then
show_deposited_item_popup(player, numDesposited, wieldStack:get_short_description())
return -- failed to deposit some
end
end
end
show_deposited_item_popup(player, numDesposited, wieldStack:get_short_description())
end
-- returns a table of {0,128,256,512...} up to the max this box supports
function logistica.get_mass_storage_valid_reserve_list(pos)
local max = logistica.get_mass_storage_max_size(pos)
local vals = {}
for _, v in ipairs(VALID_RESERVE_VALUES) do
if v <= max then
table.insert(vals, v)
end
end
return vals
end
function logistica.set_mass_storage_reserve(meta, i, value)
meta:set_int(META_RES_VAL..tostring(i), value)
end
function logistica.on_mass_storage_reserve_changed(pos, i, value)
local meta = minetest.get_meta(pos)
local intVal = tonumber(value)
if type(intVal) ~= "number" then return end
local invalid = true
for _, v in ipairs(VALID_RESERVE_VALUES) do if v == intVal then invalid = false end end
if invalid then return end
meta:set_int(META_RES_VAL..tostring(i), intVal)
end
function logistica.on_mass_storage_image_select_change(pos, i)
local meta = minetest.get_meta(pos)
local prev = meta:get_int(META_IMG_PIC)
if prev == i then meta:set_int(META_IMG_PIC, 0)
else meta:set_int(META_IMG_PIC, i) end
end
function logistica.set_mass_storage_image_slot(meta, index)
return meta:set_int(META_IMG_PIC, index)
end
-- returns an index of which slot is picked to be the front image, or 0 if there isn't one
function logistica.get_mass_storage_image_slot(meta)
return meta:get_int(META_IMG_PIC)
end
-- returns the picked reserve for the given index
function logistica.get_mass_storage_reserve(meta, index)
return meta:get_int(META_RES_VAL..tostring(index))
end
-- `newParam2` is optional, will override the lookup of node.param2 for rotation
function logistica.update_mass_storage_front_image(origPos, newParam2)
local pos = vector.new(origPos)
logistica.remove_item_on_block_front(pos)
local meta = minetest.get_meta(pos)
local slot = logistica.get_mass_storage_image_slot(meta)
if slot > 0 then
local item = meta:get_inventory():get_list("filter")[slot] or ItemStack("")
logistica.display_item_on_block_front(pos, item:get_name(), newParam2)
end
end
function logistica.get_mass_storage_imgname_or_first_item(meta)
local inv = meta:get_inventory()
if inv:is_empty("filter") then return "\n(Empty)" end
local index = meta:get_int(META_IMG_PIC)
local itemStack = inv:get_stack("filter", index)
if not itemStack:is_empty() then return "\n(Has: "..itemStack:get_description()..")" end
for _, v in ipairs(inv:get_list("filter")) do
if not v:is_empty() then return "\n(Has: "..v:get_description()..")" end
end
return "\n(Empty)"
end
function logistica.is_valid_storage_upgrade(stackName)
return logistica.craftitem.storage_upgrade[stackName] ~= nil
end
function logistica.update_mass_storage_cap(pos, optMeta)
local meta = optMeta or minetest.get_meta(pos)
local storageUpgrade = 0
local list = meta:get_inventory():get_list("upgrade") or {}
for _, item in ipairs(list) do
local upgradeDef = logistica.craftitem.storage_upgrade[item:get_name()]
if upgradeDef and upgradeDef.storage_upgrade then
storageUpgrade = storageUpgrade + upgradeDef.storage_upgrade
end
end
meta:set_int(META_UPGRADE_ADD, storageUpgrade)
end
function logistica.on_mass_storage_upgrade_change(pos, upgradeName, wasAdded)
local upgradeDef = logistica.craftitem.storage_upgrade[upgradeName]
if not upgradeDef or not upgradeDef.storage_upgrade then return true end
local meta = minetest.get_meta(pos)
local storageUpgrade = meta:get_int(META_UPGRADE_ADD)
if wasAdded then storageUpgrade = storageUpgrade + upgradeDef.storage_upgrade
else storageUpgrade = storageUpgrade - upgradeDef.storage_upgrade end
meta:set_int(META_UPGRADE_ADD, storageUpgrade)
end
function logistica.can_remove_mass_storage_upgrade(pos, upgradeName)
local upgradeDef = logistica.craftitem.storage_upgrade[upgradeName]
if not upgradeDef or not upgradeDef.storage_upgrade then return true end
local inv = minetest.get_meta(pos):get_inventory()
local maxStored = 0
for _, st in ipairs(inv:get_list("storage") or {}) do
if st:get_count() > maxStored then maxStored = st:get_count() end
end
local currMax = logistica.get_mass_storage_max_size(pos)
return (currMax - upgradeDef.storage_upgrade) >= maxStored
end