import { WOW335_TALENT_NODES } from "./wow335Talents.generated"; export interface WoW335PaladinRedoubtProfile { readonly blockChancePercent: number; readonly procChance: number; readonly strengthPercent: number; readonly damageTakenReductionPercent: number; readonly durationMs: number; } export interface WoW335PaladinVindicationProfile { readonly rank: number; readonly procChance: number; readonly attackPowerReduction: number; readonly durationMs: number; } export interface WoW335PaladinBlessedLifeProfile { readonly rank: number; readonly procChance: number; readonly incomingDamageMultiplier: number; } export const WOW335_PALADIN_BLESSED_LIFE_TALENT_ID = "talent-1744"; const WOW335_PALADIN_VINDICATION_TALENT_ID = "talent-1633"; // The local child aura row has no duration; WotLKDB spell 67 supplies 10 sec. // https://wotlkdb.com/?spell=67 const WOW335_PALADIN_VINDICATION_DURATION_MS = 10_000; /** Resolve only the generated rank chance and half-damage payload for Blessed Life. */ export function wow335PaladinBlessedLifeProfile(talentRank: number): WoW335PaladinBlessedLifeProfile | null { const rank = Math.min(3, Math.max(0, Math.trunc(Number.isFinite(talentRank) ? talentRank : 0))); if (rank === 0) return null; const talent = WOW335_TALENT_NODES.find((node) => node.id === WOW335_PALADIN_BLESSED_LIFE_TALENT_ID); const rankEffect = talent?.rankEffects[rank - 1]; const rawDescription = talent?.rawRankDescriptions[rank - 1] ?? ""; const descriptionChance = rawDescription.match(/All attacks against you have a \$h% chance to cause half damage\./i); const displayedChance = talent?.rankDescriptions[rank - 1]?.match( /All attacks against you have a (\d+(?:\.\d+)?)% chance to cause half damage\./i, ); if (!rankEffect || !descriptionChance || !displayedChance || Number(displayedChance[1]) !== rankEffect.procChance) return null; return { rank, procChance: Math.min(1, Math.max(0, rankEffect.procChance / 100)), incomingDamageMultiplier: 0.5, }; } /** Apply the tooltip's half-damage result to an incoming attack roll. */ export function wow335PaladinBlessedLifeDamageMultiplierForRoll(talentRank: number, roll: number): number { const profile = wow335PaladinBlessedLifeProfile(talentRank); if (!profile || !Number.isFinite(roll) || roll < 0 || roll >= 1) return 1; return roll < profile.procChance ? profile.incomingDamageMultiplier : 1; } /** * Resolve the generated Vindication rank chance and raw level-scaled tooltip. * WotLKDB's child AP value differs from the local tooltip formula, so this * deliberately uses only local values for proc chance and amount. */ export function wow335PaladinVindicationProfile( talentRank: number, playerLevel: number, ): WoW335PaladinVindicationProfile | null { const rank = Math.min(3, Math.max(0, Math.trunc(Number.isFinite(talentRank) ? talentRank : 0))); if (rank === 0) return null; const talent = WOW335_TALENT_NODES.find((node) => node.id === WOW335_PALADIN_VINDICATION_TALENT_ID); const rankEffect = talent?.rankEffects[rank - 1]; const rawDescription = talent?.rawRankDescriptions[rank - 1] ?? ""; if (!rankEffect || !/\$\{\s*4\.5\s*\*\s*\$PL\s*-\s*3\s*\}/.test(rawDescription)) return null; const level = Math.min(80, Math.max(1, Math.trunc(Number.isFinite(playerLevel) ? playerLevel : 1))); return { rank, procChance: Math.min(1, Math.max(0, rankEffect.procChance / 100)), attackPowerReduction: Math.max(0, 4.5 * level - 3), durationMs: WOW335_PALADIN_VINDICATION_DURATION_MS, }; } /** Read the rank values from the generated tooltip so local spell rows cannot override them. */ export function wow335PaladinRedoubtProfile( rankDescription: string | undefined, ): WoW335PaladinRedoubtProfile | null { if (!rankDescription) return null; const blockChance = rankDescription.match(/chance to block attacks with a shield by (\d+(?:\.\d+)?)%/i); const proc = rankDescription.match( /blocking an attack has a (\d+(?:\.\d+)?)% chance to increase your strength by (\d+(?:\.\d+)?)% and reduce your damage taken by (\d+(?:\.\d+)?)%\. lasts (\d+(?:\.\d+)?) seconds/i, ); if (!blockChance || !proc) return null; return { blockChancePercent: Number(blockChance[1]), procChance: Number(proc[1]) / 100, strengthPercent: Number(proc[2]), damageTakenReductionPercent: Number(proc[3]), durationMs: Number(proc[4]) * 1_000, }; } export function wow335PaladinRedoubtBuff( profile: WoW335PaladinRedoubtProfile, strength: number, ) { const gainedStrength = Math.round(Math.max(0, strength) * profile.strengthPercent / 100); return { id: "wow335:paladin:redoubt", name: "Redoubt", description: `Increases your Strength by ${profile.strengthPercent}% and reduces your damage taken by ${profile.damageTakenReductionPercent}% for ${profile.durationMs / 1_000} seconds.`, disposition: "buff" as const, durationMs: profile.durationMs, maxStacks: 1, stackBehavior: "refresh" as const, utilityTags: ["wow335-paladin-redoubt"], modifiers: [ { kind: "stat" as const, stat: "strength", operation: "percent" as const, value: profile.strengthPercent / 100 }, // Paladins gain two attack power for each point of Strength. { kind: "stat" as const, stat: "attack-power", operation: "flat" as const, value: gainedStrength * 2 }, { kind: "damage" as const, direction: "taken" as const, operation: "percent" as const, value: -profile.damageTakenReductionPercent / 100 }, ], }; }