Beispiel #1
0
 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'])
Beispiel #2
0
    def get_resistances(self):
        out = {'resist': [], 'immune': [], 'vuln': []}
        if not self.additional:  # requires 2.0
            return Resistances.from_dict(out)

        for rownum in range(69, 80):
            for resist_type, col in RESIST_COLS:
                try:
                    dtype = self.additional.value(f"{col}{rownum}")
                except IndexError:
                    dtype = None
                if dtype:
                    out[resist_type].append(dtype.lower())

        return Resistances.from_dict(out)
Beispiel #3
0
    def get_resistances(self):
        resist = {
            'resist': set(),
            'immune': set(),
            'vuln': set()
        }
        for mod in self.modifiers():
            if mod['type'] == 'resistance':
                resist['resist'].add(mod['subType'].lower())
            elif mod['type'] == 'immunity':
                resist['immune'].add(mod['subType'].lower())
            elif mod['type'] == 'vulnerability':
                resist['vuln'].add(mod['subType'].lower())

        for override in self.character_data['customDefenseAdjustments']:
            if not override['type'] == 2:
                continue
            if override['id'] not in RESIST_OVERRIDE_MAP:
                continue

            dtype, rtype = RESIST_OVERRIDE_MAP[override['id']]
            resist[RESIST_TYPE_MAP[rtype]].add(dtype.lower())

        resist = {k: list(v) for k, v in resist.items()}
        return Resistances.from_dict(resist)
Beispiel #4
0
 def resistances(self):
     out = self._resistances.copy()
     out.update(Resistances.from_dict(
         {k: self.active_effects(k)
          for k in RESIST_TYPES}),
                overwrite=False)
     return out
Beispiel #5
0
 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)
Beispiel #6
0
 def get_resistances(self) -> Resistances:
     if self.character_data is None: raise Exception('You must call get_character() first.')
     out = {'resist': [], 'immune': [], 'vuln': []}
     for dmgType in DAMAGE_TYPES:
         mult = self.calculate_stat(f"{dmgType}Multiplier", 1)
         if mult <= 0:
             out['immune'].append(dmgType)
         elif mult < 1:
             out['resist'].append(dmgType)
         elif mult > 1:
             out['vuln'].append(dmgType)
     return Resistances.from_dict(out)
Beispiel #7
0
    def __init__(
            self,
            name: str,
            size: str,
            race: str,
            alignment: str,
            ac: int,
            armortype: str,
            hp: int,
            hitdice: str,
            speed: str,
            ability_scores: BaseStats,
            saves: Saves,
            skills: Skills,
            senses: str,
            display_resists: Resistances,
            condition_immune: list,
            languages: list,
            cr: str,
            xp: int,
            # optional
            traits: list = None,
            actions: list = None,
            reactions: list = None,
            legactions: list = None,
            la_per_round=3,
            passiveperc: int = None,
            # augmented
            resistances: Resistances = None,
            attacks: AttackList = None,
            proper: bool = False,
            image_url: str = None,
            spellcasting=None,
            # sourcing
            homebrew=False,
            **kwargs):
        if traits is None:
            traits = []
        if actions is None:
            actions = []
        if reactions is None:
            reactions = []
        if legactions is None:
            legactions = []
        if attacks is None:
            attacks = AttackList()
        if spellcasting is None:
            spellcasting = MonsterSpellbook()
        if passiveperc is None:
            passiveperc = 10 + skills.perception.value

        # old/new resist handling
        if resistances is None:
            # fall back to old-style resistances (deprecated)
            vuln = kwargs.get('vuln', [])
            resist = kwargs.get('resist', [])
            immune = kwargs.get('immune', [])
            resistances = Resistances.from_dict(
                dict(vuln=vuln, resist=resist, immune=immune))

        try:
            levels = Levels({"Monster": spellcasting.caster_level or int(cr)})
        except ValueError:
            levels = None

        Sourced.__init__(self,
                         'monster',
                         homebrew,
                         source=kwargs['source'],
                         entity_id=kwargs.get('entity_id'),
                         page=kwargs.get('page'),
                         url=kwargs.get('url'),
                         is_free=kwargs.get('is_free'))
        StatBlock.__init__(self,
                           name=name,
                           stats=ability_scores,
                           attacks=attacks,
                           skills=skills,
                           saves=saves,
                           resistances=resistances,
                           spellbook=spellcasting,
                           ac=ac,
                           max_hp=hp,
                           levels=levels)
        self.size = size
        self.race = race
        self.alignment = alignment
        self.armortype = armortype
        self.hitdice = hitdice
        self.speed = speed
        self.cr = cr
        self.xp = xp
        self.passive = passiveperc
        self.senses = senses
        self.condition_immune = condition_immune
        self.languages = languages
        self.traits = traits
        self.actions = actions
        self.reactions = reactions
        self.legactions = legactions
        self.la_per_round = la_per_round
        self.proper = proper
        self.image_url = image_url
        # resistances including notes, e.g. "Bludgeoning from nonmagical weapons"
        self._displayed_resistances = display_resists or resistances
Beispiel #8
0
 def _get_resistances(self):
     return Resistances.from_dict(self.character_data['resistances'])
Beispiel #9
0
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)