import type { AuraDefinition } from "./combatAuras"; import { WOW335_TALENT_NODES } from "./wow335Talents.generated"; export const WOW335_MAGE_WINTERS_CHILL_TALENT_ID = "talent-68"; export const WOW335_MAGE_WINTERS_CHILL_TAG = "wow335-mage-winters-chill"; const WINTERS_CHILL_TALENT = WOW335_TALENT_NODES.find((talent) => ( talent.id === WOW335_MAGE_WINTERS_CHILL_TALENT_ID )); function tooltipNumber(description: string, pattern: RegExp): number { const parsed = Number(description.match(pattern)?.[1]); return Number.isFinite(parsed) ? parsed : 0; } const WINTERS_CHILL_EFFECT_DESCRIPTION = WINTERS_CHILL_TALENT?.rankDescriptions[0] ?? ""; const WINTERS_CHILL_TARGET_CRITICAL_CHANCE_PER_STACK_PERCENT = tooltipNumber( WINTERS_CHILL_EFFECT_DESCRIPTION, /increasing the chance spells will critically strike the target by\s+(\d+(?:\.\d+)?)%/i, ); const WINTERS_CHILL_TARGET_HEALING_REDUCTION_PER_STACK_PERCENT = tooltipNumber( WINTERS_CHILL_EFFECT_DESCRIPTION, /reducing healing received by\s+(\d+(?:\.\d+)?)%/i, ); const WINTERS_CHILL_DURATION_MS = tooltipNumber( WINTERS_CHILL_EFFECT_DESCRIPTION, /for\s+(\d+(?:\.\d+)?)\s+seconds/i, ) * 1_000; const WINTERS_CHILL_MAXIMUM_STACKS = tooltipNumber( WINTERS_CHILL_EFFECT_DESCRIPTION, /stacks up to\s+(\d+)\s+times/i, ); function normalizedRank(rank: number): number { return Number.isFinite(rank) ? Math.max(0, Math.min(WINTERS_CHILL_TALENT?.rankEffects.length ?? 0, Math.trunc(rank))) : 0; } function generatedRankValue(rank: number, pattern: RegExp): number { const description = WINTERS_CHILL_TALENT?.rankDescriptions[normalizedRank(rank) - 1] ?? ""; return tooltipNumber(description, pattern); } export const WOW335_MAGE_WINTERS_CHILL_AURA: AuraDefinition = { id: "wow335:mage:winters-chill", name: "Winter's Chill", description: `Each stack increases incoming spell critical chance by ${WINTERS_CHILL_TARGET_CRITICAL_CHANCE_PER_STACK_PERCENT}% and reduces healing received by ${WINTERS_CHILL_TARGET_HEALING_REDUCTION_PER_STACK_PERCENT}% for ${WINTERS_CHILL_DURATION_MS / 1_000} seconds.`, disposition: "debuff", durationMs: WINTERS_CHILL_DURATION_MS, maxStacks: WINTERS_CHILL_MAXIMUM_STACKS, stackBehavior: "add", uniqueBySource: false, // Local child data is absent; WotLKDB 3.3.5a spell 12579 identifies it as Magic-dispellable. dispelCategory: "magic", modifiers: [{ kind: "healing", direction: "taken", operation: "percent", value: -WINTERS_CHILL_TARGET_HEALING_REDUCTION_PER_STACK_PERCENT / 100, }], utilityTags: [WOW335_MAGE_WINTERS_CHILL_TAG], }; export interface Wow335MageWintersChillEvent { readonly abilityName: string; readonly attackKind: string; readonly school: string; readonly periodic: boolean; readonly landedDamage: number; } export interface Wow335MageWintersChillProfile { readonly casterCriticalChanceBonusPercent: number; readonly procChancePercent: number; readonly targetCriticalChanceBonusPercentPerStack: number; readonly targetHealingReceivedReductionPercentPerStack: number; readonly durationMs: number; readonly maximumStacks: number; } /** * Local generated Mage talent node 68, ranks 11180/28592/28593. Its rank * descriptions give the passive crit bonus, proc chance, and custom 8-second * child effect values; the stock 3.3.5a child row is not this custom payload. */ export function wow335MageWintersChillProfileForRank(rank: number): Wow335MageWintersChillProfile { const normalized = normalizedRank(rank); const rankEffect = WINTERS_CHILL_TALENT?.rankEffects[normalized - 1]; return { casterCriticalChanceBonusPercent: generatedRankValue( normalized, /critical strike chance with all spells and abilities by\s+(\d+(?:\.\d+)?)%/i, ), procChancePercent: Math.max(0, Math.min(100, rankEffect?.procChance ?? 0)), targetCriticalChanceBonusPercentPerStack: WINTERS_CHILL_TARGET_CRITICAL_CHANCE_PER_STACK_PERCENT, targetHealingReceivedReductionPercentPerStack: WINTERS_CHILL_TARGET_HEALING_REDUCTION_PER_STACK_PERCENT, durationMs: WINTERS_CHILL_DURATION_MS, maximumStacks: WINTERS_CHILL_MAXIMUM_STACKS, }; } export function wow335MageWintersChillProcChancePercent( rank: number, event: Wow335MageWintersChillEvent, ): number { const profile = wow335MageWintersChillProfileForRank(rank); const eligible = profile.procChancePercent > 0 && event.attackKind === "spell" && event.school === "frost" && !event.periodic && Number.isFinite(event.landedDamage) && event.landedDamage > 0 && event.abilityName.toLowerCase() !== "frozen orb"; return eligible ? profile.procChancePercent : 0; } export function wow335MageWintersChillShouldProc( rank: number, event: Wow335MageWintersChillEvent, roll: number, ): boolean { if (!Number.isFinite(roll) || roll < 0 || roll >= 1) return false; return roll < wow335MageWintersChillProcChancePercent(rank, event) / 100; } export function wow335MageWintersChillStacksAfterEvent( currentStacks: number, rank: number, event: Wow335MageWintersChillEvent, roll: number, ): number { const profile = wow335MageWintersChillProfileForRank(rank); const stacks = Number.isFinite(currentStacks) ? Math.max(0, Math.min(profile.maximumStacks, Math.trunc(currentStacks))) : 0; return wow335MageWintersChillShouldProc(rank, event, roll) ? Math.min(profile.maximumStacks, stacks + 1) : stacks; } export function wow335MageWintersChillTargetModifiers(stacks: number): { readonly criticalChanceBonusPercent: number; readonly healingReceivedReductionPercent: number; } { const normalized = Number.isFinite(stacks) ? Math.max(0, Math.min(WINTERS_CHILL_MAXIMUM_STACKS, Math.trunc(stacks))) : 0; return { criticalChanceBonusPercent: normalized * WINTERS_CHILL_TARGET_CRITICAL_CHANCE_PER_STACK_PERCENT, healingReceivedReductionPercent: normalized * WINTERS_CHILL_TARGET_HEALING_REDUCTION_PER_STACK_PERCENT, }; }