Update Healer Man server
This commit is contained in:
1 parent
9d7e0e2f9e
commit
35dfdaed61
94 files changed
+17034
-418
No files matched your search
@@ -0,0 +1,105 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { PLAYER_AGGRO_ID } from "./aggro";
|
||||
import { abilityTalentNodeId, type AbilityDefinition } from "./abilityCatalog";
|
||||
import { clearMobRuntimeRegistry } from "./mobRuntimeRegistry";
|
||||
import { clearPartyRuntimeRegistry } from "./partyRuntimeRegistry";
|
||||
import { useCombatStore, type TimedCombatEffect } from "./combatStore";
|
||||
|
||||
function prepareSwiftmend(): AbilityDefinition {
|
||||
useCombatStore.getState().initializeCharacter({ classId: "druid", level: 80, xp: 0 });
|
||||
useCombatStore.setState((state) => ({ health: 1_000, resource: state.maxResource }));
|
||||
const ability = useCombatStore.getState().abilities.find((candidate) => candidate.name === "Swiftmend");
|
||||
if (!ability) throw new Error("Druid Swiftmend is missing from the learned ability catalog.");
|
||||
const talentId = abilityTalentNodeId(ability);
|
||||
if (!talentId) throw new Error("Druid Swiftmend has no active talent node.");
|
||||
useCombatStore.setState((state) => ({ talentRanks: { ...state.talentRanks, [talentId]: 1 } }));
|
||||
return ability;
|
||||
}
|
||||
|
||||
function hot(
|
||||
sourceName: "Rejuvenation" | "Regrowth",
|
||||
sourceActorId: string,
|
||||
tickHealing: number,
|
||||
remainingTicks: number,
|
||||
nextTickAt: number,
|
||||
id: string,
|
||||
fixedAmount?: number,
|
||||
): TimedCombatEffect {
|
||||
const sourceAbility = useCombatStore.getState().abilities.find((ability) => ability.name === sourceName);
|
||||
if (!sourceAbility) throw new Error(`Druid ${sourceName} is missing from the learned ability catalog.`);
|
||||
return {
|
||||
id,
|
||||
kind: "hot",
|
||||
sourceAbilityId: sourceAbility.id,
|
||||
sourceActorId,
|
||||
targetId: null,
|
||||
coefficient: 0,
|
||||
basePoints: tickHealing,
|
||||
dieSides: 1,
|
||||
pointsPerLevel: 0,
|
||||
sourceLevel: 80,
|
||||
...(fixedAmount === undefined ? {} : { fixedAmount }),
|
||||
remainingTicks,
|
||||
intervalMs: 3_000,
|
||||
nextTickAt,
|
||||
};
|
||||
}
|
||||
|
||||
function castSwiftmend(ability: AbilityDefinition, now = 1_000) {
|
||||
const result = useCombatStore.getState().castAbility(ability.id, { targetActorId: null, now });
|
||||
expect(result.ok).toBe(true);
|
||||
const event = useCombatStore.getState().combatEvents.filter((candidate) => (
|
||||
candidate.kind === "healing" && candidate.abilityId === ability.id
|
||||
)).at(-1);
|
||||
if (!event || event.kind !== "healing") throw new Error("Swiftmend did not record its healing event.");
|
||||
return event;
|
||||
}
|
||||
|
||||
function expectCriticalAdjustedRawHealing(rawAmount: number, critical: boolean, baseAmount: number): void {
|
||||
expect(rawAmount).toBe(baseAmount * (critical ? 1.5 : 1));
|
||||
}
|
||||
|
||||
describe("WoW Druid active talent runtime", () => {
|
||||
beforeEach(() => {
|
||||
clearMobRuntimeRegistry();
|
||||
clearPartyRuntimeRegistry();
|
||||
});
|
||||
|
||||
it.each([
|
||||
["Rejuvenation", 100, 5, 400],
|
||||
["Regrowth", 150, 7, 900],
|
||||
] as const)("Swiftmend uses its source 12/18-second amount for %s", (sourceName, tickHealing, remainingTicks, expected) => {
|
||||
const swiftmend = prepareSwiftmend();
|
||||
const sourceHot = hot(sourceName, PLAYER_AGGRO_ID, tickHealing, remainingTicks, 4_000, "swiftmend-source-hot");
|
||||
useCombatStore.setState({ effects: [sourceHot] });
|
||||
|
||||
const event = castSwiftmend(swiftmend);
|
||||
|
||||
expectCriticalAdjustedRawHealing(event.rawAmount, event.critical, expected);
|
||||
expect(useCombatStore.getState().effects).toEqual([]);
|
||||
});
|
||||
|
||||
it("prefers its own Hot over a shorter foreign Hot", () => {
|
||||
const swiftmend = prepareSwiftmend();
|
||||
const foreignShort = hot("Rejuvenation", "party-druid-short", 25, 1, 1_500, "swiftmend-foreign-short", 25);
|
||||
const casterHot = hot("Regrowth", PLAYER_AGGRO_ID, 100, 7, 16_000, "swiftmend-caster-long");
|
||||
useCombatStore.setState({ effects: [foreignShort, casterHot] });
|
||||
|
||||
const event = castSwiftmend(swiftmend);
|
||||
|
||||
expectCriticalAdjustedRawHealing(event.rawAmount, event.critical, 600);
|
||||
expect(useCombatStore.getState().effects.map((effect) => effect.id)).toEqual([foreignShort.id]);
|
||||
});
|
||||
|
||||
it("uses the shortest foreign Rejuvenation or Regrowth when no caster Hot exists", () => {
|
||||
const swiftmend = prepareSwiftmend();
|
||||
const longerRegrowth = hot("Regrowth", "party-druid-long", 50, 7, 4_000, "swiftmend-foreign-long", 50);
|
||||
const shorterRejuvenation = hot("Rejuvenation", "party-druid-short", 100, 5, 4_000, "swiftmend-foreign-short", 100);
|
||||
useCombatStore.setState({ effects: [longerRegrowth, shorterRejuvenation] });
|
||||
|
||||
const event = castSwiftmend(swiftmend);
|
||||
|
||||
expectCriticalAdjustedRawHealing(event.rawAmount, event.critical, 400);
|
||||
expect(useCombatStore.getState().effects.map((effect) => effect.id)).toEqual([longerRegrowth.id]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user