Compare commits
18 Commits
8d9abc8cb2
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a7ca281dd | |||
| d3b7ebfc15 | |||
| 3114e0ae7f | |||
| fb7bcf5780 | |||
| d1b569979e | |||
| 4c00d3b232 | |||
| 5907cd8f27 | |||
| 5915e928c3 | |||
| 6d22b5547c | |||
| 911d51815d | |||
| 4c77395fc7 | |||
| 4653050f45 | |||
| f8ad2a6439 | |||
| 514c7e9b22 | |||
| 0c33acf06d | |||
| 63775ae3ea | |||
| 862aa03549 | |||
| 816a28986c |
+1
-31561
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def crop_icon(icon: str | None) -> list[int | str] | None:
|
||||
if not icon:
|
||||
return None
|
||||
|
||||
lines = icon.split("\n")
|
||||
xs: list[int] = []
|
||||
ys: list[int] = []
|
||||
|
||||
for y, line in enumerate(lines):
|
||||
for x, char in enumerate(line):
|
||||
if char != " ":
|
||||
xs.append(x)
|
||||
ys.append(y)
|
||||
|
||||
if not xs:
|
||||
return [0, 0, ""]
|
||||
|
||||
min_x = min(xs)
|
||||
max_x = max(xs)
|
||||
min_y = min(ys)
|
||||
max_y = max(ys)
|
||||
cropped_lines = [line[min_x : max_x + 1].rstrip() for line in lines[min_y : max_y + 1]]
|
||||
return [min_x, min_y, "\n".join(cropped_lines)]
|
||||
|
||||
|
||||
def compact_chains(data: object) -> list[list[object]]:
|
||||
if isinstance(data, dict):
|
||||
chains = data.get("chains")
|
||||
else:
|
||||
chains = data
|
||||
|
||||
if not isinstance(chains, list):
|
||||
raise ValueError("Input JSON does not contain a valid chains array")
|
||||
|
||||
compact: list[list[object]] = []
|
||||
|
||||
for chain in chains:
|
||||
if isinstance(chain, list):
|
||||
if len(chain) == 2 and isinstance(chain[1], list):
|
||||
compact.append(chain)
|
||||
continue
|
||||
if len(chain) == 3 and isinstance(chain[1], int) and isinstance(chain[2], list):
|
||||
compact.append([chain[0], chain[2]])
|
||||
continue
|
||||
raise ValueError("Invalid compact chain entry")
|
||||
|
||||
if not isinstance(chain, dict):
|
||||
raise ValueError("Expected verbose or compact chain entries as input")
|
||||
|
||||
items = chain.get("items")
|
||||
if not isinstance(items, list) or not items:
|
||||
continue
|
||||
|
||||
base_id = chain["base_id"]
|
||||
compact_items: list[list[object]] = []
|
||||
|
||||
for item in items:
|
||||
compact_items.append([
|
||||
item["item_id"],
|
||||
crop_icon(item.get("icon_nfp_16x16")),
|
||||
])
|
||||
|
||||
compact.append([base_id, compact_items])
|
||||
|
||||
return compact
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Rewrite atc_chains.json into a compact format used by compcount.lua."
|
||||
)
|
||||
parser.add_argument("source", nargs="?", default="atc_chains_uncompressed.json")
|
||||
parser.add_argument("destination", nargs="?", default="atc_chains.json")
|
||||
args = parser.parse_args()
|
||||
|
||||
source_path = Path(args.source)
|
||||
destination_path = Path(args.destination)
|
||||
original_text = source_path.read_text(encoding="utf-8")
|
||||
data = json.loads(original_text)
|
||||
compact = {"chains": compact_chains(data)}
|
||||
compact_text = json.dumps(compact, separators=(",", ":"))
|
||||
destination_path.write_text(compact_text, encoding="utf-8")
|
||||
|
||||
print(f"Compacted {source_path} -> {destination_path}")
|
||||
print(f"Old size: {len(original_text.encode('utf-8'))} bytes")
|
||||
print(f"New size: {len(compact_text.encode('utf-8'))} bytes")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+494
-98
@@ -26,10 +26,28 @@ local mf = require("morefonts-pe")
|
||||
|
||||
local frame = Pine3D.newFrame()
|
||||
frame:setBackgroundColor(colors.black)
|
||||
local meSystem = peripheral.find("meBridge") or peripheral.find("rsBridge")
|
||||
|
||||
local function findStorageBridge()
|
||||
return peripheral.find("meBridge")
|
||||
or peripheral.find("me_bridge")
|
||||
or peripheral.find("rsBridge")
|
||||
or peripheral.find("rs_bridge")
|
||||
end
|
||||
|
||||
local meSystem = findStorageBridge()
|
||||
|
||||
local function ensureStorageBridge()
|
||||
if not meSystem then
|
||||
meSystem = findStorageBridge()
|
||||
end
|
||||
|
||||
return meSystem
|
||||
end
|
||||
|
||||
local chainItemsByBaseId
|
||||
local chainAllItemsByBaseId
|
||||
local baseItemIds
|
||||
local allChainItemIds
|
||||
local itemById
|
||||
local defaultBaseId
|
||||
local whitelistLookup
|
||||
@@ -37,8 +55,15 @@ local fallbackIcon = false
|
||||
local backButton = false
|
||||
local scrollUpButton = false
|
||||
local scrollDownButton = false
|
||||
local overviewCachedCounts = {}
|
||||
local overviewCachedSortedItemIds = nil
|
||||
local overviewScrollCurrentRow = 0
|
||||
local overviewScrollTargetRow = 0
|
||||
local globalItemCounts = {}
|
||||
local globalCountRefreshInterval = 5
|
||||
local globalCountRefreshJob
|
||||
local globalCountRefreshTimer
|
||||
local globalCountLastRefreshAt = 0
|
||||
local drawOverview
|
||||
|
||||
local function ensureWhitelistLoaded()
|
||||
@@ -71,6 +96,55 @@ local function ensureWhitelistLoaded()
|
||||
end
|
||||
end
|
||||
|
||||
local function getCompressionLevel(itemId, stage)
|
||||
if stage == "item" then
|
||||
return 0
|
||||
end
|
||||
|
||||
if type(stage) == "string" then
|
||||
local level = tonumber(stage:match("^(%d+)x$"))
|
||||
if level then
|
||||
return level
|
||||
end
|
||||
end
|
||||
|
||||
if type(itemId) == "string" then
|
||||
local level = tonumber(itemId:match("_(%d+)x$"))
|
||||
if level then
|
||||
return level
|
||||
end
|
||||
end
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
local function createChainItem(itemId, iconNfp, stage)
|
||||
return {
|
||||
id = itemId,
|
||||
icon_nfp = iconNfp,
|
||||
stage = stage,
|
||||
compression_level = getCompressionLevel(itemId, stage),
|
||||
equivalent_factor = 1,
|
||||
}
|
||||
end
|
||||
|
||||
local function applyChainEquivalentFactors(baseId, orderedItems)
|
||||
local baseIndex = nil
|
||||
|
||||
for i = 1, #orderedItems do
|
||||
if orderedItems[i].id == baseId then
|
||||
baseIndex = i
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
assert(baseIndex, "Missing base item in chain: " .. tostring(baseId))
|
||||
|
||||
for i = 1, #orderedItems do
|
||||
orderedItems[i].equivalent_factor = 9 ^ (i - baseIndex)
|
||||
end
|
||||
end
|
||||
|
||||
local function ensureChainsLoaded()
|
||||
if not chainItemsByBaseId then
|
||||
ensureWhitelistLoaded()
|
||||
@@ -79,40 +153,74 @@ local function ensureChainsLoaded()
|
||||
local data = textutils.unserializeJSON(file.readAll())
|
||||
file.close()
|
||||
|
||||
assert(type(data) == "table" and type(data.chains) == "table", "Invalid JSON data")
|
||||
local chains = data
|
||||
|
||||
if type(data) == "table" and type(data.chains) == "table" then
|
||||
chains = data.chains
|
||||
end
|
||||
|
||||
assert(type(chains) == "table", "Invalid JSON data")
|
||||
|
||||
chainItemsByBaseId = {}
|
||||
chainAllItemsByBaseId = {}
|
||||
baseItemIds = {}
|
||||
allChainItemIds = {}
|
||||
itemById = {}
|
||||
|
||||
for _, chain in ipairs(data.chains) do
|
||||
if not whitelistLookup or whitelistLookup[chain.base_id] then
|
||||
for _, chain in ipairs(chains) do
|
||||
local baseId = chain.base_id or chain[1]
|
||||
|
||||
if not whitelistLookup or whitelistLookup[baseId] then
|
||||
if not defaultBaseId then
|
||||
defaultBaseId = chain.base_id
|
||||
defaultBaseId = baseId
|
||||
end
|
||||
|
||||
baseItemIds[#baseItemIds + 1] = chain.base_id
|
||||
baseItemIds[#baseItemIds + 1] = baseId
|
||||
|
||||
local items = {}
|
||||
local itemCount = 0
|
||||
local orderedItems = {}
|
||||
local pageItems = {}
|
||||
|
||||
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,
|
||||
}
|
||||
if chain.base_id then
|
||||
for _, item in ipairs(chain.items or {}) do
|
||||
local chainItem = createChainItem(item.item_id, item.icon_nfp_16x16, item.stage)
|
||||
orderedItems[#orderedItems + 1] = chainItem
|
||||
itemById[item.item_id] = chainItem
|
||||
allChainItemIds[#allChainItemIds + 1] = item.item_id
|
||||
end
|
||||
|
||||
else
|
||||
local compactItems = nil
|
||||
|
||||
if type(chain[2]) == "number" then
|
||||
compactItems = chain[3] or {}
|
||||
elseif type(chain[2]) == "table" then
|
||||
compactItems = chain[2]
|
||||
elseif type(chain[2]) == "string" and type(chain[3]) == "table" then
|
||||
orderedItems[1] = createChainItem(baseId, chain[2], "item")
|
||||
itemById[baseId] = orderedItems[1]
|
||||
allChainItemIds[#allChainItemIds + 1] = baseId
|
||||
compactItems = chain[3]
|
||||
else
|
||||
itemCount = itemCount + 1
|
||||
items[itemCount] = {
|
||||
id = item.item_id,
|
||||
icon_nfp = item.icon_nfp_16x16,
|
||||
}
|
||||
itemById[item.item_id] = items[itemCount]
|
||||
error("Invalid compact chain entry for " .. tostring(baseId))
|
||||
end
|
||||
|
||||
for i = 1, #compactItems do
|
||||
local compactItem = compactItems[i]
|
||||
local chainItem = createChainItem(compactItem[1], compactItem[2], compactItem[3])
|
||||
orderedItems[#orderedItems + 1] = chainItem
|
||||
itemById[compactItem[1]] = chainItem
|
||||
allChainItemIds[#allChainItemIds + 1] = compactItem[1]
|
||||
end
|
||||
end
|
||||
|
||||
chainItemsByBaseId[chain.base_id] = items
|
||||
applyChainEquivalentFactors(baseId, orderedItems)
|
||||
|
||||
for i = 1, #orderedItems do
|
||||
pageItems[#pageItems + 1] = orderedItems[i]
|
||||
end
|
||||
|
||||
chainAllItemsByBaseId[baseId] = orderedItems
|
||||
chainItemsByBaseId[baseId] = pageItems
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -124,6 +232,12 @@ local function getPageItems(base_id)
|
||||
return chainItemsByBaseId[base_id or defaultBaseId] or {}
|
||||
end
|
||||
|
||||
local function getAllChainItems(base_id)
|
||||
ensureChainsLoaded()
|
||||
|
||||
return chainAllItemsByBaseId[base_id or defaultBaseId] or {}
|
||||
end
|
||||
|
||||
local function getBaseItemIds()
|
||||
ensureChainsLoaded()
|
||||
|
||||
@@ -164,13 +278,49 @@ local function parseNfpImage(nfp)
|
||||
return image
|
||||
end
|
||||
|
||||
local function parseIconData(iconData)
|
||||
if type(iconData) == "string" then
|
||||
return parseNfpImage(iconData)
|
||||
end
|
||||
|
||||
if type(iconData) ~= "table" then
|
||||
return nil
|
||||
end
|
||||
|
||||
local offsetX = tonumber(iconData[1]) or 0
|
||||
local offsetY = tonumber(iconData[2]) or 0
|
||||
local croppedImage = parseNfpImage(iconData[3])
|
||||
|
||||
if not croppedImage then
|
||||
return nil
|
||||
end
|
||||
|
||||
if offsetX == 0 and offsetY == 0 then
|
||||
return croppedImage
|
||||
end
|
||||
|
||||
local image = {}
|
||||
|
||||
for y, row in pairs(croppedImage) do
|
||||
local shiftedRow = {}
|
||||
|
||||
for x, value in pairs(row) do
|
||||
shiftedRow[x + offsetX] = value
|
||||
end
|
||||
|
||||
image[y + offsetY] = shiftedRow
|
||||
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 = parseIconData(item.icon_nfp) or false
|
||||
item.icon_nfp = nil
|
||||
end
|
||||
|
||||
@@ -178,12 +328,14 @@ local function getItemIcon(item)
|
||||
end
|
||||
|
||||
local function getMeItemCount(itemId)
|
||||
if not meSystem or not itemId then
|
||||
local bridge = ensureStorageBridge()
|
||||
|
||||
if not bridge or not itemId then
|
||||
return 0
|
||||
end
|
||||
|
||||
if type(meSystem.getItem) == "function" then
|
||||
local ok, item = pcall(meSystem.getItem, { name = itemId })
|
||||
if type(bridge.getItem) == "function" then
|
||||
local ok, item = pcall(bridge.getItem, { name = itemId })
|
||||
if ok and item then
|
||||
return tonumber(item.amount or item.count or item.qty) or 0
|
||||
end
|
||||
@@ -192,6 +344,38 @@ local function getMeItemCount(itemId)
|
||||
return 0
|
||||
end
|
||||
|
||||
local function listBridgeItems(bridge)
|
||||
local listItems = bridge.listItems or bridge.getItems or bridge.listAvailableItems
|
||||
|
||||
if not listItems then
|
||||
return nil
|
||||
end
|
||||
|
||||
local ok, items = pcall(listItems, {})
|
||||
if ok and type(items) == "table" then
|
||||
local normalized = {}
|
||||
|
||||
for _, item in pairs(items) do
|
||||
normalized[#normalized + 1] = item
|
||||
end
|
||||
|
||||
return normalized
|
||||
end
|
||||
|
||||
ok, items = pcall(listItems)
|
||||
if ok and type(items) == "table" then
|
||||
local normalized = {}
|
||||
|
||||
for _, item in pairs(items) do
|
||||
normalized[#normalized + 1] = item
|
||||
end
|
||||
|
||||
return normalized
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
local function getMeItemCounts(itemIds)
|
||||
local counts = {}
|
||||
|
||||
@@ -199,56 +383,205 @@ local function getMeItemCounts(itemIds)
|
||||
counts[itemIds[i]] = 0
|
||||
end
|
||||
|
||||
if not meSystem then
|
||||
local bridge = ensureStorageBridge()
|
||||
|
||||
if not bridge then
|
||||
return counts
|
||||
end
|
||||
|
||||
local unresolved = {}
|
||||
local unresolvedCount = 0
|
||||
local snapshot = listBridgeItems(bridge)
|
||||
if not snapshot then
|
||||
return counts
|
||||
end
|
||||
|
||||
local wanted = {}
|
||||
|
||||
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
|
||||
wanted[itemIds[i]] = true
|
||||
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]
|
||||
for i = 1, #snapshot do
|
||||
local item = snapshot[i]
|
||||
local itemId = item and (item.name or item.id or item.item_id)
|
||||
|
||||
if itemId and unresolved[itemId] then
|
||||
if itemId and wanted[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 makeZeroCounts(itemIds)
|
||||
local counts = {}
|
||||
|
||||
for i = 1, #itemIds do
|
||||
counts[itemIds[i]] = 0
|
||||
end
|
||||
|
||||
return counts
|
||||
end
|
||||
|
||||
local function createAsyncCountsFromSnapshotJob(itemIds, snapshot, batchSize, baseCounts)
|
||||
local counts = {}
|
||||
local index = 1
|
||||
local wanted = {}
|
||||
|
||||
batchSize = math.max(1, math.floor(tonumber(batchSize) or 1))
|
||||
|
||||
for i = 1, #itemIds do
|
||||
wanted[itemIds[i]] = true
|
||||
counts[itemIds[i]] = 0
|
||||
end
|
||||
|
||||
if type(baseCounts) == "table" then
|
||||
for itemId, count in pairs(baseCounts) do
|
||||
if wanted[itemId] then
|
||||
counts[itemId] = tonumber(count) or 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
step = function()
|
||||
local lastIndex = math.min(#snapshot, index + batchSize - 1)
|
||||
|
||||
for i = index, lastIndex do
|
||||
local item = snapshot[i]
|
||||
local itemId = item and (item.name or item.id or item.item_id)
|
||||
|
||||
if itemId and wanted[itemId] then
|
||||
counts[itemId] = tonumber(item.amount or item.count or item.qty) or 0
|
||||
end
|
||||
end
|
||||
|
||||
index = lastIndex + 1
|
||||
return index > #snapshot, counts
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
local function getGlobalItemCounts()
|
||||
ensureChainsLoaded()
|
||||
return globalItemCounts
|
||||
end
|
||||
|
||||
local function hasGlobalItemCounts()
|
||||
return next(globalItemCounts) ~= nil
|
||||
end
|
||||
|
||||
local function getBaseEquivalentCount(baseId, itemCounts)
|
||||
local total = 0
|
||||
local chainItems = getAllChainItems(baseId)
|
||||
|
||||
for i = 1, #chainItems do
|
||||
local item = chainItems[i]
|
||||
total = total + (tonumber(itemCounts[item.id]) or 0) * (item.equivalent_factor or 1)
|
||||
end
|
||||
|
||||
return total
|
||||
end
|
||||
|
||||
local function getEquivalentLevelCounts(baseId, pageItems, itemCounts)
|
||||
local equivalentCounts = {}
|
||||
local baseEquivalentCount = getBaseEquivalentCount(baseId, itemCounts)
|
||||
|
||||
for i = 1, #pageItems do
|
||||
local item = pageItems[i]
|
||||
local factor = item.equivalent_factor or 1
|
||||
equivalentCounts[item.id] = math.floor(baseEquivalentCount / factor)
|
||||
end
|
||||
|
||||
return equivalentCounts
|
||||
end
|
||||
|
||||
local function getEquivalentBaseCounts(baseIds, itemCounts)
|
||||
local equivalentCounts = {}
|
||||
|
||||
for i = 1, #baseIds do
|
||||
local baseId = baseIds[i]
|
||||
equivalentCounts[baseId] = getBaseEquivalentCount(baseId, itemCounts)
|
||||
end
|
||||
|
||||
return equivalentCounts
|
||||
end
|
||||
|
||||
local function createGlobalCountRefreshJob()
|
||||
ensureChainsLoaded()
|
||||
|
||||
local bridge = ensureStorageBridge()
|
||||
if not bridge then
|
||||
return nil
|
||||
end
|
||||
|
||||
local snapshot = listBridgeItems(bridge)
|
||||
if not snapshot then
|
||||
return nil
|
||||
end
|
||||
|
||||
local job = createAsyncCountsFromSnapshotJob(allChainItemIds or {}, snapshot, 25, globalItemCounts)
|
||||
|
||||
return {
|
||||
step = function()
|
||||
local isDone, counts = job.step()
|
||||
|
||||
if isDone then
|
||||
globalItemCounts = counts
|
||||
globalCountLastRefreshAt = os.epoch("utc")
|
||||
end
|
||||
|
||||
return isDone, counts
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
local function shouldStartGlobalCountRefresh()
|
||||
if globalCountRefreshJob then
|
||||
return false
|
||||
end
|
||||
|
||||
if not hasGlobalItemCounts() then
|
||||
return true
|
||||
end
|
||||
|
||||
return os.epoch("utc") - globalCountLastRefreshAt >= globalCountRefreshInterval * 1000
|
||||
end
|
||||
|
||||
local function startGlobalCountRefreshIfNeeded(force)
|
||||
if globalCountRefreshJob then
|
||||
return false
|
||||
end
|
||||
|
||||
if not force and not shouldStartGlobalCountRefresh() then
|
||||
return false
|
||||
end
|
||||
|
||||
globalCountRefreshJob = createGlobalCountRefreshJob()
|
||||
if not globalCountRefreshJob then
|
||||
return false
|
||||
end
|
||||
|
||||
globalCountRefreshTimer = os.startTimer(0.01)
|
||||
return true
|
||||
end
|
||||
|
||||
local function stepGlobalCountRefresh(timerId)
|
||||
if not globalCountRefreshJob or timerId ~= globalCountRefreshTimer then
|
||||
return false, false
|
||||
end
|
||||
|
||||
local isDone = globalCountRefreshJob.step()
|
||||
|
||||
if isDone then
|
||||
globalCountRefreshJob = nil
|
||||
globalCountRefreshTimer = nil
|
||||
return true, true
|
||||
end
|
||||
|
||||
globalCountRefreshTimer = os.startTimer(0.01)
|
||||
return true, false
|
||||
end
|
||||
|
||||
local function getFallbackIcon()
|
||||
if fallbackIcon == false then
|
||||
fallbackIcon = parseNfpImage(table.concat({
|
||||
@@ -442,7 +775,7 @@ local function assertBufferValid(frame)
|
||||
end
|
||||
end
|
||||
|
||||
local function drawItem(img, cellX, cellY, count, offsetY)
|
||||
local function drawItem(img, cellX, cellY, count, offsetY, item)
|
||||
-- Convert normal monitor cells into Pine3D teletext pixels.
|
||||
local x = cellToPixelX(cellX)
|
||||
local y = cellToPixelY(cellY) + (offsetY or 0)
|
||||
@@ -459,6 +792,19 @@ local function drawItem(img, cellX, cellY, count, offsetY)
|
||||
end
|
||||
end
|
||||
|
||||
local compressionLevel = item and tonumber(item.compression_level) or 0
|
||||
|
||||
if compressionLevel > 0 and y <= frame.buffer.height and y + 8 >= 1 then
|
||||
mf.writeOn(frame, tostring(compressionLevel), colors.red, x + 15, y, {
|
||||
font = "fonts/PixelPlace",
|
||||
textAlign = "right",
|
||||
anchorHor = "right",
|
||||
anchorVer = "top",
|
||||
condense = true,
|
||||
scale = 1,
|
||||
})
|
||||
end
|
||||
|
||||
local text = formatCount(count)
|
||||
|
||||
local rightX = x + 16 -- x + math.max(imgW * scaleX - 1, 7)
|
||||
@@ -485,8 +831,8 @@ local function drawItem(img, cellX, cellY, count, offsetY)
|
||||
end
|
||||
|
||||
local function runScrollableGrid(options)
|
||||
local entries = {}
|
||||
local counts = {}
|
||||
local entries = options.getInitialEntries()
|
||||
local counts = options.getInitialCounts(entries)
|
||||
local visibleItemCount = 0
|
||||
local totalRows = 0
|
||||
local maxScrollRow = 0
|
||||
@@ -494,13 +840,14 @@ local function runScrollableGrid(options)
|
||||
local targetScrollRow = 0
|
||||
local refreshTimer
|
||||
local animationTimer
|
||||
local pendingCacheSync = false
|
||||
|
||||
local columns = 3
|
||||
local columns = 5
|
||||
local rowsPerView = 3
|
||||
local baseCellX = 4
|
||||
local baseCellX = 3
|
||||
local baseCellY = 2
|
||||
local colStepCells = 12
|
||||
local rowStepCells = 6
|
||||
local colStepCells = 8+3
|
||||
local rowStepCells = 5+3
|
||||
local rowStepPixels = rowStepCells * 3
|
||||
local scrollbarX = SCREEN_WIDTH - 1
|
||||
local scrollbarWidth = 2
|
||||
@@ -564,8 +911,8 @@ local function runScrollableGrid(options)
|
||||
end
|
||||
end
|
||||
|
||||
local function refreshGridData()
|
||||
entries, counts = options.getEntriesAndCounts()
|
||||
local function syncFromCache()
|
||||
entries, counts = options.getEntriesAndCountsFromCache(entries, counts)
|
||||
end
|
||||
|
||||
local function renderGrid()
|
||||
@@ -596,7 +943,8 @@ local function runScrollableGrid(options)
|
||||
baseCellX + colStepCells * col,
|
||||
baseCellY + rowStepCells * row,
|
||||
counts[options.getId(entry)] or 0,
|
||||
rowOffsetPixels
|
||||
rowOffsetPixels,
|
||||
entry
|
||||
)
|
||||
end
|
||||
|
||||
@@ -623,17 +971,21 @@ local function runScrollableGrid(options)
|
||||
frame:drawBuffer()
|
||||
end
|
||||
|
||||
refreshGridData()
|
||||
syncFromCache()
|
||||
renderGrid()
|
||||
refreshTimer = os.startTimer(options.refreshSeconds or 30)
|
||||
|
||||
if options.shouldStartRefreshImmediately == nil or options.shouldStartRefreshImmediately(entries, counts) then
|
||||
startGlobalCountRefreshIfNeeded(false)
|
||||
end
|
||||
|
||||
refreshTimer = os.startTimer(options.refreshSeconds or globalCountRefreshInterval)
|
||||
|
||||
while true do
|
||||
local event, p1, x, y = os.pullEvent()
|
||||
|
||||
if event == "timer" and p1 == refreshTimer then
|
||||
refreshGridData()
|
||||
renderGrid()
|
||||
refreshTimer = os.startTimer(options.refreshSeconds or 30)
|
||||
startGlobalCountRefreshIfNeeded(false)
|
||||
refreshTimer = os.startTimer(options.refreshSeconds or globalCountRefreshInterval)
|
||||
scheduleAnimation()
|
||||
elseif event == "timer" and p1 == animationTimer then
|
||||
local delta = targetScrollRow - currentScrollRow
|
||||
@@ -661,6 +1013,23 @@ local function runScrollableGrid(options)
|
||||
|
||||
persistScrollState()
|
||||
renderGrid()
|
||||
|
||||
if not animationTimer and pendingCacheSync then
|
||||
pendingCacheSync = false
|
||||
syncFromCache()
|
||||
renderGrid()
|
||||
end
|
||||
elseif event == "timer" then
|
||||
local handled, isDone = stepGlobalCountRefresh(p1)
|
||||
|
||||
if handled and isDone then
|
||||
if animationTimer or math.abs(targetScrollRow - currentScrollRow) > 0.001 then
|
||||
pendingCacheSync = true
|
||||
else
|
||||
syncFromCache()
|
||||
renderGrid()
|
||||
end
|
||||
end
|
||||
elseif event == "monitor_touch" and p1 == monName then
|
||||
if options.handleChromeTouch and options.handleChromeTouch(x, y) then
|
||||
return
|
||||
@@ -717,9 +1086,15 @@ local function drawPage(base_id)
|
||||
end
|
||||
|
||||
runScrollableGrid({
|
||||
refreshSeconds = 5,
|
||||
getEntriesAndCounts = function()
|
||||
return pageItems, getMeItemCounts(pageItemIds)
|
||||
refreshSeconds = globalCountRefreshInterval,
|
||||
getInitialEntries = function()
|
||||
return pageItems
|
||||
end,
|
||||
getInitialCounts = function()
|
||||
return getEquivalentLevelCounts(base_id, pageItems, getGlobalItemCounts())
|
||||
end,
|
||||
getEntriesAndCountsFromCache = function(currentEntries)
|
||||
return currentEntries, getEquivalentLevelCounts(base_id, pageItems, getGlobalItemCounts())
|
||||
end,
|
||||
getId = function(entry)
|
||||
return entry.id
|
||||
@@ -748,8 +1123,45 @@ drawOverview = function()
|
||||
local items = getBaseItemIds()
|
||||
local defaultIcon = getFallbackIcon()
|
||||
|
||||
local function sortOverviewItems(itemCounts)
|
||||
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)
|
||||
|
||||
return sortedItemIds
|
||||
end
|
||||
|
||||
runScrollableGrid({
|
||||
refreshSeconds = 30,
|
||||
refreshSeconds = globalCountRefreshInterval,
|
||||
getInitialEntries = function()
|
||||
if overviewCachedSortedItemIds and #overviewCachedSortedItemIds > 0 then
|
||||
return overviewCachedSortedItemIds
|
||||
end
|
||||
|
||||
return sortOverviewItems(overviewCachedCounts)
|
||||
end,
|
||||
getInitialCounts = function()
|
||||
local counts = getGlobalItemCounts()
|
||||
|
||||
if next(counts) ~= nil then
|
||||
overviewCachedCounts = getEquivalentBaseCounts(items, counts)
|
||||
end
|
||||
|
||||
return overviewCachedCounts
|
||||
end,
|
||||
getInitialScrollState = function()
|
||||
return overviewScrollCurrentRow, overviewScrollTargetRow
|
||||
end,
|
||||
@@ -757,26 +1169,10 @@ drawOverview = function()
|
||||
overviewScrollCurrentRow = currentRow
|
||||
overviewScrollTargetRow = targetRow
|
||||
end,
|
||||
getEntriesAndCounts = function()
|
||||
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)
|
||||
|
||||
return sortedItemIds, itemCounts
|
||||
getEntriesAndCountsFromCache = function()
|
||||
overviewCachedCounts = getEquivalentBaseCounts(items, getGlobalItemCounts())
|
||||
overviewCachedSortedItemIds = sortOverviewItems(overviewCachedCounts)
|
||||
return overviewCachedSortedItemIds, overviewCachedCounts
|
||||
end,
|
||||
getId = function(entry)
|
||||
return entry
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def remove_icon_png_base64_lines(path: Path) -> int:
|
||||
original = path.read_text(encoding="utf-8")
|
||||
lines = original.splitlines(keepends=True)
|
||||
|
||||
kept_lines = []
|
||||
removed = 0
|
||||
|
||||
for line in lines:
|
||||
if '"icon_png_base64"' in line:
|
||||
removed += 1
|
||||
continue
|
||||
kept_lines.append(line)
|
||||
|
||||
if removed:
|
||||
path.write_text("".join(kept_lines), encoding="utf-8")
|
||||
|
||||
return removed
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Remove icon_png_base64 fields from atc_chains.json while preserving all other text."
|
||||
)
|
||||
parser.add_argument(
|
||||
"path",
|
||||
nargs="?",
|
||||
default="atc_chains.json",
|
||||
help="Path to the JSON file to rewrite in place.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
path = Path(args.path)
|
||||
removed = remove_icon_png_base64_lines(path)
|
||||
print(f"Removed {removed} icon_png_base64 field(s) from {path}.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user