local mon = peripheral.find("monitor") assert(mon, "Kein Monitor gefunden") local monName = peripheral.getName(mon) local SCREEN_WIDTH = 40 mon.setTextScale(0.5) local oldTerm = term.redirect(mon) local Pine3D = require("Pine3D") local mf = require("morefonts-pe") local frame = Pine3D.newFrame() frame:setBackgroundColor(colors.black) local meSystem = peripheral.find("meBridge") or peripheral.find("rsBridge") local chainItemsByBaseId local baseItemIds local itemById local defaultBaseId local fallbackIcon = false local backButton = false local drawOverview local function ensureChainsLoaded() if not chainItemsByBaseId then local file = assert(fs.open("atc_chains.json", "r")) local data = textutils.unserializeJSON(file.readAll()) file.close() assert(type(data) == "table" and type(data.chains) == "table", "Ungueltige JSON-Daten") chainItemsByBaseId = {} baseItemIds = {} itemById = {} for _, chain in ipairs(data.chains) do if not defaultBaseId then defaultBaseId = chain.base_id end baseItemIds[#baseItemIds + 1] = chain.base_id local items = {} local itemCount = 0 for _, item in ipairs(chain.items or {}) do if item.stage == "item" then itemById[item.item_id] = { id = item.item_id, icon_nfp = item.icon_nfp_16x16, } else itemCount = itemCount + 1 items[itemCount] = { id = item.item_id, icon_nfp = item.icon_nfp_16x16, } itemById[item.item_id] = items[itemCount] end end chainItemsByBaseId[chain.base_id] = items end end end local function getPageItems(base_id) ensureChainsLoaded() return chainItemsByBaseId[base_id or defaultBaseId] or {} end local function getBaseItemIds() ensureChainsLoaded() return baseItemIds end local function getItemById(item_id) ensureChainsLoaded() return itemById[item_id] end local function parseNfpImage(nfp) if type(nfp) ~= "string" or nfp == "" then return nil end local image = {} local y = 1 for line in (nfp .. "\n"):gmatch("(.-)\n") do local row = {} for x = 1, #line do local c = line:sub(x, x) if c ~= " " then local value = tonumber(c, 16) if value then row[x] = 2 ^ value end end end image[y] = row y = y + 1 end return image end local function getItemIcon(item) if not item then return nil end if item.icon == nil then item.icon = parseNfpImage(item.icon_nfp) or false item.icon_nfp = nil end return item.icon or nil end local function getMeItemCount(itemId) if not meSystem or not itemId then return 0 end if type(meSystem.getItem) == "function" then local ok, item = pcall(meSystem.getItem, { name = itemId }) if ok and item then return tonumber(item.amount or item.count or item.qty) or 0 end end return 0 end local function getMeItemCounts(itemIds) local counts = {} for i = 1, #itemIds do counts[itemIds[i]] = 0 end if not meSystem then return counts end local unresolved = {} local unresolvedCount = 0 for i = 1, #itemIds do local itemId = itemIds[i] local count = getMeItemCount(itemId) counts[itemId] = count if count == 0 then unresolved[itemId] = true unresolvedCount = unresolvedCount + 1 end end if unresolvedCount == 0 then return counts end local listItems = meSystem.listItems or meSystem.getItems or meSystem.listAvailableItems if not listItems then return counts end local ok, items = pcall(listItems) if not ok or type(items) ~= "table" then return counts end for i = 1, #items do local item = items[i] local itemId = item and (item.name or item.id or item.item_id) if itemId and unresolved[itemId] then counts[itemId] = tonumber(item.amount or item.count or item.qty) or 0 unresolved[itemId] = nil unresolvedCount = unresolvedCount - 1 if unresolvedCount == 0 then break end end end return counts end local function getFallbackIcon() if fallbackIcon == false then fallbackIcon = parseNfpImage(table.concat({ "0000000000000000", "000000eeee000000", "00000eeeeee00000", "0000eee00eee0000", "00000e00000ee000", "0000000000eee000", "000000000eee0000", "00000000eee00000", "00000000ee000000", "0000000000000000", "00000000ee000000", "00000000ee000000", "0000000000000000", "0000000000000000", "0000000000000000", "0000000000000000", }, "\n")) end return fallbackIcon end local function getBackButton() if backButton == false then backButton = parseNfpImage(table.concat({ "e e", " e ", "e e", }, "\n")) end return backButton end local function imageSize(img) local w, h = 0, 0 for y, row in pairs(img) do h = math.max(h, y) for x, value in pairs(row) do if type(value) == "number" and value > 0 then w = math.max(w, x) end end end return w, h end local function formatCount(n) n = math.floor(tonumber(n) or 0) if n < 1000 then return tostring(n) elseif n < 100000 then local v = n / 1000 if v < 10 then return string.format("%.1fK", v):gsub("%.0K", "K") end return tostring(math.floor(v)) .. "K" elseif n < 100000000 then local v = n / 1000000 if v < 10 then return string.format("%.1fM", v):gsub("%.0M", "M") end return tostring(math.floor(v)) .. "M" elseif n < 100000000000 then local v = n / 1000000000 if v < 10 then return string.format("%.1fB", v):gsub("%.0B", "B") end return tostring(math.floor(v)) .. "B" else local v = n / 1000000000000 if v < 10 then return string.format("%.1fT", v):gsub("%.0T", "T") end return tostring(math.floor(v)) .. "T" end end local function drawNfpScaled(buffer, img, x, y, scaleX, scaleY) scaleX = scaleX or 2 scaleY = scaleY or 3 for imgY, row in pairs(img) do for imgX, col in pairs(row) do -- WICHTIG: -- Nur echte ComputerCraft-Farben in den Pine3D-Buffer schreiben. -- Keine Strings, keine nils, keine 0. if type(col) == "number" and col > 0 then local px = x + (imgX - 1) * scaleX local py = y + (imgY - 1) * scaleY for dy = 0, scaleY - 1 do for dx = 0, scaleX - 1 do buffer:setPixel(px + dx, py + dy, col) end end end end end end local function assertBufferValid(frame) local valid = { [colors.white] = true, [colors.orange] = true, [colors.magenta] = true, [colors.lightBlue] = true, [colors.yellow] = true, [colors.lime] = true, [colors.pink] = true, [colors.gray] = true, [colors.lightGray] = true, [colors.cyan] = true, [colors.purple] = true, [colors.blue] = true, [colors.brown] = true, [colors.green] = true, [colors.red] = true, [colors.black] = true, } for y = 1, frame.buffer.height do for x = 1, frame.buffer.width do local c = frame.buffer.colorValues[y][x] if not valid[c] then error("Ungueltiger Pixel im Buffer bei x=" .. x .. ", y=" .. y .. ": " .. tostring(c)) end end end end local function drawItem(img, cellX, cellY, count) -- Normale Monitor-Zellen in Pine3D-Teletext-Pixel umrechnen. local x = (cellX - 1) * 2 + 1 local y = (cellY - 1) * 3 + 1 local imgW, imgH = 0, 0 local scaleX = 1 local scaleY = 1 if img then imgW, imgH = imageSize(img) drawNfpScaled(frame.buffer, img, x, y, scaleX, scaleY) end local text = formatCount(count) local rightX = x + 16 -- x + math.max(imgW * scaleX - 1, 7) local bottomY = y + 16 -- y + math.max(imgH * scaleY - 1, 4) local options = { font = "fonts/QuinqueFive", textAlign = "right", anchorHor = "right", anchorVer = "bottom", condense = true, scale = 1, dx = 1, dy = 1, } -- Schatten mf.writeOn(frame, text, colors.black, rightX + 1, bottomY + 1, options) -- Weißer Count mf.writeOn(frame, text, colors.white, rightX, bottomY, options) end local function drawPage(base_id) local pageItems = getPageItems(base_id) local defaultIcon = getFallbackIcon() local backIcon = getBackButton() local backWidth, backHeight = imageSize(backIcon) local backX = SCREEN_WIDTH - backWidth + 1 local pageItemIds = {} local visibleItemCount = math.min(#pageItems, 9) for i = 1, visibleItemCount do pageItemIds[i] = pageItems[i].id end local function renderPage() local itemCounts = getMeItemCounts(pageItemIds) frame.buffer:clear() drawNfpScaled(frame.buffer, backIcon, (backX - 1) * 2 + 1, 1) for i = 1, visibleItemCount do local item = pageItems[i] local icon = getItemIcon(item) or defaultIcon drawItem(icon, 4+(8+4)*((i-1)%3), 2+(5+1)*math.floor((i-1)/3), itemCounts[item.id] or 0) end assertBufferValid(frame) frame:drawBuffer() end renderPage() local refreshTimer = os.startTimer(5) while true do local event, p1, x, y = os.pullEvent() if event == "timer" and p1 == refreshTimer then renderPage() refreshTimer = os.startTimer(5) elseif event == "monitor_touch" and p1 == monName and x >= backX and x < backX + backWidth and y >= 1 and y <= backHeight then drawOverview() return end end end drawOverview = function() local items = getBaseItemIds() local sortedItemIds = {} local visibleItemCount = 0 local function renderOverview() local itemCounts = getMeItemCounts(items) for i = 1, #items do sortedItemIds[i] = items[i] end table.sort(sortedItemIds, function(a, b) local countA = itemCounts[a] or 0 local countB = itemCounts[b] or 0 if countA ~= countB then return countA > countB end return a < b end) visibleItemCount = math.min(#sortedItemIds, 9) frame.buffer:clear() for i = 1, visibleItemCount do local itemId = sortedItemIds[i] local item = getItemById(itemId) local icon = getItemIcon(item) or getFallbackIcon() drawItem(icon, 4+(8+4)*((i-1)%3), 2+(5+1)*math.floor((i-1)/3), itemCounts[itemId] or 0) end assertBufferValid(frame) frame:drawBuffer() end renderOverview() local refreshTimer = os.startTimer(30) while true do local event, p1, x, y = os.pullEvent() if event == "timer" and p1 == refreshTimer then renderOverview() refreshTimer = os.startTimer(30) elseif event == "monitor_touch" and p1 == monName then local col = math.floor((x - 4) / 12) local row = math.floor((y - 2) / 6) if col >= 0 and col < 3 and row >= 0 and row < 3 then local index = row * 3 + col + 1 if index <= visibleItemCount then drawPage(sortedItemIds[index]) return end end end end end drawOverview() term.redirect(oldTerm)