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,9 +1,15 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { ROM_ALL_ABILITIES } from "./romAbilityCatalog";
|
||||
import { clearMobRuntimeRegistry, getMobPosition, setMobPosition } from "./mobRuntimeRegistry";
|
||||
import { useCombatStore } from "./combatStore";
|
||||
import { PLAYER_AURA_ENTITY_ID, useCombatStore } from "./combatStore";
|
||||
import { romEffectsForCast, type RomCastMechanics } from "./romCombatMechanics";
|
||||
import { romChampionCastEffects } from "./romChampionOverrides";
|
||||
import {
|
||||
ROM_CHAMPION_SOUL_FORGE_PROC_ACTION_ID,
|
||||
ROM_CHAMPION_SOUL_FORGE_PROFILE,
|
||||
ROM_CHAMPION_WAY_OF_FRENZY_PROC_ACTION_ID,
|
||||
ROM_CHAMPION_WAY_OF_FRENZY_PROFILE,
|
||||
romChampionCastEffects,
|
||||
} from "./romChampionOverrides";
|
||||
|
||||
const empty: RomCastMechanics = {
|
||||
sealStacks: 0,
|
||||
@@ -133,6 +139,234 @@ describe("RoM Champion source-specific behavior", () => {
|
||||
expect(useCombatStore.getState().resourcePools.rage).toBeGreaterThanOrEqual(10);
|
||||
});
|
||||
|
||||
it("restores Rune Pulse's source-defined Rage only when its damage lands", () => {
|
||||
const runePulse = sourceAbility(498529);
|
||||
expect(runePulse.description).toContain("When it hits, it will restore");
|
||||
|
||||
const effects = castEffects(498529);
|
||||
const hit = effects.find((effect) => effect.kind === "damage");
|
||||
expect(hit?.kind).toBe("damage");
|
||||
if (hit?.kind === "damage") expect(hit.onDamageResource).toEqual({ resource: "rage", amount: 5 });
|
||||
expect(effects.some((effect) => effect.kind === "apply-aura" && effect.aura.id.includes("-622327-")))
|
||||
.toBe(false);
|
||||
});
|
||||
|
||||
it("routes Rune Siphon's positive source component to the caster as healing", () => {
|
||||
const runeSiphon = sourceAbility(498595);
|
||||
expect(runeSiphon.secondaryClassId).toBe("rom-warlock");
|
||||
expect(runeSiphon.description).toContain("restores (DMG8) HP to the caster");
|
||||
|
||||
const effects = castEffects(498595);
|
||||
expect(effects.filter((effect) => effect.kind === "damage")).toHaveLength(2);
|
||||
expect(effects.filter((effect) => effect.kind === "heal")).toEqual([
|
||||
expect.objectContaining({ kind: "heal", recipient: "caster", basePoints: 24, pointsPerLevel: 4 }),
|
||||
]);
|
||||
|
||||
useCombatStore.getState().initializeCharacter({
|
||||
classId: "rom-champion", secondaryClassId: "rom-warlock", raceId: "rom-dwarf", level: 70,
|
||||
});
|
||||
useCombatStore.getState().registerMob("champion-rune-siphon-target", {
|
||||
name: "Rune Siphon Target",
|
||||
maxHealth: 100_000,
|
||||
});
|
||||
setMobPosition("champion-rune-siphon-target", [3, 0, 0]);
|
||||
useCombatStore.getState().selectMob("champion-rune-siphon-target");
|
||||
useCombatStore.getState().damagePlayer(Math.floor(useCombatStore.getState().maxHealth / 2));
|
||||
useCombatStore.setState((state) => ({
|
||||
resource: 100,
|
||||
resourcePools: { ...state.resourcePools, rage: 100, focus: 100 },
|
||||
}));
|
||||
const healthBeforeCast = useCombatStore.getState().health;
|
||||
|
||||
expect(useCombatStore.getState().castAbility("rom-champion-rom-warlock-elite-20", undefined, 1_000))
|
||||
.toMatchObject({ ok: true });
|
||||
expect(useCombatStore.getState().health).toBeGreaterThan(healthBeforeCast);
|
||||
});
|
||||
|
||||
it("arms Soul Forge Mystery's source-backed dark-magic bonus on critical attacks", () => {
|
||||
const soulForge = sourceAbility(498652);
|
||||
expect(soulForge.secondaryClassId).toBe("rom-warlock");
|
||||
expect(soulForge.description).toContain("If critical attack occurs while in effect");
|
||||
|
||||
const localCritAuras = soulForge.effects.filter((effect) => effect.kind === "apply-aura"
|
||||
&& effect.aura.id.includes(`-${ROM_CHAMPION_SOUL_FORGE_PROFILE.criticalAuraComponentId}-`));
|
||||
const localDarkAura = soulForge.effects.find((effect) => effect.kind === "apply-aura"
|
||||
&& effect.aura.id.includes(`-${ROM_CHAMPION_SOUL_FORGE_PROFILE.darkPowerComponentId}-`));
|
||||
expect(localCritAuras.length).toBeGreaterThan(0);
|
||||
expect(localCritAuras.every((effect) => effect.kind === "apply-aura"
|
||||
&& effect.aura.durationMs === 300_000)).toBe(true);
|
||||
expect(localDarkAura).toMatchObject({
|
||||
kind: "apply-aura",
|
||||
aura: {
|
||||
durationMs: 10_000,
|
||||
modifiers: [expect.objectContaining({ kind: "damage", direction: "dealt", school: "shadow", value: 0.1 })],
|
||||
},
|
||||
});
|
||||
|
||||
const effects = castEffects(498652);
|
||||
const criticalAura = effects.find((effect) => effect.kind === "apply-aura"
|
||||
&& effect.aura.id.includes(`-${ROM_CHAMPION_SOUL_FORGE_PROFILE.criticalAuraComponentId}-`));
|
||||
expect(criticalAura?.kind).toBe("apply-aura");
|
||||
if (criticalAura?.kind === "apply-aura") {
|
||||
expect(criticalAura.aura.procs).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
triggers: "crit",
|
||||
chance: 1,
|
||||
action: expect.objectContaining({
|
||||
kind: "custom",
|
||||
id: ROM_CHAMPION_SOUL_FORGE_PROC_ACTION_ID,
|
||||
data: expect.objectContaining({
|
||||
sourceSpellId: 498652,
|
||||
sourceComponentId: 621341,
|
||||
durationMs: 10_000,
|
||||
disposition: "buff",
|
||||
maxStacks: 1,
|
||||
stackBehavior: "refresh",
|
||||
target: "aura-source",
|
||||
modifierSchool: "shadow",
|
||||
modifierValue: 0.1,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
]));
|
||||
}
|
||||
expect(effects.some((effect) => effect.kind === "apply-aura"
|
||||
&& effect.aura.id.includes(`-${ROM_CHAMPION_SOUL_FORGE_PROFILE.darkPowerComponentId}-`))).toBe(false);
|
||||
});
|
||||
|
||||
it("grants Soul Forge Mystery's shadow bonus after a critical hit", () => {
|
||||
useCombatStore.getState().initializeCharacter({
|
||||
classId: "rom-champion",
|
||||
secondaryClassId: "rom-warlock",
|
||||
raceId: "rom-dwarf",
|
||||
level: 80,
|
||||
xp: 0,
|
||||
});
|
||||
const soulForge = useCombatStore.getState().abilities.find((ability) => ability.dbcSpellId === 498652)!;
|
||||
expect(useCombatStore.getState().castAbility(soulForge.id, undefined, 1_000).ok).toBe(true);
|
||||
expect(useCombatStore.getState().auras.some((aura) => aura.definition.id === "rom-champion-soul-forge-dark-magic"))
|
||||
.toBe(false);
|
||||
|
||||
useCombatStore.getState().registerMob("soul-forge-crit-target", { name: "Target", maxHealth: 100_000, xpReward: 0 });
|
||||
useCombatStore.getState().damageMob(
|
||||
"soul-forge-crit-target",
|
||||
100,
|
||||
2_000,
|
||||
undefined,
|
||||
"shadow",
|
||||
{
|
||||
attackKind: "spell",
|
||||
seed: "soul-forge-guaranteed-critical",
|
||||
abilityId: soulForge.id,
|
||||
criticalChanceBonusPercent: 100,
|
||||
rules: { canMiss: false, canCrit: true, canDodge: false, canParry: false, canBlock: false },
|
||||
},
|
||||
);
|
||||
|
||||
expect(useCombatStore.getState().auras).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
sourceId: PLAYER_AURA_ENTITY_ID,
|
||||
targetId: PLAYER_AURA_ENTITY_ID,
|
||||
definition: expect.objectContaining({
|
||||
id: "rom-champion-soul-forge-dark-magic",
|
||||
durationMs: ROM_CHAMPION_SOUL_FORGE_PROFILE.darkMagicAuraDurationMs,
|
||||
modifiers: [expect.objectContaining({
|
||||
kind: "damage",
|
||||
direction: "dealt",
|
||||
school: "shadow",
|
||||
operation: "percent",
|
||||
value: ROM_CHAMPION_SOUL_FORGE_PROFILE.darkMagicBonusPercent,
|
||||
})],
|
||||
}),
|
||||
}),
|
||||
]));
|
||||
});
|
||||
|
||||
it("arms Way of Frenzy only on Divine Vengeance and caps its local 2% Stamina stacks at five", () => {
|
||||
const wayOfFrenzy = sourceAbility(ROM_CHAMPION_WAY_OF_FRENZY_PROFILE.sourceSpellId);
|
||||
const divineVengeance = sourceAbility(ROM_CHAMPION_WAY_OF_FRENZY_PROFILE.triggerSpellId);
|
||||
expect(wayOfFrenzy).toMatchObject({ classId: "rom-champion", secondaryClassId: "rom-priest", passive: true });
|
||||
expect(divineVengeance.id).toBe(ROM_CHAMPION_WAY_OF_FRENZY_PROFILE.triggerAbilityId);
|
||||
expect(wayOfFrenzy.effects).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
kind: "apply-aura",
|
||||
recipient: "caster",
|
||||
aura: expect.objectContaining({
|
||||
id: "rom-champion-way-of-frenzy-passive",
|
||||
durationMs: null,
|
||||
procs: [expect.objectContaining({
|
||||
triggers: ["hit", "crit"],
|
||||
abilityIds: [divineVengeance.id],
|
||||
chance: 1,
|
||||
action: expect.objectContaining({
|
||||
kind: "custom",
|
||||
id: ROM_CHAMPION_WAY_OF_FRENZY_PROC_ACTION_ID,
|
||||
data: expect.objectContaining({
|
||||
sourceSpellId: ROM_CHAMPION_WAY_OF_FRENZY_PROFILE.sourceSpellId,
|
||||
sourceComponentId: ROM_CHAMPION_WAY_OF_FRENZY_PROFILE.sourceComponentId,
|
||||
triggerSpellId: ROM_CHAMPION_WAY_OF_FRENZY_PROFILE.triggerSpellId,
|
||||
durationMs: 60_000,
|
||||
maxStacks: ROM_CHAMPION_WAY_OF_FRENZY_PROFILE.maxStacks,
|
||||
staminaPercentPerStack: ROM_CHAMPION_WAY_OF_FRENZY_PROFILE.staminaPercentPerStack,
|
||||
}),
|
||||
}),
|
||||
})],
|
||||
}),
|
||||
}),
|
||||
]));
|
||||
expect(wayOfFrenzy.effects.some((effect) => effect.kind === "apply-aura"
|
||||
&& effect.aura.modifiers?.some((modifier) => modifier.kind === "stat" && modifier.stat === "stamina")))
|
||||
.toBe(false);
|
||||
|
||||
useCombatStore.getState().initializeCharacter({
|
||||
classId: "rom-champion",
|
||||
secondaryClassId: "rom-priest",
|
||||
raceId: "rom-dwarf",
|
||||
level: 70,
|
||||
});
|
||||
useCombatStore.getState().registerMob("way-of-frenzy-target", { name: "Target", maxHealth: 100_000, xpReward: 0 });
|
||||
const stackAuraId = "rom-champion-way-of-frenzy-stacks";
|
||||
const stackAura = () => useCombatStore.getState().auras.find((aura) => aura.definition.id === stackAuraId);
|
||||
expect(useCombatStore.getState().auras.some((aura) => aura.definition.id === "rom-champion-way-of-frenzy-passive"))
|
||||
.toBe(true);
|
||||
expect(stackAura()).toBeUndefined();
|
||||
|
||||
const unrelatedAbility = sourceAbility(498528);
|
||||
useCombatStore.getState().damageMob("way-of-frenzy-target", 100, 2_000, undefined, "physical", {
|
||||
attackKind: "melee",
|
||||
seed: "way-of-frenzy-unrelated-hit",
|
||||
abilityId: unrelatedAbility.id,
|
||||
rules: { canMiss: false, canCrit: false, canDodge: false, canParry: false, canBlock: false },
|
||||
});
|
||||
expect(stackAura()).toBeUndefined();
|
||||
|
||||
for (let hit = 0; hit < 6; hit += 1) {
|
||||
useCombatStore.getState().damageMob("way-of-frenzy-target", 100, 3_000 + hit * 1_000, undefined, "physical", {
|
||||
attackKind: "melee",
|
||||
seed: `way-of-frenzy-divine-vengeance-${hit}`,
|
||||
abilityId: divineVengeance.id,
|
||||
rules: { canMiss: false, canCrit: false, canDodge: false, canParry: false, canBlock: false },
|
||||
});
|
||||
}
|
||||
|
||||
expect(stackAura()).toMatchObject({
|
||||
stacks: ROM_CHAMPION_WAY_OF_FRENZY_PROFILE.maxStacks,
|
||||
expiresAt: 68_000,
|
||||
definition: {
|
||||
durationMs: 60_000,
|
||||
maxStacks: 5,
|
||||
stackBehavior: "add",
|
||||
modifiers: [{
|
||||
kind: "stat",
|
||||
stat: "stamina",
|
||||
operation: "percent",
|
||||
value: ROM_CHAMPION_WAY_OF_FRENZY_PROFILE.staminaPercentPerStack,
|
||||
perStack: true,
|
||||
}],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("scales Determination Rune's source max-health shield and leaves unrelated casts unchanged", () => {
|
||||
const determinationRune = sourceAbility(498584);
|
||||
const sourceShield = determinationRune.effects.find((effect) => effect.kind === "apply-aura"
|
||||
@@ -191,4 +425,16 @@ describe("RoM Champion source-specific behavior", () => {
|
||||
expect(castEffects(498599).some((effect) => effect.kind === "cooldown-reset")).toBe(false);
|
||||
expect(castEffects(498535).some((effect) => effect.kind === "cooldown-reset")).toBe(false);
|
||||
});
|
||||
|
||||
it("cleanses every modeled harmful aura on the party with Righteous Explosion", () => {
|
||||
const cleanse = castEffects(499836).find((effect) => effect.kind === "dispel");
|
||||
|
||||
expect(cleanse).toMatchObject({
|
||||
kind: "dispel",
|
||||
mode: "cleanse",
|
||||
relationship: "friendly",
|
||||
scope: "party",
|
||||
maxCount: 99,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user