Validate RoM primary five hybrid stats and pair mechanics
This commit is contained in:
1 parent
8e95480d4f
commit
c8b3a3d95a
18 files changed
+2061
-48
No files matched your search
@@ -0,0 +1,353 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { abilitiesForCharacter, isAbilityUnlocked, type AbilityDefinition, type AbilityEffect } from "./abilityCatalog";
|
||||
import { PLAYER_AGGRO_ID } from "./aggro";
|
||||
import { useCombatStore } from "./combatStore";
|
||||
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
|
||||
import { clearPartyRuntimeRegistry, registerPartyRuntimePosition } from "./partyRuntimeRegistry";
|
||||
import { usePartyStore } from "./partyStore";
|
||||
import { ROM_ALL_ABILITIES, ROM_ELITE_ABILITIES } from "./romAbilityCatalog";
|
||||
import { romPriestKnightCastEffects } from "./romPriestKnightOverrides";
|
||||
|
||||
const PAIRED_SKILLS = [
|
||||
["rom-priest", "rom-knight", 15, 492355, "Enhanced Grace of Life", "passive"],
|
||||
["rom-priest", "rom-knight", 20, 492361, "Life Link", "passive"],
|
||||
["rom-priest", "rom-knight", 25, 491648, "Last Prayer", "elite"],
|
||||
["rom-priest", "rom-knight", 30, 491366, "Divine Incarnation", "elite"],
|
||||
["rom-priest", "rom-knight", 35, 492639, "Calm Heart", "elite"],
|
||||
["rom-priest", "rom-knight", 40, 492934, "Sacrificial Cure", "elite"],
|
||||
["rom-priest", "rom-knight", 45, 493044, "Holy Power", "elite"],
|
||||
["rom-priest", "rom-knight", 50, 493271, "Light Fairy", "elite"],
|
||||
["rom-priest", "rom-knight", 60, 499968, "Splendor Ripple", "passive"],
|
||||
["rom-priest", "rom-knight", 70, 499957, "Light Guidance", "passive"],
|
||||
["rom-knight", "rom-scout", 15, 491616, "Arrow of Vengeance", "elite"],
|
||||
["rom-knight", "rom-scout", 20, 491610, "Enhanced Holy Strike", "passive"],
|
||||
["rom-knight", "rom-scout", 25, 491613, "Holy Chains", "elite"],
|
||||
["rom-knight", "rom-scout", 30, 491354, "Heavenly Arrow", "elite"],
|
||||
["rom-knight", "rom-scout", 35, 490066, "Weaken Weapon Strength", "passive"],
|
||||
["rom-knight", "rom-scout", 40, 492939, "Sacred Resistance", "elite"],
|
||||
["rom-knight", "rom-scout", 45, 493046, "Sacred Protection", "elite"],
|
||||
["rom-knight", "rom-scout", 50, 493273, "Disregard Danger", "elite"],
|
||||
["rom-knight", "rom-scout", 60, 499926, "Evil Shackles", "passive"],
|
||||
["rom-knight", "rom-scout", 70, 499923, "Blood Punishment", "passive"],
|
||||
] as const;
|
||||
|
||||
function pairedSkill(sourceId: number): AbilityDefinition {
|
||||
const found = ROM_ELITE_ABILITIES.find((ability) => ability.dbcSpellId === sourceId);
|
||||
if (!found) throw new Error(`Missing paired RoM source skill ${sourceId}`);
|
||||
return found;
|
||||
}
|
||||
|
||||
function executable(effect: AbilityEffect): boolean {
|
||||
if (effect.kind === "scripted" || effect.kind === "unsupported") return false;
|
||||
if (effect.kind === "apply-aura") return Boolean(
|
||||
effect.control || effect.aura.modifiers?.length || effect.aura.absorbs?.length
|
||||
|| effect.aura.procs?.length || effect.aura.utilityTags?.length
|
||||
|| effect.aura.controlImmunities?.length || effect.aura.redirectDamagePercent,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
describe("accepted RoM Priest/Knight and Knight/Scout pairs", () => {
|
||||
beforeEach(() => {
|
||||
clearMobRuntimeRegistry();
|
||||
clearPartyRuntimeRegistry();
|
||||
useCombatStore.setState({ playerPosition: [0, 0, 0] });
|
||||
});
|
||||
|
||||
it("has exactly the twenty RuneWaker paired skills, including passive upgrades", () => {
|
||||
for (const [primary, secondary] of [["rom-priest", "rom-knight"], ["rom-knight", "rom-scout"]] as const) {
|
||||
const pair = ROM_ELITE_ABILITIES.filter((ability) => ability.classId === primary
|
||||
&& ability.secondaryClassId === secondary);
|
||||
expect(pair, `${primary}/${secondary}`).toHaveLength(10);
|
||||
const kit = abilitiesForCharacter(primary, secondary);
|
||||
for (const skill of pair) expect(kit).toContain(skill);
|
||||
}
|
||||
});
|
||||
|
||||
it.each(PAIRED_SKILLS)("maps %s/%s level %i source %i %s (%s) with the right gate", (
|
||||
primary, secondary, unlockLevel, sourceId, name, group,
|
||||
) => {
|
||||
const skill = pairedSkill(sourceId);
|
||||
expect(skill).toMatchObject({
|
||||
classId: primary, secondaryClassId: secondary, unlockLevel,
|
||||
dbcSpellId: sourceId, name, romSkillGroup: group, passive: group === "passive",
|
||||
});
|
||||
for (const level of [10, 30, 60, 79]) {
|
||||
expect(isAbilityUnlocked(skill, level), `${name} at level ${level}`)
|
||||
.toBe(level >= unlockLevel);
|
||||
}
|
||||
});
|
||||
|
||||
it.each(PAIRED_SKILLS)("gives %s/%s level %i source %i %s (%s) an executable mechanic", (
|
||||
_primary, _secondary, _unlockLevel, sourceId, name,
|
||||
) => {
|
||||
const skill = pairedSkill(sourceId);
|
||||
expect(skill.effects.some(executable), `${name} (${sourceId})`).toBe(true);
|
||||
});
|
||||
|
||||
it("retains direct and area Knight/Scout taunts", () => {
|
||||
expect(pairedSkill(491616).effects).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ kind: "taunt" }),
|
||||
]));
|
||||
expect(pairedSkill(491354).effects).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ kind: "taunt", maxTargets: expect.any(Number) }),
|
||||
]));
|
||||
});
|
||||
|
||||
it("retains Priest/Knight healing costs and offensive/defensive effects", () => {
|
||||
const sacrifice = pairedSkill(492934);
|
||||
expect(sacrifice.costs).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ resource: "health", amount: 70, percentage: true }),
|
||||
]));
|
||||
expect(sacrifice.effects).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ kind: "heal", percentMaxHealth: expect.any(Number) }),
|
||||
]));
|
||||
expect(pairedSkill(491366).effects).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ kind: "apply-aura", aura: expect.objectContaining({
|
||||
modifiers: expect.arrayContaining([expect.objectContaining({ kind: "healing", direction: "done", value: 0.15 })]),
|
||||
}) }),
|
||||
]));
|
||||
expect(pairedSkill(493271).effects).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ kind: "summon", creatureId: 102108 }),
|
||||
]));
|
||||
});
|
||||
|
||||
it("retains Knight/Scout damage and Dexterity buff on Sacred Resistance", () => {
|
||||
const skill = pairedSkill(492939);
|
||||
expect(skill.effects.some((effect) => effect.kind === "damage" && effect.coefficient > 0)).toBe(true);
|
||||
expect(skill.effects.some((effect) => effect.kind === "apply-aura"
|
||||
&& effect.aura.modifiers?.some((modifier) => modifier.kind === "stat"
|
||||
&& modifier.stat === "dexterity" && modifier.value > 0))).toBe(true);
|
||||
});
|
||||
|
||||
it("makes Calm Heart resist Fear rather than applying an empty buff", () => {
|
||||
expect(pairedSkill(492639).effects.some((effect) => effect.kind === "apply-aura"
|
||||
&& effect.aura.controlImmunities?.includes("fear"))).toBe(true);
|
||||
});
|
||||
|
||||
it("maps Sacred Protection to a real shield block bonus", () => {
|
||||
expect(pairedSkill(493046).effects.some((effect) => effect.kind === "apply-aura"
|
||||
&& effect.aura.modifiers?.some((modifier) => modifier.kind === "stat"
|
||||
&& modifier.stat === "block-chance" && modifier.value > 0))).toBe(true);
|
||||
});
|
||||
|
||||
it("redirects damage with Disregard Danger", () => {
|
||||
expect(pairedSkill(493273).effects.some((effect) => effect.kind === "apply-aura"
|
||||
&& (effect.aura.redirectDamagePercent ?? 0) > 0)).toBe(true);
|
||||
});
|
||||
|
||||
it("applies only the advertised root from Holy Chains", () => {
|
||||
const controls = pairedSkill(491613).effects.flatMap((effect) => effect.kind === "apply-aura"
|
||||
&& effect.control ? [effect.control.kind] : []);
|
||||
expect(controls).toEqual(["root"]);
|
||||
});
|
||||
|
||||
it("increases healing received through Blood Punishment", () => {
|
||||
expect(pairedSkill(499923).effects.some((effect) => effect.kind === "apply-aura"
|
||||
&& effect.aura.utilityTags?.includes("rom-blood-punishment"))).toBe(true);
|
||||
expect(pairedSkill(499923).effects.some((effect) => effect.kind === "apply-aura"
|
||||
&& effect.aura.modifiers?.some((modifier) => modifier.kind === "healing"))).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps passive pair upgrades conditional on their parent casts", () => {
|
||||
for (const [sourceId, tag] of [
|
||||
[492355, "rom-enhanced-grace-of-life"],
|
||||
[492361, "rom-life-link"],
|
||||
[499968, "rom-splendor-ripple"],
|
||||
[499957, "rom-light-guidance"],
|
||||
[491610, "rom-enhanced-holy-strike"],
|
||||
[490066, "rom-weaken-weapon-strength"],
|
||||
[499926, "rom-evil-shackles"],
|
||||
[499923, "rom-blood-punishment"],
|
||||
] as const) {
|
||||
expect(pairedSkill(sourceId).effects.some((effect) => effect.kind === "apply-aura"
|
||||
&& effect.aura.utilityTags?.includes(tag)), `${sourceId} ${tag}`).toBe(true);
|
||||
}
|
||||
expect(pairedSkill(491648).effects.some((effect) => effect.kind === "apply-aura"
|
||||
&& effect.aura.utilityTags?.includes("rom-last-prayer"))).toBe(true);
|
||||
});
|
||||
|
||||
it("applies passive upgrades to Grace, Urgent Heal, Holy Strike, and Heavenly Arrow casts", () => {
|
||||
const ability = (sourceId: number) => ROM_ALL_ABILITIES.find((row) => row.dbcSpellId === sourceId)!;
|
||||
const grace = ability(490290);
|
||||
const enhancedGrace = romPriestKnightCastEffects(grace, grace.effects, ["rom-enhanced-grace-of-life"], 1_000);
|
||||
expect(enhancedGrace.some((effect) => effect.kind === "apply-aura"
|
||||
&& effect.aura.modifiers?.some((modifier) => modifier.kind === "resource"
|
||||
&& modifier.resource === "mana" && modifier.property === "maximum"))).toBe(true);
|
||||
const urgent = ability(491147);
|
||||
const linked = romPriestKnightCastEffects(urgent, urgent.effects, ["rom-life-link"], 1_000);
|
||||
expect(linked.filter((effect) => effect.kind === "heal" && effect.recipient === "caster")).toHaveLength(1);
|
||||
const holyStrike = ability(490148);
|
||||
const enhancedStrike = romPriestKnightCastEffects(holyStrike, holyStrike.effects, ["rom-enhanced-holy-strike"], 1_000);
|
||||
const originalDamage = holyStrike.effects.find((effect) => effect.kind === "damage");
|
||||
const enhancedDamage = enhancedStrike.find((effect) => effect.kind === "damage");
|
||||
expect(enhancedDamage?.kind === "damage" && originalDamage?.kind === "damage"
|
||||
&& enhancedDamage.coefficient > originalDamage.coefficient).toBe(true);
|
||||
const arrow = ability(491354);
|
||||
const weakened = romPriestKnightCastEffects(arrow, arrow.effects, ["rom-weaken-weapon-strength"], 1_000);
|
||||
expect(weakened.some((effect) => effect.kind === "apply-aura"
|
||||
&& effect.aura.modifiers?.some((modifier) => modifier.kind === "damage"
|
||||
&& modifier.direction === "dealt" && modifier.school === "physical" && modifier.value < 0))).toBe(true);
|
||||
});
|
||||
|
||||
it("halves Holy Chains duration against ordinary targets after Evil Shackles unlocks", () => {
|
||||
const chains = pairedSkill(491613);
|
||||
const rootDuration = (effects: readonly AbilityEffect[]) => effects
|
||||
.filter((effect): effect is Extract<AbilityEffect, { kind: "apply-aura" }> => effect.kind === "apply-aura")
|
||||
.find((effect) => effect.control?.kind === "root")?.aura.durationMs;
|
||||
const ordinary = romPriestKnightCastEffects(chains, chains.effects, ["rom-evil-shackles"], 1_000);
|
||||
const undead = romPriestKnightCastEffects(chains, chains.effects,
|
||||
["rom-evil-shackles", "rom-target-undead-demon"], 1_000);
|
||||
expect(rootDuration(ordinary)).toBe(rootDuration(chains.effects)! / 2);
|
||||
expect(rootDuration(undead)).toBe(rootDuration(chains.effects));
|
||||
});
|
||||
|
||||
it("heals multiple nearby allies with Priest Group Heal", () => {
|
||||
useCombatStore.getState().initializeCharacter({
|
||||
classId: "rom-priest", secondaryClassId: "rom-knight", raceId: "rom-human", level: 30, role: "healer",
|
||||
});
|
||||
usePartyStore.getState().initializeParty("priest-group-heal", 30, useCombatStore.getState().health, "healer", 3, "rom");
|
||||
const members = usePartyStore.getState().members.slice(0, 2);
|
||||
expect(members).toHaveLength(2);
|
||||
members.forEach((member, index) => {
|
||||
usePartyStore.getState().damageMember(member.id, Math.max(100, Math.floor(member.maxHealth * 0.4)));
|
||||
registerPartyRuntimePosition(member.id, [3 + index * 2, 0, 0]);
|
||||
});
|
||||
const injured = members.map((member) => usePartyStore.getState().members.find((entry) => entry.id === member.id)!.health);
|
||||
expect(useCombatStore.getState().castAbility("rom-priest-primary-8", undefined, 1_000)).toMatchObject({ ok: true });
|
||||
useCombatStore.getState().tick(0.1, 4_100);
|
||||
members.forEach((member, index) => {
|
||||
const healed = usePartyStore.getState().members.find((entry) => entry.id === member.id)!.health;
|
||||
expect(healed, `Group Heal recipient ${index + 1}`).toBeGreaterThan(injured[index]!);
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves Soul Source's 90-source-unit all-teammate heal", () => {
|
||||
const soulSource = ROM_ALL_ABILITIES.find((ability) => ability.dbcSpellId === 490288)!;
|
||||
expect(soulSource.radius).toBe(9);
|
||||
expect(soulSource.effects.some((effect) => effect.kind === "heal"
|
||||
&& effect.scope === "party")).toBe(true);
|
||||
});
|
||||
|
||||
it("gives Holy Shield physical immunity to the party", () => {
|
||||
const holyShield = ROM_ALL_ABILITIES.find((ability) => ability.dbcSpellId === 490495)!;
|
||||
expect(holyShield.effects.some((effect) => effect.kind === "apply-aura"
|
||||
&& effect.scope === "party" && effect.aura.modifiers?.some((modifier) => modifier.kind === "damage"
|
||||
&& modifier.direction === "taken" && modifier.school === "physical" && modifier.value <= -1))).toBe(true);
|
||||
});
|
||||
|
||||
it("taunts nearby enemies with Knight/Scout Heavenly Arrow", () => {
|
||||
useCombatStore.getState().initializeCharacter({
|
||||
classId: "rom-knight", secondaryClassId: "rom-scout", raceId: "rom-human", level: 30, role: "tank",
|
||||
});
|
||||
for (const [id, x] of [["arrow-a", 3], ["arrow-b", 5]] as const) {
|
||||
useCombatStore.getState().registerMob(id, { name: id, maxHealth: 10_000 });
|
||||
setMobPosition(id, [x, 0, 0]);
|
||||
}
|
||||
useCombatStore.getState().selectMob("arrow-a");
|
||||
expect(useCombatStore.getState().castAbility("rom-knight-rom-scout-elite-30", undefined, 1_000))
|
||||
.toMatchObject({ ok: true });
|
||||
expect(useCombatStore.getState().mobs["arrow-a"]!.forcedTarget?.actorId).toBe(PLAYER_AGGRO_ID);
|
||||
expect(useCombatStore.getState().mobs["arrow-b"]!.forcedTarget?.actorId).toBe(PLAYER_AGGRO_ID);
|
||||
});
|
||||
|
||||
it("damages multiple nearby enemies with Knight Whirlwind Shield", () => {
|
||||
useCombatStore.getState().initializeCharacter({
|
||||
classId: "rom-knight", secondaryClassId: "rom-scout", raceId: "rom-human", level: 30, role: "tank",
|
||||
});
|
||||
for (const [id, x] of [["shield-a", 3], ["shield-b", 4]] as const) {
|
||||
useCombatStore.getState().registerMob(id, { name: id, maxHealth: 10_000 });
|
||||
setMobPosition(id, [x, 0, 0]);
|
||||
}
|
||||
useCombatStore.getState().selectMob("shield-a");
|
||||
expect(useCombatStore.getState().castAbility("rom-knight-primary-3", undefined, 1_000))
|
||||
.toMatchObject({ ok: true });
|
||||
expect(useCombatStore.getState().mobs["shield-a"]!.health).toBeLessThan(10_000);
|
||||
expect(useCombatStore.getState().mobs["shield-b"]!.health).toBeLessThan(10_000);
|
||||
});
|
||||
|
||||
it("pays Sacrificial Cure's HP cost while healing an ally", () => {
|
||||
useCombatStore.getState().initializeCharacter({
|
||||
classId: "rom-priest", secondaryClassId: "rom-knight", raceId: "rom-human", level: 60, role: "healer",
|
||||
});
|
||||
usePartyStore.getState().initializeParty("priest-sacrifice", 60, useCombatStore.getState().health, "healer", 2, "rom");
|
||||
const member = usePartyStore.getState().members[0]!;
|
||||
usePartyStore.getState().damageMember(member.id, Math.floor(member.maxHealth * 0.6));
|
||||
registerPartyRuntimePosition(member.id, [3, 0, 0]);
|
||||
usePartyStore.getState().selectMember(member.id);
|
||||
const casterHealth = useCombatStore.getState().health;
|
||||
const allyHealth = usePartyStore.getState().members[0]!.health;
|
||||
expect(useCombatStore.getState().castAbility("rom-priest-rom-knight-elite-40", undefined, 1_000))
|
||||
.toMatchObject({ ok: true });
|
||||
expect(useCombatStore.getState().health).toBeLessThan(casterHealth);
|
||||
expect(usePartyStore.getState().members[0]!.health).toBeGreaterThan(allyHealth);
|
||||
});
|
||||
|
||||
it("Life Link lets Urgent Heal also heal the Priest", () => {
|
||||
useCombatStore.getState().initializeCharacter({
|
||||
classId: "rom-priest", secondaryClassId: "rom-knight", raceId: "rom-human", level: 30, role: "healer",
|
||||
});
|
||||
usePartyStore.getState().initializeParty("priest-life-link", 30, useCombatStore.getState().health, "healer", 2, "rom");
|
||||
const member = usePartyStore.getState().members[0]!;
|
||||
useCombatStore.getState().damagePlayer(Math.floor(useCombatStore.getState().maxHealth * 0.5));
|
||||
usePartyStore.getState().damageMember(member.id, Math.floor(member.maxHealth * 0.5));
|
||||
registerPartyRuntimePosition(member.id, [3, 0, 0]);
|
||||
usePartyStore.getState().selectMember(member.id);
|
||||
const casterHealth = useCombatStore.getState().health;
|
||||
const allyHealth = usePartyStore.getState().members[0]!.health;
|
||||
expect(useCombatStore.getState().castAbility("rom-priest-general-0", undefined, 1_000))
|
||||
.toMatchObject({ ok: true });
|
||||
useCombatStore.getState().tick(0.1, 2_100);
|
||||
expect(useCombatStore.getState().health).toBeGreaterThan(casterHealth);
|
||||
expect(usePartyStore.getState().members[0]!.health).toBeGreaterThan(allyHealth);
|
||||
});
|
||||
|
||||
it("Last Prayer shortens only the next cast to one second", () => {
|
||||
useCombatStore.getState().initializeCharacter({
|
||||
classId: "rom-priest", secondaryClassId: "rom-knight", raceId: "rom-human", level: 30, role: "healer",
|
||||
});
|
||||
usePartyStore.getState().initializeParty("priest-last-prayer", 30, useCombatStore.getState().health, "healer", 2, "rom");
|
||||
const member = usePartyStore.getState().members[0]!;
|
||||
usePartyStore.getState().damageMember(member.id, Math.floor(member.maxHealth * 0.5));
|
||||
registerPartyRuntimePosition(member.id, [3, 0, 0]);
|
||||
usePartyStore.getState().selectMember(member.id);
|
||||
expect(useCombatStore.getState().castAbility("rom-priest-rom-knight-elite-25", undefined, 1_000))
|
||||
.toMatchObject({ ok: true });
|
||||
expect(useCombatStore.getState().castAbility("rom-priest-primary-2", undefined, 2_600))
|
||||
.toMatchObject({ ok: true });
|
||||
expect(useCombatStore.getState().activeCast?.originalDurationMs).toBeLessThanOrEqual(1_000);
|
||||
useCombatStore.getState().tick(0.1, 3_700);
|
||||
expect(useCombatStore.getState().castAbility("rom-priest-primary-2", undefined, 4_300))
|
||||
.toMatchObject({ ok: true });
|
||||
expect(useCombatStore.getState().activeCast?.originalDurationMs).toBeGreaterThan(1_000);
|
||||
});
|
||||
|
||||
it("summons the Priest/Knight Light Fairy companion", () => {
|
||||
useCombatStore.getState().initializeCharacter({
|
||||
classId: "rom-priest", secondaryClassId: "rom-knight", raceId: "rom-human", level: 60, role: "healer",
|
||||
});
|
||||
expect(useCombatStore.getState().castAbility("rom-priest-rom-knight-elite-50", undefined, 1_000))
|
||||
.toMatchObject({ ok: true });
|
||||
useCombatStore.getState().tick(0.1, 7_100);
|
||||
expect(useCombatStore.getState().summons.some((summon) => summon.creatureId === 102108)).toBe(true);
|
||||
useCombatStore.getState().tick(1, 8_100);
|
||||
expect(useCombatStore.getState().summons.some((summon) => summon.creatureId === 102108)).toBe(true);
|
||||
});
|
||||
|
||||
it("places Disregard Danger's redirect on a teammate and Parry on the Knight", () => {
|
||||
useCombatStore.getState().initializeCharacter({
|
||||
classId: "rom-knight", secondaryClassId: "rom-scout", raceId: "rom-human", level: 60, role: "tank",
|
||||
});
|
||||
usePartyStore.getState().initializeParty("knight-redirect", 60, useCombatStore.getState().health, "tank", 2, "rom");
|
||||
const member = usePartyStore.getState().members[0]!;
|
||||
registerPartyRuntimePosition(member.id, [3, 0, 0]);
|
||||
usePartyStore.getState().selectMember(member.id);
|
||||
expect(useCombatStore.getState().castAbility("rom-knight-rom-scout-elite-50", undefined, 1_000))
|
||||
.toMatchObject({ ok: true });
|
||||
expect(useCombatStore.getState().auras.some((aura) => aura.targetId === member.id
|
||||
&& (aura.definition.redirectDamagePercent ?? 0) > 0)).toBe(true);
|
||||
expect(useCombatStore.getState().auras.some((aura) => aura.targetId === PLAYER_AGGRO_ID
|
||||
&& aura.definition.modifiers?.some((modifier) => modifier.kind === "stat"
|
||||
&& modifier.stat === "parry"))).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user