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
+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