Add tag based ore substitution

This commit is contained in:
2026-07-19 01:14:00 +02:00
parent 49e4363cd9
commit 76e734f6ba
2 changed files with 91 additions and 25 deletions
+6
View File
@@ -40,6 +40,12 @@ Vorrang. Nur externe, im Create-Netz fehlende Rohstoffe werden in exakt der
fehlenden Menge aus dem ME in `sophisticatedstorage:chest_0` exportiert. Diese fehlenden Menge aus dem ME in `sophisticatedstorage:chest_0` exportiert. Diese
erste Stufe startet bewusst noch kein ME-Autocrafting. erste Stufe startet bewusst noch kein ME-Autocrafting.
V1.5 ergaenzt Ore-/Tag-Substitution fuer vereinheitlichte Materialien. Ingots,
Nuggets, Plates, Dusts, Gears, Rods und Clumps werden ueber ihre gemeinsamen
`c:`-Tags erkannt. Dadurch sind beispielsweise `create:brass_ingot` und
`alltheores:brass_ingot` fuer Lagerzaehlung, Create-Versand und ME-Export
gleichwertig. Eindeutige Create-Bauteile verwenden weiterhin exakte IDs.
## Create-Fabrik: Peripherie erkennen ## Create-Fabrik: Peripherie erkennen
Das rein lesende Diagnoseprogramm herunterladen und starten: Das rein lesende Diagnoseprogramm herunterladen und starten:
+82 -22
View File
@@ -1,7 +1,7 @@
-- ATM10 Create factory scheduler V1 -- ATM10 Create factory scheduler V1
-- Persistent jobs, dependency planning, progress-aware retries and diagnostics. -- Persistent jobs, dependency planning, progress-aware retries and diagnostics.
local VERSION = "1.4.1" local VERSION = "1.5.0"
local REFRESH_SECONDS = 1 local REFRESH_SECONDS = 1
local CONFIRM_SECONDS = 6 local CONFIRM_SECONDS = 6
local DRAIN_SECONDS = 60 local DRAIN_SECONDS = 60
@@ -218,6 +218,51 @@ local statusText, statusColor = "Scheduler bereit", colors.lime
local errorPage = 1 local errorPage = 1
local actionPage = 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 function formatNumber(value)
local text = tostring(math.floor(tonumber(value) or 0)) local text = tostring(math.floor(tonumber(value) or 0))
local sign, digits = text:match("^([%-]?)(%d+)$") local sign, digits = text:match("^([%-]?)(%d+)$")
@@ -390,21 +435,31 @@ end
local function setBaseline(job, item) local function setBaseline(job, item)
if job.baseline[item] == nil then 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 = job.arrived or {}
job.arrived[item] = job.arrived[item] or 0 job.arrived[item] = job.arrived[item] or 0
end end
end end
local function request(address, item, count) local function request(address, item, count)
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, { local ok, result = pcall(peripheral.call, tickerName, "requestFiltered", address, {
name = item, _requestCount = math.max(1, math.floor(count)), name = candidate, _requestCount = wanted,
}) })
if not ok then return 0, tostring(result) end if ok then
local sent = tonumber(result) or 0 local sent = tonumber(result) or 0
-- Prevent two jobs in the same scheduler tick from reserving the same input. if sent > 0 then
if sent > 0 then stock[item] = math.max(0, (stock[item] or 0) - sent) end stock[candidate] = math.max(0, (stock[candidate] or 0) - sent)
return sent sentTotal, remaining = sentTotal + sent, remaining - sent
end
else
lastError = tostring(result)
end
end
return sentTotal, lastError
end end
local clearMEWait local clearMEWait
@@ -432,7 +487,7 @@ local function observeItems(job, items)
local changed = false local changed = false
job.arrived = job.arrived or {} job.arrived = job.arrived or {}
for _, item in ipairs(items) do for _, item in ipairs(items) do
local current = stock[item] or 0 local current = stockCount(item)
local previous = job.lastObserved[item] local previous = job.lastObserved[item]
if previous == nil then previous = current end if previous == nil then previous = current end
if current > previous then if current > previous then
@@ -457,7 +512,7 @@ end
local function waitForME(job, item, required) local function waitForME(job, item, required)
-- Create always has priority. Only the difference between the complete -- Create always has priority. Only the difference between the complete
-- requirement and the currently visible Create stock may leave the ME. -- 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 if createAvailable >= required then
clearMEWait(job) clearMEWait(job)
return true return true
@@ -484,17 +539,22 @@ local function waitForME(job, item, required)
job.status, job.activeStation, job.waitingFor = "WAITING_ME", nil, item job.status, job.activeStation, job.waitingFor = "WAITING_ME", nil, item
if epoch() - (job.meRequestedAt or epoch()) < ME_TRANSFER_TIMEOUT then return false end if epoch() - (job.meRequestedAt or epoch()) < ME_TRANSFER_TIMEOUT then return false end
clearMEWait(job) clearMEWait(job)
createAvailable = stock[item] or 0 createAvailable = stockCount(item)
if createAvailable >= required then return true end if createAvailable >= required then return true end
elseif job.meWaitingFor then elseif job.meWaitingFor then
clearMEWait(job) clearMEWait(job)
end end
local missing = math.max(1, required - createAvailable) 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 local meAvailable = 0
if okItem and type(meItem) == "table" then if okItems and type(meItems) == "table" then
meAvailable = tonumber(meItem.amount or meItem.count) or 0 for _, meItem in pairs(meItems) do
if type(meItem) == "table" then
meAvailable = meAvailable + (tonumber(meItem.amount or meItem.count) or 0)
end
end
end end
if meAvailable <= 0 then if meAvailable <= 0 then
failJob(job, "Zu wenig Input: " .. prettyItemName(item) failJob(job, "Zu wenig Input: " .. prettyItemName(item)
@@ -505,7 +565,7 @@ local function waitForME(job, item, required)
local wanted = math.min(missing, meAvailable) local wanted = math.min(missing, meAvailable)
local okExport, result = pcall(peripheral.call, meBridgeName, "exportItem", 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 local moved = okExport and tonumber(result) or 0
if moved <= 0 then if moved <= 0 then
failJob(job, "ME-Export fehlgeschlagen: " .. prettyItemName(item) failJob(job, "ME-Export fehlgeschlagen: " .. prettyItemName(item)
@@ -568,7 +628,7 @@ local function waitForDependency(job, item, required)
job.dependencyJobId = nil job.dependencyJobId = nil
end end
local missing = math.max(1, required - (stock[item] or 0)) local missing = math.max(1, required - stockCount(item))
local child = findJobByRecipe(producerId) local child = findJobByRecipe(producerId)
if not child then child = newJob(producerId, missing, "DEPENDENCY", job.id) end if not child then child = newJob(producerId, missing, "DEPENDENCY", job.id) end
job.status, job.activeStation = "WAITING_INPUT", nil 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 -- Requirements are kept in the logistics network and are not sent with the
-- main package. Precision Mechanisms use this for their deployer buffers. -- main package. Precision Mechanisms use this for their deployer buffers.
for _, requirement in ipairs(recipe.requires or {}) do 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) return waitForDependency(job, requirement.item, requirement.count)
end end
end end
@@ -618,7 +678,7 @@ local function dispatchNormal(job, missingOutput)
-- Check the complete recipe before sending its first package. This prevents -- Check the complete recipe before sending its first package. This prevents
-- a mixer basin from receiving only one half of a planned batch. -- a mixer basin from receiving only one half of a planned batch.
for _, ingredient in ipairs(inputPlan) do 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) return waitForDependency(job, ingredient.item, ingredient.count)
end end
end end
@@ -695,7 +755,7 @@ end
local function dispatchMetalCrushing(job, remaining, plan) local function dispatchMetalCrushing(job, remaining, plan)
if stationBusy("crushing", job) then return end if stationBusy("crushing", job) then return end
local required = math.max(1, math.min(64, math.ceil(remaining / 3))) 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 if wanted <= 0 then
waitForME(job, plan.source, required) waitForME(job, plan.source, required)
return return
@@ -709,7 +769,7 @@ end
local function dispatchMetalWashing(job, remaining, plan) local function dispatchMetalWashing(job, remaining, plan)
if stationBusy("fan:washing", job) then return end 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 if wanted <= 0 then dispatchMetalCrushing(job, remaining, plan) return end
local sent, err = request("fan:washing", plan.clump, wanted) 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 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 -- Do not wait for the complete 60-second crushing drain when the material
-- which has already reached storage can satisfy the remaining parent job. -- which has already reached storage can satisfy the remaining parent job.
-- Late crushing returns remain valid surplus and are still counted. -- 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" job.activeStation, job.status, job.phase = nil, "WAITING", "PLAN"
logEvent("#" .. job.id .. " genug Clumps angekommen - sofort weiter zum Washing", colors.lime) logEvent("#" .. job.id .. " genug Clumps angekommen - sofort weiter zum Washing", colors.lime)
saveState() saveState()
@@ -805,7 +865,7 @@ end
local function maintainStock() local function maintainStock()
if state.mode ~= "AUTO" then return end if state.mode ~= "AUTO" then return end
for _, rule in ipairs(KEEP_STOCK) do 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 if current < rule.target and not findJobByRecipe(rule.recipeId) then
local missing = rule.target - current local missing = rule.target - current
newJob(rule.recipeId, missing, "AUTO") newJob(rule.recipeId, missing, "AUTO")
@@ -872,7 +932,7 @@ local function progressText(job)
end end
if job.status == "WAITING_ME" then if job.status == "WAITING_ME" then
return "ME: " .. prettyItemName(job.meWaitingFor or job.waitingFor or "Input") 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) .. "/" .. formatNumber(job.meRequired or 0)
end end
if recipe and recipe.completion == "activity" then if recipe and recipe.completion == "activity" then