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,237 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { abilitiesForCharacter } from "./abilityCatalog";
|
||||
import { installCombatClock } from "./combatClock";
|
||||
import { createDeathKnightRunes } from "./combatActors";
|
||||
import { useCombatStore } from "./combatStore";
|
||||
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
|
||||
import {
|
||||
advanceDungeonPartyCombat,
|
||||
resetPartyRuntimeClock,
|
||||
} from "./partyRuntime";
|
||||
import {
|
||||
clearPartyRuntimeRegistry,
|
||||
registerPartyRuntimePosition,
|
||||
updatePartyRuntimeTargetVisibility,
|
||||
} from "./partyRuntimeRegistry";
|
||||
import { createConfiguredPartyMember, usePartyStore } from "./partyStore";
|
||||
import { WOW335_DEATH_KNIGHT_REAPING_TALENT_ID } from "./wow335DeathKnightReapingRuntime";
|
||||
|
||||
const TARGET_ID = "reaping-target";
|
||||
const PARTY_SEED = "wow335-death-knight-reaping";
|
||||
let restoreClock: (() => void) | undefined;
|
||||
|
||||
function bloodStrike() {
|
||||
const found = abilitiesForCharacter("death-knight", null).find((ability) => ability.name === "Blood Strike");
|
||||
if (!found) throw new Error("Missing generated Death Knight Blood Strike.");
|
||||
return found;
|
||||
}
|
||||
|
||||
function pestilence() {
|
||||
const found = abilitiesForCharacter("death-knight", null).find((ability) => ability.name === "Pestilence");
|
||||
if (!found) throw new Error("Missing generated Death Knight Pestilence.");
|
||||
return found;
|
||||
}
|
||||
|
||||
function registerTarget() {
|
||||
useCombatStore.getState().registerMob(TARGET_ID, {
|
||||
name: "Reaping target",
|
||||
maxHealth: 1_000_000,
|
||||
xpReward: 0,
|
||||
});
|
||||
setMobPosition(TARGET_ID, [2, 0, 0]);
|
||||
useCombatStore.getState().selectMob(TARGET_ID);
|
||||
}
|
||||
|
||||
describe("WoW Death Knight Reaping combat integration", () => {
|
||||
beforeEach(() => {
|
||||
restoreClock = installCombatClock(() => 1_000);
|
||||
clearMobRuntimeRegistry();
|
||||
clearPartyRuntimeRegistry();
|
||||
resetPartyRuntimeClock();
|
||||
useCombatStore.getState().initializeCharacter({ classId: "death-knight", level: 80, xp: 0 });
|
||||
usePartyStore.getState().initializeParty("wow335-death-knight-reaping", 80,
|
||||
useCombatStore.getState().health, "tank", 5);
|
||||
registerTarget();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
restoreClock?.();
|
||||
restoreClock = undefined;
|
||||
});
|
||||
|
||||
it("keeps the player's Reaping conversion after Blood Strike's second direct damage packet", () => {
|
||||
useCombatStore.setState((state) => ({
|
||||
talentRanks: { ...state.talentRanks, [WOW335_DEATH_KNIGHT_REAPING_TALENT_ID]: 3 },
|
||||
deathKnightRunes: createDeathKnightRunes(1_000),
|
||||
}));
|
||||
const cast = useCombatStore.getState().castAbility(bloodStrike().id, {
|
||||
targetActorId: TARGET_ID,
|
||||
now: 1_000,
|
||||
});
|
||||
|
||||
expect(cast.ok).toBe(true);
|
||||
expect(useCombatStore.getState().combatEvents.filter((event) => (
|
||||
event.sourceActorId === "__player__" && event.abilityId === bloodStrike().id && event.kind === "damage"
|
||||
))).toHaveLength(2);
|
||||
expect(useCombatStore.getState().deathKnightRunes[0]).toMatchObject({
|
||||
slot: 0,
|
||||
baseType: "blood",
|
||||
type: "death",
|
||||
readyAt: expect.any(Number),
|
||||
deathRuneExpiresAt: 31_000,
|
||||
deathRuneConversions: [{ source: "reaping", expiresAt: 31_000 }],
|
||||
});
|
||||
});
|
||||
|
||||
it("spreads the player's disease through Pestilence before applying its single Reaping proc", () => {
|
||||
const abilities = abilitiesForCharacter("death-knight", null);
|
||||
const pest = pestilence();
|
||||
const icyTouch = abilities.find((ability) => ability.name === "Icy Touch")!;
|
||||
const secondaryTargetId = "reaping-player-pestilence-secondary";
|
||||
useCombatStore.getState().registerMob(secondaryTargetId, {
|
||||
name: "Pestilence secondary",
|
||||
maxHealth: 1_000_000,
|
||||
xpReward: 0,
|
||||
});
|
||||
setMobPosition(secondaryTargetId, [4, 0, 0]);
|
||||
useCombatStore.getState().damageMob(secondaryTargetId, 1, 999);
|
||||
|
||||
expect(useCombatStore.getState().castAbility(icyTouch.id, {
|
||||
targetActorId: TARGET_ID,
|
||||
now: 1_000,
|
||||
}).ok).toBe(true);
|
||||
useCombatStore.setState((state) => ({
|
||||
talentRanks: { ...state.talentRanks, [WOW335_DEATH_KNIGHT_REAPING_TALENT_ID]: 3 },
|
||||
deathKnightRunes: createDeathKnightRunes(2_500),
|
||||
}));
|
||||
|
||||
expect(useCombatStore.getState().castAbility(pest.id, {
|
||||
targetActorId: TARGET_ID,
|
||||
now: 2_500,
|
||||
}).ok).toBe(true);
|
||||
|
||||
expect(useCombatStore.getState().effects).toContainEqual(expect.objectContaining({
|
||||
kind: "dot",
|
||||
tag: "frost-fever",
|
||||
sourceActorId: "__player__",
|
||||
targetId: secondaryTargetId,
|
||||
}));
|
||||
expect(useCombatStore.getState().deathKnightRunes[0]).toMatchObject({
|
||||
slot: 0,
|
||||
baseType: "blood",
|
||||
type: "death",
|
||||
deathRuneExpiresAt: 32_500,
|
||||
deathRuneConversions: [{ source: "reaping", expiresAt: 32_500 }],
|
||||
});
|
||||
expect(useCombatStore.getState().deathKnightRunes[1]).toMatchObject({
|
||||
slot: 1,
|
||||
baseType: "blood",
|
||||
type: "blood",
|
||||
});
|
||||
});
|
||||
|
||||
it("converts the generated companion's just-spent Blood rune on its landed Blood Strike", () => {
|
||||
const strike = bloodStrike();
|
||||
const abilities = abilitiesForCharacter("death-knight", null);
|
||||
const member = createConfiguredPartyMember({
|
||||
name: "Reaping DK",
|
||||
classId: "death-knight",
|
||||
specialization: "Blood",
|
||||
specializationId: "death-knight:blood",
|
||||
talentTreeId: "death-knight-blood",
|
||||
talentRanks: { [WOW335_DEATH_KNIGHT_REAPING_TALENT_ID]: 3 },
|
||||
role: "damage",
|
||||
}, 80, 0, PARTY_SEED);
|
||||
usePartyStore.setState({ members: [{
|
||||
...member,
|
||||
talentRanks: { [WOW335_DEATH_KNIGHT_REAPING_TALENT_ID]: 3 },
|
||||
runes: createDeathKnightRunes(1_000),
|
||||
cooldowns: Object.fromEntries(abilities
|
||||
.filter((ability) => ability.id !== strike.id)
|
||||
.map((ability) => [ability.id, 60_000])),
|
||||
}] });
|
||||
usePartyStore.getState().setCommand("attack");
|
||||
useCombatStore.getState().synchronizeActors();
|
||||
registerPartyRuntimePosition(member.id, [0, 0, 0]);
|
||||
useCombatStore.getState().damageMob(TARGET_ID, 1, 100);
|
||||
updatePartyRuntimeTargetVisibility(member.id, TARGET_ID, true);
|
||||
|
||||
advanceDungeonPartyCombat(1_000);
|
||||
|
||||
const updated = usePartyStore.getState().members.find((candidate) => candidate.id === member.id);
|
||||
expect(useCombatStore.getState().combatEvents.some((event) => (
|
||||
event.sourceActorId === member.id && event.abilityId === strike.id && event.kind === "damage"
|
||||
))).toBe(true);
|
||||
expect(updated?.runes[0]).toMatchObject({
|
||||
slot: 0,
|
||||
baseType: "blood",
|
||||
type: "death",
|
||||
readyAt: expect.any(Number),
|
||||
deathRuneExpiresAt: 31_000,
|
||||
deathRuneConversions: [{ source: "reaping", expiresAt: 31_000 }],
|
||||
});
|
||||
expect(useCombatStore.getState().actors[member.id]?.runes[0]).toMatchObject({ type: "death" });
|
||||
});
|
||||
|
||||
it("spreads a companion-owned disease with Pestilence and procs Reaping once for the successful cast", () => {
|
||||
const pest = pestilence();
|
||||
const abilities = abilitiesForCharacter("death-knight", null);
|
||||
const member = createConfiguredPartyMember({
|
||||
name: "Reaping Pestilence DK",
|
||||
classId: "death-knight",
|
||||
specialization: "Blood",
|
||||
specializationId: "death-knight:blood",
|
||||
talentTreeId: "death-knight-blood",
|
||||
talentRanks: { [WOW335_DEATH_KNIGHT_REAPING_TALENT_ID]: 3 },
|
||||
role: "damage",
|
||||
}, 80, 0, `${PARTY_SEED}-pestilence`);
|
||||
const secondaryTargetId = "reaping-pestilence-secondary";
|
||||
useCombatStore.getState().registerMob(secondaryTargetId, {
|
||||
name: "Pestilence secondary",
|
||||
maxHealth: 1_000_000,
|
||||
xpReward: 0,
|
||||
});
|
||||
setMobPosition(secondaryTargetId, [4, 0, 0]);
|
||||
usePartyStore.setState({ members: [{
|
||||
...member,
|
||||
talentRanks: { [WOW335_DEATH_KNIGHT_REAPING_TALENT_ID]: 3 },
|
||||
runes: createDeathKnightRunes(1_000),
|
||||
cooldowns: Object.fromEntries(abilities
|
||||
.filter((ability) => ability.id !== pest.id)
|
||||
.map((ability) => [ability.id, 60_000])),
|
||||
}] });
|
||||
usePartyStore.getState().setCommand("attack");
|
||||
useCombatStore.getState().synchronizeActors();
|
||||
registerPartyRuntimePosition(member.id, [0, 0, 0]);
|
||||
useCombatStore.getState().damageMob(TARGET_ID, 1, 100);
|
||||
useCombatStore.getState().damageMob(secondaryTargetId, 1, 100);
|
||||
updatePartyRuntimeTargetVisibility(member.id, TARGET_ID, true);
|
||||
|
||||
const icyTouch = abilities.find((ability) => ability.name === "Icy Touch")!;
|
||||
const frostFever = icyTouch.effects.find((effect) => effect.kind === "dot")!;
|
||||
expect(useCombatStore.getState().applyTimedActorEffect(
|
||||
member.id,
|
||||
TARGET_ID,
|
||||
icyTouch,
|
||||
frostFever as Extract<typeof frostFever, { kind: "dot" }>,
|
||||
1_000,
|
||||
)).toBe(true);
|
||||
|
||||
advanceDungeonPartyCombat(1_000);
|
||||
|
||||
const updated = usePartyStore.getState().members.find((candidate) => candidate.id === member.id);
|
||||
expect(useCombatStore.getState().effects).toContainEqual(expect.objectContaining({
|
||||
kind: "dot",
|
||||
tag: "frost-fever",
|
||||
sourceActorId: member.id,
|
||||
targetId: secondaryTargetId,
|
||||
}));
|
||||
expect(updated?.runes[0]).toMatchObject({
|
||||
slot: 0,
|
||||
baseType: "blood",
|
||||
type: "death",
|
||||
deathRuneExpiresAt: 31_000,
|
||||
deathRuneConversions: [{ source: "reaping", expiresAt: 31_000 }],
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user