async def get_castable_spell(ctx, name, choices=None): if choices is None: choices = await get_spell_choices(ctx) result = search(choices, name, lambda sp: sp.name) if result and result[1]: return result[0] return None
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
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())) # dc try: dc = int(self.character_data.value("AB91") or 0) except ValueError: dc = None # sab try: sab = int(self.character_data.value("AI91") or 0) except ValueError: sab = None # spellcasting mod spell_mod_value = self.character_data.value("U91") spell_mod = None if spell_mod_value: # it might be in the form of a ability name, or an int, wjdk try: spell_mod = self.get_stats().get_mod(spell_mod_value) except ValueError: try: spell_mod = int(spell_mod_value) except (TypeError, ValueError): spell_mod = None spellbook = Spellbook(slots, slots, spells, dc, sab, self.total_level, spell_mod) return spellbook
def get_spellbook(self): if self.character is None: raise Exception('You must call get_character() first.') spellbook = { 'spellslots': {}, 'spells': [], # C96:AH143 - gah. 'dc': 0, 'attackBonus': 0 } spellslots = { '1': int(self.character.cell('AK101').value or 0), '2': int(self.character.cell('E107').value or 0), '3': int(self.character.cell('AK113').value or 0), '4': int(self.character.cell('E119').value or 0), '5': int(self.character.cell('AK124').value or 0), '6': int(self.character.cell('E129').value or 0), '7': int(self.character.cell('AK134').value or 0), '8': int(self.character.cell('E138').value or 0), '9': int(self.character.cell('AK142').value or 0) } spellbook['spellslots'] = spellslots potential_spells = self.character.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({'name': result[0].name, 'strict': True}) elif len(value) > 2: spells.append({'name': value, 'strict': False}) spellbook['spells'] = spells try: spellbook['dc'] = int(self.character.cell('AB91').value or 0) except ValueError: pass try: spellbook['attackBonus'] = int( self.character.cell('AI91').value or 0) except ValueError: pass log.debug(f"Completed parsing spellbook: {spellbook}") return spellbook
async def spellbook(self, ctx): """Commands to display a character's known spells and metadata.""" character = await Character.from_ctx(ctx) embed = EmbedWithCharacter(character) embed.description = f"{character.get_name()} knows {len(character.get_spell_list())} spells." embed.add_field(name="DC", value=str(character.get_save_dc())) embed.add_field(name="Spell Attack Bonus", value=str(character.get_spell_ab())) embed.add_field(name="Spell Slots", value=character.get_remaining_slots_str() or "None") spells_known = {} choices = await get_spell_choices(ctx) for spell_ in character.get_raw_spells(): if isinstance(spell_, str): spell, strict = search(c.spells, spell_, lambda sp: sp.name) if spell is None or not strict: continue spells_known[str(spell.level)] = spells_known.get( str(spell.level), []) + [spell.name] else: spellname = spell_['name'] strict = spell_['strict'] spell = await get_castable_spell(ctx, spellname, choices) if spell is None and strict: continue elif spell is None: spells_known['unknown'] = spells_known.get( 'unknown', []) + [f"*{spellname}*"] else: if spell.source == 'homebrew': formatted = f"*{spell.name}*" else: formatted = spell.name spells_known[str(spell.level)] = spells_known.get( str(spell.level), []) + [formatted] level_name = { '0': 'Cantrips', '1': '1st Level', '2': '2nd Level', '3': '3rd Level', '4': '4th Level', '5': '5th Level', '6': '6th Level', '7': '7th Level', '8': '8th Level', '9': '9th Level' } for level, spells in sorted(list(spells_known.items()), key=lambda k: k[0]): if spells: spells.sort() embed.add_field(name=level_name.get(level, "Unknown"), value=', '.join(spells)) await ctx.send(embed=embed)
async def spellbook(self, ctx): """Commands to display a character's known spells and metadata.""" await ctx.trigger_typing() character: Character = await Character.from_ctx(ctx) embed = EmbedWithCharacter(character) embed.description = f"{character.name} knows {len(character.spellbook.spells)} spells." embed.add_field(name="DC", value=str(character.spellbook.dc)) embed.add_field(name="Spell Attack Bonus", value=str(character.spellbook.sab)) embed.add_field(name="Spell Slots", value=character.spellbook.slots_str() or "None") # dynamic help flags flag_show_multiple_source_help = False flag_show_homebrew_help = False spells_known = collections.defaultdict(lambda: []) choices = await get_spell_choices(ctx) for spell_ in character.spellbook.spells: results, strict = search(choices, spell_.name, lambda sp: sp.name, strict=True) if not strict: if len(results) > 1: spells_known['unknown'].append(f"*{spell_.name} ({'*' * len(results)})*") flag_show_multiple_source_help = True else: spells_known['unknown'].append(f"*{spell_.name}*") flag_show_homebrew_help = True else: spell = results if spell.homebrew: formatted = f"*{spell.name}*" flag_show_homebrew_help = True else: formatted = spell.name spells_known[str(spell.level)].append(formatted) level_name = {'0': 'Cantrips', '1': '1st Level', '2': '2nd Level', '3': '3rd Level', '4': '4th Level', '5': '5th Level', '6': '6th Level', '7': '7th Level', '8': '8th Level', '9': '9th Level'} for level, spells in sorted(list(spells_known.items()), key=lambda k: k[0]): if spells: spells.sort() embed.add_field(name=level_name.get(level, "Unknown"), value=', '.join(spells), inline=False) # dynamic help footer_out = [] if flag_show_homebrew_help: footer_out.append("An italicized spell indicates that the spell is homebrew.") if flag_show_multiple_source_help: footer_out.append("Asterisks after a spell indicates that the spell is being provided by multiple sources.") if footer_out: embed.set_footer(text=' '.join(footer_out)) await ctx.send(embed=embed)
def get_spellbook(self): if self.character is None: raise Exception('You must call get_character() first.') spellbook = { 'spellslots': {}, 'spells': [], 'dc': 0, 'attackBonus': 0, 'dicecloud_id': next((sl['_id'] for sl in self.character.get('spellLists', []) if not sl.get('removed')), None) } spells = self.character.get('spells', []) spellnames = [ s.get('name', '') for s in spells if not s.get('removed', False) ] for lvl in range(1, 10): numSlots = self.calculate_stat(f"level{lvl}SpellSlots") spellbook['spellslots'][str(lvl)] = numSlots for spell in spellnames: result = search(c.spells, spell.strip(), lambda sp: sp.name) if result and result[0] and result[1]: spellbook['spells'].append({ 'name': result[0].name, 'strict': True }) else: spellbook['spells'].append({ 'name': spell, 'strict': False }) # non-strict spell sls = [(0, 0)] # ab, dc for sl in self.character.get('spellLists', []): try: ab = int(self.evaluator.eval(sl.get('attackBonus'))) dc = int(self.evaluator.eval(sl.get('saveDC'))) sls.append((ab, dc)) except: pass sl = sorted(sls, key=lambda k: k[0], reverse=True)[0] spellbook['attackBonus'] = sl[0] spellbook['dc'] = sl[1] log.debug(f"Completed parsing spellbook: {spellbook}") return spellbook
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
def get_spellbook(self): if self.character_data is None: raise Exception('You must call get_character() first.') potential_spells = [s 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 spell_lists = {} # list_id: (ab, dc, scam) for sl in self.character_data.get('spellLists', []): try: ab_calc = sl.get('attackBonus') ab = int(self.evaluator.eval(ab_calc)) dc = int(self.evaluator.eval(sl.get('saveDC'))) try: scam = self.get_stats().get_mod(next(m for m in STAT_NAMES if m in ab_calc)) except StopIteration: scam = 0 spell_lists[sl['_id']] = (ab, dc, scam) except: pass sab, dc, scam = sorted(spell_lists.values(), key=lambda k: k[0], reverse=True)[0] if spell_lists else (0, 0, 0) spells = [] for spell in potential_spells: spell_list_id = spell['parent']['id'] spell_ab, spell_dc, spell_mod = spell_lists.get(spell_list_id, (None, None, None)) if spell_ab == sab: spell_ab = None if spell_dc == dc: spell_dc = None if spell_mod == scam: spell_mod = None result, strict = search(compendium.spells, spell['name'].strip(), lambda sp: sp.name, strict=True) if result and strict: spells.append(SpellbookSpell.from_spell(result, sab=spell_ab, dc=spell_dc, mod=spell_mod)) else: spells.append(SpellbookSpell(spell['name'].strip(), sab=spell_ab, dc=spell_dc, mod=spell_mod)) spellbook = Spellbook(slots, slots, spells, dc, sab, self.get_levels().total_level, scam) log.debug(f"Completed parsing spellbook: {spellbook.to_dict()}") return spellbook
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, 0)] # ab, dc, scam for sl in self.character_data.get('spellLists', []): try: ab_calc = sl.get('attackBonus') ab = int(self.evaluator.eval(ab_calc)) dc = int(self.evaluator.eval(sl.get('saveDC'))) scam = self.get_stats().get_mod( next(m for m in STAT_NAMES if m in ab_calc)) spell_lists.append((ab, dc, scam)) except: pass sab, dc, scam = sorted(spell_lists, key=lambda k: k[0], reverse=True)[0] spellbook = Spellbook(slots, slots, spells, dc, sab, self.get_levels().total_level, scam) log.debug(f"Completed parsing spellbook: {spellbook.to_dict()}") return spellbook
def get_spellbook(self): if self.character_data is None: raise Exception('You must call get_character() first.') spellcasterLevel = 0 castingClasses = 0 spell_mod = 0 pactSlots = 0 pactLevel = 1 hasSpells = False for _class in self.character_data['classes']: castingAbility = _class['definition']['spellCastingAbilityId'] or \ (_class['subclassDefinition'] or {}).get('spellCastingAbilityId') if castingAbility: casterMult = CASTER_TYPES.get(_class['definition']['name'], 1) spellcasterLevel += _class['level'] * casterMult castingClasses += 1 if casterMult else 0 # warlock multiclass fix spell_mod = max(spell_mod, self.stat_from_id(castingAbility)) class_features = { cf['name'] for cf in _class['definition']['classFeatures'] if cf['requiredLevel'] <= _class['level'] } if _class['subclassDefinition']: class_features.update({ cf['name'] for cf in _class['subclassDefinition']['classFeatures'] if cf['requiredLevel'] <= _class['level'] }) hasSpells = 'Spellcasting' in class_features or hasSpells 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 hasSpells: 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 save_dc_bonus = max(self.get_stat("spell-save-dc"), self.get_stat("warlock-spell-save-dc")) attack_bonus_bonus = max(self.get_stat("spell-attacks"), self.get_stat("warlock-spell-attacks")) dc = 8 + spell_mod + prof + save_dc_bonus sab = spell_mod + 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(compendium.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, spell_mod or None) return spellbook
async def spellbook(self, ctx, *args): """ Commands to display a character's known spells and metadata. __Valid Arguments__ all - Display all of a character's known spells, including unprepared ones. """ await ctx.trigger_typing() character: Character = await Character.from_ctx(ctx) ep = embeds.EmbedPaginator(EmbedWithCharacter(character)) ep.add_field(name="DC", value=str(character.spellbook.dc), inline=True) ep.add_field(name="Spell Attack Bonus", value=str(character.spellbook.sab), inline=True) ep.add_field(name="Spell Slots", value=character.spellbook.slots_str() or "None", inline=True) show_unprepared = 'all' in args known_count = len(character.spellbook.spells) prepared_count = sum(1 for spell in character.spellbook.spells if spell.prepared) if known_count == prepared_count: ep.add_description(f"{character.name} knows {known_count} spells.") else: ep.add_description(f"{character.name} has {prepared_count} spells prepared and knows {known_count} spells.") # dynamic help flags flag_show_multiple_source_help = False flag_show_homebrew_help = False flag_show_prepared_help = False flag_show_prepared_underline_help = False spells_known = collections.defaultdict(lambda: []) choices = await get_spell_choices(ctx) for sb_spell in character.spellbook.spells: if not (sb_spell.prepared or show_unprepared): flag_show_prepared_help = True continue # homebrew / multisource formatting results, strict = search(choices, sb_spell.name, lambda sp: sp.name, strict=True) if not strict: known_level = 'unknown' if len(results) > 1: formatted = f"*{sb_spell.name} ({'*' * len(results)})*" flag_show_multiple_source_help = True else: formatted = f"*{sb_spell.name}*" flag_show_homebrew_help = True else: spell = results known_level = str(spell.level) if spell.homebrew: formatted = f"*{spell.name}*" flag_show_homebrew_help = True else: formatted = spell.name # prepared formatting if show_unprepared and sb_spell.prepared: formatted = f"__{formatted}__" flag_show_prepared_underline_help = True spells_known[known_level].append(formatted) level_name = { '0': 'Cantrips', '1': '1st Level', '2': '2nd Level', '3': '3rd Level', '4': '4th Level', '5': '5th Level', '6': '6th Level', '7': '7th Level', '8': '8th Level', '9': '9th Level' } for level, spells in sorted(list(spells_known.items()), key=lambda k: k[0]): if spells: spells.sort(key=lambda s: s.lstrip('*_')) ep.add_field(name=level_name.get(level, "Unknown"), value=', '.join(spells), inline=False) # dynamic help footer_out = [] if flag_show_homebrew_help: footer_out.append("An italicized spell indicates that the spell is homebrew.") if flag_show_multiple_source_help: footer_out.append("Asterisks after a spell indicates that the spell is being provided by multiple sources.") if flag_show_prepared_help: footer_out.append(f"Unprepared spells were not shown. Use \"{ctx.prefix}spellbook all\" to view them!") if flag_show_prepared_underline_help: footer_out.append("Prepared spells are marked with an underline.") if footer_out: ep.set_footer(value=' '.join(footer_out)) await ep.send_to(ctx)