forked from phenom/healer-man
Update class mechanics, stat rulesets, and CoA reference catalogs
This commit is contained in:
1 parent
b16821e92e
commit
39458d5de6
221 files changed
+2584962
-568
No files matched your search
@@ -62,6 +62,92 @@ function registerTarget() {
|
||||
setMobPosition(TARGET_ID, [5, 0, 0]);
|
||||
}
|
||||
|
||||
function prepareWarlockPriestSupportCompanion(readySpellId: number, startingPsi: number) {
|
||||
useCombatStore.getState().initializeCharacter({ classId: "rom-priest", raceId: "rom-dwarf", level: 80 });
|
||||
usePartyStore.getState().initializeParty("rom-warlock-priest-psi-companion", 80,
|
||||
useCombatStore.getState().health, "healer", 2, "rom");
|
||||
const companion = createConfiguredPartyMember({
|
||||
name: "Warlock/Priest Psi Companion",
|
||||
classId: "rom-warlock",
|
||||
secondaryClassId: "rom-priest",
|
||||
raceId: "rom-dwarf",
|
||||
specialization: "Warlock",
|
||||
role: "healer",
|
||||
}, 80, 0, "rom-warlock-priest-psi-companion");
|
||||
const ally = createConfiguredPartyMember({
|
||||
name: "Warlock/Priest Heal Target",
|
||||
classId: "rom-warlock",
|
||||
secondaryClassId: "rom-priest",
|
||||
raceId: "rom-dwarf",
|
||||
specialization: "Warlock",
|
||||
role: "damage",
|
||||
}, 80, 1, "rom-warlock-priest-psi-companion");
|
||||
const pairAbilities = abilitiesForCharacter("rom-warlock", "rom-priest");
|
||||
const readyAbility = pairAbilities.find((candidate) => candidate.dbcSpellId === readySpellId)!;
|
||||
const cooldowns = Object.fromEntries(pairAbilities
|
||||
.filter((candidate) => candidate.id !== readyAbility.id)
|
||||
.map((candidate) => [candidate.id, 100_000]));
|
||||
usePartyStore.setState({
|
||||
command: "attack",
|
||||
members: [{
|
||||
...companion,
|
||||
cooldowns,
|
||||
resourcePools: { ...companion.resourcePools, focus: 100, psi: startingPsi },
|
||||
resource: 100,
|
||||
}, ally],
|
||||
});
|
||||
useCombatStore.getState().synchronizeActors();
|
||||
registerPartyRuntimePosition(companion.id, [1, 0, 0]);
|
||||
registerPartyRuntimePosition(ally.id, [2, 0, 0]);
|
||||
usePartyStore.getState().damageMember(ally.id, 500);
|
||||
useCombatStore.getState().damagePlayer(500);
|
||||
registerTarget();
|
||||
useCombatStore.getState().damageMob(TARGET_ID, 100, 900);
|
||||
updatePartyRuntimeTargetVisibility(companion.id, TARGET_ID, true);
|
||||
return { companion, ally, readyAbility };
|
||||
}
|
||||
|
||||
function prepareWarlockPriestFriendlyTarget() {
|
||||
usePartyStore.getState().initializeParty("rom-warlock-priest-psi-player", 80,
|
||||
useCombatStore.getState().health, "healer", 2, "rom");
|
||||
const partyMember = usePartyStore.getState().members[0]!;
|
||||
registerPartyRuntimePosition(partyMember.id, [2, 0, 0]);
|
||||
usePartyStore.getState().damageMember(partyMember.id, 500);
|
||||
useCombatStore.getState().damagePlayer(500);
|
||||
usePartyStore.getState().selectMember(partyMember.id);
|
||||
return partyMember;
|
||||
}
|
||||
|
||||
function completeWarlockCompanionFriendlyCast(
|
||||
companionId: string,
|
||||
abilityId: string,
|
||||
now: number,
|
||||
castTimeMs: number,
|
||||
targetId = PLAYER_AURA_ENTITY_ID,
|
||||
) {
|
||||
const position: [number, number, number] = [1, 0, 0];
|
||||
usePartyStore.setState((state) => ({
|
||||
members: state.members.map((member) => member.id === companionId ? {
|
||||
...member,
|
||||
activeCast: {
|
||||
abilityId,
|
||||
school: "shadow",
|
||||
mode: "cast",
|
||||
startedAt: now,
|
||||
completesAt: now + castTimeMs,
|
||||
originalDurationMs: castTimeMs,
|
||||
pushbackHits: 0,
|
||||
origin: position,
|
||||
targetId,
|
||||
},
|
||||
activeCastTargetId: targetId,
|
||||
status: "healing" as const,
|
||||
} : member),
|
||||
}));
|
||||
useCombatStore.getState().commitCompanionActors(usePartyStore.getState().members);
|
||||
advanceDungeonPartyCombat(now + castTimeMs);
|
||||
}
|
||||
|
||||
describe("RuneWaker Warlock runtime", () => {
|
||||
beforeAll(async () => {
|
||||
await preloadAbilityCatalogsForClasses(["rom-warlock"]);
|
||||
@@ -210,6 +296,98 @@ describe("RuneWaker Warlock runtime", () => {
|
||||
expect(useCombatStore.getState().resourcePools.psi).toBe(6);
|
||||
});
|
||||
|
||||
it("describes current CoA Breathing Control triggers without restoring the RuneWaker cooldown reset", () => {
|
||||
const breathingControl = ability(498634);
|
||||
expect(breathingControl.description).toContain(
|
||||
"Psychic Arrows, Warp Charge, Weakening Weave Curse, Slash, Soul Trauma, Puzzlement, Heart Collection Strike, or Surge of Malice",
|
||||
);
|
||||
expect(breathingControl.description).toContain("trigger Enraged automatically");
|
||||
expect(breathingControl.description).toContain("does not publish the proc chance, trigger timing, or extra Focus amount");
|
||||
expect(breathingControl.description).not.toContain("reset Enraged's cooldown");
|
||||
expect(breathingControl.effects.some((effect) => effect.kind === "cooldown-reset")).toBe(false);
|
||||
});
|
||||
|
||||
it("grants one capped caster Psi from an accepted Ring of Comfort cast for the exact pair", () => {
|
||||
prepareWarlock("rom-priest");
|
||||
const ringOfComfort = abilitiesForCharacter("rom-warlock", "rom-priest")
|
||||
.find((candidate) => candidate.dbcSpellId === 498641)!;
|
||||
const context = {
|
||||
sealStacks: 0,
|
||||
bleedCount: 0,
|
||||
charged: false,
|
||||
mainHandDagger: false,
|
||||
casterMaxHealth: 10_000,
|
||||
targetHealthPercent: 1,
|
||||
} as const;
|
||||
expect(romEffectsForCast(ringOfComfort, context).filter((effect) => (
|
||||
effect.kind === "resource" && effect.resource === "psi"
|
||||
))).toEqual([{ kind: "resource", resource: "psi", amount: 1, scope: "caster" }]);
|
||||
const otherPair = { ...ringOfComfort, secondaryClassId: "rom-warrior" as const };
|
||||
expect(romEffectsForCast(otherPair, context).some((effect) => (
|
||||
effect.kind === "resource" && effect.resource === "psi"
|
||||
))).toBe(false);
|
||||
|
||||
const partyMember = prepareWarlockPriestFriendlyTarget();
|
||||
useCombatStore.setState((state) => ({ cooldowns: { ...state.cooldowns, [ringOfComfort.id]: 2_000 } }));
|
||||
expect(useCombatStore.getState().castAbility(ringOfComfort.id, undefined, 1_000).ok).toBe(false);
|
||||
expect(useCombatStore.getState().resourcePools.psi).toBe(0);
|
||||
useCombatStore.setState((state) => ({ cooldowns: { ...state.cooldowns, [ringOfComfort.id]: 0 } }));
|
||||
expect(useCombatStore.getState().castAbility(ringOfComfort.id, undefined, 3_000)).toMatchObject({ ok: true });
|
||||
const healingEvents = useCombatStore.getState().combatEvents.filter((event) => event.kind === "healing"
|
||||
&& event.abilityId === ringOfComfort.id);
|
||||
expect(healingEvents.length).toBeGreaterThan(1);
|
||||
expect(useCombatStore.getState().resourcePools.psi).toBe(1);
|
||||
expect(usePartyStore.getState().members.find((candidate) => candidate.id === partyMember.id)?.resourcePools.psi ?? 0).toBe(0);
|
||||
|
||||
useCombatStore.setState((state) => ({
|
||||
resourcePools: { ...state.resourcePools, psi: 5 },
|
||||
cooldowns: { ...state.cooldowns, [ringOfComfort.id]: 0 },
|
||||
}));
|
||||
expect(useCombatStore.getState().castAbility(ringOfComfort.id, undefined, 12_000)).toMatchObject({ ok: true });
|
||||
expect(useCombatStore.getState().resourcePools.psi).toBe(6);
|
||||
});
|
||||
|
||||
it("grants one capped caster Psi from a generated Warlock/Priest Ring of Comfort cast", () => {
|
||||
const { companion, ally, readyAbility } = prepareWarlockPriestSupportCompanion(498641, 5);
|
||||
completeWarlockCompanionFriendlyCast(companion.id, readyAbility.id, 1_000, readyAbility.castTimeMs, ally.id);
|
||||
const result = usePartyStore.getState().members.find((candidate) => candidate.id === companion.id)!;
|
||||
expect(result.resourcePools.psi).toBe(6);
|
||||
expect(usePartyStore.getState().members.find((candidate) => candidate.id === ally.id)?.resourcePools.psi ?? 0).toBe(0);
|
||||
expect(useCombatStore.getState().combatEvents.filter((event) => event.kind === "resource"
|
||||
&& event.abilityId === readyAbility.id && event.targetActorId === companion.id)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("routes Mind Shield's Urgent Heal Psi to the player caster, not its selected ally", () => {
|
||||
prepareWarlock("rom-priest");
|
||||
const urgentHeal = abilitiesForCharacter("rom-warlock", "rom-priest")
|
||||
.find((candidate) => candidate.dbcSpellId === 491147)!;
|
||||
const partyMember = prepareWarlockPriestFriendlyTarget();
|
||||
const firstCast = useCombatStore.getState().castAbility(urgentHeal.id, undefined, 1_000);
|
||||
expect(firstCast).toMatchObject({ ok: true });
|
||||
finishCast(1_000);
|
||||
expect(useCombatStore.getState().resourcePools.psi).toBe(1);
|
||||
expect(usePartyStore.getState().members.find((candidate) => candidate.id === partyMember.id)?.resourcePools.psi ?? 0).toBe(0);
|
||||
|
||||
useCombatStore.setState((state) => ({
|
||||
resourcePools: { ...state.resourcePools, psi: 5 },
|
||||
cooldowns: { ...state.cooldowns, [urgentHeal.id]: 0 },
|
||||
}));
|
||||
const secondCast = useCombatStore.getState().castAbility(urgentHeal.id, undefined, 10_000);
|
||||
expect(secondCast).toMatchObject({ ok: true });
|
||||
finishCast(10_000);
|
||||
expect(useCombatStore.getState().resourcePools.psi).toBe(6);
|
||||
});
|
||||
|
||||
it("routes Mind Shield's companion Urgent Heal Psi to its Warlock/Priest caster", () => {
|
||||
const { companion, ally, readyAbility } = prepareWarlockPriestSupportCompanion(491147, 5);
|
||||
completeWarlockCompanionFriendlyCast(companion.id, readyAbility.id, 1_000, readyAbility.castTimeMs, ally.id);
|
||||
const result = usePartyStore.getState().members.find((candidate) => candidate.id === companion.id)!;
|
||||
expect(result.resourcePools.psi).toBe(6);
|
||||
expect(usePartyStore.getState().members.find((candidate) => candidate.id === ally.id)?.resourcePools.psi ?? 0).toBe(0);
|
||||
expect(useCombatStore.getState().combatEvents.filter((event) => event.kind === "resource"
|
||||
&& event.abilityId === readyAbility.id && event.targetActorId === companion.id)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("uses current Warlock/Priest passive effects and pair upgrades", () => {
|
||||
const context = {
|
||||
sealStacks: 0,
|
||||
@@ -277,7 +455,7 @@ describe("RuneWaker Warlock runtime", () => {
|
||||
}).some((effect) => effect.kind === "damage")).toBe(true);
|
||||
|
||||
expect(romEffectsForCast(ability(491147), context)).toContainEqual({
|
||||
kind: "resource", resource: "psi", amount: 1,
|
||||
kind: "resource", resource: "psi", amount: 1, scope: "caster",
|
||||
});
|
||||
expect(ability(498633).description).toContain("12% Wisdom for 20 seconds");
|
||||
expect(ability(498648).description).toContain("Ring of Comfort's reset chance is not specified");
|
||||
|
||||
Reference in new issue
Block a user