#!/usr/bin/env node import { createHash } from "node:crypto"; import { readFile, writeFile } from "node:fs/promises"; import path from "node:path"; import { projectRoot, readJson, sourceRootFor } from "./lib/recipe.mjs"; const recipe = await readJson(path.join(projectRoot, "scripts/runewaker-pipeline/recipes/raksha-temple-population.json")); const source = path.join(sourceRootFor(recipe), recipe.source.resourceRoot ?? "Resource", "data/npcobject.db"); const bytes = await readFile(source); const dataStart = 140; const count = bytes.readInt32LE(132); const recordSize = 2_224; const raceOffset = 176; if (count <= 0 || dataStart + count * recordSize > bytes.length) throw new Error("Unexpected NPCObjectDB layout"); const knownIds = []; const undeadIds = []; const demonIds = []; for (let index = 0; index < count; index += 1) { const row = dataStart + index * recordSize; if (bytes.readInt32LE(row + 4) !== 1) throw new Error(`Unexpected NPC object type at row ${index}`); const id = bytes.readInt32LE(row); if (id <= 0) continue; knownIds.push(id); const race = bytes.readInt32LE(row + raceOffset); // ObjectStruct.h Race_ENUM names value 5 Undead. The source Demon Soldier // and Demon Bodo NPC records use value 12. if (race === 5) undeadIds.push(id); if (race === 12) demonIds.push(id); } const sorted = (ids) => [...new Set(ids)].sort((left, right) => left - right); const output = { schemaVersion: 1, sourceSha256: createHash("sha256").update(bytes).digest("hex"), raceOffset, knownIds: sorted(knownIds), undeadIds: sorted(undeadIds), demonIds: sorted(demonIds), }; await writeFile( path.join(projectRoot, "src/game/generated/romCreatureFamilies.json"), `${JSON.stringify(output)}\n`, ); console.log(`Exported ${output.knownIds.length} NPC families (${output.undeadIds.length} undead, ${output.demonIds.length} demon).`);