Release Healer Man 0.1.59

This commit is contained in:
phenom committed 2026-09-27 10:16:42 -04:00
1 parent 11934a3979
commit ab1434a3b8
44 files changed
+1242 -103

No files matched your search

+96 -4
View File
@@ -1940,32 +1940,124 @@ describe("combat store", () => {
&& aura.definition.name.startsWith("Rupture"))?.expiresAt).toBe(19_000);
});
it("lets Penance damage an enemy and continue pulsing", () => {
it("channels Penance damage immediately and at one-second intervals without healing its caster", () => {
useCombatStore.getState().initializeCharacter({ classId: "priest", level: 80 });
unlockReadySpell("wow335-priest-penance");
registerTarget("penance-target", [8, 0, 0], 100_000, 0);
useCombatStore.getState().damagePlayer(500);
const casterHealth = useCombatStore.getState().health;
expect(useCombatStore.getState().castAbility("wow335-priest-penance", [0, 0, 0], 1_000).ok).toBe(true);
expect(useCombatStore.getState().activeCast).toMatchObject({ mode: "channel", totalTicks: 3,
completesAt: 3_000, nextTickAt: 1_000 });
expect(useCombatStore.getState().cooldowns["wow335-priest-penance"]).toBeGreaterThan(1_000);
expect(useCombatStore.getState().mobs["penance-target"].health).toBe(100_000);
useCombatStore.getState().tick(0, 1_000);
const first = useCombatStore.getState().mobs["penance-target"].health;
expect(first).toBeLessThan(100_000);
expect(useCombatStore.getState().health).toBe(casterHealth);
useCombatStore.getState().tick(0, 1_999);
expect(useCombatStore.getState().mobs["penance-target"].health).toBe(first);
useCombatStore.getState().tick(0, 2_000);
expect(useCombatStore.getState().mobs["penance-target"].health).toBeLessThan(first);
useCombatStore.getState().tick(0, 3_000);
expect(useCombatStore.getState().effects.some((effect) => effect.sourceAbilityId === "wow335-priest-penance")).toBe(false);
expect(useCombatStore.getState().activeCast).toBeNull();
const after = useCombatStore.getState().mobs["penance-target"].health;
useCombatStore.getState().tick(0, 4_000);
expect(useCombatStore.getState().mobs["penance-target"].health).toBe(after);
});
it("lets Penance heal a friendly target immediately and twice afterward", () => {
it("channels Penance healing and stops future pulses when interrupted", () => {
useCombatStore.getState().initializeCharacter({ classId: "priest", level: 80 });
unlockReadySpell("wow335-priest-penance");
usePartyStore.getState().selectSelf();
useCombatStore.getState().damagePlayer(2_000);
const before = useCombatStore.getState().health;
expect(useCombatStore.getState().castAbility("wow335-priest-penance", [0, 0, 0], 1_000).ok).toBe(true);
useCombatStore.getState().tick(0, 1_000);
const first = useCombatStore.getState().health;
expect(first).toBeGreaterThan(before);
useCombatStore.getState().tick(0, 2_000);
expect(useCombatStore.getState().health).toBeGreaterThan(first);
const interruptedAt = useCombatStore.getState().health;
expect(useCombatStore.getState().cancelCast()).toBe(true);
useCombatStore.getState().tick(0, 3_000);
expect(useCombatStore.getState().effects.some((effect) => effect.sourceAbilityId === "wow335-priest-penance")).toBe(false);
expect(useCombatStore.getState().health).toBe(interruptedAt);
expect(useCombatStore.getState().activeCast).toBeNull();
});
it("channels ground-area Volley over its selected point without a target or post-channel damage", () => {
useCombatStore.getState().initializeCharacter({ classId: "hunter", level: 80 });
registerTarget("volley-a", [10, 0, 0], 100_000, 0);
registerTarget("volley-b", [12, 0, 0], 100_000, 0);
registerTarget("volley-outside", [25, 0, 0], 100_000, 0);
expect(useCombatStore.getState().castAbility("wow335-hunter-volley", {
now: 1_000, origin: [0, 0, 0], targetActorId: null, groundPosition: [10, 0, 0],
}).ok).toBe(true);
expect(useCombatStore.getState().activeCast).toMatchObject({ mode: "channel", totalTicks: 6,
nextTickAt: 2_000, completesAt: 7_000 });
useCombatStore.getState().tick(0, 2_000);
expect(useCombatStore.getState().mobs["volley-a"].health).toBeLessThan(100_000);
expect(useCombatStore.getState().mobs["volley-b"].health).toBeLessThan(100_000);
expect(useCombatStore.getState().mobs["volley-outside"].health).toBe(100_000);
useCombatStore.getState().tick(0, 7_000);
expect(useCombatStore.getState().activeCast).toBeNull();
expect(useCombatStore.getState().areaEffects.some((effect) => effect.sourceAbilityId === "wow335-hunter-volley"))
.toBe(false);
const finalHealth = useCombatStore.getState().mobs["volley-a"].health;
useCombatStore.getState().tick(0, 9_000);
expect(useCombatStore.getState().mobs["volley-a"].health).toBe(finalHealth);
});
it("applies Blizzard damage during its eight source-timed channel pulses", () => {
useCombatStore.getState().initializeCharacter({ classId: "mage", level: 80 });
registerTarget("blizzard-target", [10, 0, 0], 100_000, 0);
expect(useCombatStore.getState().castAbility("wow335-mage-blizzard", {
now: 1_000, origin: [0, 0, 0], targetActorId: null, groundPosition: [10, 0, 0],
}).ok).toBe(true);
expect(useCombatStore.getState().activeCast).toMatchObject({ mode: "channel", totalTicks: 8 });
useCombatStore.getState().tick(0, 2_000);
expect(useCombatStore.getState().mobs["blizzard-target"].health).toBeLessThan(100_000);
useCombatStore.getState().tick(0, 9_000);
expect(useCombatStore.getState().activeCast).toBeNull();
expect(useCombatStore.getState().combatEvents.filter((event) => event.kind === "damage"
&& event.abilityId === "wow335-mage-blizzard" && event.targetActorId === "blizzard-target"))
.toHaveLength(8);
expect(useCombatStore.getState().areaEffects.some((effect) => effect.sourceAbilityId === "wow335-mage-blizzard"))
.toBe(false);
expect(useCombatStore.getState().auras.some((aura) => aura.definition.name === "Blizzard"))
.toBe(false);
const finalHealth = useCombatStore.getState().mobs["blizzard-target"].health;
useCombatStore.getState().tick(0, 11_000);
expect(useCombatStore.getState().mobs["blizzard-target"].health).toBe(finalHealth);
});
it.each([
["warlock", "wow335-warlock-rain-of-fire", 4, 2_000],
["druid", "wow335-druid-hurricane", 10, 1_000],
] as const)("applies %s ground-channel damage only during its pulses", (classId, abilityId, pulses, intervalMs) => {
useCombatStore.getState().initializeCharacter({ classId, level: 80 });
expect(useCombatStore.getState().abilities.find((ability) => ability.id === abilityId))
.toMatchObject({ castMode: "channel", castTimeMs: pulses * intervalMs, channelPulses: pulses });
registerTarget("channel-target", [10, 0, 0], 100_000, 0);
expect(abilityAtLevel(useCombatStore.getState().abilities.find((ability) => ability.id === abilityId)!, 80))
.toMatchObject({ castMode: "channel", castTimeMs: pulses * intervalMs, channelPulses: pulses });
expect(useCombatStore.getState().castAbility(abilityId, {
now: 1_000, origin: [0, 0, 0], targetActorId: null, groundPosition: [10, 0, 0],
}).ok).toBe(true);
expect(useCombatStore.getState().activeCast).toMatchObject({ mode: "channel", totalTicks: pulses,
nextTickAt: 1_000 + intervalMs });
useCombatStore.getState().tick(0, 1_000 + intervalMs);
expect(useCombatStore.getState().mobs["channel-target"].health).toBeLessThan(100_000);
useCombatStore.getState().tick(0, 1_000 + pulses * intervalMs);
expect(useCombatStore.getState().activeCast).toBeNull();
expect(useCombatStore.getState().combatEvents.filter((event) => event.kind === "damage"
&& event.abilityId === abilityId && event.targetActorId === "channel-target"))
.toHaveLength(pulses);
expect(useCombatStore.getState().areaEffects.some((effect) => effect.sourceAbilityId === abilityId))
.toBe(false);
const finalHealth = useCombatStore.getState().mobs["channel-target"].health;
useCombatStore.getState().tick(0, 2_000 + pulses * intervalMs);
expect(useCombatStore.getState().mobs["channel-target"].health).toBe(finalHealth);
});
it("selects only the three lowest-health nearby Divine Hymn targets per two-second pulse", () => {