111 lines
4.8 KiB
TypeScript
111 lines
4.8 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { abilityById } from "./abilityCatalog";
|
|
import { channelAmountEffects, channelPulseCount } from "./combatChannelAmounts";
|
|
import { useCombatStore } from "./combatStore";
|
|
import { clearPartyRuntimeRegistry, registerPartyRuntimePosition } from "./partyRuntimeRegistry";
|
|
import { usePartyStore } from "./partyStore";
|
|
import { romEffectsForCast } from "./romCombatMechanics";
|
|
|
|
const prayer = abilityById("rom-priest-rom-warlock-elite-25")!;
|
|
|
|
function prayerEffects(targetHealthPercent: number) {
|
|
return romEffectsForCast(prayer, {
|
|
sealStacks: 0,
|
|
bleedCount: 0,
|
|
charged: false,
|
|
mainHandDagger: false,
|
|
casterMaxHealth: 1_000,
|
|
targetHealthPercent: 1,
|
|
friendlyTargetHealthPercent: targetHealthPercent,
|
|
});
|
|
}
|
|
|
|
describe("RoM Priest/Warlock Prayer of Tribulation", () => {
|
|
it("channels four source-authored friendly heals and fills missing timing from CoA", () => {
|
|
expect(prayer).toMatchObject({
|
|
dbcSpellId: 498770,
|
|
target: "friendly",
|
|
castMode: "channel",
|
|
castTimeMs: 1_500,
|
|
cooldownMs: 5_000,
|
|
channelPulses: 4,
|
|
});
|
|
const heals = prayer.effects.filter((effect) => effect.kind === "heal");
|
|
expect(heals).toHaveLength(4);
|
|
expect(prayer.effects.some((effect) => effect.kind === "damage")).toBe(false);
|
|
expect(channelPulseCount(prayer)).toBe(4);
|
|
expect(channelAmountEffects(prayer, 4).filter(({ effect }) => effect.kind === "heal"))
|
|
.toEqual(heals.map((effect) => ({ effect, multiplier: 0.25 })));
|
|
});
|
|
|
|
it("applies one +2% defense stack above 40%, two below, and none at 40%", () => {
|
|
const buffStacks = (effects: ReturnType<typeof prayerEffects>) => effects.filter((effect) => (
|
|
effect.kind === "apply-aura" && effect.aura.modifiers?.some((modifier) => (
|
|
modifier.kind === "stat" && modifier.stat === "armor"
|
|
))
|
|
));
|
|
expect(buffStacks(prayerEffects(0.41))).toHaveLength(1);
|
|
expect(buffStacks(prayerEffects(0.4))).toHaveLength(0);
|
|
expect(buffStacks(prayerEffects(0.39))).toHaveLength(2);
|
|
for (const effect of buffStacks(prayerEffects(0.39))) {
|
|
expect(effect).toMatchObject({
|
|
kind: "apply-aura",
|
|
aura: { durationMs: 10_000, maxStacks: 8, stackBehavior: "add" },
|
|
});
|
|
if (effect.kind === "apply-aura") {
|
|
expect(effect.aura.modifiers?.some((modifier) => (
|
|
modifier.kind === "stat" && modifier.stat === "armor" && modifier.value === 0.02 && modifier.perStack
|
|
))).toBe(true);
|
|
expect(effect.aura.modifiers?.some((modifier) => (
|
|
modifier.kind === "stat" && modifier.stat === "magical-defense" && modifier.value === 0.02 && modifier.perStack
|
|
))).toBe(true);
|
|
}
|
|
}
|
|
});
|
|
|
|
it("heals the selected ally over four pulses and applies two stacks below 40%", () => {
|
|
clearPartyRuntimeRegistry();
|
|
useCombatStore.getState().initializeCharacter({
|
|
classId: "rom-priest",
|
|
secondaryClassId: "rom-warlock",
|
|
raceId: "rom-dwarf",
|
|
level: 30,
|
|
role: "healer",
|
|
});
|
|
usePartyStore.getState().initializeParty("rom-priest-warlock-prayer", 30, useCombatStore.getState().health, "healer", 2, "rom");
|
|
const member = usePartyStore.getState().members[0]!;
|
|
usePartyStore.getState().damageMember(member.id, member.maxHealth - 1);
|
|
// Keep the target below 40% through the four small heals so this cast
|
|
// exercises the tooltip's low-health double-stack branch at resolve time.
|
|
usePartyStore.setState((state) => ({
|
|
members: state.members.map((partyMember) => partyMember.id === member.id
|
|
? { ...partyMember, maxHealth: 10_000 }
|
|
: partyMember),
|
|
}));
|
|
registerPartyRuntimePosition(member.id, [3, 0, 0]);
|
|
usePartyStore.getState().selectMember(member.id);
|
|
const healthAtCast = usePartyStore.getState().members[0]!.health;
|
|
|
|
expect(useCombatStore.getState().castAbility(prayer.id, undefined, 1_000)).toMatchObject({ ok: true });
|
|
const healthByPulse: number[] = [];
|
|
let previousAt = 1_000;
|
|
for (const now of [1_375, 1_750, 2_125, 2_500]) {
|
|
useCombatStore.getState().tick((now - previousAt) / 1_000, now);
|
|
healthByPulse.push(usePartyStore.getState().members[0]!.health);
|
|
previousAt = now;
|
|
}
|
|
expect(healthByPulse[0]).toBeGreaterThan(healthAtCast);
|
|
expect(healthByPulse[1]).toBeGreaterThan(healthByPulse[0]!);
|
|
expect(healthByPulse[2]).toBeGreaterThan(healthByPulse[1]!);
|
|
expect(healthByPulse[3]).toBeGreaterThan(healthByPulse[2]!);
|
|
|
|
const defenseAura = useCombatStore.getState().auras.find((aura) => (
|
|
aura.targetId === member.id && aura.definition.name.includes("Prayer of Tribulation")
|
|
));
|
|
expect(defenseAura?.stacks).toBe(2);
|
|
expect(defenseAura?.definition.modifiers?.some((modifier) => (
|
|
modifier.kind === "stat" && modifier.stat === "armor" && modifier.perStack
|
|
))).toBe(true);
|
|
});
|
|
});
|