forked from phenom/healer-man
Update Healer Man server
This commit is contained in:
1 parent
9d7e0e2f9e
commit
35dfdaed61
94 files changed
+17034
-418
No files matched your search
@@ -1,7 +1,10 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { abilityAtLevel, type AbilityDefinition, type AbilityEffect } from "./abilityCatalog";
|
||||
import { aiHintsForAbility, targetingRulesForAbility } from "./abilityRules";
|
||||
import { WOW335_ABILITIES_BY_CLASS } from "./wow335AbilityCatalog";
|
||||
import {
|
||||
WOW335_ABILITIES_BY_CLASS,
|
||||
WOW335_TRIGGERED_ABILITIES_BY_SPELL_ID,
|
||||
} from "./wow335AbilityCatalog";
|
||||
import generatedData from "./wow335AbilityData.generated.json";
|
||||
import { talentNodeById } from "./talentCatalog";
|
||||
import { runtimeTalentNodeById } from "./talentRuntimeCatalog";
|
||||
@@ -39,6 +42,99 @@ describe("WoW 3.3.5 executable effect mapping", () => {
|
||||
expect(byId("wow335-priest-power-word-shield").gcdMs).toBe(1_500);
|
||||
});
|
||||
|
||||
it("keeps Bloodthirst's player cast, next-hit aura, and triggered heal on their source rows", () => {
|
||||
const bloodthirst = abilityAtLevel(byId("wow335-warrior-bloodthirst"), 80);
|
||||
expect(bloodthirst).toMatchObject({
|
||||
dbcSpellId: 23881,
|
||||
target: "hostile",
|
||||
range: { min: 0, max: 5 },
|
||||
cost: { resource: "rage", amount: 20 },
|
||||
cooldownMs: 4_000,
|
||||
});
|
||||
expect(effectsOfKind(bloodthirst, "damage")).toHaveLength(1);
|
||||
const bloodthirstAura = effectsOfKind(bloodthirst, "apply-aura")
|
||||
.find((effect) => effect.sourceAuraType === 42);
|
||||
expect(bloodthirstAura).toMatchObject({
|
||||
recipient: "caster",
|
||||
aura: {
|
||||
durationMs: 8_000,
|
||||
procs: [expect.objectContaining({
|
||||
triggers: ["hit", "crit"],
|
||||
attackKinds: ["melee"],
|
||||
charges: 3,
|
||||
action: { kind: "custom", id: "wow335-trigger-spell", data: { spellId: 23880 } },
|
||||
})],
|
||||
},
|
||||
});
|
||||
expect(WOW335_TRIGGERED_ABILITIES_BY_SPELL_ID[23880]?.effects).toEqual([
|
||||
expect.objectContaining({ kind: "heal", percentMaxHealth: 0.01 }),
|
||||
]);
|
||||
});
|
||||
|
||||
it("selects Sweeping Strikes' player self-buff row instead of its damage child", () => {
|
||||
const source = byId("wow335-warrior-sweeping-strikes");
|
||||
const sweepingStrikes = abilityAtLevel(source, 80);
|
||||
expect(source).toMatchObject({ unlockLevel: 30, sourceUnlockLevel: 30 });
|
||||
expect(sweepingStrikes).toMatchObject({
|
||||
dbcSpellId: 12328,
|
||||
target: "self",
|
||||
range: { min: 0, max: 0 },
|
||||
cost: { resource: "rage", amount: 30 },
|
||||
cooldownMs: 30_000,
|
||||
});
|
||||
expect(effectsOfKind(sweepingStrikes, "damage")).toEqual([]);
|
||||
expect(effectsOfKind(sweepingStrikes, "apply-aura")).toEqual([
|
||||
expect.objectContaining({
|
||||
recipient: "caster",
|
||||
aura: expect.objectContaining({
|
||||
durationMs: 30_000,
|
||||
procs: [expect.objectContaining({
|
||||
triggers: ["hit", "crit"],
|
||||
attackKinds: ["melee"],
|
||||
charges: 5,
|
||||
action: { kind: "custom", id: "wow335-sweeping-strikes-secondary-hit" },
|
||||
})],
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("maps Battle Shout and Commanding Shout to nearby party-wide buffs", () => {
|
||||
for (const [abilityId, auraCount] of [
|
||||
["wow335-warrior-battle-shout", 2],
|
||||
["wow335-warrior-commanding-shout", 1],
|
||||
] as const) {
|
||||
const ability = abilityAtLevel(byId(abilityId), 80);
|
||||
const auras = effectsOfKind(ability, "apply-aura");
|
||||
expect(ability).toMatchObject({ target: "self", radius: 30 });
|
||||
expect(targetingRulesForAbility(ability)).toMatchObject({ shape: "circle", radius: 30 });
|
||||
expect(auras).toHaveLength(auraCount);
|
||||
expect(auras.every((effect) => effect.scope === "party")).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("maps Nature's Swiftness to one next-Nature-spell cast-time aura and its shared cooldown", () => {
|
||||
const natureSwiftness = abilityAtLevel(byId("wow335-shaman-nature-s-swiftness"), 80);
|
||||
const druidNatureSwiftness = abilityAtLevel(byId("wow335-druid-nature-s-swiftness"), 80);
|
||||
const elementalMastery = abilityAtLevel(byId("wow335-shaman-elemental-mastery"), 80);
|
||||
|
||||
expect(natureSwiftness.cooldownGroup).toBe("shaman-nature-swiftness");
|
||||
expect(elementalMastery.cooldownGroup).toBe("shaman-nature-swiftness");
|
||||
expect(natureSwiftness.cooldownMs).toBe(180_000);
|
||||
expect(druidNatureSwiftness.cooldownMs).toBe(180_000);
|
||||
for (const ability of [natureSwiftness, druidNatureSwiftness]) {
|
||||
const auraEffect = effectsOfKind(ability, "apply-aura")[0];
|
||||
expect(auraEffect).toMatchObject({
|
||||
recipient: "caster",
|
||||
aura: {
|
||||
durationMs: null,
|
||||
utilityTags: ["wow335-next-nature-instant"],
|
||||
},
|
||||
});
|
||||
if (auraEffect?.kind === "apply-aura") expect(auraEffect.aura.modifiers).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it("combines each Holy Nova damage rank with its matching healing payload", () => {
|
||||
const nova = byId("wow335-priest-holy-nova");
|
||||
expect(nova.ranks).toHaveLength(9);
|
||||
@@ -55,6 +151,47 @@ describe("WoW 3.3.5 executable effect mapping", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps Holy Shock's cast wrapper while combining damage and healing ranks", () => {
|
||||
const shock = byId("wow335-paladin-holy-shock");
|
||||
expect(shock.ranks).toHaveLength(7);
|
||||
for (const rank of shock.ranks ?? []) {
|
||||
expect(effectsOfKind(rank, "damage")).toHaveLength(1);
|
||||
expect(effectsOfKind(rank, "heal")).toHaveLength(1);
|
||||
expect(rank.cost.amount).toBeGreaterThan(0);
|
||||
expect(rank.cooldownMs).toBe(6_000);
|
||||
}
|
||||
|
||||
expect(abilityAtLevel(shock, 80)).toMatchObject({
|
||||
dbcSpellId: 48825,
|
||||
target: "any",
|
||||
range: { min: 0, max: 40 },
|
||||
castMode: "instant",
|
||||
gcdMs: 1_500,
|
||||
cooldownMs: 6_000,
|
||||
});
|
||||
});
|
||||
|
||||
it("models Life Tap as a health cost and mana restoration, not a heal", () => {
|
||||
const lifeTap = abilityAtLevel(byId("wow335-warlock-life-tap"), 80);
|
||||
expect(lifeTap).toMatchObject({
|
||||
dbcSpellId: 57946,
|
||||
target: "self",
|
||||
cost: { resource: "health", amount: 1_490 },
|
||||
});
|
||||
expect(effectsOfKind(lifeTap, "resource")).toContainEqual(
|
||||
{ kind: "resource", resource: "mana", amount: 1_490 },
|
||||
);
|
||||
expect(effectsOfKind(lifeTap, "heal")).toEqual([]);
|
||||
|
||||
for (const id of [
|
||||
"wow335-warlock-create-healthstone",
|
||||
"wow335-warlock-create-soulstone",
|
||||
"wow335-warlock-demonic-empowerment",
|
||||
]) {
|
||||
expect(effectsOfKind(abilityAtLevel(byId(id), 80), "heal"), id).toEqual([]);
|
||||
}
|
||||
});
|
||||
|
||||
it("maps Envenom to Deadly Poison dose damage and its poison-frequency buff", () => {
|
||||
const envenom = abilityAtLevel(byId("wow335-rogue-envenom"), 80);
|
||||
expect(effectsOfKind(envenom, "finisher-damage")).toEqual([
|
||||
@@ -143,7 +280,7 @@ describe("WoW 3.3.5 executable effect mapping", () => {
|
||||
expect(byId("wow335-priest-divine-hymn").ranks?.map((rank) => rank.spellId)).toEqual([64843]);
|
||||
expect(byId("wow335-death-knight-death-coil").ranks?.map((rank) => rank.spellId)).not.toContain(47632);
|
||||
expect(byId("wow335-death-knight-death-coil")).toMatchObject({
|
||||
target: "hostile",
|
||||
target: "any",
|
||||
effects: expect.arrayContaining([expect.objectContaining({ kind: "damage" })]),
|
||||
});
|
||||
});
|
||||
@@ -185,15 +322,16 @@ describe("WoW 3.3.5 executable effect mapping", () => {
|
||||
it("provides mechanics for previously silent class scripts and hidden proc spells", () => {
|
||||
expect(effectsOfKind(byId("wow335-warrior-slam"), "damage")).not.toEqual([]);
|
||||
expect(effectsOfKind(byId("wow335-warrior-bloodthirst"), "damage")).toContainEqual(
|
||||
expect.objectContaining({ coefficient: 1.2 }),
|
||||
expect.objectContaining({ coefficient: 1, basePoints: 50, bonusPowerCoefficient: 1 }),
|
||||
);
|
||||
expect(effectsOfKind(byId("wow335-warrior-bloodrage"), "resource")).toEqual([
|
||||
{ kind: "resource", resource: "rage", amount: 20 },
|
||||
]);
|
||||
expect(effectsOfKind(byId("wow335-warrior-bloodrage"), "heal")).toEqual([]);
|
||||
expect(effectsOfKind(byId("wow335-warrior-execute"), "execute")).toContainEqual(
|
||||
expect.objectContaining({ coefficient: 1.8 }),
|
||||
expect(effectsOfKind(abilityAtLevel(byId("wow335-warrior-execute"), 80), "damage")).toContainEqual(
|
||||
expect.objectContaining({ basePoints: 1_456, coefficient: 1 }),
|
||||
);
|
||||
expect(effectsOfKind(byId("wow335-warrior-execute"), "execute")).toEqual([]);
|
||||
expect(effectsOfKind(byId("wow335-warrior-execute"), "heal")).toEqual([]);
|
||||
expect(effectsOfKind(byId("wow335-warrior-bladestorm"), "dot")).toEqual([
|
||||
expect.objectContaining({ coefficient: 0.6, ticks: 6, intervalMs: 1_000 }),
|
||||
@@ -209,13 +347,25 @@ describe("WoW 3.3.5 executable effect mapping", () => {
|
||||
expect.objectContaining({ coefficient: 2 }),
|
||||
);
|
||||
expect(effectsOfKind(byId("wow335-rogue-mutilate"), "combo")).not.toEqual([]);
|
||||
expect(effectsOfKind(byId("wow335-rogue-killing-spree"), "dot")).toContainEqual(
|
||||
expect.objectContaining({ coefficient: 0.5, ticks: 5 }),
|
||||
expect(effectsOfKind(byId("wow335-rogue-killing-spree"), "dot")).toEqual([]);
|
||||
expect(effectsOfKind(byId("wow335-rogue-killing-spree"), "utility")).toContainEqual(
|
||||
expect.objectContaining({ action: "class-script", durationMs: 2_000 }),
|
||||
);
|
||||
expect(byId("wow335-rogue-killing-spree").target).toBe("hostile");
|
||||
expect(effectsOfKind(byId("wow335-warlock-conflagrate"), "damage")).toEqual([
|
||||
expect.objectContaining({ coefficient: 1.4 }),
|
||||
expect.objectContaining({ coefficient: 0, school: "fire" }),
|
||||
]);
|
||||
expect(effectsOfKind(byId("wow335-death-knight-death-grip"), "pull")).not.toEqual([]);
|
||||
expect(effectsOfKind(byId("wow335-death-knight-death-grip"), "taunt")).toEqual([
|
||||
{ kind: "taunt", durationMs: 3_000 },
|
||||
]);
|
||||
expect(effectsOfKind(byId("wow335-death-knight-chains-of-ice"), "status")).toEqual([
|
||||
{ kind: "status", status: "slow", durationMs: 10_000, magnitude: 0.95 },
|
||||
]);
|
||||
expect(effectsOfKind(byId("wow335-death-knight-pestilence"), "damage")).toEqual([]);
|
||||
expect(effectsOfKind(byId("wow335-death-knight-pestilence"), "utility")).toContainEqual(
|
||||
expect.objectContaining({ action: "class-script", effectType: 77 }),
|
||||
);
|
||||
expect(effectsOfKind(byId("wow335-shaman-windfury-weapon"), "apply-aura")[0]?.aura.procs).not.toEqual([]);
|
||||
expect(effectsOfKind(byId("wow335-shaman-lightning-shield"), "apply-aura")[0]?.aura.procs).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ triggers: "damage-taken", charges: 3 }),
|
||||
@@ -249,6 +399,92 @@ describe("WoW 3.3.5 executable effect mapping", () => {
|
||||
expect(hurricane.cost.amount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("makes Empower Rune Weapon activate all six runes from the player spell", () => {
|
||||
const empowerRuneWeapon = abilityAtLevel(byId("wow335-death-knight-empower-rune-weapon"), 80);
|
||||
expect(empowerRuneWeapon.dbcSpellId).toBe(47568);
|
||||
expect(effectsOfKind(empowerRuneWeapon, "rune")).toEqual([
|
||||
{ kind: "rune", action: "activate", count: 6 },
|
||||
]);
|
||||
expect(effectsOfKind(empowerRuneWeapon, "resource")).toEqual([
|
||||
{ kind: "resource", resource: "runic-power", amount: 25 },
|
||||
]);
|
||||
});
|
||||
|
||||
it("models Death Knight source healing, rune, charge, and magic-shell effects", () => {
|
||||
const deathCoil = abilityAtLevel(byId("wow335-death-knight-death-coil"), 80);
|
||||
expect(deathCoil.target).toBe("any");
|
||||
expect(effectsOfKind(deathCoil, "damage")).not.toEqual([]);
|
||||
expect(effectsOfKind(deathCoil, "heal")).not.toEqual([]);
|
||||
|
||||
const deathStrike = abilityAtLevel(byId("wow335-death-knight-death-strike"), 80);
|
||||
expect(effectsOfKind(deathStrike, "heal")).toEqual([
|
||||
expect.objectContaining({ coefficient: 0, percentMaxHealth: 0.05 }),
|
||||
]);
|
||||
|
||||
const antiMagicShell = abilityAtLevel(byId("wow335-death-knight-anti-magic-shell"), 80);
|
||||
expect(effectsOfKind(antiMagicShell, "apply-aura")).toEqual([
|
||||
expect.objectContaining({
|
||||
recipient: "caster",
|
||||
aura: expect.objectContaining({
|
||||
durationMs: 5_000,
|
||||
absorbs: [expect.objectContaining({
|
||||
amount: 0.5,
|
||||
fraction: 0.75,
|
||||
schools: ["holy", "fire", "nature", "frost", "shadow", "arcane"],
|
||||
})],
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
|
||||
const antiMagicZone = abilityAtLevel(byId("wow335-death-knight-anti-magic-zone"), 80);
|
||||
expect(antiMagicZone).toMatchObject({
|
||||
target: "self",
|
||||
description: expect.stringContaining("75% of incoming magic damage"),
|
||||
targeting: { shape: "ground", radius: 10, groundRange: 30 },
|
||||
});
|
||||
expect(effectsOfKind(antiMagicZone, "summon")).toEqual([
|
||||
expect.objectContaining({
|
||||
creatureId: 28306,
|
||||
durationMs: 10_000,
|
||||
antiMagicZone: {
|
||||
radius: 10,
|
||||
capacityBase: 10_000,
|
||||
capacityPerAttackPower: 2,
|
||||
absorbFraction: 0.75,
|
||||
intervalMs: 250,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const boneShield = abilityAtLevel(byId("wow335-death-knight-bone-shield"), 80);
|
||||
expect(effectsOfKind(boneShield, "apply-aura")).toEqual([
|
||||
expect.objectContaining({ aura: expect.objectContaining({
|
||||
durationMs: 300_000,
|
||||
initialStacks: 4,
|
||||
maxStacks: 4,
|
||||
modifiers: expect.arrayContaining([
|
||||
expect.objectContaining({ kind: "damage", direction: "taken", value: -0.2, perStack: false }),
|
||||
expect.objectContaining({ kind: "damage", direction: "dealt", value: 0.02, perStack: false }),
|
||||
]),
|
||||
procs: [expect.objectContaining({
|
||||
triggers: "damage-taken",
|
||||
charges: 4,
|
||||
action: { kind: "custom", id: "wow335-death-knight-bone-shield-charge" },
|
||||
})],
|
||||
}) }),
|
||||
]);
|
||||
|
||||
expect(effectsOfKind(abilityAtLevel(byId("wow335-death-knight-blood-tap"), 80), "rune")).toEqual([
|
||||
{ kind: "rune", action: "activate", count: 1, runeType: "blood", convertToDeathMs: 20_000 },
|
||||
]);
|
||||
expect(effectsOfKind(abilityAtLevel(byId("wow335-death-knight-deathchill"), 80), "apply-aura")).toEqual([
|
||||
expect.objectContaining({
|
||||
recipient: "caster",
|
||||
aura: expect.objectContaining({ utilityTags: expect.arrayContaining(["wow335-deathchill-next-critical"]) }),
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps every rank of imported channels in channel mode", () => {
|
||||
const mismatches = Object.values(WOW335_ABILITIES_BY_CLASS).flat()
|
||||
.filter((ability) => ability.castMode === "channel" && ability.ranks?.some((rank) => rank.castMode !== "channel"))
|
||||
@@ -321,6 +557,14 @@ describe("WoW 3.3.5 executable effect mapping", () => {
|
||||
.toEqual(expect.arrayContaining([expect.objectContaining({ stat: "incoming-ranged-hit", value: -25 })]));
|
||||
});
|
||||
|
||||
it("keeps Blink's root and stun breaks without inventing target-relative teleport movement", () => {
|
||||
const blink = byId("wow335-mage-blink");
|
||||
expect(blink.dbcSpellId).toBe(1953);
|
||||
expect(effectsOfKind(blink, "movement")).toEqual([]);
|
||||
expect(effectsOfKind(blink, "apply-aura").map((effect) => effect.aura.controlImmunities))
|
||||
.toEqual([["stun"], ["root"]]);
|
||||
});
|
||||
|
||||
it("keeps triggered shield damage children out of learned Shaman ranks", () => {
|
||||
const lightningShield = abilityAtLevel(byId("wow335-shaman-lightning-shield"), 80);
|
||||
expect(lightningShield.dbcSpellId).toBe(49281);
|
||||
@@ -427,6 +671,36 @@ describe("WoW 3.3.5 executable effect mapping", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("maps Paladin revive, healing riders, class-group blessings, and aura mechanics", () => {
|
||||
const redemption = byId("wow335-paladin-redemption");
|
||||
expect(redemption.target).toBe("friendly");
|
||||
expect(effectsOfKind(redemption, "resurrection")).toContainEqual(expect.objectContaining({
|
||||
flatHealth: 65,
|
||||
}));
|
||||
|
||||
const divineStorm = byId("wow335-paladin-divine-storm");
|
||||
expect(divineStorm.effects).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ kind: "damage", coefficient: 1.1, school: "physical" }),
|
||||
expect.objectContaining({ kind: "heal", percentOfDamage: 0.25, scope: "party", maximumTargets: 3 }),
|
||||
]));
|
||||
|
||||
const beacon = abilityAtLevel(byId("wow335-paladin-beacon-of-light"), 80);
|
||||
expect(effectsOfKind(beacon, "apply-aura")[0]?.aura.utilityTags).toContain("beacon-of-light");
|
||||
|
||||
const greaterBlessing = byId("wow335-paladin-greater-blessing-of-kings");
|
||||
expect(effectsOfKind(greaterBlessing, "apply-aura")[0]?.scope).toBe("party");
|
||||
const avengersShield = byId("wow335-paladin-avenger-s-shield");
|
||||
expect(avengersShield.targeting).toMatchObject({ shape: "chain", maximumTargets: 3, jumpRange: 10 });
|
||||
|
||||
const auraMastery = byId("wow335-paladin-aura-mastery");
|
||||
expect(effectsOfKind(auraMastery, "apply-aura").every((effect) => (
|
||||
effect.scope === "party" && effect.aura.utilityTags?.includes("wow335-aura-mastery-silence-interrupt")
|
||||
))).toBe(true);
|
||||
const divineFavor = byId("wow335-paladin-divine-favor");
|
||||
expect(effectsOfKind(divineFavor, "apply-aura")[0]?.aura.utilityTags)
|
||||
.toContain("wow335-divine-favor-next-holy-heal");
|
||||
});
|
||||
|
||||
it("keeps periodic damage and healing auras out of the generic HUD strip", () => {
|
||||
const pain = byId("wow335-priest-shadow-word-pain");
|
||||
const painAura = effectsOfKind(pain, "apply-aura")
|
||||
@@ -491,4 +765,23 @@ describe("WoW 3.3.5 executable effect mapping", () => {
|
||||
aura: { disposition: "debuff", dispelCategory: "magic" },
|
||||
});
|
||||
});
|
||||
|
||||
it("maps Soulshatter to its source 50-yard self-centered threat reduction", () => {
|
||||
const soulshatter = byId("wow335-warlock-soulshatter");
|
||||
const source = generatedData.classes.warlock.find((chain) => chain.name === "Soulshatter")!;
|
||||
|
||||
expect(source.ranks[0]).toMatchObject({
|
||||
spellId: 29858,
|
||||
description: expect.stringContaining("Reduces threat by 50%"),
|
||||
effects: [expect.objectContaining({ effectType: 3, basePoints: -50, radius: 50 })],
|
||||
});
|
||||
expect(soulshatter).toMatchObject({
|
||||
dbcSpellId: 29858,
|
||||
target: "self",
|
||||
radius: 50,
|
||||
});
|
||||
expect(effectsOfKind(soulshatter, "threat")).toEqual([
|
||||
{ kind: "threat", mode: "percent", amount: -50 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user