def __init__(self): self.cfeats = self.load_json('srd-classfeats.json', []) self.classes = self.load_json('srd-classes.json', []) self.conditions = self.load_json('conditions.json', []) self.feats = self.load_json('srd-feats.json', []) self.itemprops = self.load_json('itemprops.json', {}) self.monsters = self.load_json('srd-bestiary.json', []) self.names = self.load_json('names.json', []) self.rules = self.load_json('rules.json', []) self.spells = [Spell.from_data(r) for r in self.load_json('srd-spells.json', [])] self.backgrounds = [Background.from_data(b) for b in self.load_json('srd-backgrounds.json', [])] self.items = [i for i in self.load_json('srd-items.json', []) if i.get('type') is not '$'] self.monster_mash = [Monster.from_data(m) for m in self.monsters] self.subclasses = self.load_subclasses() srd_races = self.load_json('srd-races.json', []) self.fancyraces = [Race.from_data(r) for r in srd_races] self.rfeats = [] for race in srd_races: for entry in race['entries']: if isinstance(entry, dict) and 'name' in entry: temp = {'name': "{}: {}".format(race['name'], entry['name']), 'text': parse_data_entry(entry['entries']), 'srd': race['srd']} self.rfeats.append(temp)
async def subclass(self, ctx, name: str): """Looks up a subclass.""" guild_settings = await self.get_settings(ctx.message.server) pm = guild_settings.get("pm_result", False) srd = guild_settings.get("srd", False) destination = ctx.message.author if pm else ctx.message.channel result = await search_and_select(ctx, c.subclasses, name, lambda e: e['name'], srd=srd) if not result.get('srd') and srd: return await self.send_srd_error(ctx, result) embed = EmbedWithAuthor(ctx) embed.title = result['name'] for level_features in result['subclassFeatures']: for feature in level_features: for entry in feature['entries']: if not isinstance(entry, dict): continue if not entry.get('type') == 'entries': continue text = parse_data_entry(entry['entries']) embed.add_field( name=entry['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 subclass(self, ctx, name: str): """Looks up a subclass.""" guild_settings = await self.get_settings(ctx.guild) pm = guild_settings.get("pm_result", False) srd = guild_settings.get("srd", False) destination = ctx.author if pm else ctx.channel result, metadata = await search_and_select(ctx, c.subclasses, name, lambda e: e['name'], srd=srd, return_metadata=True) metadata['srd'] = srd await self.add_training_data("subclass", name, result['name'], metadata=metadata) if not result.get('srd') and srd: return await self.send_srd_error(ctx, result) embed = EmbedWithAuthor(ctx) embed.title = result['name'] embed.description = f"*Source: {result['source']}*" for level_features in result['subclassFeatures']: for feature in level_features: for entry in feature['entries']: if not isinstance(entry, dict): continue if not entry.get('type') == 'entries': continue text = parse_data_entry(entry['entries']) embed.add_field(name=entry['name'], value=(text[:1019] + "...") if len(text) > 1023 else text) embed.set_footer(text=f"Use {ctx.prefix}classfeat to look up a feature if it is cut off.") await destination.send(embed=embed)
def load_rfeats(self): ret = [] for race in self.srd_races: for entry in race['entries']: if isinstance(entry, dict) and 'name' in entry: temp = {'name': "{}: {}".format(race['name'], entry['name']), 'text': parse_data_entry(entry['entries']), 'srd': race['srd']} ret.append(temp) return ret
def get_traits(self): traits = [] for entry in self.entries: if isinstance(entry, dict) and 'name' in entry: temp = { 'name': entry['name'], 'text': parse_data_entry(entry['entries']) } traits.append(temp) return traits
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)
def __init__(self): with open('./res/conditions.json', 'r') as f: self.conditions = json.load(f) with open('./res/rules.json', 'r') as f: self.rules = json.load(f) with open('./res/feats.json', 'r') as f: self.feats = json.load(f) with open('./res/races.json', 'r') as f: _raw = json.load(f) self.rfeats = [] self.fancyraces = [Race.from_data(r) for r in _raw] for race in _raw: for entry in race['entries']: if isinstance(entry, dict) and 'name' in entry: temp = { 'name': "{}: {}".format(race['name'], entry['name']), 'text': parse_data_entry(entry['entries']), 'srd': race['srd'] } self.rfeats.append(temp) with open('./res/classes.json', 'r') as f: self.classes = json.load(f) with open('./res/classfeats.json') as f: self.cfeats = json.load(f) with open('./res/bestiary.json', 'r') as f: self.monsters = json.load(f) self.monster_mash = [Monster.from_data(m) for m in self.monsters] with open('./res/spells.json', 'r') as f: self.spells = [Spell.from_data(r) for r in json.load(f)] with open('./res/items.json', 'r') as f: _items = json.load(f) self.items = [i for i in _items if i.get('type') is not '$'] with open('./res/backgrounds.json', 'r') as f: self.backgrounds = [Background.from_data(b) for b in json.load(f)] self.subclasses = self.load_subclasses() with open('./res/itemprops.json', 'r') as f: self.itemprops = json.load(f) with open('./res/names.json', 'r') as f: self.names = json.load(f)
async def subclass(self, ctx, name: str): """Looks up a subclass.""" choices = compendium.subclasses + compendium.nsubclass_names result = await self._lookup_search(ctx, choices, name, lambda e: e['name'], search_type='subclass') if not result: return embed = EmbedWithAuthor(ctx) embed.title = result['name'] embed.description = f"*Source: {result['source']}*" for level_features in result['subclassFeatures']: for feature in level_features: for entry in feature['entries']: if not isinstance(entry, dict): continue if not entry.get('type') == 'entries': continue text = parse_data_entry(entry['entries']) embed.add_field(name=entry['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)
async def item_lookup(self, ctx, *, name): """Looks up an item.""" 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 self.bot.db.incr('items_looked_up_life') result = await search_and_select(ctx, c.items, name, lambda e: e['name'], srd=srd) embed = EmbedWithAuthor(ctx) item = result if not item['srd'] and srd: return await self.send_srd_error(ctx, result) name = item['name'] damage = '' extras = '' properties = [] proptext = "" if 'type' in item: type_ = ', '.join(i for i in ( [ITEM_TYPES.get(t, 'n/a') for t in item['type'].split(',')] + ["Wondrous Item" if item.get('wondrous') else '']) if i) for iType in item['type'].split(','): if iType in ('M', 'R', 'GUN'): damage = f"{item.get('dmg1', 'n/a')} {DMGTYPES.get(item.get('dmgType'), 'n/a')}" \ if 'dmg1' in item and 'dmgType' in item else '' type_ += f', {item.get("weaponCategory")}' if iType == 'S': damage = f"AC +{item.get('ac', 'n/a')}" if iType == 'LA': damage = f"AC {item.get('ac', 'n/a')} + DEX" if iType == 'MA': damage = f"AC {item.get('ac', 'n/a')} + DEX (Max 2)" if iType == 'HA': damage = f"AC {item.get('ac', 'n/a')}" if iType == 'SHP': # ships for p in ("CREW", "PASS", "CARGO", "DMGT", "SHPREP"): a = PROPS.get(p, 'n/a') proptext += f"**{a.title()}**: {c.itemprops[p]}\n" extras = f"Speed: {item.get('speed')}\nCarrying Capacity: {item.get('carryingcapacity')}\n" \ f"Crew {item.get('crew')}, AC {item.get('vehAc')}, HP {item.get('vehHp')}" if 'vehDmgThresh' in item: extras += f", Damage Threshold {item['vehDmgThresh']}" if iType == 'siege weapon': extras = f"Size: {SIZES.get(item.get('size'), 'Unknown')}\n" \ f"AC {item.get('ac')}, HP {item.get('hp')}\n" \ f"Immunities: {item.get('immune')}" else: type_ = ', '.join( i for i in ("Wondrous Item" if item.get('wondrous') else '', item.get('technology')) if i) rarity = str(item.get('rarity')).replace('None', '') if 'tier' in item: if rarity: rarity += f', {item["tier"]}' else: rarity = item['tier'] type_and_rarity = type_ + (f", {rarity}" if rarity else '') value = (item.get('value', 'n/a') + (', ' if 'weight' in item else '')) if 'value' in item else '' weight = (item.get('weight', 'n/a') + (' lb.' if item.get('weight') == '1' else ' lbs.')) \ if 'weight' in item else '' weight_and_value = value + weight for prop in item.get('property', []): if not prop: continue a = b = prop a = PROPS.get(a, 'n/a') if b in c.itemprops: proptext += f"**{a.title()}**: {c.itemprops[b]}\n" if b == 'V': a += " (" + item.get('dmg2', 'n/a') + ")" if b in ('T', 'A'): a += " (" + item.get('range', 'n/a') + "ft.)" if b == 'RLD': a += " (" + item.get('reload', 'n/a') + " shots)" properties.append(a) properties = ', '.join(properties) damage_and_properties = f"{damage} - {properties}" if properties else damage damage_and_properties = (' --- ' + damage_and_properties) if weight_and_value and damage_and_properties else \ damage_and_properties embed.title = name desc = f"*{type_and_rarity}*\n{weight_and_value}{damage_and_properties}\n{extras}" embed.description = parse_data_entry(desc) if 'reqAttune' in item: if item['reqAttune'] is True: # can be truthy, but not true embed.add_field(name="Attunement", value=f"Requires Attunement") else: embed.add_field( name="Attunement", value=f"Requires Attunement {item['reqAttune']}") text = parse_data_entry(item.get('entries', [])) if proptext: text = f"{text}\n{proptext}" if len(text) > 5500: text = text[:5500] + "..." field_name = "Description" for piece in [text[i:i + 1024] for i in range(0, len(text), 1024)]: embed.add_field(name=field_name, value=piece) field_name = "** **" embed.set_footer( text= f"Item | {item.get('source', 'Unknown')} {item.get('page', 'Unknown')}" ) if pm: await self.bot.send_message(ctx.message.author, embed=embed) else: await self.bot.say(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)
def __init__(self): with open('./res/conditions.json', 'r') as f: self.conditions = json.load(f) with open('./res/rules.json', 'r') as f: self.rules = json.load(f) with open('./res/feats.json', 'r') as f: self.feats = json.load(f) with open('./res/races.json', 'r') as f: _raw = json.load(f) self.rfeats = [] self.races = copy.deepcopy(_raw) self.fancyraces = [Race.from_data(r) for r in self.races] for race in _raw: for entry in race['entries']: if isinstance(entry, dict) and 'name' in entry: temp = { 'name': "{}: {}".format(race['name'], entry['name']), 'text': parse_data_entry(entry['entries']), 'srd': race['srd'] } self.rfeats.append(temp) with open('./res/classes.json', 'r', encoding='utf-8-sig') as f: _raw = json.load(f) self.cfeats = [] self.classes = copy.deepcopy(_raw) for _class in _raw: for level in _class.get('classFeatures', []): for feature in level: fe = { 'name': f"{_class['name']}: {feature['name']}", 'text': parse_data_entry(feature['entries']), 'srd': _class['srd'] } self.cfeats.append(fe) options = [ e for e in feature['entries'] if isinstance(e, dict) and e['type'] == 'options' ] for option in options: for opt_entry in option.get('entries', []): fe = { 'name': f"{_class['name']}: {feature['name']}: {_resolve_name(opt_entry)}", 'text': f"{_parse_prereqs(opt_entry)}{parse_data_entry(opt_entry['entries'])}", 'srd': _class['srd'] } self.cfeats.append(fe) for subclass in _class.get('subclasses', []): for level in subclass.get('subclassFeatures', []): for feature in level: options = [ f for f in feature.get('entries', []) if isinstance(f, dict) and f['type'] == 'options' ] # battlemaster only for option in options: for opt_entry in option.get('entries', []): fe = { 'name': f"{_class['name']}: {option['name']}: " f"{_resolve_name(opt_entry)}", 'text': parse_data_entry(opt_entry['entries']), 'srd': subclass.get('srd', False) } self.cfeats.append(fe) for entry in feature.get('entries', []): if not isinstance(entry, dict): continue if not entry.get('type') == 'entries': continue fe = { 'name': f"{_class['name']}: {subclass['name']}: {entry['name']}", 'text': parse_data_entry(entry['entries']), 'srd': subclass.get('srd', False) } self.cfeats.append(fe) options = [ e for e in entry['entries'] if isinstance(e, dict) and e['type'] == 'options' ] for option in options: for opt_entry in option.get('entries', []): fe = { 'name': f"{_class['name']}: {subclass['name']}: {entry['name']}: " f"{_resolve_name(opt_entry)}", 'text': parse_data_entry( opt_entry['entries']), 'srd': subclass.get('srd', False) } self.cfeats.append(fe) with open('./res/bestiary.json', 'r') as f: self.monsters = json.load(f) self.monster_mash = [Monster.from_data(m) for m in self.monsters] with open('./res/spells.json', 'r') as f: self.spells = json.load(f) with open('./res/items.json', 'r') as f: _items = json.load(f) self.items = [i for i in _items if i.get('type') is not '$'] with open('./res/auto_spells.json', 'r') as f: self.autospells = json.load(f) with open('./res/backgrounds.json', 'r') as f: self.backgrounds = json.load(f) self.subclasses = self.load_subclasses() with open('./res/itemprops.json', 'r') as f: self.itemprops = json.load(f)
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)