Beispiel #1
0
    def _get_skills(self) -> Skills:
        out = {}

        # main skills
        for skill_id, skill in self.character_data['skills'].items():
            prof_type = {1: 0, 2: 0.5, 3: 1, 4: 2}.get(skill['prof'], 0)
            adv_type = derive_adv(skill['adv'], skill['dis'])
            out[SKILL_MAP[skill_id]] = Skill(skill['modifier'], prof_type,
                                             skill['bonus'], adv_type)

        # initiative
        out['initiative'] = Skill(
            self.character_data['initiative']['modifier'],
            adv=self.character_data['initiative']['adv'] or None)

        # ability skills (base strength, dex, etc checks)
        for stat_key, skill in zip(constants.STAT_ABBREVIATIONS,
                                   constants.STAT_NAMES):
            stat_obj = self.character_data['stats'][stat_key]
            prof_type = {
                1: 0,
                2: 0.5,
                3: 1,
                4: 2
            }.get(stat_obj['modifierProficiency'], 0)
            out[skill] = Skill(stat_obj['modifier'], prof_type)

        return Skills(out)
Beispiel #2
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 #3
0
    def _get_skills(self) -> Skills:
        out = {}

        def derive_adv(skl):
            advs = set()
            for adv in skl['adv']:
                if not adv['restriction']:
                    advs.add(True)
            for adv in skl['dis']:
                if not adv['restriction']:
                    advs.add(False)

            if len(advs) == 1:
                return advs.pop()
            return None

        for skill_id, skill in self.character_data['skills'].items():
            prof_type = {1: 0, 2: 0.5, 3: 1, 4: 2}.get(skill['prof'], 0)
            adv_type = derive_adv(skill)
            out[SKILL_MAP[skill_id]] = Skill(skill['modifier'], prof_type,
                                             skill['bonus'], adv_type)

        out['initiative'] = Skill(
            self.character_data['initiative']['modifier'],
            adv=self.character_data['initiative']['adv'] or None)

        for stat_key, skill in zip(constants.STAT_ABBREVIATIONS,
                                   constants.STAT_NAMES):
            out[skill] = Skill(
                self.character_data['stats'][stat_key]['modifier'])

        return Skills(out)
Beispiel #4
0
 def new(cls, name: str, controller_id: str, init: int, init_skill: Skill, max_hp: int, ac: int, private: bool,
         resists: Resistances, ctx, combat):
     skills = Skills.default()
     skills.update({"initiative": init_skill})
     levels = Levels({"Monster": 0})
     id = create_combatant_id()
     return cls(ctx, combat, id, name, controller_id, private, init,
                levels=levels, resistances=resists, skills=skills, max_hp=max_hp, ac=ac)
Beispiel #5
0
    def __init__(self,
                 name: str,
                 stats: BaseStats = None,
                 levels: Levels = None,
                 attacks: AttackList = None,
                 skills: Skills = None,
                 saves: Saves = None,
                 resistances: Resistances = None,
                 spellbook: Spellbook = None,
                 ac: int = None,
                 max_hp: int = None,
                 hp: int = None,
                 temp_hp: int = 0,
                 creature_type: str = None):
        if stats is None:
            stats = BaseStats.default()
        if levels is None:
            levels = Levels()
        if attacks is None:
            attacks = AttackList()
        if skills is None:
            skills = Skills.default(stats)
        if saves is None:
            saves = Saves.default(stats)
        if resistances is None:
            resistances = Resistances()
        if spellbook is None:
            spellbook = Spellbook()
        if hp is None:
            hp = max_hp

        # ===== static =====
        # all actors have a name
        self._name = name
        # ability scores
        self._stats = stats
        # at least a total level
        self._levels = levels
        # attacks - list of automation actions
        self._attacks = attacks
        # skill profs/saving throws
        self._skills = skills
        self._saves = saves
        # defensive resistances
        self._resistances = resistances
        # assigned by combatant type
        self._creature_type = creature_type

        # ===== dynamic =====
        # hp/ac
        self._ac = ac
        self._max_hp = max_hp
        self._hp = hp
        self._temp_hp = temp_hp

        # spellbook
        self._spellbook = spellbook
Beispiel #6
0
    def get_skills_and_saves(self) -> (Skills, Saves):
        if self.character_data is None: raise Exception('You must call get_character() first.')
        character = self.character_data
        stats = self.get_stats()

        NAME_SET = set(SKILL_NAMES + SAVE_NAMES)
        ADV_INT_MAP = {-1: False, 0: None, 1: True}
        profs = {}
        effects = collections.defaultdict(lambda: 0)

        # calculate profs
        for prof in character.get('proficiencies', []):
            if prof.get('enabled', False) and not prof.get('removed', False):
                profs[prof.get('name')] = prof.get('value') \
                    if prof.get('value') > profs.get(prof.get('name', 'None'), 0) \
                    else profs[prof.get('name')]

        # and effects
        for effect in self.character_data.get('effects', []):
            if effect.get('stat') in NAME_SET \
                    and effect.get('enabled', True) \
                    and not effect.get('removed', False):
                statname = effect.get('stat')
                if effect.get('operation') == 'disadvantage':
                    effects[statname] = max(-1, effects[statname] - 1)
                if effect.get('operation') == 'advantage':
                    effects[statname] = min(1, effects[statname] + 1)

        # assign skills
        skills = {}
        for skill in SKILL_NAMES:
            prof_mult = profs.get(skill, 0)
            base_val = floor(stats.get_mod(SKILL_MAP[skill]) + stats.prof_bonus * prof_mult)
            adv = ADV_INT_MAP.get(effects.get(skill))
            if skill not in STAT_NAMES:
                value = int(self.calculate_stat(skill, base=base_val))
            else:
                value = base_val
            skills[skill] = Skill(
                value,
                prof=prof_mult,
                adv=adv
            )

        # and saves
        saves = {}
        for save in SAVE_NAMES:
            prof_mult = profs.get(save, 0)
            base_val = floor(stats.get_mod(SKILL_MAP[save]) + stats.prof_bonus * prof_mult)
            adv = ADV_INT_MAP.get(effects.get(save))
            saves[save] = Skill(
                int(self.calculate_stat(save, base=base_val)),
                prof=prof_mult,
                adv=adv
            )

        return Skills(skills), Saves(saves)
Beispiel #7
0
 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)
Beispiel #8
0
    def get_skills_and_saves(self):
        if self.character_data is None:
            raise Exception('You must call get_character() first.')
        character = self.character_data

        skills = {}
        saves = {}
        is_joat = self.version == 2 and bool(character.cell("AR45").value)
        for cell, skill, advcell in SKILL_CELL_MAP:
            if isinstance(cell, int):
                advcell = f"F{cell}"
                profcell = f"H{cell}"
                cell = f"I{cell}"
            else:
                profcell = None
            try:
                value = int(character.cell(cell).value)
            except (TypeError, ValueError):
                raise MissingAttribute(skill)

            adv = None
            if self.version == 2 and advcell:
                advtype = character.cell(advcell).value
                if advtype in {'a', 'adv', 'advantage'}:
                    adv = True
                elif advtype in {'d', 'dis', 'disadvantage'}:
                    adv = False

            prof = 0
            if "Save" not in skill and is_joat:
                prof = 0.5
            if profcell:
                proftype = character.cell(profcell).value_unformatted
                if proftype == 'e':
                    prof = 2
                elif proftype and proftype != '0':
                    prof = 1

            skl_obj = Skill(value, prof, adv=adv)
            if "Save" in skill:
                saves[skill] = skl_obj
            else:
                skills[skill] = skl_obj

        skills = Skills(skills)
        saves = Saves(saves)
        return skills, saves
Beispiel #9
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 #10
0
def migrate_monster(old_monster):
    def spaced_to_camel(spaced):
        return re.sub(r"\s+(\w)", lambda m: m.group(1).upper(), spaced.lower())

    for old_key in ('raw_saves', 'raw_skills'):
        if old_key in old_monster:
            del old_monster[old_key]

    if 'spellcasting' in old_monster and old_monster['spellcasting']:
        old_spellcasting = old_monster.pop('spellcasting')
        old_monster['spellbook'] = Spellbook(
            {}, {}, [SpellbookSpell(s) for s in old_spellcasting['spells']],
            old_spellcasting['dc'], old_spellcasting['attackBonus'],
            old_spellcasting['casterLevel']).to_dict()
    else:
        old_monster['spellbook'] = Spellbook({}, {}, []).to_dict()

    base_stats = BaseStats(0, old_monster.pop('strength'),
                           old_monster.pop('dexterity'),
                           old_monster.pop('constitution'),
                           old_monster.pop('intelligence'),
                           old_monster.pop('wisdom'),
                           old_monster.pop('charisma'))
    old_monster['ability_scores'] = base_stats.to_dict()

    old_saves = old_monster.pop('saves')
    saves = Saves.default(base_stats)
    save_updates = {}
    for save, value in old_saves.items():
        if value != saves[save]:
            save_updates[save] = value
    saves.update(save_updates)
    old_monster['saves'] = saves.to_dict()

    old_skills = old_monster.pop('skills')
    skills = Skills.default(base_stats)
    skill_updates = {}
    for skill, value in old_skills.items():
        name = spaced_to_camel(skill)
        if value != skills[name]:
            skill_updates[name] = value
    skills.update(skill_updates)
    old_monster['skills'] = skills.to_dict()

    new_monster = Monster.from_bestiary(old_monster)
    return new_monster
Beispiel #11
0
 def new(cls, name: str, controller_id: str, init: int, init_skill: Skill, max_hp: int, ac: int, private: bool,
         resists: Resistances, ctx, combat):
     skills = Skills.default()
     skills.update({"initiative": init_skill})
     return cls(ctx, combat, name, controller_id, private, init, resistances=resists, skills=skills,
                max_hp=max_hp, ac=ac)
Beispiel #12
0
    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)
Beispiel #13
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)
Beispiel #14
0
    def get_skills_and_saves(self):
        """Returns a dict of all the character's skills."""
        if self.character_data is None:
            raise Exception('You must call get_character() first.')
        character = self.character_data
        stats = self.get_stats()
        profBonus = stats.prof_bonus

        profs = dict()
        bonuses = dict()

        for modtype in character['modifiers'].values(
        ):  # calculate proficiencies in all skills
            for mod in modtype:
                mod['subType'] = mod['subType'].replace(
                    "-saving-throws", "Save")
                if mod['type'] == 'half-proficiency':
                    profs[mod['subType']] = max(profs.get(mod['subType'], 0),
                                                0.5)
                elif mod['type'] == 'proficiency':
                    profs[mod['subType']] = max(profs.get(mod['subType'], 0),
                                                1)
                elif mod['type'] == 'expertise':
                    profs[mod['subType']] = 2
                elif mod['type'] == 'bonus':
                    if not mod['isGranted']:
                        continue
                    if mod['statId'] is not None:
                        bonuses[mod['subType']] = bonuses.get(
                            mod['subType'], 0) + self.stat_from_id(
                                mod['statId'])
                    else:
                        bonuses[mod['subType']] = bonuses.get(
                            mod['subType'], 0) + (mod['value'] or 0)

        profs['animalHandling'] = profs.get('animal-handling', 0)
        profs['sleightOfHand'] = profs.get('sleight-of-hand', 0)

        skills = {}
        for skill in SKILL_NAMES:  # add proficiency and bonuses to skills
            relevantprof = profs.get(skill, 0)
            relevantbonus = bonuses.get(skill, 0)
            if 'ability-checks' in profs:
                relevantprof = max(relevantprof, profs['ability-checks'])
            if 'ability-checks' in bonuses:
                relevantbonus += bonuses['ability-checks']
            skills[skill] = Skill(
                floor(
                    stats.get_mod(SKILL_MAP[skill]) +
                    (profBonus * relevantprof) + relevantbonus), relevantprof,
                relevantbonus)

        saves = {}
        for save in SAVE_NAMES:  # add proficiency and bonuses to skills
            relevantprof = profs.get(save, 0)
            relevantbonus = bonuses.get(save, 0)
            if 'saving-throws' in profs:
                relevantprof = max(relevantprof, profs['saving-throws'])
            if 'saving-throws' in bonuses:
                relevantbonus += bonuses['saving-throws']
            saves[save] = Skill(
                floor(
                    stats.get_mod(SKILL_MAP[save]) +
                    (profBonus * relevantprof) + relevantbonus), relevantprof,
                relevantbonus)

        ignored_ids = set()
        for charval in self.character_data['characterValues']:
            if charval['valueId'] in HOUSERULE_SKILL_MAP and charval[
                    'valueId'] not in ignored_ids:
                skill_name = HOUSERULE_SKILL_MAP[charval['valueId']]
                if charval['typeId'] == 23:  # override
                    skills[skill_name] = Skill(charval['value'])
                    ignored_ids.add(
                        charval['valueId']
                    )  # this must be the final value so we stop looking
                elif charval['typeId'] in {
                        24, 25
                }:  # PROBABLY skill magic/misc bonus
                    skills[skill_name].value += charval['value']
                    skills[skill_name].bonus += charval['value']
                elif charval['typeId'] == 26:  # proficiency stuff
                    relevantprof = profs.get(skill_name, 0)
                    skills[skill_name].value -= relevantprof * profBonus
                    if charval[
                            'value'] == 0:  # no prof, don't need to do anything
                        skills[skill_name].prof = 0
                    elif charval['value'] == 1:  # half prof, round down
                        skills[skill_name].value += profBonus // 2
                        skills[skill_name].prof = 0.5
                    elif charval['value'] == 2:  # half, round up
                        skills[skill_name].value += ceil(profBonus / 2)
                        skills[skill_name].prof = 0.5
                    elif charval['value'] == 3:  # full
                        skills[skill_name].value += profBonus
                        skills[skill_name].prof = 1
                    elif charval['value'] == 4:  # double
                        skills[skill_name].value += profBonus * 2
                        skills[skill_name].prof = 2

        skills = Skills(skills)
        saves = Saves(saves)

        return skills, saves
Beispiel #15
0
    def get_skills_and_saves(self):
        """Returns a dict of all the character's skills."""
        if self.character_data is None:
            raise Exception('You must call get_character() first.')
        stats = self.get_stats()
        profBonus = stats.prof_bonus

        profs = dict()
        bonuses = dict()
        advantages = collections.defaultdict(lambda: [])

        for mod in self.modifiers():
            mod['subType'] = mod['subType'].replace("-saving-throws", "Save")
            if mod['type'] == 'half-proficiency':
                profs[mod['subType']] = max(profs.get(mod['subType'], 0), 0.5)
            elif mod['type'] == 'proficiency':
                profs[mod['subType']] = max(profs.get(mod['subType'], 0), 1)
            elif mod['type'] == 'expertise':
                profs[mod['subType']] = 2
            elif mod['type'] == 'bonus':
                if not mod['isGranted']:
                    continue
                if mod['statId'] is not None:
                    bonuses[mod['subType']] = bonuses.get(
                        mod['subType'], 0) + self.stat_from_id(mod['statId'])
                else:
                    bonuses[mod['subType']] = bonuses.get(
                        mod['subType'], 0) + (mod['value'] or 0)
            elif mod['type'] == 'advantage' and not mod[
                    'restriction']:  # unconditional adv
                advantages[mod['subType']].append(True)
            elif mod['type'] == 'disadvantage' and not mod[
                    'restriction']:  # unconditional dis
                advantages[mod['subType']].append(False)

        profs['animalHandling'] = profs.get('animal-handling', 0)
        profs['sleightOfHand'] = profs.get('sleight-of-hand', 0)
        advantages['animalHandling'] = advantages['animal-handling']
        advantages['sleightOfHand'] = advantages['sleight-of-hand']

        def _simplify_adv(adv_list):
            adv_set = set(adv_list)
            if len(adv_set) == 1:
                return adv_set.pop()
            return None

        skills = {}
        for skill in SKILL_NAMES:  # add proficiency and bonuses to skills
            relevantprof = profs.get(skill, 0)
            relevantbonus = bonuses.get(skill, 0)
            relevantadv = _simplify_adv(advantages[skill])
            if 'ability-checks' in profs and skill != 'initiative':
                relevantprof = max(relevantprof, profs['ability-checks'])
            if 'ability-checks' in bonuses and skill != 'initiative':
                relevantbonus += bonuses['ability-checks']
            skills[skill] = Skill(floor(
                stats.get_mod(SKILL_MAP[skill]) + (profBonus * relevantprof) +
                relevantbonus),
                                  relevantprof,
                                  relevantbonus,
                                  adv=relevantadv)

        # saves
        saves = {}
        for save in SAVE_NAMES:  # add proficiency and bonuses to skills
            relevantprof = profs.get(save, 0)
            relevantbonus = bonuses.get(save, 0)
            relevantadv = _simplify_adv(advantages[save])
            if 'saving-throws' in profs:
                relevantprof = max(relevantprof, profs['saving-throws'])
            if 'saving-throws' in bonuses:
                relevantbonus += bonuses['saving-throws']
            saves[save] = Skill(floor(
                stats.get_mod(SKILL_MAP[save]) + (profBonus * relevantprof) +
                relevantbonus),
                                relevantprof,
                                relevantbonus,
                                adv=relevantadv)

        # values
        ignored_ids = set()
        for charval in self.character_data['characterValues']:
            if charval['value'] is None:
                continue

            if charval['typeId'] == 39:  # misc saving throw bonus
                save_id = SAVE_NAMES[charval['valueId'] - 1]
                save_bonus = charval['value']
                saves[save_id].value += save_bonus
                saves[save_id].bonus += save_bonus
            elif charval['valueId'] in HOUSERULE_SKILL_MAP and charval[
                    'valueId'] not in ignored_ids:
                skill_name = HOUSERULE_SKILL_MAP[charval['valueId']]
                if charval['typeId'] == 23:  # override
                    skills[skill_name] = Skill(charval['value'])
                    ignored_ids.add(
                        charval['valueId']
                    )  # this must be the final value so we stop looking
                elif charval['typeId'] in {
                        24, 25
                }:  # PROBABLY skill magic/misc bonus
                    skills[skill_name].value += charval['value']
                    skills[skill_name].bonus += charval['value']
                elif charval['typeId'] == 26:  # proficiency stuff
                    relevantprof = profs.get(skill_name, 0)
                    skills[skill_name].value -= relevantprof * profBonus
                    if charval[
                            'value'] == 0:  # no prof, don't need to do anything
                        skills[skill_name].prof = 0
                    elif charval['value'] == 1:  # half prof, round down
                        skills[skill_name].value += profBonus // 2
                        skills[skill_name].prof = 0.5
                    elif charval['value'] == 2:  # half, round up
                        skills[skill_name].value += ceil(profBonus / 2)
                        skills[skill_name].prof = 0.5
                    elif charval['value'] == 3:  # full
                        skills[skill_name].value += profBonus
                        skills[skill_name].prof = 1
                    elif charval['value'] == 4:  # double
                        skills[skill_name].value += profBonus * 2
                        skills[skill_name].prof = 2

        skills = Skills(skills)
        saves = Saves(saves)

        return skills, saves
Beispiel #16
0
    def get_skills_and_saves(self):
        if self.character_data is None:
            raise Exception('You must call get_character() first.')
        character = self.character_data
        skills = {}
        saves = {}
        is_joat = False
        all_check_bonus = 0

        if self.version == (2, 0):
            is_joat = bool(character.value("AR45"))
            all_check_bonus = int(character.value("AQ26") or 0)
        elif self.version == (2, 1):
            is_joat = bool(character.value("AQ59"))
            all_check_bonus = int(character.value("AR58"))

        joat_bonus = int(is_joat and self.get_stats().prof_bonus // 2)

        # calculate str, dex, con, etc checks
        for cell, skill, advcell in BASE_ABILITY_CHECKS:
            try:
                # add bonuses manually since the cell does not include them
                value = int(
                    character.value(cell)) + all_check_bonus + joat_bonus
            except (TypeError, ValueError):
                raise MissingAttribute(skill)

            prof = 0
            if is_joat:
                prof = 0.5

            skl_obj = Skill(value, prof)
            skills[skill] = skl_obj

        # read the value of the rest of the skills
        for cell, skill, advcell in SKILL_CELL_MAP:
            if isinstance(cell, int):
                advcell = f"F{cell}"
                profcell = f"H{cell}"
                cell = f"I{cell}"
            else:
                profcell = None
            try:
                value = int(character.value(cell))
            except (TypeError, ValueError):
                raise MissingAttribute(skill)

            adv = None
            if self.version >= (2, 0) and advcell:
                advtype = character.unformatted_value(advcell)
                if advtype in {'a', 'adv', 'advantage'}:
                    adv = True
                elif advtype in {'d', 'dis', 'disadvantage'}:
                    adv = False

            prof = 0
            if "Save" not in skill and is_joat:
                prof = 0.5
            if profcell:
                proftype = character.unformatted_value(profcell)
                if proftype == 'e':
                    prof = 2
                elif proftype and proftype != '0':
                    prof = 1

            skl_obj = Skill(value, prof, adv=adv)
            if "Save" in skill:
                saves[skill] = skl_obj
            else:
                skills[skill] = skl_obj

        skills = Skills(skills)
        saves = Saves(saves)
        return skills, saves
Beispiel #17
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,
                 cr: str,
                 xp: int,
                 passiveperc: int = None,
                 senses: str = '',
                 vuln: list = None,
                 resist: list = None,
                 immune: list = None,
                 condition_immune: list = None,
                 saves: Saves = None,
                 skills: Skills = None,
                 languages: list = None,
                 traits: list = None,
                 actions: list = None,
                 reactions: list = None,
                 legactions: list = None,
                 la_per_round=3,
                 srd=True,
                 source='homebrew',
                 attacks: AttackList = None,
                 proper: bool = False,
                 image_url: str = None,
                 spellcasting=None,
                 page=None,
                 display_resists: Resistances = None,
                 **_):
        if vuln is None:
            vuln = []
        if resist is None:
            resist = []
        if immune is None:
            immune = []
        if condition_immune is None:
            condition_immune = []
        if saves is None:
            saves = Saves.default(ability_scores)
        if skills is None:
            skills = Skills.default(ability_scores)
        if languages is None:
            languages = []
        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 = Spellbook({}, {}, [])
        if passiveperc is None:
            passiveperc = 10 + skills.perception.value

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

        resistances = Resistances(vuln=vuln, resist=resist, immune=immune)

        super(Monster, self).__init__(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.srd = srd
        self.source = source
        self.proper = proper
        self.image_url = image_url
        self.page = page  # this should really be by source, but oh well
        # resistances including notes, e.g. "Bludgeoning from nonmagical weapons"
        self._displayed_resistances = display_resists or resistances