import type { Spell } from './types'; /** A parsed MPMB spell entry (scripts/parse_mpmb.py output). */ export interface MpmbSpell { name: string; source?: { source: string; page: number | null }[]; level?: number; school?: string; time?: string; range?: string; components?: string; duration?: string; save?: string; ritual?: boolean; classes?: string[]; description?: string; } // MPMB sourcebook codes → readable labels. Unknown codes pass through unchanged. const SOURCE_LABEL: Record = { P: 'PHB', X: 'XGtE', T: 'TCE', V: 'VGtM', M: 'MToF', MM: 'MM', E: 'Eberron', 'E:RLW': 'Eberron', WGtE: 'Wayfinder', MOT: 'Theros', G: 'GGtR', S: 'SCAG', SCC: 'Strixhaven', FToD: "Fizban's", BoMT: 'BoMT', AcqInc: 'Acq. Inc.', RotF: 'Rime', 'S:AiS': 'Spelljammer', 'P:AitM': 'Planescape', LLoK: 'Lost Lab', W: 'Witchlight', DMG: 'DMG', }; const SCHOOL: Record = { abjur: 'Abjuration', conj: 'Conjuration', div: 'Divination', ench: 'Enchantment', evoc: 'Evocation', illus: 'Illusion', necro: 'Necromancy', trans: 'Transmutation', }; const TIME: Record = { '1 a': '1 action', '1 bns': '1 bonus action', '1 rea': '1 reaction', '1 min': '1 minute', '10 min': '10 minutes', '1 h': '1 hour', '8 h': '8 hours', '24 h': '24 hours', }; /** The primary (canonical, first-listed) source code of a parsed MPMB entry. */ export function mpmbPrimarySource(raw: { source?: { source: string }[] }): string { return raw.source?.[0]?.source ?? ''; } /** Readable label for an MPMB source code (e.g. 'X' -> 'XGtE'). */ export function sourceLabel(code: string): string { return SOURCE_LABEL[code] ?? code; } function slugify(name: string): string { return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); } /** Normalize a parsed MPMB spell into the app's Open5e-shaped Spell. */ export function normalizeMpmbSpell(raw: MpmbSpell): Spell | null { if (!raw.name) return null; const level = typeof raw.level === 'number' ? raw.level : 0; const conc = /conc/i.test(raw.duration ?? ''); const school = raw.school ? SCHOOL[raw.school.toLowerCase()] ?? raw.school : ''; return { slug: slugify(raw.name), name: raw.name, desc: raw.description ?? '', level_int: level, level: level === 0 ? 'Cantrip' : `Level ${level}`, school, casting_time: raw.time ? TIME[raw.time] ?? raw.time : '', range: raw.range ?? '', duration: raw.duration ?? '', concentration: conc ? 'yes' : 'no', requires_concentration: conc, ritual: raw.ritual ? 'yes' : 'no', can_be_cast_as_ritual: !!raw.ritual, components: raw.components ?? '', dnd_class: (raw.classes ?? []).map((c) => c.charAt(0).toUpperCase() + c.slice(1)).join(', '), source: sourceLabel(mpmbPrimarySource(raw)), }; }