From eee51ee05aac3120cd0c690ab06b6c663d84a1a1 Mon Sep 17 00:00:00 2001 From: Dystroyer8 Date: Sat, 18 Jul 2026 21:03:43 +0200 Subject: [PATCH] Add recursive factory dependencies and press pool --- README.md | 7 ++ create_factory_scheduler.lua | 151 ++++++++++++++++++++++++++++++++--- 2 files changed, 145 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 9c42e55..49cf492 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/create_factory_scheduler.lua b/create_factory_scheduler.lua index 4d9bb36..76b94ba 100644 --- a/create_factory_scheduler.lua +++ b/create_factory_scheduler.lua @@ -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