def get_stats(self) -> BaseStats: """Returns a dict of stats.""" if self.character_data is None: raise Exception('You must call get_character() first.') if self.stats: return self.stats character = self.character_data profByLevel = floor(self.get_levels().total_level / 4 + 1.75) prof_bonus = self.get_stat('proficiency-bonus', base=int(profByLevel)) stat_dict = {} for i, stat in enumerate(('strength', 'dexterity', 'constitution', 'intelligence', 'wisdom', 'charisma')): base = next(s for s in character['stats'] if s['id'] == i + 1)['value'] bonus = next(s for s in character['bonusStats'] if s['id'] == i + 1)['value'] or 0 override = next(s for s in character['overrideStats'] if s['id'] == i + 1)['value'] stat_dict[stat] = override or self.get_stat(f"{stat}-score", base=base + bonus) stats = BaseStats(prof_bonus, **stat_dict) self.stats = stats return stats
def __init__(self, owner: str, upstream: str, active: bool, sheet_type: str, import_version: int, name: str, description: str, image: str, stats: dict, levels: dict, attacks: list, skills: dict, resistances: dict, saves: dict, ac: int, max_hp: int, hp: int, temp_hp: int, cvars: dict, options: dict, overrides: dict, consumables: list, death_saves: dict, spellbook: dict, live, race: str, background: str, **kwargs): if kwargs: log.warning(f"Unused kwargs: {kwargs}") # sheet metadata self._owner = owner self._upstream = upstream self._active = active self._sheet_type = sheet_type self._import_version = import_version # main character info self.name = name self._description = description self._image = image self.stats = BaseStats.from_dict(stats) self.levels = Levels.from_dict(levels) self._attacks = [Attack.from_dict(atk) for atk in attacks] self.skills = Skills.from_dict(skills) self.resistances = Resistances.from_dict(resistances) self.saves = Saves.from_dict(saves) # hp/ac self.ac = ac self.max_hp = max_hp self._hp = hp self._temp_hp = temp_hp # customization self.cvars = cvars self.options = CharOptions.from_dict(options) self.overrides = ManualOverrides.from_dict(overrides) # ccs self.consumables = [ CustomCounter.from_dict(self, cons) for cons in consumables ] self.death_saves = DeathSaves.from_dict(death_saves) # spellbook spellbook = Spellbook.from_dict(spellbook) super(Character, self).__init__(spellbook) # live sheet integrations self._live = live integration = INTEGRATION_MAP.get(live) if integration: self._live_integration = integration(self) else: self._live_integration = None # misc research things self.race = race self.background = background
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']) return cls(**data)
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)
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
def get_stats(self): """Returns a dict of stats.""" if self.character_data is None: raise Exception('You must call get_character() first.') character = self.character_data try: prof_bonus = int(character.cell("H14").value) except (TypeError, ValueError): raise MissingAttribute("Proficiency Bonus") index = 15 stat_dict = {} for stat in ('strength', 'dexterity', 'constitution', 'intelligence', 'wisdom', 'charisma'): try: stat_dict[stat] = int(character.cell("C" + str(index)).value) index += 5 except (TypeError, ValueError): raise MissingAttribute(stat) stats = BaseStats(prof_bonus, **stat_dict) return stats
def get_stats(self) -> BaseStats: if self.character_data is None: raise Exception('You must call get_character() first.') if self.stats: return self.stats self.get_levels() stat_dict = { 'proficiencyBonus': int(self.calculate_stat('proficiencyBonus')) } for stat in ('strength', 'dexterity', 'constitution', 'wisdom', 'intelligence', 'charisma'): stat_dict[stat] = int(self.calculate_stat(stat)) stat_dict[stat + 'Mod'] = int(stat_dict[stat]) // 2 - 5 self.evaluator.names.update(stat_dict) stats = BaseStats(stat_dict['proficiencyBonus'], stat_dict['strength'], stat_dict['dexterity'], stat_dict['constitution'], stat_dict['intelligence'], stat_dict['wisdom'], stat_dict['charisma']) self.stats = stats return stats
def from_critterdb(cls, data): ability_scores = BaseStats(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 errors.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) traits = parse_critterdb_traits(data, 'additionalAbilities') actions = parse_critterdb_traits(data, 'actions') reactions = parse_critterdb_traits(data, 'reactions') legactions = parse_critterdb_traits(data, 'legendaryActions') attacks = [] for atk_src in (traits, actions, reactions, legactions): for trait in atk_src: attacks.extend(trait.attacks) resists = { "resist": data['stats']['damageResistances'], "immune": data['stats']['damageImmunities'], "vuln": data['stats']['damageVulnerabilities'] } spellcasting = parse_critterdb_spellcasting(traits) return cls(data['name'], data['stats']['size'], data['stats']['race'], data['stats']['alignment'], data['stats']['armorClass'], data['stats']['armorType'], hp, hitdice, data['stats']['speed'], ability_scores, cr, data['stats']['experiencePoints'], None, ', '.join(data['stats']['senses']), data['stats']['damageVulnerabilities'], data['stats']['damageResistances'], data['stats']['damageImmunities'], data['stats']['conditionImmunities'], saves, skills, data['stats']['languages'], traits, actions, reactions, legactions, data['stats']['legendaryActionsPerRound'], True, 'homebrew', attacks, data['flavor']['nameIsProper'], data['flavor']['imageUrl'], raw_resists=resists, spellcasting=spellcasting)