Beispiel #1
0
    def get_spellbook(self):
        if self.character_data is None:
            raise Exception('You must call get_character() first.')
        spellcasterLevel = 0
        castingClasses = 0
        spellMod = 0
        pactSlots = 0
        pactLevel = 1
        for _class in self.character_data['classes']:
            castingAbility = _class['definition']['spellCastingAbilityId'] or \
                             (_class['subclassDefinition'] or {}).get('spellCastingAbilityId')
            if castingAbility:
                castingClasses += 1
                casterMult = CASTER_TYPES.get(_class['definition']['name'], 1)
                spellcasterLevel += _class['level'] * casterMult
                spellMod = max(spellMod, self.stat_from_id(castingAbility))
            if _class['definition']['name'] == 'Warlock':
                pactSlots = pact_slots_by_level(_class['level'])
                pactLevel = pact_level_by_level(_class['level'])

        if castingClasses > 1:
            spellcasterLevel = floor(spellcasterLevel)
        else:
            if spellcasterLevel >= 1:
                spellcasterLevel = ceil(spellcasterLevel)
            else:
                spellcasterLevel = 0
        log.debug(f"Caster level: {spellcasterLevel}")

        slots = {}
        for lvl in range(1, 10):
            slots[str(lvl)] = SLOTS_PER_LEVEL[lvl](spellcasterLevel)
        slots[str(pactLevel)] += pactSlots

        prof = self.get_stats().prof_bonus
        attack_bonus_bonus = self.get_stat("spell-attacks")
        dc = 8 + spellMod + prof
        sab = spellMod + prof + attack_bonus_bonus

        spellnames = []
        for src in self.character_data['classSpells']:
            spellnames.extend(s['definition']['name'].replace('\u2019', "'")
                              for s in src['spells'])
        for src in self.character_data['spells'].values():
            spellnames.extend(s['definition']['name'].replace('\u2019', "'")
                              for s in src)

        spells = []
        for value in spellnames:
            result = search(c.spells, value, lambda sp: sp.name, strict=True)
            if result and result[0] and result[1]:
                spells.append(SpellbookSpell(result[0].name, True))
            elif len(value) > 2:
                spells.append(SpellbookSpell(value))

        spellbook = Spellbook(slots, slots, spells, dc, sab,
                              self.get_levels().total_level)
        return spellbook
Beispiel #2
0
    def get_spellbook(self):
        if self.character_data is None:
            raise Exception('You must call get_character() first.')
        # max slots
        slots = {
            '1': int(self.character_data.cell('AK101').value or 0),
            '2': int(self.character_data.cell('E107').value or 0),
            '3': int(self.character_data.cell('AK113').value or 0),
            '4': int(self.character_data.cell('E119').value or 0),
            '5': int(self.character_data.cell('AK124').value or 0),
            '6': int(self.character_data.cell('E129').value or 0),
            '7': int(self.character_data.cell('AK134').value or 0),
            '8': int(self.character_data.cell('E138').value or 0),
            '9': int(self.character_data.cell('AK142').value or 0)
        }

        # spells C96:AH143
        potential_spells = self.character_data.range(
            "D96:AH143")  # returns a matrix, the docs lie
        if self.additional:
            potential_spells.extend(self.additional.range("D17:AH64"))

        spells = []
        for row in potential_spells:
            for cell in row:
                if cell.value and not cell.value in IGNORED_SPELL_VALUES:
                    value = cell.value.strip()
                    result = search(c.spells,
                                    value,
                                    lambda sp: sp.name,
                                    strict=True)
                    if result and result[0] and result[1]:
                        spells.append(SpellbookSpell(result[0].name, True))
                    elif len(value) > 2:
                        spells.append(SpellbookSpell(value))

        try:
            dc = int(self.character_data.cell('AB91').value or 0)
        except ValueError:
            dc = None

        try:
            sab = int(self.character_data.cell('AI91').value or 0)
        except ValueError:
            sab = None

        spellbook = Spellbook(slots, slots, spells, dc, sab, self.total_level)
        return spellbook
Beispiel #3
0
def parse_critterdb_spellcasting(traits):
    known_spells = []
    usual_dc = (0, 0)  # dc, number of spells using dc
    usual_sab = (0, 0)  # same thing
    caster_level = 1
    for trait in traits:
        if not 'Spellcasting' in trait.name:
            continue
        desc = trait.desc
        level_match = re.search(r"is a (\d+)[stndrh]{2}-level", desc)
        ab_dc_match = re.search(r"spell save DC (\d+), [+-](\d+) to hit", desc)
        spells = []
        for spell_match in re.finditer(
                r"(?:(?:(?:\d[stndrh]{2}\slevel)|(?:Cantrip))\s(?:\(.+\))|(?:At will)|(?:\d/day)): (.+)$", desc,
                re.MULTILINE):
            spell_texts = spell_match.group(1).split(', ')
            for spell_text in spell_texts:
                s = spell_text.strip('* _')
                spells.append(s.lower())
        if level_match:
            caster_level = max(caster_level, int(level_match.group(1)))
        if ab_dc_match:
            ab = int(ab_dc_match.group(2))
            dc = int(ab_dc_match.group(1))
            if len(spells) > usual_dc[1]:
                usual_dc = (dc, len(spells))
            if len(spells) > usual_sab[1]:
                usual_sab = (ab, len(spells))
        known_spells.extend(s for s in spells if s not in known_spells)
    dc = usual_dc[0]
    sab = usual_sab[0]
    log.debug(f"Lvl {caster_level}; DC: {dc}; SAB: {sab}; Spells: {known_spells}")
    spells = [SpellbookSpell(s) for s in known_spells]
    spellbook = Spellbook({}, {}, spells, dc, sab, caster_level)
    return spellbook
Beispiel #4
0
    def get_spellbook(self):
        if self.character_data is None:
            raise Exception('You must call get_character() first.')
        # max slots
        slots = {
            '1': int(self.character_data.value("AK101") or 0),
            '2': int(self.character_data.value("E107") or 0),
            '3': int(self.character_data.value("AK113") or 0),
            '4': int(self.character_data.value("E119") or 0),
            '5': int(self.character_data.value("AK124") or 0),
            '6': int(self.character_data.value("E129") or 0),
            '7': int(self.character_data.value("AK134") or 0),
            '8': int(self.character_data.value("E138") or 0),
            '9': int(self.character_data.value("AK142") or 0)
        }

        # spells C96:AH143
        potential_spells = self.character_data.value_range("D96:AH143")
        if self.additional:
            potential_spells.extend(self.additional.value_range("D17:AH64"))

        spells = []
        for value in potential_spells:
            value = value.strip()
            if len(value) > 2 and value not in IGNORED_SPELL_VALUES:
                log.debug(f"Searching for spell {value}")
                result, strict = search(compendium.spells,
                                        value,
                                        lambda sp: sp.name,
                                        strict=True)
                if result and strict:
                    spells.append(SpellbookSpell(result.name, True))
                else:
                    spells.append(SpellbookSpell(value.strip()))

        try:
            dc = int(self.character_data.value("AB91") or 0)
        except ValueError:
            dc = None

        try:
            sab = int(self.character_data.value("AI91") or 0)
        except ValueError:
            sab = None

        spellbook = Spellbook(slots, slots, spells, dc, sab, self.total_level)
        return spellbook
Beispiel #5
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']

        vuln = parse_resists(data['vulnerable']) if 'vulnerable' in data else None
        resist = parse_resists(data['resist']) if 'resist' in data else None
        immune = parse_resists(data['immune']) if 'immune' in data else None
        condition_immune = data.get('conditionImmune', []) if 'conditionImmune' in data else None

        raw_resists = {
            "vuln": parse_resists(data['vulnerable'], False) if 'vulnerable' in data else [],
            "resist": parse_resists(data['resist'], False) if 'resist' in data else [],
            "immune": parse_resists(data['immune'], False) if 'immune' in data else []
        }

        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'])

        source = data['source']
        proper = bool(data.get('isNamedCreature') or data.get('isNPC'))

        attacks = 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, raw_resists=raw_resists)
Beispiel #6
0
 def add_known_spell(self, spell):
     """Adds a spell to the character's known spell list.
     :param spell (Spell) - the Spell.
     :returns self"""
     if spell.name in self.spellbook:
         raise InvalidArgument("You already know this spell.")
     sbs = SpellbookSpell.from_spell(spell)
     self.spellbook.spells.append(sbs)
     self.overrides.spells.append(sbs)
Beispiel #7
0
    def get_spellbook(self):
        if self.character_data is None:
            raise Exception('You must call get_character() first.')
        spellnames = [
            s.get('name', '') for s in self.character_data.get('spells', [])
            if not s.get('removed', False)
        ]

        slots = {}
        for lvl in range(1, 10):
            num_slots = int(self.calculate_stat(f"level{lvl}SpellSlots"))
            slots[str(lvl)] = num_slots

        spells = []
        for spell in spellnames:
            result = search(compendium.spells, spell.strip(),
                            lambda sp: sp.name)
            if result and result[0] and result[1]:
                spells.append(SpellbookSpell.from_spell(result[0]))
            else:
                spells.append(SpellbookSpell(spell.strip()))

        spell_lists = [(0, 0)]  # ab, dc
        for sl in self.character_data.get('spellLists', []):
            try:
                ab = int(self.evaluator.eval(sl.get('attackBonus')))
                dc = int(self.evaluator.eval(sl.get('saveDC')))
                spell_lists.append((ab, dc))
            except:
                pass
        sl = sorted(spell_lists, key=lambda k: k[0], reverse=True)[0]
        sab = sl[0]
        dc = sl[1]

        spellbook = Spellbook(slots, slots, spells, dc, sab,
                              self.get_levels().total_level)

        log.debug(f"Completed parsing spellbook: {spellbook.to_dict()}")

        return spellbook
Beispiel #8
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 #9
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