49 lines
2.0 KiB
TypeScript
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
|
|
));
|
|
}
|