101 lines
5.3 KiB
TypeScript
101 lines
5.3 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
import { PLAYER_AGGRO_ID } from "./aggro";
|
|
import { installCombatClock } from "./combatClock";
|
|
import { useCombatStore, PLAYER_AURA_ENTITY_ID } from "./combatStore";
|
|
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
|
|
import { createConfiguredPartyMember, usePartyStore } from "./partyStore";
|
|
import { EMPTY_ITEM_STATS } from "./itemStats";
|
|
import {
|
|
WOW335_PALADIN_VENGEANCE_AURA_TAG,
|
|
WOW335_PALADIN_VENGEANCE_DAMAGE_ABILITY_IDS,
|
|
WOW335_PALADIN_VENGEANCE_TALENT_ID,
|
|
} from "./wow335PaladinVengeanceRuntime";
|
|
|
|
const TARGET_ID = "paladin-vengeance-target";
|
|
let restoreClock: (() => void) | undefined;
|
|
|
|
function registerTarget(id = TARGET_ID) {
|
|
useCombatStore.getState().registerMob(id, { name: "Vengeance target", maxHealth: 1_000_000, xpReward: 0 });
|
|
setMobPosition(id, [2, 0, 0]);
|
|
}
|
|
|
|
function criticalHit(actorId: string, now: number) {
|
|
return useCombatStore.getState().damageMob(TARGET_ID, 100, now,
|
|
{ actorId, role: "damage", abilityId: WOW335_PALADIN_VENGEANCE_DAMAGE_ABILITY_IDS["Crusader Strike"] },
|
|
"physical", {
|
|
attackKind: "melee",
|
|
seed: `${actorId}:${now}:vengeance-test`,
|
|
abilityId: WOW335_PALADIN_VENGEANCE_DAMAGE_ABILITY_IDS["Crusader Strike"],
|
|
criticalChanceBonusPercent: 100,
|
|
rules: { canMiss: false, canDodge: false, canParry: false, canBlock: false, resistanceMode: "none" },
|
|
});
|
|
}
|
|
|
|
describe("WoW Paladin Vengeance combat integration", () => {
|
|
beforeEach(() => {
|
|
restoreClock = installCombatClock(() => 1_000);
|
|
clearMobRuntimeRegistry();
|
|
useCombatStore.getState().initializeCharacter({ classId: "paladin", level: 80, xp: 0 });
|
|
usePartyStore.getState().initializeParty("wow335-paladin-vengeance", 80, useCombatStore.getState().health, "damage", 5);
|
|
registerTarget();
|
|
});
|
|
|
|
afterEach(() => {
|
|
restoreClock?.();
|
|
restoreClock = undefined;
|
|
});
|
|
|
|
it("applies a stack after player crits and uses existing stacks only on the next qualifying hit", () => {
|
|
useCombatStore.setState((state) => ({
|
|
talentRanks: { ...state.talentRanks, [WOW335_PALADIN_VENGEANCE_TALENT_ID]: 3 },
|
|
}));
|
|
const first = criticalHit(PLAYER_AGGRO_ID, 1_000);
|
|
expect(first.combatResult?.critical).toBe(true);
|
|
expect(useCombatStore.getState().auras.find((aura) => aura.targetId === PLAYER_AURA_ENTITY_ID
|
|
&& aura.definition.utilityTags?.includes(WOW335_PALADIN_VENGEANCE_AURA_TAG)))
|
|
.toMatchObject({ stacks: 1, appliedAt: 1_000, expiresAt: 16_000 });
|
|
|
|
const withStack = useCombatStore.getState().damageMob(TARGET_ID, 100, 2_000,
|
|
{ actorId: PLAYER_AGGRO_ID, role: "damage", abilityId: WOW335_PALADIN_VENGEANCE_DAMAGE_ABILITY_IDS["Crusader Strike"] },
|
|
"physical", {
|
|
attackKind: "melee", seed: "vengeance-followup", abilityId: WOW335_PALADIN_VENGEANCE_DAMAGE_ABILITY_IDS["Crusader Strike"],
|
|
rules: { canMiss: false, canDodge: false, canParry: false, canBlock: false, canCrit: false, resistanceMode: "none" },
|
|
});
|
|
expect(withStack.amount).toBeGreaterThan(0);
|
|
expect(useCombatStore.getState().auras.find((aura) => aura.targetId === PLAYER_AURA_ENTITY_ID
|
|
&& aura.definition.utilityTags?.includes(WOW335_PALADIN_VENGEANCE_AURA_TAG))?.stacks).toBe(1);
|
|
criticalHit(PLAYER_AGGRO_ID, 3_000);
|
|
expect(useCombatStore.getState().auras.find((aura) => aura.targetId === PLAYER_AURA_ENTITY_ID
|
|
&& aura.definition.utilityTags?.includes(WOW335_PALADIN_VENGEANCE_AURA_TAG)))
|
|
.toMatchObject({ stacks: 2, appliedAt: 3_000, expiresAt: 18_000 });
|
|
registerTarget("paladin-vengeance-control");
|
|
useCombatStore.setState((state) => ({ auras: state.auras.filter((aura) => !aura.definition.utilityTags?.includes(
|
|
WOW335_PALADIN_VENGEANCE_AURA_TAG,
|
|
)) }));
|
|
const withoutStack = useCombatStore.getState().damageMob("paladin-vengeance-control", 100, 2_000,
|
|
{ actorId: PLAYER_AGGRO_ID, role: "damage", abilityId: WOW335_PALADIN_VENGEANCE_DAMAGE_ABILITY_IDS["Crusader Strike"] },
|
|
"physical", {
|
|
attackKind: "melee", seed: "vengeance-followup", abilityId: WOW335_PALADIN_VENGEANCE_DAMAGE_ABILITY_IDS["Crusader Strike"],
|
|
rules: { canMiss: false, canDodge: false, canParry: false, canBlock: false, canCrit: false, resistanceMode: "none" },
|
|
});
|
|
expect(withStack.amount).toBeGreaterThan(withoutStack.amount);
|
|
});
|
|
|
|
it("uses the companion Paladin's own rank and owner-scoped stack aura", () => {
|
|
const talentRanks = { [WOW335_PALADIN_VENGEANCE_TALENT_ID]: 2 };
|
|
const member = createConfiguredPartyMember({
|
|
name: "Vengeance Paladin", classId: "paladin", specialization: "Retribution",
|
|
specializationId: "paladin:retribution", talentTreeId: "paladin-retribution", talentRanks, role: "damage",
|
|
}, 80, 0, "paladin-vengeance-companion");
|
|
usePartyStore.setState({ members: [{ ...member, gearStats: EMPTY_ITEM_STATS, talentRanks }] });
|
|
useCombatStore.getState().synchronizeActors();
|
|
|
|
const result = criticalHit(member.id, 2_000);
|
|
expect(result.combatResult?.critical).toBe(true);
|
|
expect(useCombatStore.getState().auras).toContainEqual(expect.objectContaining({
|
|
sourceId: member.id, targetId: member.id, appliedAt: 2_000, expiresAt: 17_000,
|
|
stacks: 1, definition: expect.objectContaining({ utilityTags: [WOW335_PALADIN_VENGEANCE_AURA_TAG] }),
|
|
}));
|
|
});
|
|
});
|