forked from phenom/healer-man
131 lines
5.4 KiB
TypeScript
131 lines
5.4 KiB
TypeScript
import { beforeEach, describe, expect, it } from "vitest";
|
|
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
|
|
import { clearPartyRuntimeRegistry } from "./partyRuntimeRegistry";
|
|
import { PLAYER_AGGRO_ID } from "./aggro";
|
|
import { abilitiesForClass } from "./abilityCatalog";
|
|
import { resolveHealingValue } from "./combatAuras";
|
|
import { useCombatStore } from "./combatStore";
|
|
import { usePartyStore } from "./partyStore";
|
|
import { talentModifiersForRanks } from "./talentModifiers";
|
|
import { WOW335_MAGE_WINTERS_CHILL_TAG } from "./wow335MageWintersChillRuntime";
|
|
|
|
function registerTarget(id: string): void {
|
|
useCombatStore.getState().registerMob(id, { name: id, maxHealth: 100_000, xpReward: 0 });
|
|
setMobPosition(id, [8, 0, 0]);
|
|
}
|
|
|
|
function setPlayerWintersChillRank(rank: number): void {
|
|
useCombatStore.setState((state) => {
|
|
const talentRanks = { ...state.talentRanks, "talent-68": rank };
|
|
return {
|
|
talentRanks,
|
|
talentModifiers: talentModifiersForRanks(talentRanks, state.classId),
|
|
};
|
|
});
|
|
}
|
|
|
|
function setPartyMageWintersChillRank(memberId: string, rank: number): void {
|
|
usePartyStore.setState((state) => ({
|
|
members: state.members.map((member) => member.id === memberId ? {
|
|
...member,
|
|
classId: "mage",
|
|
talentRanks: { ...(rank > 0 ? { "talent-68": rank } : {}) },
|
|
resourceType: "mana",
|
|
resourceName: "Mana",
|
|
maxResource: 10_000,
|
|
resource: 9_000,
|
|
resourcePools: { ...member.resourcePools, mana: 9_000 },
|
|
} : member),
|
|
}));
|
|
}
|
|
|
|
function damageSpell(
|
|
targetId: string,
|
|
abilityId: string,
|
|
school: "fire" | "frost",
|
|
now: number,
|
|
sourceActorId = PLAYER_AGGRO_ID,
|
|
periodic = false,
|
|
): number {
|
|
const member = usePartyStore.getState().members.find((candidate) => candidate.id === sourceActorId);
|
|
useCombatStore.getState().damageMob(targetId, 100, now, {
|
|
actorId: sourceActorId,
|
|
role: member?.role ?? "damage",
|
|
abilityId,
|
|
}, school, {
|
|
attackKind: "spell",
|
|
seed: `wow335-mage-winters-chill:${sourceActorId}:${targetId}:${abilityId}:${now}`,
|
|
abilityId,
|
|
periodic,
|
|
rules: { canMiss: false, canCrit: true, canDodge: false, canParry: false, canBlock: false },
|
|
});
|
|
return useCombatStore.getState().lastCombatResult!.attackTable.criticalPercent;
|
|
}
|
|
|
|
describe("WoW Mage Winter's Chill combat integration", () => {
|
|
beforeEach(() => {
|
|
clearMobRuntimeRegistry();
|
|
clearPartyRuntimeRegistry();
|
|
useCombatStore.getState().initializeCharacter({ classId: "mage", level: 80, xp: 0 });
|
|
usePartyStore.getState().initializeParty("mage-winters-chill-tests", 1, useCombatStore.getState().health);
|
|
});
|
|
|
|
it("stacks on positive direct Frost hits and increases later Mage spell crit against the target", () => {
|
|
const frostbolt = abilitiesForClass("mage").find((ability) => ability.name === "Frostbolt")!;
|
|
const fireball = abilitiesForClass("mage").find((ability) => ability.name === "Fireball")!;
|
|
setPlayerWintersChillRank(3);
|
|
registerTarget("winter-chill-target");
|
|
registerTarget("winter-clean-target");
|
|
|
|
for (let stack = 0; stack < 6; stack += 1) {
|
|
damageSpell("winter-chill-target", frostbolt.id, "frost", 1_000 + stack * 1_000);
|
|
}
|
|
const aura = useCombatStore.getState().auras.find((candidate) => (
|
|
candidate.targetId === "winter-chill-target"
|
|
&& candidate.definition.utilityTags?.includes(WOW335_MAGE_WINTERS_CHILL_TAG)
|
|
));
|
|
expect(aura?.stacks).toBe(5);
|
|
expect(aura?.expiresAt).toBe(14_000);
|
|
expect(resolveHealingValue(1_000, [aura!], "taken")).toBeCloseTo(650);
|
|
|
|
const cleanCrit = damageSpell("winter-clean-target", fireball.id, "fire", 7_000);
|
|
const chilledCrit = damageSpell("winter-chill-target", fireball.id, "fire", 8_000);
|
|
expect(chilledCrit - cleanCrit).toBe(5);
|
|
});
|
|
|
|
it("uses party Mage ranks for passive crit, direct Frost procs, and incoming spell vulnerability", () => {
|
|
const frostbolt = abilitiesForClass("mage").find((ability) => ability.name === "Frostbolt")!;
|
|
const fireball = abilitiesForClass("mage").find((ability) => ability.name === "Fireball")!;
|
|
const member = usePartyStore.getState().members[0]!;
|
|
setPartyMageWintersChillRank(member.id, 0);
|
|
registerTarget("party-winter-clean");
|
|
registerTarget("party-winter-chill");
|
|
|
|
const baselineCrit = damageSpell("party-winter-clean", fireball.id, "fire", 1_000, member.id);
|
|
setPartyMageWintersChillRank(member.id, 3);
|
|
const frostCrit = damageSpell("party-winter-chill", frostbolt.id, "frost", 2_000, member.id);
|
|
const aura = useCombatStore.getState().auras.find((candidate) => (
|
|
candidate.targetId === "party-winter-chill"
|
|
&& candidate.definition.utilityTags?.includes(WOW335_MAGE_WINTERS_CHILL_TAG)
|
|
));
|
|
const chilledCrit = damageSpell("party-winter-chill", fireball.id, "fire", 3_000, member.id);
|
|
|
|
expect(frostCrit - baselineCrit).toBeCloseTo(3);
|
|
expect(aura?.stacks).toBe(1);
|
|
expect(chilledCrit - baselineCrit).toBeCloseTo(4);
|
|
});
|
|
|
|
it("does not arm the debuff from periodic Frost damage", () => {
|
|
const frostbolt = abilitiesForClass("mage").find((ability) => ability.name === "Frostbolt")!;
|
|
setPlayerWintersChillRank(3);
|
|
registerTarget("periodic-winter-target");
|
|
|
|
damageSpell("periodic-winter-target", frostbolt.id, "frost", 1_000, PLAYER_AGGRO_ID, true);
|
|
|
|
expect(useCombatStore.getState().auras.some((aura) => (
|
|
aura.targetId === "periodic-winter-target"
|
|
&& aura.definition.utilityTags?.includes(WOW335_MAGE_WINTERS_CHILL_TAG)
|
|
))).toBe(false);
|
|
});
|
|
});
|