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`);