import type { Armor5e } from '@/lib/compendium/types'; import type { ArmorMechanics } from '../types'; /** * 5e equipment armor → unified mechanics. * * The source `ac` is a string ("11 + Dex", "14 + Dex (max 2)", "18", "+2" for a * shield) and `dexCap` is null (light/unlimited), a number (medium cap), or 0 * (heavy). We trust `dexCap` and parse the leading number out of `ac`. */ export function normalizeArmor5e(raw: Armor5e): ArmorMechanics | null { if (!raw.name) return null; const type = (raw.type ?? '').toLowerCase(); const isShield = type === 'shield'; const acStr = raw.ac ?? ''; // Shields read "+2"; armor reads "11 + Dex" or "18". const baseAc = Number(/-?\d+/.exec(acStr)?.[0] ?? (isShield ? 2 : 10)); const category: ArmorMechanics['category'] = isShield ? 'shield' : type === 'light' ? 'light' : type === 'medium' ? 'medium' : type === 'heavy' ? 'heavy' : 'unarmored'; // Shields don't cap Dex (they're additive); honor the source dexCap otherwise. // When the source omits dexCap, fall back by category: heavy ignores Dex (0), // medium caps at +2, light/unarmored is uncapped (null). const dexCap = isShield ? null : raw.dexCap !== undefined && raw.dexCap !== null ? raw.dexCap : category === 'heavy' ? 0 : category === 'medium' ? 2 : null; return { name: raw.name, category, baseAc, dexCap, stealthDisadvantage: !!raw.stealthPenalty, }; }