def parse_critterdb_traits(data, key): traits = [] attacks = [] for trait in data['stats'][key]: name = trait['name'] raw = trait['description'] overrides = list(AVRAE_ATTACK_OVERRIDES_RE.finditer(raw)) raw_atks = list(ATTACK_RE.finditer(raw)) raw_damage = list(JUST_DAMAGE_RE.finditer(raw)) filtered = AVRAE_ATTACK_OVERRIDES_RE.sub('', raw) desc = markdownify(filtered).strip() if overrides: for override in overrides: if override.group('simple'): attacks.append( Attack.from_dict({ 'name': override.group(2) or name, 'attackBonus': override.group(3) or None, 'damage': override.group(4) or None, 'details': desc })) elif (freeform_override := override.group('freeform')): try: attack_yaml = yaml.safe_load(freeform_override) except yaml.YAMLError: raise ExternalImportError( f"Monster had an invalid automation YAML ({data['name']}: {name})" ) if not isinstance(attack_yaml, list): attack_yaml = [attack_yaml] for atk in attack_yaml: if isinstance(atk, dict): atk['name'] = atk_name = atk.get('name') or name try: attacks.append(Attack.from_dict(atk)) except Exception: raise ExternalImportError( f"An automation YAML contained an invalid attack ({data['name']}: {atk_name})" ) else: raise ExternalImportError( f"An automation YAML contained an invalid attack ({data['name']}: {name})" ) # else: empty override, so skip this attack. elif raw_atks: for atk in raw_atks: attack_bonus = atk.group('attackBonus').lstrip('+') # Bonus damage bonus = "" if (bonus_damage_type := atk.group('damageTypeBonus')) and \ (bonus_damage := atk.group('damageBonusInt') or atk.group('damageBonusDice')):
attack_bonus = atk.group('attackBonus').lstrip('+') # Bonus damage bonus = "" if (bonus_damage_type := atk.group('damageTypeBonus')) and \ (bonus_damage := atk.group('damageBonusInt') or atk.group('damageBonusDice')): bonus = f" + {bonus_damage} [{bonus_damage_type}]" # Versatile Attacks if (vers_damage_type := atk.group('damageTypeVers')) and \ (verse_damage := atk.group('damageIntVers') or atk.group('damageDiceVers')): damage = f"{verse_damage} [{vers_damage_type}]" + bonus attacks.append( Attack.from_dict({ 'name': f"2 Handed {name}", 'attackBonus': attack_bonus, 'damage': damage, 'details': desc })) # Ranged Attacks if (ranged_damage_type := atk.group('damageTypeRanged')) and \ (ranged_damage := atk.group('damageRangedInt') or atk.group('damageRangedDice')): # ranged damage = f"{ranged_damage}[{ranged_damage_type}]" + bonus attacks.append( Attack.from_dict({ 'name': f"Ranged {name}", 'attackBonus': attack_bonus, 'damage': damage, 'details': desc }))