const fs = require('fs'); const path = 'k:/ghidra/crusader_map_viewer/map_renderer/.cache/scene-cache/remorse/map-248/b27ea0d8d2a1a391/scene.json'; const scene = JSON.parse(fs.readFileSync(path, 'utf8')); const items = scene.items.filter((item) => item.shapeDefId === 'shape:1232'); function qlo(item) { return item.quality & 0xff; } function dist(a, b) { return Math.hypot(a.world.x - b.world.x, a.world.y - b.world.y); } for (const item of items.filter((entry) => entry.npcPreview?.name === 'Observer')) { const pairs = items.filter((candidate) => candidate.id !== item.id && candidate.frame !== item.frame && qlo(candidate) === qlo(item) && dist(candidate, item) <= 128); console.log(JSON.stringify({ id: item.id, src: item.mapSourceIndex, frame: item.frame, mapNum: item.mapNum, npcNum: item.npcNum, qlo: qlo(item), world: item.world, pairs: pairs.map((candidate) => ({ id: candidate.id, src: candidate.mapSourceIndex, frame: candidate.frame, mapNum: candidate.mapNum, npcNum: candidate.npcNum, name: candidate.npcPreview?.name || null, qlo: qlo(candidate) })) })); }