async def feat(self, ctx, *, name: str): """Looks up a feat.""" try: guild_id = ctx.message.server.id pm = self.settings.get(guild_id, {}).get("pm_result", False) srd = self.settings.get(guild_id, {}).get("srd", False) except: pm = False srd = False destination = ctx.message.author if pm else ctx.message.channel result = await search_and_select(ctx, c.feats, name, lambda e: e['name']) if not result['name'] == 'Grappler' and srd: # the only SRD feat. return await self.send_srd_error(ctx, result) text = parse_data_entry(result['entries']) prereq = "None" if 'prerequisite' in result: for entry in result['prerequisite']: if 'race' in entry: prereq = ' or '.join( f"{r['name']}" + (f" ({r['subrace']})" if 'subrace' in r else '') for r in entry['race']) if 'ability' in entry: abilities = [] for ab in entry['ability']: abilities.extend(f"{ABILITY_MAP.get(a)} {s}" for a, s in ab.items()) prereq = ' or '.join(abilities) if 'spellcasting' in entry: prereq = "The ability to cast at least one spell" if 'proficiency' in entry: prereq = f"Proficiency with {entry['proficiency'][0]['armor']} armor" ability = None if 'ability' in result: if 'choose' in result['ability']: ability = ' or '.join(ABILITY_MAP.get(a) for a in result['ability']['choose'][0]['from']) else: ability = ' or '.join(ABILITY_MAP.get(a) for a in result['ability'].keys()) embed = EmbedWithAuthor(ctx) embed.title = result['name'] embed.add_field(name="Prerequisite", value=prereq) embed.add_field(name="Source", value=result['source']) if ability: embed.add_field(name="Ability Improvement", value=f"Increase your {ability} score by 1, up to a maximum of 20.") _name = 'Description' for piece in [text[i:i + 1024] for i in range(0, len(text), 1024)]: embed.add_field(name=_name, value=piece) _name = '** **' await self.bot.send_message(destination, embed=embed)
async def _class(self, ctx, name: str, level: int = None): """Looks up a class, or all features of a certain level.""" try: guild_id = ctx.message.server.id pm = self.settings.get(guild_id, {}).get("pm_result", False) srd = self.settings.get(guild_id, {}).get("srd", False) except: pm = False srd = False destination = ctx.message.author if pm else ctx.message.channel if level is not None and not 0 < level < 21: return await self.bot.say("Invalid level.") result = await search_and_select(ctx, c.classes, name, lambda e: e['name'], srd=srd) if not result['srd'] and srd: return await self.send_srd_error(ctx, result) embed = EmbedWithAuthor(ctx) if level is None: embed.title = result['name'] embed.add_field(name="Hit Die", value=f"1d{result['hd']['faces']}") embed.add_field(name="Saving Throws", value=', '.join( ABILITY_MAP.get(p) for p in result['proficiency'])) levels = [] starting_profs = f"You are proficient with the following items, " \ f"in addition to any proficiencies provided by your race or background.\n" \ f"Armor: {', '.join(result['startingProficiencies'].get('armor', ['None']))}\n" \ f"Weapons: {', '.join(result['startingProficiencies'].get('weapons', ['None']))}\n" \ f"Tools: {', '.join(result['startingProficiencies'].get('tools', ['None']))}\n" \ f"Skills: Choose {result['startingProficiencies']['skills']['choose']} from " \ f"{', '.join(result['startingProficiencies']['skills']['from'])}" equip_choices = '\n'.join( f"• {i}" for i in result['startingEquipment']['default']) gold_alt = f"Alternatively, you may start with {result['startingEquipment']['goldAlternative']} gp " \ f"to buy your own equipment." if 'goldAlternative' in result['startingEquipment'] else '' starting_items = f"You start with the following items, plus anything provided by your background.\n" \ f"{equip_choices}\n" \ f"{gold_alt}" for level in range(1, 21): level_str = [] level_features = result['classFeatures'][level - 1] for feature in level_features: level_str.append(feature.get('name')) levels.append(', '.join(level_str)) embed.add_field(name="Starting Proficiencies", value=starting_profs) embed.add_field(name="Starting Equipment", value=starting_items) level_features_str = "" for i, l in enumerate(levels): level_features_str += f"`{i+1}` {l}\n" embed.description = level_features_str embed.set_footer(text="Use !classfeat to look up a feature.") else: embed.title = f"{result['name']}, Level {level}" level_resources = {} level_features = result['classFeatures'][level - 1] for table in result['classTableGroups']: relevant_row = table['rows'][level - 1] for i, col in enumerate(relevant_row): level_resources[table['colLabels'][i]] = parse_data_entry( [col]) for res_name, res_value in level_resources.items(): embed.add_field(name=res_name, value=res_value) for f in level_features: text = parse_data_entry(f['entries']) embed.add_field(name=f['name'], value=(text[:1019] + "...") if len(text) > 1023 else text) embed.set_footer( text="Use !classfeat to look up a feature if it is cut off.") await self.bot.send_message(destination, embed=embed)
async def createCharSheet(self, ctx, final_level, dicecloud_userId, race=None, _class=None, subclass=None, background=None): dc = DicecloudClient.getInstance() caveats = [] # a to do list for the user # Name Gen + Setup # DMG name gen name = self.nameGen() race = race or random.choice([r for r in c.fancyraces if r['source'] in ('PHB', 'VGM', 'MTF')]) _class = _class or random.choice([cl for cl in c.classes if not 'UA' in cl.get('source')]) subclass = subclass or random.choice([s for s in _class['subclasses'] if not 'UA' in s['source']]) background = background or random.choice(c.backgrounds) try: char_id = dc.create_character(name=name, race=race.name, backstory=background['name']) except MeteorClientException: return await self.bot.say("I am having problems connecting to Dicecloud. Please try again later.") try: await dc.share_character(char_id, dicecloud_userId) except: dc.delete_character(char_id) # clean up return await self.bot.say("Invalid dicecloud username.") loadingMessage = await self.bot.send_message(ctx.message.channel, "Generating character, please wait...") # Stat Gen # Allow user to enter base values caveats.append("**Base Ability Scores**: Enter your base ability scores (without modifiers) in the feature " "titled Base Ability Scores.") # Race Gen # Racial Features speed = race.get_speed_int() if speed: dc.insert_effect(char_id, Parent.race(char_id), 'base', value=int(speed), stat='speed') for k, v in race.ability.items(): if not k == 'choose': dc.insert_effect(char_id, Parent.race(char_id), 'add', value=int(v), stat=ABILITY_MAP[k].lower()) else: dc.insert_effect(char_id, Parent.race(char_id), 'add', value=int(v[0].get('amount', 1))) caveats.append( f"**Racial Ability Bonus ({int(v[0].get('amount', 1)):+})**: In your race (Journal tab), select the" f" score you want a bonus to (choose {v[0]['count']} from {', '.join(v[0]['from'])}).") for t in race.get_traits(): dc.insert_feature(char_id, t['name'], t['text']) caveats.append("**Racial Features**: Check that the number of uses for each feature is correct, and apply " "any effects they grant.") # Class Gen # Class Features class_id = dc.insert_class(char_id, final_level, _class['name']) dc.insert_effect(char_id, Parent.class_(class_id), 'add', stat=f"d{_class['hd']['faces']}HitDice", calculation=f"{_class['name']}Level") hpPerLevel = (int(_class['hd']['faces']) / 2) + 1 firstLevelHp = int(_class['hd']['faces']) - hpPerLevel dc.insert_effect(char_id, Parent.class_(class_id), 'add', stat='hitPoints', calculation=f"{hpPerLevel}*{_class['name']}Level+{firstLevelHp}") caveats.append("**HP**: HP is currently calculated using class average; change the value in the Journal tab " "under your class if you wish to change it.") for saveProf in _class['proficiency']: profKey = ABILITY_MAP.get(saveProf).lower() + 'Save' dc.insert_proficiency(char_id, Parent.class_(class_id), profKey, type_='save') for prof in _class['startingProficiencies'].get('armor', []): dc.insert_proficiency(char_id, Parent.class_(class_id), prof, type_='armor') for prof in _class['startingProficiencies'].get('weapons', []): dc.insert_proficiency(char_id, Parent.class_(class_id), prof, type_='weapon') for prof in _class['startingProficiencies'].get('tools', []): dc.insert_proficiency(char_id, Parent.class_(class_id), prof, type_='tool') for _ in range(int(_class['startingProficiencies']['skills']['choose'])): dc.insert_proficiency(char_id, Parent.class_(class_id), type_='skill') # add placeholders caveats.append(f"**Skill Proficiencies**: You get to choose your skill proficiencies. Under your class " f"in the Journal tab, you may select {_class['startingProficiencies']['skills']['choose']} " f"skills from {', '.join(_class['startingProficiencies']['skills']['from'])}.") equip_choices = '\n'.join(f"• {i}" for i in _class['startingEquipment']['default']) gold_alt = f"Alternatively, you may start with {_class['startingEquipment']['goldAlternative']} gp " \ f"to buy your own equipment." if 'goldAlternative' in _class['startingEquipment'] else '' starting_items = f"You start with the following items, plus anything provided by your background.\n" \ f"{equip_choices}\n" \ f"{gold_alt}" caveats.append(f"**Starting Class Equipment**: {starting_items}") level_resources = {} for table in _class['classTableGroups']: relevant_row = table['rows'][final_level - 1] for i, col in enumerate(relevant_row): level_resources[table['colLabels'][i]] = parse_data_entry([col]) for res_name, res_value in level_resources.items(): stat_name = CLASS_RESOURCE_NAMES.get(res_name) if stat_name: try: dc.insert_effect(char_id, Parent.class_(class_id), 'base', value=int(res_value), stat=stat_name) except ValueError: # edge case: level 20 barb rage pass num_subclass_features = 0 for level in range(1, final_level + 1): level_features = _class['classFeatures'][level - 1] for f in level_features: if f.get('gainSubclassFeature'): num_subclass_features += 1 text = parse_data_entry(f['entries'], True) dc.insert_feature(char_id, f['name'], text) for num in range(num_subclass_features): level_features = subclass['subclassFeatures'][num] for feature in level_features: for entry in feature.get('entries', []): if not isinstance(entry, dict): continue if not entry.get('type') == 'entries': continue fe = {'name': entry['name'], 'text': parse_data_entry(entry['entries'], True)} dc.insert_feature(char_id, fe['name'], fe['text']) caveats.append("**Class Features**: Check that the number of uses for each feature is correct, and apply " "any effects they grant.") caveats.append("**Spellcasting**: If your class can cast spells, be sure to set your number of known spells, " "max prepared, DC, attack bonus, and what spells you know in the Spells tab. You can add a " "spell to your spellbook by connecting the character to Avrae and running `!sb add <SPELL>`.") # Background Gen # Inventory/Trait Gen for trait in background['trait']: text = '\n'.join(t for t in trait['text'] if t) if 'proficiency' in trait['name'].lower(): for skill in text.split(', '): if skill in SKILL_MAP: dc.insert_proficiency(char_id, Parent.background(char_id), SKILL_MAP.get(skill)) else: dc.insert_proficiency(char_id, Parent.background(char_id), skill, type_='tool') elif 'language' in trait['name'].lower(): for lang in text.split(', '): dc.insert_proficiency(char_id, Parent.background(char_id), lang, type_='language') caveats.append("**Languages**: Some backgrounds' languages may ask you to choose one or more. Fill " "this out in the Persona tab.") elif trait['name'].lower().startswith('feature'): tname = trait['name'][9:] dc.insert_feature(char_id, tname, text) elif trait['name'].lower().startswith('equipment'): caveats.append(f"**Background Equipment**: Your background grants you {text}") # await dc.transfer_ownership(char_id, dicecloud_userId) TODO fix your stuff, dicecloud out = f"Generated {name}! I have PMed you the link." await self.bot.send_message(ctx.message.author, f"https://dicecloud.com/character/{char_id}/{name}") await self.bot.send_message(ctx.message.author, "**__Caveats__**\nNot everything is automagical! Here are some things you still " "have to do manually:\n" + '\n\n'.join(caveats)) await self.bot.send_message(ctx.message.author, f"When you're ready, load your character into Avrae with the command " f"`!dicecloud https://dicecloud.com/character/{char_id}/{name} -cc`") await self.bot.edit_message(loadingMessage, out)
async def genChar(self, ctx, final_level, race=None, _class=None, subclass=None, background=None): loadingMessage = await self.bot.send_message(ctx.message.channel, "Generating character, please wait...") color = random.randint(0, 0xffffff) # Name Gen # DMG name gen name = self.nameGen() # Stat Gen # 4d6d1 # reroll if too low/high stats = self.genStats() await self.bot.send_message(ctx.message.author, "**Stats for {0}:** `{1}`".format(name, stats)) # Race Gen # Racial Features race = race or random.choice([r for r in c.fancyraces if r.source in ('PHB', 'VGM', 'MTF')]) embed = EmbedWithAuthor(ctx) embed.title = race.name embed.description = f"Source: {race.source}" embed.add_field(name="Speed", value=race.get_speed_str()) embed.add_field(name="Size", value=race.size) embed.add_field(name="Ability Bonuses", value=race.get_asi_str()) for t in race.get_traits(): f_text = t['text'] f_text = [f_text[i:i + 1024] for i in range(0, len(f_text), 1024)] embed.add_field(name=t['name'], value=f_text[0]) for piece in f_text[1:]: embed.add_field(name="** **", value=piece) embed.colour = color await self.bot.send_message(ctx.message.author, embed=embed) # Class Gen # Class Features _class = _class or random.choice([cl for cl in c.classes if not 'UA' in cl.get('source')]) subclass = subclass or random.choice([s for s in _class['subclasses'] if not 'UA' in s['source']]) embed = EmbedWithAuthor(ctx) embed.title = f"{_class['name']} ({subclass['name']})" embed.add_field(name="Hit Die", value=f"1d{_class['hd']['faces']}") embed.add_field(name="Saving Throws", value=', '.join(ABILITY_MAP.get(p) for p in _class['proficiency'])) levels = [] starting_profs = f"You are proficient with the following items, " \ f"in addition to any proficiencies provided by your race or background.\n" \ f"Armor: {', '.join(_class['startingProficiencies'].get('armor', ['None']))}\n" \ f"Weapons: {', '.join(_class['startingProficiencies'].get('weapons', ['None']))}\n" \ f"Tools: {', '.join(_class['startingProficiencies'].get('tools', ['None']))}\n" \ f"Skills: Choose {_class['startingProficiencies']['skills']['choose']} from " \ f"{', '.join(_class['startingProficiencies']['skills']['from'])}" equip_choices = '\n'.join(f"• {i}" for i in _class['startingEquipment']['default']) gold_alt = f"Alternatively, you may start with {_class['startingEquipment']['goldAlternative']} gp " \ f"to buy your own equipment." if 'goldAlternative' in _class['startingEquipment'] else '' starting_items = f"You start with the following items, plus anything provided by your background.\n" \ f"{equip_choices}\n" \ f"{gold_alt}" for level in range(1, final_level + 1): level_str = [] level_features = _class['classFeatures'][level - 1] for feature in level_features: level_str.append(feature.get('name')) levels.append(', '.join(level_str)) embed.add_field(name="Starting Proficiencies", value=starting_profs) embed.add_field(name="Starting Equipment", value=starting_items) level_features_str = "" for i, l in enumerate(levels): level_features_str += f"`{i+1}` {l}\n" embed.description = level_features_str embed.colour = color await self.bot.send_message(ctx.message.author, embed=embed) embed = EmbedWithAuthor(ctx) level_resources = {} for table in _class['classTableGroups']: relevant_row = table['rows'][final_level - 1] for i, col in enumerate(relevant_row): level_resources[table['colLabels'][i]] = parse_data_entry([col]) for res_name, res_value in level_resources.items(): embed.add_field(name=res_name, value=res_value) embed.colour = color await self.bot.send_message(ctx.message.author, embed=embed) embed_queue = [EmbedWithAuthor(ctx)] num_subclass_features = 0 num_fields = 0 def inc_fields(text): nonlocal num_fields num_fields += 1 if num_fields > 25: embed_queue.append(EmbedWithAuthor(ctx)) num_fields = 0 if len(str(embed_queue[-1].to_dict())) + len(text) > 5800: embed_queue.append(EmbedWithAuthor(ctx)) num_fields = 0 for level in range(1, final_level + 1): level_features = _class['classFeatures'][level - 1] for f in level_features: if f.get('gainSubclassFeature'): num_subclass_features += 1 text = parse_data_entry(f['entries']) text = [text[i:i + 1024] for i in range(0, len(text), 1024)] inc_fields(text[0]) embed_queue[-1].add_field(name=f['name'], value=text[0]) for piece in text[1:]: inc_fields(piece) embed_queue[-1].add_field(name="\u200b", value=piece) for num in range(num_subclass_features): level_features = subclass['subclassFeatures'][num] for feature in level_features: for entry in feature.get('entries', []): if not isinstance(entry, dict): continue if not entry.get('type') == 'entries': continue fe = {'name': entry['name'], 'text': parse_data_entry(entry['entries'])} text = [fe['text'][i:i + 1024] for i in range(0, len(fe['text']), 1024)] inc_fields(text[0]) embed_queue[-1].add_field(name=fe['name'], value=text[0]) for piece in text[1:]: inc_fields(piece) embed_queue[-1].add_field(name="\u200b", value=piece) for embed in embed_queue: embed.colour = color await self.bot.send_message(ctx.message.author, embed=embed) # Background Gen # Inventory/Trait Gen background = background or random.choice(c.backgrounds) embed = EmbedWithAuthor(ctx) embed.title = background['name'] embed.description = f"*Source: {background.get('source', 'Unknown')}*" ignored_fields = ['suggested characteristics', 'specialty', 'harrowing event'] for trait in background['trait']: if trait['name'].lower() in ignored_fields: continue text = '\n'.join(t for t in trait['text'] if t) text = [text[i:i + 1024] for i in range(0, len(text), 1024)] embed.add_field(name=trait['name'], value=text[0]) for piece in text[1:]: embed.add_field(name="\u200b", value=piece) embed.colour = color await self.bot.send_message(ctx.message.author, embed=embed) out = "{6}\n{0}, {1} {7} {2} {3}. {4} Background.\nStat Array: `{5}`\nI have PM'd you full character details.".format( name, race.name, _class['name'], final_level, background['name'], stats, ctx.message.author.mention, subclass['name']) await self.bot.edit_message(loadingMessage, out)
async def _class(self, ctx, name: str, level: int = None): """Looks up a class, or all features of a certain level.""" if level is not None and not 0 < level < 21: return await ctx.send("Invalid level.") choices = compendium.classes + compendium.nclass_names result = await self._lookup_search(ctx, choices, name, lambda e: e['name'], search_type='class') if not result: return embed = EmbedWithAuthor(ctx) if level is None: embed.title = result['name'] embed.add_field(name="Hit Die", value=f"1d{result['hd']['faces']}") embed.add_field(name="Saving Throws", value=', '.join(ABILITY_MAP.get(p) for p in result['proficiency'])) levels = [] starting_profs = f"You are proficient with the following items, " \ f"in addition to any proficiencies provided by your race or background.\n" \ f"Armor: {', '.join(result['startingProficiencies'].get('armor', ['None']))}\n" \ f"Weapons: {', '.join(result['startingProficiencies'].get('weapons', ['None']))}\n" \ f"Tools: {', '.join(result['startingProficiencies'].get('tools', ['None']))}\n" \ f"Skills: Choose {result['startingProficiencies']['skills']['choose']} from " \ f"{', '.join(result['startingProficiencies']['skills']['from'])}" equip_choices = '\n'.join(f"• {i}" for i in result['startingEquipment']['default']) gold_alt = f"Alternatively, you may start with {result['startingEquipment']['goldAlternative']} gp " \ f"to buy your own equipment." if 'goldAlternative' in result['startingEquipment'] else '' starting_items = f"You start with the following items, plus anything provided by your background.\n" \ f"{equip_choices}\n" \ f"{gold_alt}" for level in range(1, 21): level_str = [] level_features = result['classFeatures'][level - 1] for feature in level_features: level_str.append(feature.get('name')) levels.append(', '.join(level_str)) embed.add_field(name="Starting Proficiencies", value=starting_profs, inline=False) embed.add_field(name="Starting Equipment", value=starting_items, inline=False) level_features_str = "" for i, l in enumerate(levels): level_features_str += f"`{i + 1}` {l}\n" embed.description = level_features_str embed.set_footer(text=f"Use {ctx.prefix}classfeat to look up a feature.") else: embed.title = f"{result['name']}, Level {level}" level_resources = {} level_features = result['classFeatures'][level - 1] for table in result.get('classTableGroups', []): relevant_row = table['rows'][level - 1] for i, col in enumerate(relevant_row): level_resources[table['colLabels'][i]] = parse_data_entry([col]) for res_name, res_value in level_resources.items(): if res_value != '0': embed.add_field(name=res_name, value=res_value) for f in level_features: text = parse_data_entry(f['entries']) embed.add_field(name=f['name'], value=(text[:1019] + "...") if len(text) > 1023 else text, inline=False) embed.set_footer(text=f"Use {ctx.prefix}classfeat to look up a feature if it is cut off.") await (await self._get_destination(ctx)).send(embed=embed)