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 { 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]); }); });