Release Healer Man 0.1.22

This commit is contained in:
phenom committed 2026-09-09 11:21:33 -04:00
1 parent f6f2375145
commit 7f6001723a
144 files changed
+54107 -690

No files matched your search

@@ -2,6 +2,11 @@
import { open, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { DUNGEON_DEFINITIONS } from "../../src/game/dungeonRegistry";
import {
WOW_DUNGEON_BASIC_ONLY_BOSS_EXCEPTIONS,
WOW_DUNGEON_NO_TRASH_EXCEPTIONS,
} from "../../src/game/wowDungeonContentExceptions";
const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const publicRoot = path.join(projectRoot, "public");
@@ -18,24 +23,14 @@ const validTargets = new Set([
]);
const validSchools = new Set(["physical", "arcane", "fire", "frost", "nature", "shadow", "holy"]);
const validEffects = new Set([
"damage", "heal", "aura", "control", "transform", "summon", "call-for-help", "encounter-event",
]);
const basicOnlyBossExceptions = new Map([
[
"wailing-caverns:entry-3653",
"Kresh is melee-only in the pinned AzerothCore snapshot; the historical server has no spell or SmartAI row for entry 3653.",
],
[
"wailing-caverns:deviate-faerie-dragon",
"The server-faithful rare entry 5912 has no spell rows and is despawned by the Wailing Caverns initialization event.",
],
]);
const noTrashExceptions = new Map([
[
"trial-of-the-champion",
"Trial of the Champion is an encounter arena; its imported combat population is the five Grand Champion bosses, not a trash route.",
],
"damage", "heal", "aura", "target-aura", "control", "transform", "summon", "call-for-help", "encounter-event",
]);
const basicOnlyBossExceptions = new Map(
WOW_DUNGEON_BASIC_ONLY_BOSS_EXCEPTIONS.map(({ key, reason }) => [key, reason]),
);
const noTrashExceptions = new Map(
WOW_DUNGEON_NO_TRASH_EXCEPTIONS.map(({ key, reason }) => [key, reason]),
);
const animationIds = {
idle: new Set([0]),
move: new Set([4, 5]),
@@ -152,13 +147,21 @@ const [campaign, catalog, wailingFixture] = await Promise.all([
readJson("dungeon-pipeline/fixtures/wailing-caverns/runtime-fixture.json"),
]);
const campaignIds = new Set(campaign.dungeons.map((dungeon) => dungeon.slug));
const definitions = catalog.definitions.filter((definition) => campaignIds.has(definition.id));
const definitions = DUNGEON_DEFINITIONS.filter((definition) => campaignIds.has(definition.id));
const coverage = new Map(catalog.coverage.map((entry) => [entry.dungeonId, entry]));
const wailingModels = wailingFixtureModels(wailingFixture);
const issues = [];
if (basicOnlyBossExceptions.size !== WOW_DUNGEON_BASIC_ONLY_BOSS_EXCEPTIONS.length) {
issues.push("The shared basic-only boss exception contract contains a duplicate key.");
}
if (noTrashExceptions.size !== WOW_DUNGEON_NO_TRASH_EXCEPTIONS.length) {
issues.push("The shared no-trash exception contract contains a duplicate key.");
}
const modelUsers = new Map();
const animationFallbacks = [];
const dungeons = [];
const actualBasicOnlyBossKeys = new Set();
const actualNoTrashDungeonKeys = new Set();
if (campaign.dungeons.length !== 54) {
issues.push("Expected 54 campaign dungeons; found " + campaign.dungeons.length + ".");
@@ -179,15 +182,15 @@ for (const dungeon of definitions) {
let specialAttacks = 0;
let nativeModels = 0;
let proceduralModels = 0;
const optionalBossEntityIds = new Set((dungeon.objectiveOrder ?? [])
.filter((objective) => objective.optional)
.map((objective) => dungeon.staticSpawns?.find((spawn) => spawn.id === objective.id)?.entityId)
.filter(Boolean));
if (!entities.length) dungeonIssues.push("has no combat entities.");
if (!bosses.length) dungeonIssues.push("has no bosses.");
if (!mobs.length && !noTrashExceptions.has(dungeon.id)) dungeonIssues.push("has no trash mobs.");
if (!(dungeon.staticSpawns?.length || dungeon.roamingPacks?.length)) dungeonIssues.push("has no combat spawns.");
if (!mobs.length) {
actualNoTrashDungeonKeys.add(dungeon.id);
if (!noTrashExceptions.has(dungeon.id)) dungeonIssues.push("has no trash mobs.");
}
if (!(dungeon.staticSpawns?.length || dungeon.roamingPacks?.length || dungeon.encounterSpawnGroups?.some((group) => group.spawns?.length))) {
dungeonIssues.push("has no combat spawns.");
}
for (const spawn of dungeon.staticSpawns ?? []) {
if (!dungeon.entities?.[spawn.entityId]) {
@@ -201,6 +204,13 @@ for (const dungeon of definitions) {
}
}
}
for (const group of dungeon.encounterSpawnGroups ?? []) {
for (const spawn of group.spawns ?? []) {
if (!dungeon.entities?.[spawn.entityId]) {
dungeonIssues.push("encounter group " + group.id + " references missing entity " + spawn.entityId + ".");
}
}
}
for (const [entityId, entity] of entities) {
const entityAttacks = entity.combat?.attacks ?? [];
@@ -209,8 +219,9 @@ for (const dungeon of definitions) {
attacks += entityAttacks.length;
const specials = entityAttacks.filter((attack) => attack.id !== "basic-attack");
specialAttacks += specials.length;
if (entity.kind === "boss" && !specials.length && !optionalBossEntityIds.has(entityId)) {
if (entity.kind === "boss" && !specials.length) {
const exceptionKey = dungeon.id + ":" + entityId;
actualBasicOnlyBossKeys.add(exceptionKey);
if (!basicOnlyBossExceptions.has(exceptionKey)) {
dungeonIssues.push(entityId + " has no boss mechanic beyond its basic attack.");
}
@@ -247,6 +258,17 @@ for (const dungeon of definitions) {
});
}
for (const key of basicOnlyBossExceptions.keys()) {
if (!actualBasicOnlyBossKeys.has(key)) {
issues.push("Stale basic-only boss exception does not match a runtime boss: " + key + ".");
}
}
for (const key of noTrashExceptions.keys()) {
if (!actualNoTrashDungeonKeys.has(key)) {
issues.push("Stale no-trash exception does not match a runtime dungeon: " + key + ".");
}
}
for (const [url, users] of modelUsers) {
try {
const names = await readGlbAnimationNames(url);