Add casing recipes and automatic component stock
This commit is contained in:
@@ -15,8 +15,11 @@ create_factory_scheduler
|
||||
|
||||
Neue normale Module und Rezepte werden oben in der Tabelle `RECIPES` registriert.
|
||||
Die Scheduler-Logik muss dafuer nicht veraendert werden. Mehrstufige oder zufaellige
|
||||
Produktionsketten erhalten einen benannten Planner; V1 enthaelt den Planner fuer
|
||||
Asurine -> Zinc Clumps/Nuggets -> gewaschene Zinc Nuggets.
|
||||
Produktionsketten erhalten einen benannten Planner. V1.2 nutzt einen gemeinsamen
|
||||
Metall-Planner fuer Asurine -> Zinc und Crimsite -> Iron. Im AUTO-Modus haelt der
|
||||
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.
|
||||
|
||||
## Create-Fabrik: Peripherie erkennen
|
||||
|
||||
|
||||
+150
-32
@@ -1,7 +1,7 @@
|
||||
-- ATM10 Create factory scheduler V1
|
||||
-- Persistent jobs, dependency planning, progress-aware retries and diagnostics.
|
||||
|
||||
local VERSION = "1.1.0"
|
||||
local VERSION = "1.2.0"
|
||||
local REFRESH_SECONDS = 1
|
||||
local CONFIRM_SECONDS = 6
|
||||
local DRAIN_SECONDS = 60
|
||||
@@ -20,6 +20,11 @@ local RECIPES = {
|
||||
input = "create:asurine", inputCount = 64,
|
||||
outputs = { "alltheores:zinc_clump", "alltheores:zinc_nugget" }, completion = "activity",
|
||||
},
|
||||
crush_crimsite = {
|
||||
label = "64 CRIMSITE CRUSHEN", station = "crushing", address = "crushing",
|
||||
input = "create:crimsite", inputCount = 64,
|
||||
outputs = { "alltheores:iron_clump", "minecraft:iron_nugget" }, completion = "activity",
|
||||
},
|
||||
strip_oak = {
|
||||
label = "64 STRIPPED OAK", station = "saw:2", address = "saw:2",
|
||||
input = "minecraft:oak_log", inputCount = 64,
|
||||
@@ -43,14 +48,91 @@ local RECIPES = {
|
||||
},
|
||||
output = "create:andesite_alloy", outputCount = 64, targetCount = 64,
|
||||
},
|
||||
mixer_cogwheel = {
|
||||
label = "64 SMALL COGWHEELS", station = "mixer:1", address = "mixer:1",
|
||||
inputs = {
|
||||
{ item = "minecraft:oak_planks", count = 64 },
|
||||
{ item = "create:shaft", count = 64 },
|
||||
},
|
||||
output = "create:cogwheel", outputCount = 64, targetCount = 64,
|
||||
},
|
||||
mixer_large_cogwheel = {
|
||||
label = "64 LARGE COGWHEELS", station = "mixer:2", address = "mixer:2",
|
||||
inputs = {
|
||||
{ item = "minecraft:oak_planks", count = 128 },
|
||||
{ item = "create:shaft", count = 64 },
|
||||
},
|
||||
output = "create:large_cogwheel", outputCount = 64, targetCount = 64,
|
||||
},
|
||||
casing_andesite = {
|
||||
label = "64 ANDESITE CASINGS", station = "casing", address = "casing",
|
||||
inputs = {
|
||||
{ item = "minecraft:stripped_oak_log", count = 64 },
|
||||
{ item = "create:andesite_alloy", count = 64 },
|
||||
},
|
||||
output = "create:andesite_casing", outputCount = 64, targetCount = 64,
|
||||
},
|
||||
casing_copper = {
|
||||
label = "64 COPPER CASINGS", station = "casing", address = "casing",
|
||||
inputs = {
|
||||
{ item = "minecraft:stripped_oak_log", count = 64 },
|
||||
{ item = "minecraft:copper_ingot", count = 64 },
|
||||
},
|
||||
output = "create:copper_casing", outputCount = 64, targetCount = 64,
|
||||
},
|
||||
casing_brass = {
|
||||
label = "64 BRASS CASINGS", station = "casing", address = "casing",
|
||||
inputs = {
|
||||
{ item = "minecraft:stripped_oak_log", count = 64 },
|
||||
{ item = "create:brass_ingot", count = 64 },
|
||||
},
|
||||
output = "create:brass_casing", outputCount = 64, targetCount = 64,
|
||||
},
|
||||
casing_railway = {
|
||||
label = "64 RAILWAY CASINGS", station = "casing", address = "casing",
|
||||
inputs = {
|
||||
{ item = "create:brass_casing", count = 64 },
|
||||
{ item = "create:sturdy_sheet", count = 64 },
|
||||
},
|
||||
output = "create:railway_casing", outputCount = 64, targetCount = 64,
|
||||
},
|
||||
precision_mechanism = {
|
||||
label = "16 GOLD SHEETS -> PRECISION", station = "precisionmechanism", address = "precisionmechanism",
|
||||
input = "create:golden_sheet", inputCount = 16,
|
||||
outputs = { "create:precision_mechanism" }, completion = "activity",
|
||||
},
|
||||
zinc_nuggets = {
|
||||
label = "576 ZINC NUGGETS", planner = "zinc_nuggets",
|
||||
output = "alltheores:zinc_nugget", targetCount = 576,
|
||||
},
|
||||
iron_nuggets = {
|
||||
label = "64 IRON NUGGETS", planner = "iron_nuggets",
|
||||
output = "minecraft:iron_nugget", targetCount = 64,
|
||||
},
|
||||
}
|
||||
|
||||
local ACTION_ORDER = {
|
||||
"crush_asurine", "strip_oak", "planks_oak", "shafts", "zinc_nuggets", "mixer_alloy",
|
||||
"crush_asurine", "crush_crimsite", "strip_oak", "planks_oak", "shafts", "zinc_nuggets",
|
||||
"iron_nuggets",
|
||||
"mixer_alloy", "mixer_cogwheel", "mixer_large_cogwheel",
|
||||
"casing_andesite", "casing_copper", "casing_brass", "casing_railway", "precision_mechanism",
|
||||
}
|
||||
|
||||
local KEEP_STOCK = {
|
||||
{ recipeId = "mixer_cogwheel", item = "create:cogwheel", target = 64 },
|
||||
{ recipeId = "mixer_large_cogwheel", item = "create:large_cogwheel", target = 64 },
|
||||
{ recipeId = "iron_nuggets", item = "minecraft:iron_nugget", target = 64 },
|
||||
}
|
||||
|
||||
local METAL_PLANS = {
|
||||
zinc_nuggets = {
|
||||
name = "Zinc", source = "create:asurine", sourceName = "Asurine",
|
||||
clump = "alltheores:zinc_clump", nugget = "alltheores:zinc_nugget",
|
||||
},
|
||||
iron_nuggets = {
|
||||
name = "Iron", source = "create:crimsite", sourceName = "Crimsite",
|
||||
clump = "alltheores:iron_clump", nugget = "minecraft:iron_nugget",
|
||||
},
|
||||
}
|
||||
|
||||
local function epoch()
|
||||
@@ -73,12 +155,13 @@ monitor.setTextScale(0.5)
|
||||
|
||||
local stock, stockEntries, vaults = {}, {}, {}
|
||||
local stockPage, stockPageCount = 1, 1
|
||||
local stockPageButtons, buttons, modeButtons, errorPageButtons = {}, {}, {}, {}
|
||||
local stockPageButtons, buttons, modeButtons, errorPageButtons, actionPageButtons = {}, {}, {}, {}, {}
|
||||
local events = {}
|
||||
local state = { version = 1, nextJobId = 1, mode = "MANUAL", jobs = {}, errors = {} }
|
||||
local armed, armedUntil = nil, 0
|
||||
local statusText, statusColor = "Scheduler bereit", colors.lime
|
||||
local errorPage = 1
|
||||
local actionPage = 1
|
||||
|
||||
local function formatNumber(value)
|
||||
local text = tostring(math.floor(tonumber(value) or 0))
|
||||
@@ -362,7 +445,7 @@ local function updateNormal(job)
|
||||
job.progress = total
|
||||
if total > 0 and job.status ~= "DRAINING" then beginDrain(job) end
|
||||
if job.status == "DRAINING" and epoch() - job.lastProgressAt >= DRAIN_SECONDS then finishJob(job)
|
||||
elseif job.status == "RUNNING" and epoch() - job.lastProgressAt >= DRAIN_SECONDS then retryOrFail(job, 1, "Kein Crushing-Output") end
|
||||
elseif job.status == "RUNNING" and epoch() - job.lastProgressAt >= DRAIN_SECONDS then retryOrFail(job, 1, "Kein Output angekommen") end
|
||||
return
|
||||
end
|
||||
local current = produced(job, recipe.output)
|
||||
@@ -383,33 +466,34 @@ local function updateNormal(job)
|
||||
end
|
||||
end
|
||||
|
||||
local function dispatchZincCrushing(job, remaining)
|
||||
local function dispatchMetalCrushing(job, remaining, plan)
|
||||
if stationBusy("crushing", job) then return end
|
||||
local wanted = math.min(stock["create:asurine"] or 0, math.max(1, math.min(64, math.ceil(remaining / 3))))
|
||||
if wanted <= 0 then failJob(job, "Zu wenig Asurine fuer fehlende Zinc Nuggets") return end
|
||||
local sent, err = request("crushing", "create:asurine", wanted)
|
||||
if sent <= 0 then failJob(job, "Asurine-Versand fehlgeschlagen: " .. tostring(err or "unbekannt")) 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 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"
|
||||
job.lastBatchInput, job.phaseStartedAt, job.lastProgressAt, job.warned = sent, epoch(), epoch(), false
|
||||
logEvent("#" .. job.id .. " Teilauftrag: " .. sent .. " Asurine crushen", colors.orange) saveState()
|
||||
logEvent("#" .. job.id .. " Teilauftrag: " .. sent .. " " .. plan.sourceName .. " crushen", colors.orange) saveState()
|
||||
end
|
||||
|
||||
local function dispatchZincWashing(job, remaining)
|
||||
local function dispatchMetalWashing(job, remaining, plan)
|
||||
if stationBusy("fan:washing", job) then return end
|
||||
local wanted = math.min(stock["alltheores:zinc_clump"] or 0, math.ceil(remaining / 9))
|
||||
if wanted <= 0 then dispatchZincCrushing(job, remaining) return end
|
||||
local sent, err = request("fan:washing", "alltheores:zinc_clump", wanted)
|
||||
if sent <= 0 then failJob(job, "Zinc-Clump-Versand fehlgeschlagen: " .. tostring(err or "unbekannt")) return end
|
||||
local wanted = math.min(stock[plan.clump] or 0, math.ceil(remaining / 9))
|
||||
if wanted <= 0 then dispatchMetalCrushing(job, remaining, plan) return end
|
||||
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
|
||||
job.activeStation, job.phase, job.status = "fan:washing", "WASHING", "RUNNING"
|
||||
job.lastBatchInput, job.phaseStartedAt, job.lastProgressAt, job.warned = sent, epoch(), epoch(), false
|
||||
job.phaseOutputBaseline = produced(job, "alltheores:zinc_nugget")
|
||||
job.phaseOutputBaseline = produced(job, plan.nugget)
|
||||
job.phaseExpected = sent * 9
|
||||
logEvent("#" .. job.id .. " Teilauftrag: " .. sent .. " Clumps waschen", colors.orange) saveState()
|
||||
end
|
||||
|
||||
local function updateZinc(job)
|
||||
observeItems(job, { "alltheores:zinc_nugget", "alltheores:zinc_clump" })
|
||||
local nuggets = produced(job, "alltheores:zinc_nugget")
|
||||
local function updateMetal(job)
|
||||
local plan = METAL_PLANS[job.recipeId]
|
||||
observeItems(job, { plan.nugget, plan.clump })
|
||||
local nuggets = produced(job, plan.nugget)
|
||||
job.progress = math.min(job.targetCount, nuggets)
|
||||
local remaining = math.max(0, job.targetCount - nuggets)
|
||||
if remaining <= 0 then
|
||||
@@ -417,16 +501,16 @@ local function updateZinc(job)
|
||||
if epoch() - job.lastProgressAt >= DRAIN_SECONDS then finishJob(job) end
|
||||
return
|
||||
end
|
||||
if job.status == "WAITING" then job.activeStation = nil dispatchZincWashing(job, remaining) return end
|
||||
if job.status == "WAITING" then job.activeStation = nil 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.
|
||||
-- Late crushing returns remain valid surplus and are still counted.
|
||||
if job.phase == "CRUSHING" and (stock["alltheores:zinc_clump"] or 0) * 9 >= remaining then
|
||||
if job.phase == "CRUSHING" and (stock[plan.clump] or 0) * 9 >= remaining then
|
||||
job.activeStation, job.status, job.phase = nil, "WAITING", "PLAN"
|
||||
logEvent("#" .. job.id .. " genug Clumps angekommen - sofort weiter zum Washing", colors.lime)
|
||||
saveState()
|
||||
dispatchZincWashing(job, remaining)
|
||||
dispatchMetalWashing(job, remaining, plan)
|
||||
return
|
||||
end
|
||||
|
||||
@@ -439,7 +523,7 @@ local function updateZinc(job)
|
||||
if job.phase == "CRUSHING" then
|
||||
job.status, job.phase, job.lastProgressAt = "WAITING", "PLAN", epoch() saveState()
|
||||
elseif job.phase == "WASHING" then
|
||||
local phaseGain = math.max(0, produced(job, "alltheores:zinc_nugget") - (job.phaseOutputBaseline or 0))
|
||||
local phaseGain = math.max(0, produced(job, plan.nugget) - (job.phaseOutputBaseline or 0))
|
||||
if phaseGain >= (job.phaseExpected or 0) then
|
||||
-- Batch arrived completely, but the parent still needs more: replan.
|
||||
job.status, job.phase, job.lastProgressAt = "WAITING", "PLAN", epoch()
|
||||
@@ -447,37 +531,54 @@ local function updateZinc(job)
|
||||
else
|
||||
retryOrFail(job, remaining, "Washing-Nachlauf beendet")
|
||||
end
|
||||
elseif idle >= DRAIN_SECONDS then retryOrFail(job, remaining, "Zinkauftrag ohne Fortschritt") end
|
||||
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
|
||||
if not terminal(job) then
|
||||
local recipe = RECIPES[job.recipeId]
|
||||
if not recipe then failJob(job, "Rezept nicht mehr vorhanden")
|
||||
elseif recipe.planner == "zinc_nuggets" then updateZinc(job)
|
||||
elseif METAL_PLANS[recipe.planner] then updateMetal(job)
|
||||
else updateNormal(job) end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function newJob(recipeId)
|
||||
newJob = function(recipeId, targetCount, origin)
|
||||
local recipe = RECIPES[recipeId]
|
||||
local job = {
|
||||
id = state.nextJobId, recipeId = recipeId, status = "WAITING",
|
||||
phase = recipe.planner and "PLAN" or "NORMAL",
|
||||
targetCount = recipe.targetCount or recipe.outputCount or 1,
|
||||
targetCount = targetCount or recipe.targetCount or recipe.outputCount or 1,
|
||||
origin = origin or "MANUAL",
|
||||
baseline = {}, lastObserved = {}, arrived = {}, progress = 0, retries = 0,
|
||||
createdAt = epoch(), lastProgressAt = epoch(), phaseStartedAt = epoch(),
|
||||
}
|
||||
state.nextJobId = state.nextJobId + 1
|
||||
if recipe.output then setBaseline(job, recipe.output) end
|
||||
for _, output in ipairs(recipe.outputs or {}) do setBaseline(job, output) end
|
||||
if recipe.planner == "zinc_nuggets" then setBaseline(job, "alltheores:zinc_clump") end
|
||||
local metalPlan = METAL_PLANS[recipe.planner]
|
||||
if metalPlan then setBaseline(job, metalPlan.clump) end
|
||||
state.jobs[#state.jobs + 1] = job
|
||||
statusText, statusColor = "EINGEREIHT #" .. job.id .. ": " .. recipe.label, colors.orange
|
||||
logEvent(statusText, colors.orange) saveState() return job
|
||||
logEvent(statusText .. (job.origin == "AUTO" and " [AUTO]" or ""), colors.orange) saveState() return job
|
||||
end
|
||||
|
||||
local function maintainStock()
|
||||
if state.mode ~= "AUTO" then return end
|
||||
for _, rule in ipairs(KEEP_STOCK) do
|
||||
local current = stock[rule.item] or 0
|
||||
if current < rule.target and not findJobByRecipe(rule.recipeId) then
|
||||
local missing = rule.target - current
|
||||
newJob(rule.recipeId, missing, "AUTO")
|
||||
logEvent("KEEP STOCK: " .. prettyItemName(rule.item) .. " "
|
||||
.. formatNumber(current) .. "/" .. formatNumber(rule.target), colors.cyan)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function cancelJob(job)
|
||||
@@ -565,6 +666,18 @@ local function drawModes(width)
|
||||
end
|
||||
end
|
||||
|
||||
local function drawActionPage(width)
|
||||
local perPage = 6
|
||||
local pages = math.max(1, math.ceil(#ACTION_ORDER / perPage))
|
||||
actionPage = math.max(1, math.min(actionPage, pages))
|
||||
center(10, "< REZEPTE " .. actionPage .. "/" .. pages .. " >", colors.yellow)
|
||||
actionPageButtons = {
|
||||
{ direction = -1, x1 = 1, x2 = math.floor(width / 2), y1 = 10, y2 = 10, enabled = actionPage > 1 },
|
||||
{ direction = 1, x1 = math.floor(width / 2) + 1, x2 = width, y1 = 10, y2 = 10, enabled = actionPage < pages },
|
||||
}
|
||||
return (actionPage - 1) * perPage + 1, math.min(#ACTION_ORDER, actionPage * perPage)
|
||||
end
|
||||
|
||||
local function drawLogs(width, height, top)
|
||||
local half = math.floor(width / 2)
|
||||
writeAt(2, top, "LIVE-LOG", colors.yellow)
|
||||
@@ -622,11 +735,13 @@ local function render()
|
||||
local top, gap = 11, 2
|
||||
local buttonWidth = math.floor((width - 6 - gap) / 2)
|
||||
local left, right = 3, 3 + buttonWidth + gap
|
||||
for index, recipeId in ipairs(ACTION_ORDER) do
|
||||
local firstAction, lastAction = drawActionPage(width)
|
||||
for absoluteIndex = firstAction, lastAction do
|
||||
local index, recipeId = absoluteIndex - firstAction + 1, ACTION_ORDER[absoluteIndex]
|
||||
drawAction(recipeId, index % 2 == 1 and left or right, top + math.floor((index - 1) / 2) * 2,
|
||||
(index % 2 == 1 and left or right) + buttonWidth - 1, top + math.floor((index - 1) / 2) * 2)
|
||||
end
|
||||
drawLogs(width, height, top + math.ceil(#ACTION_ORDER / 2) * 2)
|
||||
drawLogs(width, height, 17)
|
||||
drawVaults(width, height)
|
||||
writeAt(2, height, "Bestellen/Abbrechen/OFF: doppelt beruehren | Nachlauf: " .. DRAIN_SECONDS .. "s", colors.gray)
|
||||
end
|
||||
@@ -670,6 +785,9 @@ local function handleTouch(x, y)
|
||||
for _, button in ipairs(errorPageButtons) do
|
||||
if inside(x, y, button) then if button.enabled then errorPage = errorPage + button.direction end return end
|
||||
end
|
||||
for _, button in ipairs(actionPageButtons) do
|
||||
if inside(x, y, button) then if button.enabled then actionPage = actionPage + button.direction end return end
|
||||
end
|
||||
for _, button in ipairs(modeButtons) do
|
||||
if inside(x, y, button) then
|
||||
if button.mode == "OFF" then
|
||||
@@ -707,7 +825,7 @@ while true do
|
||||
if armed and epoch() > armedUntil then
|
||||
armed, armedUntil = nil, 0 statusText, statusColor = "Bestaetigung abgelaufen", colors.gray
|
||||
end
|
||||
if refreshStock() then updateJobs() end
|
||||
if refreshStock() then maintainStock() updateJobs() end
|
||||
refreshVaults()
|
||||
render() refreshTimer = os.startTimer(REFRESH_SECONDS)
|
||||
elseif event == "peripheral" or event == "peripheral_detach" then refreshStock() refreshVaults() render() end
|
||||
|
||||
Reference in New Issue
Block a user