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

49 lines
2.0 KiB
TypeScript

import type { AbilityDefinition } from "./abilityCatalog";
import { targetingRulesForAbility } from "./abilityRules";
import { COA_FIRST_PARTY_CLASS_IDS } from "./coaFirstPartyRoster";
import type { RotationContext } from "./combatRotation";
type CoaPeriodicContext = Pick<RotationContext, "selfId" | "maintainedPeriodicEffects">;
type MaintainedPeriodicEffect = NonNullable<RotationContext["maintainedPeriodicEffects"]>[number];
const COA_PERIODIC_SOURCES = new Set(["coa-starter", "coa-progression", "coa-tree"]);
/**
* Identifies the narrow CoA case where maintenance is target-specific. Callers
* should use the selected ability target (the same recipient used for scoring)
* when asking whether its effect is already present.
*/
export function usesCoaTargetedPeriodicMaintenance(
ability: AbilityDefinition,
_context?: CoaPeriodicContext,
): boolean {
if (!COA_FIRST_PARTY_CLASS_IDS.some((classId) => classId === ability.classId)
|| !ability.source || !COA_PERIODIC_SOURCES.has(ability.source)
|| targetingRulesForAbility(ability).shape !== "unit") return false;
return ability.effects.some((effect) => (
effect.kind === "dot" && (ability.target === "hostile" || ability.target === "any")
) || (
effect.kind === "hot"
&& effect.recipient !== "caster"
&& (ability.target === "friendly" || ability.target === "any")
));
}
/** A live periodic on another target or from another actor never maintains this cast target. */
export function coaPeriodicActionMaintainedOnTarget(
ability: AbilityDefinition,
context: CoaPeriodicContext,
targetId: string | null | undefined,
): boolean {
const casterId = context.selfId;
if (!usesCoaTargetedPeriodicMaintenance(ability, context) || !casterId || !targetId) return false;
return (context.maintainedPeriodicEffects ?? []).some((effect: MaintainedPeriodicEffect) => (
effect.abilityId === ability.id
&& effect.sourceActorId === casterId
&& effect.targetId === targetId
&& Number.isFinite(effect.remainingMs)
&& effect.remainingMs > 0
));
}