Update Healer Man server
This commit is contained in:
1 parent
35dfdaed61
commit
4fb7a0b9c8
161 files changed
+24618
-529
No files matched your search
@@ -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 });
|
||||
|
||||
Reference in new issue
Block a user