Update Healer Man server

This commit is contained in:
phenom committed 2026-10-02 10:38:12 -04:00
1 parent 4c768fbec6
commit b16821e92e
330 files changed
+35406 -1760

No files matched your search

@@ -1,6 +1,7 @@
import { beforeEach, describe, expect, it } from "vitest";
import { PLAYER_AURA_ENTITY_ID, resolvedAbilityCosts, useCombatStore } from "./combatStore";
import { abilityAtLevel, abilityById } from "./abilityCatalog";
import { applyAura } from "./combatAuras";
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
import { clearPartyRuntimeRegistry } from "./partyRuntimeRegistry";
import { EMPTY_ITEM_STATS } from "./itemStats";
@@ -46,6 +47,49 @@ function stableCombatUnit(seed: string): number {
return (hash >>> 0) / 0x1_0000_0000;
}
function markTargetAsCharacterDefender(): void {
useCombatStore.setState((state) => ({
mobs: {
...state.mobs,
[TARGET_ID]: { ...state.mobs[TARGET_ID]!, characterDefender: { level: 80 } },
},
}));
}
function grantMaelstrom(stacks: number, now: number): void {
const definition = {
id: "wow335:shaman:maelstrom-weapon",
name: "Maelstrom Weapon",
description: "Your next listed spell has its cast time reduced by 20% per stack.",
disposition: "buff" as const,
durationMs: 30_000,
maxStacks: 5,
initialStacks: 1,
stackBehavior: "add" as const,
utilityTags: [WOW335_SHAMAN_MAELSTROM_WEAPON_AURA_TAG],
};
useCombatStore.setState((state) => ({
auras: [...applyAura(state.auras, {
definition,
sourceId: PLAYER_AURA_ENTITY_ID,
targetId: PLAYER_AURA_ENTITY_ID,
stacks,
now,
}).auras],
}));
}
function firstPlayerLightningBoltDamage(): number {
const event = useCombatStore.getState().combatEvents.find((candidate) => (
candidate.kind === "damage"
&& candidate.sourceActorId === PLAYER_AURA_ENTITY_ID
&& candidate.targetActorId === TARGET_ID
&& candidate.abilityId === shamanAbility("Lightning Bolt").id
));
expect(event, "player Lightning Bolt damage event").toBeDefined();
return event!.effectiveAmount;
}
function setupShaman(): void {
clearMobRuntimeRegistry();
clearPartyRuntimeRegistry();
@@ -97,9 +141,10 @@ function setupShaman(): void {
useCombatStore.getState().selectMob(TARGET_ID);
}
function cast(abilityId: string, now: number, targetActorId: string | null = TARGET_ID): void {
function cast(abilityId: string, now: number, targetActorId: string | null = TARGET_ID) {
const result = useCombatStore.getState().castAbility(abilityId, { targetActorId, now });
expect(result.ok, JSON.stringify(result)).toBe(true);
return result;
}
describe("WoW Shaman Maelstrom Weapon combat runtime", () => {
@@ -326,4 +371,97 @@ describe("WoW Shaman Maelstrom Weapon combat runtime", () => {
expect(activeCast!.completesAt! - activeCast!.startedAt!).toBe(2_500);
expect(maelstromAura()?.expiresAt).toBe(3_000);
});
it("reduces instant Lightning Bolt damage only against a character defender, capped at five stacks", () => {
const run = (stacks: number, characterDefender: boolean): number => {
setupShaman();
setMaelstromRank(3);
if (characterDefender) markTargetAsCharacterDefender();
const lightningBolt = shamanAbility("Lightning Bolt");
if (stacks > 0) grantMaelstrom(stacks, 5_000);
cast(lightningBolt.id, 5_000);
const activeCast = useCombatStore.getState().activeCast;
if (activeCast) {
useCombatStore.setState((state) => ({
activeCast: state.activeCast ? { ...state.activeCast, completesAt: 7_500 } : null,
}));
useCombatStore.getState().tick(0.1, 7_500);
}
return firstPlayerLightningBoltDamage();
};
const pvpBaseline = run(0, true);
const pvpFiveStacks = run(5, true);
expect(pvpBaseline).toBeGreaterThan(0);
expect(pvpFiveStacks).toBe(Math.round(pvpBaseline * 0.9));
const npcBaseline = run(0, false);
const npcFiveStacks = run(5, false);
expect(npcFiveStacks).toBe(npcBaseline);
});
it("retains consumed Maelstrom stacks through cast completion and channel pulses", () => {
const runCast = (stacks: number): { readonly damage: number; readonly snapshot: unknown } => {
setupShaman();
setMaelstromRank(3);
markTargetAsCharacterDefender();
const lightningBolt = shamanAbility("Lightning Bolt");
if (stacks > 0) grantMaelstrom(stacks, 5_000);
cast(lightningBolt.id, 5_000);
const activeCast = useCombatStore.getState().activeCast;
expect(activeCast).not.toBeNull();
const snapshot = {
rank: activeCast!.maelstromWeaponRank,
stacks: activeCast!.maelstromWeaponStacksConsumed,
};
expect(maelstromAura()).toBeUndefined();
useCombatStore.setState((state) => ({
activeCast: state.activeCast ? { ...state.activeCast, completesAt: 20_000 } : null,
...(stacks > 0 ? { talentRanks: { ...state.talentRanks, "talent-2057": 0 } } : {}),
}));
useCombatStore.getState().tick(0.1, 20_000);
return { damage: firstPlayerLightningBoltDamage(), snapshot };
};
const castBaseline = runCast(0);
const castWithStacks = runCast(3);
expect(castWithStacks.snapshot).toEqual({ rank: 3, stacks: 3 });
expect(castWithStacks.damage).toBe(Math.round(castBaseline.damage * 0.94));
const runChannel = (stacks: number): { readonly damage: number; readonly snapshot: unknown } => {
setupShaman();
setMaelstromRank(3);
markTargetAsCharacterDefender();
const lightningBolt = shamanAbility("Lightning Bolt");
if (stacks > 0) grantMaelstrom(stacks, 5_000);
cast(lightningBolt.id, 5_000);
const activeCast = useCombatStore.getState().activeCast;
expect(activeCast).not.toBeNull();
const snapshot = {
rank: activeCast!.maelstromWeaponRank,
stacks: activeCast!.maelstromWeaponStacksConsumed,
};
expect(maelstromAura()).toBeUndefined();
useCombatStore.setState((state) => ({
activeCast: state.activeCast ? {
...state.activeCast,
mode: "channel",
totalTicks: 2,
ticksCompleted: 0,
originalDurationMs: 2_000,
completesAt: 7_000,
nextTickAt: 6_000,
} : null,
...(stacks > 0 ? { talentRanks: { ...state.talentRanks, "talent-2057": 0 } } : {}),
}));
useCombatStore.getState().tick(0.1, 6_000);
return { damage: firstPlayerLightningBoltDamage(), snapshot };
};
const channelBaseline = runChannel(0);
const channelWithStacks = runChannel(3);
expect(channelWithStacks.snapshot).toEqual({ rank: 3, stacks: 3 });
expect(channelWithStacks.damage).toBe(Math.round(channelBaseline.damage * 0.94));
});
});