--local itemToCraftInternalName, itemToCraftAmount = "nuclearcraft:empty_active_heat_sink", 15 --local itemToCraftInternalName, itemToCraftAmount = "nuclearcraft:empty_heat_sink", 1 --local itemToCraftInternalName, itemToCraftAmount = "nuclearcraft:helium_collector", 32 --local itemToCraftInternalName, itemToCraftAmount = "nuclearcraft:plate_advanced", 32 --local itemToCraftInternalName, itemToCraftAmount = "nuclearcraft:pump", 1 --local itemToCraftInternalName, itemToCraftAmount = "nuclearcraft:fission_reactor_controller", 1 --local itemToCraftInternalName, itemToCraftAmount = "nuclearcraft:fission_reactor_port", 6 local itemToCraftInternalName, itemToCraftAmount = "mekanism:quantum_entangloporter", 2 local function agc(name) return assert(components:getFirst(name), "failed to get component "..name) end local function pp(tbl) for k,v in pairs(tbl) do print(k, tostring(v)) end end local computer = agc("computer") local fs = require("filesystem") local kernel = require("kernel") local crafter = agc("digitalCrafter") local interface = agc("adapter") local loadedRecipes = load(fs:readAllText("./recipeParseLib.lua"),"recipeParseLib.lua")():getRecipes() print("Loaded the following recipes:") pp(loadedRecipes) local meStockCache = {} -- dict; how much we have in storage minus what we have reserved as we go local function me_GetStock(internalItemName) if itemToCraftInternalName == internalItemName then return 0 end -- dont allow using the item that we want to craft local cached = meStockCache[internalItemName] if not cached then cached = interface:getCount(internalItemName) meStockCache[internalItemName] = cached end return cached end local itemsToConsume = {} local function me_ReserveItem(internalItemName, amount) assert(meStockCache[internalItemName], "why didnt we look up the stock earlier???") assert(meStockCache[internalItemName] >= amount, "there are not enough items available to reserve them. Tried to reserve "..internalItemName.." x"..tostring(amount)) meStockCache[internalItemName] = meStockCache[internalItemName] - amount itemsToConsume[internalItemName] = (itemsToConsume[internalItemName] or 0) + amount end local function me_PushIntoCrafter(internalItemName, amount) assert(meStockCache[internalItemName], "why didnt we look up the stock earlier???") assert((itemsToConsume[internalItemName] or 0) >= amount, "we never reserved enough for "..internalItemName.." x"..tostring(amount)..". Why are we trying to extract it?") itemsToConsume[internalItemName] = (itemsToConsume[internalItemName] or 0) - amount interface:extract(internalItemName, 1, amount) end local function tblToKeyStr(tbl) return table.concat(tbl,";") end local function keyStrToTbl(keyStr) local rv = {} local buf = "" for i = 1, #keyStr do local chr = keyStr:sub(i,i) if chr == ";" then table.insert(rv, buf) buf = "" else buf = buf .. chr end end table.insert(rv, buf) return rv end local missingItems = {} -- dict local currentItemsNeededToCraft = {} -- dict local plannedCraftSteps = {} -- array<(tblToKeyStr({*ingredients, result}), amount:int)> local function planCraftForItem(itemNameToCraft, amount) local recipe = loadedRecipes[itemNameToCraft] if not recipe then print(("Could not find a recipe for %s"):format(itemNameToCraft)) return amount end -- we dont have a recipe and therefore cannot craft it -- compute concrete recipe applications in the form [concreteIngredientName1-9] --> concreteOutputName local meAmount = me_GetStock(itemNameToCraft) local meAmountToTake = math.min(meAmount, amount) local itemsLeftToCraft = amount - meAmountToTake local craftIterations = math.ceil(itemsLeftToCraft / recipe.resultCount) print(("looks like we want to craft %s times a recipe yielding %sx %s as we want %s more items of that."):format(craftIterations, recipe.resultCount, itemNameToCraft, itemsLeftToCraft)) while itemsLeftToCraft > 0 do local recipeHasFailed = false local allIngredients = {} for slotIdx=1,9 do local recipeItemOptions = recipe.slots[slotIdx] if recipeItemOptions then --print("option cnt: ", #recipeItemOptions) -- try to find one item we have in stock and reserve this item local itemWasFound = false for _, value in ipairs(recipeItemOptions) do if me_GetStock(value) > 0 then me_ReserveItem(value, 1) itemWasFound = true allIngredients[slotIdx] = value break end end if not itemWasFound then -- if not, try to find one that we have an recipe for local foundAny = false for _, value in ipairs(recipeItemOptions) do assert(value ~= "", "candidate was empty?") if loadedRecipes[value] then -- if this item is craftable, queue it currentItemsNeededToCraft[value] = (currentItemsNeededToCraft[value] or 0) + 1 allIngredients[slotIdx] = value foundAny = true end end if not foundAny then local missingKey = tblToKeyStr(recipeItemOptions) missingItems[missingKey] = (missingItems[missingKey] or 0) + 1 recipeHasFailed = true end end else allIngredients[slotIdx] = "nil" end end -- store the recipe in the form tblToKeyStr({*ingredients, result}) inside plannedCraftSteps if not recipeHasFailed then print("--- concrete recipe: ---") pp(allIngredients) local key = tblToKeyStr(allIngredients)..";"..itemNameToCraft if false and plannedCraftSteps[#plannedCraftSteps][1] == key then -- TODO can do an optimization here where we craft the item one more time at once, assuming all inputs and the output stack to big enough sizes else table.insert(plannedCraftSteps, {key, 1}) end end itemsLeftToCraft = itemsLeftToCraft - recipe.resultCount --print(("iteration finished, we need to craft %s more"):format(math.max(0,itemsLeftToCraft))) end print((next(missingItems) == nil) and "yay" or "uh oh looks like we are missing some items for this crafting step") return 0 end local function safeCraft(...) while true do local cooldown = crafter.cooldownRemaining if cooldown <= 0 then break end sleep(cooldown) end return crafter:craft(...) end ------------------------------------------------------------------------------------------------------- local keyEvents = {} kernel:registerEventCallback("charTyped", function(...) table.insert(keyEvents, {...}) end) kernel:registerEventCallback("keyPressed", function(...) table.insert(keyEvents, {...}) end) ---@param eventFilterOrNil "charTyped"|"keyPressed"|nil local function waitForInputEvent(eventFilterOrNil) sleep(0) while #keyEvents == 0 do sleep(0.5) end local res = table.remove(keyEvents, 1) if eventFilterOrNil == nil or res[1] == eventFilterOrNil then return res else return waitForInputEvent(eventFilterOrNil) end end print(("We want to craft %sx %s"):format(itemToCraftAmount, itemToCraftInternalName)) currentItemsNeededToCraft[itemToCraftInternalName] = itemToCraftAmount while true do print("new crafting iteration") pp(currentItemsNeededToCraft) local toCraft = next(currentItemsNeededToCraft) if not toCraft then break end -- try to obtain item "toCraft" assert(type(toCraft) == "string") local toCraft_amount = currentItemsNeededToCraft[toCraft] local amountLeft = planCraftForItem(toCraft, toCraft_amount) print("amount left in current step:", amountLeft) assert(amountLeft <= toCraft_amount) if amountLeft == toCraft_amount then -- if we made no progress, we neither have it in stock nor have a recipe for it --> mark it as missing local tblKey = toCraft missingItems[tblKey] = (missingItems[tblKey] or 0) + amountLeft currentItemsNeededToCraft[toCraft] = nil elseif amountLeft == 0 then currentItemsNeededToCraft[toCraft] = nil end end local canCraft = (next(missingItems) == nil) if canCraft then print("We have all the needed items. Do we start crafting? [y/n]. Will consume the following ingredients to craft "..itemToCraftAmount.." '"..itemToCraftInternalName.."':") local keys = {} for k,_ in pairs(itemsToConsume) do table.insert(keys, k) end table.sort(keys, function (x, y) return itemsToConsume[x] > itemsToConsume[y] end) local padsize = #tostring(itemsToConsume[keys[1]]) for _, k in ipairs(keys) do local v = itemsToConsume[k] print(string.format("%sx%s: %s", v, string.rep(" ", padsize-#tostring(v)), k)) end local doCraft = nil while doCraft == nil do local evnt = waitForInputEvent("charTyped") print("event:", evnt) local char = evnt[2]:lower() if char == "y" or char == "\n" then doCraft = true elseif char == "n" then doCraft = false end end if not doCraft then print("Aborted by user.") return end print("Crafting...") for i = #plannedCraftSteps, 1, -1 do local currRecipeToCraft = plannedCraftSteps[i] print(("Current step (%d/%d): %s"):format(#plannedCraftSteps-i+1, #plannedCraftSteps, currRecipeToCraft[1])) local ingredientsAndResult = keyStrToTbl(currRecipeToCraft[1]) for i = 1, #ingredientsAndResult do if ingredientsAndResult[i] == "nil" then ingredientsAndResult[i] = nil elseif i < #ingredientsAndResult then -- for all ingredients me_PushIntoCrafter(ingredientsAndResult[i], 1) end end local resultName = ingredientsAndResult[#ingredientsAndResult] local recipeResultCount = loadedRecipes[resultName].resultCount -- how many results we get from one craft local recipeCraftCount = currRecipeToCraft[2] safeCraft( table.pack(table.unpack(ingredientsAndResult, 1, #ingredientsAndResult-1)), -- ingredients resultName, -- result recipeCraftCount, -- how often we craft it false -- outputIntoOwnInventory ) print(("noting that we have crafted '%s'."):format(resultName)) itemsToConsume[resultName] = (itemsToConsume[resultName] or 0) + recipeCraftCount * recipeResultCount -- account for intermediate crafting recipes end print(("Done, you are now the proud owner of %sx %s!"):format(itemToCraftAmount, itemToCraftInternalName)) else print("Cannot craft, we are missing the following items / recipes:") for k,v in pairs(missingItems) do print(string.format("%sx%s: %s", v, string.rep(" ", #tostring(v)), k)) end end