import { beforeEach, describe, expect, it } from "vitest"; import { COA_ABILITIES_BY_CLASS } from "./coaAbilityCatalog"; import { coaTreeEntryById } from "./coaLiveCatalog"; import { COA_BARBARIAN_DECAPITATE_HEALTH_THRESHOLD, COA_BARBARIAN_DECAPITATE_TALENT_ENTRY_ID, coaBarbarianDecapitateTargetAllowed, isCoaBarbarianDecapitate, } from "./coaBarbarianExecuteRuntime"; import { useCombatStore } from "./combatStore"; import { evaluateCombatRotation, type RotationContext } from "./combatRotation"; import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry"; import { clearPartyRuntimeRegistry, registerPartyRuntimePosition, updatePartyRuntimeTargetVisibility, } from "./partyRuntimeRegistry"; import { partyAbilityTargetIsValid } from "./partyRuntime"; import { createConfiguredPartyMember } from "./partyStore"; import { resourceProfileForClass } from "./abilityCatalog"; const TARGET_ID = "coa-decapitate-target"; const TALENT_RANKS = Object.freeze({ [`coa-entry-${COA_BARBARIAN_DECAPITATE_TALENT_ENTRY_ID}`]: 1 }); function decapitate() { const ability = COA_ABILITIES_BY_CLASS.barbarian.find((candidate) => ( candidate.talentEntryId === COA_BARBARIAN_DECAPITATE_TALENT_ENTRY_ID )); if (!ability) throw new Error("The source-backed CoA Barbarian Decapitate action is missing."); return ability; } function setTargetHealth(health: number): void { useCombatStore.setState((state) => ({ mobs: { ...state.mobs, [TARGET_ID]: { ...state.mobs[TARGET_ID]!, health, dead: health <= 0 }, }, })); } function rotationContext(targetHealth: number): RotationContext { const ability = decapitate(); const resource = resourceProfileForClass("barbarian").type; return { now: 1_000, classId: "barbarian", specializationId: "barbarian:brutality", role: "damage", level: 60, health: 1_000, maxHealth: 1_000, resources: { [resource]: 10_000 }, maximumResources: { [resource]: 10_000 }, primaryResource: resource, comboPoints: 0, comboTargetId: null, runes: [], abilities: [ability], hostileTarget: { id: TARGET_ID, health: targetHealth, maxHealth: 1_000 }, hostileCount: 1, }; } describe("CoA Barbarian Decapitate execute window", () => { beforeEach(() => { clearMobRuntimeRegistry(); clearPartyRuntimeRegistry(); useCombatStore.getState().initializeCharacter({ classId: "barbarian", specializationId: "barbarian:brutality", role: "damage", level: 60, talentRanks: TALENT_RANKS, }); useCombatStore.getState().setPlayerPosition([0, 0, 0]); useCombatStore.getState().registerMob(TARGET_ID, { name: "Decapitate target", maxHealth: 1_000, xpReward: 0, }); setMobPosition(TARGET_ID, [2, 0, 0]); useCombatStore.getState().selectMob(TARGET_ID); }); it("pins the execute threshold to the source-backed tree description and exact action identity", () => { const source = coaTreeEntryById(COA_BARBARIAN_DECAPITATE_TALENT_ENTRY_ID); expect(source?.rankDescriptions?.[0]).toMatchObject({ spellId: 804414 }); expect(source?.description).toContain("Only usable on enemies below 35% health."); expect(COA_BARBARIAN_DECAPITATE_HEALTH_THRESHOLD).toBe(0.35); const ability = decapitate(); expect(isCoaBarbarianDecapitate(ability)).toBe(true); expect(coaBarbarianDecapitateTargetAllowed(ability, { health: 349, maxHealth: 1_000 })).toBe(true); expect(coaBarbarianDecapitateTargetAllowed(ability, { health: 350, maxHealth: 1_000 })).toBe(false); expect(coaBarbarianDecapitateTargetAllowed(ability, { health: 351, maxHealth: 1_000 })).toBe(false); for (const target of [undefined, null, { health: 349 }, { health: 349, maxHealth: 0 }, { health: 0, maxHealth: 1_000 }, { health: 349, maxHealth: 1_000, dead: true }]) { expect(coaBarbarianDecapitateTargetAllowed(ability, target)).toBe(false); } expect(isCoaBarbarianDecapitate({ classId: "guardian", talentEntryId: 5744 })).toBe(false); expect(isCoaBarbarianDecapitate({ classId: "barbarian", talentEntryId: 1234 })).toBe(false); expect(coaBarbarianDecapitateTargetAllowed({ classId: "barbarian", talentEntryId: 1234 }, undefined)).toBe(true); expect(coaBarbarianDecapitateTargetAllowed({ classId: "guardian", talentEntryId: 5744 }, undefined)).toBe(true); }); it("rejects the player's boundary cast without spending resource or cooldown, then allows it below 35%", () => { const ability = decapitate(); expect(useCombatStore.getState().talentRanks[`coa-entry-${ability.talentEntryId}`]).toBe(1); setTargetHealth(350); const before = useCombatStore.getState(); const rejected = useCombatStore.getState().castAbility(ability.id, { now: 1_000, targetActorId: TARGET_ID }); const after = useCombatStore.getState(); expect(rejected).toMatchObject({ ok: false, reason: "condition-not-met" }); expect(after.resourcePools).toEqual(before.resourcePools); expect(after.cooldowns).toEqual(before.cooldowns); expect(after.globalCooldownEndsAt).toBe(before.globalCooldownEndsAt); setTargetHealth(349); expect(useCombatStore.getState().castAbility(ability.id, { now: 2_000, targetActorId: TARGET_ID })) .toMatchObject({ ok: true, abilityId: ability.id }); }); it("applies the same target boundary to a Barbarian companion", () => { const ability = decapitate(); const companion = createConfiguredPartyMember({ name: "Brutality companion", classId: "barbarian", specialization: "Brutality", specializationId: "barbarian:brutality", talentTreeId: "barbarian:brutality", talentRanks: TALENT_RANKS, role: "damage", }, 60, 0, "coa-decapitate-companion"); registerPartyRuntimePosition(companion.id, [0, 0, 0]); updatePartyRuntimeTargetVisibility(companion.id, TARGET_ID, true); setTargetHealth(350); expect(partyAbilityTargetIsValid(companion, ability, TARGET_ID, undefined, 1_000)).toBe(false); setTargetHealth(349); expect(partyAbilityTargetIsValid(companion, ability, TARGET_ID, undefined, 1_000)).toBe(true); }); it("keeps Decapitate out of the companion rotation above and at the boundary", () => { const belowThreshold = evaluateCombatRotation(rotationContext(349)); expect(belowThreshold?.ability.id).toBe(decapitate().id); expect(evaluateCombatRotation(rotationContext(350))?.ability.id).not.toBe(decapitate().id); expect(evaluateCombatRotation(rotationContext(500))?.ability.id).not.toBe(decapitate().id); }); });