Update Healer Man server
This commit is contained in:
1 parent
35dfdaed61
commit
4fb7a0b9c8
161 files changed
+24618
-529
No files matched your search
@@ -0,0 +1,268 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
|
||||
import { clearPartyRuntimeRegistry, registerPartyRuntimePosition } from "./partyRuntimeRegistry";
|
||||
import type { OnlineSessionPlayer } from "../app/onlineSessionTypes";
|
||||
import { installCombatClock } from "./combatClock";
|
||||
import { createDeathKnightRunes } from "./combatActors";
|
||||
import { useCombatStore, PLAYER_AURA_ENTITY_ID, type CombatSummon } from "./combatStore";
|
||||
import { usePartyStore } from "./partyStore";
|
||||
import { configureOnlineSessionRuntime, onlinePlayerActorId } from "./onlineSessionRuntime";
|
||||
import {
|
||||
WOW335_DEATH_KNIGHT_MARK_OF_BLOOD_TAG,
|
||||
WOW335_DEATH_KNIGHT_MARK_OF_BLOOD_ABILITY_ID,
|
||||
} from "./wow335DeathKnightMarkOfBloodRuntime";
|
||||
|
||||
let now = 1_000;
|
||||
let restoreClock: (() => void) | undefined;
|
||||
|
||||
function markAbility() {
|
||||
const ability = useCombatStore.getState().abilities.find((candidate) => candidate.dbcSpellId === 49005);
|
||||
if (!ability) throw new Error("Missing generated Death Knight Mark of Blood spell.");
|
||||
return ability;
|
||||
}
|
||||
|
||||
function registerAttacker(id: string) {
|
||||
useCombatStore.getState().registerMob(id, {
|
||||
name: id,
|
||||
maxHealth: 100_000,
|
||||
xpReward: 0,
|
||||
attacks: [{
|
||||
id: `${id}-strike`,
|
||||
name: "Strike",
|
||||
delivery: "melee",
|
||||
target: "primary",
|
||||
school: "physical",
|
||||
animation: "attack",
|
||||
range: 5,
|
||||
cooldownMs: 1_000,
|
||||
damageMultiplier: 0.01,
|
||||
}],
|
||||
});
|
||||
setMobPosition(id, [2, 0, 0]);
|
||||
}
|
||||
|
||||
function castMark() {
|
||||
const ability = markAbility();
|
||||
const result = useCombatStore.getState().castAbility(ability.id, { now });
|
||||
expect(result.ok).toBe(true);
|
||||
return ability;
|
||||
}
|
||||
|
||||
function remoteVictim(health = 9_000): OnlineSessionPlayer {
|
||||
return {
|
||||
accountId: "mark-remote-victim",
|
||||
username: "Mark victim",
|
||||
character: {
|
||||
id: "mark-remote-character",
|
||||
name: "Mark victim",
|
||||
classId: "warrior",
|
||||
categoryId: "wow",
|
||||
raceId: "human",
|
||||
gender: "female",
|
||||
level: 80,
|
||||
},
|
||||
role: "tank",
|
||||
position: [0, 0, 0],
|
||||
yaw: 0,
|
||||
grounded: true,
|
||||
health,
|
||||
maxHealth: 10_000,
|
||||
resource: 0,
|
||||
maxResource: 100,
|
||||
resourceName: "Rage",
|
||||
selectedTargetId: null,
|
||||
activeCast: null,
|
||||
animationEvent: null,
|
||||
equipment: [],
|
||||
updatedAt: now,
|
||||
};
|
||||
}
|
||||
|
||||
describe("WoW Death Knight Mark of Blood combat integration", () => {
|
||||
beforeEach(() => {
|
||||
now = 1_000;
|
||||
restoreClock = installCombatClock(() => now);
|
||||
clearMobRuntimeRegistry();
|
||||
clearPartyRuntimeRegistry();
|
||||
useCombatStore.getState().initializeCharacter({ classId: "death-knight", level: 80, xp: 0 });
|
||||
usePartyStore.getState().initializeParty(
|
||||
"wow335-death-knight-mark-of-blood", 80, useCombatStore.getState().health, "tank", 5,
|
||||
);
|
||||
useCombatStore.setState((state) => ({
|
||||
globalCooldownEndsAt: 0,
|
||||
deathKnightRunes: createDeathKnightRunes(now),
|
||||
talentRanks: { ...state.talentRanks, "talent-1949": 1 },
|
||||
resource: 100,
|
||||
resourcePools: { "runic-power": 100 },
|
||||
}));
|
||||
registerAttacker("mark-target");
|
||||
useCombatStore.getState().selectMob("mark-target");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
restoreClock?.();
|
||||
restoreClock = undefined;
|
||||
configureOnlineSessionRuntime({ active: false, authority: true, localAccountId: null });
|
||||
});
|
||||
|
||||
it("casts the generated talent spell with twenty charges and a twenty-second duration", () => {
|
||||
castMark();
|
||||
const mark = useCombatStore.getState().auras.find((aura) => aura.targetId === "mark-target"
|
||||
&& aura.definition.utilityTags?.includes(WOW335_DEATH_KNIGHT_MARK_OF_BLOOD_TAG));
|
||||
|
||||
expect(mark).toMatchObject({
|
||||
sourceId: PLAYER_AURA_ENTITY_ID,
|
||||
stacks: 20,
|
||||
expiresAt: now + 20_000,
|
||||
});
|
||||
expect(mark?.definition.maxStacks).toBe(20);
|
||||
});
|
||||
|
||||
it("heals the player when the marked enemy lands an attack and consumes one charge", () => {
|
||||
castMark();
|
||||
const maximumHealth = useCombatStore.getState().maxHealth;
|
||||
const healthBefore = maximumHealth - 5_000;
|
||||
useCombatStore.setState({ health: healthBefore });
|
||||
useCombatStore.getState().damageMob("mark-target", 1, now);
|
||||
|
||||
useCombatStore.getState().advanceMobCombat(now + 450, [0, 0, 0]);
|
||||
|
||||
const state = useCombatStore.getState();
|
||||
const damage = [...state.combatEvents].reverse().find((event) => event.kind === "damage"
|
||||
&& event.sourceActorId === "mark-target"
|
||||
&& event.targetActorId === PLAYER_AURA_ENTITY_ID);
|
||||
const healing = [...state.combatEvents].reverse().find((event) => event.kind === "healing"
|
||||
&& event.abilityId === WOW335_DEATH_KNIGHT_MARK_OF_BLOOD_ABILITY_ID
|
||||
&& event.targetActorId === PLAYER_AURA_ENTITY_ID);
|
||||
const mark = state.auras.find((aura) => aura.targetId === "mark-target"
|
||||
&& aura.definition.utilityTags?.includes(WOW335_DEATH_KNIGHT_MARK_OF_BLOOD_TAG));
|
||||
|
||||
expect(damage?.effectiveAmount).toBeGreaterThan(0);
|
||||
expect(healing).toMatchObject({ rawAmount: Math.round(maximumHealth * 0.04) });
|
||||
expect(healing?.effectiveAmount).toBe(healing?.rawAmount);
|
||||
expect(state.health).toBe(healthBefore - damage!.effectiveAmount + healing!.effectiveAmount);
|
||||
expect(mark?.stacks).toBe(19);
|
||||
});
|
||||
|
||||
it("heals a party companion when the marked enemy lands an attack on them", () => {
|
||||
const target = usePartyStore.getState().members[0]!;
|
||||
const maxHealth = target.maxHealth;
|
||||
const healthBefore = maxHealth - 1_000;
|
||||
usePartyStore.setState((state) => ({
|
||||
members: state.members.map((member) => member.id === target.id
|
||||
? { ...member, health: healthBefore }
|
||||
: member),
|
||||
}));
|
||||
registerPartyRuntimePosition(target.id, [0, 0, 0]);
|
||||
castMark();
|
||||
useCombatStore.getState().damageMob("mark-target", 50_000, now, {
|
||||
actorId: target.id,
|
||||
role: target.role ?? "damage",
|
||||
});
|
||||
|
||||
useCombatStore.getState().advanceMobCombat(now + 450, [0, 0, 0]);
|
||||
|
||||
const state = useCombatStore.getState();
|
||||
const memberAfter = usePartyStore.getState().members.find((member) => member.id === target.id)!;
|
||||
const damage = [...state.combatEvents].reverse().find((event) => event.kind === "damage"
|
||||
&& event.sourceActorId === "mark-target"
|
||||
&& event.targetActorId === target.id);
|
||||
const healing = [...state.combatEvents].reverse().find((event) => event.kind === "healing"
|
||||
&& event.abilityId === WOW335_DEATH_KNIGHT_MARK_OF_BLOOD_ABILITY_ID
|
||||
&& event.targetActorId === target.id);
|
||||
const mark = state.auras.find((aura) => aura.targetId === "mark-target"
|
||||
&& aura.definition.utilityTags?.includes(WOW335_DEATH_KNIGHT_MARK_OF_BLOOD_TAG));
|
||||
|
||||
expect(damage?.effectiveAmount).toBeGreaterThan(0);
|
||||
expect(healing).toMatchObject({ rawAmount: Math.round(maxHealth * 0.04) });
|
||||
expect(healing?.effectiveAmount).toBe(healing?.rawAmount);
|
||||
expect(memberAfter.health).toBe(healthBefore - damage!.effectiveAmount + healing!.effectiveAmount);
|
||||
expect(mark?.stacks).toBe(19);
|
||||
});
|
||||
|
||||
it("heals a summoned companion from the marked enemy's damage event", () => {
|
||||
const summon: CombatSummon = {
|
||||
id: "mark-summon-victim",
|
||||
sourceAbilityId: "test-summon",
|
||||
creatureId: 0,
|
||||
targetId: "mark-target",
|
||||
expiresAt: now + 60_000,
|
||||
kind: "summon",
|
||||
name: "Companion",
|
||||
ownerActorId: PLAYER_AURA_ENTITY_ID,
|
||||
health: 9_000,
|
||||
maxHealth: 10_000,
|
||||
position: [0, 0, 0],
|
||||
nextActionAt: 0,
|
||||
pulseIntervalMs: 1_000,
|
||||
attackDamage: 0,
|
||||
abilityIds: [],
|
||||
resources: {},
|
||||
cooldowns: {},
|
||||
globalCooldownEndsAt: 0,
|
||||
cast: null,
|
||||
controlledUntil: 0,
|
||||
schoolLockedUntil: {},
|
||||
};
|
||||
useCombatStore.setState({ summons: [summon] });
|
||||
castMark();
|
||||
useCombatStore.getState().damageMob("mark-target", 50_000, now, {
|
||||
actorId: summon.id,
|
||||
role: "tank",
|
||||
});
|
||||
|
||||
useCombatStore.getState().advanceMobCombat(now + 450, [0, 0, 0]);
|
||||
|
||||
const state = useCombatStore.getState();
|
||||
const summonAfter = state.summons.find((candidate) => candidate.id === summon.id)!;
|
||||
const damage = [...state.combatEvents].reverse().find((event) => event.kind === "damage"
|
||||
&& event.sourceActorId === "mark-target"
|
||||
&& event.targetActorId === summon.id);
|
||||
const healing = [...state.combatEvents].reverse().find((event) => event.kind === "healing"
|
||||
&& event.abilityId === WOW335_DEATH_KNIGHT_MARK_OF_BLOOD_ABILITY_ID
|
||||
&& event.targetActorId === summon.id);
|
||||
const mark = state.auras.find((aura) => aura.targetId === "mark-target"
|
||||
&& aura.definition.utilityTags?.includes(WOW335_DEATH_KNIGHT_MARK_OF_BLOOD_TAG));
|
||||
|
||||
expect(damage?.effectiveAmount).toBeGreaterThan(0);
|
||||
expect(healing).toMatchObject({ rawAmount: 400 });
|
||||
expect(summonAfter.health).toBe(summon.health - damage!.effectiveAmount + healing!.effectiveAmount);
|
||||
expect(mark?.stacks).toBe(19);
|
||||
});
|
||||
|
||||
it("heals an online player from the marked enemy's damage event", () => {
|
||||
const victim = remoteVictim();
|
||||
const victimId = onlinePlayerActorId(victim.accountId);
|
||||
configureOnlineSessionRuntime({
|
||||
active: true,
|
||||
authority: true,
|
||||
localAccountId: "mark-local",
|
||||
players: [victim],
|
||||
});
|
||||
registerPartyRuntimePosition(victimId, victim.position);
|
||||
useCombatStore.getState().synchronizeActors();
|
||||
castMark();
|
||||
useCombatStore.getState().damageMob("mark-target", 50_000, now, {
|
||||
actorId: victimId,
|
||||
role: victim.role ?? "damage",
|
||||
});
|
||||
|
||||
useCombatStore.getState().advanceMobCombat(now + 450, [0, 0, 0]);
|
||||
|
||||
const state = useCombatStore.getState();
|
||||
const victimAfter = state.actors[victimId]!;
|
||||
const damage = [...state.combatEvents].reverse().find((event) => event.kind === "damage"
|
||||
&& event.sourceActorId === "mark-target"
|
||||
&& event.targetActorId === victimId);
|
||||
const healing = [...state.combatEvents].reverse().find((event) => event.kind === "healing"
|
||||
&& event.abilityId === WOW335_DEATH_KNIGHT_MARK_OF_BLOOD_ABILITY_ID
|
||||
&& event.targetActorId === victimId);
|
||||
const mark = state.auras.find((aura) => aura.targetId === "mark-target"
|
||||
&& aura.definition.utilityTags?.includes(WOW335_DEATH_KNIGHT_MARK_OF_BLOOD_TAG));
|
||||
|
||||
expect(damage?.effectiveAmount).toBeGreaterThan(0);
|
||||
expect(healing).toMatchObject({ rawAmount: 400 });
|
||||
expect(victimAfter.health).toBe(victim.health - damage!.effectiveAmount + healing!.effectiveAmount);
|
||||
expect(mark?.stacks).toBe(19);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user