Update Healer Man server

This commit is contained in:
phenom committed 2026-09-30 09:57:13 -04:00
1 parent 35dfdaed61
commit 4fb7a0b9c8
161 files changed
+24618 -529

No files matched your search

+140 -1
View File
@@ -1,11 +1,14 @@
import { beforeEach, describe, expect, it } from "vitest";
import { abilityById } from "./abilityCatalog";
import { abilitiesForClass, abilityById } from "./abilityCatalog";
import { DEFAULT_ENEMY_ATTACK } from "./enemyAttacks";
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
import { PLAYER_AURA_ENTITY_ID, useCombatStore } from "./combatStore";
import { clearPartyRuntimeRegistry, registerPartyRuntimePosition } from "./partyRuntimeRegistry";
import { createConfiguredPartyMember, usePartyStore } from "./partyStore";
import {
ROM_KNIGHT_DIVINE_PUNISHMENT_TAG,
ROM_KNIGHT_GODS_VENGEANCE_TAG,
ROM_KNIGHT_HOLY_LIGHTS_FURY_TAG,
ROM_KNIGHT_SHIELD_OF_VALOR_TAG,
} from "./romKnightOverrides";
import { romEffectsForCast } from "./romCombatMechanics";
@@ -32,6 +35,7 @@ function godsVengeanceCastTime(wantProc: boolean): number {
describe("RuneWaker Knight periodic skills", () => {
beforeEach(() => {
clearMobRuntimeRegistry();
clearPartyRuntimeRegistry();
useCombatStore.setState({ playerPosition: [0, 0, 0] });
});
@@ -105,6 +109,13 @@ describe("RuneWaker Knight periodic skills", () => {
expect(useCombatStore.getState().comboTargetId).toBe("war-prayer-target");
expect(useCombatStore.getState().comboPoints).toBe(Math.min(tick, 4));
}
const punishment = abilityById("rom-knight-general-0")!;
expect(useCombatStore.getState().castAbility(punishment.id, undefined, 20_000)).toMatchObject({ ok: true });
expect(useCombatStore.getState().comboPoints).toBe(0);
expect(useCombatStore.getState().combatEvents.some((event) => event.kind === "damage"
&& event.abilityId === punishment.id && event.targetActorId === "war-prayer-target"
&& event.effectiveAmount > 0)).toBe(true);
});
it("limits Divine Punishment's sourced 1% bonus to Punishment", () => {
@@ -162,6 +173,56 @@ describe("RuneWaker Knight periodic skills", () => {
})).toEqual(romEffectsForCast(abilityById("rom-knight-primary-1")!, context));
});
it("makes Divine Punishment apply its sourced Light-damage vulnerability", () => {
useCombatStore.getState().initializeCharacter({
classId: "rom-knight", secondaryClassId: "rom-warrior", raceId: "rom-human", level: 80,
});
useCombatStore.getState().registerMob("divine-punishment-target", {
name: "Divine Punishment Target", maxHealth: 100_000,
});
setMobPosition("divine-punishment-target", [3, 0, 0]);
useCombatStore.getState().selectMob("divine-punishment-target");
expect(useCombatStore.getState().castAbility("rom-knight-primary-1", undefined, 1_000))
.toMatchObject({ ok: true });
expect(useCombatStore.getState().castAbility("rom-knight-general-0", undefined, 12_000))
.toMatchObject({ ok: true });
const vulnerability = useCombatStore.getState().auras.find((aura) => (
aura.targetId === "divine-punishment-target"
&& aura.definition.id === "rom-knight-divine-punishment-light-vulnerability"
));
expect(vulnerability?.definition).toMatchObject({
durationMs: 10_000,
modifiers: [{ kind: "damage", direction: "taken", school: "holy", operation: "percent", value: 0.01 }],
});
expect(vulnerability?.expiresAt).toBe(22_000);
});
it("reduces Holy Seal cooldown by three seconds with Holy Light's Fury", () => {
useCombatStore.getState().initializeCharacter({
classId: "rom-knight", secondaryClassId: "rom-priest", raceId: "rom-human", level: 80,
});
const state = useCombatStore.getState();
const holySeal = state.abilities.find((ability) => ability.dbcSpellId === 490149)!;
const furyPassive = state.abilities.find((ability) => ability.dbcSpellId === 491635)!;
expect(furyPassive.passive).toBe(true);
expect(furyPassive.effects).toEqual([
expect.objectContaining({
kind: "apply-aura",
aura: expect.objectContaining({ utilityTags: [ROM_KNIGHT_HOLY_LIGHTS_FURY_TAG] }),
}),
]);
useCombatStore.getState().registerMob("holy-lights-fury-target", {
name: "Holy Light's Fury Target", maxHealth: 100_000,
});
setMobPosition("holy-lights-fury-target", [3, 0, 0]);
useCombatStore.getState().selectMob("holy-lights-fury-target");
expect(useCombatStore.getState().castAbility(holySeal.id, undefined, 1_000)).toMatchObject({ ok: true });
expect(useCombatStore.getState().cooldowns[holySeal.id]).toBe(8_000);
});
it.each([true, false])("lets God's Vengeance preserve Holy Seal when the 50% roll is %s", (preservesSeal) => {
const godVengeance = abilityById("rom-knight-rom-rogue-elite-50")!;
expect(godVengeance).toMatchObject({ dbcSpellId: 499934, passive: true });
@@ -246,6 +307,84 @@ describe("RuneWaker Knight periodic skills", () => {
expect(useCombatStore.getState().cooldowns[holySeal.id]).toBe(24_000);
});
it("reduces only the party Knight's Holy Seal cooldown on an active Shield of Valor parry", () => {
const runIncomingCase = (
suffix: string,
auraActive: boolean,
parryRating: number,
wantedOutcome: "parry" | "hit",
) => {
const seed = `shield-of-valor-party-${suffix}`;
useCombatStore.getState().initializeCharacter({ classId: "warrior", level: 80 });
const knight = createConfiguredPartyMember({
name: "Shield of Valor Knight",
classId: "rom-knight",
specialization: "Primary",
role: "tank",
}, 80, 0, seed);
const knightAbilities = abilitiesForClass("rom-knight");
const holySeal = knightAbilities.find((ability) => ability.source === "runewaker"
&& ability.dbcSpellId === 490_149)!;
const shieldOfValor = knightAbilities.find((ability) => ability.source === "runewaker"
&& ability.dbcSpellId === 490_468)!;
usePartyStore.getState().initializeParty(seed, 80, useCombatStore.getState().health, "tank", 2, "rom");
usePartyStore.setState({ members: [{
...knight,
gearStats: { ...knight.gearStats, parryRating },
cooldowns: { ...knight.cooldowns, [holySeal.id]: 25_000 },
}] });
registerPartyRuntimePosition(knight.id, [0, 0, 0]);
useCombatStore.getState().synchronizeActors();
if (auraActive) {
const shieldAura = shieldOfValor.effects.find((effect) => effect.kind === "apply-aura");
if (!shieldAura || shieldAura.kind !== "apply-aura") throw new Error("Shield of Valor has no aura row");
useCombatStore.getState().applyActorAura(knight.id, knight.id, shieldOfValor, shieldAura, 1_000);
}
const mobId = `shield-of-valor-party-attacker-${suffix}`;
useCombatStore.getState().registerMob(mobId, {
name: "Shield of Valor party attacker",
maxHealth: 100_000,
xpReward: 0,
attacks: [DEFAULT_ENEMY_ATTACK],
});
setMobPosition(mobId, [2, 0, 0]);
useCombatStore.getState().damageMob(mobId, 1, 1_100, {
actorId: knight.id,
role: "tank",
});
let outcome: string | undefined;
for (let now = 1_450; now < 16_000; now += 100) {
useCombatStore.getState().advanceMobCombat(now);
const event = [...useCombatStore.getState().combatEvents].reverse().find((entry) => (
entry.sourceActorId === mobId && entry.targetActorId === knight.id && entry.occurredAt === now
));
if (event?.outcome === wantedOutcome) {
outcome = event.outcome;
break;
}
}
return {
outcome,
cooldown: useCombatStore.getState().actors[knight.id]?.cooldowns[holySeal.id],
};
};
const activeParry = runIncomingCase("active-parry", true, 100_000, "parry");
expect(activeParry.outcome).toBe("parry");
expect(activeParry.cooldown).toBe(24_000);
const inactiveParry = runIncomingCase("inactive-parry", false, 100_000, "parry");
expect(inactiveParry.outcome).toBe("parry");
expect(inactiveParry.cooldown).toBe(25_000);
const activeHit = runIncomingCase("active-hit", true, 0, "hit");
expect(activeHit.outcome).toBe("hit");
expect(activeHit.cooldown).toBe(25_000);
});
it("activates the source-backed Evil Shackles passive state", () => {
const passive = abilityById("rom-knight-rom-scout-elite-60")!;
expect(passive).toMatchObject({ dbcSpellId: 499926, passive: true });