rewrite autocrafting script
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
|
||||
|
||||
--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<itemInternalName, amount>; 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<itemInternalName[], amount>
|
||||
local currentItemsNeededToCraft = {} -- dict<keyStrToTbl(itemInternalName[]), amount>
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user