Implement basic auto-crafting

This commit is contained in:
Gabriel Tofvesson 2025-05-15 22:00:10 +02:00
parent 87ce5758e6
commit 4785e22f30
5 changed files with 175 additions and 31 deletions

View File

@ -64,6 +64,10 @@ function itemstack.fromItemDetail(detail, itemLimit)
return newItemStack(item.fromDetail(detail), detail.count, itemLimit) return newItemStack(item.fromDetail(detail), detail.count, itemLimit)
end end
function itemstack.fromSlot(per, slot)
return itemstack.fromItemDetail(per.getItemDetail(slot), per.getItemLimit(slot))
end
function itemstack.fromString(str) function itemstack.fromString(str)
local strStack = textutils.unserialize(str) local strStack = textutils.unserialize(str)
if type(strStack.item) ~= "string" or if type(strStack.item) ~= "string" or

View File

@ -72,7 +72,7 @@ function prototype:moveItemsTo(count, fromSlot, otherChest, toSlot)
return 0 return 0
end end
local tx = self.peripheral.pushItems(peripheral.getName(otherChest.peripheral), fromSlot, count, toSlot) local tx = self.peripheral.pushItems(otherChest:getIdentifier(), fromSlot, count, toSlot)
myStack.count = myStack.count - tx myStack.count = myStack.count - tx
if myStack.count == 0 then if myStack.count == 0 then
self.slots[fromSlot] = nil self.slots[fromSlot] = nil
@ -87,6 +87,41 @@ function prototype:moveItemsTo(count, fromSlot, otherChest, toSlot)
return tx return tx
end end
function prototype:moveItemsToPeripheral(count, fromSlot, target, toSlot)
local myStack = self:getSlot(fromSlot)
local tx = self.peripheral.pushItems(peripheral.getName(target), fromSlot, count, toSlot)
myStack.count = myStack.count - tx
if myStack.count == 0 then
self.slots[fromSlot] = nil
end
return tx
end
function prototype:pullItemsFromPeripheral(count, fromSlot, fromTarget, toSlot)
local myStack = self:getSlot(toSlot)
local perStack = itemstack.fromItemDetail(fromTarget.getItemDetail(fromSlot), fromTarget.getItemLimit(fromSlot))
if not perStack:canTransferTo(myStack) then
error("Cannot pull items from peripheral to chest: "..textutils.serialise({
from = { peripheral = peripheral.getName(fromTarget), slot = fromSlot },
to = { chest = self:getIdentifier(), slot = toSlot },
count = count
}))
end
local tx = fromTarget.pushItems(self:getIdentifier(), fromSlot, count, toSlot)
if myStack:isBlank() then
myStack.item = perStack.item
self[toSlot] = myStack
end
myStack.count = myStack.count + tx
return tx
end
function prototype:getIdentifier() function prototype:getIdentifier()
return peripheral.getName(self.peripheral) return peripheral.getName(self.peripheral)
end end

86
recipe.lua Normal file
View File

@ -0,0 +1,86 @@
local itemstack = require("itemstack")
local stackgroup = require("stackgroup")
local util = require("util")
local prototype = {}
function prototype:getCost()
local costs = {}
for _,input in pairs(self.inputs) do
local itemCost = util.find(costs, function(_, item) return item == input.item end)
if itemCost == nil then
itemCost = 0
end
costs[input.item] = itemCost + input.count
end
return costs
end
-- Returns table of item -> missing-count mappings. Empty table if recipe can be afforded
function prototype:canAfford(chests)
local storage = stackgroup.fromChests(table.unpack(chests))
local costs = self:getCost()
local missing = {}
for item,cost in pairs(costs) do
local group = storage:findGroup(item)
if group == nil or group.total < cost then
local totalMissing = cost
if group ~= nil then
totalMissing = totalMissing - group.total
end
missing[item] = totalMissing
end
end
return missing
end
function prototype:beginCrafting(chests)
local storage = stackgroup.fromChests(table.unpack(chests))
for _,input in pairs(self.inputs) do
local group = storage:findGroup(input.item)
group:moveItemsToPeripheral(chests, input.count, input.machine, input.slot)
end
-- Return poll function to check for readiness
return function()
for _,output in pairs(self.outputs) do
local stackdata = itemstack.fromSlot(output.machine, output.slot)
if output.item ~= stackdata.item or output.count > stackdata.count then
return false
end
end
return true
end
end
local recipe = {}
-- Input or output of a recipe
function recipe.component(machine, slot, item, count)
return {
machine = machine,
slot = slot,
item = item,
count = count
}
end
function recipe.create(inputs, outputs)
local recipeDef = {
inputs = inputs,
outputs = outputs
}
setmetatable(recipeDef, { __index = prototype })
return recipeDef
end
return recipe

View File

@ -2,35 +2,24 @@ local util = require("util")
local stackgroup = {} local stackgroup = {}
local prototype = {} local prototype = {}
local metatable = {}
function prototype:defragment(chests) function prototype:defragment(chests)
table.sort(self, function(a, b) return a.count < b.count end) table.sort(self, function(a, b) return a.count < b.count end)
local resultGroup = {
item = self.item,
total = self.total
}
setmetatable(resultGroup, metatable)
local top = #self local top = #self
local bottom = 1
-- Find largest non-full stack -- Find largest non-full stack
for i=top,bottom,-1 do for i=top,1,-1 do
local entry = self[i] local entry = self[i]
if entry:getStack(chests):availableCount() ~= 0 then if entry:getStack(chests):availableCount() ~= 0 then
top = i top = i
break break
else
resultGroup[#resultGroup + 1] = entry
end end
end end
while top > bottom do while top > 1 do
local topEntry = self[top] local topEntry = self[top]
local bottomEntry = self[bottom] local bottomEntry = self[1]
local txCap = math.min(bottomEntry.count, topEntry:getStack(chests):availableCount()) local txCap = math.min(bottomEntry.count, topEntry:getStack(chests):availableCount())
local tx = bottomEntry:getChest(chests):moveItemsTo(txCap, bottomEntry.slot, topEntry:getChest(chests), topEntry.slot) local tx = bottomEntry:getChest(chests):moveItemsTo(txCap, bottomEntry.slot, topEntry:getChest(chests), topEntry.slot)
@ -48,23 +37,46 @@ function prototype:defragment(chests)
topEntry.count = topEntry.count + tx topEntry.count = topEntry.count + tx
if bottomEntry.count == 0 then if bottomEntry.count == 0 then
bottom = bottom + 1 top = top - 1
table.remove(self, 1)
end end
if topEntry:getStack(chests):availableCount() == 0 then if topEntry:getStack(chests):availableCount() == 0 then
top = top - 1 top = top - 1
resultGroup[#resultGroup + 1] = topEntry end
end end
end end
if top == bottom and self[top].count > 0 then function prototype:moveItemsToPeripheral(chests, count, per, slot)
resultGroup[#resultGroup+1] = self[top] if count > self.total then
error("Insufficient items available to transfer to peripheral: "..textutils.serialise({
available = self.total,
count = count
}))
end
table.sort(self, function(a, b) return a.count < b.count end)
local totalMoved = 0
while totalMoved < count do
local currentSlot = self[1]
local txCap = math.min(currentSlot.count, count - totalMoved)
local tx = currentSlot:getChest(chests):moveItemsToPeripheral(txCap, currentSlot.slot, per, slot)
if tx == 0 then
error("Unexpected error when transferring items to peripheral: "..textutils.serialise({
expected = txCap,
from = { chest = currentSlot:getChest(chests):getIdentifier(), count = currentSlot.count },
to = { peripheral = peripheral.getName(per) }
}))
end end
return resultGroup self.total = self.total - tx
totalMoved = totalMoved + tx
if tx == currentSlot.count then
table.remove(self, 1)
end
end
end end
metatable.__index = prototype
local groupEntryPrototype = {} local groupEntryPrototype = {}
@ -76,13 +88,15 @@ function groupEntryPrototype:getChest(chests)
return chests[self.chestIndex] return chests[self.chestIndex]
end end
local groupEntryMetatable = { local stackgroupSetPrototype = {}
__index = groupEntryPrototype function stackgroupSetPrototype:findGroup(item)
} return util.find(self, function(group) return group.item == item end)
end
function stackgroup.fromChests(...) function stackgroup.fromChests(...)
local chests = { ... } local chests = { ... }
local groups = {} local groups = {}
setmetatable(groups, { __index = stackgroupSetPrototype })
for chestIndex,chest in ipairs(chests) do for chestIndex,chest in ipairs(chests) do
for slotKey,slot in pairs(chest.slots) do for slotKey,slot in pairs(chest.slots) do
@ -93,7 +107,7 @@ function stackgroup.fromChests(...)
total = 0 total = 0
} }
setmetatable(group, metatable) setmetatable(group, { __index = prototype })
groups[#groups + 1] = group groups[#groups + 1] = group
end end
@ -103,7 +117,7 @@ function stackgroup.fromChests(...)
slot = slotKey, slot = slotKey,
count = slot.count count = slot.count
} }
setmetatable(entry, groupEntryMetatable) setmetatable(entry, { __index = groupEntryPrototype })
group[#group + 1] = entry group[#group + 1] = entry
group.total = group.total + slot.count group.total = group.total + slot.count

View File

@ -26,12 +26,17 @@ function util.deepEqual(a, b)
end end
function util.find(array, f) function util.find(array, f)
for _,v in pairs(array) do for k,v in pairs(array) do
if f(v) then if f(v, k) then
return v return v
end end
end end
return nil return nil
end end
function util.isEmpty(tbl)
local next, t = pairs(tbl)
return next(t) == nil
end
return util return util