Update Healer Man server
This commit is contained in:
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]) }),
|
||||
}));
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user