Files
healer-man/scripts/generate-bite-sized-layouts.ts
2026-09-27 21:48:00 -04:00

36 lines
3.3 KiB
TypeScript

import { writeFileSync } from "node:fs";
import { DUNGEON_DEFINITIONS, AVAILABLE_RANDOMIZED_CREATURE_CATALOG, BITE_SIZED_RANDOMIZED_CREATURE_CATALOG } from "../src/game/dungeonRegistry";
import { dungeonContentSourceId, isBiteSizedDungeonId, isRandomizedBiteSizedDungeonId } from "../src/game/dungeonContentSource";
import { sourceBossCandidates } from "../src/game/dungeons/sourceBossCandidates";
import { BITE_SIZED_STOCKADES_BOSS_CANDIDATES } from "../src/game/dungeons/biteSizedStockades";
const layouts = DUNGEON_DEFINITIONS.filter((d) => isBiteSizedDungeonId(d.id) && !isRandomizedBiteSizedDungeonId(d.id));
const sources = DUNGEON_DEFINITIONS.filter((d) => !isBiteSizedDungeonId(d.id));
const defaults = layouts.map((d) => {
const sourceDungeonId = dungeonContentSourceId(d.id);
const bossIds = new Set(d.bosses.map((b) => b.id));
const boss = [...d.staticSpawns, ...(d.encounterSpawnGroups ?? []).flatMap((g) => g.spawns)].find((s) => bossIds.has(s.id))!;
return { id: `${d.id}:default`, dungeonId: d.id, sourceDungeonId, name: d.title.split(": ").slice(1).join(": ") || d.title,
revision: 0, legacyLayout: true, partySpawn: { footPosition: d.entrance.footPosition, yaw: d.entrance.yaw },
mobs: d.staticSpawns.filter((s) => !bossIds.has(s.id)).map((s) => ({ ...s, sourceDungeonId })),
bossSpawn: { footPosition: boss.position, yaw: boss.yaw ?? 0 }, bossActivation: d.bosses[0].eventOnly ? "after-mobs" : "immediate" };
});
const metadata = layouts.map((d) => {
const source = sources.find((s) => s.id === dungeonContentSourceId(d.id))!;
const candidates = sourceBossCandidates(source);
return { id: d.id, sourceDungeonId: source.id, title: source.title, expansion: source.expansion,
bounds: source.bounds, difficultyIds: source.difficultyVariants.map((v) => v.id),
legacyBlockers: d.serverGameObjects.filter((object) => !object.interaction && object.states.find((state) => state.id === object.initialState)?.blocksMovement)
.map((object) => ({ position: object.position, yaw: object.yaw ?? 0, halfExtents: object.halfExtents })),
bossCandidates: (source.id === "stormwind-stockade" ? BITE_SIZED_STOCKADES_BOSS_CANDIDATES.map((b) => candidates.find((c) => c.objective.id === b.sourceSpawnId)!) : candidates)
.map((b) => ({ sourceDungeonId: source.id, entityId: b.spawn.entityId, objectiveId: b.objective.id, name: b.objective.name })) };
});
const creatures = sources.flatMap((d) => Object.values(d.entities).map((e) => ({ sourceDungeonId: d.id, entityId: e.id, kind: e.kind,
name: e.name, dungeonTitle: d.title, modelUrl: e.visual.model?.url ?? null })));
const pool = (entries: typeof AVAILABLE_RANDOMIZED_CREATURE_CATALOG) => entries.map((e) => ({ sourceDungeonId: e.sourceDungeonId, entityId: e.entityId,
objectiveId: `randomized:${e.key}`, key: e.key, name: e.name, kind: e.kind }));
writeFileSync(new URL("../shared/bite-sized-defaults.json", import.meta.url), `${JSON.stringify({ schemaVersion: 1, spots: defaults }, null, 2)}\n`);
writeFileSync(new URL("../shared/bite-sized-catalog.json", import.meta.url), `${JSON.stringify({ dungeons: metadata, creatures,
pools: { "randomized-bite-sized-stormwind-stockade": pool(AVAILABLE_RANDOMIZED_CREATURE_CATALOG),
"randomized-bite-sized-stormwind-stockade-available": pool(BITE_SIZED_RANDOMIZED_CREATURE_CATALOG) } })}\n`);