Complete RoM pair elite mechanics and creature family rules
This commit is contained in:
1 parent
c8b3a3d95a
commit
0206fda9e2
21 files changed
+1082
-134
No files matched your search
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user