Files
healer-man/scripts/runewaker-pipeline/export-rom-creature-families.mjs

44 lines
1.8 KiB
JavaScript

#!/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).`);