import { mkdirSync, writeFileSync } from "node:fs"; import { expect, it } from "vitest"; import { createCharacter } from "../app/accountRepository"; import { randomAppearance } from "../app/characterCatalog"; import { abilitiesForClass } from "./abilityCatalog"; import { useCombatStore } from "./combatStore"; import { subscribeCombatEvents } from "./combatEvents"; import { deriveDungeonEncounterCombatEvents, type DungeonEncounterMobCursor } from "./dungeonEncounterCombatEvents"; import { activateDungeonEncounterDefinitions, activeDungeonCompletedObjectiveIds, activeDungeonRunIsComplete, advanceDungeonEncounters, dispatchActiveDungeonEntityEvent, exportDungeonEncounterCheckpointSnapshots, resetDungeonEncounters } from "./dungeonEncounterRuntime"; import { requireDungeonDefinition } from "./dungeonRegistry"; import { dungeonEnemyHealthBaseline, scaledDungeonEnemyLevel } from "./dungeonLevelScaling"; import { completedDungeonRun, finalDungeonBoss, useDungeonRunStore } from "./dungeonRunStore"; import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry"; import { usePartyStore } from "./partyStore"; import { advanceDungeonPartyCombat, partyCombatRangeBand, resetPartyRuntimeClock } from "./partyRuntime"; import { clearPartyRuntimeRegistry, registerPartyRuntimePosition, updatePartyRuntimeTargetVisibility } from "./partyRuntimeRegistry"; import { useGameStore } from "./store"; it("turns an actual RoM party's lethal damage to Anglo into one timed completion and a resumable result", () => { const dungeon = requireDungeonDefinition("barren-caves"); const boss = finalDungeonBoss(dungeon)!; const spawn = dungeon.staticSpawns.find((entry) => entry.id === boss.id)!; const entity = dungeon.entities[spawn.entityId]; const bossCombat = entity.combat; if (!bossCombat) throw new Error("Anglo must have a combat definition"); const healthMultiplier = bossCombat.healthMultiplier ?? 1; clearMobRuntimeRegistry(); clearPartyRuntimeRegistry(); resetPartyRuntimeClock(); useGameStore.setState({ activeDungeonId: dungeon.id, activeDifficultyId: "normal", gameMode: "dungeon", gmTestMode: false }); activateDungeonEncounterDefinitions(dungeon.id, dungeon.encounters ?? [], "normal"); resetDungeonEncounters("dungeon", dungeon.id); const combat = useCombatStore.getState(); combat.initializeCharacter({ classId: "rom-priest", raceId: "rom-human", level: 1 }); combat.setPlayerPosition([-18, 0, 0]); usePartyStore.getState().initializeParty("completion-run", 1, useCombatStore.getState().health, "healer", 5, "rom"); combat.synchronizeActors(); const bossLevel = scaledDungeonEnemyLevel(1, true); combat.registerMob(boss.id, { name: entity.name, boss: true, level: bossLevel, xpReward: 0, maxHealth: dungeonEnemyHealthBaseline(bossLevel, "runewaker")! * healthMultiplier, healthMultiplier, attacks: bossCombat.attacks }); setMobPosition(boss.id, [0, 0, 0]); for (const member of usePartyStore.getState().members) { registerPartyRuntimePosition(member.id, [-partyCombatRangeBand(member).preferred, 0, 0]); updatePartyRuntimeTargetVisibility(member.id, boss.id, true); } const party = () => [{ name: "Runview", classId: "rom-priest", level: 1, role: "healer", npc: false }, ...usePartyStore.getState().members.map((member) => ({ name: member.name, classId: member.classId, level: member.level, role: member.role, npc: true }))]; const epoch = Date.now() - 181000; const run = useDungeonRunStore.getState(); run.begin(dungeon.id, "normal"); run.start(epoch, party()); combat.damageMob(boss.id, 1, epoch); const heal = abilitiesForClass("rom-priest").find((ability) => ability.name === "Urgent Heal")!; let cursor: DungeonEncounterMobCursor = {}; let npcDamage = 0; let healing = 0; let deaths = 0; const stop = subscribeCombatEvents((event) => { if (event.kind === "damage" && event.targetActorId === boss.id && event.sourceActorId !== "player") npcDamage += event.effectiveAmount; if (event.kind === "healing") healing += event.effectiveAmount; }); try { for (let now = epoch + 100; now <= epoch + 181000; now += 100) { combat.tick(.1, now); advanceDungeonPartyCombat(now); combat.advanceMobCombat(now); const tank = usePartyStore.getState().members.find((member) => member.role === "tank")!; if (tank.health < tank.maxHealth * .65 && !useCombatStore.getState().activeCast) { usePartyStore.getState().selectMember(tank.id); combat.castAbility(heal.id, undefined, now); } const mobs = useCombatStore.getState().mobs; const events = deriveDungeonEncounterCombatEvents(cursor, mobs, now); cursor = events.cursor; for (const event of events.events) { dispatchActiveDungeonEntityEvent(event); if (event.kind === "entity-death") deaths += 1; } const dead = Object.values(mobs).filter((mob) => mob.dead).map((mob) => mob.id); advanceDungeonEncounters(dungeon.id, { kind: "tick", now, facts: { registeredEntityIds: Object.keys(mobs), deadEntityIds: dead } }); run.advance(activeDungeonCompletedObjectiveIds(dungeon.id), dead, now, party()); if (useDungeonRunStore.getState().progress?.finishedAt !== null) break; } const progress = useDungeonRunStore.getState().progress!; expect(useCombatStore.getState().mobs[boss.id].dead).toBe(true); expect(activeDungeonRunIsComplete(dungeon.id)).toBe(true); expect(npcDamage).toBeGreaterThan(0); expect(healing).toBeGreaterThan(0); expect(deaths).toBe(1); expect(completedDungeonRun(progress)).toMatchObject({ fullClear: true, killedBossIds: [boss.id] }); expect(progress.durationMs).toBeGreaterThan(20000); expect(usePartyStore.getState().members.every((member) => member.health > 0)).toBe(true); const snapshots = exportDungeonEncounterCheckpointSnapshots(dungeon.id, 0); if (process.env.DUNGEON_RUN_REPORT === "1") { const values = new Map(); const character = createCharacter({ ownerId: "qa-owner", name: "Runview", raceId: "rom-human", classId: "rom-priest", categoryId: "rom", gender: "female", appearance: randomAppearance("rom-human", "female", () => 0) }, { getItem: (key) => values.get(key) ?? null, setItem: (key, value) => { values.set(key, value); }, removeItem: (key) => { values.delete(key); } }).character!; mkdirSync("artifacts/dungeon-runs", { recursive: true }); writeFileSync("artifacts/dungeon-runs/combat-completion.json", `${JSON.stringify({ npcDamage, healing, deaths, progress, character: { ...character, dungeonRunCheckpoint: { version: 2, dungeonId: dungeon.id, difficultyId: "normal", checkpointId: "entrance", spawn: { footPosition: dungeon.entrance.footPosition, yaw: dungeon.entrance.yaw }, encounters: snapshots, savedAt: Date.now(), run: progress } } }, null, 2)}\n`); } } finally { stop(); clearMobRuntimeRegistry(); clearPartyRuntimeRegistry(); } });