Files
healer-man/src/game/coaWitchDoctorBrewingRuntime.test.ts
2026-10-06 22:25:34 -04:00

169 lines
8.1 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { abilitiesForClass, type AbilityDefinition } from "./abilityCatalog";
import { applyAura, type ActiveAura } from "./combatAuras";
import {
coaWitchDoctorActiveIngredients,
coaWitchDoctorBrewingAbilityForCast,
coaWitchDoctorCanBrewIngredient,
coaWitchDoctorIngredientCapacity,
coaWitchDoctorIngredientIdForAbility,
COA_WITCH_DOCTOR_FROG_BONES_POTION_SHIELD,
COA_WITCH_DOCTOR_FROG_BONES_SPLASH_SHIELD,
} from "./coaWitchDoctorBrewingRuntime";
const casterId = "witch-doctor-caster";
function abilityByTalentEntry(talentEntryId: number): AbilityDefinition {
const ability = abilitiesForClass("witch-doctor").find((candidate) => candidate.talentEntryId === talentEntryId);
if (!ability) throw new Error(`Missing Witch Doctor talent action ${talentEntryId}`);
return ability;
}
function markerFromIngredient(ability: AbilityDefinition): Extract<AbilityDefinition["effects"][number], { kind: "apply-aura" }> {
const projected = coaWitchDoctorBrewingAbilityForCast(ability, [], casterId, 1_000);
const marker = projected.effects.find((effect) => effect.kind === "apply-aura"
&& effect.aura.id.startsWith("coa-witch-doctor:active-ingredient:"));
if (!marker || marker.kind !== "apply-aura") throw new Error(`Missing marker effect for ${ability.name}`);
return marker;
}
function activate(
ability: AbilityDefinition,
sourceId = casterId,
targetId = sourceId,
appliedAt = 1_000,
): ActiveAura {
const marker = markerFromIngredient(ability);
return applyAura([], { definition: marker.aura, sourceId, targetId, now: appliedAt }).aura;
}
describe("CoA Witch Doctor Brewing state and Potion Toss", () => {
it("uses actor-owned, non-stacking ingredient markers and preserves Frog Bones cauldron pulses", () => {
const frogBones = abilityByTalentEntry(29741);
const projected = coaWitchDoctorBrewingAbilityForCast(frogBones, [], casterId, 1_000);
const cauldronPulse = projected.effects.find((effect) => effect.kind === "apply-aura"
&& effect.sourceAuraType === 23);
expect(projected.effects.some((effect) => effect.kind === "shield")).toBe(false);
expect(projected.effects.some((effect) => effect.kind === "apply-aura" && effect.sourceAuraType === 4)).toBe(false);
expect(cauldronPulse).toMatchObject({
kind: "apply-aura",
aura: { maxStacks: 1, stackBehavior: "refresh" },
recipient: "caster",
});
expect(projected.effects.filter((effect) => effect.kind === "apply-aura"
&& effect.aura.id === "coa-witch-doctor:active-ingredient:frog-bones")).toHaveLength(1);
const marker = markerFromIngredient(frogBones);
expect(marker.aura).toMatchObject({
durationMs: null,
maxStacks: 1,
stackBehavior: "refresh",
hideFromAuraStrip: true,
utilityTags: ["coa-witch-doctor-active-ingredient:frog-bones"],
});
expect(marker.aura.modifiers ?? []).toEqual([]);
expect(marker.aura.procs ?? []).toEqual([]);
});
it("gates new ingredients at the sourced one-or-two capacity without replacing a full brew", () => {
const jungleShrooms = abilityByTalentEntry(12645);
const frogBones = abilityByTalentEntry(29741);
const bloodthistle = abilityByTalentEntry(6021);
const jungle = activate(jungleShrooms);
const ranks = { "coa-entry-6645": 1 };
expect(coaWitchDoctorIngredientCapacity({})).toBe(1);
expect(coaWitchDoctorIngredientCapacity(ranks)).toBe(2);
expect(coaWitchDoctorActiveIngredients([jungle], casterId, 2_000)).toEqual(new Set(["jungle-shrooms"]));
expect(coaWitchDoctorCanBrewIngredient(jungleShrooms, [jungle], casterId, {}, 2_000)).toBe(true);
expect(coaWitchDoctorCanBrewIngredient(frogBones, [jungle], casterId, {}, 2_000)).toBe(false);
expect(coaWitchDoctorCanBrewIngredient(frogBones, [jungle], casterId, ranks, 2_000)).toBe(true);
expect(coaWitchDoctorCanBrewIngredient(bloodthistle, [jungle, activate(frogBones)], casterId, ranks, 2_000))
.toBe(false);
});
it("identifies only the four exact Ingredient cast actions", () => {
expect(coaWitchDoctorIngredientIdForAbility(abilityByTalentEntry(12645))).toBe("jungle-shrooms");
expect(coaWitchDoctorIngredientIdForAbility(abilityByTalentEntry(29741))).toBe("frog-bones");
expect(coaWitchDoctorIngredientIdForAbility(abilityByTalentEntry(6021))).toBe("bloodthistle");
const knownAbility = abilityByTalentEntry(29741);
const fishOil = { ...knownAbility, dbcSpellId: 801662, talentEntryId: undefined };
expect(coaWitchDoctorIngredientIdForAbility(fishOil)).toBe("fish-oil");
expect(coaWitchDoctorIngredientIdForAbility({ ...fishOil, talentEntryId: 6645 })).toBeNull();
expect(coaWitchDoctorIngredientIdForAbility({ ...fishOil, classId: "mage" })).toBeNull();
expect(coaWitchDoctorIngredientIdForAbility({ ...knownAbility, talentEntryId: 29_740 })).toBeNull();
});
it("counts only this caster's live ingredient markers", () => {
const frogBones = abilityByTalentEntry(29741);
const marker = activate(frogBones);
const wrongSource = activate(frogBones, "another-witch-doctor", casterId);
const wrongTarget = activate(frogBones, casterId, "another-witch-doctor");
const expired = { ...activate(frogBones), expiresAt: 2_000 };
expect(coaWitchDoctorActiveIngredients(
[marker, wrongSource, wrongTarget, expired], casterId, 2_000,
)).toEqual(new Set(["frog-bones"]));
expect(coaWitchDoctorActiveIngredients([wrongSource, wrongTarget], casterId, 1_500).size).toBe(0);
});
it("adds only the sourced eight-second Frog Bones shield to Potion Toss's selected unit heal", () => {
const toss = abilityByTalentEntry(12646);
const frogBones = abilityByTalentEntry(29741);
const baseHeal = toss.effects.find((effect) => effect.kind === "heal");
const activeMarker = activate(frogBones);
const projected = coaWitchDoctorBrewingAbilityForCast(toss, [activeMarker], casterId, 2_000);
const shields = projected.effects.filter((effect) => effect.kind === "shield");
expect(projected.target).toBe("friendly");
expect(projected.effects.find((effect) => effect.kind === "heal")).toBe(baseHeal);
expect(shields).toEqual([COA_WITCH_DOCTOR_FROG_BONES_POTION_SHIELD]);
expect(shields[0]).toMatchObject({
basePoints: 100,
dieSides: 1,
coefficient: 0,
pointsPerLevel: 5,
sourceLevel: 10,
spiritCoefficient: 0.35,
bonusPowerCoefficient: 0.8,
durationMs: 8_000,
});
expect("maxScalingLevel" in shields[0] ? shields[0].maxScalingLevel : undefined).toBeUndefined();
const foreignMarker = activate(frogBones, "another-witch-doctor");
const inactiveProjection = coaWitchDoctorBrewingAbilityForCast(toss, [foreignMarker], casterId, 2_000);
expect(inactiveProjection.effects.some((effect) => effect.kind === "shield")).toBe(false);
const otherIngredientMarker = activate(abilityByTalentEntry(6021));
const nonFrogProjection = coaWitchDoctorBrewingAbilityForCast(toss, [otherIngredientMarker], casterId, 2_000);
expect(nonFrogProjection.effects.some((effect) => effect.kind === "shield")).toBe(false);
});
it("projects only the verified Frog Bones rider on Splash Potion's area heal", () => {
const splash = abilityByTalentEntry(7128);
const frogBones = abilityByTalentEntry(29741);
const baseHeal = splash.effects.find((effect) => effect.kind === "heal");
const activeMarker = activate(frogBones);
const projected = coaWitchDoctorBrewingAbilityForCast(splash, [activeMarker], casterId, 2_000);
expect(baseHeal).toMatchObject({
kind: "heal",
basePoints: 54,
dieSides: 3,
pointsPerLevel: 1.3710700273513794,
sourceLevel: 17,
bonusPowerCoefficient: 0.224494,
spiritCoefficient: 0.08,
scope: "party",
radius: 10,
maximumTargets: 8,
});
expect(projected.effects).toEqual([baseHeal, COA_WITCH_DOCTOR_FROG_BONES_SPLASH_SHIELD]);
expect(projected.effects.some((effect) => effect.kind === "shield"
&& effect.basePoints === 25 && effect.durationMs === 8_000)).toBe(true);
const unmarked = coaWitchDoctorBrewingAbilityForCast(splash, [], casterId, 2_000);
expect(unmarked.effects).toEqual([baseHeal]);
});
});