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())
|
||||||
+484
-88
@@ -26,10 +26,28 @@ local mf = require("morefonts-pe")
|
|||||||
|
|
||||||
local frame = Pine3D.newFrame()
|
local frame = Pine3D.newFrame()
|
||||||
frame:setBackgroundColor(colors.black)
|
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 chainItemsByBaseId
|
||||||
|
local chainAllItemsByBaseId
|
||||||
local baseItemIds
|
local baseItemIds
|
||||||
|
local allChainItemIds
|
||||||
local itemById
|
local itemById
|
||||||
local defaultBaseId
|
local defaultBaseId
|
||||||
local whitelistLookup
|
local whitelistLookup
|
||||||
@@ -37,8 +55,15 @@ local fallbackIcon = false
|
|||||||
local backButton = false
|
local backButton = false
|
||||||
local scrollUpButton = false
|
local scrollUpButton = false
|
||||||
local scrollDownButton = false
|
local scrollDownButton = false
|
||||||
|
local overviewCachedCounts = {}
|
||||||
|
local overviewCachedSortedItemIds = nil
|
||||||
local overviewScrollCurrentRow = 0
|
local overviewScrollCurrentRow = 0
|
||||||
local overviewScrollTargetRow = 0
|
local overviewScrollTargetRow = 0
|
||||||
|
local globalItemCounts = {}
|
||||||
|
local globalCountRefreshInterval = 5
|
||||||
|
local globalCountRefreshJob
|
||||||
|
local globalCountRefreshTimer
|
||||||
|
local globalCountLastRefreshAt = 0
|
||||||
local drawOverview
|
local drawOverview
|
||||||
|
|
||||||
local function ensureWhitelistLoaded()
|
local function ensureWhitelistLoaded()
|
||||||
@@ -71,6 +96,55 @@ local function ensureWhitelistLoaded()
|
|||||||
end
|
end
|
||||||
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()
|
local function ensureChainsLoaded()
|
||||||
if not chainItemsByBaseId then
|
if not chainItemsByBaseId then
|
||||||
ensureWhitelistLoaded()
|
ensureWhitelistLoaded()
|
||||||
@@ -79,40 +153,74 @@ local function ensureChainsLoaded()
|
|||||||
local data = textutils.unserializeJSON(file.readAll())
|
local data = textutils.unserializeJSON(file.readAll())
|
||||||
file.close()
|
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 = {}
|
chainItemsByBaseId = {}
|
||||||
|
chainAllItemsByBaseId = {}
|
||||||
baseItemIds = {}
|
baseItemIds = {}
|
||||||
|
allChainItemIds = {}
|
||||||
itemById = {}
|
itemById = {}
|
||||||
|
|
||||||
for _, chain in ipairs(data.chains) do
|
for _, chain in ipairs(chains) do
|
||||||
if not whitelistLookup or whitelistLookup[chain.base_id] then
|
local baseId = chain.base_id or chain[1]
|
||||||
|
|
||||||
|
if not whitelistLookup or whitelistLookup[baseId] then
|
||||||
if not defaultBaseId then
|
if not defaultBaseId then
|
||||||
defaultBaseId = chain.base_id
|
defaultBaseId = baseId
|
||||||
end
|
end
|
||||||
|
|
||||||
baseItemIds[#baseItemIds + 1] = chain.base_id
|
baseItemIds[#baseItemIds + 1] = baseId
|
||||||
|
|
||||||
local items = {}
|
local orderedItems = {}
|
||||||
local itemCount = 0
|
local pageItems = {}
|
||||||
|
|
||||||
|
if chain.base_id then
|
||||||
for _, item in ipairs(chain.items or {}) do
|
for _, item in ipairs(chain.items or {}) do
|
||||||
if item.stage == "item" then
|
local chainItem = createChainItem(item.item_id, item.icon_nfp_16x16, item.stage)
|
||||||
itemById[item.item_id] = {
|
orderedItems[#orderedItems + 1] = chainItem
|
||||||
id = item.item_id,
|
itemById[item.item_id] = chainItem
|
||||||
icon_nfp = item.icon_nfp_16x16,
|
allChainItemIds[#allChainItemIds + 1] = item.item_id
|
||||||
}
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
itemCount = itemCount + 1
|
local compactItems = nil
|
||||||
items[itemCount] = {
|
|
||||||
id = item.item_id,
|
if type(chain[2]) == "number" then
|
||||||
icon_nfp = item.icon_nfp_16x16,
|
compactItems = chain[3] or {}
|
||||||
}
|
elseif type(chain[2]) == "table" then
|
||||||
itemById[item.item_id] = items[itemCount]
|
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
|
||||||
|
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
|
||||||
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
|
end
|
||||||
end
|
end
|
||||||
@@ -124,6 +232,12 @@ local function getPageItems(base_id)
|
|||||||
return chainItemsByBaseId[base_id or defaultBaseId] or {}
|
return chainItemsByBaseId[base_id or defaultBaseId] or {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function getAllChainItems(base_id)
|
||||||
|
ensureChainsLoaded()
|
||||||
|
|
||||||
|
return chainAllItemsByBaseId[base_id or defaultBaseId] or {}
|
||||||
|
end
|
||||||
|
|
||||||
local function getBaseItemIds()
|
local function getBaseItemIds()
|
||||||
ensureChainsLoaded()
|
ensureChainsLoaded()
|
||||||
|
|
||||||
@@ -164,13 +278,49 @@ local function parseNfpImage(nfp)
|
|||||||
return image
|
return image
|
||||||
end
|
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)
|
local function getItemIcon(item)
|
||||||
if not item then
|
if not item then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
if item.icon == nil then
|
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
|
item.icon_nfp = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -178,12 +328,14 @@ local function getItemIcon(item)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function getMeItemCount(itemId)
|
local function getMeItemCount(itemId)
|
||||||
if not meSystem or not itemId then
|
local bridge = ensureStorageBridge()
|
||||||
|
|
||||||
|
if not bridge or not itemId then
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(meSystem.getItem) == "function" then
|
if type(bridge.getItem) == "function" then
|
||||||
local ok, item = pcall(meSystem.getItem, { name = itemId })
|
local ok, item = pcall(bridge.getItem, { name = itemId })
|
||||||
if ok and item then
|
if ok and item then
|
||||||
return tonumber(item.amount or item.count or item.qty) or 0
|
return tonumber(item.amount or item.count or item.qty) or 0
|
||||||
end
|
end
|
||||||
@@ -192,6 +344,38 @@ local function getMeItemCount(itemId)
|
|||||||
return 0
|
return 0
|
||||||
end
|
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 function getMeItemCounts(itemIds)
|
||||||
local counts = {}
|
local counts = {}
|
||||||
|
|
||||||
@@ -199,56 +383,205 @@ local function getMeItemCounts(itemIds)
|
|||||||
counts[itemIds[i]] = 0
|
counts[itemIds[i]] = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
if not meSystem then
|
local bridge = ensureStorageBridge()
|
||||||
|
|
||||||
|
if not bridge then
|
||||||
return counts
|
return counts
|
||||||
end
|
end
|
||||||
|
|
||||||
local unresolved = {}
|
local snapshot = listBridgeItems(bridge)
|
||||||
local unresolvedCount = 0
|
if not snapshot then
|
||||||
|
return counts
|
||||||
|
end
|
||||||
|
|
||||||
|
local wanted = {}
|
||||||
|
|
||||||
for i = 1, #itemIds do
|
for i = 1, #itemIds do
|
||||||
local itemId = itemIds[i]
|
wanted[itemIds[i]] = true
|
||||||
local count = getMeItemCount(itemId)
|
|
||||||
counts[itemId] = count
|
|
||||||
|
|
||||||
if count == 0 then
|
|
||||||
unresolved[itemId] = true
|
|
||||||
unresolvedCount = unresolvedCount + 1
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if unresolvedCount == 0 then
|
for i = 1, #snapshot do
|
||||||
return counts
|
local item = snapshot[i]
|
||||||
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)
|
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
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
return counts
|
return counts
|
||||||
end
|
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()
|
local function getFallbackIcon()
|
||||||
if fallbackIcon == false then
|
if fallbackIcon == false then
|
||||||
fallbackIcon = parseNfpImage(table.concat({
|
fallbackIcon = parseNfpImage(table.concat({
|
||||||
@@ -442,7 +775,7 @@ local function assertBufferValid(frame)
|
|||||||
end
|
end
|
||||||
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.
|
-- Convert normal monitor cells into Pine3D teletext pixels.
|
||||||
local x = cellToPixelX(cellX)
|
local x = cellToPixelX(cellX)
|
||||||
local y = cellToPixelY(cellY) + (offsetY or 0)
|
local y = cellToPixelY(cellY) + (offsetY or 0)
|
||||||
@@ -459,6 +792,19 @@ local function drawItem(img, cellX, cellY, count, offsetY)
|
|||||||
end
|
end
|
||||||
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 text = formatCount(count)
|
||||||
|
|
||||||
local rightX = x + 16 -- x + math.max(imgW * scaleX - 1, 7)
|
local rightX = x + 16 -- x + math.max(imgW * scaleX - 1, 7)
|
||||||
@@ -485,8 +831,8 @@ local function drawItem(img, cellX, cellY, count, offsetY)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function runScrollableGrid(options)
|
local function runScrollableGrid(options)
|
||||||
local entries = {}
|
local entries = options.getInitialEntries()
|
||||||
local counts = {}
|
local counts = options.getInitialCounts(entries)
|
||||||
local visibleItemCount = 0
|
local visibleItemCount = 0
|
||||||
local totalRows = 0
|
local totalRows = 0
|
||||||
local maxScrollRow = 0
|
local maxScrollRow = 0
|
||||||
@@ -494,13 +840,14 @@ local function runScrollableGrid(options)
|
|||||||
local targetScrollRow = 0
|
local targetScrollRow = 0
|
||||||
local refreshTimer
|
local refreshTimer
|
||||||
local animationTimer
|
local animationTimer
|
||||||
|
local pendingCacheSync = false
|
||||||
|
|
||||||
local columns = 3
|
local columns = 5
|
||||||
local rowsPerView = 3
|
local rowsPerView = 3
|
||||||
local baseCellX = 4
|
local baseCellX = 3
|
||||||
local baseCellY = 2
|
local baseCellY = 2
|
||||||
local colStepCells = 12
|
local colStepCells = 8+3
|
||||||
local rowStepCells = 6
|
local rowStepCells = 5+3
|
||||||
local rowStepPixels = rowStepCells * 3
|
local rowStepPixels = rowStepCells * 3
|
||||||
local scrollbarX = SCREEN_WIDTH - 1
|
local scrollbarX = SCREEN_WIDTH - 1
|
||||||
local scrollbarWidth = 2
|
local scrollbarWidth = 2
|
||||||
@@ -564,8 +911,8 @@ local function runScrollableGrid(options)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function refreshGridData()
|
local function syncFromCache()
|
||||||
entries, counts = options.getEntriesAndCounts()
|
entries, counts = options.getEntriesAndCountsFromCache(entries, counts)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function renderGrid()
|
local function renderGrid()
|
||||||
@@ -596,7 +943,8 @@ local function runScrollableGrid(options)
|
|||||||
baseCellX + colStepCells * col,
|
baseCellX + colStepCells * col,
|
||||||
baseCellY + rowStepCells * row,
|
baseCellY + rowStepCells * row,
|
||||||
counts[options.getId(entry)] or 0,
|
counts[options.getId(entry)] or 0,
|
||||||
rowOffsetPixels
|
rowOffsetPixels,
|
||||||
|
entry
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -623,17 +971,21 @@ local function runScrollableGrid(options)
|
|||||||
frame:drawBuffer()
|
frame:drawBuffer()
|
||||||
end
|
end
|
||||||
|
|
||||||
refreshGridData()
|
syncFromCache()
|
||||||
renderGrid()
|
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
|
while true do
|
||||||
local event, p1, x, y = os.pullEvent()
|
local event, p1, x, y = os.pullEvent()
|
||||||
|
|
||||||
if event == "timer" and p1 == refreshTimer then
|
if event == "timer" and p1 == refreshTimer then
|
||||||
refreshGridData()
|
startGlobalCountRefreshIfNeeded(false)
|
||||||
renderGrid()
|
refreshTimer = os.startTimer(options.refreshSeconds or globalCountRefreshInterval)
|
||||||
refreshTimer = os.startTimer(options.refreshSeconds or 30)
|
|
||||||
scheduleAnimation()
|
scheduleAnimation()
|
||||||
elseif event == "timer" and p1 == animationTimer then
|
elseif event == "timer" and p1 == animationTimer then
|
||||||
local delta = targetScrollRow - currentScrollRow
|
local delta = targetScrollRow - currentScrollRow
|
||||||
@@ -661,6 +1013,23 @@ local function runScrollableGrid(options)
|
|||||||
|
|
||||||
persistScrollState()
|
persistScrollState()
|
||||||
renderGrid()
|
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
|
elseif event == "monitor_touch" and p1 == monName then
|
||||||
if options.handleChromeTouch and options.handleChromeTouch(x, y) then
|
if options.handleChromeTouch and options.handleChromeTouch(x, y) then
|
||||||
return
|
return
|
||||||
@@ -717,9 +1086,15 @@ local function drawPage(base_id)
|
|||||||
end
|
end
|
||||||
|
|
||||||
runScrollableGrid({
|
runScrollableGrid({
|
||||||
refreshSeconds = 5,
|
refreshSeconds = globalCountRefreshInterval,
|
||||||
getEntriesAndCounts = function()
|
getInitialEntries = function()
|
||||||
return pageItems, getMeItemCounts(pageItemIds)
|
return pageItems
|
||||||
|
end,
|
||||||
|
getInitialCounts = function()
|
||||||
|
return getEquivalentLevelCounts(base_id, pageItems, getGlobalItemCounts())
|
||||||
|
end,
|
||||||
|
getEntriesAndCountsFromCache = function(currentEntries)
|
||||||
|
return currentEntries, getEquivalentLevelCounts(base_id, pageItems, getGlobalItemCounts())
|
||||||
end,
|
end,
|
||||||
getId = function(entry)
|
getId = function(entry)
|
||||||
return entry.id
|
return entry.id
|
||||||
@@ -748,17 +1123,7 @@ drawOverview = function()
|
|||||||
local items = getBaseItemIds()
|
local items = getBaseItemIds()
|
||||||
local defaultIcon = getFallbackIcon()
|
local defaultIcon = getFallbackIcon()
|
||||||
|
|
||||||
runScrollableGrid({
|
local function sortOverviewItems(itemCounts)
|
||||||
refreshSeconds = 30,
|
|
||||||
getInitialScrollState = function()
|
|
||||||
return overviewScrollCurrentRow, overviewScrollTargetRow
|
|
||||||
end,
|
|
||||||
setScrollState = function(currentRow, targetRow)
|
|
||||||
overviewScrollCurrentRow = currentRow
|
|
||||||
overviewScrollTargetRow = targetRow
|
|
||||||
end,
|
|
||||||
getEntriesAndCounts = function()
|
|
||||||
local itemCounts = getMeItemCounts(items)
|
|
||||||
local sortedItemIds = {}
|
local sortedItemIds = {}
|
||||||
|
|
||||||
for i = 1, #items do
|
for i = 1, #items do
|
||||||
@@ -776,7 +1141,38 @@ drawOverview = function()
|
|||||||
return a < b
|
return a < b
|
||||||
end)
|
end)
|
||||||
|
|
||||||
return sortedItemIds, itemCounts
|
return sortedItemIds
|
||||||
|
end
|
||||||
|
|
||||||
|
runScrollableGrid({
|
||||||
|
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,
|
||||||
|
setScrollState = function(currentRow, targetRow)
|
||||||
|
overviewScrollCurrentRow = currentRow
|
||||||
|
overviewScrollTargetRow = targetRow
|
||||||
|
end,
|
||||||
|
getEntriesAndCountsFromCache = function()
|
||||||
|
overviewCachedCounts = getEquivalentBaseCounts(items, getGlobalItemCounts())
|
||||||
|
overviewCachedSortedItemIds = sortOverviewItems(overviewCachedCounts)
|
||||||
|
return overviewCachedSortedItemIds, overviewCachedCounts
|
||||||
end,
|
end,
|
||||||
getId = function(entry)
|
getId = function(entry)
|
||||||
return 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