Files
healer-man/src/game/wow335ShamanFlurry.integration.test.ts
2026-10-02 10:38:12 -04:00

172 lines
5.8 KiB
TypeScript

import { beforeEach, describe, expect, it } from "vitest";
import { EMPTY_ITEM_STATS } from "./itemStats";
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
import { clearPartyRuntimeRegistry } from "./partyRuntimeRegistry";
import { usePartyStore, createConfiguredPartyMember } from "./partyStore";
import { PLAYER_AURA_ENTITY_ID, useCombatStore } from "./combatStore";
import {
WOW335_SHAMAN_FLURRY_AURA_TAG,
WOW335_SHAMAN_FLURRY_DURATION_MS,
} from "./wow335ShamanFlurryRuntime";
const TARGET_ID = "wow335-shaman-flurry-target";
const FLURRY_RANK_ID = "talent-602";
function registerTarget(): void {
useCombatStore.getState().registerMob(TARGET_ID, {
name: "Flurry target",
level: 80,
maxHealth: 1_000_000,
xpReward: 0,
});
setMobPosition(TARGET_ID, [3, 0, 0]);
useCombatStore.setState((state) => ({
mobs: {
...state.mobs,
[TARGET_ID]: {
...state.mobs[TARGET_ID]!,
characterDefender: { level: 80, dodgePercent: 0, parryPercent: 0, blockPercent: 0 },
},
},
}));
}
function setupPlayerShaman(rank: number): void {
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-flurry-player", 80, useCombatStore.getState().health);
registerTarget();
useCombatStore.setState((state) => ({
gearStats: {
...state.gearStats,
hitRating: 100_000,
criticalStrikeRating: 100_000,
expertiseRating: 100_000,
},
talentRanks: { ...state.talentRanks, [FLURRY_RANK_ID]: rank },
autoAttackTargetId: TARGET_ID,
nextAutoAttackAt: 1_000,
}));
}
function flurryAura(actorId: string) {
return useCombatStore.getState().auras.find((aura) => (
aura.sourceId === actorId
&& aura.targetId === actorId
&& aura.definition.utilityTags?.includes(WOW335_SHAMAN_FLURRY_AURA_TAG)
));
}
describe("WoW Shaman Flurry combat integration", () => {
beforeEach(() => setupPlayerShaman(0));
it("procs from the player's landed melee crit and hastens the next main-hand interval", () => {
const intervalForRank = (rank: number) => {
setupPlayerShaman(rank);
useCombatStore.getState().tick(0.1, 1_000);
const state = useCombatStore.getState();
expect(state.lastCombatResult?.critical).toBe(true);
expect(state.lastCombatResult?.landed).toBe(true);
return state.nextAutoAttackAt - 1_000;
};
const baselineInterval = intervalForRank(0);
const flurryInterval = intervalForRank(1);
const aura = flurryAura(PLAYER_AURA_ENTITY_ID);
expect(flurryInterval).toBeLessThan(baselineInterval);
expect(aura).toMatchObject({
stacks: 3,
appliedAt: 1_000,
expiresAt: 1_000 + WOW335_SHAMAN_FLURRY_DURATION_MS,
definition: {
modifiers: [{ kind: "stat", stat: "melee-haste", operation: "flat", value: 4, perStack: false }],
},
});
});
it("uses a generated companion Shaman's exact rank and consumes the next three autoattack attempts", () => {
clearMobRuntimeRegistry();
clearPartyRuntimeRegistry();
useCombatStore.getState().initializeCharacter({ classId: "mage", level: 80, xp: 0 });
usePartyStore.getState().initializeParty("wow-shaman-flurry-companion", 80, useCombatStore.getState().health);
const baseMember = createConfiguredPartyMember({
name: "Flurry Shaman",
classId: "shaman",
specialization: "Enhancement",
specializationId: "shaman:enhancement",
talentTreeId: "shaman-enhancement",
talentRanks: { [FLURRY_RANK_ID]: 2 },
role: "damage",
}, 80, 0, "wow-shaman-flurry-companion:member");
const member = {
...baseMember,
gearStats: { ...EMPTY_ITEM_STATS, hitRating: 100_000, criticalStrikeRating: 100_000, expertiseRating: 100_000 },
health: baseMember.maxHealth,
};
usePartyStore.setState({ members: [member] });
useCombatStore.getState().commitCompanionActors([member]);
registerTarget();
const trigger = useCombatStore.getState().damageMob(
TARGET_ID,
100,
2_000,
{ actorId: member.id, role: member.role, abilityId: "party-auto-attack" },
"physical",
{
attackKind: "melee",
seed: "flurry-companion-trigger",
abilityId: "party-auto-attack",
rules: {
isAutoAttack: true,
canMiss: false,
canDodge: false,
canParry: false,
canBlock: false,
},
},
);
expect(trigger.combatResult?.landed).toBe(true);
expect(trigger.combatResult?.critical).toBe(true);
expect(flurryAura(member.id)).toMatchObject({
stacks: 3,
definition: {
modifiers: [{ kind: "stat", stat: "melee-haste", operation: "flat", value: 6, perStack: false }],
},
});
// A fake rank on the Mage player must not supply the companion's Flurry value.
useCombatStore.setState((state) => ({
talentRanks: { ...state.talentRanks, [FLURRY_RANK_ID]: 3 },
}));
for (let swing = 1; swing <= 3; swing += 1) {
useCombatStore.getState().damageMob(
TARGET_ID,
100,
2_000 + swing * 100,
{ actorId: member.id, role: member.role, abilityId: "party-auto-attack" },
"physical",
{
attackKind: "melee",
seed: `flurry-companion-swing-${swing}`,
abilityId: "party-auto-attack",
rules: {
isAutoAttack: true,
canCrit: false,
canMiss: false,
canDodge: false,
canParry: false,
canBlock: false,
},
},
);
const aura = flurryAura(member.id);
expect(aura?.stacks ?? 0).toBe(3 - swing);
}
expect(flurryAura(PLAYER_AURA_ENTITY_ID)).toBeUndefined();
});
});