forked from phenom/healer-man
Update Healer Man server
This commit is contained in:
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));
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user