def parse_spellcasting(monster): if 'trait' not in monster: monster['trait'] = [] known_spells = [] usual_dc = (0, 0) # dc, number of spells using dc usual_sab = (0, 0) # same thing caster_level = 1 for cast_type in monster['spellcasting']: trait = { 'name': cast_type['name'], 'text': render(cast_type['headerEntries']) } type_dc = re.search(r'\(spell save DC (\d+)', '\n'.join(cast_type['headerEntries'])) type_sab = re.search(r'{@?hit (\d+)}', '\n'.join(cast_type['headerEntries'])) type_caster_level = re.search(r'(\d+)[stndrh]{2}-level', '\n'.join(cast_type['headerEntries'])) type_spells = [] if 'will' in cast_type: type_spells.extend(extract_spell(s) for s in cast_type['will']) spells = render(', '.join(cast_type['will'])) trait['text'] += f"\nAt will: {spells}" if 'daily' in cast_type: for times_per_day, spells in cast_type['daily'].items(): each = ' each' if times_per_day.endswith('e') else '' times_per_day = times_per_day.rstrip('e') type_spells.extend(extract_spell(s) for s in spells) spells = render(', '.join(spells)) trait['text'] += f"\n{times_per_day}/day{each}: {spells}" if 'spells' in cast_type: for level, level_data in cast_type['spells'].items(): spells = level_data['spells'] level_text = get_spell_level(level) slots = f"{level_data.get('slots', 'unknown')} slots" if level != '0' else "at will" type_spells.extend(extract_spell(s) for s in spells) spells = render(', '.join(spells)) trait['text'] += f"\n{level_text} ({slots}): {spells}" trait['text'] = render(trait['text']) monster['trait'].append(trait) known_spells.extend(type_spells) if type_dc and (len(type_spells) > usual_dc[1] or not usual_dc[0]): usual_dc = (int(type_dc.group(1)), len(type_spells)) if type_sab and (len(type_spells) > usual_sab[1] or not usual_sab[0]): usual_sab = (int(type_sab.group(1)), len(type_spells)) if type_caster_level: caster_level = int(type_caster_level.group(1)) dc = usual_dc[0] sab = usual_sab[0] monster['spellcasting'] = { 'spells': known_spells, 'dc': dc, 'attackBonus': sab, 'casterLevel': caster_level } # overwrite old log.info( f"Lvl {caster_level}; DC: {dc}; SAB: {sab}; Spells: {known_spells}")
def object_actions(objects): for object in objects: if 'actionEntries' in object: object['entries'].append('__Actions__') object['entries'].append(render(object['actionEntries'])) del object['actionEntries'] # also unparsed return objects
def parse_invocations(): out = [] optfeats = get_data('optionalfeatures.json')['optionalfeature'] invocs = [i for i in optfeats if i['featureType'] == 'EI'] for invoc in invocs: log.info(f"Parsing invocation {invoc['name']}") text = render(invoc['entries']) if 'prerequisite' in invoc: prereqs = [] for prereq in invoc['prerequisite']: if prereq['type'] == 'prereqPact': prereqs.append(f"Pact of the {prereq['entry']}") elif prereq['type'] == 'prereqPatron': prereqs.append(f"Patron: {prereq['entry']}") elif prereq['type'] == 'prereqLevel': prereqs.append(f"Level {prereq['level']}") elif prereq['type'] == 'prereqSpell': prereqs.append(f"*{', '.join(prereq['entries'])}* spell") else: log.warning(f"Unknown prereq type: {prereq['type']}") text = f"*Prerequisite: {', '.join(prereqs)}*\n{text}" inv = { 'name': f"Warlock: Eldritch Invocation: {invoc['name']}", 'text': text, 'srd': invoc['source'] == 'PHB' } out.append(inv) return out
def parse(data): processed = [] for spell in data: log.info(f"Parsing {spell['name']}...") parsetime(spell) parserange(spell) parsecomponents(spell) parseduration(spell) parseclasses(spell) srdfilter(spell) ritual = spell.get('meta', {}).get('ritual', False) desc = render(spell['entries']) if 'entriesHigherLevel' in spell: higherlevels = render(spell['entriesHigherLevel']) \ .replace("**At Higher Levels**: ", "") else: higherlevels = None if NEW_AUTOMATION: automation = get_automation(spell) else: automation = get_automation_from_old(spell) newspell = { "name": spell['name'], "level": spell['level'], "school": spell['school'], "casttime": spell['time'], "range": spell['range'], "components": spell['components'], "duration": spell['duration'], "description": desc, "classes": spell['classes'], "subclasses": spell['subclasses'], "ritual": ritual, "higherlevels": higherlevels, "source": spell['source'], "page": spell.get('page', '?'), "concentration": spell['concentration'], "automation": automation, "srd": spell['srd'] } processed.append(recursive_tag(newspell)) processed = ensure_ml_order(processed) return processed
def parse_traits(raw): traits = [] for entry in raw['entries']: if entry['type'] == 'list': for item in entry['items']: trait = { 'name': item['name'], 'text': render(item.get('entry') or item.get('entries')) } traits.append(trait) elif entry['type'] in ('entries', 'section'): trait = {'name': entry['name'], 'text': render(entry['entries'])} traits.append(trait) else: log.warning(f"Unknown entry: {entry}") continue return traits
def render_variant_eqs(entries, inherits): for i, entry in enumerate(entries): if isinstance(entry, str): entries[i] = re.sub(r"{=(\w+)}", lambda m: inherits.get(m.group(1), m.group(0)), entry) else: entries[i] = render(entry) return entries
def parse_ac(data): for monster in data: log.info(f"Parsing {monster['name']} AC") if isinstance(monster['ac'][0], int): monster['ac'] = {'ac': int(monster['ac'][0])} elif isinstance(monster['ac'][0], dict): monster['ac'] = {'ac': int(monster['ac'][0]['ac']), 'armortype': render(monster['ac'][0].get('from', []), join_char=', ')} else: log.warning(f"Unknown AC type: {monster['ac']}") raise Exception return data
def monster_render(data): for monster in data: log.info(f"Rendering {monster['name']}") for t in ('trait', 'action', 'reaction', 'legendary'): log.info(f" Rendering {t}s") if t in monster: temp = [] for entry in monster[t]: text = render(entry['entries']) temp.append({'name': entry.get('name', ''), 'text': text}) monster[t] = temp if 'spellcasting' in monster: parse_spellcasting(monster) return data
def prerender(data): for item in data: if 'entries' in item: item['desc'] = render(item['entries']) del item['entries'] else: item['desc'] = "" if 'additionalEntries' in item: item['desc'] += f"\n\n{render(item['additionalEntries'])}" item['desc'] = item['desc'].strip() for k, v in item.items(): item[k] = recursive_tag(v) return data
def prerender(data): out = [] for feat in data: log.debug(feat['name']) desc = render(feat['entries']) prereq = parse_prereq(feat) ability = parse_ability(feat) new_feat = { "name": feat['name'], "prerequisite": prereq, "source": feat['source'], "page": feat['page'], "desc": desc, "ability": ability } out.append(new_feat) return out
def parse_classfeats(data): out = [] for _class in data: log.info(f"Parsing classfeats for class {_class['name']}...") for level in _class.get('classFeatures', []): for feature in level: fe = { 'name': f"{_class['name']}: {feature['name']}", 'text': render(feature['entries']), 'srd': _class['srd'] } log.info(f"Found feature: {fe['name']}") out.append(fe) options = [ e for e in feature['entries'] if isinstance(e, dict) and e.get('type') == 'options' ] for option in options: for opt_entry in option.get('entries', []): fe = { 'name': f"{_class['name']}: {feature['name']}: {_resolve_name(opt_entry)}", 'text': f"{render(opt_entry['entries'])}", 'srd': _class['srd'] } log.info(f"Found option: {fe['name']}") out.append(fe) subentries = [ e for e in feature['entries'] if isinstance(e, dict) and e.get('type') == 'entries' ] for opt_entry in subentries: fe = { 'name': f"{_class['name']}: {feature['name']}: {_resolve_name(opt_entry)}", 'text': f"{render(opt_entry['entries'])}", 'srd': _class['srd'] } log.info(f"Found subentry: {fe['name']}") out.append(fe) for subclass in _class.get('subclasses', []): log.info(f"Parsing classfeats for subclass {subclass['name']}...") for level in subclass.get('subclassFeatures', []): for feature in level: options = [ f for f in feature.get('entries', []) if isinstance(f, dict) and f['type'] == 'options' ] # battlemaster only for option in options: for opt_entry in option.get('entries', []): fe = { 'name': f"{_class['name']}: {option['name']}: " f"{_resolve_name(opt_entry)}", 'text': render(opt_entry['entries']), 'srd': subclass.get('srd', False) } log.info(f"Found option: {fe['name']}") out.append(fe) for entry in feature.get('entries', []): if not isinstance(entry, dict): continue if not entry.get('type') == 'entries': continue fe = { 'name': f"{_class['name']}: {subclass['name']}: {entry['name']}", 'text': render(entry['entries']), 'srd': subclass.get('srd', False) } log.info(f"Found feature: {fe['name']}") out.append(fe) options = [ e for e in entry['entries'] if isinstance(e, dict) and e.get('type') == 'options' ] for option in options: for opt_entry in option.get('entries', []): fe = { 'name': f"{_class['name']}: {subclass['name']}: {entry['name']}: " f"{_resolve_name(opt_entry)}", 'text': render(opt_entry['entries']), 'srd': subclass.get('srd', False) } log.info(f"Found option: {fe['name']}") out.append(fe) subentries = [ e for e in entry['entries'] if isinstance(e, dict) and e.get('type') == 'entries' ] for opt_entry in subentries: fe = { 'name': f"{_class['name']}: {subclass['name']}: {entry['name']}: " f"{_resolve_name(opt_entry)}", 'text': f"{render(opt_entry['entries'])}", 'srd': _class['srd'] } log.info(f"Found subentry: {fe['name']}") out.append(fe) return out