Beispiel #1
0
    async def classfeat(self, ctx, *, name: str):
        """Looks up a class feature."""
        result: SourcedTrait = await self._lookup_search3(
            ctx, {'class': compendium.cfeats}, name, query_type='classfeat')

        embed = EmbedWithAuthor(ctx)
        embed.title = result.name
        embed.url = result.url
        set_maybe_long_desc(embed, result.text)
        handle_source_footer(embed, result, "Class Feature")

        await (await self._get_destination(ctx)).send(embed=embed)
Beispiel #2
0
    async def _show_reference_options(ctx, destination):
        embed = EmbedWithAuthor(ctx)
        embed.title = "Rules"
        categories = ', '.join(a['type'] for a in compendium.rule_references)
        embed.description = f"Use `{ctx.prefix}{ctx.invoked_with} <category>` to look at all actions of " \
                            f"a certain type.\nCategories: {categories}"

        for actiontype in compendium.rule_references:
            embed.add_field(name=actiontype['fullName'], value=', '.join(a['name'] for a in actiontype['items']),
                            inline=False)

        await destination.send(embed=embed)
Beispiel #3
0
    async def classfeat(self, ctx, *, name: str):
        """Looks up a class feature."""
        choices = compendium.cfeats + compendium.ncfeat_names
        result = await self._lookup_search(ctx, choices, name, lambda e: e['name'], search_type='classfeat')
        if not result:
            return

        embed = EmbedWithAuthor(ctx)
        embed.title = result['name']
        set_maybe_long_desc(embed, result['text'])

        await (await self._get_destination(ctx)).send(embed=embed)
Beispiel #4
0
    async def feat(self, ctx, *, name: str):
        """Looks up a feat."""
        result: gamedata.Feat = await self._lookup_search3(ctx, {'feat': compendium.feats}, name)

        embed = EmbedWithAuthor(ctx)
        embed.title = result.name
        embed.url = result.url
        if result.prerequisite:
            embed.add_field(name="Prerequisite", value=result.prerequisite, inline=False)
        add_fields_from_long_text(embed, "Description", result.desc)
        handle_source_footer(embed, result, "Feat")
        await (await self._get_destination(ctx)).send(embed=embed)
Beispiel #5
0
    async def racefeat(self, ctx, *, name: str):
        """Looks up a racial feature."""
        result: SourcedTrait = await self._lookup_search3(ctx,
                                                          {'race': compendium.rfeats, 'subrace': compendium.subrfeats},
                                                          name, 'racefeat')

        embed = EmbedWithAuthor(ctx)
        embed.title = result.name
        embed.url = result.url
        set_maybe_long_desc(embed, result.text)
        handle_source_footer(embed, result, "Race Feature")

        await (await self._get_destination(ctx)).send(embed=embed)
Beispiel #6
0
    async def token(self, ctx, name=None, *args):
        """
        Shows a monster or your character's token.
        __Valid Arguments__
        -border <plain|none (player token only)> - Overrides the token border.
        """
        if name is None or name.startswith('-'):
            token_cmd = self.bot.get_command('playertoken')
            if token_cmd is None:
                return await ctx.send("Error: SheetManager cog not loaded.")
            if name:
                args = (name, *args)
            return await ctx.invoke(token_cmd, *args)

        # select monster
        choices = await get_monster_choices(ctx, filter_by_license=False)
        monster = await self._lookup_search3(ctx, {'monster': choices}, name)
        await Stats.increase_stat(ctx, "monsters_looked_up_life")

        # select border
        ddb_user = await self.bot.ddb.get_ddb_user(ctx, ctx.author.id)
        is_subscriber = ddb_user and ddb_user.is_subscriber
        token_args = argparse(args)

        if monster.homebrew:
            # homebrew: generate token
            if not monster.get_image_url():
                return await ctx.send("This monster has no image.")
            try:
                image = await img.generate_token(monster.get_image_url(),
                                                 is_subscriber, token_args)
            except Exception as e:
                return await ctx.send(f"Error generating token: {e}")
        else:
            # official monsters
            token_url = monster.get_token_url(is_subscriber)
            if token_args.last('border') == 'plain':
                token_url = monster.get_token_url(False)

            if not token_url:
                return await ctx.send("This monster has no image.")

            image = await img.fetch_monster_image(token_url)

        embed = EmbedWithAuthor(ctx)
        embed.title = monster.name
        embed.description = f"{monster.size} monster."

        file = discord.File(image, filename="image.png")
        embed.set_image(url="attachment://image.png")
        await ctx.send(embed=embed, file=file)
Beispiel #7
0
    async def background(self, ctx, *, name: str):
        """Looks up a background."""
        result: gamedata.Background = await self._lookup_search3(ctx, {'background': compendium.backgrounds}, name)

        embed = EmbedWithAuthor(ctx)
        embed.url = result.url
        embed.title = result.name
        handle_source_footer(embed, result, "Background")

        for trait in result.traits:
            text = trim_str(trait.text, 1024)
            embed.add_field(name=trait.name, value=text, inline=False)

        await (await self._get_destination(ctx)).send(embed=embed)
Beispiel #8
0
    async def randname(self, ctx, race=None, option=None):
        """Generates a random name, optionally from a given race."""
        if race is None:
            return await ctx.send(f"Your random name: {self.old_name_gen()}")

        embed = EmbedWithAuthor(ctx)
        race_names = await search_and_select(ctx, compendium.names, race, lambda e: e['race'])
        if option is None:
            table = await get_selection(ctx, [(t['name'], t) for t in race_names['tables']])
        else:
            table = await search_and_select(ctx, race_names['tables'], option, lambda e: e['name'])
        embed.title = f"{table['name']} {race_names['race']} Name"
        embed.description = random.choice(table['choices'])
        await ctx.send(embed=embed)
Beispiel #9
0
    async def _non_srd(self, ctx, result, search_type=None):
        if search_type is not None:
            await self.bot.mdb.analytics_nsrd_lookup.update_one({"type": search_type, "name": result.name},
                                                                {"$inc": {"num_lookups": 1}},
                                                                upsert=True)

        embed = EmbedWithAuthor(ctx)
        embed.title = f"{result.name} is not available in the SRD!"
        embed.description = f"Unfortunately, {result.name} is not available in the SRD (what Wizards of the Coast " \
                            f"offers for free). You can see everything that is available in the SRD [here](" \
                            f"http://dnd.wizards.com/articles/features/systems-reference-document-srd).\n\n" \
                            f"In the near future, you will be able to connect your D&D Beyond account to Avrae to " \
                            f"view the non-SRD content you own on D&D Beyond; stay tuned!"
        await ctx.send(embed=embed)
Beispiel #10
0
    async def condition(self, ctx, *, name: str):
        """Looks up a condition."""
        guild_settings = await self.get_settings(ctx.message.server)
        pm = guild_settings.get("pm_result", False)

        destination = ctx.message.author if pm else ctx.message.channel

        result = await search_and_select(ctx, c.conditions, name,
                                         lambda e: e['name'])

        embed = EmbedWithAuthor(ctx)
        embed.title = result['name']
        embed.description = result['desc']

        await self.bot.send_message(destination, embed=embed)
Beispiel #11
0
    async def race(self, ctx, *, name: str):
        """Looks up a race."""
        result: gamedata.Race = await self._lookup_search3(ctx,
                                                           {'race': compendium.races, 'subrace': compendium.subraces},
                                                           name, 'race')

        embed = EmbedWithAuthor(ctx)
        embed.title = result.name
        embed.url = result.url
        embed.add_field(name="Speed", value=result.speed)
        embed.add_field(name="Size", value=result.size)
        for t in result.traits:
            add_fields_from_long_text(embed, t.name, t.text)
        handle_source_footer(embed, result, "Race")
        await (await self._get_destination(ctx)).send(embed=embed)
Beispiel #12
0
    async def monimage(self, ctx, *, name):
        """Shows a monster's image."""
        choices = await get_monster_choices(ctx, filter_by_license=False)
        monster = await self._lookup_search3(ctx, {'monster': choices}, name)
        await Stats.increase_stat(ctx, "monsters_looked_up_life")

        url = monster.get_image_url()
        embed = EmbedWithAuthor(ctx)
        embed.title = monster.name
        embed.description = f"{monster.size} monster."

        if not url:
            return await ctx.channel.send("This monster has no image.")

        embed.set_image(url=url)
        await ctx.send(embed=embed)
Beispiel #13
0
    async def condition(self, ctx, *, name: str):
        """Looks up a condition."""
        guild_settings = await self.get_settings(ctx.guild)
        pm = guild_settings.get("pm_result", False)

        destination = ctx.author if pm else ctx.channel

        result, metadata = await search_and_select(ctx, c.conditions, name, lambda e: e['name'], return_metadata=True)

        await self.add_training_data("condition", name, result['name'], metadata=metadata)

        embed = EmbedWithAuthor(ctx)
        embed.title = result['name']
        embed.description = result['desc']

        await destination.send(embed=embed)
Beispiel #14
0
    async def classfeat(self, ctx, *, name: str):
        """Looks up a class feature."""
        guild_settings = await self.get_settings(ctx.guild)
        pm = guild_settings.get("pm_result", False)
        destination = ctx.author if pm else ctx.channel

        result, metadata = await search_and_select(ctx, c.cfeats, name, lambda e: e['name'], return_metadata=True)
        await self.add_training_data("classfeat", name, result['name'], metadata=metadata)

        embed = EmbedWithAuthor(ctx)
        embed.title = result['name']
        desc = result['text']
        desc = [desc[i:i + 1024] for i in range(0, len(desc), 1024)]
        embed.description = ''.join(desc[:2])
        for piece in desc[2:]:
            embed.add_field(name="** **", value=piece)

        await destination.send(embed=embed)
Beispiel #15
0
    async def race(self, ctx, *, name: str):
        """Looks up a race."""
        choices = compendium.fancyraces + compendium.nrace_names
        result = await self._lookup_search(ctx, choices, name, lambda e: e.name, search_type='race', is_obj=True)
        if not result:
            return

        embed = EmbedWithAuthor(ctx)
        embed.title = result.name
        embed.description = f"Source: {result.source}"
        embed.add_field(name="Speed", value=result.get_speed_str())
        embed.add_field(name="Size", value=result.size)
        if result.ability:
            embed.add_field(name="Ability Bonuses", value=result.get_asi_str())
        for t in result.get_traits():
            add_fields_from_long_text(embed, t['name'], t['text'])

        await (await self._get_destination(ctx)).send(embed=embed)
Beispiel #16
0
    async def feat(self, ctx, *, name: str):
        """Looks up a feat."""
        choices = compendium.feats + compendium.nfeat_names
        result = await self._lookup_search(ctx, choices, name, lambda e: e['name'], search_type='feat')
        if not result:
            return

        embed = EmbedWithAuthor(ctx)
        embed.title = result['name']
        if result['prerequisite']:
            embed.add_field(name="Prerequisite", value=result['prerequisite'], inline=False)
        if result['ability']:
            embed.add_field(name="Ability Improvement",
                            value=f"Increase your {result['ability']} score by 1, up to a maximum of 20.", inline=False)

        add_fields_from_long_text(embed, "Description", result['desc'])
        embed.set_footer(text=f"Feat | {result['source']} {result['page']}")
        await (await self._get_destination(ctx)).send(embed=embed)
Beispiel #17
0
    async def subclass(self, ctx, *, name: str):
        """Looks up a subclass."""
        result: gamedata.Subclass = await self._lookup_search3(ctx, {'class': compendium.subclasses}, name,
                                                               query_type='subclass')

        embed = EmbedWithAuthor(ctx)
        embed.url = result.url
        embed.title = result.name
        embed.description = f"*Source: {result.source_str()}*"

        for level in result.levels:
            for feature in level:
                text = trim_str(feature.text, 1024)
                embed.add_field(name=feature.name, value=text, inline=False)

        handle_source_footer(embed, result, f"Use {ctx.prefix}classfeat to look up a feature if it is cut off.",
                             add_source_str=False)

        await (await self._get_destination(ctx)).send(embed=embed)
    async def token(self, ctx, *, name=None):
        """Shows a token for a monster or player. May not support all monsters."""

        if name is None:
            token_cmd = self.bot.get_command('playertoken')
            if token_cmd is None:
                return await ctx.send("Error: SheetManager cog not loaded.")
            return await ctx.invoke(token_cmd)

        monster, metadata = await select_monster_full(ctx,
                                                      name,
                                                      return_metadata=True)

        metadata['homebrew'] = monster.source == 'homebrew'
        await self.add_training_data("monster",
                                     name,
                                     monster.name,
                                     metadata=metadata)

        url = monster.get_image_url()

        if not monster.source == 'homebrew':
            embed = EmbedWithAuthor(ctx)
            embed.title = monster.name
            embed.description = f"{monster.size} monster."
            embed.set_image(url=url)
            embed.set_footer(text="This command may not support all monsters.")

            await ctx.send(embed=embed)
        else:
            if not url:
                return await ctx.channel.send("This monster has no image.")

            try:
                processed = await generate_token(url)
            except Exception as e:
                return await ctx.channel.send(f"Error generating token: {e}")

            file = discord.File(processed, filename="image.png")
            await ctx.channel.send(
                "I generated this token for you! If it seems  wrong, you can make your own at "
                "<http://rolladvantage.com/tokenstamp/>!",
                file=file)
Beispiel #19
0
    async def rule(self, ctx, *, name: str):
        """Looks up a rule."""
        guild_settings = await self.get_settings(ctx.message.server)
        pm = guild_settings.get("pm_result", False)

        destination = ctx.message.author if pm else ctx.message.channel

        result = await search_and_select(ctx, c.rules, name,
                                         lambda e: e['name'])

        embed = EmbedWithAuthor(ctx)
        embed.title = result['name']
        desc = result['desc']
        desc = [desc[i:i + 1024] for i in range(0, len(desc), 1024)]
        embed.description = ''.join(desc[:2])
        for piece in desc[2:]:
            embed.add_field(name="** **", value=piece)

        await self.bot.send_message(destination, embed=embed)
Beispiel #20
0
    async def spell(self, ctx, *, name: str):
        """Looks up a spell."""
        choices = await get_spell_choices(ctx, filter_by_license=False)
        spell = await self._lookup_search3(ctx, {'spell': choices}, name)

        embed = EmbedWithAuthor(ctx)
        embed.url = spell.url
        color = embed.colour

        embed.title = spell.name
        school_level = f"{spell.get_level()} {spell.get_school().lower()}" if spell.level > 0 \
            else f"{spell.get_school().lower()} cantrip"
        embed.description = f"*{school_level}. " \
                            f"({', '.join(itertools.chain(spell.classes, spell.subclasses))})*"
        if spell.ritual:
            time = f"{spell.time} (ritual)"
        else:
            time = spell.time

        meta = f"**Casting Time**: {time}\n" \
               f"**Range**: {spell.range}\n" \
               f"**Components**: {spell.components}\n" \
               f"**Duration**: {spell.duration}"
        embed.add_field(name="Meta", value=meta)

        higher_levels = spell.higherlevels
        pieces = chunk_text(spell.description)

        embed.add_field(name="Description", value=pieces[0], inline=False)

        embed_queue = [embed]
        if len(pieces) > 1:
            for i, piece in enumerate(pieces[1::2]):
                temp_embed = discord.Embed()
                temp_embed.colour = color
                if (next_idx := (i + 1) * 2) < len(
                        pieces
                ):  # this is chunked into 1024 pieces, and descs can handle 2
                    temp_embed.description = piece + pieces[next_idx]
                else:
                    temp_embed.description = piece
                embed_queue.append(temp_embed)
Beispiel #21
0
    async def background(self, ctx, *, name: str):
        """Looks up a background."""
        choices = compendium.backgrounds + compendium.nbackground_names
        result = await self._lookup_search(ctx, choices, name, lambda e: e.name, search_type='background', is_obj=True)
        if not result:
            return

        embed = EmbedWithAuthor(ctx)
        embed.title = result.name
        embed.set_footer(text=f"Background | {result.source} {result.page}")

        ignored_fields = ['suggested characteristics', 'personality trait', 'ideal', 'bond', 'flaw', 'specialty',
                          'harrowing event']
        for trait in result.traits:
            if trait['name'].lower() in ignored_fields: continue
            text = trait['text']
            text = textwrap.shorten(text, width=1020, placeholder="...")
            embed.add_field(name=trait['name'], value=text, inline=False)

        await (await self._get_destination(ctx)).send(embed=embed)
    async def patron_roscoe(self, ctx):
        embed = EmbedWithAuthor(ctx)
        embed.title = "Roscoe's Feast"
        embed.description = "*6th level conjuration. (Cleric, Druid)*"
        embed.add_field(name="Casting Time", value="10 minutes")
        embed.add_field(name="Range", value="30 feet")
        embed.add_field(name="Components", value="V, S, M (A gem encrusted bowl worth 1000 gold pieces)")
        embed.add_field(name="Duration", value="Instantaneous")
        embed.add_field(
            name="Description",
            value="You call forth the Avatar of Roscoe who brings with him a magnificent feast of chicken and waffles.\n"
                  "The feast takes 1 hour to consume and disappears at the end of that time, and the beneficial effects "
                  "don't set in until this hour is over. Up to twelve creatures can partake of the feast.\n"
                  "A creature that partakes of the feast gains several benefits. "
                  "The creature is cured of all diseases and poison, becomes immune to poison and being frightened, and "
                  "makes all Wisdom saving throws with advantage. Its hit point maximum also increases by 2d10, and it "
                  "gains the same number of hit points. These benefits last for 24 hours.")

        embed.set_footer(text=f"Spell | Thanks Roscoe!")
        await ctx.send(embed=embed)
Beispiel #23
0
    async def feat(self, ctx, *, name: str):
        """Looks up a feat."""
        guild_settings = await self.get_settings(ctx.guild)
        pm = guild_settings.get("pm_result", False)
        destination = ctx.author if pm else ctx.channel

        result, metadata = await search_and_select(ctx, c.feats, name, lambda e: e['name'], return_metadata=True)
        await self.add_training_data("feat", name, result['name'], metadata=metadata)

        embed = EmbedWithAuthor(ctx)
        embed.title = result['name']
        if result['prerequisite']:
            embed.add_field(name="Prerequisite", value=result['prerequisite'])
        if result['ability']:
            embed.add_field(name="Ability Improvement",
                            value=f"Increase your {result['ability']} score by 1, up to a maximum of 20.")
        _name = 'Description'
        for piece in [result['desc'][i:i + 1024] for i in range(0, len(result['desc']), 1024)]:
            embed.add_field(name=_name, value=piece)
            _name = '** **'
        embed.set_footer(text=f"Feat | {result['source']} {result['page']}")
        await destination.send(embed=embed)
Beispiel #24
0
    async def background(self, ctx, *, name: str):
        """Looks up a background."""
        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

        result = await search_and_select(ctx,
                                         c.backgrounds,
                                         name,
                                         lambda e: e['name'],
                                         srd=srd)

        if not result['srd'] and srd:
            return await self.send_srd_error(ctx, result)

        embed = EmbedWithAuthor(ctx)
        embed.title = result['name']
        embed.description = f"*Source: {result.get('source', 'Unknown')}*"

        ignored_fields = [
            'suggested characteristics', 'personality trait', 'ideal', 'bond',
            'flaw', 'specialty', 'harrowing event'
        ]
        for trait in result['trait']:
            if trait['name'].lower() in ignored_fields: continue
            text = '\n'.join(t for t in trait['text'] if t)
            text = textwrap.shorten(text, width=1020, placeholder="...")
            embed.add_field(name=trait['name'], value=text)

        # do stuff here
        if pm:
            await self.bot.send_message(ctx.message.author, embed=embed)
        else:
            await self.bot.say(embed=embed)
Beispiel #25
0
    async def subclass(self, ctx, name: str):
        """Looks up a subclass."""
        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.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)
Beispiel #26
0
    async def token(self, ctx, *, name=None):
        """Shows a monster's image."""

        if name is None:
            token_cmd = self.bot.get_command('playertoken')
            if token_cmd is None:
                return await ctx.send("Error: SheetManager cog not loaded.")
            return await ctx.invoke(token_cmd)

        choices = await get_monster_choices(ctx, filter_by_license=False)
        monster = await self._lookup_search3(ctx, {'monster': choices}, name)
        await Stats.increase_stat(ctx, "monsters_looked_up_life")

        url = monster.get_image_url()
        embed = EmbedWithAuthor(ctx)
        embed.title = monster.name
        embed.description = f"{monster.size} monster."

        if not url:
            return await ctx.channel.send("This monster has no image.")

        embed.set_image(url=url)
        await ctx.send(embed=embed)
Beispiel #27
0
    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 subclass(self, ctx, name: str):
        """Looks up a subclass."""
        guild_settings = await self.get_settings(ctx.guild)
        pm = guild_settings.get("pm_result", False)
        destination = ctx.author if pm else ctx.channel

        result, metadata = await search_and_select(ctx,
                                                   c.subclasses,
                                                   name,
                                                   lambda e: e['name'],
                                                   return_metadata=True)
        await self.add_training_data("subclass",
                                     name,
                                     result['name'],
                                     metadata=metadata)

        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)
Beispiel #29
0
    async def rule(self, ctx, *, name: str = None):
        """Looks up a rule."""
        destination = await self._get_destination(ctx)

        if name is None:
            return await self._show_reference_options(ctx, destination)

        options = []
        for actiontype in compendium.rule_references:
            if name == actiontype['type']:
                return await self._show_action_options(ctx, actiontype, destination)
            else:
                options.extend(actiontype['items'])

        result, metadata = await search_and_select(ctx, options, name, lambda e: e['fullName'], return_metadata=True)
        await self.add_training_data("reference", name, result['fullName'], metadata=metadata)

        embed = EmbedWithAuthor(ctx)
        embed.title = result['fullName']
        embed.description = f"*{result['short']}*"
        add_fields_from_long_text(embed, "Description", result['desc'])
        embed.set_footer(text=f"Rule | {result['source']}")

        await destination.send(embed=embed)
Beispiel #30
0
    async def race(self, ctx, *, name: str):
        """Looks up a race."""
        guild_settings = await self.get_settings(ctx.guild)
        pm = guild_settings.get("pm_result", False)
        destination = ctx.author if pm else ctx.channel

        result, metadata = await search_and_select(ctx, c.fancyraces, name, lambda e: e.name, return_metadata=True)
        await self.add_training_data("race", name, result.name, metadata=metadata)

        embed = EmbedWithAuthor(ctx)
        embed.title = result.name
        embed.description = f"Source: {result.source}"
        embed.add_field(name="Speed", value=result.get_speed_str())
        embed.add_field(name="Size", value=result.size)
        if result.ability:
            embed.add_field(name="Ability Bonuses", value=result.get_asi_str())
        for t in result.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)

        await destination.send(embed=embed)