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

@@ -0,0 +1,197 @@
import { beforeEach, describe, expect, it } from "vitest";
import { PLAYER_AGGRO_ID } from "./aggro";
import { abilitiesForClass, abilityAtLevel, abilityTalentNodeId, preloadAbilityCatalogsForClasses } from "./abilityCatalog";
import { useCombatStore, PLAYER_AURA_ENTITY_ID } from "./combatStore";
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
import { DEFAULT_ENEMY_ATTACK } from "./enemyAttacks";
import { clearPartyRuntimeRegistry, registerPartyRuntimePosition } from "./partyRuntimeRegistry";
import { createConfiguredPartyMember, usePartyStore } from "./partyStore";
import { EMPTY_ITEM_STATS } from "./itemStats";
import { resetPartyRuntimeClock } from "./partyRuntime";
import { WOW335_SHAMAN_TIDAL_WAVES_AURA_TAG } from "./wow335ShamanTidalWavesRuntime";
function shamanAbility(name: string) {
const source = abilitiesForClass("shaman").find((candidate) => candidate.name === name);
expect(source, `generated Shaman ${name}`).toBeDefined();
return abilityAtLevel(source!, 80);
}
function shieldAuraEffect(name: string) {
const ability = shamanAbility(name);
const effect = ability.effects.find((candidate) => candidate.kind === "apply-aura");
expect(effect, `${name} apply-aura`).toBeDefined();
if (!effect || effect.kind !== "apply-aura") throw new Error(`${name} aura is missing`);
return { ability, effect };
}
function registerAttacker(id: string, targetActorId: string, engagedAt = 100): void {
useCombatStore.getState().registerMob(id, {
name: id,
level: 80,
maxHealth: 100_000,
xpReward: 0,
attacks: [{ ...DEFAULT_ENEMY_ATTACK, id: `${id}:swing`, range: 30 }],
});
setMobPosition(id, [2, 0, 0]);
useCombatStore.getState().damageMob(id, 1, engagedAt, { actorId: targetActorId, role: "tank" }, "physical");
}
describe("WoW Shaman shield proc integration", () => {
beforeEach(async () => {
clearMobRuntimeRegistry();
clearPartyRuntimeRegistry();
resetPartyRuntimeClock();
await preloadAbilityCatalogsForClasses(["shaman", "warrior"]);
useCombatStore.getState().initializeCharacter({ classId: "mage", level: 80 });
useCombatStore.setState({ auras: [], effects: [], combatEvents: [] });
usePartyStore.getState().initializeParty("shaman-shields-test", 80, useCombatStore.getState().health, "damage", 1);
});
it("scales player Lightning Shield damage and Earth Shield charges/healing, then gates Tidal Waves to its 5s ICD", () => {
useCombatStore.getState().initializeCharacter({ classId: "shaman", role: "healer", level: 80 });
useCombatStore.setState((state) => ({
health: Math.round(state.maxHealth * 0.5),
talentRanks: {
...state.talentRanks,
"talent-607": 3,
"talent-2059": 3,
"talent-2063": 3,
},
}));
const lightning = shieldAuraEffect("Lightning Shield");
const earth = shieldAuraEffect("Earth Shield");
const earthTalentNodeId = abilityTalentNodeId(earth.ability);
useCombatStore.setState((state) => ({
talentRanks: { ...state.talentRanks, ...(earthTalentNodeId ? { [earthTalentNodeId]: 1 } : {}) },
}));
const lightningCast = useCombatStore.getState().castAbility(lightning.ability.id, {
targetActorId: PLAYER_AURA_ENTITY_ID, now: 1_000,
});
expect(lightningCast.ok, lightningCast.reason).toBe(true);
const earthCast = useCombatStore.getState().castAbility(earth.ability.id, {
targetActorId: null, now: 4_000,
});
expect(earthCast.ok, earthCast.reason).toBe(true);
const earthAura = useCombatStore.getState().auras.find((aura) => (
aura.sourceId === PLAYER_AGGRO_ID && aura.targetId === PLAYER_AURA_ENTITY_ID
&& aura.definition.procs?.some((proc) => proc.id.endsWith(":earth-shield"))
));
expect(earthAura, useCombatStore.getState().auras.map((aura) => `${aura.sourceId}:${aura.targetId}:${aura.definition.name}:${aura.definition.procs?.map((proc) => proc.id).join(",")}`).join(" | "))
.toBeDefined();
expect(earthAura?.definition.procs?.find((proc) => proc.id.endsWith(":earth-shield"))).toMatchObject({
charges: 12,
internalCooldownMs: 3_000,
});
registerAttacker("shaman-player-shield-attacker", PLAYER_AGGRO_ID, 4_000);
useCombatStore.getState().advanceMobCombat(5_450);
const lightningProc = [...useCombatStore.getState().combatEvents].reverse().find((event) => (
event.kind === "damage" && event.sourceActorId === PLAYER_AGGRO_ID
&& event.abilityId?.endsWith(":lightning-shield")
));
const lightningBase = lightning.effect.aura.procs?.find((proc) => proc.id.endsWith(":lightning-shield"))?.action;
expect(lightningBase?.kind).toBe("damage");
if (lightningBase?.kind !== "damage") throw new Error("Lightning Shield damage proc is missing");
expect(lightningProc?.rawAmount).toBe(Math.round(lightningBase.amount * 1.6));
expect(useCombatStore.getState().auras.find((aura) => aura.definition.exclusiveGroup === "shaman:elemental-shield")
?.procStates.find((proc) => proc.procId.endsWith(":lightning-shield"))?.remainingCharges).toBe(2);
useCombatStore.getState().advanceMobCombat(5_450);
const tidalAuraAtFirstHeal = useCombatStore.getState().auras.find((aura) => (
aura.sourceId === PLAYER_AGGRO_ID && aura.targetId === PLAYER_AGGRO_ID
&& aura.definition.utilityTags?.includes(WOW335_SHAMAN_TIDAL_WAVES_AURA_TAG)
));
const earthProcEvents = () => useCombatStore.getState().combatEvents.filter((event) => (
event.kind === "healing" && event.sourceActorId === PLAYER_AGGRO_ID
&& event.abilityId?.endsWith(":earth-shield")
));
const earthBase = earthAura?.definition.procs?.find((proc) => proc.id.endsWith(":earth-shield"))?.action;
expect(earthBase?.kind).toBe("heal");
if (earthBase?.kind !== "heal") throw new Error("Earth Shield heal proc is missing");
expect(earthProcEvents()[0]?.rawAmount).toBe(Math.round(earthBase.amount * 1.3));
expect(tidalAuraAtFirstHeal?.stacks).toBe(1);
useCombatStore.getState().advanceMobCombat(7_350);
useCombatStore.getState().advanceMobCombat(9_250);
expect(useCombatStore.getState().auras.find((aura) => (
aura.sourceId === PLAYER_AGGRO_ID && aura.targetId === PLAYER_AGGRO_ID
&& aura.definition.utilityTags?.includes(WOW335_SHAMAN_TIDAL_WAVES_AURA_TAG)
))?.stacks).toBe(1);
let now = 9_250;
while (earthProcEvents().length < 3 && now < 30_000) {
now += 1_900;
useCombatStore.getState().advanceMobCombat(now);
}
expect(earthProcEvents()).toHaveLength(3);
expect(earthProcEvents()[2].occurredAt - earthProcEvents()[0].occurredAt).toBeGreaterThanOrEqual(5_000);
expect(useCombatStore.getState().auras.find((aura) => (
aura.sourceId === PLAYER_AGGRO_ID && aura.targetId === PLAYER_AGGRO_ID
&& aura.definition.utilityTags?.includes(WOW335_SHAMAN_TIDAL_WAVES_AURA_TAG)
))?.stacks).toBe(2);
expect(useCombatStore.getState().auras.find((aura) => aura.instanceId === earthAura?.instanceId)
?.procStates.find((proc) => proc.procId.endsWith(":earth-shield"))?.remainingCharges).toBe(9);
});
it("applies Improved Earth Shield charge and healing bonuses to a generated companion's shield proc", () => {
const shamanBase = createConfiguredPartyMember({
name: "Shield Shaman",
classId: "shaman",
specialization: "Restoration",
specializationId: "shaman:restoration",
talentTreeId: "shaman-restoration",
talentRanks: { "talent-607": 3, "talent-2059": 3, "talent-2063": 3 },
role: "healer",
}, 80, 0, "shaman-shields-test");
const tankBase = createConfiguredPartyMember({
name: "Shield Tank",
classId: "warrior",
specialization: "Protection",
specializationId: "warrior:protection",
talentTreeId: "warrior-protection",
role: "tank",
}, 80, 1, "shaman-shields-test");
const shaman = { ...shamanBase, gearStats: EMPTY_ITEM_STATS, health: shamanBase.maxHealth };
const tank = { ...tankBase, gearStats: EMPTY_ITEM_STATS, health: Math.round(tankBase.maxHealth * 0.35) };
usePartyStore.setState({ members: [shaman, tank] });
registerPartyRuntimePosition(shaman.id, [0, 0, 0]);
registerPartyRuntimePosition(tank.id, [0, 0, 0]);
useCombatStore.getState().synchronizeActors();
const earth = shieldAuraEffect("Earth Shield");
for (const effect of earth.ability.effects.filter((candidate) => candidate.kind === "apply-aura")) {
if (effect.kind === "apply-aura") {
expect(useCombatStore.getState().applyActorAura(shaman.id, tank.id, earth.ability, effect, 500)).toBe(true);
}
}
const aura = useCombatStore.getState().auras.find((candidate) => (
candidate.sourceId === shaman.id && candidate.targetId === tank.id
&& candidate.definition.procs?.some((proc) => proc.id.endsWith(":earth-shield"))
));
expect(aura?.definition.procs?.find((proc) => proc.id.endsWith(":earth-shield"))).toMatchObject({
charges: 12,
internalCooldownMs: 3_000,
});
registerAttacker("shaman-party-shield-attacker", tank.id);
useCombatStore.getState().advanceMobCombat(1_450);
const procEvent = [...useCombatStore.getState().combatEvents].reverse().find((event) => (
event.kind === "healing" && event.sourceActorId === shaman.id
&& event.targetActorId === tank.id && event.abilityId?.endsWith(":earth-shield")
));
const baseAction = aura?.definition.procs?.find((proc) => proc.id.endsWith(":earth-shield"))?.action;
expect(baseAction?.kind).toBe("heal");
if (baseAction?.kind !== "heal") throw new Error("Earth Shield heal proc is missing");
expect(procEvent?.rawAmount).toBe(Math.round(baseAction.amount * 1.3));
expect(procEvent?.effectiveAmount).toBeGreaterThan(0);
expect(useCombatStore.getState().auras.find((candidate) => candidate.instanceId === aura?.instanceId)
?.procStates.find((proc) => proc.procId.endsWith(":earth-shield"))?.remainingCharges).toBe(11);
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]) }),
}));
});
});