Complete RoM pair elite mechanics and creature family rules

This commit is contained in:
phenom committed 2026-09-23 19:38:13 -04:00
1 parent c8b3a3d95a
commit 0206fda9e2
21 files changed
+1082 -134

No files matched your search

+153 -3
View File
@@ -5,8 +5,9 @@ import { useCombatStore } from "./combatStore";
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
import { clearPartyRuntimeRegistry, registerPartyRuntimePosition } from "./partyRuntimeRegistry";
import { usePartyStore } from "./partyStore";
import { commitPartyAbilityUse } from "./partyAbilityAi";
import { ROM_ALL_ABILITIES, ROM_ELITE_ABILITIES } from "./romAbilityCatalog";
import { romPriestKnightCastEffects } from "./romPriestKnightOverrides";
import { romPriestKnightAdditionalAbilities, romPriestKnightCastEffects } from "./romPriestKnightOverrides";
const PAIRED_SKILLS = [
["rom-priest", "rom-knight", 15, 492355, "Enhanced Grace of Life", "passive"],
@@ -58,7 +59,7 @@ describe("accepted RoM Priest/Knight and Knight/Scout pairs", () => {
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);
expect(pair, `${primary}/${secondary}`).toHaveLength(primary === "rom-priest" ? 11 : 10);
const kit = abilitiesForCharacter(primary, secondary);
for (const skill of pair) expect(kit).toContain(skill);
}
@@ -197,11 +198,58 @@ describe("accepted RoM Priest/Knight and Knight/Scout pairs", () => {
.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);
["rom-evil-shackles"], 1_000, ["rom-target-undead-demon"]);
expect(rootDuration(ordinary)).toBe(rootDuration(chains.effects)! / 2);
expect(rootDuration(undead)).toBe(rootDuration(chains.effects));
});
it("adds source 499967's extra Wave Armor heal only with Splendor Ripple", () => {
const waveArmor = ROM_ALL_ABILITIES.find((ability) => ability.dbcSpellId === 490273)!;
const ordinary = romPriestKnightCastEffects(waveArmor, waveArmor.effects, [], 1_000);
const upgraded = romPriestKnightCastEffects(waveArmor, waveArmor.effects, ["rom-splendor-ripple"], 1_000);
expect(ordinary.filter((effect) => effect.kind === "heal")).toHaveLength(0);
expect(upgraded).toEqual(expect.arrayContaining([
expect.objectContaining({ kind: "heal", basePoints: 106.25, pointsPerLevel: 21.25 }),
]));
});
it("adds source 499924's Blood Arrow healing bonus only with Blood Punishment", () => {
const bloodArrow = ROM_ALL_ABILITIES.find((ability) => ability.dbcSpellId === 490434)!;
const ordinary = romPriestKnightCastEffects(bloodArrow, bloodArrow.effects, [], 1_000);
const upgraded = romPriestKnightCastEffects(bloodArrow, bloodArrow.effects, ["rom-blood-punishment"], 1_000);
expect(ordinary.some((effect) => effect.kind === "apply-aura"
&& effect.aura.utilityTags?.includes("rom-blood-arrow-upgrade"))).toBe(false);
expect(upgraded.some((effect) => effect.kind === "apply-aura"
&& effect.aura.durationMs === 30_000
&& effect.aura.modifiers?.some((modifier) => modifier.kind === "healing"
&& modifier.direction === "taken" && modifier.value === 0.5))).toBe(true);
});
it("builds source 499966 Devotion Halo and grants conditional Light Guidance healing", () => {
const [halo] = romPriestKnightAdditionalAbilities(ROM_ALL_ABILITIES);
expect(halo).toMatchObject({
id: "rom-priest-rom-knight-elite-70-devotion-halo",
dbcSpellId: 499966,
classId: "rom-priest",
secondaryClassId: "rom-knight",
unlockLevel: 70,
radius: 20,
passive: false,
});
expect(abilitiesForCharacter("rom-priest", "rom-knight").some((ability) => ability.dbcSpellId === 499966)).toBe(true);
expect(halo!.effects.some((effect) => effect.kind === "apply-aura"
&& effect.scope === "party" && effect.aura.durationMs === 900_000
&& effect.aura.modifiers?.some((modifier) => modifier.kind === "stat"
&& modifier.stat === "wisdom" && modifier.value === 0.05))).toBe(true);
const ordinary = romPriestKnightCastEffects(halo!, halo!.effects, [], 1_000);
const guided = romPriestKnightCastEffects(halo!, halo!.effects, ["rom-light-guidance"], 1_000);
expect(ordinary.some((effect) => effect.kind === "apply-aura"
&& effect.aura.modifiers?.some((modifier) => modifier.kind === "healing"))).toBe(false);
expect(guided.some((effect) => effect.kind === "apply-aura"
&& effect.aura.modifiers?.some((modifier) => modifier.kind === "healing"
&& modifier.direction === "done" && modifier.value === 0.05))).toBe(true);
});
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",
@@ -251,6 +299,32 @@ describe("accepted RoM Priest/Knight and Knight/Scout pairs", () => {
expect(useCombatStore.getState().mobs["arrow-b"]!.forcedTarget?.actorId).toBe(PLAYER_AGGRO_ID);
});
it.each([
[30, "living", false, 0],
[30, "undead", true, 10_000],
[60, "living", true, 5_000],
[60, "demon", true, 10_000],
] as const)("uses Holy Chains against %s-level %s for the correct duration", (
level, creatureFamily, allowed, durationMs,
) => {
useCombatStore.getState().initializeCharacter({
classId: "rom-knight", secondaryClassId: "rom-scout", raceId: "rom-human", level, role: "tank",
});
useCombatStore.getState().registerMob("chains-target", {
name: "Chains Target", maxHealth: 10_000, creatureFamily,
});
setMobPosition("chains-target", [3, 0, 0]);
useCombatStore.getState().selectMob("chains-target");
const cast = useCombatStore.getState().castAbility("rom-knight-rom-scout-elite-25", undefined, 1_000);
if (!allowed) {
expect(cast).toMatchObject({ ok: false, reason: "condition-not-met" });
return;
}
expect(cast).toMatchObject({ ok: true });
const root = useCombatStore.getState().mobs["chains-target"]!.statuses.find((status) => status.kind === "root");
expect(root?.endsAt).toBe(1_000 + durationMs);
});
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",
@@ -322,6 +396,82 @@ describe("accepted RoM Priest/Knight and Knight/Scout pairs", () => {
expect(useCombatStore.getState().activeCast?.originalDurationMs).toBeGreaterThan(1_000);
});
it("Splendor Ripple adds healing when the Priest casts Wave Armor at level 60", () => {
useCombatStore.getState().initializeCharacter({
classId: "rom-priest", secondaryClassId: "rom-knight", raceId: "rom-human", level: 60, role: "healer",
});
usePartyStore.getState().initializeParty("priest-ripple", 60, 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);
const injured = usePartyStore.getState().members[0]!.health;
expect(useCombatStore.getState().castAbility("rom-priest-primary-1", undefined, 1_000))
.toMatchObject({ ok: true });
expect(usePartyStore.getState().members[0]!.health).toBeGreaterThan(injured);
});
it("casts Devotion Halo on the party and gains Light Guidance at level 70", () => {
useCombatStore.getState().initializeCharacter({
classId: "rom-priest", secondaryClassId: "rom-knight", raceId: "rom-human", level: 70, role: "healer",
});
usePartyStore.getState().initializeParty("priest-halo", 70, useCombatStore.getState().health, "healer", 2, "rom");
const member = usePartyStore.getState().members[0]!;
registerPartyRuntimePosition(member.id, [3, 0, 0]);
expect(useCombatStore.getState().castAbility("rom-priest-rom-knight-elite-70-devotion-halo", undefined, 1_000))
.toMatchObject({ ok: true });
expect(useCombatStore.getState().auras.some((aura) => aura.targetId === member.id
&& aura.definition.id === "rom-pair-devotion-halo")).toBe(true);
expect(useCombatStore.getState().auras.some((aura) => aura.targetId === member.id
&& aura.definition.id === "rom-pair-devotion-halo-mana")).toBe(true);
expect(useCombatStore.getState().auras.some((aura) => aura.targetId === PLAYER_AGGRO_ID
&& aura.definition.id === "rom-pair-devotion-halo-mana")).toBe(false);
expect(useCombatStore.getState().auras.some((aura) => aura.targetId === PLAYER_AGGRO_ID
&& aura.definition.id === "rom-pair-light-guidance-healing")).toBe(true);
});
it("reduces a companion's actual Mana spending while Devotion Halo is active", () => {
useCombatStore.getState().initializeCharacter({
classId: "rom-priest", secondaryClassId: "rom-knight", raceId: "rom-human", level: 70, role: "healer",
});
usePartyStore.getState().initializeParty("priest-halo-cost", 70, useCombatStore.getState().health, "healer", 2, "rom");
const partyMember = usePartyStore.getState().members[0]!;
registerPartyRuntimePosition(partyMember.id, [3, 0, 0]);
const member = {
...partyMember,
classId: "rom-priest" as const,
secondaryClassId: "rom-knight" as const,
resourceType: "mana" as const,
resource: 100_000,
maxResource: 100_000,
resourcePools: { mana: 100_000 },
cooldowns: {},
globalCooldownEndsAt: 0,
};
const spell = abilitiesForCharacter("rom-priest", "rom-knight")
.find((ability) => !ability.passive && ability.unlockLevel <= 70
&& ability.cost.resource === "mana" && ability.cost.amount >= 10)!;
const ordinary = commitPartyAbilityUse(member, spell, 2_000)!;
expect(ordinary).not.toBeNull();
expect(useCombatStore.getState().castAbility("rom-priest-rom-knight-elite-70-devotion-halo", undefined, 1_000))
.toMatchObject({ ok: true });
const discounted = commitPartyAbilityUse(member, spell, 2_000)!;
expect(discounted).not.toBeNull();
expect(discounted.resourcePools.mana).toBeGreaterThan(ordinary.resourcePools.mana!);
});
it("adds Blood Punishment's healing bonus during Knight/Scout Blood Arrow", () => {
useCombatStore.getState().initializeCharacter({
classId: "rom-knight", secondaryClassId: "rom-scout", raceId: "rom-human", level: 79, role: "tank",
});
expect(useCombatStore.getState().castAbility("rom-scout-general-4", undefined, 1_000))
.toMatchObject({ ok: true });
expect(useCombatStore.getState().auras.some((aura) => aura.targetId === PLAYER_AGGRO_ID
&& aura.definition.id === "rom-pair-blood-arrow-healing-received"
&& aura.definition.modifiers?.some((modifier) => modifier.kind === "healing"
&& modifier.direction === "taken" && modifier.value === 0.5))).toBe(true);
});
it("summons the Priest/Knight Light Fairy companion", () => {
useCombatStore.getState().initializeCharacter({
classId: "rom-priest", secondaryClassId: "rom-knight", raceId: "rom-human", level: 60, role: "healer",