Update Healer Man server

This commit is contained in:
phenom committed 2026-09-30 09:57:13 -04:00
1 parent 35dfdaed61
commit 4fb7a0b9c8
161 files changed
+24618 -529

No files matched your search

@@ -0,0 +1,250 @@
import { beforeEach, describe, expect, it } from "vitest";
import { PLAYER_AURA_ENTITY_ID, resolvedAbilityCosts, useCombatStore } from "./combatStore";
import { abilityAtLevel, abilityById } from "./abilityCatalog";
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
import { clearPartyRuntimeRegistry } from "./partyRuntimeRegistry";
import { EMPTY_ITEM_STATS } from "./itemStats";
import { usePartyStore } from "./partyStore";
import { WOW335_SHAMAN_MAELSTROM_WEAPON_AURA_TAG } from "./wow335ShamanMaelstromWeaponRuntime";
const TARGET_ID = "maelstrom-target";
const OFF_HAND_WEAPON_ID = "maelstrom-test-off-hand";
function runtimeManaCost(abilityId: string, state: ReturnType<typeof useCombatStore.getState>, now: number): number {
const catalogAbility = abilityById(abilityId);
expect(catalogAbility).not.toBeNull();
const rankedAbility = abilityAtLevel(catalogAbility!, state.level);
return resolvedAbilityCosts(rankedAbility, state.talentModifiers, { ...state, now })
.find((cost) => cost.resource === "mana")?.amount ?? 0;
}
function shamanAbility(name: string) {
const ability = useCombatStore.getState().abilities.find((candidate) => candidate.name === name);
expect(ability, `Shaman ${name}`).toBeDefined();
return ability!;
}
function setMaelstromRank(rank: number): void {
useCombatStore.setState((state) => ({
talentRanks: { ...state.talentRanks, "talent-2057": rank },
}));
}
function maelstromAura() {
return useCombatStore.getState().auras.find((aura) => (
aura.targetId === PLAYER_AURA_ENTITY_ID
&& aura.definition.utilityTags?.includes(WOW335_SHAMAN_MAELSTROM_WEAPON_AURA_TAG)
));
}
function stableCombatUnit(seed: string): number {
let hash = 2_166_136_261;
for (let index = 0; index < seed.length; index += 1) {
hash ^= seed.charCodeAt(index);
hash = Math.imul(hash, 16_777_619);
}
return (hash >>> 0) / 0x1_0000_0000;
}
function setupShaman(): void {
clearMobRuntimeRegistry();
clearPartyRuntimeRegistry();
const offHandStats = {
...EMPTY_ITEM_STATS,
meleeWeapon: { damageMin: 20, damageMax: 30, damageSchool: "physical" as const, speedMs: 5_000 },
};
useCombatStore.getState().initializeCharacter({
classId: "shaman",
level: 80,
xp: 0,
inventory: [{
instanceId: OFF_HAND_WEAPON_ID,
itemId: 99001,
itemKey: OFF_HAND_WEAPON_ID,
name: "Slow off-hand weapon",
quality: "uncommon",
inventorySlot: "off-hand",
armorType: null,
weaponType: "mace",
itemLevel: 80,
sourceItemLevel: 80,
stats: offHandStats,
quantity: 1,
sourceBossKey: "test",
sourceBossName: "Test",
acquiredAt: 1,
}],
equipment: { player: { "off-hand": OFF_HAND_WEAPON_ID } },
});
useCombatStore.getState().resetSettings();
useCombatStore.getState().setPlayerPosition([0, 0, 0]);
// Keep a distinct one-second main-hand value so the Lava Lash assertion
// proves that the proc uses its equipped five-second off-hand weapon.
useCombatStore.setState((state) => ({
gearStats: {
...state.gearStats,
meleeWeapon: { damageMin: 20, damageMax: 30, damageSchool: "physical", speedMs: 1_000 },
},
}));
usePartyStore.getState().initializeParty("wow-shaman-maelstrom-tests", 80, useCombatStore.getState().health);
useCombatStore.getState().registerMob(TARGET_ID, {
name: "Maelstrom target",
level: 80,
maxHealth: 1_000_000,
xpReward: 0,
});
setMobPosition(TARGET_ID, [3, 0, 0]);
useCombatStore.getState().selectMob(TARGET_ID);
}
function cast(abilityId: string, now: number, targetActorId: string | null = TARGET_ID): void {
const result = useCombatStore.getState().castAbility(abilityId, { targetActorId, now });
expect(result.ok, JSON.stringify(result)).toBe(true);
}
describe("WoW Shaman Maelstrom Weapon combat runtime", () => {
beforeEach(setupShaman);
it("procs on landed melee hits using Lava Lash's equipped off-hand speed", () => {
setMaelstromRank(3);
const lavaLash = shamanAbility("Lava Lash");
const seedUnitAtMainHandRate = (now: number) => stableCombatUnit(
`${PLAYER_AURA_ENTITY_ID}:${lavaLash.id}:${TARGET_ID}:${now}:direct:wow335-shaman-maelstrom-weapon`,
);
const now = Array.from({ length: 1_000 }, (_, index) => 1_000 + index)
.find((candidate) => seedUnitAtMainHandRate(candidate) >= 0.2)!;
expect(now).toBeDefined();
cast(lavaLash.id, now);
expect(seedUnitAtMainHandRate(now)).toBeGreaterThanOrEqual(0.2);
expect(maelstromAura()).toMatchObject({ stacks: 1, expiresAt: now + 30_000 });
expect(useCombatStore.getState().combatEvents.some((event) => (
event.kind === "damage" && event.abilityId === lavaLash.id && event.effectiveAmount > 0
))).toBe(true);
});
it("adds one stack per proc, refreshes the 30-second duration, and caps at five", () => {
setMaelstromRank(3);
const lavaLash = shamanAbility("Lava Lash");
const interval = Math.max(lavaLash.cooldownMs, lavaLash.gcdMs, 1_500) + 10;
let now = 1_000;
for (let hit = 1; hit <= 6; hit += 1) {
cast(lavaLash.id, now);
expect(maelstromAura()?.stacks).toBe(Math.min(hit, 5));
expect(maelstromAura()?.expiresAt).toBe(now + 30_000);
now += interval;
}
});
it("reduces Lightning Bolt cast time per stack, consumes the whole bundle at cast start, and still charges Mana", () => {
setMaelstromRank(3);
const lavaLash = shamanAbility("Lava Lash");
const lightningBolt = shamanAbility("Lightning Bolt");
const interval = Math.max(lavaLash.cooldownMs, lavaLash.gcdMs, 1_500) + 10;
let now = 1_000;
for (let hit = 0; hit < 5; hit += 1) {
cast(lavaLash.id, now);
now += interval;
}
expect(maelstromAura()?.stacks).toBe(5);
// Earth Shock is not listed by the generated Maelstrom tooltip and must
// leave the active next-spell bundle alone.
const earthShock = shamanAbility("Earth Shock");
cast(earthShock.id, now);
expect(maelstromAura()?.stacks).toBe(5);
now += Math.max(earthShock.cooldownMs, earthShock.gcdMs, 1_500) + 10;
const before = useCombatStore.getState();
const manaBefore = before.resource;
const expectedManaCost = runtimeManaCost(lightningBolt.id, before, now);
cast(lightningBolt.id, now);
expect(useCombatStore.getState().activeCast).toBeNull();
expect(maelstromAura()).toBeUndefined();
expect(useCombatStore.getState().resource).toBe(manaBefore - expectedManaCost);
});
it("applies a partial cast-time reduction, consumes the bundle on start, and does not make the spell free", () => {
const lightningBolt = shamanAbility("Lightning Bolt");
cast(lightningBolt.id, 500);
const baselineCast = useCombatStore.getState().activeCast;
expect(baselineCast).not.toBeNull();
const baselineDurationMs = baselineCast!.completesAt - baselineCast!.startedAt;
setupShaman();
setMaelstromRank(3);
const lavaLash = shamanAbility("Lava Lash");
const rankedLightningBolt = shamanAbility("Lightning Bolt");
cast(lavaLash.id, 1_000);
expect(maelstromAura()?.stacks).toBe(1);
const now = 3_000;
const before = useCombatStore.getState();
const manaBefore = before.resource;
const expectedManaCost = runtimeManaCost(rankedLightningBolt.id, before, now);
cast(rankedLightningBolt.id, now);
const activeCast = useCombatStore.getState().activeCast;
expect(activeCast!.completesAt! - activeCast!.startedAt!)
.toBe(Math.round(baselineDurationMs * 0.8));
expect(maelstromAura()).toBeUndefined();
expect(useCombatStore.getState().resource).toBe(manaBefore);
useCombatStore.getState().tick(0.1, activeCast!.completesAt);
expect(useCombatStore.getState().resource).toBeLessThan(manaBefore);
expect(useCombatStore.getState().resource).toBeCloseTo(manaBefore - expectedManaCost, 0);
});
it("does not proc from a spell hit or a melee hit without a trained rank", () => {
const lavaLash = shamanAbility("Lava Lash");
const lightningBolt = shamanAbility("Lightning Bolt");
cast(lavaLash.id, 1_000);
expect(maelstromAura()).toBeUndefined();
setMaelstromRank(3);
cast(lightningBolt.id, 3_000);
expect(maelstromAura()).toBeUndefined();
});
it("does not proc from a missed melee attack", () => {
setMaelstromRank(3);
const lavaLash = shamanAbility("Lava Lash");
useCombatStore.getState().registerMob(TARGET_ID, {
name: "High level Maelstrom target",
level: 200,
maxHealth: 1_000_000,
xpReward: 0,
});
const now = 1_000;
cast(lavaLash.id, now);
const damageEvent = useCombatStore.getState().combatEvents.find((event) => (
event.kind === "damage" && event.abilityId === lavaLash.id
));
expect(damageEvent?.effectiveAmount).toBe(0);
expect(maelstromAura()).toBeUndefined();
});
it("ignores an expired Maelstrom aura when calculating cast time", () => {
setMaelstromRank(3);
const lavaLash = shamanAbility("Lava Lash");
const lightningBolt = shamanAbility("Lightning Bolt");
cast(lavaLash.id, 1_000);
const generatedAura = maelstromAura();
expect(generatedAura).toBeDefined();
useCombatStore.setState((state) => ({
auras: state.auras.map((aura) => aura.instanceId === generatedAura!.instanceId
? { ...aura, expiresAt: 3_000 }
: aura),
}));
cast(lightningBolt.id, 3_000);
const activeCast = useCombatStore.getState().activeCast;
expect(activeCast!.completesAt! - activeCast!.startedAt!).toBe(2_500);
expect(maelstromAura()?.expiresAt).toBe(3_000);
});
});