def from_data(cls, d): ability_scores = BaseStats.from_dict(d['ability_scores']) saves = Saves.from_dict(d['saves']) skills = Skills.from_dict(d['skills']) display_resists = Resistances.from_dict(d['display_resists'], smart=False) traits = [Trait(**t) for t in d['traits']] actions = [Trait(**t) for t in d['actions']] reactions = [Trait(**t) for t in d['reactions']] legactions = [Trait(**t) for t in d['legactions']] bonus_actions = [Trait(**t) for t in d.get('bonus_actions', [])] mythic_actions = [Trait(**t) for t in d.get('mythic_actions', [])] resistances = Resistances.from_dict(d['resistances']) attacks = AttackList.from_dict(d['attacks']) if d['spellbook'] is not None: spellcasting = MonsterSpellbook.from_dict(d['spellbook']) else: spellcasting = None return cls(d['name'], d['size'], d['race'], d['alignment'], d['ac'], d['armortype'], d['hp'], d['hitdice'], d['speed'], ability_scores, saves, skills, d['senses'], display_resists, d['condition_immune'], d['languages'], d['cr'], d['xp'], traits=traits, actions=actions, reactions=reactions, legactions=legactions, bonus_actions=bonus_actions, mythic_actions=mythic_actions, la_per_round=d['la_per_round'], passiveperc=d['passiveperc'], # augmented resistances=resistances, attacks=attacks, proper=d['proper'], image_url=d['image_url'], spellcasting=spellcasting, token_free_fp=d['token_free'], token_sub_fp=d['token_sub'], # sourcing source=d['source'], entity_id=d['id'], page=d['page'], url=d['url'], is_free=d['isFree'])
async def attack_import(self, ctx, *, data): """ Imports an attack from JSON exported from the Avrae Dashboard. """ character: Character = await Character.from_ctx(ctx) try: attack_json = json.loads(data) except json.decoder.JSONDecodeError: return await ctx.send("This is not a valid attack.") if not isinstance(attack_json, list): attack_json = [attack_json] try: attacks = AttackList.from_dict(attack_json) except: return await ctx.send("This is not a valid attack.") conflicts = [a for a in character.overrides.attacks if a.name.lower() in [new.name.lower() for new in attacks]] if conflicts: if await confirm(ctx, f"This will overwrite {len(conflicts)} attacks with the same name " f"({', '.join(c.name for c in conflicts)}). Continue?"): for conflict in conflicts: character.overrides.attacks.remove(conflict) else: return await ctx.send("Okay, aborting.") character.overrides.attacks.extend(attacks) await character.commit(ctx) out = f"Imported {len(attacks)} attacks:\n{attacks.build_str(character)}" await ctx.send(out)
def from_bestiary(cls, data): for key in ('traits', 'actions', 'reactions', 'legactions'): data[key] = [Trait(**t) for t in data.pop(key)] data['spellcasting'] = Spellbook.from_dict(data.pop('spellbook')) data['saves'] = Saves.from_dict(data['saves']) data['skills'] = Skills.from_dict(data['skills']) data['ability_scores'] = BaseStats.from_dict(data['ability_scores']) data['attacks'] = AttackList.from_dict(data['attacks']) if 'display_resists' in data: data['display_resists'] = Resistances.from_dict( data['display_resists']) return cls(**data)
def from_bestiary(cls, data, source): for key in ('traits', 'actions', 'reactions', 'legactions'): data[key] = [Trait(**t) for t in data.pop(key)] data['spellcasting'] = MonsterSpellbook.from_dict(data.pop('spellbook')) data['saves'] = Saves.from_dict(data['saves']) data['skills'] = Skills.from_dict(data['skills']) data['ability_scores'] = BaseStats.from_dict(data['ability_scores']) data['attacks'] = AttackList.from_dict(data['attacks']) if 'resistances' in data: data['resistances'] = Resistances.from_dict(data['resistances']) if 'display_resists' in data: data['display_resists'] = Resistances.from_dict(data['display_resists'], smart=False) else: data['display_resists'] = Resistances() if 'source' in data: del data['source'] return cls(homebrew=True, source=source, **data)
async def attack_import(self, ctx, *, data: str): """ Imports an attack from JSON or YAML exported from the Avrae Dashboard. """ # strip any code blocks if data.startswith(('```\n', '```json\n', '```yaml\n', '```yml\n', '```py\n')) and data.endswith('```'): data = '\n'.join(data.split('\n')[1:]).rstrip('`\n') character: Character = await Character.from_ctx(ctx) try: attack_json = yaml.safe_load(data) except yaml.YAMLError: return await ctx.send("This is not a valid attack.") if not isinstance(attack_json, list): attack_json = [attack_json] try: attacks = AttackList.from_dict(attack_json) except Exception: return await ctx.send("This is not a valid attack.") conflicts = [ a for a in character.overrides.attacks if a.name.lower() in [new.name.lower() for new in attacks] ] if conflicts: if await confirm( ctx, f"This will overwrite {len(conflicts)} attacks with the same name " f"({', '.join(c.name for c in conflicts)}). Continue? (Reply with yes/no)" ): for conflict in conflicts: character.overrides.attacks.remove(conflict) else: return await ctx.send("Okay, aborting.") character.overrides.attacks.extend(attacks) await character.commit(ctx) out = f"Imported {len(attacks)} attacks:\n{attacks.build_str(character)}" await ctx.send(out)
def attacks(self): if 'attacks' not in self._cache: # attacks granted by effects are cached so that the same object is referenced in initTracker (#950) self._cache['attacks'] = self._attacks + AttackList.from_dict(self.active_effects('attack')) return self._cache['attacks']
def _monster_factory(data, bestiary_name): ability_scores = BaseStats(data['stats']['proficiencyBonus'] or 0, data['stats']['abilityScores']['strength'] or 10, data['stats']['abilityScores']['dexterity'] or 10, data['stats']['abilityScores']['constitution'] or 10, data['stats']['abilityScores']['intelligence'] or 10, data['stats']['abilityScores']['wisdom'] or 10, data['stats']['abilityScores']['charisma'] or 10) cr = {0.125: '1/8', 0.25: '1/4', 0.5: '1/2'}.get(data['stats']['challengeRating'], str(data['stats']['challengeRating'])) num_hit_die = data['stats']['numHitDie'] hit_die_size = data['stats']['hitDieSize'] con_by_level = num_hit_die * ability_scores.get_mod('con') hp = floor(((hit_die_size + 1) / 2) * num_hit_die) + con_by_level hitdice = f"{num_hit_die}d{hit_die_size} + {con_by_level}" proficiency = data['stats']['proficiencyBonus'] if proficiency is None: raise ExternalImportError(f"Monster's proficiency bonus is nonexistent ({data['name']}).") skills = Skills.default(ability_scores) skill_updates = {} for skill in data['stats']['skills']: name = spaced_to_camel(skill['name']) if skill['proficient']: mod = skills[name].value + proficiency else: mod = skill.get('value') if mod is not None: skill_updates[name] = mod skills.update(skill_updates) saves = Saves.default(ability_scores) save_updates = {} for save in data['stats']['savingThrows']: name = save['ability'].lower() + 'Save' if save['proficient']: mod = saves.get(name).value + proficiency else: mod = save.get('value') if mod is not None: save_updates[name] = mod saves.update(save_updates) attacks = [] traits, atks = parse_critterdb_traits(data, 'additionalAbilities') attacks.extend(atks) actions, atks = parse_critterdb_traits(data, 'actions') attacks.extend(atks) reactions, atks = parse_critterdb_traits(data, 'reactions') attacks.extend(atks) legactions, atks = parse_critterdb_traits(data, 'legendaryActions') attacks.extend(atks) attacks = AttackList.from_dict(attacks) spellcasting = parse_critterdb_spellcasting(traits, ability_scores) resistances = Resistances.from_dict(dict(vuln=data['stats']['damageVulnerabilities'], resist=data['stats']['damageResistances'], immune=data['stats']['damageImmunities'])) return Monster(name=data['name'], size=data['stats']['size'], race=data['stats']['race'], alignment=data['stats']['alignment'], ac=data['stats']['armorClass'], armortype=data['stats']['armorType'], hp=hp, hitdice=hitdice, speed=data['stats']['speed'], ability_scores=ability_scores, saves=saves, skills=skills, senses=', '.join(data['stats']['senses']), resistances=resistances, display_resists=resistances, condition_immune=data['stats']['conditionImmunities'], languages=data['stats']['languages'], cr=cr, xp=data['stats']['experiencePoints'], traits=traits, actions=actions, reactions=reactions, legactions=legactions, la_per_round=data['stats']['legendaryActionsPerRound'], attacks=attacks, proper=data['flavor']['nameIsProper'], image_url=data['flavor']['imageUrl'], spellcasting=spellcasting, homebrew=True, source=bestiary_name)
def from_data(cls, data): # print(f"Parsing {data['name']}") _type = parse_type(data['type']) alignment = parse_alignment(data['alignment']) speed = parse_speed(data['speed']) ac = data['ac']['ac'] armortype = data['ac'].get('armortype') or None if not 'special' in data['hp']: hp = data['hp']['average'] hitdice = data['hp']['formula'] else: hp = 0 hitdice = data['hp']['special'] scores = BaseStats(0, data['str'] or 10, data['dex'] or 10, data['con'] or 10, data['int'] or 10, data['wis'] or 10, data['cha'] or 10) if isinstance(data['cr'], dict): cr = data['cr']['cr'] else: cr = data['cr'] # resistances vuln = parse_resists(data['vulnerable'], notated=False) if 'vulnerable' in data else None resist = parse_resists(data['resist'], notated=False) if 'resist' in data else None immune = parse_resists(data['immune'], notated=False) if 'immune' in data else None display_resists = Resistances(*[ parse_resists(data.get(r)) for r in ('resist', 'immune', 'vulnerable') ]) condition_immune = data.get('conditionImmune', []) if 'conditionImmune' in data else None languages = data.get('languages', '').split(', ') if 'languages' in data else None traits = [Trait(t['name'], t['text']) for t in data.get('trait', [])] actions = [Trait(t['name'], t['text']) for t in data.get('action', [])] legactions = [ Trait(t['name'], t['text']) for t in data.get('legendary', []) ] reactions = [ Trait(t['name'], t['text']) for t in data.get('reaction', []) ] skills = Skills.default(scores) skills.update(data['skill']) saves = Saves.default(scores) saves.update(data['save']) scores.prof_bonus = _calc_prof(scores, saves, skills) source = data['source'] proper = bool(data.get('isNamedCreature') or data.get('isNPC')) attacks = AttackList.from_dict(data.get('attacks', [])) spellcasting = data.get('spellcasting', {}) spells = [SpellbookSpell(s) for s in spellcasting.get('spells', [])] spellbook = Spellbook({}, {}, spells, spellcasting.get('dc'), spellcasting.get('attackBonus'), spellcasting.get('casterLevel', 1)) return cls(data['name'], parsesize(data['size']), _type, alignment, ac, armortype, hp, hitdice, speed, scores, cr, xp_by_cr(cr), data['passive'], data.get('senses', ''), vuln, resist, immune, condition_immune, saves, skills, languages, traits, actions, reactions, legactions, 3, data.get('srd', False), source, attacks, spellcasting=spellbook, page=data.get('page'), proper=proper, display_resists=display_resists)