import type { AbilityDefinition, AbilityEffect } from "./abilityCatalog"; import type { ProcTrigger } from "./combatAuras"; export const ROM_PRIEST_WARRIOR_CONDENSED_RAGE_PHYSICAL_DAMAGE_BONUS = 0.22; export const ROM_PRIEST_CHAMPION_RAGE_CONTROL_PER_COMPLETED_CHANNEL_SECOND = 20; export const ROM_PRIEST_CHAMPION_RAGE_CONTROL_CHANNEL_SECOND_MS = 1_000; export const ROM_PRIEST_VOW_OF_SILENCE_NPC_DURATION_MS = 8_400; export const ROM_PRIEST_VOW_OF_SILENCE_CHARACTER_DURATION_MS = 4_200; /** Current CoA Vow of Silence is 8.4s on NPCs and 4.2s on player targets. */ export function romPriestVowOfSilenceDurationMs(characterDefender: boolean): number { return characterDefender ? ROM_PRIEST_VOW_OF_SILENCE_CHARACTER_DURATION_MS : ROM_PRIEST_VOW_OF_SILENCE_NPC_DURATION_MS; } /** Current CoA Condensed Rage (493038) passively increases Physical Damage by 22%. */ export function romPriestWarriorCondensedRagePhysicalDamageMultiplier( hasCondensedRage: boolean, ): number { return hasCondensedRage ? 1 + ROM_PRIEST_WARRIOR_CONDENSED_RAGE_PHYSICAL_DAMAGE_BONUS : 1; } export type RomPriestChampionRageControlProgress = | { kind: "channel"; durationMs: number } | { kind: "ordinary-cast"; durationMs: number }; /** * Current CoA Rage Control (498610) grants 20 Rage per second of channeling. * Only whole completed channel-seconds pay out; an ordinary cast never counts. */ export function romPriestChampionRageControlGain( learnedRageControl: boolean, progress: RomPriestChampionRageControlProgress, ): number { if (!learnedRageControl || progress.kind !== "channel" || !Number.isFinite(progress.durationMs) || progress.durationMs <= 0) return 0; const completedSeconds = Math.floor( progress.durationMs / ROM_PRIEST_CHAMPION_RAGE_CONTROL_CHANNEL_SECOND_MS, ); return completedSeconds * ROM_PRIEST_CHAMPION_RAGE_CONTROL_PER_COMPLETED_CHANNEL_SECOND; } export const ROM_PRIEST_CHAMPION_REGENERATE_PROC_ID = "rom-priest-protection-rune-seal-reset"; export const ROM_PRIEST_CHAMPION_REGENERATE_PROC_ACTION_ID = "rom-priest-protection-rune-seal-reset-heart-rune"; export const ROM_PRIEST_CHAMPION_REGENERATE_PROC_AURA_ID = "rom-priest-protection-rune-seal-regenerate"; export const ROM_PRIEST_CHAMPION_PROTECTION_RUNE_SEAL_SPELL_ID = 498_613; export const ROM_PRIEST_CHAMPION_REGENERATE_SPELL_ID = 490_269; export const ROM_PRIEST_CHAMPION_HEART_RUNE_ENERGY_SPELL_ID = 498_612; /** RuneWaker's exact passive tooltip supplies 8%; current CoA says only "slight chance." */ export const ROM_PRIEST_CHAMPION_REGENERATE_PROC_CHANCE = 0.08; const ROM_PRIEST_REGENERATE_ABILITY_ID = "rom-priest-general-2"; const PROTECTION_RUNE_SEAL_REGENERATE_PROC_AURA = { id: ROM_PRIEST_CHAMPION_REGENERATE_PROC_AURA_ID, name: "Protection Rune Seal: Regenerate", disposition: "buff" as const, // Keep the exact 20-second final HoT tick eligible before aura expiry runs. durationMs: 20_001, hideFromAuraStrip: true, procs: [{ id: ROM_PRIEST_CHAMPION_REGENERATE_PROC_ID, triggers: "periodic" as const, abilityIds: [ROM_PRIEST_REGENERATE_ABILITY_ID], chance: ROM_PRIEST_CHAMPION_REGENERATE_PROC_CHANCE, action: { kind: "custom" as const, id: ROM_PRIEST_CHAMPION_REGENERATE_PROC_ACTION_ID }, }], }; export interface RomPriestChampionProtectionRuneSealProcContext { readonly primaryClassId: string | null; readonly secondaryClassId?: string | null; readonly learnedPassiveSpellIds: readonly number[]; readonly trigger: ProcTrigger; readonly triggeringAbilitySpellId?: number; readonly healingAmount?: number; } /** * Protection Rune Seal resets Heart Rune Energy only for a learned exact pair * and a positive periodic heal from Regenerate. The chance roll is owned by * the aura proc definition above. */ export function romPriestChampionProtectionRuneSealProcIsEligible( context: RomPriestChampionProtectionRuneSealProcContext, ): boolean { return context.primaryClassId === "rom-priest" && context.secondaryClassId === "rom-champion" && context.learnedPassiveSpellIds.includes(ROM_PRIEST_CHAMPION_PROTECTION_RUNE_SEAL_SPELL_ID) && context.trigger === "periodic" && context.triggeringAbilitySpellId === ROM_PRIEST_CHAMPION_REGENERATE_SPELL_ID && Number.isFinite(context.healingAmount) && (context.healingAmount ?? 0) > 0; } /** * The local Infused Light Chain cooldown rider is not present in current CoA: * ID 498610 is now Rage Control, so it no longer overrides Chain of Light. */ export function romPriestChampionCooldownOverride( _ability: AbilityDefinition, _learnedPassiveSourceIds: readonly number[], ): number | null { return null; } /** Righteous Explosion removes every modeled harmful effect from the party. */ export function romPriestChampionCastEffects( ability: AbilityDefinition, effects: readonly AbilityEffect[], learnedPassiveSpellIds: readonly number[] = [], ): readonly AbilityEffect[] { if (ability.source === "runewaker" && ability.classId === "rom-priest" && ability.dbcSpellId === 490269 && learnedPassiveSpellIds.includes(ROM_PRIEST_CHAMPION_PROTECTION_RUNE_SEAL_SPELL_ID)) { return [{ kind: "apply-aura", aura: PROTECTION_RUNE_SEAL_REGENERATE_PROC_AURA, recipient: "caster", sourceEffectType: -1, sourceAuraType: -1, miscValue: 0, triggerSpellId: 498613, }, ...effects]; } if (ability.source !== "runewaker" || ability.classId !== "rom-champion" || ability.secondaryClassId !== "rom-priest" || ability.dbcSpellId !== 499836) return effects; return effects.map((effect) => effect.kind === "dispel" && effect.mode === "cleanse" ? { ...effect, maxCount: 99, scope: "party" } : effect); }