153 lines
6.8 KiB
TypeScript
153 lines
6.8 KiB
TypeScript
import { beforeEach, describe, expect, it } from "vitest";
|
|
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
|
|
import { clearPartyRuntimeRegistry, registerPartyRuntimePosition } from "./partyRuntimeRegistry";
|
|
import { PLAYER_AURA_ENTITY_ID, useCombatStore } from "./combatStore";
|
|
import { WOW335_ABILITIES_BY_CLASS } from "./wow335AbilityCatalog";
|
|
import { usePartyStore } from "./partyStore";
|
|
|
|
function shamanAbility(name: string) {
|
|
const ability = useCombatStore.getState().abilities.find((candidate) => candidate.name === name);
|
|
expect(ability, `Shaman ${name}`).toBeDefined();
|
|
return ability!;
|
|
}
|
|
|
|
function finishCast(abilityId: string, targetId: string | undefined, startedAt: number): void {
|
|
const requestTargetId = targetId === PLAYER_AURA_ENTITY_ID ? null : targetId;
|
|
const result = useCombatStore.getState().castAbility(
|
|
abilityId,
|
|
requestTargetId !== undefined
|
|
? { targetActorId: requestTargetId, now: startedAt }
|
|
: { now: startedAt },
|
|
);
|
|
expect(result.ok, JSON.stringify(result)).toBe(true);
|
|
const cast = useCombatStore.getState().activeCast;
|
|
if (cast) useCombatStore.getState().tick(0.1, cast.completesAt);
|
|
}
|
|
|
|
describe("WoW Shaman source-backed proc talents", () => {
|
|
beforeEach(() => {
|
|
clearMobRuntimeRegistry();
|
|
clearPartyRuntimeRegistry();
|
|
useCombatStore.getState().initializeCharacter({ classId: "shaman", level: 80, xp: 0 });
|
|
useCombatStore.getState().resetSettings();
|
|
useCombatStore.getState().setPlayerPosition([0, 0, 0]);
|
|
usePartyStore.getState().initializeParty("wow-shaman-proc-tests", 80, useCombatStore.getState().health);
|
|
for (const [index, member] of usePartyStore.getState().members.entries()) {
|
|
registerPartyRuntimePosition(member.id, [4 + index, 0, 0]);
|
|
}
|
|
});
|
|
|
|
it.each([
|
|
[1, 500],
|
|
[2, 1_000],
|
|
[3, 1_500],
|
|
])("reduces the shared Shock cooldown by %i rank(s) (%i ms) with Reverberation", (rank, reductionMs) => {
|
|
const flameShock = shamanAbility("Flame Shock");
|
|
const shockAbilities = useCombatStore.getState().abilities.filter((ability) => (
|
|
ability.cooldownGroup === "shaman-shock"
|
|
));
|
|
expect(shockAbilities.length).toBeGreaterThan(1);
|
|
|
|
const target = `reverberation-target-${rank}`;
|
|
useCombatStore.getState().registerMob(target, {
|
|
name: "Reverberation target",
|
|
level: 80,
|
|
maxHealth: 100_000,
|
|
xpReward: 0,
|
|
});
|
|
setMobPosition(target, [8, 0, 0]);
|
|
useCombatStore.getState().selectMob(target);
|
|
useCombatStore.setState((state) => ({
|
|
talentRanks: { ...state.talentRanks, "talent-575": rank },
|
|
}));
|
|
|
|
const result = useCombatStore.getState().castAbility(flameShock.id, undefined, 1_000);
|
|
expect(result.ok, JSON.stringify(result)).toBe(true);
|
|
const cast = useCombatStore.getState().activeCast;
|
|
if (cast) useCombatStore.getState().tick(0.1, cast.completesAt);
|
|
|
|
const expectedReadyAt = 1_000 + 6_000 - reductionMs;
|
|
for (const ability of shockAbilities) {
|
|
expect(useCombatStore.getState().cooldowns[ability.id], ability.name).toBe(expectedReadyAt);
|
|
}
|
|
});
|
|
|
|
it("uses the ranked Ancestral Healing reduction and heals the lowest-health ally from Ancestral Awakening", () => {
|
|
const members = usePartyStore.getState().members;
|
|
const mostInjured = members[0]!;
|
|
usePartyStore.setState((state) => ({
|
|
members: state.members.map((member) => member.id === mostInjured.id
|
|
? { ...member, health: Math.round(member.maxHealth * 0.4) }
|
|
: member),
|
|
}));
|
|
useCombatStore.setState((state) => ({
|
|
health: state.maxHealth - 1,
|
|
gearStats: { ...state.gearStats, criticalStrikeRating: 100_000 },
|
|
talentRanks: { ...state.talentRanks, "talent-2061": 3, "talent-581": 3 },
|
|
}));
|
|
|
|
const heal = shamanAbility("Healing Wave");
|
|
finishCast(heal.id, PLAYER_AURA_ENTITY_ID, 1_000);
|
|
|
|
const events = useCombatStore.getState().combatEvents;
|
|
const directHeal = events.find((event) => event.kind === "healing" && event.abilityId === heal.id);
|
|
expect(directHeal?.critical).toBe(true);
|
|
const awakeningHeal = events.find((event) => (
|
|
event.kind === "healing" && event.abilityId === "wow335-shaman-ancestral-awakening"
|
|
));
|
|
expect(awakeningHeal).toMatchObject({ targetActorId: mostInjured.id, school: "nature" });
|
|
expect(awakeningHeal?.rawAmount).toBe(Math.round((directHeal?.rawAmount ?? 0) * 0.3));
|
|
expect(usePartyStore.getState().members.find((member) => member.id === mostInjured.id)?.health)
|
|
.toBeGreaterThan(Math.round(mostInjured.maxHealth * 0.4));
|
|
|
|
const ancestralHealing = useCombatStore.getState().auras.find((aura) => (
|
|
aura.targetId === PLAYER_AURA_ENTITY_ID
|
|
&& aura.definition.utilityTags?.includes("wow335-shaman-ancestral-healing")
|
|
));
|
|
expect(ancestralHealing).toMatchObject({ expiresAt: directHeal!.occurredAt + 15_000 });
|
|
expect(ancestralHealing?.definition.modifiers).toContainEqual({
|
|
kind: "damage",
|
|
direction: "taken",
|
|
school: "physical",
|
|
operation: "percent",
|
|
value: -0.1,
|
|
});
|
|
});
|
|
|
|
it("reduces Elemental Mastery and Thunderstorm cooldowns on damaging Lightning Bolt hits", () => {
|
|
const state = useCombatStore.getState();
|
|
const lightningBolt = shamanAbility("Lightning Bolt");
|
|
const sourceCooldownAbilities = ["Elemental Mastery", "Thunderstorm"].map((name) => (
|
|
WOW335_ABILITIES_BY_CLASS.shaman.find((ability) => ability.name === name)
|
|
));
|
|
expect(sourceCooldownAbilities.every(Boolean)).toBe(true);
|
|
const cooldownAbilities = sourceCooldownAbilities as NonNullable<typeof sourceCooldownAbilities[number]>[];
|
|
const target = "shamanism-target";
|
|
useCombatStore.getState().registerMob(target, { name: "Shamanism target", level: 80, maxHealth: 100_000, xpReward: 0 });
|
|
setMobPosition(target, [8, 0, 0]);
|
|
useCombatStore.getState().selectMob(target);
|
|
useCombatStore.setState((current) => ({
|
|
talentRanks: { ...current.talentRanks, "talent-2252": 3 },
|
|
abilities: [...current.abilities, ...cooldownAbilities.filter((ability) => (
|
|
!current.abilities.some((candidate) => candidate.id === ability.id)
|
|
))],
|
|
cooldowns: {
|
|
...current.cooldowns,
|
|
[cooldownAbilities[0]!.id]: 20_000,
|
|
[cooldownAbilities[1]!.id]: 20_000,
|
|
},
|
|
}));
|
|
|
|
const result = useCombatStore.getState().castAbility(lightningBolt.id, undefined, 1_000);
|
|
expect(result.ok, JSON.stringify(result)).toBe(true);
|
|
const cast = useCombatStore.getState().activeCast;
|
|
if (cast) useCombatStore.getState().tick(0.1, cast.completesAt);
|
|
|
|
expect(useCombatStore.getState().combatEvents.some((event) => (
|
|
event.kind === "damage" && event.abilityId === lightningBolt.id && event.effectiveAmount > 0
|
|
))).toBe(true);
|
|
expect(useCombatStore.getState().cooldowns[cooldownAbilities[0]!.id]).toBe(19_000);
|
|
expect(useCombatStore.getState().cooldowns[cooldownAbilities[1]!.id]).toBe(19_000);
|
|
});
|
|
});
|