Files
ATM10_CC_Codes/create_factory_test.lua
T

419 lines
13 KiB
Lua

-- ATM10 Create factory logistics test controller
-- Phase 1: Stock Ticker requests and live stock display, no redstone control.
local REFRESH_SECONDS = 1.0
local CONFIRM_SECONDS = 6.0
local JOB_TIMEOUT_SECONDS = 180.0
local LOG_FILE = "factory_test_log.txt"
local trackedItems = {
{ id = "create:asurine", label = "Asurine" },
{ id = "minecraft:andesite", label = "Andesite" },
{ id = "create:andesite_alloy", label = "Andesite Alloy" },
{ id = "minecraft:oak_log", label = "Oak Logs" },
{ id = "minecraft:stripped_oak_log", label = "Stripped Oak" },
{ id = "minecraft:oak_planks", label = "Oak Planks" },
{ id = "alltheores:zinc_clump", label = "Zinc Clumps" },
{ id = "alltheores:zinc_nugget", label = "Zinc Nuggets" },
{ id = "create:shaft", label = "Shafts" },
}
local actions = {
{
id = "crush_asurine",
label = "64 ASURINE CRUSHEN",
lockKey = "crushing",
address = "crushing",
item = "create:asurine",
inputCount = 64,
completion = {
mode = "any_increase",
outputs = { "alltheores:zinc_clump", "alltheores:zinc_nugget" },
},
},
{
id = "strip_oak",
label = "64 STRIPPED OAK",
lockKey = "saw:2",
address = "saw:2",
item = "minecraft:oak_log",
inputCount = 64,
completion = {
mode = "expected",
output = "minecraft:stripped_oak_log",
outputPerInput = 1,
},
},
{
id = "planks_oak",
label = "64 OAK PLANKS",
lockKey = "saw:2",
address = "saw:2",
item = "minecraft:stripped_oak_log",
-- Create yields 6 planks per stripped log. 11 inputs produce 66 planks.
inputCount = 11,
completion = {
mode = "expected",
output = "minecraft:oak_planks",
outputPerInput = 6,
},
},
{
id = "shafts",
label = "64 SHAFTS",
lockKey = "saw:1",
address = "saw:1",
item = "create:andesite_alloy",
-- Create yields 6 shafts per Andesite Alloy. 11 inputs produce 66 shafts.
inputCount = 11,
completion = {
mode = "expected",
output = "create:shaft",
outputPerInput = 6,
},
},
}
local function findPeripheral(wantedType)
for _, name in ipairs(peripheral.getNames()) do
local peripheralType = peripheral.getType(name)
if peripheralType == wantedType then
return name
end
end
return nil
end
local tickerName = findPeripheral("Create_StockTicker")
local monitorName = findPeripheral("monitor")
if not tickerName then
error("Kein Create Stock Ticker am Wired-Modem-Netz gefunden", 0)
end
if not monitorName then
error("Kein Monitor am Wired-Modem-Netz gefunden", 0)
end
local monitor = peripheral.wrap(monitorName)
monitor.setTextScale(0.5)
local stock = {}
local buttons = {}
local messages = {}
local jobs = {}
local armedAction = nil
local armedUntil = 0
local statusText = "Bereit - nur Testbetrieb, keine Redstone-Steuerung"
local statusColor = colors.lime
local function formatNumber(value)
local text = tostring(math.floor(tonumber(value) or 0))
local sign, digits = text:match("^([%-]?)(%d+)$")
if not digits then
return text
end
local formatted = digits:reverse():gsub("(%d%d%d)", "%1."):reverse():gsub("^%.", "")
return sign .. formatted
end
local function timeText()
return textutils.formatTime(os.time(), true)
end
local function appendLog(message)
local line = "[" .. timeText() .. "] " .. message
messages[#messages + 1] = line
while #messages > 4 do
table.remove(messages, 1)
end
local file = fs.open(LOG_FILE, "a")
if file then
file.writeLine(line)
file.close()
end
end
local function refreshStock()
local ok, result = pcall(peripheral.call, tickerName, "stock")
if not ok then
statusText = "Stock-Ticker-Fehler: " .. tostring(result)
statusColor = colors.red
return false
end
local nextStock = {}
for _, entry in pairs(result or {}) do
if type(entry) == "table" and entry.name then
nextStock[entry.name] = (nextStock[entry.name] or 0) + (tonumber(entry.count) or 0)
end
end
stock = nextStock
return true
end
local function jobComplete(job)
if job.completion.mode == "expected" then
local current = stock[job.completion.output] or 0
return current >= job.baseline[job.completion.output] + job.expectedOutput
end
if job.completion.mode == "any_increase" then
for _, output in ipairs(job.completion.outputs) do
if (stock[output] or 0) > (job.baseline[output] or 0) then
return true
end
end
end
return false
end
local function updateJobs()
for lockKey, job in pairs(jobs) do
if jobComplete(job) then
statusText = "FERTIG: " .. job.label
statusColor = colors.lime
appendLog(statusText)
jobs[lockKey] = nil
elseif os.clock() - job.startedAt > JOB_TIMEOUT_SECONDS and not job.timedOut then
job.timedOut = true
statusText = "TIMEOUT: " .. job.label .. " - Anlage pruefen"
statusColor = colors.red
appendLog(statusText)
end
end
end
local function writeAt(x, y, text, foreground, background)
local width, height = monitor.getSize()
if y < 1 or y > height or x > width then
return
end
monitor.setCursorPos(math.max(1, x), y)
monitor.setTextColor(foreground or colors.white)
monitor.setBackgroundColor(background or colors.black)
monitor.write(tostring(text):sub(1, math.max(0, width - x + 1)))
end
local function center(y, text, foreground, background)
local width = monitor.getSize()
local x = math.max(1, math.floor((width - #text) / 2) + 1)
writeAt(x, y, text, foreground, background)
end
local function drawButton(action, x1, y1, x2, y2)
local isArmed = armedAction == action.id and os.clock() <= armedUntil
local available = stock[action.item] or 0
local activeJob = jobs[action.lockKey]
local enabled = available >= action.inputCount and activeJob == nil
local background = colors.gray
local foreground = colors.lightGray
if activeJob then
background = activeJob.timedOut and colors.red or colors.orange
foreground = activeJob.timedOut and colors.white or colors.black
elseif enabled then
background = isArmed and colors.orange or colors.green
foreground = isArmed and colors.black or colors.white
end
for y = y1, y2 do
writeAt(x1, y, string.rep(" ", x2 - x1 + 1), foreground, background)
end
local label = action.label
if activeJob then
label = (activeJob.timedOut and "TIMEOUT: " or "IN ARBEIT: ") .. activeJob.label
elseif isArmed then
label = "NOCHMALS: " .. label
elseif not enabled then
label = "ZU WENIG INPUT: " .. label
end
local labelX = math.max(x1, math.floor((x1 + x2 - #label) / 2))
writeAt(labelX, math.floor((y1 + y2) / 2), label, foreground, background)
buttons[#buttons + 1] = {
action = action,
x1 = x1,
y1 = y1,
x2 = x2,
y2 = y2,
enabled = enabled,
activeJob = activeJob,
}
end
local function render()
local width, height = monitor.getSize()
monitor.setBackgroundColor(colors.black)
monitor.setTextColor(colors.white)
monitor.clear()
buttons = {}
center(1, "CREATE FACTORY - LOGISTIKTEST", colors.cyan)
center(2, "Ticker: " .. tickerName .. " | Monitor: " .. monitorName, colors.gray)
local columnWidth = math.floor((width - 6) / 2)
local leftX = 3
local rightX = leftX + columnWidth + 2
local firstStockRow = 4
for index, item in ipairs(trackedItems) do
local column = (index - 1) % 2
local row = firstStockRow + math.floor((index - 1) / 2)
local x = column == 0 and leftX or rightX
local amount = formatNumber(stock[item.id] or 0)
local text = item.label .. ": " .. amount
writeAt(x, row, text, (stock[item.id] or 0) > 0 and colors.lime or colors.gray)
end
local statusRow = firstStockRow + math.ceil(#trackedItems / 2) + 1
center(statusRow, statusText, statusColor)
local buttonTop = statusRow + 2
local gap = 2
local buttonWidth = math.floor((width - 6 - gap) / 2)
local xLeft = 3
local xRight = xLeft + buttonWidth + gap
local buttonHeight = 2
drawButton(actions[1], xLeft, buttonTop, xLeft + buttonWidth - 1, buttonTop + buttonHeight - 1)
drawButton(actions[2], xRight, buttonTop, xRight + buttonWidth - 1, buttonTop + buttonHeight - 1)
drawButton(actions[3], xLeft, buttonTop + 3, xLeft + buttonWidth - 1, buttonTop + 4)
drawButton(actions[4], xRight, buttonTop + 3, xRight + buttonWidth - 1, buttonTop + 4)
local logTitleRow = math.min(height - 4, buttonTop + 6)
writeAt(2, logTitleRow, "Letzte Ereignisse:", colors.yellow)
for index, message in ipairs(messages) do
writeAt(2, logTitleRow + index, message, colors.lightGray)
end
writeAt(2, height, "Jede Bestellung muss innerhalb 6 Sekunden zweimal beruehrt werden.", colors.gray)
end
local function submit(action)
local filter = {
name = action.item,
_requestCount = action.inputCount,
}
local baseline = {}
if action.completion.mode == "expected" then
baseline[action.completion.output] = stock[action.completion.output] or 0
else
for _, output in ipairs(action.completion.outputs) do
baseline[output] = stock[output] or 0
end
end
local ok, sent = pcall(
peripheral.call,
tickerName,
"requestFiltered",
action.address,
filter
)
if not ok then
statusText = "BESTELLFEHLER: " .. tostring(sent)
statusColor = colors.red
appendLog(statusText)
return
end
local amount = tonumber(sent) or 0
if amount <= 0 then
statusText = "NICHT GESENDET: " .. action.label
statusColor = colors.orange
appendLog(statusText)
refreshStock()
return
end
local expectedOutput = 0
if action.completion.mode == "expected" then
expectedOutput = amount * action.completion.outputPerInput
end
jobs[action.lockKey] = {
label = action.label,
actionId = action.id,
address = action.address,
inputItem = action.item,
inputSent = amount,
completion = action.completion,
baseline = baseline,
expectedOutput = expectedOutput,
startedAt = os.clock(),
timedOut = false,
}
statusText = "GESTARTET: " .. action.label .. " (" .. formatNumber(amount) .. " Input)"
statusColor = colors.orange
appendLog(statusText)
refreshStock()
end
local function handleTouch(x, y)
for _, button in ipairs(buttons) do
if x >= button.x1 and x <= button.x2 and y >= button.y1 and y <= button.y2 then
if button.activeJob then
statusText = "Auftrag laeuft bereits: " .. button.activeJob.label
statusColor = button.activeJob.timedOut and colors.red or colors.orange
return
end
if not button.enabled then
statusText = "Zu wenig Rohstoff fuer diesen Auftrag"
statusColor = colors.orange
appendLog(statusText)
return
end
local now = os.clock()
if armedAction == button.action.id and now <= armedUntil then
local selected = button.action
armedAction = nil
armedUntil = 0
submit(selected)
else
armedAction = button.action.id
armedUntil = now + CONFIRM_SECONDS
statusText = "Zur Bestaetigung denselben Knopf erneut beruehren"
statusColor = colors.orange
end
return
end
end
end
appendLog("Testcontroller gestartet auf Computer " .. tostring(os.getComputerID()))
refreshStock()
render()
local refreshTimer = os.startTimer(REFRESH_SECONDS)
while true do
local event, parameter1, parameter2, parameter3 = os.pullEvent()
if event == "monitor_touch" and parameter1 == monitorName then
handleTouch(parameter2, parameter3)
render()
elseif event == "timer" and parameter1 == refreshTimer then
if armedAction and os.clock() > armedUntil then
armedAction = nil
armedUntil = 0
statusText = "Bestaetigung abgelaufen"
statusColor = colors.gray
end
refreshStock()
updateJobs()
render()
refreshTimer = os.startTimer(REFRESH_SECONDS)
elseif event == "peripheral_detach" or event == "peripheral" then
refreshStock()
render()
end
end