From 4785e22f30f29bd9dca3d9405e8099dcfd861bfb Mon Sep 17 00:00:00 2001 From: Gabriel Tofvesson Date: Thu, 15 May 2025 22:00:10 +0200 Subject: [PATCH] Implement basic auto-crafting --- itemstack.lua | 4 +++ localchest.lua | 37 +++++++++++++++++++++- recipe.lua | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++ stackgroup.lua | 70 ++++++++++++++++++++++++---------------- util.lua | 9 ++++-- 5 files changed, 175 insertions(+), 31 deletions(-) create mode 100644 recipe.lua diff --git a/itemstack.lua b/itemstack.lua index 28bd783..e4754a5 100644 --- a/itemstack.lua +++ b/itemstack.lua @@ -64,6 +64,10 @@ function itemstack.fromItemDetail(detail, itemLimit) return newItemStack(item.fromDetail(detail), detail.count, itemLimit) end +function itemstack.fromSlot(per, slot) + return itemstack.fromItemDetail(per.getItemDetail(slot), per.getItemLimit(slot)) +end + function itemstack.fromString(str) local strStack = textutils.unserialize(str) if type(strStack.item) ~= "string" or diff --git a/localchest.lua b/localchest.lua index b7d4a4d..73ad013 100644 --- a/localchest.lua +++ b/localchest.lua @@ -72,7 +72,7 @@ function prototype:moveItemsTo(count, fromSlot, otherChest, toSlot) return 0 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 if myStack.count == 0 then self.slots[fromSlot] = nil @@ -87,6 +87,41 @@ function prototype:moveItemsTo(count, fromSlot, otherChest, toSlot) return tx 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() return peripheral.getName(self.peripheral) end diff --git a/recipe.lua b/recipe.lua new file mode 100644 index 0000000..6d0210a --- /dev/null +++ b/recipe.lua @@ -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 \ No newline at end of file diff --git a/stackgroup.lua b/stackgroup.lua index d769e4e..edea9ee 100644 --- a/stackgroup.lua +++ b/stackgroup.lua @@ -2,35 +2,24 @@ local util = require("util") local stackgroup = {} local prototype = {} -local metatable = {} function prototype:defragment(chests) 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 bottom = 1 -- Find largest non-full stack - for i=top,bottom,-1 do + for i=top,1,-1 do local entry = self[i] if entry:getStack(chests):availableCount() ~= 0 then top = i break - else - resultGroup[#resultGroup + 1] = entry end end - while top > bottom do + while top > 1 do local topEntry = self[top] - local bottomEntry = self[bottom] + local bottomEntry = self[1] local txCap = math.min(bottomEntry.count, topEntry:getStack(chests):availableCount()) 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 if bottomEntry.count == 0 then - bottom = bottom + 1 + top = top - 1 + table.remove(self, 1) end if topEntry:getStack(chests):availableCount() == 0 then top = top - 1 - resultGroup[#resultGroup + 1] = topEntry end end - - if top == bottom and self[top].count > 0 then - resultGroup[#resultGroup+1] = self[top] - end - - return resultGroup end -metatable.__index = prototype +function prototype:moveItemsToPeripheral(chests, count, per, slot) + 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 + + self.total = self.total - tx + totalMoved = totalMoved + tx + if tx == currentSlot.count then + table.remove(self, 1) + end + end +end local groupEntryPrototype = {} @@ -76,13 +88,15 @@ function groupEntryPrototype:getChest(chests) return chests[self.chestIndex] end -local groupEntryMetatable = { - __index = groupEntryPrototype -} +local stackgroupSetPrototype = {} +function stackgroupSetPrototype:findGroup(item) + return util.find(self, function(group) return group.item == item end) +end function stackgroup.fromChests(...) local chests = { ... } local groups = {} + setmetatable(groups, { __index = stackgroupSetPrototype }) for chestIndex,chest in ipairs(chests) do for slotKey,slot in pairs(chest.slots) do @@ -93,7 +107,7 @@ function stackgroup.fromChests(...) total = 0 } - setmetatable(group, metatable) + setmetatable(group, { __index = prototype }) groups[#groups + 1] = group end @@ -103,7 +117,7 @@ function stackgroup.fromChests(...) slot = slotKey, count = slot.count } - setmetatable(entry, groupEntryMetatable) + setmetatable(entry, { __index = groupEntryPrototype }) group[#group + 1] = entry group.total = group.total + slot.count diff --git a/util.lua b/util.lua index df6d7f1..7074a45 100644 --- a/util.lua +++ b/util.lua @@ -26,12 +26,17 @@ function util.deepEqual(a, b) end function util.find(array, f) - for _,v in pairs(array) do - if f(v) then + for k,v in pairs(array) do + if f(v, k) then return v end end return nil end +function util.isEmpty(tbl) + local next, t = pairs(tbl) + return next(t) == nil +end + return util \ No newline at end of file