Files
ATM10_CC_Codes/create_factory_test.lua
T

489 lines
15 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 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 stockEntries = {}
local stockPage = 1
local stockPageCount = 1
local stockPageButtons = {}
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 prettyItemName(itemId)
local path = tostring(itemId):match("^[^:]+:(.+)$") or tostring(itemId)
path = path:gsub("_", " ")
return (path:gsub("(%a)([%w']*)", function(first, rest)
return first:upper() .. rest:lower()
end))
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", true)
if not ok then
statusText = "Stock-Ticker-Fehler: " .. tostring(result)
statusColor = colors.red
return false
end
local nextStock = {}
local detailsByName = {}
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)
detailsByName[entry.name] = entry
end
end
stock = nextStock
local nextEntries = {}
for itemId, count in pairs(nextStock) do
local details = detailsByName[itemId] or {}
local displayName = details.displayName
if type(displayName) ~= "string" or displayName == "" then
displayName = prettyItemName(itemId)
end
nextEntries[#nextEntries + 1] = {
id = itemId,
label = displayName,
count = count,
}
end
table.sort(nextEntries, function(a, b)
local aLabel = a.label:lower()
local bLabel = b.label:lower()
if aLabel == bLabel then
return a.id < b.id
end
return aLabel < bLabel
end)
stockEntries = nextEntries
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 drawStockPage(width)
local itemsPerPage = 10
stockPageCount = math.max(1, math.ceil(#stockEntries / itemsPerPage))
stockPage = math.max(1, math.min(stockPage, stockPageCount))
stockPageButtons = {}
writeAt(3, 3, "<", stockPage > 1 and colors.yellow or colors.gray)
center(3, "LAGERBESTAND " .. tostring(stockPage) .. "/" .. tostring(stockPageCount), colors.cyan)
writeAt(width - 2, 3, ">", stockPage < stockPageCount and colors.yellow or colors.gray)
stockPageButtons[#stockPageButtons + 1] = {
direction = -1,
x1 = 1,
x2 = math.floor(width / 3),
y = 3,
enabled = stockPage > 1,
}
stockPageButtons[#stockPageButtons + 1] = {
direction = 1,
x1 = math.ceil(width * 2 / 3),
x2 = width,
y = 3,
enabled = stockPage < stockPageCount,
}
local columnWidth = math.floor((width - 6) / 2)
local leftX = 3
local rightX = leftX + columnWidth + 2
local firstIndex = (stockPage - 1) * itemsPerPage + 1
for position = 1, itemsPerPage do
local entry = stockEntries[firstIndex + position - 1]
if entry then
local column = (position - 1) % 2
local row = 4 + math.floor((position - 1) / 2)
local x = column == 0 and leftX or rightX
local maxLabelLength = math.max(8, columnWidth - 14)
local label = entry.label
if #label > maxLabelLength then
label = label:sub(1, maxLabelLength - 1) .. "~"
end
writeAt(x, row, label .. ": " .. formatNumber(entry.count), colors.lime)
end
end
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)
drawStockPage(width)
local statusRow = 10
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 _, pageButton in ipairs(stockPageButtons) do
if y == pageButton.y and x >= pageButton.x1 and x <= pageButton.x2 then
if pageButton.enabled then
stockPage = stockPage + pageButton.direction
statusText = "Lagerseite " .. tostring(stockPage) .. "/" .. tostring(stockPageCount)
statusColor = colors.cyan
end
return
end
end
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