Files
healer-man/src/game/romPriestScoutCuringShot.test.ts
T
2026-10-02 10:38:12 -04:00

163 lines
5.9 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { AbilityDefinition } from "./abilityCatalog";
import {
isRomPriestScoutCuringShot,
ROM_PRIEST_SCOUT_CURING_SHOT_COOLDOWN_MS,
ROM_PRIEST_SCOUT_CURING_SHOT_DESCRIPTION,
ROM_PRIEST_SCOUT_CURING_SHOT_DURATION_MS,
ROM_PRIEST_SCOUT_CURING_SHOT_ELEMENTAL_AURA,
ROM_PRIEST_SCOUT_CURING_SHOT_ELEMENTAL_DAMAGE_BONUS,
ROM_PRIEST_SCOUT_CURING_SHOT_ELEMENTAL_SCHOOLS,
ROM_PRIEST_SCOUT_CURING_SHOT_HOT_TICKS,
ROM_PRIEST_SCOUT_CURING_SHOT_ID,
ROM_PRIEST_SCOUT_CURING_SHOT_IMMEDIATE_HEAL,
ROM_PRIEST_SCOUT_CURING_SHOT_RADIUS,
ROM_PRIEST_SCOUT_CURING_SHOT_RANGE,
ROM_PRIEST_SCOUT_CURING_SHOT_TICK_INTERVAL_MS,
romPriestScoutCuringShotCatalogOverride,
} from "./romPriestScoutCuringShot";
const localAbility: AbilityDefinition = {
id: "rom-priest-rom-scout-elite-35",
classId: "rom-priest",
secondaryClassId: "rom-scout",
dbcSpellId: ROM_PRIEST_SCOUT_CURING_SHOT_ID,
name: "Curing Shot",
unlockLevel: 35,
icon: "/assets/ui/rom-spells/577011.png",
sigil: "CS",
description: "Local two-second heal row.",
target: "friendly",
range: { min: 0, max: 20 },
radius: 20,
targeting: { shape: "circle", radius: 20, maximumTargets: 5 },
castMode: "cast",
castTimeMs: 2_000,
cooldownMs: 12_000,
gcdMs: 1_500,
cost: { resource: "mana", amount: 120 },
costs: [{ resource: "mana", amount: 120 }],
effects: [{ kind: "damage", coefficient: 0.2 }],
source: "runewaker",
romSkillGroup: "elite",
ranks: [{
rank: 1,
spellId: ROM_PRIEST_SCOUT_CURING_SHOT_ID,
level: 35,
description: "Local two-second heal row.",
castMode: "cast",
castTimeMs: 2_000,
cooldownMs: 12_000,
gcdMs: 1_500,
cost: { resource: "mana", amount: 120 },
costs: [{ resource: "mana", amount: 120 }],
effects: [{ kind: "damage", coefficient: 0.2 }],
}],
};
describe("current CoA Priest/Scout Curing Shot catalog overlay", () => {
it("uses current CoA cast, cooldown, radius, direct heal, HoT, and elemental-buff metadata", () => {
const ability = romPriestScoutCuringShotCatalogOverride(localAbility);
expect(ability).toMatchObject({
name: "Curing Shot",
target: "friendly",
range: { min: 0, max: ROM_PRIEST_SCOUT_CURING_SHOT_RANGE },
radius: ROM_PRIEST_SCOUT_CURING_SHOT_RADIUS,
targeting: { shape: "circle", radius: ROM_PRIEST_SCOUT_CURING_SHOT_RADIUS },
castMode: "instant",
castTimeMs: 0,
cooldownMs: ROM_PRIEST_SCOUT_CURING_SHOT_COOLDOWN_MS,
description: ROM_PRIEST_SCOUT_CURING_SHOT_DESCRIPTION,
passive: false,
});
expect(ability.effects).toEqual([
{
kind: "heal",
coefficient: 0,
basePoints: ROM_PRIEST_SCOUT_CURING_SHOT_IMMEDIATE_HEAL,
recipient: "caster",
scope: "party",
radius: ROM_PRIEST_SCOUT_CURING_SHOT_RADIUS,
},
{
kind: "hot",
coefficient: 0,
basePoints: 167.5,
ticks: ROM_PRIEST_SCOUT_CURING_SHOT_HOT_TICKS,
intervalMs: ROM_PRIEST_SCOUT_CURING_SHOT_TICK_INTERVAL_MS,
recipient: "caster",
scope: "party",
radius: ROM_PRIEST_SCOUT_CURING_SHOT_RADIUS,
},
expect.objectContaining({
kind: "apply-aura",
recipient: "caster",
aura: ROM_PRIEST_SCOUT_CURING_SHOT_ELEMENTAL_AURA,
}),
]);
expect(ROM_PRIEST_SCOUT_CURING_SHOT_DURATION_MS).toBe(18_000);
expect(ROM_PRIEST_SCOUT_CURING_SHOT_ELEMENTAL_DAMAGE_BONUS).toBe(0.1);
expect(ROM_PRIEST_SCOUT_CURING_SHOT_ELEMENTAL_SCHOOLS).toEqual([
"frost",
]);
expect(ROM_PRIEST_SCOUT_CURING_SHOT_ELEMENTAL_AURA).toMatchObject({
durationMs: 18_000,
modifiers: [
{ kind: "damage", direction: "dealt", school: "frost", operation: "percent", value: 0.1 },
],
});
});
it("does not carry over the local five-target cap and preserves the unsupported-to-scale local cost", () => {
const ability = romPriestScoutCuringShotCatalogOverride(localAbility);
expect(ability.targeting).toEqual({ shape: "circle", radius: 10 });
expect(ability.cost).toEqual({ resource: "mana", amount: 120 });
expect(ability.costs).toEqual([{ resource: "mana", amount: 120 }]);
expect(ability.ranks?.[0]).toMatchObject({
castMode: "instant",
castTimeMs: 0,
cooldownMs: 6_000,
cost: { resource: "mana", amount: 120 },
costs: [{ resource: "mana", amount: 120 }],
targeting: { shape: "circle", radius: 10 },
effects: ability.effects,
});
expect(ability.ranks?.[0]?.targeting).not.toHaveProperty("maximumTargets");
});
it("recognizes only the exact Priest-primary/Scout-secondary source identity", () => {
expect(isRomPriestScoutCuringShot({
classId: "rom-priest",
secondaryClassId: "rom-scout",
sourceSkillId: ROM_PRIEST_SCOUT_CURING_SHOT_ID,
})).toBe(true);
expect(isRomPriestScoutCuringShot({
classId: "rom-scout",
secondaryClassId: "rom-priest",
sourceSkillId: ROM_PRIEST_SCOUT_CURING_SHOT_ID,
})).toBe(false);
expect(isRomPriestScoutCuringShot({
classId: "rom-priest",
secondaryClassId: "rom-mage",
sourceSkillId: ROM_PRIEST_SCOUT_CURING_SHOT_ID,
})).toBe(false);
expect(isRomPriestScoutCuringShot({
classId: "rom-priest",
secondaryClassId: "rom-scout",
sourceSkillId: ROM_PRIEST_SCOUT_CURING_SHOT_ID + 1,
})).toBe(false);
});
it("leaves other rows and non-RuneWaker copies unchanged", () => {
const wrongPair = { ...localAbility, secondaryClassId: "rom-mage" as const };
const wrongId = { ...localAbility, dbcSpellId: ROM_PRIEST_SCOUT_CURING_SHOT_ID + 1 };
const wrongSource = { ...localAbility, source: "core" as const };
expect(romPriestScoutCuringShotCatalogOverride(wrongPair)).toBe(wrongPair);
expect(romPriestScoutCuringShotCatalogOverride(wrongId)).toBe(wrongId);
expect(romPriestScoutCuringShotCatalogOverride(wrongSource)).toBe(wrongSource);
});
});