Add tag based ore substitution
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
-- ATM10 Create factory scheduler V1
|
||||
-- Persistent jobs, dependency planning, progress-aware retries and diagnostics.
|
||||
|
||||
local VERSION = "1.4.1"
|
||||
local VERSION = "1.5.0"
|
||||
local REFRESH_SECONDS = 1
|
||||
local CONFIRM_SECONDS = 6
|
||||
local DRAIN_SECONDS = 60
|
||||
@@ -218,6 +218,51 @@ local statusText, statusColor = "Scheduler bereit", colors.lime
|
||||
local errorPage = 1
|
||||
local actionPage = 1
|
||||
|
||||
-- ATM10/Almost Unified uses the common NeoForge `c:` material tags. Resolve
|
||||
-- ordinary metal forms by tag so e.g. create:brass_ingot and
|
||||
-- alltheores:brass_ingot behave as the same ingredient everywhere.
|
||||
local MATERIAL_FORMS = {
|
||||
ingot = "ingots", nugget = "nuggets", plate = "plates", dust = "dusts",
|
||||
gear = "gears", rod = "rods", clump = "clumps",
|
||||
}
|
||||
|
||||
local function materialTag(item)
|
||||
if type(item) ~= "string" then return nil end
|
||||
local path = item:match("^[^:]+:(.+)$")
|
||||
if not path then return nil end
|
||||
for singular, plural in pairs(MATERIAL_FORMS) do
|
||||
local material = path:match("^(.+)_" .. singular .. "$")
|
||||
if material then return "#c:" .. plural .. "/" .. material end
|
||||
end
|
||||
end
|
||||
|
||||
local function equivalentItem(left, right)
|
||||
if left == right then return true end
|
||||
local leftTag, rightTag = materialTag(left), materialTag(right)
|
||||
return leftTag ~= nil and leftTag == rightTag
|
||||
end
|
||||
|
||||
local function stockCount(item)
|
||||
local total = 0
|
||||
for storedItem, count in pairs(stock) do
|
||||
if equivalentItem(item, storedItem) then total = total + (tonumber(count) or 0) end
|
||||
end
|
||||
return total
|
||||
end
|
||||
|
||||
local function stockCandidates(item)
|
||||
local candidates, alternatives = {}, {}
|
||||
if (stock[item] or 0) > 0 then candidates[#candidates + 1] = item end
|
||||
for storedItem, count in pairs(stock) do
|
||||
if storedItem ~= item and (tonumber(count) or 0) > 0 and equivalentItem(item, storedItem) then
|
||||
alternatives[#alternatives + 1] = storedItem
|
||||
end
|
||||
end
|
||||
table.sort(alternatives)
|
||||
for _, storedItem in ipairs(alternatives) do candidates[#candidates + 1] = storedItem end
|
||||
return candidates
|
||||
end
|
||||
|
||||
local function formatNumber(value)
|
||||
local text = tostring(math.floor(tonumber(value) or 0))
|
||||
local sign, digits = text:match("^([%-]?)(%d+)$")
|
||||
@@ -390,21 +435,31 @@ end
|
||||
|
||||
local function setBaseline(job, item)
|
||||
if job.baseline[item] == nil then
|
||||
job.baseline[item], job.lastObserved[item] = stock[item] or 0, stock[item] or 0
|
||||
job.baseline[item], job.lastObserved[item] = stockCount(item), stockCount(item)
|
||||
job.arrived = job.arrived or {}
|
||||
job.arrived[item] = job.arrived[item] or 0
|
||||
end
|
||||
end
|
||||
|
||||
local function request(address, item, count)
|
||||
local ok, result = pcall(peripheral.call, tickerName, "requestFiltered", address, {
|
||||
name = item, _requestCount = math.max(1, math.floor(count)),
|
||||
})
|
||||
if not ok then return 0, tostring(result) end
|
||||
local sent = tonumber(result) or 0
|
||||
-- Prevent two jobs in the same scheduler tick from reserving the same input.
|
||||
if sent > 0 then stock[item] = math.max(0, (stock[item] or 0) - sent) end
|
||||
return sent
|
||||
local remaining, sentTotal, lastError = math.max(1, math.floor(count)), 0, nil
|
||||
for _, candidate in ipairs(stockCandidates(item)) do
|
||||
if remaining <= 0 then break end
|
||||
local wanted = math.min(remaining, stock[candidate] or 0)
|
||||
local ok, result = pcall(peripheral.call, tickerName, "requestFiltered", address, {
|
||||
name = candidate, _requestCount = wanted,
|
||||
})
|
||||
if ok then
|
||||
local sent = tonumber(result) or 0
|
||||
if sent > 0 then
|
||||
stock[candidate] = math.max(0, (stock[candidate] or 0) - sent)
|
||||
sentTotal, remaining = sentTotal + sent, remaining - sent
|
||||
end
|
||||
else
|
||||
lastError = tostring(result)
|
||||
end
|
||||
end
|
||||
return sentTotal, lastError
|
||||
end
|
||||
|
||||
local clearMEWait
|
||||
@@ -432,7 +487,7 @@ local function observeItems(job, items)
|
||||
local changed = false
|
||||
job.arrived = job.arrived or {}
|
||||
for _, item in ipairs(items) do
|
||||
local current = stock[item] or 0
|
||||
local current = stockCount(item)
|
||||
local previous = job.lastObserved[item]
|
||||
if previous == nil then previous = current end
|
||||
if current > previous then
|
||||
@@ -457,7 +512,7 @@ end
|
||||
local function waitForME(job, item, required)
|
||||
-- Create always has priority. Only the difference between the complete
|
||||
-- requirement and the currently visible Create stock may leave the ME.
|
||||
local createAvailable = stock[item] or 0
|
||||
local createAvailable = stockCount(item)
|
||||
if createAvailable >= required then
|
||||
clearMEWait(job)
|
||||
return true
|
||||
@@ -484,17 +539,22 @@ local function waitForME(job, item, required)
|
||||
job.status, job.activeStation, job.waitingFor = "WAITING_ME", nil, item
|
||||
if epoch() - (job.meRequestedAt or epoch()) < ME_TRANSFER_TIMEOUT then return false end
|
||||
clearMEWait(job)
|
||||
createAvailable = stock[item] or 0
|
||||
createAvailable = stockCount(item)
|
||||
if createAvailable >= required then return true end
|
||||
elseif job.meWaitingFor then
|
||||
clearMEWait(job)
|
||||
end
|
||||
|
||||
local missing = math.max(1, required - createAvailable)
|
||||
local okItem, meItem = pcall(peripheral.call, meBridgeName, "getItem", { name = item })
|
||||
local meFilter = materialTag(item) or item
|
||||
local okItems, meItems = pcall(peripheral.call, meBridgeName, "getItems", { name = meFilter })
|
||||
local meAvailable = 0
|
||||
if okItem and type(meItem) == "table" then
|
||||
meAvailable = tonumber(meItem.amount or meItem.count) or 0
|
||||
if okItems and type(meItems) == "table" then
|
||||
for _, meItem in pairs(meItems) do
|
||||
if type(meItem) == "table" then
|
||||
meAvailable = meAvailable + (tonumber(meItem.amount or meItem.count) or 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
if meAvailable <= 0 then
|
||||
failJob(job, "Zu wenig Input: " .. prettyItemName(item)
|
||||
@@ -505,7 +565,7 @@ local function waitForME(job, item, required)
|
||||
|
||||
local wanted = math.min(missing, meAvailable)
|
||||
local okExport, result = pcall(peripheral.call, meBridgeName, "exportItem",
|
||||
{ name = item, count = wanted }, ME_BUFFER_NAME)
|
||||
{ name = meFilter, count = wanted }, ME_BUFFER_NAME)
|
||||
local moved = okExport and tonumber(result) or 0
|
||||
if moved <= 0 then
|
||||
failJob(job, "ME-Export fehlgeschlagen: " .. prettyItemName(item)
|
||||
@@ -568,7 +628,7 @@ local function waitForDependency(job, item, required)
|
||||
job.dependencyJobId = nil
|
||||
end
|
||||
|
||||
local missing = math.max(1, required - (stock[item] or 0))
|
||||
local missing = math.max(1, required - stockCount(item))
|
||||
local child = findJobByRecipe(producerId)
|
||||
if not child then child = newJob(producerId, missing, "DEPENDENCY", job.id) end
|
||||
job.status, job.activeStation = "WAITING_INPUT", nil
|
||||
@@ -610,7 +670,7 @@ local function dispatchNormal(job, missingOutput)
|
||||
-- Requirements are kept in the logistics network and are not sent with the
|
||||
-- main package. Precision Mechanisms use this for their deployer buffers.
|
||||
for _, requirement in ipairs(recipe.requires or {}) do
|
||||
if (stock[requirement.item] or 0) < requirement.count then
|
||||
if stockCount(requirement.item) < requirement.count then
|
||||
return waitForDependency(job, requirement.item, requirement.count)
|
||||
end
|
||||
end
|
||||
@@ -618,7 +678,7 @@ local function dispatchNormal(job, missingOutput)
|
||||
-- Check the complete recipe before sending its first package. This prevents
|
||||
-- a mixer basin from receiving only one half of a planned batch.
|
||||
for _, ingredient in ipairs(inputPlan) do
|
||||
if ingredient.count <= 0 or (stock[ingredient.item] or 0) < ingredient.count then
|
||||
if ingredient.count <= 0 or stockCount(ingredient.item) < ingredient.count then
|
||||
return waitForDependency(job, ingredient.item, ingredient.count)
|
||||
end
|
||||
end
|
||||
@@ -695,7 +755,7 @@ end
|
||||
local function dispatchMetalCrushing(job, remaining, plan)
|
||||
if stationBusy("crushing", job) then return end
|
||||
local required = math.max(1, math.min(64, math.ceil(remaining / 3)))
|
||||
local wanted = math.min(stock[plan.source] or 0, required)
|
||||
local wanted = math.min(stockCount(plan.source), required)
|
||||
if wanted <= 0 then
|
||||
waitForME(job, plan.source, required)
|
||||
return
|
||||
@@ -709,7 +769,7 @@ end
|
||||
|
||||
local function dispatchMetalWashing(job, remaining, plan)
|
||||
if stationBusy("fan:washing", job) then return end
|
||||
local wanted = math.min(stock[plan.clump] or 0, math.ceil(remaining / 9))
|
||||
local wanted = math.min(stockCount(plan.clump), math.ceil(remaining / 9))
|
||||
if wanted <= 0 then dispatchMetalCrushing(job, remaining, plan) return end
|
||||
local sent, err = request("fan:washing", plan.clump, wanted)
|
||||
if sent <= 0 then failJob(job, plan.name .. "-Clump-Versand fehlgeschlagen: " .. tostring(err or "unbekannt")) return end
|
||||
@@ -741,7 +801,7 @@ local function updateMetal(job)
|
||||
-- Do not wait for the complete 60-second crushing drain when the material
|
||||
-- which has already reached storage can satisfy the remaining parent job.
|
||||
-- Late crushing returns remain valid surplus and are still counted.
|
||||
if job.phase == "CRUSHING" and (stock[plan.clump] or 0) * 9 >= remaining then
|
||||
if job.phase == "CRUSHING" and stockCount(plan.clump) * 9 >= remaining then
|
||||
job.activeStation, job.status, job.phase = nil, "WAITING", "PLAN"
|
||||
logEvent("#" .. job.id .. " genug Clumps angekommen - sofort weiter zum Washing", colors.lime)
|
||||
saveState()
|
||||
@@ -805,7 +865,7 @@ end
|
||||
local function maintainStock()
|
||||
if state.mode ~= "AUTO" then return end
|
||||
for _, rule in ipairs(KEEP_STOCK) do
|
||||
local current = stock[rule.item] or 0
|
||||
local current = stockCount(rule.item)
|
||||
if current < rule.target and not findJobByRecipe(rule.recipeId) then
|
||||
local missing = rule.target - current
|
||||
newJob(rule.recipeId, missing, "AUTO")
|
||||
@@ -872,7 +932,7 @@ local function progressText(job)
|
||||
end
|
||||
if job.status == "WAITING_ME" then
|
||||
return "ME: " .. prettyItemName(job.meWaitingFor or job.waitingFor or "Input")
|
||||
.. " " .. formatNumber(stock[job.meWaitingFor or ""] or 0)
|
||||
.. " " .. formatNumber(stockCount(job.meWaitingFor or ""))
|
||||
.. "/" .. formatNumber(job.meRequired or 0)
|
||||
end
|
||||
if recipe and recipe.completion == "activity" then
|
||||
|
||||
Reference in New Issue
Block a user