Update Healer Man server
This commit is contained in:
1 parent
35dfdaed61
commit
4fb7a0b9c8
161 files changed
+24618
-529
No files matched your search
@@ -0,0 +1,448 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { abilityAtLevel, abilitiesForClass, preloadAbilityCatalogsForClasses } from "./abilityCatalog";
|
||||
import { useCombatStore } from "./combatStore";
|
||||
import { clearMobRuntimeRegistry } from "./mobRuntimeRegistry";
|
||||
import { clearPartyRuntimeRegistry, registerPartyRuntimePosition } from "./partyRuntimeRegistry";
|
||||
import { advanceDungeonPartyCombat, resetPartyRuntimeClock } from "./partyRuntime";
|
||||
import { createConfiguredPartyMember, usePartyStore, type PartyMember } from "./partyStore";
|
||||
import { EMPTY_ITEM_STATS } from "./itemStats";
|
||||
import { partyAbilityAmount } from "./partyAbilityAi";
|
||||
import { WOW335_SHAMAN_TIDAL_WAVES_AURA_TAG } from "./wow335ShamanTidalWavesRuntime";
|
||||
|
||||
function stableUnit(seed: string): number {
|
||||
let hash = 2_166_136_261;
|
||||
for (const character of seed) {
|
||||
hash ^= character.charCodeAt(0);
|
||||
hash = Math.imul(hash, 16_777_619);
|
||||
}
|
||||
return (hash >>> 0) / 0x1_0000_0000;
|
||||
}
|
||||
|
||||
function shamanAbility(name: string) {
|
||||
const source = abilitiesForClass("shaman").find((candidate) => candidate.name === name);
|
||||
expect(source, `generated Shaman ${name}`).toBeDefined();
|
||||
return abilityAtLevel(source!, 80);
|
||||
}
|
||||
|
||||
function onlyReady(member: PartyMember, abilityName: string): PartyMember {
|
||||
const readyAbility = shamanAbility(abilityName);
|
||||
return {
|
||||
...member,
|
||||
cooldowns: Object.fromEntries(abilitiesForClass("shaman").map((candidate) => [
|
||||
candidate.id,
|
||||
candidate.id === readyAbility.id ? 0 : 1_000_000,
|
||||
])),
|
||||
activeCast: null,
|
||||
activeCastTargetId: null,
|
||||
nextActionAt: 0,
|
||||
};
|
||||
}
|
||||
|
||||
function noAbilitiesReady(member: PartyMember): PartyMember {
|
||||
return {
|
||||
...member,
|
||||
cooldowns: Object.fromEntries(abilitiesForClass("shaman").map((candidate) => [candidate.id, 1_000_000])),
|
||||
activeCast: null,
|
||||
activeCastTargetId: null,
|
||||
nextActionAt: 0,
|
||||
};
|
||||
}
|
||||
|
||||
async function setupShamanParty(options: {
|
||||
readonly tidalRank?: number;
|
||||
readonly riptideRank?: number;
|
||||
readonly waterShieldRank?: number;
|
||||
readonly secondShaman?: boolean;
|
||||
readonly secondShamanTidalRank?: number;
|
||||
readonly spellPower?: number;
|
||||
readonly criticalStrikeRating?: number;
|
||||
} = {}) {
|
||||
clearMobRuntimeRegistry();
|
||||
clearPartyRuntimeRegistry();
|
||||
resetPartyRuntimeClock();
|
||||
await preloadAbilityCatalogsForClasses(["shaman", "warrior"]);
|
||||
useCombatStore.getState().initializeCharacter({ classId: "mage", role: "damage", level: 80 });
|
||||
useCombatStore.setState({ auras: [], effects: [], combatEvents: [] });
|
||||
usePartyStore.getState().initializeParty("shaman-party-runtime", 80, useCombatStore.getState().health, "damage", 3);
|
||||
|
||||
const talentRanks = {
|
||||
"talent-2063": options.tidalRank ?? 0,
|
||||
"talent-2064": options.riptideRank ?? 0,
|
||||
"talent-583": options.waterShieldRank ?? 0,
|
||||
};
|
||||
const firstShamanBase = createConfiguredPartyMember({
|
||||
name: "Party Shaman",
|
||||
classId: "shaman",
|
||||
specialization: "Restoration",
|
||||
specializationId: "shaman:restoration",
|
||||
talentTreeId: "shaman-restoration",
|
||||
talentRanks,
|
||||
role: "healer",
|
||||
}, 80, 0, "shaman-party-runtime:first");
|
||||
const spellPower = options.spellPower ?? 0;
|
||||
const criticalStrikeRating = options.criticalStrikeRating ?? 0;
|
||||
const firstShaman: PartyMember = {
|
||||
...onlyReady(firstShamanBase, "Healing Wave"),
|
||||
gearStats: { ...EMPTY_ITEM_STATS, spellPower, criticalStrikeRating },
|
||||
resource: 10_000,
|
||||
maxResource: 20_000,
|
||||
resourcePools: { ...firstShamanBase.resourcePools, mana: 10_000 },
|
||||
health: firstShamanBase.maxHealth,
|
||||
};
|
||||
const tankBase = createConfiguredPartyMember({
|
||||
name: "Party Tank",
|
||||
classId: "warrior",
|
||||
specialization: "Protection",
|
||||
specializationId: "warrior:protection",
|
||||
talentTreeId: "warrior-protection",
|
||||
role: "tank",
|
||||
}, 80, 1, "shaman-party-runtime:tank");
|
||||
const tank: PartyMember = {
|
||||
...tankBase,
|
||||
health: Math.round(tankBase.maxHealth * 0.25),
|
||||
activeCast: null,
|
||||
activeCastTargetId: null,
|
||||
nextActionAt: 0,
|
||||
};
|
||||
const members: PartyMember[] = [firstShaman, tank];
|
||||
if (options.secondShaman) {
|
||||
const secondBase = createConfiguredPartyMember({
|
||||
name: "Second Party Shaman",
|
||||
classId: "shaman",
|
||||
specialization: "Restoration",
|
||||
specializationId: "shaman:restoration",
|
||||
talentTreeId: "shaman-restoration",
|
||||
talentRanks: {
|
||||
"talent-2063": options.secondShamanTidalRank ?? 0,
|
||||
"talent-2064": 1,
|
||||
"talent-583": 0,
|
||||
},
|
||||
role: "healer",
|
||||
}, 80, 2, "shaman-party-runtime:second");
|
||||
members.push({
|
||||
...onlyReady(secondBase, "Healing Wave"),
|
||||
gearStats: { ...EMPTY_ITEM_STATS },
|
||||
resource: 10_000,
|
||||
maxResource: 20_000,
|
||||
resourcePools: { ...secondBase.resourcePools, mana: 10_000 },
|
||||
health: secondBase.maxHealth,
|
||||
});
|
||||
}
|
||||
usePartyStore.setState({ members });
|
||||
useCombatStore.getState().commitCompanionActors(usePartyStore.getState().members);
|
||||
for (const member of usePartyStore.getState().members) registerPartyRuntimePosition(member.id, [0, 0, 0]);
|
||||
return usePartyStore.getState().members;
|
||||
}
|
||||
|
||||
function findPartyHealTime(
|
||||
memberId: string,
|
||||
abilityId: string,
|
||||
targetId: string,
|
||||
predicate: (roll: number, now: number) => boolean,
|
||||
from = 20_000,
|
||||
): number {
|
||||
for (let now = from; now < from + 100_000; now += 1) {
|
||||
if (predicate(stableUnit(`${memberId}:${abilityId}:${targetId}:${now}:wow335-shaman-improved-water-shield`), now)) {
|
||||
return now;
|
||||
}
|
||||
}
|
||||
throw new Error("Could not find a seeded Shaman party healing proc timestamp.");
|
||||
}
|
||||
|
||||
describe("party WoW Shaman healing talents", () => {
|
||||
it("procs rank-owned Improved Water Shield from its own critical Healing Wave and restores only its mana", async () => {
|
||||
const members = await setupShamanParty({
|
||||
tidalRank: 3,
|
||||
riptideRank: 1,
|
||||
waterShieldRank: 3,
|
||||
spellPower: 1_000,
|
||||
criticalStrikeRating: 100_000,
|
||||
secondShaman: true,
|
||||
});
|
||||
const shaman = members[0];
|
||||
const tank = members[1];
|
||||
const secondShaman = members[2];
|
||||
const playerManaBefore = useCombatStore.getState().resourcePools.mana;
|
||||
const riptide = shamanAbility("Riptide");
|
||||
const healingWave = shamanAbility("Healing Wave");
|
||||
const riptideAt = findPartyHealTime(
|
||||
shaman.id,
|
||||
riptide.id,
|
||||
tank.id,
|
||||
(roll, now) => roll >= 0.3 && stableUnit(
|
||||
`${shaman.id}:${healingWave.id}:${tank.id}:${now + 3_500}:wow335-shaman-improved-water-shield`,
|
||||
) < 0.6,
|
||||
);
|
||||
usePartyStore.setState((state) => ({
|
||||
members: state.members.map((member) => member.id === shaman.id
|
||||
? onlyReady(member, "Riptide")
|
||||
: member.id === secondShaman.id ? noAbilitiesReady(member) : member),
|
||||
}));
|
||||
useCombatStore.getState().commitCompanionActors(usePartyStore.getState().members);
|
||||
advanceDungeonPartyCombat(riptideAt);
|
||||
const tidalAura = useCombatStore.getState().auras.find((aura) => (
|
||||
aura.sourceId === shaman.id
|
||||
&& aura.targetId === shaman.id
|
||||
&& aura.definition.utilityTags?.includes(WOW335_SHAMAN_TIDAL_WAVES_AURA_TAG)
|
||||
));
|
||||
expect(tidalAura).toMatchObject({ stacks: 2, expiresAt: riptideAt + 15_000 });
|
||||
expect(useCombatStore.getState().effects).toContainEqual(expect.objectContaining({
|
||||
kind: "hot",
|
||||
sourceActorId: shaman.id,
|
||||
sourceAbilityId: riptide.id,
|
||||
targetId: tank.id,
|
||||
}));
|
||||
|
||||
usePartyStore.setState((state) => ({
|
||||
members: state.members.map((member) => member.id === shaman.id
|
||||
? onlyReady(member, "Healing Wave")
|
||||
: member.id === secondShaman.id ? noAbilitiesReady(member) : member),
|
||||
}));
|
||||
useCombatStore.getState().commitCompanionActors(usePartyStore.getState().members);
|
||||
advanceDungeonPartyCombat(riptideAt + 1_500);
|
||||
const activeCast = usePartyStore.getState().members.find((member) => member.id === shaman.id)?.activeCast;
|
||||
expect(activeCast).toMatchObject({
|
||||
abilityId: healingWave.id,
|
||||
originalDurationMs: Math.round(healingWave.castTimeMs * 0.8),
|
||||
tidalWavesChargesConsumed: 2,
|
||||
});
|
||||
expect(useCombatStore.getState().auras.find((aura) => aura.instanceId === tidalAura?.instanceId)?.stacks).toBe(1);
|
||||
|
||||
advanceDungeonPartyCombat(activeCast!.completesAt);
|
||||
|
||||
const combat = useCombatStore.getState();
|
||||
const healingEvent = [...combat.combatEvents].reverse().find((event) => (
|
||||
event.kind === "healing" && event.sourceActorId === shaman.id && event.abilityId === healingWave.id
|
||||
));
|
||||
const waterShieldEvent = [...combat.combatEvents].reverse().find((event) => (
|
||||
event.kind === "resource" && event.sourceActorId === shaman.id
|
||||
&& event.abilityId === "wow335-shaman-improved-water-shield"
|
||||
));
|
||||
const effect = healingWave.effects.find((candidate) => candidate.kind === "heal");
|
||||
expect(effect).toBeDefined();
|
||||
const ordinaryHeal = partyAbilityAmount(shaman, healingWave, effect!);
|
||||
expect(healingEvent).toMatchObject({ targetActorId: tank.id, school: "nature", critical: true });
|
||||
expect(healingEvent?.rawAmount).toBe(Math.round((ordinaryHeal + 120) * 1.5));
|
||||
expect(waterShieldEvent).toMatchObject({
|
||||
targetActorId: shaman.id,
|
||||
effectiveAmount: 400,
|
||||
overheal: 0,
|
||||
});
|
||||
expect(combat.combatEvents).not.toContainEqual(expect.objectContaining({
|
||||
kind: "resource",
|
||||
sourceActorId: secondShaman.id,
|
||||
abilityId: "wow335-shaman-improved-water-shield",
|
||||
}));
|
||||
expect(combat.actors[shaman.id]?.resources.mana.current).toBe(
|
||||
usePartyStore.getState().members.find((member) => member.id === shaman.id)?.resourcePools.mana,
|
||||
);
|
||||
expect(combat.resourcePools.mana).toBe(playerManaBefore);
|
||||
expect(useCombatStore.getState().effects).toContainEqual(expect.objectContaining({
|
||||
kind: "hot",
|
||||
sourceActorId: shaman.id,
|
||||
sourceAbilityId: riptide.id,
|
||||
targetId: tank.id,
|
||||
}));
|
||||
});
|
||||
|
||||
it("adds party Tidal Waves charges to Lesser Healing Wave crit chance and consumes one charge", async () => {
|
||||
const members = await setupShamanParty({ tidalRank: 3, riptideRank: 1 });
|
||||
const shaman = members[0];
|
||||
const tank = members[1];
|
||||
const riptide = shamanAbility("Riptide");
|
||||
const lesserHealingWave = shamanAbility("Lesser Healing Wave");
|
||||
let riptideAt = 1_000;
|
||||
for (; riptideAt < 100_000; riptideAt += 1) {
|
||||
if (stableUnit(`${shaman.id}:${lesserHealingWave.id}:${tank.id}:${riptideAt + 3_000}:heal`) < 0.2) break;
|
||||
}
|
||||
expect(riptideAt).toBeLessThan(100_000);
|
||||
|
||||
usePartyStore.setState((state) => ({
|
||||
members: state.members.map((member) => member.id === shaman.id ? onlyReady(member, "Riptide") : member),
|
||||
}));
|
||||
useCombatStore.getState().commitCompanionActors(usePartyStore.getState().members);
|
||||
advanceDungeonPartyCombat(riptideAt);
|
||||
expect(useCombatStore.getState().auras).toContainEqual(expect.objectContaining({
|
||||
sourceId: shaman.id,
|
||||
targetId: shaman.id,
|
||||
stacks: 2,
|
||||
definition: expect.objectContaining({ utilityTags: expect.arrayContaining([WOW335_SHAMAN_TIDAL_WAVES_AURA_TAG]) }),
|
||||
}));
|
||||
|
||||
usePartyStore.setState((state) => ({
|
||||
members: state.members.map((member) => member.id === shaman.id
|
||||
? onlyReady(member, "Lesser Healing Wave")
|
||||
: member),
|
||||
}));
|
||||
useCombatStore.getState().commitCompanionActors(usePartyStore.getState().members);
|
||||
advanceDungeonPartyCombat(riptideAt + 1_500);
|
||||
const activeCast = usePartyStore.getState().members.find((member) => member.id === shaman.id)?.activeCast;
|
||||
expect(activeCast).toMatchObject({
|
||||
abilityId: lesserHealingWave.id,
|
||||
originalDurationMs: lesserHealingWave.castTimeMs,
|
||||
tidalWavesChargesConsumed: 2,
|
||||
});
|
||||
expect(useCombatStore.getState().auras).toContainEqual(expect.objectContaining({
|
||||
sourceId: shaman.id,
|
||||
targetId: shaman.id,
|
||||
stacks: 1,
|
||||
definition: expect.objectContaining({ utilityTags: expect.arrayContaining([WOW335_SHAMAN_TIDAL_WAVES_AURA_TAG]) }),
|
||||
}));
|
||||
advanceDungeonPartyCombat(activeCast!.completesAt);
|
||||
|
||||
const healingEvent = [...useCombatStore.getState().combatEvents].reverse().find((event) => (
|
||||
event.kind === "healing" && event.sourceActorId === shaman.id && event.abilityId === lesserHealingWave.id
|
||||
));
|
||||
expect(healingEvent).toMatchObject({ targetActorId: tank.id, critical: true });
|
||||
});
|
||||
|
||||
it("expires party Tidal Waves after 15 seconds and ignores another Shaman's Riptide HoT", async () => {
|
||||
const members = await setupShamanParty({ tidalRank: 3, riptideRank: 1, secondShaman: true });
|
||||
const shaman = members[0];
|
||||
const tank = members[1];
|
||||
const secondShaman = members[2];
|
||||
const riptide = shamanAbility("Riptide");
|
||||
const chainHeal = shamanAbility("Chain Heal");
|
||||
usePartyStore.setState((state) => ({
|
||||
members: state.members.map((member) => member.id === shaman.id
|
||||
? onlyReady(member, "Riptide")
|
||||
: member.id === secondShaman.id
|
||||
? { ...onlyReady(member, "Healing Wave"), cooldowns: Object.fromEntries(
|
||||
abilitiesForClass("shaman").map((ability) => [ability.id, 1_000_000]),
|
||||
) }
|
||||
: member),
|
||||
}));
|
||||
useCombatStore.getState().commitCompanionActors(usePartyStore.getState().members);
|
||||
const riptideAt = 1_000;
|
||||
advanceDungeonPartyCombat(riptideAt);
|
||||
const aura = useCombatStore.getState().auras.find((candidate) => (
|
||||
candidate.sourceId === shaman.id
|
||||
&& candidate.targetId === shaman.id
|
||||
&& candidate.definition.utilityTags?.includes(WOW335_SHAMAN_TIDAL_WAVES_AURA_TAG)
|
||||
));
|
||||
expect(aura).toMatchObject({ stacks: 2, expiresAt: 16_000 });
|
||||
|
||||
const initialHot = useCombatStore.getState().effects.find((effect) => (
|
||||
effect.kind === "hot" && effect.sourceActorId === shaman.id && effect.targetId === tank.id
|
||||
));
|
||||
expect(initialHot?.sourceAbilityId).toBe(riptide.id);
|
||||
const chainEffect = chainHeal.effects.find((effect) => effect.kind === "heal");
|
||||
expect(chainEffect).toBeDefined();
|
||||
const baseAmount = partyAbilityAmount(secondShaman, chainHeal, chainEffect!);
|
||||
usePartyStore.setState((state) => ({
|
||||
members: state.members.map((member) => member.id === secondShaman.id
|
||||
? onlyReady(member, "Chain Heal")
|
||||
: member.id === shaman.id
|
||||
? { ...member, cooldowns: Object.fromEntries(abilitiesForClass("shaman").map((ability) => [ability.id, 1_000_000])) }
|
||||
: member),
|
||||
}));
|
||||
useCombatStore.getState().commitCompanionActors(usePartyStore.getState().members);
|
||||
advanceDungeonPartyCombat(riptideAt + 1_000);
|
||||
const secondCast = usePartyStore.getState().members.find((member) => member.id === secondShaman.id)?.activeCast;
|
||||
expect(secondCast?.abilityId).toBe(chainHeal.id);
|
||||
advanceDungeonPartyCombat(secondCast!.completesAt);
|
||||
const secondHeal = [...useCombatStore.getState().combatEvents].reverse().find((event) => (
|
||||
event.kind === "healing" && event.sourceActorId === secondShaman.id
|
||||
&& event.abilityId === chainHeal.id && event.targetActorId === tank.id
|
||||
));
|
||||
expect(secondHeal?.rawAmount).toBe(baseAmount);
|
||||
expect(useCombatStore.getState().auras).not.toContainEqual(expect.objectContaining({
|
||||
sourceId: secondShaman.id,
|
||||
targetId: secondShaman.id,
|
||||
definition: expect.objectContaining({ utilityTags: expect.arrayContaining([WOW335_SHAMAN_TIDAL_WAVES_AURA_TAG]) }),
|
||||
}));
|
||||
expect(useCombatStore.getState().effects).toContainEqual(expect.objectContaining({
|
||||
id: initialHot?.id,
|
||||
sourceActorId: shaman.id,
|
||||
targetId: tank.id,
|
||||
}));
|
||||
|
||||
usePartyStore.setState((state) => ({
|
||||
members: state.members.map((member) => member.id === shaman.id
|
||||
? onlyReady(member, "Healing Wave")
|
||||
: member.id === secondShaman.id
|
||||
? { ...member, cooldowns: Object.fromEntries(abilitiesForClass("shaman").map((ability) => [ability.id, 1_000_000])) }
|
||||
: member),
|
||||
}));
|
||||
useCombatStore.getState().commitCompanionActors(usePartyStore.getState().members);
|
||||
advanceDungeonPartyCombat(16_000);
|
||||
const expiredCast = usePartyStore.getState().members.find((member) => member.id === shaman.id)?.activeCast;
|
||||
expect(expiredCast?.abilityId).toBe(shamanAbility("Healing Wave").id);
|
||||
expect(expiredCast?.originalDurationMs).toBe(shamanAbility("Healing Wave").castTimeMs);
|
||||
expect(useCombatStore.getState().auras.find((candidate) => candidate.instanceId === aura?.instanceId)?.stacks).toBe(2);
|
||||
});
|
||||
|
||||
it("consumes only the Riptide caster's matching HoT for the Chain Heal primary target", async () => {
|
||||
const members = await setupShamanParty({
|
||||
riptideRank: 1,
|
||||
secondShaman: true,
|
||||
secondShamanTidalRank: 3,
|
||||
});
|
||||
const shaman = members[0];
|
||||
const tank = members[1];
|
||||
const secondShaman = members[2];
|
||||
const riptide = shamanAbility("Riptide");
|
||||
const chainHeal = shamanAbility("Chain Heal");
|
||||
usePartyStore.setState((state) => ({
|
||||
members: state.members.map((member) => member.id === shaman.id
|
||||
? onlyReady(member, "Riptide")
|
||||
: member.id === secondShaman.id
|
||||
? { ...onlyReady(member, "Healing Wave"), cooldowns: Object.fromEntries(
|
||||
abilitiesForClass("shaman").map((ability) => [ability.id, 1_000_000]),
|
||||
) }
|
||||
: member),
|
||||
}));
|
||||
useCombatStore.getState().commitCompanionActors(usePartyStore.getState().members);
|
||||
advanceDungeonPartyCombat(1_000);
|
||||
const riptideHot = useCombatStore.getState().effects.find((effect) => (
|
||||
effect.kind === "hot" && effect.sourceActorId === shaman.id
|
||||
&& effect.sourceAbilityId === riptide.id && effect.targetId === tank.id
|
||||
));
|
||||
expect(riptideHot).toBeDefined();
|
||||
|
||||
const baseAmount = partyAbilityAmount(secondShaman, chainHeal, chainHeal.effects.find((effect) => effect.kind === "heal")!);
|
||||
usePartyStore.setState((state) => ({
|
||||
members: state.members.map((member) => member.id === secondShaman.id
|
||||
? onlyReady(member, "Chain Heal")
|
||||
: member.id === shaman.id
|
||||
? { ...member, cooldowns: Object.fromEntries(abilitiesForClass("shaman").map((ability) => [ability.id, 1_000_000])) }
|
||||
: member),
|
||||
}));
|
||||
useCombatStore.getState().commitCompanionActors(usePartyStore.getState().members);
|
||||
advanceDungeonPartyCombat(2_500);
|
||||
const otherCasterCast = usePartyStore.getState().members.find((member) => member.id === secondShaman.id)?.activeCast;
|
||||
advanceDungeonPartyCombat(otherCasterCast!.completesAt);
|
||||
const otherCasterHeal = [...useCombatStore.getState().combatEvents].reverse().find((event) => (
|
||||
event.kind === "healing" && event.sourceActorId === secondShaman.id
|
||||
&& event.abilityId === chainHeal.id && event.targetActorId === tank.id
|
||||
));
|
||||
expect(otherCasterHeal?.rawAmount).toBe(baseAmount);
|
||||
expect(useCombatStore.getState().auras).toContainEqual(expect.objectContaining({
|
||||
sourceId: secondShaman.id,
|
||||
targetId: secondShaman.id,
|
||||
stacks: 3,
|
||||
definition: expect.objectContaining({
|
||||
utilityTags: expect.arrayContaining([WOW335_SHAMAN_TIDAL_WAVES_AURA_TAG]),
|
||||
}),
|
||||
}));
|
||||
expect(useCombatStore.getState().effects).toContainEqual(expect.objectContaining({ id: riptideHot?.id }));
|
||||
|
||||
usePartyStore.setState((state) => ({
|
||||
members: state.members.map((member) => member.id === shaman.id
|
||||
? onlyReady(member, "Chain Heal")
|
||||
: member.id === secondShaman.id
|
||||
? { ...member, cooldowns: Object.fromEntries(abilitiesForClass("shaman").map((ability) => [ability.id, 1_000_000])) }
|
||||
: member),
|
||||
}));
|
||||
useCombatStore.getState().commitCompanionActors(usePartyStore.getState().members);
|
||||
advanceDungeonPartyCombat(otherCasterCast!.completesAt + 1_000);
|
||||
const ownCasterCast = usePartyStore.getState().members.find((member) => member.id === shaman.id)?.activeCast;
|
||||
advanceDungeonPartyCombat(ownCasterCast!.completesAt);
|
||||
const ownCasterHeal = [...useCombatStore.getState().combatEvents].reverse().find((event) => (
|
||||
event.kind === "healing" && event.sourceActorId === shaman.id
|
||||
&& event.abilityId === chainHeal.id && event.targetActorId === tank.id
|
||||
));
|
||||
expect(ownCasterHeal?.rawAmount).toBe(Math.round(baseAmount * 1.25));
|
||||
expect(useCombatStore.getState().effects).not.toContainEqual(expect.objectContaining({ id: riptideHot?.id }));
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user