Update Healer Man server

This commit is contained in:
phenom committed 2026-09-28 22:46:45 -04:00
1 parent 9d7e0e2f9e
commit 35dfdaed61
94 files changed
+17034 -418

No files matched your search

+552
View File
@@ -0,0 +1,552 @@
import { beforeEach, describe, expect, it } from "vitest";
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
import { clearPartyRuntimeRegistry, updatePartyRuntimePosition } from "./partyRuntimeRegistry";
import { PLAYER_AURA_ENTITY_ID, useCombatStore } from "./combatStore";
import { usePartyStore } from "./partyStore";
import { abilityTalentNodeId, preloadAbilityCatalogsForClasses } from "./abilityCatalog";
import { applyAura } from "./combatAuras";
import { WOW335_TALENT_NODES } from "./wow335Talents.generated";
function abilityId(name: string): string {
const ability = useCombatStore.getState().abilities.find((candidate) => candidate.name === name);
expect(ability, name).toBeDefined();
return ability!.id;
}
function unlockTalentAbility(name: string): string {
const ability = useCombatStore.getState().abilities.find((candidate) => candidate.name === name);
expect(ability, name).toBeDefined();
const nodeId = abilityTalentNodeId(ability!);
expect(nodeId, name).toBeTruthy();
useCombatStore.setState((state) => ({ talentRanks: { ...state.talentRanks, [nodeId!]: 1 } }));
return ability!.id;
}
function setTalentRank(name: string, rank: number): void {
const talent = WOW335_TALENT_NODES.find((candidate) => candidate.name === name);
expect(talent, name).toBeDefined();
useCombatStore.setState((state) => ({
talentRanks: { ...state.talentRanks, [talent!.id]: rank },
}));
}
describe("WoW 3.3.5 Paladin source-backed runtime mechanics", () => {
beforeEach(async () => {
clearMobRuntimeRegistry();
clearPartyRuntimeRegistry();
await preloadAbilityCatalogsForClasses(["paladin"]);
useCombatStore.getState().initializeCharacter({ classId: "paladin", level: 80 });
useCombatStore.getState().resetSettings();
useCombatStore.getState().setPlayerPosition([0, 0, 0]);
usePartyStore.getState().initializeParty("wow335-paladin-runtime-tests", 80, useCombatStore.getState().health);
useCombatStore.setState((state) => ({
resource: state.maxResource,
resourcePools: { ...state.resourcePools, mana: state.maxResourcePools.mana },
}));
});
it("blocks silence with Aura Mastery for its sourced 10-second window, then allows silence after expiry", () => {
const mobId = "paladin-aura-mastery-silence";
useCombatStore.getState().registerMob(mobId, {
name: "Silence attacker",
maxHealth: 10_000,
attacks: [{
id: "silence",
name: "Silence",
delivery: "area",
target: "primary",
school: "shadow",
animation: "cast",
castTimeMs: 0,
range: 30,
cooldownMs: 10_000,
initialCooldownMs: [0, 0],
repeatCooldownMs: [10_000, 10_000],
damageMultiplier: 0,
effect: { kind: "control", mechanic: "silence", durationMs: 3_000 },
}],
});
setMobPosition(mobId, [2, 0, 0]);
useCombatStore.getState().selectMob(mobId);
useCombatStore.getState().damageMob(mobId, 1, 1_000);
const activate = useCombatStore.getState().castAbility(unlockTalentAbility("Aura Mastery"), undefined, 1_000);
expect(activate).toMatchObject({ ok: true });
expect(useCombatStore.getState().auras.some((aura) => (
aura.targetId === PLAYER_AURA_ENTITY_ID
&& aura.definition.controlImmunities?.includes("silence")
&& aura.expiresAt === 11_000
))).toBe(true);
const partyMember = usePartyStore.getState().members[0]!;
expect(useCombatStore.getState().auras.some((aura) => (
aura.targetId === partyMember.id
&& aura.definition.controlImmunities?.includes("silence")
&& aura.expiresAt === 11_000
))).toBe(true);
useCombatStore.getState().advanceMobCombat(1_450);
expect(useCombatStore.getState().controlMechanic).toBeNull();
expect(useCombatStore.getState().schoolLockedUntil).toEqual({});
useCombatStore.getState().tick(0.1, 11_001);
expect(useCombatStore.getState().auras.some((aura) => (
aura.definition.controlImmunities?.includes("silence")
))).toBe(false);
useCombatStore.getState().advanceMobCombat(11_450);
expect(useCombatStore.getState().controlMechanic).toBe("silence");
expect(useCombatStore.getState().controlledUntil).toBe(14_450);
});
it("lets Redemption target and revive a defeated party member at the rank's source health", () => {
const member = usePartyStore.getState().members[0]!;
usePartyStore.setState((party) => ({
members: party.members.map((candidate) => candidate.id === member.id
? { ...candidate, health: 0, status: "down" as const }
: candidate),
}));
updatePartyRuntimePosition(member.id, 4, 0, 0);
usePartyStore.getState().selectMember(member.id);
const cast = useCombatStore.getState().castAbility(abilityId("Redemption"), undefined, 1_000);
expect(cast).toMatchObject({ ok: true });
const activeCast = useCombatStore.getState().activeCast;
expect(activeCast?.targetId).toBe(member.id);
useCombatStore.getState().tick(0.1, activeCast!.completesAt);
expect(usePartyStore.getState().members.find((candidate) => candidate.id === member.id)?.health).toBe(1_760);
expect(useCombatStore.getState().combatEvents.some((event) => (
event.kind === "resurrection" && event.targetActorId === member.id
))).toBe(true);
});
it("blocks Redemption while any living enemy is engaged", () => {
const member = usePartyStore.getState().members[0]!;
usePartyStore.setState((party) => ({
members: party.members.map((candidate) => candidate.id === member.id
? { ...candidate, health: 0, status: "down" as const }
: candidate),
}));
updatePartyRuntimePosition(member.id, 4, 0, 0);
usePartyStore.getState().selectMember(member.id);
const targetId = "paladin-redemption-combat-gate";
useCombatStore.getState().registerMob(targetId, { name: "Engaged enemy", maxHealth: 10_000 });
setMobPosition(targetId, [4, 0, 0]);
useCombatStore.setState((state) => ({
mobs: { ...state.mobs, [targetId]: { ...state.mobs[targetId]!, engaged: true } },
}));
const result = useCombatStore.getState().castAbility(abilityId("Redemption"), undefined, 1_000);
expect(result).toMatchObject({ ok: false, reason: "condition-not-met" });
expect(useCombatStore.getState().activeCast).toBeNull();
expect(usePartyStore.getState().members.find((candidate) => candidate.id === member.id)?.health).toBe(0);
});
it("enforces Hammer of Wrath's 20% execute gate", () => {
const targetId = "paladin-hammer-gate-target";
useCombatStore.getState().registerMob(targetId, { name: "Target", maxHealth: 10_000 });
setMobPosition(targetId, [4, 0, 0]);
useCombatStore.setState((state) => ({
mobs: { ...state.mobs, [targetId]: { ...state.mobs[targetId]!, health: 2_001 } },
selectedTargetId: targetId,
}));
const result = useCombatStore.getState().castAbility(abilityId("Hammer of Wrath"), undefined, 1_000);
expect(result).toMatchObject({ ok: false, reason: "condition-not-met" });
});
it("requires a shield before Holy Shield can be activated", () => {
useCombatStore.setState({ equipment: {}, inventory: [] });
expect(useCombatStore.getState().abilities.find((candidate) => candidate.name === "Holy Shield")?.target).toBe("self");
const result = useCombatStore.getState().castAbility(unlockTalentAbility("Holy Shield"), undefined, 1_000);
expect(result).toMatchObject({ ok: false, reason: "condition-not-met" });
});
it("procs three 8-second Reckoning charges from melee hits and adds one landed autoattack per charge", () => {
setTalentRank("Reckoning", 5);
const mobId = "paladin-reckoning-trigger";
useCombatStore.getState().registerMob(mobId, {
name: "Reckoning attacker",
level: 80,
maxHealth: 100_000,
attacks: [{
id: "reckoning-melee",
name: "Melee attack",
delivery: "melee",
target: "primary",
school: "physical",
animation: "attack",
castTimeMs: 0,
range: 8,
cooldownMs: 500,
initialCooldownMs: [0, 0],
repeatCooldownMs: [500, 500],
damageMultiplier: 0.1,
}],
});
setMobPosition(mobId, [2, 0, 0]);
useCombatStore.getState().damageMob(mobId, 1, 1_000);
const ensureMeleeHit = applyAura(useCombatStore.getState().auras, {
definition: {
id: "test:ensure-reckoning-melee-hit",
name: "Ensure incoming melee hit",
disposition: "buff",
durationMs: null,
modifiers: [{ kind: "stat", stat: "incoming-melee-hit", operation: "flat", value: 100 }],
},
sourceId: mobId,
targetId: PLAYER_AURA_ENTITY_ID,
now: 1_000,
}).auras;
useCombatStore.setState((state) => ({ auras: [...ensureMeleeHit] }));
let procAt: number | null = null;
for (let now = 1_450; now <= 30_000; now += 250) {
useCombatStore.getState().advanceMobCombat(now);
if (useCombatStore.getState().auras.some((aura) => (
aura.targetId === PLAYER_AURA_ENTITY_ID
&& aura.definition.utilityTags?.includes("wow335-paladin-reckoning")
))) {
procAt = now;
break;
}
}
expect(procAt).not.toBeNull();
const reckoning = useCombatStore.getState().auras.find((aura) => (
aura.targetId === PLAYER_AURA_ENTITY_ID
&& aura.definition.utilityTags?.includes("wow335-paladin-reckoning")
));
expect(reckoning).toMatchObject({ stacks: 3, expiresAt: procAt! + 8_000 });
useCombatStore.setState((state) => ({
autoAttackTargetId: mobId,
nextAutoAttackAt: procAt! + 100,
gearStats: { ...state.gearStats, hitRating: 1_000_000, expertiseRating: 1_000_000 },
}));
let autoAttackAt = procAt! + 100;
for (let charge = 2; charge >= 0; charge -= 1) {
useCombatStore.getState().tick(0.1, autoAttackAt);
const swingEvents = useCombatStore.getState().combatEvents.filter((event) => (
event.kind === "damage"
&& event.sourceActorId === PLAYER_AURA_ENTITY_ID
&& event.targetActorId === mobId
&& event.occurredAt === autoAttackAt
));
expect(swingEvents).toHaveLength(2);
const remaining = useCombatStore.getState().auras.find((aura) => (
aura.targetId === PLAYER_AURA_ENTITY_ID
&& aura.definition.utilityTags?.includes("wow335-paladin-reckoning")
));
if (charge > 0) expect(remaining?.stacks).toBe(charge);
else expect(remaining).toBeUndefined();
autoAttackAt = useCombatStore.getState().nextAutoAttackAt;
}
});
it("reflects the sourced rank percentage of incoming critical damage with Eye for an Eye", () => {
const mobId = "paladin-eye-for-an-eye-crit";
useCombatStore.getState().registerMob(mobId, {
name: "Critical attacker",
level: 80,
maxHealth: 100_000,
damageMultiplier: 1,
attacks: [{
id: "eye-crit-18",
name: "Critical attack",
delivery: "area",
target: "primary",
school: "fire",
animation: "cast",
castTimeMs: 0,
range: 30,
cooldownMs: 10_000,
initialCooldownMs: [0, 0],
repeatCooldownMs: [10_000, 10_000],
damageMultiplier: 1,
}],
});
setMobPosition(mobId, [2, 0, 0]);
useCombatStore.getState().selectMob(mobId);
useCombatStore.getState().damageMob(mobId, 1, 1_000);
const ensureSpellHit = applyAura([], {
definition: {
id: "test:ensure-incoming-spell-hit",
name: "Ensure incoming spell hit",
disposition: "buff",
durationMs: null,
modifiers: [{ kind: "stat", stat: "incoming-spell-hit", operation: "flat", value: 100 }],
},
sourceId: mobId,
targetId: PLAYER_AURA_ENTITY_ID,
now: 1_000,
}).auras;
useCombatStore.setState((state) => ({
talentRanks: { ...state.talentRanks, "talent-1632": 3 },
auras: [...state.auras, ...ensureSpellHit],
}));
// This seeded instant spell attack rolls inside the crit slice of the
// equal-level attack table, so the test does not rely on chance.
useCombatStore.getState().advanceMobCombat(1_450);
useCombatStore.getState().advanceMobCombat(3_100);
const events = useCombatStore.getState().combatEvents;
const incoming = events.find((event) => event.kind === "damage"
&& event.sourceActorId === mobId && event.targetActorId === PLAYER_AURA_ENTITY_ID);
expect(incoming?.critical, JSON.stringify(incoming)).toBe(true);
const reflected = events.find((event) => event.kind === "damage"
&& event.targetActorId === mobId && event.abilityId === "talent-1632");
expect(reflected?.rawAmount).toBe(Math.round((incoming?.effectiveAmount ?? 0) * 0.15));
});
it("bounces Avenger's Shield to three total targets without damage falloff", () => {
const shieldId = unlockTalentAbility("Avenger's Shield");
const targetIds = [
"paladin-avenger-target",
"paladin-avenger-bounce-a",
"paladin-avenger-bounce-b",
"paladin-avenger-bounce-c",
"paladin-avenger-out-of-range",
];
for (const id of targetIds) {
useCombatStore.getState().registerMob(id, { name: id, maxHealth: 1_000_000 });
}
setMobPosition(targetIds[0]!, [4, 0, 0]);
setMobPosition(targetIds[1]!, [10, 0, 0]);
setMobPosition(targetIds[2]!, [16, 0, 0]);
setMobPosition(targetIds[3]!, [22, 0, 0]);
setMobPosition(targetIds[4]!, [10, 0, 12]);
useCombatStore.getState().selectMob(targetIds[0]!);
const result = useCombatStore.getState().castAbility(shieldId, undefined, 1_000);
expect(result, JSON.stringify(result)).toMatchObject({ ok: true });
expect(result.affectedMobIds).toEqual(targetIds.slice(0, 3));
expect(result.affectedMobIds).not.toContain(targetIds[3]);
expect(result.affectedMobIds).not.toContain(targetIds[4]);
const damageEvents = useCombatStore.getState().combatEvents.filter((event) => (
event.kind === "damage" && event.abilityId === shieldId
&& result.affectedMobIds.includes(event.targetActorId)
));
expect(damageEvents).toHaveLength(3);
const normalizedRawDamage = damageEvents.map((event) => Math.round(event.rawAmount / (event.critical ? 2 : 1)));
expect(new Set(normalizedRawDamage).size).toBe(1);
});
it("applies a Greater Blessing to the selected ally's class group", () => {
const members = usePartyStore.getState().members;
const target = members[0]!;
usePartyStore.setState((party) => ({
members: party.members.map((candidate, index) => ({
...candidate,
classId: index < 2 ? "mage" : "priest",
})),
}));
for (const member of members) updatePartyRuntimePosition(member.id, 1, 0, 0);
usePartyStore.getState().selectMember(target.id);
const result = useCombatStore.getState().castAbility(abilityId("Greater Blessing of Kings"), undefined, 1_000);
expect(result).toMatchObject({ ok: true });
const blessedIds = new Set(useCombatStore.getState().auras
.filter((aura) => aura.definition.name.includes("Greater Blessing of Kings"))
.map((aura) => aura.targetId));
expect(blessedIds).toEqual(new Set([target.id, members[1]!.id]));
expect(blessedIds.has(PLAYER_AURA_ENTITY_ID)).toBe(false);
});
it("heals at most three party actors from Divine Storm damage", () => {
const members = usePartyStore.getState().members;
usePartyStore.setState((party) => ({
members: party.members.map((member) => ({ ...member, health: 1 })),
}));
members.forEach((member, index) => updatePartyRuntimePosition(member.id, index + 1, 0, 0));
useCombatStore.setState((state) => ({ health: Math.max(1, Math.round(state.maxHealth * 0.1)) }));
const targetId = "paladin-divine-storm-target";
const nearbyTargetId = "paladin-divine-storm-nearby-target";
const distantTargetId = "paladin-divine-storm-distant-target";
useCombatStore.getState().registerMob(targetId, { name: "Target", maxHealth: 100_000 });
useCombatStore.getState().registerMob(nearbyTargetId, { name: "Nearby Target", maxHealth: 100_000 });
useCombatStore.getState().registerMob(distantTargetId, { name: "Distant Target", maxHealth: 100_000 });
setMobPosition(targetId, [4, 0, 0]);
setMobPosition(nearbyTargetId, [7, 0, 0]);
setMobPosition(distantTargetId, [13, 0, 0]);
useCombatStore.setState((state) => ({ selectedTargetId: targetId }));
const divineStorm = unlockTalentAbility("Divine Storm");
const result = useCombatStore.getState().castAbility(divineStorm, { targetActorId: targetId, now: 1_000 });
expect(result, JSON.stringify(result)).toMatchObject({ ok: true });
expect(result.affectedMobIds).toEqual(expect.arrayContaining([targetId, nearbyTargetId]));
expect(result.affectedMobIds).not.toContain(distantTargetId);
const healEvents = useCombatStore.getState().combatEvents.filter((event) => (
event.kind === "healing" && event.abilityId === divineStorm && event.effectiveAmount > 0
));
expect(healEvents.length).toBeGreaterThan(0);
expect(healEvents.length).toBeLessThanOrEqual(3);
expect(healEvents.every((event) => event.rawAmount > 0)).toBe(true);
});
it("transfers direct healing to the active Beacon of Light target", () => {
const member = usePartyStore.getState().members[0]!;
updatePartyRuntimePosition(member.id, 4, 0, 0);
usePartyStore.setState((party) => ({
members: party.members.map((candidate) => candidate.id === member.id
? { ...candidate, health: 100 }
: candidate),
}));
usePartyStore.getState().selectMember(member.id);
const beaconResult = useCombatStore.getState().castAbility(unlockTalentAbility("Beacon of Light"), undefined, 1_000);
expect(beaconResult).toMatchObject({ ok: true });
const beaconCast = useCombatStore.getState().activeCast;
if (beaconCast) useCombatStore.getState().tick(0.1, beaconCast.completesAt);
expect(useCombatStore.getState().auras.some((aura) => (
aura.targetId === member.id && aura.definition.utilityTags?.includes("beacon-of-light")
))).toBe(true);
useCombatStore.getState().damagePlayer(5_000);
const beforeTransfer = usePartyStore.getState().members.find((candidate) => candidate.id === member.id)!.health;
const heal = useCombatStore.getState().castAbility(abilityId("Flash of Light"), undefined, 3_000);
expect(heal).toMatchObject({ ok: true });
const healCast = useCombatStore.getState().activeCast;
if (healCast) useCombatStore.getState().tick(0.1, healCast.completesAt);
const afterTransfer = usePartyStore.getState().members.find((candidate) => candidate.id === member.id)!.health;
expect(afterTransfer).toBeGreaterThan(beforeTransfer);
expect(useCombatStore.getState().auras.some((aura) => aura.targetId === member.id
&& aura.definition.utilityTags?.includes("beacon-of-light"))).toBe(true);
});
it("limits Divine Sacrifice to the remaining portion of its shared damage cap", () => {
const member = usePartyStore.getState().members[0]!;
updatePartyRuntimePosition(member.id, 0, 0, 0);
usePartyStore.setState((party) => ({
members: party.members.map((candidate) => candidate.id === member.id
? { ...candidate, health: candidate.maxHealth }
: candidate),
}));
usePartyStore.getState().selectMember(member.id);
const sacrifice = unlockTalentAbility("Divine Sacrifice");
expect(useCombatStore.getState().castAbility(sacrifice, undefined, 1_000).ok).toBe(true);
const cap = Math.round(useCombatStore.getState().maxHealth * 1.5);
const anchor = useCombatStore.getState().auras.find((aura) => (
aura.targetId === PLAYER_AURA_ENTITY_ID
&& aura.definition.utilityTags?.includes("wow335-paladin-divine-sacrifice")
));
expect(anchor?.definition.redirectDamageCapPercent).toBe(1.5);
expect(useCombatStore.getState().auras.some((aura) => (
aura.targetId === member.id && aura.definition.utilityTags?.includes("wow335-paladin-divine-sacrifice")
))).toBe(true);
useCombatStore.setState((state) => ({
auras: state.auras.map((aura) => aura.instanceId === anchor!.instanceId
? { ...aura, redirectedDamage: cap - 20 }
: aura),
}));
const mobId = "paladin-divine-sacrifice-partial";
useCombatStore.getState().registerMob(mobId, { name: "Cap test attacker", maxHealth: 100_000 });
setMobPosition(mobId, [2, 0, 0]);
useCombatStore.getState().damageMob(mobId, 1, 1_000, { actorId: member.id, role: "tank" });
useCombatStore.setState((state) => ({
mobs: { ...state.mobs, [mobId]: { ...state.mobs[mobId]!, attackDamage: 1_000 } },
}));
const playerBefore = useCombatStore.getState().health;
const memberBefore = usePartyStore.getState().members.find((candidate) => candidate.id === member.id)!.health;
useCombatStore.getState().advanceMobCombat(1_450);
useCombatStore.getState().advanceMobCombat(3_350);
expect(playerBefore - useCombatStore.getState().health).toBe(20);
expect(usePartyStore.getState().members.find((candidate) => candidate.id === member.id)!.health).toBeLessThan(memberBefore);
expect(useCombatStore.getState().auras.find((aura) => aura.instanceId === anchor!.instanceId)?.redirectedDamage).toBe(cap);
});
it("stops Divine Sacrifice redirects after the shared damage cap is exhausted", () => {
const member = usePartyStore.getState().members[0]!;
updatePartyRuntimePosition(member.id, 0, 0, 0);
usePartyStore.setState((party) => ({
members: party.members.map((candidate) => candidate.id === member.id
? { ...candidate, health: candidate.maxHealth }
: candidate),
}));
usePartyStore.getState().selectMember(member.id);
const sacrifice = unlockTalentAbility("Divine Sacrifice");
expect(useCombatStore.getState().castAbility(sacrifice, undefined, 1_000).ok).toBe(true);
const anchor = useCombatStore.getState().auras.find((aura) => (
aura.targetId === PLAYER_AURA_ENTITY_ID
&& aura.definition.utilityTags?.includes("wow335-paladin-divine-sacrifice")
));
expect(anchor).toBeDefined();
const cap = Math.round(useCombatStore.getState().maxHealth * 1.5);
useCombatStore.setState((state) => ({
auras: state.auras.map((aura) => aura.instanceId === anchor!.instanceId
? { ...aura, redirectedDamage: cap }
: aura),
}));
const mobId = "paladin-divine-sacrifice-exhausted";
useCombatStore.getState().registerMob(mobId, { name: "Cap test attacker", maxHealth: 100_000 });
setMobPosition(mobId, [2, 0, 0]);
useCombatStore.getState().damageMob(mobId, 1, 1_000, { actorId: member.id, role: "tank" });
useCombatStore.setState((state) => ({
mobs: { ...state.mobs, [mobId]: { ...state.mobs[mobId]!, attackDamage: 1_000 } },
}));
const playerBefore = useCombatStore.getState().health;
const memberBefore = usePartyStore.getState().members.find((candidate) => candidate.id === member.id)!.health;
useCombatStore.getState().advanceMobCombat(1_450);
const resolvedDamage = useCombatStore.getState().advanceMobCombat(3_350);
expect(resolvedDamage, JSON.stringify(useCombatStore.getState().mobs[mobId])).toBeGreaterThan(0);
expect(useCombatStore.getState().health).toBe(playerBefore);
expect(usePartyStore.getState().members.find((candidate) => candidate.id === member.id)!.health).toBeLessThan(memberBefore);
expect(useCombatStore.getState().auras.find((aura) => aura.instanceId === anchor!.instanceId)?.redirectedDamage).toBe(cap);
});
it("does not redirect damage from an attack that targets the Paladin", () => {
const sacrifice = unlockTalentAbility("Divine Sacrifice");
expect(useCombatStore.getState().castAbility(sacrifice, undefined, 1_000).ok).toBe(true);
const anchor = useCombatStore.getState().auras.find((aura) => (
aura.targetId === PLAYER_AURA_ENTITY_ID
&& aura.definition.utilityTags?.includes("wow335-paladin-divine-sacrifice")
));
expect(anchor).toBeDefined();
const mobId = "paladin-divine-sacrifice-self-hit";
useCombatStore.getState().registerMob(mobId, { name: "Self hit attacker", maxHealth: 100_000 });
setMobPosition(mobId, [2, 0, 0]);
useCombatStore.getState().damageMob(mobId, 1, 1_000);
useCombatStore.setState((state) => ({
mobs: { ...state.mobs, [mobId]: { ...state.mobs[mobId]!, attackDamage: 1_000 } },
}));
const playerBefore = useCombatStore.getState().health;
expect(useCombatStore.getState().advanceMobCombat(1_450)).toBeGreaterThan(0);
expect(useCombatStore.getState().health).toBeLessThan(playerBefore);
expect(useCombatStore.getState().auras.find((aura) => aura.instanceId === anchor!.instanceId)?.redirectedDamage).toBe(0);
});
it("makes the next eligible Divine Favor heal critical and consumes its aura", () => {
const maxHealth = useCombatStore.getState().maxHealth;
useCombatStore.getState().damagePlayer(Math.round(maxHealth * 0.4));
const activate = useCombatStore.getState().castAbility(unlockTalentAbility("Divine Favor"), undefined, 1_000);
expect(activate).toMatchObject({ ok: true });
expect(useCombatStore.getState().auras.some((aura) => (
aura.definition.utilityTags?.includes("wow335-divine-favor-next-holy-heal")
))).toBe(true);
const flashOfLight = abilityId("Flash of Light");
const heal = useCombatStore.getState().castAbility(flashOfLight, undefined, 3_000);
expect(heal).toMatchObject({ ok: true });
const activeCast = useCombatStore.getState().activeCast;
if (activeCast) useCombatStore.getState().tick(0.1, activeCast.completesAt);
expect(useCombatStore.getState().combatEvents.some((event) => (
event.kind === "healing" && event.abilityId === flashOfLight && event.critical
))).toBe(true);
expect(useCombatStore.getState().auras.some((aura) => (
aura.definition.utilityTags?.includes("wow335-divine-favor-next-holy-heal")
))).toBe(false);
});
});