Phase 2: wire MPMB spell data in (XGtE/TCE + more sources)
The app loaded SRD-only spells; the parsed MPMB spell file was shallow (names only) and
unused. Now:
- parse_mpmb.py extracts the rich spell fields (level, school, casting time, range,
components, duration, save, ritual, classes) and writes to src/data/srd.
- New compendium/mpmb.ts normalizes MPMB spells to the Open5e Spell shape (school/time
expansion, source-code → label) — pure, unit-tested.
- loadSpells merges 204 non-SRD MPMB spells (95 XGtE, 21 TCE, + Eberron/Theros/Strixhaven/
Fizban's/etc.) into the 321 SRD spells (525 total), deduped by name.
- Spell type gains a field; the compendium spell list shows it ('Cantrip · XGtE')
and adds a Source filter.
Verified in-app (Toll the Dead → 'Cantrip · XGtE'). 333 tests green, build OK.
Only mpmb-spells.json regenerated; feats/others untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+20
-2
@@ -2,8 +2,9 @@
|
||||
"""Parse MPMB all_WotC_published.js into structured JSON files"""
|
||||
import re, json, os
|
||||
|
||||
INPUT = "/home/nilsb/Downloads/all_WotC_published.js"
|
||||
OUT_DIR = "/home/nilsb/Documents/Projects/TTRPG_manager/src/assets/srd"
|
||||
INPUT = os.environ.get("MPMB_INPUT", "/home/nilsb/Downloads/all_WotC_published.js")
|
||||
# Output path (the app reads from src/data/srd). Override with MPMB_OUT for a dry run.
|
||||
OUT_DIR = os.environ.get("MPMB_OUT", "/home/nilsb/Documents/Projects/TTRPG_manager/src/data/srd")
|
||||
|
||||
def parse_mpmb(filepath):
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
@@ -87,6 +88,16 @@ def safe_extract_fields(obj_str):
|
||||
'description': r'description\s*:\s*["\']([^"]+)["\']',
|
||||
'weight': r'weight\s*:\s*(-?\d+)',
|
||||
'prerequisite': r'prerequisite\s*:\s*["\']([^"\']+)["\']',
|
||||
# --- spell fields (additive; only present on SpellsList entries) ---
|
||||
'level': r'\blevel\s*:\s*(\d+)',
|
||||
'school': r'\bschool\s*:\s*["\']([^"\']+)["\']',
|
||||
'time': r'\btime\s*:\s*["\']([^"\']+)["\']',
|
||||
'range': r'\brange\s*:\s*["\']([^"\']+)["\']',
|
||||
'components': r'\bcomponents\s*:\s*["\']([^"\']+)["\']',
|
||||
'duration': r'\bduration\s*:\s*["\']([^"\']+)["\']',
|
||||
'save': r'\bsave\s*:\s*["\']([^"\']+)["\']',
|
||||
'ritual': r'\britual\s*:\s*(true|false)',
|
||||
'classes': r'\bclasses\s*:\s*(\[[^\]]*\])',
|
||||
}
|
||||
for field, pattern in patterns.items():
|
||||
m = re.search(pattern, obj_str)
|
||||
@@ -94,6 +105,13 @@ def safe_extract_fields(obj_str):
|
||||
val = m.group(1)
|
||||
if field == 'weight':
|
||||
val = int(val)
|
||||
elif field == 'level':
|
||||
val = int(val)
|
||||
elif field == 'ritual':
|
||||
val = (val == 'true')
|
||||
elif field == 'classes':
|
||||
# ["cleric","warlock"] -> list of class names
|
||||
val = re.findall(r'["\']([^"\']+)["\']', val)
|
||||
elif field == 'source':
|
||||
# Parse [[SourceName, page], [SourceName2, page2]]
|
||||
sources = re.findall(r'\["([^"]+)",?\s*(\d+)?\]', val)
|
||||
|
||||
Reference in New Issue
Block a user