logistica-cd2025/logic/crafting_supplier.lua

216 lines
8.5 KiB
Lua

local S = logistica.TRANSLATOR
local INV_MAIN = "main"
local INV_CRAFT = "crf"
local INV_HOUT = "hout"
local function ret(remaining, optError)
return { remaining = remaining, error = optError and S(optError) or nil }
end
local function count_items_to_stack(list)
local map = {}
for _, stack in ipairs(list) do
if not stack:is_empty() then
local stName = stack:get_name()
if not map[stName] then map[stName] = 0 end
map[stName] = map[stName] + stack:get_count()
end
end
local items = {}
local i = 0
for name, count in pairs(map) do
i = i + 1
local item = ItemStack(name) ; item:set_count(count)
items[i] = item
end
return items
end
-- returns table { newList = listWithoutStack, takenStack = stackOfHowManyRemoved }
local function list_without_stack(invList, takeStack)
local newList = {}
local countedItems = count_items_to_stack(invList)
local takenStack = ItemStack(takeStack) ; takenStack:set_count(1)
for _, v in pairs(countedItems) do
if takeStack:get_count() > 0 and v:get_name() == takeStack:get_name() then
local countLeftoverAfterRemoved = math.max(0, v:get_count() - takeStack:get_count())
local modifiedV = ItemStack(v) ; modifiedV:set_count(countLeftoverAfterRemoved)
takenStack:set_count(takenStack:get_count() + v:get_count() - countLeftoverAfterRemoved)
if countLeftoverAfterRemoved > 0 then
table.insert(newList, modifiedV)
end
elseif v:get_count() > 0 then
table.insert(newList, v)
end
end
takenStack:set_count(takenStack:get_count() - 1)
return { newList = newList, takenStack = takenStack }
end
local function consume_from_network(craftItems, times, network, depth)
if times <= 0 then return end
local acceptItem = function (_) return 0 end
for _, itemStack in ipairs(craftItems) do
local consumeStack = ItemStack(itemStack) ; consumeStack:set_count(itemStack:get_count() * times)
logistica.take_stack_from_network(consumeStack, network, acceptItem, true, false, false, depth + 1)
end
end
-- returns table {countCanCraft = # (0 or 1), newExtrasList = extrasMadeByCrafting - removed items}
local function consume_for_craft(craftItems, craftItemsMult, extrasMadeByCrafting, network, depth, dryRun)
local itemTaken = ItemStack("")
local acceptItem = function(st) itemTaken:add_item(st) ; return 0 end
local extrasCopy = table.copy(extrasMadeByCrafting)
local toConsumeFromNetwork = {}
for _, _itemStack in ipairs(craftItems) do
itemTaken:clear()
local itemStack = ItemStack(_itemStack)
if dryRun then
-- when doing a dryRun the actual items are not removed from the network, so we need to make sure
-- we have enough in the network by accounting for how many have been "crafted" so far
itemStack:set_count(itemStack:get_count() * craftItemsMult)
end
-- first check if we can take it from the extrasCopy
local takenFromExtras = 0
for _, v in ipairs(extrasCopy) do
if v:get_name() == itemStack:get_name() then
takenFromExtras = math.min(v:get_count(), itemStack:get_count())
itemStack:set_count(itemStack:get_count() - takenFromExtras)
if not dryRun then -- if not dry run, actually use up items in the extras copy list
v:set_count(math.max(0, v:get_count() - takenFromExtras))
end
end
end
-- then if any still needed, take from network
if itemStack:get_count() > 0 then
logistica.take_stack_from_network(itemStack, network, acceptItem, true, false, true, depth + 1)
if not dryRun and itemTaken:get_count() > 0 then
table.insert(toConsumeFromNetwork, ItemStack(itemTaken))
end
end
-- if there aren't enough combined items, we just can't craft this
if (takenFromExtras + itemTaken:get_count()) < itemStack:get_count() then
return { countCanCraft = 0, newExtrasList = extrasMadeByCrafting }
end
end
-- if we got here, it means we CAN craft this. remove the items as needed
if not dryRun then
consume_from_network(toConsumeFromNetwork, 1, network, depth)
end
return { countCanCraft = 1, newExtrasList = extrasCopy }
end
-- returns a list of ItemStacks to be used for caching, which may be a sublist of INV_MAIN if the machine is off
function logistica.crafting_supplier_get_main_list(pos)
local isOn = logistica.is_machine_on(pos)
local inv = minetest.get_meta(pos):get_inventory()
local mainList = inv:get_list(INV_MAIN)
if isOn then return mainList
else
local sublist = {}
for i, stack in ipairs(mainList) do
if i ~= 1 then
table.insert(sublist, stack)
end
end
return sublist
end
end
-- returns table {remaining = # How many items remain to fulfil, 0 if successful, errorMsg = "error description here"/nil}
function logistica.take_item_from_crafting_supplier(pos, _takeStack, network, collectorFunc, useMetadata, dryRun, _depth)
local depth = _depth or 0
local takeStack = ItemStack(_takeStack)
local remaining = takeStack:get_count()
local takeStackName = takeStack:get_name()
local inv = minetest.get_meta(pos):get_inventory()
-- first try to take from supply, ignore the 1st slot (which is for the crafted item)
local supplierResult = logistica.take_item_from_supplier(pos, takeStack, network, collectorFunc, useMetadata, dryRun, 1)
remaining = supplierResult.remaining
if remaining <= 0 then return ret(0) end -- everything was taken from existing supply, we're done
-- only craft if machine is on
if not logistica.is_machine_on(pos) then return ret(_takeStack:get_count()) end
-- if we still have a number of requested itsm to fulfil, try crafting them
takeStack:set_count(remaining)
local craftStack = inv:get_stack(INV_MAIN, 1)
-- if names are different, we can't craft this request
if inv:is_empty(INV_CRAFT) or craftStack:get_name() ~= takeStack:get_name() then
return ret(remaining)
end
inv:set_list(INV_HOUT, {})
local numCrafted = 0
local isEnough = false
local craftItemMult = 0
repeat
craftItemMult = craftItemMult + 1
--
local recipeItems = count_items_to_stack(inv:get_list(INV_CRAFT))
-- use the output of any previous loop iterations to make it available to take from - except for the item we have to send to requester
local extrasListsMinusTarget = list_without_stack(inv:get_list(INV_HOUT), takeStack)
local extrasMadeByCrafting = extrasListsMinusTarget.newList -- extra items output by the previous craft loops (aka substitutes)
-- consume items required to craft the item from the extras and network if needed
local consumeResult = consume_for_craft(recipeItems, craftItemMult, extrasMadeByCrafting, network, depth, dryRun)
local numCanCraft = consumeResult.countCanCraft -- how many we can craft, really the function returns 0 or 1
-- if not a dry run, we might have taken some items from the extras, so override the HOUT list with our used-up list
if not dryRun then
if extrasListsMinusTarget.takenStack:get_count() > 0 then
table.insert(consumeResult.newExtrasList, extrasListsMinusTarget.takenStack)
end
inv:set_list(INV_HOUT, consumeResult.newExtrasList)
end
numCrafted = numCrafted + numCanCraft
if numCanCraft > 0 then -- now "craft" the item
logistica.autocrafting_produce_single_item(inv, INV_CRAFT, nil, INV_HOUT)
end
isEnough = inv:contains_item(INV_HOUT, takeStack) or numCanCraft == 0 or numCrafted >= 99
until (isEnough)
if numCrafted == 0 then return ret(remaining, "Not enough materials available to craft items from crafting supplier") end -- nothing could be crafted
remaining = math.max(0, remaining - numCrafted)
-- give the item to the collector
local taken = inv:remove_item(INV_HOUT, takeStack)
local leftover = collectorFunc(taken)
-- now move any extras from the hidden to the main inventory - deleting extras (TODO: maybe drop them)
if not dryRun then
local extraNotTaken = 0
local toInsert = {}
for _, st in ipairs(inv:get_list(INV_HOUT)) do
if st:get_name() == takeStackName then
extraNotTaken = extraNotTaken + st:get_count()
else
table.insert(toInsert, st)
end
end
taken:set_count(leftover + extraNotTaken)
if not taken:is_empty() then
local main = inv:get_list(INV_MAIN) or {}
for i = 2, #main do
taken = main[i]:add_item(taken)
end
inv:set_list(INV_MAIN, main)
end
for _, insertStack in ipairs(toInsert) do
inv:add_item(INV_MAIN, insertStack)
end
logistica.update_cache_at_pos(pos, LOG_CACHE_SUPPLIER, network)
end
return ret(remaining)
end