Add recursive factory dependencies and press pool

This commit is contained in:
2026-07-18 21:03:43 +02:00
parent c963557649
commit eee51ee05a
2 changed files with 145 additions and 13 deletions
+7
View File
@@ -21,6 +21,13 @@ erste Keep-Stock-Regler kleine Cogwheels, grosse Cogwheels und Iron Nuggets auf
jeweils 64 Stueck. Mixer-, Casing- und Precision-Rezepte sind ueber blätterbare
Rezeptseiten erreichbar.
Seit V1.3 erzeugt der Scheduler fehlende Zwischenprodukte als persistente
Vorauftraege. Beispielsweise wartet ein Andesite-Casing-Auftrag automatisch auf
fehlendes Andesite Alloy. `presse:1` bis `presse:6` bilden einen gemeinsamen Pool
fuer Gold-, Iron-, Copper-, Brass-, Zinc- und Steel-Plates. Precision Mechanisms
verwenden in ATM10 `alltheores:gold_plate` und warten vor dem Start auf ihre
Cogwheel- und Iron-Nugget-Puffer.
## Create-Fabrik: Peripherie erkennen
Das rein lesende Diagnoseprogramm herunterladen und starten:
+138 -13
View File
@@ -1,7 +1,7 @@
-- ATM10 Create factory scheduler V1
-- Persistent jobs, dependency planning, progress-aware retries and diagnostics.
local VERSION = "1.2.0"
local VERSION = "1.3.0"
local REFRESH_SECONDS = 1
local CONFIRM_SECONDS = 6
local DRAIN_SECONDS = 60
@@ -98,9 +98,44 @@ local RECIPES = {
},
precision_mechanism = {
label = "16 GOLD SHEETS -> PRECISION", station = "precisionmechanism", address = "precisionmechanism",
input = "create:golden_sheet", inputCount = 16,
input = "alltheores:gold_plate", inputCount = 16,
requires = {
{ item = "create:cogwheel", count = 80 },
{ item = "create:large_cogwheel", count = 80 },
{ item = "minecraft:iron_nugget", count = 80 },
},
outputs = { "create:precision_mechanism" }, completion = "activity",
},
press_gold = {
label = "64 GOLD PLATES", stations = { "presse:1", "presse:2", "presse:3", "presse:4", "presse:5", "presse:6" },
input = "minecraft:gold_ingot", inputCount = 64,
output = "alltheores:gold_plate", outputCount = 64, targetCount = 64,
},
press_iron = {
label = "64 IRON PLATES", stations = { "presse:1", "presse:2", "presse:3", "presse:4", "presse:5", "presse:6" },
input = "minecraft:iron_ingot", inputCount = 64,
output = "alltheores:iron_plate", outputCount = 64, targetCount = 64,
},
press_copper = {
label = "64 COPPER PLATES", stations = { "presse:1", "presse:2", "presse:3", "presse:4", "presse:5", "presse:6" },
input = "minecraft:copper_ingot", inputCount = 64,
output = "alltheores:copper_plate", outputCount = 64, targetCount = 64,
},
press_brass = {
label = "64 BRASS PLATES", stations = { "presse:1", "presse:2", "presse:3", "presse:4", "presse:5", "presse:6" },
input = "create:brass_ingot", inputCount = 64,
output = "alltheores:brass_plate", outputCount = 64, targetCount = 64,
},
press_zinc = {
label = "64 ZINC PLATES", stations = { "presse:1", "presse:2", "presse:3", "presse:4", "presse:5", "presse:6" },
input = "alltheores:zinc_ingot", inputCount = 64,
output = "alltheores:zinc_plate", outputCount = 64, targetCount = 64,
},
press_steel = {
label = "64 STEEL PLATES", stations = { "presse:1", "presse:2", "presse:3", "presse:4", "presse:5", "presse:6" },
input = "alltheores:steel_ingot", inputCount = 64,
output = "alltheores:steel_plate", outputCount = 64, targetCount = 64,
},
zinc_nuggets = {
label = "576 ZINC NUGGETS", planner = "zinc_nuggets",
output = "alltheores:zinc_nugget", targetCount = 576,
@@ -116,6 +151,7 @@ local ACTION_ORDER = {
"iron_nuggets",
"mixer_alloy", "mixer_cogwheel", "mixer_large_cogwheel",
"casing_andesite", "casing_copper", "casing_brass", "casing_railway", "precision_mechanism",
"press_gold", "press_iron", "press_copper", "press_brass", "press_zinc", "press_steel",
}
local KEEP_STOCK = {
@@ -135,6 +171,27 @@ local METAL_PLANS = {
},
}
local PRODUCER_BY_ITEM = {
["minecraft:stripped_oak_log"] = "strip_oak",
["minecraft:oak_planks"] = "planks_oak",
["create:shaft"] = "shafts",
["create:andesite_alloy"] = "mixer_alloy",
["create:cogwheel"] = "mixer_cogwheel",
["create:large_cogwheel"] = "mixer_large_cogwheel",
["alltheores:zinc_nugget"] = "zinc_nuggets",
["minecraft:iron_nugget"] = "iron_nuggets",
["create:andesite_casing"] = "casing_andesite",
["create:copper_casing"] = "casing_copper",
["create:brass_casing"] = "casing_brass",
["create:railway_casing"] = "casing_railway",
["alltheores:gold_plate"] = "press_gold",
["alltheores:iron_plate"] = "press_iron",
["alltheores:copper_plate"] = "press_copper",
["alltheores:brass_plate"] = "press_brass",
["alltheores:zinc_plate"] = "press_zinc",
["alltheores:steel_plate"] = "press_steel",
}
local function epoch()
if os.epoch then return math.floor(os.epoch("utc") / 1000) end
return math.floor(os.clock())
@@ -303,6 +360,12 @@ local function findJobByRecipe(recipeId)
end
end
local function findJobById(jobId)
for _, job in ipairs(state.jobs) do
if job.id == jobId then return job end
end
end
local function stationBusy(station, exceptJob)
for _, job in ipairs(state.jobs) do
if job ~= exceptJob and not terminal(job) and job.activeStation == station then return true end
@@ -310,6 +373,17 @@ local function stationBusy(station, exceptJob)
return false
end
local function chooseStation(recipe, job)
if recipe.stations then
for _, station in ipairs(recipe.stations) do
if not stationBusy(station, job) then return station end
end
return nil
end
if stationBusy(recipe.station, job) then return nil end
return recipe.station
end
local function produced(job, item)
-- Count real positive arrivals. Unlike a stock difference this never drops
-- when another factory step consumes the output while the job is running.
@@ -372,9 +446,47 @@ local function observeItems(job, items)
return changed
end
local newJob
local function waitForDependency(job, item, required)
local producerId = PRODUCER_BY_ITEM[item]
if not producerId or producerId == job.recipeId then
failJob(job, "Zu wenig Input: " .. prettyItemName(item)
.. " (" .. formatNumber(stock[item] or 0) .. "/" .. formatNumber(required) .. ")")
return false
end
if job.dependencyJobId then
local previous = findJobById(job.dependencyJobId)
if previous and (previous.status == "FAILED" or previous.status == "CANCELLED") then
failJob(job, "Vorauftrag #" .. previous.id .. " fuer " .. prettyItemName(item)
.. " ist " .. previous.status)
return false
elseif previous and not terminal(previous) then
job.status, job.activeStation, job.waitingFor = "WAITING_INPUT", nil, item
return false
end
job.dependencyJobId = nil
end
local missing = math.max(1, required - (stock[item] or 0))
local child = findJobByRecipe(producerId)
if not child then child = newJob(producerId, missing, "DEPENDENCY", job.id) end
job.status, job.activeStation = "WAITING_INPUT", nil
job.waitingFor, job.dependencyJobId = item, child and child.id or nil
if job.lastDependencyLog ~= item then
job.lastDependencyLog = item
logEvent("#" .. job.id .. " wartet auf " .. prettyItemName(item)
.. " (Vorauftrag #" .. tostring(child and child.id or "?") .. ")", colors.cyan)
end
saveState()
return false
end
local function dispatchNormal(job, missingOutput)
local recipe = RECIPES[job.recipeId]
if stationBusy(recipe.station, job) then job.status = "WAITING" return false end
local selectedStation = chooseStation(recipe, job)
if not selectedStation then job.status = "WAITING" return false end
local inputPlan = {}
if recipe.inputs then
@@ -392,20 +504,25 @@ local function dispatchNormal(job, missingOutput)
inputPlan[1] = { item = recipe.input, count = desiredInput }
end
-- 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
return waitForDependency(job, requirement.item, requirement.count)
end
end
-- 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
failJob(job, "Zu wenig Input: " .. prettyItemName(ingredient.item)
.. " (" .. formatNumber(stock[ingredient.item] or 0)
.. "/" .. formatNumber(ingredient.count) .. ")")
return false
return waitForDependency(job, ingredient.item, ingredient.count)
end
end
local sentInputs = {}
for _, ingredient in ipairs(inputPlan) do
local sent, err = request(recipe.address, ingredient.item, ingredient.count)
local sent, err = request(recipe.address or selectedStation, ingredient.item, ingredient.count)
if sent ~= ingredient.count then
failJob(job, "Teilversand " .. prettyItemName(ingredient.item)
.. ": " .. formatNumber(sent) .. "/" .. formatNumber(ingredient.count)
@@ -418,7 +535,8 @@ local function dispatchNormal(job, missingOutput)
job.lastBatchInputs = sentInputs
job.lastBatchInput = nil
job.activeStation, job.status = recipe.station, "RUNNING"
job.activeStation, job.status = selectedStation, "RUNNING"
job.waitingFor, job.dependencyJobId, job.lastDependencyLog = nil, nil, nil
job.phaseStartedAt, job.lastProgressAt, job.warned = epoch(), epoch(), false
local inputTotal = 0
for _, count in pairs(sentInputs) do inputTotal = inputTotal + count end
@@ -440,6 +558,10 @@ local function updateNormal(job)
local watched = recipe.outputs or { recipe.output }
observeItems(job, watched)
if recipe.completion == "activity" then
if job.status == "WAITING" or job.status == "WAITING_INPUT" then
dispatchNormal(job, 1)
return
end
local total = 0
for _, item in ipairs(watched) do total = total + produced(job, item) end
job.progress = total
@@ -456,7 +578,7 @@ local function updateNormal(job)
if epoch() - job.lastProgressAt >= DRAIN_SECONDS then finishJob(job) end
return
end
if job.status == "WAITING" then dispatchNormal(job, missing)
if job.status == "WAITING" or job.status == "WAITING_INPUT" then dispatchNormal(job, missing)
elseif job.status == "DRAINING" and epoch() - job.lastProgressAt >= DRAIN_SECONDS then
retryOrFail(job, missing, "Nachlauf beendet, Output unvollstaendig")
elseif job.status == "RUNNING" then
@@ -534,8 +656,6 @@ local function updateMetal(job)
elseif idle >= DRAIN_SECONDS then retryOrFail(job, remaining, plan.name .. "-Auftrag ohne Fortschritt") end
end
local newJob
local function updateJobs()
if state.mode == "OFF" then return end
for _, job in ipairs(state.jobs) do
@@ -548,13 +668,14 @@ local function updateJobs()
end
end
newJob = function(recipeId, targetCount, origin)
newJob = function(recipeId, targetCount, origin, parentJobId)
local recipe = RECIPES[recipeId]
local job = {
id = state.nextJobId, recipeId = recipeId, status = "WAITING",
phase = recipe.planner and "PLAN" or "NORMAL",
targetCount = targetCount or recipe.targetCount or recipe.outputCount or 1,
origin = origin or "MANUAL",
parentJobId = parentJobId,
baseline = {}, lastObserved = {}, arrived = {}, progress = 0, retries = 0,
createdAt = epoch(), lastProgressAt = epoch(), phaseStartedAt = epoch(),
}
@@ -631,6 +752,10 @@ end
local function progressText(job)
local recipe = RECIPES[job.recipeId]
local idle = math.max(0, epoch() - (job.lastProgressAt or epoch()))
if job.status == "WAITING_INPUT" then
return "WARTE: " .. prettyItemName(job.waitingFor or "Input")
.. (job.dependencyJobId and (" (#" .. job.dependencyJobId .. ")") or "")
end
if recipe and recipe.completion == "activity" then
return formatNumber(job.progress or 0) .. " ANGEKOMMEN " .. tostring(job.phase) .. " " .. idle .. "s"
end