Exemplo n.º 1
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)
Exemplo n.º 2
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)
Exemplo n.º 3
0
def test_alias_skill_comparison():
    s1 = AliasSkill(Skill(1))
    s2 = AliasSkill(Skill(2))

    assert s1 == s1
    assert s1 != s2
    assert s2 > s1
    assert s2 >= s1
    assert not s2 < s1
    assert not s2 <= s1
    assert s1 >= s1
    assert s1 <= s1

    assert s1 == 1
    assert s1 != 2
    assert s2 > 1
    assert s2 >= 1
    assert not s2 < 1
    assert not s2 <= 1
    assert s1 >= 1
    assert s1 <= 1

    assert 1 == s1
    assert 1 != s2
    assert 2 > s1
    assert 2 >= s1
    assert not 2 < s1
    assert not 2 <= s1
    assert 1 >= s1
    assert 1 <= s1
Exemplo n.º 4
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)
Exemplo n.º 5
0
    def _get_saves(self) -> Saves:
        out = {}
        for stat_key, save_key in zip(constants.STAT_ABBREVIATIONS, constants.SAVE_NAMES):
            out[save_key] = Skill(self.character_data['stats'][stat_key]['save'],
                                  prof=1 if self.character_data['stats'][stat_key]['saveProficiency'] else 0)

        return Saves(out)
Exemplo n.º 6
0
def run_save(save_key, caster, args, embed):
    """
    Runs a caster's saving throw, building on an existing embed and handling most arguments.

    Also handles save bonuses from ieffects if caster is a combatant.

    :type save_key: str
    :type caster: cogs5e.models.sheet.statblock.StatBlock
    :type args: utils.argparser.ParsedArguments
    :type embed: discord.Embed
    :return: The total of each save.
    :rtype: SaveResult
    """
    if save_key.startswith('death'):
        save = Skill(0)
        stat_name = stat = 'Death'
        save_name = 'Death Save'
    else:
        try:
            save = caster.saves.get(save_key)
            stat = save_key[:3]
            stat_name = verbose_stat(stat).title()
            save_name = f"{stat_name} Save"
        except ValueError:
            raise InvalidArgument('That\'s not a valid save.')

    # -title
    if args.last('title'):
        embed.title = args.last('title', '') \
            .replace('[name]', caster.get_title_name()) \
            .replace('[sname]', save_name)
    elif args.last('h'):
        embed.title = f"An unknown creature makes {a_or_an(save_name)}!"
    else:
        embed.title = f'{caster.get_title_name()} makes {a_or_an(save_name)}!'

    # ieffect handling
    if isinstance(caster, init.Combatant):
        # -sb
        args['b'] = args.get('b') + caster.active_effects('sb')
        # -sadv/sdis
        sadv_effects = caster.active_effects('sadv')
        sdis_effects = caster.active_effects('sdis')
        if 'all' in sadv_effects or stat in sadv_effects:
            args[
                'adv'] = True  # Because adv() only checks last() just forcibly add them
        if 'all' in sdis_effects or stat in sdis_effects:
            args['dis'] = True

    result = _run_common(save, args, embed, rr_format="Save {}")
    return SaveResult(rolls=result.rolls,
                      skill=save,
                      skill_name=stat_name,
                      skill_roll_result=result)
Exemplo n.º 7
0
    def _get_saves(self) -> Saves:
        out = {}
        for stat_key, save_key in zip(constants.STAT_ABBREVIATIONS,
                                      constants.SAVE_NAMES):
            stat_data = self.character_data['stats'][stat_key]
            adv_type = derive_adv(stat_data['saveAdv'], stat_data['saveDis'])
            out[save_key] = Skill(
                stat_data['save'],
                prof=1 if stat_data['saveProficiency'] else 0,
                adv=adv_type)

        return Saves(out)
Exemplo n.º 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
Exemplo n.º 9
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
Exemplo n.º 10
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
Exemplo n.º 11
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
Exemplo n.º 12
0
 def init_skill(self):
     # groups: if all combatants are the same type, return the first one's skill, otherwise +0
     if all(isinstance(c, MonsterCombatant) for c in self._combatants) \
             and len(set(c.monster_name for c in self._combatants)) == 1:
         return self._combatants[0].init_skill
     return Skill(0)
Exemplo n.º 13
0
def migrate(character):
    name = character['stats']['name']
    sheet_type = character.get('type')
    import_version = character.get('version')
    print(f"Migrating {name} - {sheet_type} v{import_version}")

    owner = character['owner']
    upstream = character['upstream']
    active = character['active']

    description = character['stats'].get('description', "No description")
    image = character['stats']['image']

    stats = {
        "prof_bonus": character['stats']['proficiencyBonus'],
        "strength": character['stats']['strength'],
        "dexterity": character['stats']['dexterity'],
        "constitution": character['stats']['constitution'],
        "intelligence": character['stats']['intelligence'],
        "wisdom": character['stats']['wisdom'],
        "charisma": character['stats']['charisma']
    }

    # classes
    classes = {}
    for c, l in character['levels'].items():
        if c.endswith("Level"):
            classes[c[:-5]] = l
    for cls, lvl in list(classes.items())[:]:
        if any(inv in cls for inv in ".$"):
            classes.pop(cls)
    levels = {"total_level": character['levels']['level'], "classes": classes}

    # attacks
    attacks = []
    for a in character['attacks']:
        try:
            bonus = int(a['attackBonus'])
            bonus_calc = None
        except (ValueError, TypeError):
            bonus = None
            bonus_calc = a['attackBonus']
        atk = {
            "name": a['name'],
            "bonus": bonus,
            "damage": a['damage'],
            "details": a.get('details'),
            "bonus_calc": bonus_calc
        }
        attacks.append(atk)

    # skills and saves
    skills = {}
    for skill_name in SKILL_NAMES:
        value = character['skills'][skill_name]
        skefct = character.get('skill_effects', {}).get(skill_name)
        adv = True if skefct == 'adv' else False if skefct == 'dis' else None
        skl = Skill(value, 0, 0, adv)
        skills[skill_name] = skl.to_dict()

    saves = {}
    for save_name in SAVE_NAMES:
        value = character['saves'][save_name]
        skefct = character.get('skill_effects', {}).get(save_name)
        adv = True if skefct == 'adv' else False if skefct == 'dis' else None
        skl = Skill(value, 0, 0, adv)
        saves[save_name] = skl.to_dict()

    # combat
    resistances = {
        "resist": character.get('resist', []),
        "immune": character.get('immune', []),
        "vuln": character.get('vuln', [])
    }
    ac = character.get('armor', 10)
    max_hp = character.get('hp',
                           4)  # you get 4 hp if your character is that old
    hp = character.get('consumables', {}).get('hp', {}).get('value', max_hp)
    temp_hp = character.get('consumables', {}).get('temphp',
                                                   {}).get('value', 0)

    cvars = character.get('cvars', {})
    options = {"options": character.get('settings', {})}

    # overrides
    override_attacks = []
    for a in character.get('overrides', {}).get('attacks', []):
        try:
            bonus = int(a['attackBonus'])
            bonus_calc = None
        except (ValueError, TypeError):
            bonus = None
            bonus_calc = a['attackBonus']
        atk = {
            "name": a['name'],
            "bonus": bonus,
            "damage": a['damage'],
            "details": a['details'],
            "bonus_calc": bonus_calc
        }
        override_attacks.append(atk)
    override_spells = []
    for old_spell in character.get('overrides', {}).get('spells', []):
        if isinstance(old_spell, dict):
            spl = SpellbookSpell(old_spell['name'], old_spell['strict'])
        else:
            spl = SpellbookSpell(old_spell, True)
        override_spells.append(spl.to_dict())
    overrides = {
        "desc": character.get('overrides', {}).get('desc'),
        "image": character.get('overrides', {}).get('image'),
        "attacks": override_attacks,
        "spells": override_spells
    }

    # other things
    consumables = []
    for cname, cons in character.get('consumables', {}).get('custom',
                                                            {}).items():
        value = cons['value']
        minv = cons.get('min')
        maxv = cons.get('max')
        reset = cons.get('reset')
        display_type = cons.get('type')
        live_id = cons.get('live')
        counter = CustomCounter(None, cname, value, minv, maxv, reset,
                                display_type, live_id)
        consumables.append(counter.to_dict())

    death_saves = {
        "successes":
        character.get('consumables', {}).get('deathsaves',
                                             {}).get('success',
                                                     {}).get('value', 0),
        "fails":
        character.get('consumables', {}).get('deathsaves',
                                             {}).get('fail',
                                                     {}).get('value', 0)
    }

    # spellcasting
    slots = {}
    max_slots = {}
    for l in range(1, 10):
        slots[str(l)] = character.get('consumables',
                                      {}).get('spellslots',
                                              {}).get(str(l),
                                                      {}).get('value', 0)
        max_slots[str(l)] = character.get('spellbook',
                                          {}).get('spellslots',
                                                  {}).get(str(l), 0)
    spells = []
    for old_spell in character.get('spellbook', {}).get('spells', []):
        if isinstance(old_spell, dict):
            spl = SpellbookSpell(old_spell['name'], old_spell['strict'])
        else:
            spl = SpellbookSpell(old_spell, True)
        spells.append(spl.to_dict())
    spellbook = {
        "slots": slots,
        "max_slots": max_slots,
        "spells": spells,
        "dc": character.get('spellbook', {}).get('dc'),
        "sab": character.get('spellbook', {}).get('attackBonus'),
        "caster_level": character['levels']['level']
    }

    live = 'dicecloud' if character.get('live') else None
    race = character.get('race')
    background = character.get('background')

    char = Character(owner, upstream, active, sheet_type, import_version, name,
                     description, image, stats, levels, attacks, skills,
                     resistances, saves, ac, max_hp, hp, temp_hp, cvars,
                     options, overrides, consumables, death_saves, spellbook,
                     live, race, background)
    return char