forked from phenom/healer-man
123 lines
5.4 KiB
TypeScript
123 lines
5.4 KiB
TypeScript
import { afterEach, describe, expect, it } from "vitest";
|
|
import type { OnlineSharedWorld } from "../app/onlineSessionTypes";
|
|
import {
|
|
activateDungeonEncounterDefinitions,
|
|
dungeonEncounterSnapshot,
|
|
resetDungeonEncounters,
|
|
} from "./dungeonEncounterRuntime";
|
|
import { applySharedWorld, eventForRelay } from "./OnlineSessionBridge";
|
|
import { PLAYER_AGGRO_ID } from "./aggro";
|
|
import { usePartyStore } from "./partyStore";
|
|
import type { StructuredCombatEvent } from "./combatEvents";
|
|
import { useCombatStore } from "./combatStore";
|
|
import { useGameStore } from "./store";
|
|
import {
|
|
WAILING_NARALEX_ENCOUNTER,
|
|
useWailingEncounterStore,
|
|
} from "./wailingCavernsEncounter";
|
|
|
|
const EMPTY_SHARED_WORLD = {
|
|
dungeonId: "wailing-caverns",
|
|
mobs: {},
|
|
mobTransforms: {},
|
|
partyMembers: [],
|
|
partyPositions: {},
|
|
partyRuntime: {
|
|
command: "defend",
|
|
activeMobId: null,
|
|
objectiveBossId: null,
|
|
memberTargetIds: {},
|
|
},
|
|
wailing: null,
|
|
manastorm: null,
|
|
} as const satisfies OnlineSharedWorld;
|
|
|
|
describe("online shared-world hydration", () => {
|
|
afterEach(() => {
|
|
useWailingEncounterStore.getState().reset("dungeon", 0);
|
|
useGameStore.getState().activateDungeon("wailing-caverns");
|
|
});
|
|
|
|
it("accepts an authority snapshot older than a follower's local revision", () => {
|
|
activateDungeonEncounterDefinitions(
|
|
"wailing-caverns",
|
|
[WAILING_NARALEX_ENCOUNTER],
|
|
"normal",
|
|
);
|
|
useGameStore.getState().activateDungeon("wailing-caverns");
|
|
const authorityBase = dungeonEncounterSnapshot(WAILING_NARALEX_ENCOUNTER.id)!;
|
|
resetDungeonEncounters("dungeon", "wailing-caverns");
|
|
const follower = dungeonEncounterSnapshot(WAILING_NARALEX_ENCOUNTER.id)!;
|
|
const authority = {
|
|
...authorityBase,
|
|
phase: "ready",
|
|
revision: Math.max(0, follower.revision - 1),
|
|
} as const;
|
|
expect(authority.revision).toBeLessThan(follower.revision);
|
|
|
|
applySharedWorld({ ...EMPTY_SHARED_WORLD, encounters: [authority] });
|
|
|
|
expect(dungeonEncounterSnapshot(WAILING_NARALEX_ENCOUNTER.id)).toMatchObject({
|
|
phase: "ready",
|
|
revision: authority.revision,
|
|
});
|
|
});
|
|
|
|
it("awards the authority's card rolls once to a follower, including after reconnect", () => {
|
|
useCombatStore.getState().initializeCharacter({ classId: "priest", level: 20 });
|
|
useCombatStore.getState().registerMob("shared-boss", { name: "Lady Anacondra", boss: true, maxHealth: 1 });
|
|
const mob = useCombatStore.getState().mobs["shared-boss"];
|
|
const world = { ...EMPTY_SHARED_WORLD, mobs: { "shared-boss": { ...mob, dead: true, looted: true, health: 0, bossCardDrop: { bossKey: "wow:lady anacondra", spawnId: mob.id, claimId: "shared-kill:1", rarities: ["normal", "epic"] as const } } } };
|
|
applySharedWorld(world);
|
|
applySharedWorld(world);
|
|
expect(useCombatStore.getState().bossCards.counts["wow:lady anacondra"]).toEqual({ normal: 1, epic: 1 });
|
|
const bossCards = JSON.parse(JSON.stringify(useCombatStore.getState().bossCards));
|
|
useCombatStore.getState().initializeCharacter({ classId: "priest", level: 20, bossCards });
|
|
applySharedWorld(world);
|
|
expect(useCombatStore.getState().bossCards.counts["wow:lady anacondra"]).toEqual({ normal: 1, epic: 1 });
|
|
expect(useCombatStore.getState().inventory).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("online damage presentation relay", () => {
|
|
it("relays authority companion hits so followers can display them", () => {
|
|
useCombatStore.getState().initializeCharacter({ classId: "priest", level: 20 });
|
|
useCombatStore.getState().registerMob("relay-mob", { name: "Relay mob", maxHealth: 100 });
|
|
const oldMembers = usePartyStore.getState().members;
|
|
usePartyStore.setState({ members: [{ id: "companion" } as (typeof oldMembers)[number]] });
|
|
try {
|
|
const event = {
|
|
id: 1, occurredAt: 1_000, kind: "damage", sourceActorId: "companion", targetActorId: "relay-mob",
|
|
rawAmount: 55, effectiveAmount: 40, overheal: 0, absorbed: 0, critical: true,
|
|
} satisfies StructuredCombatEvent;
|
|
expect(eventForRelay(event, "leader", true)).toMatchObject({
|
|
kind: "damage", sourceActorId: "companion", targetActorId: "relay-mob", effectiveAmount: 40, critical: true,
|
|
});
|
|
expect(eventForRelay(event, "leader", false)).toBeNull();
|
|
} finally {
|
|
usePartyStore.setState({ members: oldMembers });
|
|
}
|
|
});
|
|
|
|
it("relays authority AI hits on the leader and companions with their crit flag", () => {
|
|
useCombatStore.getState().initializeCharacter({ classId: "priest", level: 20 });
|
|
const oldMembers = usePartyStore.getState().members;
|
|
usePartyStore.setState({ members: [{ id: "companion" } as (typeof oldMembers)[number]] });
|
|
try {
|
|
const hit = {
|
|
id: 2, occurredAt: 2_000, kind: "damage", sourceActorId: "mob", targetActorId: PLAYER_AGGRO_ID,
|
|
rawAmount: 80, effectiveAmount: 50, overheal: 0, absorbed: 30, critical: true,
|
|
} satisfies StructuredCombatEvent;
|
|
expect(eventForRelay(hit, "leader", true)).toMatchObject({
|
|
sourceActorId: "mob", targetActorId: "online-player:leader", effectiveAmount: 50, critical: true,
|
|
});
|
|
expect(eventForRelay({ ...hit, targetActorId: "companion" }, "leader", true)).toMatchObject({
|
|
sourceActorId: "mob", targetActorId: "companion", effectiveAmount: 50, critical: true,
|
|
});
|
|
expect(eventForRelay(hit, "leader", false)).toBeNull();
|
|
} finally {
|
|
usePartyStore.setState({ members: oldMembers });
|
|
}
|
|
});
|
|
});
|