Update Healer Man server
This commit is contained in:
1 parent
35dfdaed61
commit
4fb7a0b9c8
161 files changed
+24618
-529
No files matched your search
@@ -4,6 +4,7 @@ import { clearPartyRuntimeRegistry, registerPartyRuntimePosition } from "./party
|
||||
import { installCombatClock } from "./combatClock";
|
||||
import { createDeathKnightRunes } from "./combatActors";
|
||||
import { useCombatStore, PLAYER_AURA_ENTITY_ID } from "./combatStore";
|
||||
import { applyAura as applyCombatAura } from "./combatAuras";
|
||||
import { usePartyStore } from "./partyStore";
|
||||
import { talentNodesForTree } from "./talentCatalog";
|
||||
|
||||
@@ -60,6 +61,31 @@ function registerXpTarget(id: string, xpReward: number) {
|
||||
setMobPosition(id, [2, 0, 0]);
|
||||
}
|
||||
|
||||
function installAntiMagicShell(targetId: string, maximumHealth: number) {
|
||||
const result = applyCombatAura(useCombatStore.getState().auras, {
|
||||
definition: {
|
||||
id: "wow335:48707:anti-magic-shell",
|
||||
name: "Anti-Magic Shell",
|
||||
disposition: "buff",
|
||||
durationMs: 5_000,
|
||||
maxStacks: 1,
|
||||
stackBehavior: "replace",
|
||||
utilityTags: ["wow335-death-knight-anti-magic-shell"],
|
||||
absorbs: [{
|
||||
id: "wow335:48707:anti-magic-shell-absorb",
|
||||
amount: maximumHealth * 0.5,
|
||||
schools: ["holy", "fire", "nature", "frost", "shadow", "arcane"],
|
||||
fraction: 0.75,
|
||||
}],
|
||||
},
|
||||
sourceId: targetId,
|
||||
targetId,
|
||||
now,
|
||||
});
|
||||
useCombatStore.setState({ auras: [...result.auras] });
|
||||
return result.aura;
|
||||
}
|
||||
|
||||
describe("Death Knight source-backed runtime mechanics", () => {
|
||||
beforeEach(() => {
|
||||
now = 1_000;
|
||||
@@ -117,6 +143,7 @@ describe("Death Knight source-backed runtime mechanics", () => {
|
||||
it("absorbs three quarters of magical damage up to half of maximum health", () => {
|
||||
const shell = ability("Anti-Magic Shell");
|
||||
expect(useCombatStore.getState().castAbility(shell.id, { now }).ok).toBe(true);
|
||||
useCombatStore.setState({ resource: 0, resourcePools: { "runic-power": 0 } });
|
||||
const aura = useCombatStore.getState().auras.find((candidate) => candidate.targetId === PLAYER_AURA_ENTITY_ID
|
||||
&& candidate.definition.name === "Anti-Magic Shell");
|
||||
expect(aura?.absorbs[0]).toMatchObject({
|
||||
@@ -127,10 +154,141 @@ describe("Death Knight source-backed runtime mechanics", () => {
|
||||
|
||||
now += 1;
|
||||
expect(useCombatStore.getState().damagePlayer(100, "fire")).toBe(25);
|
||||
expect(useCombatStore.getState().resourcePools["runic-power"]).toBe(1.5);
|
||||
expect(useCombatStore.getState().resource).toBe(1.5);
|
||||
expect(useCombatStore.getState().auras.find((candidate) => candidate.instanceId === aura!.instanceId)
|
||||
?.absorbs[0].remaining).toBe(Math.round(useCombatStore.getState().maxHealth * 0.5) - 75);
|
||||
});
|
||||
|
||||
it("caps AMS absorption at half maximum health before awarding proportional Runic Power", () => {
|
||||
useCombatStore.setState({
|
||||
health: 1_000,
|
||||
maxHealth: 1_000,
|
||||
resource: 20,
|
||||
resourcePools: { "runic-power": 20 },
|
||||
});
|
||||
expect(useCombatStore.getState().castAbility(ability("Anti-Magic Shell").id, { now }).ok).toBe(true);
|
||||
useCombatStore.setState({ resource: 0, resourcePools: { "runic-power": 0 } });
|
||||
|
||||
expect(useCombatStore.getState().damagePlayer(1_000, "fire", undefined, now + 1)).toBe(500);
|
||||
expect(useCombatStore.getState().resourcePools["runic-power"]).toBe(10);
|
||||
expect(useCombatStore.getState().auras.find((aura) => aura.targetId === PLAYER_AURA_ENTITY_ID
|
||||
&& aura.definition.utilityTags?.includes("wow335-death-knight-anti-magic-shell"))?.absorbs[0].remaining).toBe(0);
|
||||
});
|
||||
|
||||
it("grants Runic Power for magic absorbed during the normal enemy-attack path", () => {
|
||||
expect(useCombatStore.getState().castAbility(ability("Anti-Magic Shell").id, { now }).ok).toBe(true);
|
||||
useCombatStore.setState({ resource: 0, resourcePools: { "runic-power": 0 } });
|
||||
const shell = useCombatStore.getState().auras.find((aura) => aura.targetId === PLAYER_AURA_ENTITY_ID
|
||||
&& aura.definition.utilityTags?.includes("wow335-death-knight-anti-magic-shell"))!;
|
||||
const capacityBefore = shell.absorbs[0].remaining;
|
||||
useCombatStore.getState().registerMob("ams-player-attacker", {
|
||||
name: "AMS player attacker",
|
||||
maxHealth: 1_000,
|
||||
xpReward: 0,
|
||||
attacks: [{
|
||||
id: "ams-player-fire",
|
||||
name: "Fire strike",
|
||||
delivery: "melee",
|
||||
target: "primary",
|
||||
school: "fire",
|
||||
animation: "attack",
|
||||
range: 5,
|
||||
cooldownMs: 1_000,
|
||||
damageMultiplier: 1,
|
||||
}],
|
||||
});
|
||||
setMobPosition("ams-player-attacker", [2, 0, 0]);
|
||||
useCombatStore.getState().damageMob("ams-player-attacker", 1, now);
|
||||
|
||||
useCombatStore.getState().advanceMobCombat(now + 450, [0, 0, 0]);
|
||||
const shellAfterHit = useCombatStore.getState().auras.find((aura) => aura.instanceId === shell.instanceId);
|
||||
const absorbed = capacityBefore - (shellAfterHit?.absorbs[0].remaining ?? 0);
|
||||
expect(absorbed).toBeGreaterThan(0);
|
||||
expect(useCombatStore.getState().resourcePools["runic-power"]).toBeCloseTo(absorbed * 0.02);
|
||||
});
|
||||
|
||||
it("clamps AMS Runic Power gain and ignores physical, missing, and expired shells", () => {
|
||||
expect(useCombatStore.getState().castAbility(ability("Anti-Magic Shell").id, { now }).ok).toBe(true);
|
||||
useCombatStore.setState({ resource: 0, resourcePools: { "runic-power": 0 } });
|
||||
useCombatStore.getState().damagePlayer(100, "physical", undefined, now + 1);
|
||||
expect(useCombatStore.getState().resourcePools["runic-power"]).toBe(0);
|
||||
|
||||
useCombatStore.setState({ auras: [] });
|
||||
useCombatStore.getState().damagePlayer(100, "fire", undefined, now + 2);
|
||||
expect(useCombatStore.getState().resourcePools["runic-power"]).toBe(0);
|
||||
|
||||
const shell = installAntiMagicShell(PLAYER_AURA_ENTITY_ID, useCombatStore.getState().maxHealth);
|
||||
useCombatStore.setState({
|
||||
resource: 99,
|
||||
resourcePools: { "runic-power": 99 },
|
||||
auras: useCombatStore.getState().auras.map((aura) => aura.instanceId === shell.instanceId
|
||||
? { ...aura, expiresAt: now + 3 }
|
||||
: aura),
|
||||
});
|
||||
useCombatStore.getState().damagePlayer(100, "fire", undefined, now + 4);
|
||||
expect(useCombatStore.getState().resourcePools["runic-power"]).toBe(99);
|
||||
|
||||
useCombatStore.setState({
|
||||
resource: 99,
|
||||
resourcePools: { "runic-power": 99 },
|
||||
auras: [shell],
|
||||
});
|
||||
useCombatStore.getState().damagePlayer(100, "fire", undefined, now + 1);
|
||||
expect(useCombatStore.getState().resourcePools["runic-power"]).toBe(100);
|
||||
});
|
||||
|
||||
it("grants a Death Knight companion Runic Power from its own live AMS absorb", () => {
|
||||
const target = usePartyStore.getState().members[0];
|
||||
if (!target) throw new Error("Expected a party member for the companion AMS test.");
|
||||
const companion = {
|
||||
...target,
|
||||
classId: "death-knight" as const,
|
||||
resourceType: "runic-power" as const,
|
||||
resource: 0,
|
||||
maxResource: 100,
|
||||
resourcePools: { ...target.resourcePools, "runic-power": 0 },
|
||||
};
|
||||
usePartyStore.setState((state) => ({
|
||||
members: state.members.map((member) => member.id === target.id ? companion : member),
|
||||
}));
|
||||
useCombatStore.setState((state) => {
|
||||
const actors = { ...state.actors };
|
||||
delete actors[target.id];
|
||||
return { ...state, actors };
|
||||
});
|
||||
registerPartyRuntimePosition(target.id, [0, 0, 0]);
|
||||
const aura = installAntiMagicShell(target.id, companion.maxHealth);
|
||||
useCombatStore.getState().registerMob("ams-companion-attacker", {
|
||||
name: "AMS companion attacker",
|
||||
maxHealth: 1_000,
|
||||
xpReward: 0,
|
||||
attacks: [{
|
||||
id: "ams-companion-fire",
|
||||
name: "Fire strike",
|
||||
delivery: "melee",
|
||||
target: "primary",
|
||||
school: "fire",
|
||||
animation: "attack",
|
||||
range: 5,
|
||||
cooldownMs: 1_000,
|
||||
damageMultiplier: 1,
|
||||
}],
|
||||
});
|
||||
setMobPosition("ams-companion-attacker", [2, 0, 0]);
|
||||
useCombatStore.getState().damageMob("ams-companion-attacker", 1, now, {
|
||||
actorId: target.id,
|
||||
role: target.role,
|
||||
});
|
||||
|
||||
useCombatStore.getState().advanceMobCombat(now + 450, [0, 0, 0]);
|
||||
const updated = usePartyStore.getState().members.find((member) => member.id === target.id)!;
|
||||
const shellAfterHit = useCombatStore.getState().auras.find((candidate) => candidate.instanceId === aura.instanceId);
|
||||
const absorbed = companion.maxHealth * 0.5 - (shellAfterHit?.absorbs[0].remaining ?? 0);
|
||||
expect(absorbed).toBeGreaterThan(0);
|
||||
expect(updated.resourcePools["runic-power"]).toBeCloseTo(absorbed * 0.02);
|
||||
});
|
||||
|
||||
it("blocks harmful magic controls during Anti-Magic Shell while allowing physical controls", () => {
|
||||
const shell = ability("Anti-Magic Shell");
|
||||
expect(useCombatStore.getState().castAbility(shell.id, { now }).ok).toBe(true);
|
||||
|
||||
Reference in new issue
Block a user