diff --git a/inventoryClient.lua b/inventoryClient.lua index 467c27a..5017e15 100644 --- a/inventoryClient.lua +++ b/inventoryClient.lua @@ -3,8 +3,8 @@ -- Shows the same dashboard but runs NO automation tasks. -- Orders and smelter controls are sent to the master. -- --- Uses Opus UI framework via manager/client_display.lua --- (mirrors manager/display.lua used by the server). +-- Reuses the same Opus UI display module (manager/display.lua) +-- as the server, with an ops shim that sends network commands. ------------------------------------------------- -- Default configuration (overridden by .client_config) @@ -75,174 +75,11 @@ end loadConfig() ------------------------------------------------- --- State (received from master) +-- Network modem ------------------------------------------------- -local cache = { - itemList = {}, - grandTotal = 0, - chestCount = 0, - totalSlots = 0, - usedSlots = 0, - freeSlots = 0, - usedRatio = 0, - dropperOk = false, - barrelOk = false, - furnaceCount = 0, - furnaceStatus = {}, -} - -local activity = { - sorting = false, - dispensing = false, - scanning = false, - smelting = false, - defragging = false, - composting = false, - crafting = false, -} - -local activeAlerts = {} -local smeltingPaused = false -local disabledRecipes = {} -local SMELTABLE = {} -- populated from master broadcast -local CRAFTABLE = {} -- populated from master broadcast -local craftTurtleOk = false -local connected = false -- true once first state received - -------------------------------------------------- --- UI State (local to client) -------------------------------------------------- - -local selectedAmount = 1 -local amountOptions = {1, 4, 8, 16, 32, 64} -local statusMessage = "" -local statusColor = colors.white -local statusTimer = 0 - -local touchZones = {} -local pendingZones = {} -local needsRedraw = true - -local currentPage = 1 -local totalPages = 1 -local searchQuery = "" -local showKeyboard = false - -local kbRows = { - {"Q","W","E","R","T","Y","U","I","O","P"}, - {"A","S","D","F","G","H","J","K","L"}, - {"Z","X","C","V","B","N","M"}, -} - -------------------------------------------------- --- Smelter dashboard state -------------------------------------------------- - -local smelterView = "status" -local smelterPage = 1 -local smelterTotalPages = 1 -local smelterTouchZones = {} -local smelterPendingZones = {} -local smelterNeedsRedraw = true - -------------------------------------------------- --- Monitor setup -------------------------------------------------- - -local mon = nil -local monName = nil -local smelterMon = nil -local smelterMonName = nil local networkModem = nil -local function setupMonitor() - mon, monName = ui.setupMonitor(MONITOR_SIDE, SMELTER_MONITOR_SIDE) - return mon ~= nil -end - -local function setupSmelterMonitor() - smelterMon, smelterMonName = ui.setupSmelterMonitor(SMELTER_MONITOR_SIDE, monName) - return smelterMon ~= nil -end - -------------------------------------------------- --- Filtered items helper -------------------------------------------------- - -local function getFilteredItems() - return ui.getFilteredItems(cache.itemList, searchQuery) -end - -------------------------------------------------- --- Touch zone helpers -------------------------------------------------- - -local function addZone(x1, y1, x2, y2, action, data) - ui.addZone(pendingZones, x1, y1, x2, y2, action, data) -end - -local function hitTest(x, y) - return ui.hitTest(touchZones, x, y) -end - -------------------------------------------------- --- Crafting helpers (delegated to shared ui module) -------------------------------------------------- - ---- Get stock total for an item from itemList -local function getItemTotal(itemName) - for _, item in ipairs(cache.itemList) do - if item.name == itemName then return item.total end - end - return 0 -end - -local function getRecipeIngredients(recipe) - return ui.getRecipeIngredients(recipe) -end - -local function canCraftRecipe(recipe) - return ui.canCraftRecipe(recipe, getItemTotal) -end - -local function maxCraftBatches(recipe) - return ui.maxCraftBatches(recipe, getItemTotal) -end - -local function getMissingIngredients(recipe) - return ui.getMissingIngredients(recipe, getItemTotal) -end - -------------------------------------------------- --- Smelter touch zone helpers -------------------------------------------------- - -local function addSmelterZone(x1, y1, x2, y2, action, data) - ui.addZone(smelterPendingZones, x1, y1, x2, y2, action, data) -end - -local function smelterHitTest(x, y) - return ui.hitTest(smelterTouchZones, x, y) -end - -------------------------------------------------- --- Drawing helpers (delegated to shared ui module) -------------------------------------------------- - -local draw = nil - -local function setDrawTarget(target) - draw = target - ui.draw = target -end - -local function monWrite(x, y, text, fg, bg) ui.monWrite(x, y, text, fg, bg) end -local function monFill(y, color) ui.monFill(y, color) end -local function monCenter(y, text, fg, bg) ui.monCenter(y, text, fg, bg) end -local function monBar(x, y, w, r, bc, bgc) ui.monBar(x, y, w, r, bc, bgc) end -local function drawButton(x, y, t, fg, bg, pl, pr) return ui.drawButton(x, y, t, fg, bg, pl, pr) end - ------------------------------------------------- -- Command ID generation (idempotency) ------------------------------------------------- @@ -265,6 +102,169 @@ local function sendToMaster(message) networkModem.transmit(ORDER_CHANNEL, CLIENT_CHANNEL, message) end +------------------------------------------------- +-- State — mirrors the manager's state module interface +-- so display.lua can read from it transparently. +------------------------------------------------- + +local state = {} + +state.cache = { + catalogue = {}, + itemList = {}, + itemListDirty = false, + grandTotal = 0, + chestCount = 0, + totalSlots = 0, + usedSlots = 0, + freeSlots = 0, + usedRatio = 0, + dropperOk = false, + barrelOk = false, + furnaceCount = 0, + furnaceStatus = {}, +} + +state.activity = { + sorting = false, + dispensing = false, + scanning = false, + smelting = false, + defragging = false, + composting = false, + crafting = false, +} + +state.needsRedraw = true +state.smelterNeedsRedraw = true +state.statusMessage = "" +state.statusColor = colors.white +state.statusTimer = 0 + +state.activeAlerts = {} +state.smeltingPaused = false +state.disabledRecipes = {} + +-- Client receives itemList pre-built from master; no-op here. +function state.ensureItemList() end + +------------------------------------------------- +-- Config — mirrors cfg interface for display.lua +------------------------------------------------- + +local cfg = { + MONITOR_SIDE = MONITOR_SIDE, + SMELTER_MONITOR_SIDE = SMELTER_MONITOR_SIDE, + SMELTABLE = {}, -- populated from master broadcast + CRAFTABLE = {}, -- populated from master broadcast +} + +------------------------------------------------- +-- Ops shim — sends commands to master instead +-- of executing locally. Exposes the same API +-- that display.lua calls on ctx.ops. +------------------------------------------------- + +local ops = {} + +--- Get stock total for an item from the received itemList. +local function getItemTotal(itemName) + for _, item in ipairs(state.cache.itemList) do + if item.name == itemName then return item.total end + end + return 0 +end + +function ops.getRecipeIngredients(recipe) + local ingredients = {} + for _, item in ipairs(recipe.grid) do + if item then + ingredients[item] = (ingredients[item] or 0) + 1 + end + end + return ingredients +end + +function ops.canCraftRecipe(recipe) + local ingredients = ops.getRecipeIngredients(recipe) + for itemName, needed in pairs(ingredients) do + if (getItemTotal(itemName) or 0) < needed then return false end + end + return true +end + +function ops.maxCraftBatches(recipe) + local ingredients = ops.getRecipeIngredients(recipe) + local minBatches = math.huge + for itemName, needed in pairs(ingredients) do + local batches = math.floor((getItemTotal(itemName) or 0) / needed) + if batches < minBatches then minBatches = batches end + end + if minBatches == math.huge then return 0 end + return minBatches +end + +function ops.getMissingIngredients(recipe) + local ingredients = ops.getRecipeIngredients(recipe) + local missing = {} + for itemName, needed in pairs(ingredients) do + local have = getItemTotal(itemName) or 0 + if have < needed then + table.insert(missing, { name = itemName, have = have, need = needed }) + end + end + return missing +end + +function ops.orderItem(itemName, amount) + sendToMaster({ + type = "order", + itemName = itemName, + amount = amount, + dropperName = CLIENT_DROPPER_NAME ~= "" and CLIENT_DROPPER_NAME or nil, + }) + log.info("ORDER", "Sent to master: %s x%d", itemName, amount) +end + +function ops.saveDisabledRecipes() + -- Client doesn't persist locally; the master handles persistence. + -- We forward the current disabledRecipes state to master via + -- toggle/enable/disable commands (already handled by display.lua events). +end + +function ops.craftItem(recipeIdx) + sendToMaster({ type = "craft", recipeIdx = recipeIdx }) + local recipe = cfg.CRAFTABLE[recipeIdx] + if recipe then + log.info("CRAFT", "Craft request sent: %s", recipe.output) + end + -- Return "pending" — the actual result arrives asynchronously + return true, "Sent to master" +end + +------------------------------------------------- +-- Connected flag (shown as "waiting" screen) +------------------------------------------------- + +local connected = false +local craftTurtleOk = false + +------------------------------------------------- +-- Build context for display.lua (same interface +-- that the manager passes to it). +------------------------------------------------- + +local ctx = { + cfg = cfg, + state = state, + log = log, + ops = ops, + craftTurtleName = nil, -- updated from master broadcasts +} + +-- Load the shared Opus UI display module — same one the manager uses. +local D = dofile(_path("manager/display.lua"))(ctx) + ------------------------------------------------- -- Client dropper discovery ------------------------------------------------- @@ -312,920 +312,6 @@ local function announceDroppers() end end -------------------------------------------------- --- Inventory Dashboard (identical to master) -------------------------------------------------- - -local function drawDashboard() - if not mon then return end - - local w, h = mon.getSize() - pendingZones = {} - - setDrawTarget(window.create(mon, 1, 1, w, h, false)) - draw.setBackgroundColor(colors.black) - draw.clear() - - if not connected then - monFill(1, colors.blue) - monCenter(1, " ** INVENTORY CLIENT ** ", colors.white, colors.blue) - local midY = math.floor(h / 2) - monCenter(midY - 1, "Waiting for master...", colors.lightGray, colors.black) - monCenter(midY + 1, "Make sure the master is running", colors.gray, colors.black) - monCenter(midY + 2, "and connected via wired modem.", colors.gray, colors.black) - monFill(h, colors.blue) - draw.setVisible(true) - touchZones = pendingZones - return - end - - -- ===== Title bar ===== - monFill(1, colors.blue) - monCenter(1, " ** INVENTORY MANAGER ** ", colors.white, colors.blue) - - -- ===== Status bar ===== - monFill(2, colors.gray) - local statusParts = {} - table.insert(statusParts, string.format(" Chests: %d", cache.chestCount)) - table.insert(statusParts, cache.dropperOk and "Dropper: OK" or "Dropper: --") - table.insert(statusParts, cache.barrelOk and "Barrel: OK" or "Barrel: --") - if cache.furnaceCount and cache.furnaceCount > 0 then - table.insert(statusParts, string.format("Furnaces: %d", cache.furnaceCount)) - end - - local actParts = {} - if activity.sorting then table.insert(actParts, "SORTING") end - if activity.dispensing then table.insert(actParts, "DISPENSING") end - if activity.smelting then table.insert(actParts, "SMELTING") end - if activity.scanning then table.insert(actParts, "SCANNING") end - if activity.defragging then table.insert(actParts, "DEFRAG") end - if activity.composting then table.insert(actParts, "COMPOST") end - - monWrite(2, 2, table.concat(statusParts, " | "), colors.white, colors.gray) - - if #actParts > 0 then - local actStr = " " .. table.concat(actParts, " | ") .. " " - monWrite(w - #actStr, 2, actStr, colors.white, colors.orange) - end - - -- ===== Divider ===== - monFill(3, colors.lightBlue) - monCenter(3, string.rep("-", math.min(w - 4, 60)), colors.cyan, colors.lightBlue) - - -- ===== Storage capacity ===== - monFill(4, colors.black) - local capLabel = string.format(" Storage: %d/%d slots (%d free)", - cache.usedSlots, cache.totalSlots, cache.freeSlots) - monWrite(2, 4, capLabel, colors.lightGray, colors.black) - - local barStart = #capLabel + 4 - local barWidth = w - barStart - 2 - if barWidth > 4 then - local barColor = colors.lime - if cache.usedRatio > 0.9 then barColor = colors.red - elseif cache.usedRatio > 0.7 then barColor = colors.orange - elseif cache.usedRatio > 0.5 then barColor = colors.yellow - end - monBar(barStart, 4, barWidth, cache.usedRatio, barColor, colors.gray) - local pctStr = string.format(" %d%% ", math.floor(cache.usedRatio * 100)) - local pctX = barStart + math.floor(barWidth / 2) - math.floor(#pctStr / 2) - monWrite(pctX, 4, pctStr, colors.white, barColor) - end - - -- ===== Amount selector (row 5) ===== - monFill(5, colors.black) - monWrite(2, 5, "Qty:", colors.lightGray, colors.black) - local btnX = 7 - for _, amt in ipairs(amountOptions) do - local label = tostring(amt) - local bg = (amt == selectedAmount) and colors.cyan or colors.gray - local fg = (amt == selectedAmount) and colors.white or colors.lightGray - local x1, y1, x2, y2 = drawButton(btnX, 5, label, fg, bg) - addZone(x1, y1, x2, y2, "amount", amt) - btnX = x2 + 2 - end - - local refreshBg = activity.scanning and colors.yellow or colors.green - local refreshFg = activity.scanning and colors.black or colors.white - local refreshTxt = activity.scanning and "Scanning" or "Refresh" - local scanX = w - #refreshTxt - 3 - local sx1, sy1, sx2, sy2 = drawButton(scanX, 5, refreshTxt, refreshFg, refreshBg, 1, 1) - addZone(sx1, sy1, sx2, sy2, "scan", nil) - - -- ===== Search bar + Pagination (row 6) ===== - monFill(6, colors.black) - - local kbLabel = showKeyboard and " X " or " ? " - local kbBg = showKeyboard and colors.red or colors.purple - monWrite(2, 6, kbLabel, colors.white, kbBg) - addZone(2, 6, 4, 6, "kb_toggle", nil) - - local queryDisplay = searchQuery - if showKeyboard then - queryDisplay = queryDisplay .. "|" - elseif queryDisplay == "" then - queryDisplay = "search..." - end - local fieldW = math.floor(w * 0.4) - if fieldW < 10 then fieldW = 10 end - local displayText = queryDisplay:sub(1, fieldW) - displayText = displayText .. string.rep("_", math.max(0, fieldW - #displayText)) - monWrite(6, 6, displayText, - (searchQuery == "" and not showKeyboard) and colors.gray or colors.white, - colors.black) - addZone(6, 6, 5 + fieldW, 6, "kb_toggle", nil) - - local filteredItems = getFilteredItems() - - local maxRows = h - 10 - if maxRows < 1 then maxRows = 1 end - totalPages = math.max(1, math.ceil(#filteredItems / maxRows)) - if currentPage > totalPages then currentPage = totalPages end - if currentPage < 1 then currentPage = 1 end - - local pageStr = string.format("Pg %d/%d", currentPage, totalPages) - local navW = 3 + 1 + #pageStr + 1 + 3 - local navX = w - navW - - if currentPage > 1 then - monWrite(navX, 6, " < ", colors.white, colors.gray) - addZone(navX, 6, navX + 2, 6, "page_prev", nil) - else - monWrite(navX, 6, " < ", colors.lightGray, colors.black) - end - - monWrite(navX + 4, 6, pageStr, colors.lightGray, colors.black) - - local nextX = navX + 4 + #pageStr + 1 - if currentPage < totalPages then - monWrite(nextX, 6, " > ", colors.white, colors.gray) - addZone(nextX, 6, nextX + 2, 6, "page_next", nil) - else - monWrite(nextX, 6, " > ", colors.lightGray, colors.black) - end - - -- ===== Column headers (row 7) ===== - local row = 7 - monFill(row, colors.gray) - monWrite(2, row, "#", colors.lightGray, colors.gray) - monWrite(5, row, "Item", colors.lightGray, colors.gray) - monWrite(w - 22, row, "Qty", colors.lightGray, colors.gray) - monWrite(w - 14, row, "Stock", colors.lightGray, colors.gray) - monWrite(w - 1, row, ">", colors.lightGray, colors.gray) - row = row + 1 - - -- ===== Item rows ===== - local maxCount = 0 - for _, item in ipairs(filteredItems) do - if item.total > maxCount then maxCount = item.total end - end - if maxCount == 0 then maxCount = 1 end - - local startIdx = (currentPage - 1) * maxRows + 1 - local endIdx = math.min(startIdx + maxRows - 1, #filteredItems) - - if #filteredItems == 0 then - monFill(8, colors.black) - monFill(9, colors.black) - if searchQuery ~= "" then - monCenter(9, "No items match \"" .. searchQuery .. "\"", colors.gray, colors.black) - else - monCenter(9, "No items in storage", colors.gray, colors.black) - end - row = 10 - else - for i = startIdx, endIdx do - local item = filteredItems[i] - local y = row - local short = item.name:gsub("^minecraft:", ""):gsub("_", " ") - short = short:sub(1,1):upper() .. short:sub(2) - - local maxNameLen = w - 30 - if #short > maxNameLen then - short = short:sub(1, maxNameLen - 2) .. ".." - end - - local rowBg = ((i - startIdx) % 2 == 0) and colors.black or colors.gray - monFill(y, rowBg) - - monWrite(2, y, string.format("%2d", i), colors.lightBlue, rowBg) - monWrite(5, y, short, colors.white, rowBg) - monWrite(w - 22, y, tostring(item.total), colors.yellow, rowBg) - - local ratio = item.total / maxCount - local barColor = colors.lime - if ratio < 0.25 then barColor = colors.red - elseif ratio < 0.5 then barColor = colors.orange - end - monBar(w - 14, y, 12, ratio, barColor, rowBg == colors.gray and colors.lightGray or colors.gray) - - monWrite(w - 1, y, ">", colors.orange, rowBg) - addZone(1, y, w, y, "order", item.name) - - row = row + 1 - end - end - - local lastItemRow = h - 3 - while row <= lastItemRow do - monFill(row, colors.black) - row = row + 1 - end - - if showKeyboard then - local keyW = 3 - local keyGap = 1 - - local kbDefs = { - { keys = kbRows[1], specials = {{ label = " Bksp ", action = "kb_bksp", bg = colors.red }} }, - { keys = kbRows[2], specials = {{ label = " Done ", action = "kb_done", bg = colors.green }} }, - { keys = kbRows[3], specials = { - { label = " Space ", action = "kb_space", bg = colors.lightGray }, - { label = " Clr ", action = "kb_clear", bg = colors.orange }, - }}, - } - - for rowIdx, def in ipairs(kbDefs) do - local y = h - 3 + rowIdx - monFill(y, colors.black) - - local keysW = #def.keys * keyW + math.max(0, #def.keys - 1) * keyGap - local specialsW = 0 - for _, sp in ipairs(def.specials) do - specialsW = specialsW + keyGap + #sp.label - end - local rowW = keysW + specialsW - local x = math.floor((w - rowW) / 2) + 1 - - for ki, key in ipairs(def.keys) do - monWrite(x, y, " " .. key .. " ", colors.white, colors.gray) - addZone(x, y, x + keyW - 1, y, "kb_key", key:lower()) - x = x + keyW - if ki < #def.keys then x = x + keyGap end - end - - for _, sp in ipairs(def.specials) do - x = x + keyGap - monWrite(x, y, sp.label, colors.white, sp.bg) - addZone(x, y, x + #sp.label - 1, y, sp.action, nil) - x = x + #sp.label - end - end - else - -- ===== Status message (h-2) ===== - monFill(h - 2, colors.black) - if #activeAlerts > 0 then - local alertIdx = math.floor(os.epoch("utc") / 2000) % #activeAlerts + 1 - local a = activeAlerts[alertIdx] - local alertMsg = string.format(" LOW STOCK: %s (%d/%d) ", a.label, a.current, a.min) - monCenter(h - 2, alertMsg, colors.white, colors.red) - elseif statusTimer > 0 and #statusMessage > 0 then - monCenter(h - 2, statusMessage, statusColor, colors.black) - end - - -- ===== Footer (h-1) ===== - monFill(h - 1, colors.gray) - local footerLeft = string.format(" Total: %d items | %d types ", - cache.grandTotal, #cache.itemList) - monWrite(2, h - 1, footerLeft, colors.white, colors.gray) - - if searchQuery ~= "" then - local filterNote = string.format("| Showing %d ", #filteredItems) - monWrite(2 + #footerLeft + 1, h - 1, filterNote, colors.yellow, colors.gray) - end - - local timeStr = textutils.formatTime(os.time(), true) - monWrite(w - #timeStr - 1, h - 1, timeStr, colors.lightGray, colors.gray) - - -- ===== Bottom accent (h) ===== - monFill(h, colors.blue) - local bottomMsg = " Tap item to order " - if activity.dispensing then - bottomMsg = " DISPENSING... " - elseif activity.smelting then - bottomMsg = " SMELTING... " - elseif activity.sorting then - bottomMsg = " SORTING BARREL... " - elseif activity.defragging then - bottomMsg = " DEFRAGMENTING... " - elseif activity.composting then - bottomMsg = " COMPOSTING... " - end - monCenter(h, bottomMsg, colors.lightBlue, colors.blue) - end - - draw.setVisible(true) - touchZones = pendingZones -end - -------------------------------------------------- --- Smelter Dashboard (identical to master) -------------------------------------------------- - -local function drawSmelterDashboard() - if not smelterMon then return end - - local w, h = smelterMon.getSize() - smelterPendingZones = {} - - setDrawTarget(window.create(smelterMon, 1, 1, w, h, false)) - draw.setBackgroundColor(colors.black) - draw.clear() - - if not connected then - monFill(1, colors.purple) - monCenter(1, " ** SMELTER CLIENT ** ", colors.white, colors.purple) - monCenter(math.floor(h / 2), "Waiting for master...", colors.gray, colors.black) - monFill(h, colors.purple) - draw.setVisible(true) - smelterTouchZones = smelterPendingZones - return - end - - -- ===== Title bar ===== - monFill(1, colors.purple) - monCenter(1, " ** SMELTER DASHBOARD ** ", colors.white, colors.purple) - - -- ===== Status bar ===== - monFill(2, colors.gray) - local activeCount = 0 - for _, fs in ipairs(cache.furnaceStatus or {}) do - if fs.active then activeCount = activeCount + 1 end - end - local statusStr = string.format(" Furnaces: %d Active: %d", - cache.furnaceCount or 0, activeCount) - monWrite(2, 2, statusStr, colors.white, colors.gray) - - local pauseLabel = smeltingPaused and " PAUSED " or " ACTIVE " - local pauseBg = smeltingPaused and colors.red or colors.lime - local pauseFg = smeltingPaused and colors.white or colors.black - monWrite(w - #pauseLabel, 2, pauseLabel, pauseFg, pauseBg) - addSmelterZone(w - #pauseLabel, 2, w - 1, 2, "toggle_pause", nil) - - -- ===== Divider ===== - monFill(3, colors.magenta) - monCenter(3, string.rep("-", math.min(w - 4, 60)), colors.pink, colors.magenta) - - -- ===== Tab row ===== - monFill(4, colors.black) - local tabStatusBg = smelterView == "status" and colors.purple or colors.gray - local tabSmeltBg = smelterView == "smelt" and colors.purple or colors.gray - local tabCraftBg = smelterView == "craft" and colors.purple or colors.gray - local tabMissingBg = smelterView == "missing" and colors.purple or colors.gray - local bx1, by1, bx2, by2 - bx1, by1, bx2, by2 = drawButton(2, 4, "Status", colors.white, tabStatusBg) - addSmelterZone(bx1, by1, bx2, by2, "tab", "status") - - bx1, by1, bx2, by2 = drawButton(bx2 + 2, 4, "Smelt", colors.white, tabSmeltBg) - addSmelterZone(bx1, by1, bx2, by2, "tab", "smelt") - - bx1, by1, bx2, by2 = drawButton(bx2 + 2, 4, "Craft", colors.white, tabCraftBg) - addSmelterZone(bx1, by1, bx2, by2, "tab", "craft") - - bx1, by1, bx2, by2 = drawButton(bx2 + 2, 4, "Missing", colors.white, tabMissingBg) - addSmelterZone(bx1, by1, bx2, by2, "tab", "missing") - - if smelterView == "status" then - -- ===== Furnace Status View ===== - monFill(5, colors.gray) - local outCol = math.floor(w * 0.40) - local fuelCol = math.floor(w * 0.65) - local statCol = w - 6 - monWrite(2, 5, "#", colors.lightGray, colors.gray) - monWrite(4, 5, "T", colors.lightGray, colors.gray) - monWrite(6, 5, "Input", colors.lightGray, colors.gray) - monWrite(outCol, 5, "Output", colors.lightGray, colors.gray) - monWrite(fuelCol, 5, "Fuel", colors.lightGray, colors.gray) - monWrite(statCol, 5, "State", colors.lightGray, colors.gray) - - local furnaceList = cache.furnaceStatus or {} - local maxRows = h - 8 - if maxRows < 1 then maxRows = 1 end - smelterTotalPages = math.max(1, math.ceil(#furnaceList / maxRows)) - if smelterPage > smelterTotalPages then smelterPage = smelterTotalPages end - if smelterPage < 1 then smelterPage = 1 end - - local startIdx = (smelterPage - 1) * maxRows + 1 - local endIdx = math.min(startIdx + maxRows - 1, #furnaceList) - - local row = 6 - if #furnaceList == 0 then - monFill(7, colors.black) - monCenter(7, "No furnaces found on network", colors.gray, colors.black) - row = 8 - else - for i = startIdx, endIdx do - local fs = furnaceList[i] - local y = row - local rowBg = ((i - startIdx) % 2 == 0) and colors.black or colors.gray - monFill(y, rowBg) - - monWrite(2, y, string.format("%d", i), colors.lightBlue, rowBg) - - local typeAbbr = "F" - local typeColor = colors.orange - if fs.type == "minecraft:smoker" then - typeAbbr = "S" - typeColor = colors.green - elseif fs.type == "minecraft:blast_furnace" then - typeAbbr = "B" - typeColor = colors.cyan - end - monWrite(4, y, typeAbbr, typeColor, rowBg) - - if fs.input then - local inName = fs.input.name:gsub("^minecraft:", ""):gsub("_", " ") - local maxIn = outCol - 8 - if #inName > maxIn then inName = inName:sub(1, maxIn - 2) .. ".." end - monWrite(6, y, inName, colors.white, rowBg) - monWrite(outCol - 4, y, "x" .. fs.input.count, colors.yellow, rowBg) - else - monWrite(6, y, "(empty)", colors.lightGray, rowBg) - end - - if fs.output then - local outName = fs.output.name:gsub("^minecraft:", ""):gsub("_", " ") - local maxOut = fuelCol - outCol - 5 - if #outName > maxOut then outName = outName:sub(1, maxOut - 2) .. ".." end - monWrite(outCol, y, outName, colors.white, rowBg) - monWrite(fuelCol - 4, y, "x" .. fs.output.count, colors.yellow, rowBg) - else - monWrite(outCol, y, "-", colors.lightGray, rowBg) - end - - if fs.fuel then - local fuelName = fs.fuel.name:gsub("^minecraft:", ""):gsub("_", " ") - local maxFuel = statCol - fuelCol - 4 - if #fuelName > maxFuel then fuelName = fuelName:sub(1, maxFuel - 2) .. ".." end - monWrite(fuelCol, y, fuelName, colors.white, rowBg) - monWrite(statCol - 4, y, "x" .. fs.fuel.count, colors.yellow, rowBg) - else - monWrite(fuelCol, y, "-", colors.lightGray, rowBg) - end - - if smeltingPaused then - monWrite(statCol, y, "PAUSE", colors.red, rowBg) - elseif fs.active then - monWrite(statCol, y, " COOK", colors.lime, rowBg) - elseif fs.input and not fs.fuel then - monWrite(statCol, y, "FUEL?", colors.orange, rowBg) - else - monWrite(statCol, y, " IDLE", colors.lightGray, rowBg) - end - - row = row + 1 - end - end - - while row <= h - 2 do - monFill(row, colors.black) - row = row + 1 - end - - elseif smelterView == "smelt" then - -- ===== Smelt Recipe Manager View ===== - -- Build item totals lookup from itemList - local itemTotals = {} - for _, item in ipairs(cache.itemList) do - itemTotals[item.name] = item.total - end - - local recipeList = {} - for inputName, recipe in pairs(SMELTABLE) do - local short = inputName:gsub("^minecraft:", ""):gsub("_", " ") - short = short:sub(1,1):upper() .. short:sub(2) - local resultShort = recipe.result:gsub("^minecraft:", ""):gsub("_", " ") - resultShort = resultShort:sub(1,1):upper() .. resultShort:sub(2) - local types = "" - for _, ft in ipairs(recipe.furnaces) do - if ft == "minecraft:furnace" then types = types .. "F" - elseif ft == "minecraft:smoker" then types = types .. "S" - elseif ft == "minecraft:blast_furnace" then types = types .. "B" - end - end - local enabled = not disabledRecipes[inputName] - local inStorage = itemTotals[inputName] or 0 - table.insert(recipeList, { - inputName = inputName, - inputShort = short, - resultShort = resultShort, - types = types, - enabled = enabled, - inStorage = inStorage, - }) - end - table.sort(recipeList, function(a, b) return a.inputShort < b.inputShort end) - - local arrowCol = math.floor(w * 0.30) - local typeCol = math.floor(w * 0.60) - local stockCol = math.floor(w * 0.72) - local toggleCol = w - 5 - - monFill(5, colors.gray) - monWrite(2, 5, "Input", colors.lightGray, colors.gray) - monWrite(arrowCol, 5, "Output", colors.lightGray, colors.gray) - monWrite(typeCol, 5, "Type", colors.lightGray, colors.gray) - monWrite(stockCol, 5, "Stock", colors.lightGray, colors.gray) - monWrite(toggleCol, 5, "On?", colors.lightGray, colors.gray) - - local bulkX = w - 22 - bx1, by1, bx2, by2 = drawButton(bulkX, 4, "All On", colors.white, colors.green) - addSmelterZone(bx1, by1, bx2, by2, "enable_all", nil) - bx1, by1, bx2, by2 = drawButton(bx2 + 2, 4, "All Off", colors.white, colors.red) - addSmelterZone(bx1, by1, bx2, by2, "disable_all", nil) - - local maxRows = h - 8 - if maxRows < 1 then maxRows = 1 end - smelterTotalPages = math.max(1, math.ceil(#recipeList / maxRows)) - if smelterPage > smelterTotalPages then smelterPage = smelterTotalPages end - if smelterPage < 1 then smelterPage = 1 end - - local startIdx = (smelterPage - 1) * maxRows + 1 - local endIdx = math.min(startIdx + maxRows - 1, #recipeList) - - local row = 6 - for i = startIdx, endIdx do - local r = recipeList[i] - local y = row - local rowBg = ((i - startIdx) % 2 == 0) and colors.black or colors.gray - monFill(y, rowBg) - - local maxInputLen = arrowCol - 3 - local inputDisplay = r.inputShort - if #inputDisplay > maxInputLen then - inputDisplay = inputDisplay:sub(1, maxInputLen - 2) .. ".." - end - monWrite(2, y, inputDisplay, colors.white, rowBg) - - local maxOutLen = typeCol - arrowCol - 2 - local outDisplay = r.resultShort - if #outDisplay > maxOutLen then - outDisplay = outDisplay:sub(1, maxOutLen - 2) .. ".." - end - monWrite(arrowCol, y, outDisplay, colors.lightBlue, rowBg) - - monWrite(typeCol, y, r.types, colors.orange, rowBg) - monWrite(stockCol, y, tostring(r.inStorage), colors.yellow, rowBg) - - if r.enabled then - monWrite(toggleCol, y, " ON ", colors.white, colors.green) - else - monWrite(toggleCol, y, " OFF", colors.white, colors.red) - end - addSmelterZone(1, y, w, y, "toggle_recipe", r.inputName) - - row = row + 1 - end - - while row <= h - 2 do - monFill(row, colors.black) - row = row + 1 - end - - elseif smelterView == "craft" then - -- ===== Available Crafting Recipes ===== - - -- Turtle status on tab row - local tLabel = craftTurtleOk and " Turtle OK " or " No Turtle " - local tBg = craftTurtleOk and colors.lime or colors.red - local tFg = craftTurtleOk and colors.black or colors.white - monWrite(w - #tLabel, 4, tLabel, tFg, tBg) - - local availList = {} - for idx, recipe in ipairs(CRAFTABLE) do - if canCraftRecipe(recipe) then - local short = recipe.output:gsub("^minecraft:", ""):gsub("_", " ") - short = short:sub(1,1):upper() .. short:sub(2) - local batches = maxCraftBatches(recipe) - table.insert(availList, { - idx = idx, - short = short, - count = recipe.count, - batches = batches, - }) - end - end - - monFill(5, colors.gray) - local makeCol = w - 6 - monWrite(2, 5, "#", colors.lightGray, colors.gray) - monWrite(4, 5, "Output", colors.lightGray, colors.gray) - monWrite(math.floor(w * 0.45), 5, "Yield", colors.lightGray, colors.gray) - monWrite(math.floor(w * 0.60), 5, "Can Make", colors.lightGray, colors.gray) - monWrite(makeCol, 5, "Go", colors.lightGray, colors.gray) - - local maxRows = h - 8 - if maxRows < 1 then maxRows = 1 end - smelterTotalPages = math.max(1, math.ceil(#availList / maxRows)) - if smelterPage > smelterTotalPages then smelterPage = smelterTotalPages end - if smelterPage < 1 then smelterPage = 1 end - - local startIdx = (smelterPage - 1) * maxRows + 1 - local endIdx = math.min(startIdx + maxRows - 1, #availList) - - local row = 6 - if #availList == 0 then - monFill(7, colors.black) - monCenter(7, "No recipes available to craft", colors.gray, colors.black) - row = 8 - else - for i = startIdx, endIdx do - local r = availList[i] - local y = row - local rowBg = ((i - startIdx) % 2 == 0) and colors.black or colors.gray - monFill(y, rowBg) - - monWrite(2, y, string.format("%2d", i), colors.lightBlue, rowBg) - - local maxNameLen = math.floor(w * 0.40) - local nameDisplay = r.short - if #nameDisplay > maxNameLen then - nameDisplay = nameDisplay:sub(1, maxNameLen - 2) .. ".." - end - monWrite(4, y, nameDisplay, colors.white, rowBg) - - monWrite(math.floor(w * 0.45), y, "x" .. r.count, colors.yellow, rowBg) - monWrite(math.floor(w * 0.60), y, - string.format("x%d", r.batches), colors.lime, rowBg) - - if craftTurtleOk then - monWrite(makeCol, y, " MAKE ", colors.white, colors.green) - addSmelterZone(makeCol, y, makeCol + 5, y, "craft", r.idx) - else - monWrite(makeCol, y, " ---- ", colors.gray, colors.black) - end - - row = row + 1 - end - end - - while row <= h - 2 do - monFill(row, colors.black) - row = row + 1 - end - - elseif smelterView == "missing" then - -- ===== Unavailable Crafting Recipes ===== - - local missList = {} - for idx, recipe in ipairs(CRAFTABLE) do - if not canCraftRecipe(recipe) then - local short = recipe.output:gsub("^minecraft:", ""):gsub("_", " ") - short = short:sub(1,1):upper() .. short:sub(2) - local missing = getMissingIngredients(recipe) - local parts = {} - for _, m in ipairs(missing) do - local mShort = m.name:gsub("^minecraft:", ""):gsub("_", " ") - table.insert(parts, string.format("%s %d/%d", mShort, m.have, m.need)) - end - table.insert(missList, { - idx = idx, - short = short, - count = recipe.count, - summary = table.concat(parts, ", "), - }) - end - end - - monFill(5, colors.gray) - monWrite(2, 5, "#", colors.lightGray, colors.gray) - monWrite(4, 5, "Output", colors.lightGray, colors.gray) - monWrite(math.floor(w * 0.35), 5, "Missing (have/need)", colors.lightGray, colors.gray) - - local maxRows = h - 8 - if maxRows < 1 then maxRows = 1 end - smelterTotalPages = math.max(1, math.ceil(#missList / maxRows)) - if smelterPage > smelterTotalPages then smelterPage = smelterTotalPages end - if smelterPage < 1 then smelterPage = 1 end - - local startIdx = (smelterPage - 1) * maxRows + 1 - local endIdx = math.min(startIdx + maxRows - 1, #missList) - - local row = 6 - if #missList == 0 then - monFill(7, colors.black) - monCenter(7, "All recipes can be crafted!", colors.lime, colors.black) - row = 8 - else - for i = startIdx, endIdx do - local r = missList[i] - local y = row - local rowBg = ((i - startIdx) % 2 == 0) and colors.black or colors.gray - monFill(y, rowBg) - - monWrite(2, y, string.format("%2d", i), colors.lightBlue, rowBg) - - local nameCol = math.floor(w * 0.35) - 5 - local nameDisplay = r.short .. " x" .. r.count - if #nameDisplay > nameCol then - nameDisplay = nameDisplay:sub(1, nameCol - 2) .. ".." - end - monWrite(4, y, nameDisplay, colors.white, rowBg) - - local missCol = math.floor(w * 0.35) - local missW = w - missCol - 1 - local summaryDisplay = r.summary - if #summaryDisplay > missW then - summaryDisplay = summaryDisplay:sub(1, missW - 2) .. ".." - end - monWrite(missCol, y, summaryDisplay, colors.red, rowBg) - - row = row + 1 - end - end - - while row <= h - 2 do - monFill(row, colors.black) - row = row + 1 - end - end - - -- ===== Pagination (h - 1) ===== - monFill(h - 1, colors.gray) - local pageStr = string.format("Pg %d/%d", smelterPage, smelterTotalPages) - monCenter(h - 1, pageStr, colors.white, colors.gray) - - if smelterPage > 1 then - monWrite(2, h - 1, " < ", colors.white, colors.lightGray) - addSmelterZone(2, h - 1, 4, h - 1, "page_prev", nil) - end - if smelterPage < smelterTotalPages then - monWrite(w - 3, h - 1, " > ", colors.white, colors.lightGray) - addSmelterZone(w - 3, h - 1, w - 1, h - 1, "page_next", nil) - end - - -- ===== Bottom accent ===== - monFill(h, colors.purple) - local bottomMsg = "" - if smelterView == "status" or smelterView == "smelt" then - local enabledCount = 0 - local totalRecipes = 0 - for _ in pairs(SMELTABLE) do totalRecipes = totalRecipes + 1 end - for inputName in pairs(SMELTABLE) do - if not disabledRecipes[inputName] then enabledCount = enabledCount + 1 end - end - bottomMsg = string.format(" Smelt: %d/%d enabled ", enabledCount, totalRecipes) - if activity.smelting then bottomMsg = " SMELTING... " end - elseif smelterView == "craft" then - bottomMsg = " Tap MAKE to craft " - if activity.crafting then bottomMsg = " CRAFTING... " end - elseif smelterView == "missing" then - local totalC = #CRAFTABLE - local availC = 0 - for _, r in ipairs(CRAFTABLE) do - if canCraftRecipe(r) then availC = availC + 1 end - end - bottomMsg = string.format(" Available: %d/%d recipes ", availC, totalC) - end - monCenter(h, bottomMsg, colors.pink, colors.purple) - - draw.setVisible(true) - smelterTouchZones = smelterPendingZones -end - -------------------------------------------------- --- Touch handler (sends orders to master via modem) -------------------------------------------------- - -local function handleTouch(x, y) - local action, data = hitTest(x, y) - if not action then return end - - if action == "amount" then - selectedAmount = data - log.debug("UI", "Amount set to %s", data) - needsRedraw = true - - elseif action == "order" then - local itemName = data - if itemName then - local short = itemName:gsub("^minecraft:", ""):gsub("_", " ") - statusMessage = string.format("Ordering %s x%d...", short, selectedAmount) - statusColor = colors.cyan - statusTimer = 10 - needsRedraw = true - -- Send order to master - sendToMaster({ - type = "order", - itemName = itemName, - amount = selectedAmount, - dropperName = CLIENT_DROPPER_NAME ~= "" and CLIENT_DROPPER_NAME or nil, - }) - log.info("ORDER", "Sent to master: %s x%d", itemName, selectedAmount) - end - - elseif action == "scan" then - statusMessage = "Requesting refresh..." - statusColor = colors.cyan - statusTimer = 3 - needsRedraw = true - sendToMaster({ type = "scan" }) - log.debug("UI", "Scan request sent to master") - - elseif action == "kb_toggle" then - showKeyboard = not showKeyboard - needsRedraw = true - - elseif action == "kb_key" then - if #searchQuery < 30 then - searchQuery = searchQuery .. data - end - currentPage = 1 - needsRedraw = true - - elseif action == "kb_bksp" then - if #searchQuery > 0 then - searchQuery = searchQuery:sub(1, -2) - end - currentPage = 1 - needsRedraw = true - - elseif action == "kb_space" then - if #searchQuery < 30 then - searchQuery = searchQuery .. " " - end - currentPage = 1 - needsRedraw = true - - elseif action == "kb_done" then - showKeyboard = false - needsRedraw = true - - elseif action == "kb_clear" then - searchQuery = "" - currentPage = 1 - needsRedraw = true - - elseif action == "page_prev" then - if currentPage > 1 then - currentPage = currentPage - 1 - end - needsRedraw = true - - elseif action == "page_next" then - if currentPage < totalPages then - currentPage = currentPage + 1 - end - needsRedraw = true - end -end - -------------------------------------------------- --- Smelter touch handler (sends commands to master) -------------------------------------------------- - -local function handleSmelterTouch(x, y) - local action, data = smelterHitTest(x, y) - if not action then return end - - if action == "tab" then - smelterView = data - smelterPage = 1 - smelterNeedsRedraw = true - - elseif action == "toggle_pause" then - sendToMaster({ type = "toggle_pause" }) - log.debug("UI", "Toggle pause sent to master") - smelterNeedsRedraw = true - - elseif action == "toggle_recipe" then - sendToMaster({ type = "toggle_recipe", recipe = data }) - log.debug("UI", "Toggle recipe sent: %s", data) - smelterNeedsRedraw = true - - elseif action == "enable_all" then - sendToMaster({ type = "enable_all" }) - log.debug("UI", "Enable all sent to master") - smelterNeedsRedraw = true - - elseif action == "disable_all" then - sendToMaster({ type = "disable_all" }) - log.debug("UI", "Disable all sent to master") - smelterNeedsRedraw = true - - elseif action == "page_prev" then - if smelterPage > 1 then - smelterPage = smelterPage - 1 - end - smelterNeedsRedraw = true - - elseif action == "page_next" then - if smelterPage < smelterTotalPages then - smelterPage = smelterPage + 1 - end - smelterNeedsRedraw = true - - elseif action == "craft" then - sendToMaster({ type = "craft", recipeIdx = data }) - local recipe = CRAFTABLE[data] - if recipe then - local short = recipe.output:gsub("^minecraft:", ""):gsub("_", " ") - log.info("CRAFT", "Craft request sent: %s", short) - end - smelterNeedsRedraw = true - end -end - ------------------------------------------------- -- Main ------------------------------------------------- @@ -1236,14 +322,14 @@ local function main() print("=================================") print("") - if setupMonitor() then - log.info("INIT", "Monitor: %s", monName or MONITOR_SIDE) + if D.setupMonitor() then + log.info("INIT", "Monitor: %s", D.monName or MONITOR_SIDE) else log.warn("INIT", "No monitor on %s", MONITOR_SIDE) end - if setupSmelterMonitor() then - log.info("INIT", "Smelter monitor: %s", smelterMonName or SMELTER_MONITOR_SIDE) + if D.setupSmelterMonitor() then + log.info("INIT", "Smelter monitor: %s", D.smelterMonName or SMELTER_MONITOR_SIDE) else log.warn("INIT", "No smelter monitor on %s", SMELTER_MONITOR_SIDE) end @@ -1270,8 +356,8 @@ local function main() print("") -- Draw initial "waiting" screen - needsRedraw = true - smelterNeedsRedraw = true + state.needsRedraw = true + state.smelterNeedsRedraw = true parallel.waitForAny( -- Task 1: Modem receiver (state updates + order replies) @@ -1280,39 +366,50 @@ local function main() local event, side, channel, replyChannel, message, distance = os.pullEvent("modem_message") if channel == BROADCAST_CHANNEL and type(message) == "table" and message.type == "state" then -- Update all state from master + local c = state.cache if message.cache then - cache.itemList = message.cache.itemList or cache.itemList - cache.grandTotal = message.cache.grandTotal ~= nil and message.cache.grandTotal or cache.grandTotal - cache.chestCount = message.cache.chestCount ~= nil and message.cache.chestCount or cache.chestCount - cache.totalSlots = message.cache.totalSlots ~= nil and message.cache.totalSlots or cache.totalSlots - cache.usedSlots = message.cache.usedSlots ~= nil and message.cache.usedSlots or cache.usedSlots - cache.freeSlots = message.cache.freeSlots ~= nil and message.cache.freeSlots or cache.freeSlots - cache.usedRatio = message.cache.usedRatio ~= nil and message.cache.usedRatio or cache.usedRatio - cache.dropperOk = message.cache.dropperOk - cache.barrelOk = message.cache.barrelOk - cache.furnaceCount = message.cache.furnaceCount ~= nil and message.cache.furnaceCount or cache.furnaceCount - cache.furnaceStatus = message.cache.furnaceStatus or cache.furnaceStatus + c.itemList = message.cache.itemList or c.itemList + c.grandTotal = message.cache.grandTotal ~= nil and message.cache.grandTotal or c.grandTotal + c.chestCount = message.cache.chestCount ~= nil and message.cache.chestCount or c.chestCount + c.totalSlots = message.cache.totalSlots ~= nil and message.cache.totalSlots or c.totalSlots + c.usedSlots = message.cache.usedSlots ~= nil and message.cache.usedSlots or c.usedSlots + c.freeSlots = message.cache.freeSlots ~= nil and message.cache.freeSlots or c.freeSlots + c.usedRatio = message.cache.usedRatio ~= nil and message.cache.usedRatio or c.usedRatio + c.dropperOk = message.cache.dropperOk + c.barrelOk = message.cache.barrelOk + c.furnaceCount = message.cache.furnaceCount ~= nil and message.cache.furnaceCount or c.furnaceCount + c.furnaceStatus = message.cache.furnaceStatus or c.furnaceStatus + -- Also build catalogue from itemList so display.lua + -- smelter tab can look up stock by item name + if message.cache.catalogue then + c.catalogue = message.cache.catalogue + end end if message.activity then - activity = message.activity + for k, v in pairs(message.activity) do + state.activity[k] = v + end end if message.alerts then - activeAlerts = message.alerts + state.activeAlerts = message.alerts end if message.smeltingPaused ~= nil then - smeltingPaused = message.smeltingPaused + state.smeltingPaused = message.smeltingPaused end if message.disabledRecipes then - disabledRecipes = message.disabledRecipes + state.disabledRecipes = message.disabledRecipes end if message.smeltable then - SMELTABLE = message.smeltable + cfg.SMELTABLE = message.smeltable end if message.craftable then - CRAFTABLE = message.craftable + cfg.CRAFTABLE = message.craftable end if message.craftTurtleOk ~= nil then craftTurtleOk = message.craftTurtleOk + -- display.lua supports ctx.craftTurtleOk as an override + -- so remote clients don't need local peripheral access. + ctx.craftTurtleOk = craftTurtleOk end if not connected then @@ -1320,31 +417,31 @@ local function main() log.info("NET", "Connected to master!") end - needsRedraw = true - smelterNeedsRedraw = true + state.needsRedraw = true + state.smelterNeedsRedraw = true elseif channel == CLIENT_CHANNEL and type(message) == "table" and message.type == "order_result" then -- Order result from master - statusMessage = message.message or "" - statusColor = message.color or (message.success and colors.lime or colors.red) - statusTimer = 5 - needsRedraw = true + state.statusMessage = message.message or "" + state.statusColor = message.color or (message.success and colors.lime or colors.red) + state.statusTimer = 5 + state.needsRedraw = true if message.success then - log.info("ORDER", "%s", statusMessage) + log.info("ORDER", "%s", state.statusMessage) else - log.warn("ORDER", "%s", statusMessage) + log.warn("ORDER", "%s", state.statusMessage) end elseif channel == CLIENT_CHANNEL and type(message) == "table" and message.type == "craft_result" then -- Craft result from master - statusMessage = message.error or (message.success and "Craft complete" or "Craft failed") - statusColor = message.success and colors.lime or colors.red - statusTimer = 5 - smelterNeedsRedraw = true + state.statusMessage = message.error or (message.success and "Craft complete" or "Craft failed") + state.statusColor = message.success and colors.lime or colors.red + state.statusTimer = 5 + state.smelterNeedsRedraw = true if message.success then - log.info("CRAFT", "%s", statusMessage) + log.info("CRAFT", "%s", state.statusMessage) else - log.warn("CRAFT", "%s", statusMessage) + log.warn("CRAFT", "%s", state.statusMessage) end end end @@ -1352,21 +449,21 @@ local function main() -- Task 2: Inventory dashboard redraw function() - needsRedraw = true + state.needsRedraw = true while true do - if needsRedraw then - needsRedraw = false - pcall(drawDashboard) + if state.needsRedraw then + state.needsRedraw = false + pcall(D.drawDashboard) end - if statusTimer > 0 then - statusTimer = statusTimer - 0.1 - if statusTimer <= 0 then - statusTimer = 0 - needsRedraw = true + if state.statusTimer > 0 then + state.statusTimer = state.statusTimer - 0.1 + if state.statusTimer <= 0 then + state.statusTimer = 0 + state.needsRedraw = true end end - if #activeAlerts > 0 then - needsRedraw = true + if #state.activeAlerts > 0 then + state.needsRedraw = true end sleep(0.1) end @@ -1374,11 +471,11 @@ local function main() -- Task 3: Smelter dashboard redraw function() - smelterNeedsRedraw = true + state.smelterNeedsRedraw = true while true do - if smelterNeedsRedraw then - smelterNeedsRedraw = false - pcall(drawSmelterDashboard) + if state.smelterNeedsRedraw then + state.smelterNeedsRedraw = false + pcall(D.drawSmelterDashboard) end sleep(0.1) end @@ -1417,12 +514,12 @@ local function main() function() while true do local event, side, x, y = os.pullEvent("monitor_touch") - if smelterMonName and side == smelterMonName then + if D.smelterMonName and side == D.smelterMonName then log.debug("TOUCH", "x=%d y=%d", x, y) - handleSmelterTouch(x, y) + D.handleSmelterTouch(x, y) else log.debug("TOUCH", "x=%d y=%d", x, y) - handleTouch(x, y) + D.handleTouch(x, y) end end end