1a9e5e2c18
- Rules abstraction (5e + pf2e) behind one interface, fully tested - Pure combat engine: turn-order safe across add/remove/reorder/init-change - Dexie storage with real cascade deletes + Zod validation on write - Seedable dice engine with notation parser - Lazy SRD compendium (code-split), bestiary -> combat - Strict TS, 54 unit tests, Playwright e2e smoke (all green) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
124 lines
4.5 KiB
Python
124 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
"""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"
|
|
|
|
def parse_mpmb(filepath):
|
|
with open(filepath, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
# Find all list assignments: ListName["key"] = { ... };
|
|
# Strategy: extract each list type by finding assignment patterns
|
|
lists = {}
|
|
list_names = [
|
|
"SourceList", "MagicItemsList", "SpellsList", "ClassList",
|
|
"RaceList", "FeatsList", "BackgroundList", "BackgroundFeatureList",
|
|
"CreatureList", "WeaponsList", "AmmoList", "GearList", "CompanionList"
|
|
]
|
|
|
|
for list_name in list_names:
|
|
entries = {}
|
|
# Pattern: ListName["key"] = { ... };
|
|
# Match multi-line objects by counting braces
|
|
pattern = re.escape(list_name) + r'\["([^"]+)"\]\s*=\s*\{'
|
|
for match in re.finditer(pattern, content):
|
|
key = match.group(1)
|
|
start = match.end() - 1 # position of opening {
|
|
# Find matching closing brace
|
|
depth = 0
|
|
i = start
|
|
in_string = False
|
|
string_char = None
|
|
while i < len(content):
|
|
c = content[i]
|
|
if in_string:
|
|
if c == '\\':
|
|
i += 2
|
|
continue
|
|
if c == string_char:
|
|
in_string = False
|
|
i += 1
|
|
continue
|
|
if c in ('"', "'"):
|
|
in_string = True
|
|
string_char = c
|
|
elif c == '{':
|
|
depth += 1
|
|
elif c == '}':
|
|
depth -= 1
|
|
if depth == 0:
|
|
obj_str = content[start:i+1]
|
|
break
|
|
elif c == '/' and i+1 < len(content) and content[i+1] in ('/', '*'):
|
|
# Skip comments
|
|
if content[i+1] == '/':
|
|
while i < len(content) and content[i] != '\n':
|
|
i += 1
|
|
continue
|
|
elif content[i+1] == '*':
|
|
i += 2
|
|
while i < len(content) and not (content[i] == '*' and content[i+1] == '/'):
|
|
i += 1
|
|
i += 2
|
|
continue
|
|
i += 1
|
|
else:
|
|
continue # no closing brace found
|
|
|
|
# Store raw object text (can't safely eval, but we can extract key fields)
|
|
entries[key] = obj_str
|
|
|
|
if entries:
|
|
lists[list_name] = entries
|
|
print(f" {list_name}: {len(entries)} entries")
|
|
|
|
return lists
|
|
|
|
def safe_extract_fields(obj_str):
|
|
"""Extract common fields from a JS object literal string."""
|
|
fields = {}
|
|
patterns = {
|
|
'name': r'name\s*:\s*["\']([^"\']+)["\']',
|
|
'type': r'type\s*:\s*["\']([^"\']+)["\']',
|
|
'rarity': r'rarity\s*:\s*["\']([^"\']+)["\']',
|
|
'source': r'source\s*:\s*(\[\[.*?\]\])',
|
|
'description': r'description\s*:\s*["\']([^"]+)["\']',
|
|
'weight': r'weight\s*:\s*(-?\d+)',
|
|
'prerequisite': r'prerequisite\s*:\s*["\']([^"\']+)["\']',
|
|
}
|
|
for field, pattern in patterns.items():
|
|
m = re.search(pattern, obj_str)
|
|
if m:
|
|
val = m.group(1)
|
|
if field == 'weight':
|
|
val = int(val)
|
|
elif field == 'source':
|
|
# Parse [[SourceName, page], [SourceName2, page2]]
|
|
sources = re.findall(r'\["([^"]+)",?\s*(\d+)?\]', val)
|
|
val = [{"source": s[0], "page": int(s[1]) if s[1] else None} for s in sources]
|
|
fields[field] = val
|
|
return fields
|
|
|
|
if __name__ == "__main__":
|
|
print("Parsing MPMB data file...")
|
|
lists = parse_mpmb(INPUT)
|
|
|
|
os.makedirs(OUT_DIR, exist_ok=True)
|
|
|
|
# Save raw entry counts
|
|
for name, entries in lists.items():
|
|
# Extract key fields for each entry
|
|
extracted = {}
|
|
for key, obj_str in entries.items():
|
|
extracted[key] = safe_extract_fields(obj_str)
|
|
|
|
outname = f"mpmb-{name.lower().replace('list','')}.json"
|
|
outpath = os.path.join(OUT_DIR, outname)
|
|
with open(outpath, 'w') as f:
|
|
json.dump(extracted, f, indent=2)
|
|
print(f" Saved {outname}: {len(extracted)} entries")
|
|
|
|
print("\nDone! Files saved to", OUT_DIR)
|