Files
healer-man/src/game/bossTesting.test.ts
T
2026-09-16 14:30:58 -04:00

145 lines
8.9 KiB
TypeScript

import { afterEach, describe, expect, it } from "vitest";
import { anotherRandomTestingBoss, buildBossTestingCatalog, BOSS_TESTING_CATALOG, createBossTestingRoom, randomTestingBoss } from "./bossTesting";
import { clearBossTestingDungeons, DUNGEON_DEFINITIONS, registerBossTestingDungeon, requireDungeonDefinition } from "./dungeonRegistry";
import { activateDungeonSession, restartActiveDungeonSession } from "./dungeonSession";
import { useGameStore } from "./store";
import { useCombatStore } from "./combatStore";
import { bossTestIsDefeated } from "./BossTestingBridge";
import type { DungeonDefinition } from "./dungeonTypes";
import { dungeonEnemyScaling } from "./dungeonBossScaling";
import { creatureRuntimeBaseStats } from "./azerothCoreReference";
import { bossDifficultyMultipliers } from "./dungeonDifficulty";
import { finalDungeonBoss } from "./dungeonRunStore";
afterEach(() => {
useGameStore.getState().activateDungeon("wailing-caverns");
clearBossTestingDungeons();
});
describe("Boss Testing", () => {
const boss = BOSS_TESTING_CATALOG.find((entry) => entry.dungeonId === "wailing-caverns" && entry.name === "Lady Anacondra")!;
const source = requireDungeonDefinition(boss.dungeonId);
it("includes unready and event-only bosses and isolates catalog changes from shipped content", () => {
const blocked: DungeonDefinition = { ...source, availability: "blocked", staticSpawns: [], roamingPacks: [],
encounterSpawnGroups: [{ id: "event", spawns: [boss.spawn] }], entities: { [boss.entityId]: source.entities[boss.entityId] } };
expect(buildBossTestingCatalog([blocked])).toHaveLength(1);
const count = DUNGEON_DEFINITIONS.length;
const room = createBossTestingRoom(source, boss);
registerBossTestingDungeon(room.definition, source.id);
expect(requireDungeonDefinition(source.id)).toBe(source);
expect(DUNGEON_DEFINITIONS).toHaveLength(count);
expect(room.definition.staticSpawns).toHaveLength(1);
expect(room.definition.roamingPacks).toEqual([]);
expect(room.definition.entities[boss.entityId].combat).toEqual(source.entities[boss.entityId].combat);
expect(room.definition.staticSpawns[0].position).toEqual(boss.spawn.position);
expect(room.definition.assets).toEqual({ visual: [], collision: [] });
});
it("preserves boss phases and scripted summons without spawning unrelated dungeon packs", () => {
const fixture: DungeonDefinition = { ...source, encounters: [
{ id: "boss-mechanics", dungeonId: source.id, initialPhase: "idle", rules: [{ id: "adds", trigger: { kind: "entity-health", entityId: boss.spawn.id, atOrBelowPercent: 50 },
actions: [{ id: "summon", kind: "activate-spawn-group", spawnGroupId: "boss-adds" }, { id: "loot", kind: "grant-loot", lootTableId: "test" }] }] },
{ id: "other-boss", dungeonId: source.id, initialPhase: "idle", rules: [{ id: "other", trigger: { kind: "entity-engaged", entityId: "unrelated" }, actions: [] }] },
], encounterSpawnGroups: [{ id: "boss-adds", spawns: [{ ...boss.spawn, id: "add", entityId: boss.entityId }] }, { id: "trash", spawns: [] }] };
const room = createBossTestingRoom(fixture, boss);
expect(room.definition.encounters?.map((entry) => entry.id)).toEqual([`${room.definition.id}:boss-mechanics`]);
expect(room.definition.encounters?.[0].dungeonId).toBe(room.definition.id);
expect(room.definition.encounters?.[0].rules[0].actions).toEqual([{ id: "summon", kind: "activate-spawn-group", spawnGroupId: "boss-adds" }]);
expect(room.definition.encounterSpawnGroups?.map((entry) => entry.id)).toEqual(["boss-adds"]);
expect(room.definition.staticSpawns).toHaveLength(1);
});
it("starts an unready boss room, survives scene retries, and retains testing mode when restarted", () => {
const room = createBossTestingRoom(source, boss);
registerBossTestingDungeon(room.definition, source.id);
expect(activateDungeonSession(room.definition.id)).toBe(false);
expect(activateDungeonSession(room.definition.id, undefined, { gmTestMode: true, bossTest: room.session })).toBe(true);
expect(restartActiveDungeonSession()).toBe(true);
useGameStore.getState().resetAtEntrance();
expect(useGameStore.getState()).toMatchObject({ gmTestMode: true, bossTest: room.session, overlay: null });
});
it("uses Celebras's native health at the player's scaled level without campaign health floors", () => {
const celebras = BOSS_TESTING_CATALOG.find((entry) => entry.name === "Celebras the Cursed")!;
const maraudon = requireDungeonDefinition(celebras.dungeonId);
const room = createBossTestingRoom(maraudon, celebras);
const entity = room.definition.entities[celebras.entityId];
const scale = (level: number, difficulty = 1) => dungeonEnemyScaling(room.definition, entity,
celebras.spawn.id, level, room.definition.defaultDifficultyId, difficulty);
const nativeHealth = creatureRuntimeBaseStats(entity.combat!.serverEntry!, 2)!.health
* entity.combat!.healthMultiplier!;
expect(scale(1)).toMatchObject({ level: 2, maxHealth: nativeHealth });
expect(Math.round(scale(1).maxHealth)).toBe(575);
expect(scale(8).maxHealth).toBeGreaterThan(scale(1).maxHealth);
expect(scale(20).maxHealth).toBeGreaterThan(scale(8).maxHealth);
expect(scale(1, 10).maxHealth).toBe(nativeHealth * bossDifficultyMultipliers(10).health);
const campaign = dungeonEnemyScaling(maraudon, maraudon.entities[celebras.entityId], celebras.spawn.id,
1, maraudon.defaultDifficultyId, 1);
expect(campaign.maxHealth).toBe(900);
expect(scale(1).damageMultiplier).toBe(campaign.damageMultiplier);
expect(entity.combat?.attacks).toEqual(maraudon.entities[celebras.entityId].combat?.attacks);
});
it("keeps low extrapolated boss health alive long enough to test mechanics", () => {
const zereketh = BOSS_TESTING_CATALOG.find((entry) => entry.name === "Zereketh the Unbound")!;
const arcatraz = requireDungeonDefinition(zereketh.dungeonId);
const room = createBossTestingRoom(arcatraz, zereketh);
const entity = room.definition.entities[zereketh.entityId];
const nativeHealth = creatureRuntimeBaseStats(entity.combat!.serverEntry!, 2)!.health
* entity.combat!.healthMultiplier!;
const testing = dungeonEnemyScaling(
room.definition,
entity,
zereketh.spawn.id,
1,
room.definition.defaultDifficultyId,
1,
);
expect(nativeHealth).toBeLessThan(100);
expect(testing).toMatchObject({ level: 2, maxHealth: 540 });
});
it("omits the campaign finale bonus even when testing the source dungeon's final boss", () => {
const final = finalDungeonBoss(source)!;
const finalBoss = BOSS_TESTING_CATALOG.find((entry) => entry.dungeonId === source.id && entry.spawn.id === final.id)!;
const room = createBossTestingRoom(source, finalBoss);
const entity = room.definition.entities[finalBoss.entityId];
const testing = dungeonEnemyScaling(room.definition, entity, finalBoss.spawn.id, 1, source.defaultDifficultyId, 1);
const campaign = dungeonEnemyScaling(source, entity, finalBoss.spawn.id, 1, source.defaultDifficultyId, 1);
expect(testing.maxHealth).toBe(Math.max(
creatureRuntimeBaseStats(entity.combat!.serverEntry!, 2)!.health
* (entity.combat?.healthMultiplier ?? 1),
540,
));
expect(campaign.maxHealth).toBe(Math.max(testing.maxHealth, 900 * 1.8));
});
it("recognizes actual boss deaths and awards no progression or collectible rewards", () => {
const room = createBossTestingRoom(source, boss);
registerBossTestingDungeon(room.definition, source.id);
useGameStore.getState().activateDungeon(room.definition.id, undefined, { bossTest: room.session, gmTestMode: true });
useCombatStore.getState().initializeCharacter({ classId: "mage", level: 20 });
expect(bossTestIsDefeated()).toBe(false);
useCombatStore.getState().registerMob(boss.spawn.id, { name: boss.name, boss: true, maxHealth: 10, xpReward: 500, hasLoot: true, lootSourceId: boss.entityId });
const xp = useCombatStore.getState().xp;
useCombatStore.getState().damageMob(boss.spawn.id, 100000, Date.now());
expect(bossTestIsDefeated()).toBe(true);
const combat = useCombatStore.getState();
expect(combat.xp).toBe(xp);
expect(combat.mobs[boss.spawn.id]).toMatchObject({ hasLoot: false, pendingLoot: [] });
expect(combat.mobs[boss.spawn.id].bossCardDrop).toBeFalsy();
});
it("draws a boss only from the selected filters and handles empty queues", () => {
expect(randomTestingBoss([], () => 0.5)).toBeNull();
const candidates = BOSS_TESTING_CATALOG.slice(0, 2);
expect(randomTestingBoss(candidates, () => 0)).toBe(candidates[0]);
expect(randomTestingBoss(candidates, () => 1)).toBe(candidates[1]);
expect(randomTestingBoss(candidates, () => Number.NaN)).toBe(candidates[0]);
expect(anotherRandomTestingBoss(candidates, candidates[0].key, () => 0)).toBe(candidates[1]);
expect(anotherRandomTestingBoss([candidates[0]], candidates[0].key)).toBeNull();
});
});