461 lines
10 KiB
Lua
461 lines
10 KiB
Lua
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
|
|
local ok, image = pcall(paintutils.loadImage, "/icons16/diamond16.nfp")
|
|
fallbackIcon = ok and image or nil
|
|
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 itemCounts = getMeItemCounts(items)
|
|
local sortedItemIds = {}
|
|
|
|
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)
|
|
|
|
local 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()
|
|
|
|
while true do
|
|
local _, side, x, y = os.pullEvent("monitor_touch")
|
|
|
|
if side == 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)
|