Add protected ME fallback for Create inputs

This commit is contained in:
2026-07-18 22:37:19 +02:00
parent 090fec482a
commit 1480119c8e
2 changed files with 111 additions and 9 deletions
+6
View File
@@ -34,6 +34,12 @@ wird erst freigegeben, nachdem der Vorauftrag seine Ankunft registriert hat. So
kann ein nachfolgender Auftrag den neuen Bestand nicht mehr im selben Tick
wegnehmen und dadurch einen falschen Retry oder Fehler ausloesen.
V1.4 bindet die Advanced-Peripherals-ME-Bridge als geschuetzten Fallback ein.
Vorhandener Create-Bestand und Create-eigene Produktionsketten haben weiterhin
Vorrang. Nur externe, im Create-Netz fehlende Rohstoffe werden in exakt der
fehlenden Menge aus dem ME in `sophisticatedstorage:chest_0` exportiert. Diese
erste Stufe startet bewusst noch kein ME-Autocrafting.
## Create-Fabrik: Peripherie erkennen
Das rein lesende Diagnoseprogramm herunterladen und starten:
+105 -9
View File
@@ -1,7 +1,7 @@
-- ATM10 Create factory scheduler V1
-- Persistent jobs, dependency planning, progress-aware retries and diagnostics.
local VERSION = "1.3.2"
local VERSION = "1.4.0"
local REFRESH_SECONDS = 1
local CONFIRM_SECONDS = 6
local DRAIN_SECONDS = 60
@@ -12,6 +12,8 @@ local STATE_TEMP_FILE = STATE_FILE .. ".tmp"
local EVENT_LOG_FILE = "factory_scheduler_log.txt"
local MAX_EVENTS = 5
local MAX_ERRORS = 50
local ME_BUFFER_NAME = "sophisticatedstorage:chest_0"
local ME_TRANSFER_TIMEOUT = 15
-- Add ordinary modules/recipes here. Multi-step chains use a named planner.
local RECIPES = {
@@ -200,6 +202,7 @@ end
local tickerName = findPeripheral("Create_StockTicker")
local monitorName = findPeripheral("monitor")
local meBridgeName = findPeripheral("me_bridge")
if not tickerName then error("Kein Create Stock Ticker gefunden", 0) end
if not monitorName then error("Kein Monitor gefunden", 0) end
local monitor = peripheral.wrap(monitorName)
@@ -404,7 +407,10 @@ local function request(address, item, count)
return sent
end
local clearMEWait
local function finishJob(job)
clearMEWait(job)
job.status, job.activeStation, job.finishedAt = "DONE", nil, epoch()
statusText, statusColor = "FERTIG: " .. RECIPES[job.recipeId].label, colors.lime
logEvent("#" .. job.id .. " fertig: " .. RECIPES[job.recipeId].label, colors.lime)
@@ -412,6 +418,7 @@ local function finishJob(job)
end
local function failJob(job, reason)
clearMEWait(job)
job.status, job.activeStation, job.finishedAt = "FAILED", nil, epoch()
statusText, statusColor = "FEHLER #" .. job.id .. ": " .. reason, colors.red
logEvent(statusText, colors.red) addError(job, "FAILED", reason) saveState()
@@ -443,6 +450,78 @@ end
local newJob
clearMEWait = function(job)
job.meWaitingFor, job.meRequired, job.meRequestedAt, job.meMoved = nil, nil, nil, nil
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
if createAvailable >= required then
clearMEWait(job)
return true
end
if not meBridgeName or not peripheral.isPresent(meBridgeName) then
failJob(job, "Zu wenig Input: " .. prettyItemName(item)
.. " (Create " .. formatNumber(createAvailable) .. "/" .. formatNumber(required)
.. ", ME Bridge fehlt)")
return false
end
if not peripheral.isPresent(ME_BUFFER_NAME) then
failJob(job, "ME-Puffer " .. ME_BUFFER_NAME .. " nicht erreichbar")
return false
end
local okOnline, online = pcall(peripheral.call, meBridgeName, "isConnected")
if not okOnline or not online then
failJob(job, "ME Bridge offline fuer " .. prettyItemName(item))
return false
end
if job.meWaitingFor == item then
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
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 meAvailable = 0
if okItem and type(meItem) == "table" then
meAvailable = tonumber(meItem.amount or meItem.count) or 0
end
if meAvailable <= 0 then
failJob(job, "Zu wenig Input: " .. prettyItemName(item)
.. " (Create " .. formatNumber(createAvailable) .. "/" .. formatNumber(required)
.. ", ME 0)")
return false
end
local wanted = math.min(missing, meAvailable)
local okExport, result = pcall(peripheral.call, meBridgeName, "exportItem",
{ name = item, count = wanted }, ME_BUFFER_NAME)
local moved = okExport and tonumber(result) or 0
if moved <= 0 then
failJob(job, "ME-Export fehlgeschlagen: " .. prettyItemName(item)
.. (okExport and " (Puffer voll oder nicht erreichbar)" or (" - " .. tostring(result))))
return false
end
job.status, job.activeStation, job.waitingFor = "WAITING_ME", nil, item
job.meWaitingFor, job.meRequired = item, required
job.meRequestedAt, job.meMoved = epoch(), moved
logEvent("#" .. job.id .. " ME-Fallback: " .. formatNumber(moved) .. " "
.. prettyItemName(item) .. " (Create hatte " .. formatNumber(createAvailable) .. ")", colors.purple)
saveState()
return false
end
local function dependencyReleased(job)
if not job.dependencyJobId then return true end
local dependency = findJobById(job.dependencyJobId)
@@ -473,9 +552,7 @@ end
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
return waitForME(job, item, required)
end
if job.dependencyJobId then
@@ -508,6 +585,9 @@ end
local function dispatchNormal(job, missingOutput)
local recipe = RECIPES[job.recipeId]
if not dependencyReleased(job) then return false end
if job.meWaitingFor then
if not waitForME(job, job.meWaitingFor, job.meRequired or 1) then return false end
end
local selectedStation = chooseStation(recipe, job)
if not selectedStation then job.status = "WAITING" return false end
@@ -560,6 +640,7 @@ local function dispatchNormal(job, missingOutput)
job.lastBatchInput = nil
job.activeStation, job.status = selectedStation, "RUNNING"
job.waitingFor, job.dependencyJobId, job.lastDependencyLog = nil, nil, nil
clearMEWait(job)
job.phaseStartedAt, job.lastProgressAt, job.warned = epoch(), epoch(), false
local inputTotal = 0
for _, count in pairs(sentInputs) do inputTotal = inputTotal + count end
@@ -581,7 +662,7 @@ 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
if job.status == "WAITING" or job.status == "WAITING_INPUT" or job.status == "WAITING_ME" then
dispatchNormal(job, 1)
return
end
@@ -601,7 +682,7 @@ local function updateNormal(job)
if epoch() - job.lastProgressAt >= DRAIN_SECONDS then finishJob(job) end
return
end
if job.status == "WAITING" or job.status == "WAITING_INPUT" then dispatchNormal(job, missing)
if job.status == "WAITING" or job.status == "WAITING_INPUT" or job.status == "WAITING_ME" 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
@@ -613,8 +694,12 @@ end
local function dispatchMetalCrushing(job, remaining, plan)
if stationBusy("crushing", job) then return end
local wanted = math.min(stock[plan.source] or 0, math.max(1, math.min(64, math.ceil(remaining / 3))))
if wanted <= 0 then failJob(job, "Zu wenig " .. plan.sourceName .. " fuer fehlende " .. plan.name .. " Nuggets") return end
local required = math.max(1, math.min(64, math.ceil(remaining / 3)))
local wanted = math.min(stock[plan.source] or 0, required)
if wanted <= 0 then
waitForME(job, plan.source, required)
return
end
local sent, err = request("crushing", plan.source, wanted)
if sent <= 0 then failJob(job, plan.sourceName .. "-Versand fehlgeschlagen: " .. tostring(err or "unbekannt")) return end
job.activeStation, job.phase, job.status = "crushing", "CRUSHING", "RUNNING"
@@ -646,7 +731,12 @@ local function updateMetal(job)
if epoch() - job.lastProgressAt >= DRAIN_SECONDS then finishJob(job) end
return
end
if job.status == "WAITING" then job.activeStation = nil dispatchMetalWashing(job, remaining, plan) return end
if job.status == "WAITING" or job.status == "WAITING_ME" then
job.activeStation = nil
if job.meWaitingFor and not waitForME(job, job.meWaitingFor, job.meRequired or 1) then return end
dispatchMetalWashing(job, remaining, plan)
return
end
-- Do not wait for the complete 60-second crushing drain when the material
-- which has already reached storage can satisfy the remaining parent job.
@@ -726,6 +816,7 @@ local function maintainStock()
end
local function cancelJob(job)
clearMEWait(job)
job.status, job.activeStation, job.finishedAt = "CANCELLED", nil, epoch()
logEvent("#" .. job.id .. " manuell abgebrochen", colors.red)
addError(job, "CANCELLED", "Manuell abgebrochen; bereits versendete Pakete laufen aus")
@@ -779,6 +870,11 @@ local function progressText(job)
return "WARTE: " .. prettyItemName(job.waitingFor or "Input")
.. (job.dependencyJobId and (" (#" .. job.dependencyJobId .. ")") or "")
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(job.meRequired or 0)
end
if recipe and recipe.completion == "activity" then
return formatNumber(job.progress or 0) .. " ANGEKOMMEN " .. tostring(job.phase) .. " " .. idle .. "s"
end