Example #1
0
    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)
Example #2
0
    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)
Example #5
0
    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)