Exemple #1
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)
Exemple #2
0
    async def list(self, ctx):
        embed = EmbedWithAuthor(ctx)

        has_at_least_1 = False

        user_objs = await self.personal_cls.get_ctx_map(ctx)
        user_obj_names = list(user_objs.keys())
        if user_obj_names:
            has_at_least_1 = True
            embeds.add_fields_from_long_text(
                embed, f"Your {self.obj_name_pl.title()}",
                ', '.join(sorted(user_obj_names)))

        async for subscription_doc in self.workshop_sub_meth(ctx):
            try:
                the_collection = await workshop.WorkshopCollection.from_id(
                    ctx, subscription_doc['object_id'])
            except workshop.CollectionNotFound:
                continue
            if bindings := subscription_doc[self.binding_key]:
                has_at_least_1 = True
                embed.add_field(name=the_collection.name,
                                value=', '.join(
                                    sorted(ab['name'] for ab in bindings)),
                                inline=False)
Exemple #3
0
    async def item_lookup(self, ctx, *, name):
        """Looks up an item."""
        choices = await get_item_choices(ctx, filter_by_license=False)
        item = await self._lookup_search3(ctx, {'magic-item': choices},
                                          name,
                                          query_type='item')

        embed = EmbedWithAuthor(ctx)

        embed.title = item.name
        embed.url = item.url
        embed.description = item.meta

        if item.attunement:
            if item.attunement 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.attunement}",
                                inline=False)

        text = trim_str(item.desc, 5500)
        add_fields_from_long_text(embed, "Description", text)

        if item.image:
            embed.set_thumbnail(url=item.image)

        handle_source_footer(embed, item, "Item")

        await Stats.increase_stat(ctx, "items_looked_up_life")
        await (await self._get_destination(ctx)).send(embed=embed)
Exemple #4
0
    async def _view(self, ctx, name):
        collectable = await helpers.get_collectable_named(
            ctx, name, self.personal_cls, self.workshop_cls,
            self.workshop_sub_meth, self.is_alias, self.obj_name,
            self.obj_name_pl, self.name)
        if collectable is None:
            return await ctx.send(f"No {self.obj_name} named {name} found.")
        elif isinstance(collectable, self.personal_cls):  # personal
            await send_long_code_text(
                ctx,
                outside_codeblock=f'**{name}**:',
                inside_codeblock=
                f"{ctx.prefix}{self.obj_copy_command} {collectable.name} {collectable.code}",
                codeblock_language='py')
            return
        else:  # collection
            embed = EmbedWithAuthor(ctx)
            the_collection = await collectable.load_collection(ctx)
            owner = await user_from_id(ctx, the_collection.owner)
            embed.title = f"{ctx.prefix}{name}" if self.is_alias else name
            embed.description = f"From {the_collection.name} by {owner}.\n" \
                                f"[View on Workshop]({the_collection.url})"
            embeds.add_fields_from_long_text(
                embed, "Help", collectable.docs or "No documentation.")

            if isinstance(collectable, workshop.WorkshopAlias):
                await collectable.load_subcommands(ctx)
                if collectable.subcommands:
                    subcommands = "\n".join(f"**{sc.name}** - {sc.short_docs}"
                                            for sc in collectable.subcommands)
                    embed.add_field(name="Subcommands",
                                    value=subcommands,
                                    inline=False)

            return await ctx.send(embed=embed)
Exemple #5
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)

        text = spell.description
        higher_levels = spell.higherlevels

        if len(text) > 1020:
            pieces = [text[:1020]] + [
                text[i:i + 2040] for i in range(1020, len(text), 2040)
            ]
        else:
            pieces = [text]

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

        embed_queue = [embed]
        if len(pieces) > 1:
            for piece in pieces[1:]:
                temp_embed = discord.Embed()
                temp_embed.colour = color
                temp_embed.description = piece
                embed_queue.append(temp_embed)

        if higher_levels:
            add_fields_from_long_text(embed_queue[-1], "At Higher Levels",
                                      higher_levels)

        embed_queue[-1].set_footer(text=f"Spell | {spell.source_str()}")
        if spell.homebrew:
            add_homebrew_footer(embed_queue[-1])

        if spell.image:
            embed_queue[0].set_thumbnail(url=spell.image)

        await Stats.increase_stat(ctx, "spells_looked_up_life")
        destination = await self._get_destination(ctx)
        for embed in embed_queue:
            await destination.send(embed=embed)
Exemple #6
0
 def add_action_field(title, action_source):
     action_texts = (
         f"**{action.name}**: {action.build_str(caster=caster, automation_only=not verbose)}"
         for action in action_source)
     action_text = '\n'.join(action_texts)
     embeds.add_fields_from_long_text(embed,
                                      field_name=title,
                                      text=action_text)
Exemple #7
0
    async def spell(self, ctx, *, name: str):
        """Looks up a spell."""
        spell, metadata = await select_spell_full(ctx, name, return_metadata=True,
                                                  extra_choices=compendium.nspell_names,
                                                  selectkey=self.nsrd_selectkey_obj)
        metadata['homebrew'] = spell.source == 'homebrew'
        await self.add_training_data("spell", name, spell.name, metadata=metadata, srd=spell.srd)
        if not (metadata['homebrew'] or spell.srd):
            return await self._non_srd(ctx, spell, "spell")

        embed = EmbedWithAuthor(ctx)
        color = embed.colour

        embed.title = spell.name
        embed.description = f"*{spell.get_level()} {spell.get_school().lower()}. " \
                            f"({', '.join(itertools.chain(spell.classes, spell.subclasses))})*"
        if spell.ritual:
            time = f"{spell.time} (ritual)"
        else:
            time = spell.time
        embed.add_field(name="Casting Time", value=time)
        embed.add_field(name="Range", value=spell.range)
        embed.add_field(name="Components", value=spell.components)
        embed.add_field(name="Duration", value=spell.duration)

        text = spell.description
        higher_levels = spell.higherlevels

        if len(text) > 1020:
            pieces = [text[:1020]] + [text[i:i + 2040] for i in range(1020, len(text), 2040)]
        else:
            pieces = [text]

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

        embed_queue = [embed]
        if len(pieces) > 1:
            for piece in pieces[1:]:
                temp_embed = discord.Embed()
                temp_embed.colour = color
                temp_embed.description = piece
                embed_queue.append(temp_embed)

        if higher_levels:
            add_fields_from_long_text(embed_queue[-1], "At Higher Levels", higher_levels)

        if spell.source == 'homebrew':
            embed_queue[-1].set_footer(text="Homebrew content.", icon_url=HOMEBREW_ICON)
        else:
            embed_queue[-1].set_footer(text=f"Spell | {spell.source} {spell.page}")

        if spell.image:
            embed_queue[0].set_thumbnail(url=spell.image)

        destination = await self._get_destination(ctx)
        for embed in embed_queue:
            await destination.send(embed=embed)
Exemple #8
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)
Exemple #9
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)
Exemple #10
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)
Exemple #11
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)
Exemple #12
0
    async def send_character_details(self, ctx, final_level, race=None, _class=None, subclass=None, background=None):
        loadingMessage = await ctx.channel.send("Generating character, please wait...")
        color = random.randint(0, 0xffffff)

        # Name Gen
        #    DMG name gen
        name = self.old_name_gen()
        # Stat Gen
        #    4d6d1
        #        reroll if too low/high
        stats = [roll('4d6kh3').total for _ in range(6)]
        await ctx.author.send("**Stats for {0}:** `{1}`".format(name, stats))
        # Race Gen
        #    Racial Features
        race = race or random.choice(await get_race_choices(ctx))

        embed = EmbedWithAuthor(ctx)
        embed.title = race.name
        embed.add_field(name="Speed", value=race.speed)
        embed.add_field(name="Size", value=race.size)
        for t in race.traits:
            embeds.add_fields_from_long_text(embed, t.name, t.text)
        embed.set_footer(text=f"Race | {race.source_str()}")

        embed.colour = color
        await ctx.author.send(embed=embed)

        # Class Gen
        #    Class Features

        # class
        _class = _class or random.choice(await available(ctx, compendium.classes, 'class'))
        subclass = subclass or (random.choice(subclass_choices)
                                if (subclass_choices := await available(ctx, _class.subclasses, 'class')) else None)
        embed = EmbedWithAuthor(ctx)

        embed.title = _class.name
        embed.add_field(name="Hit Points", value=_class.hit_points)

        levels = []
        for level in range(1, final_level + 1):
            level = _class.levels[level - 1]
            levels.append(', '.join([feature.name for feature in level]))

        embed.add_field(name="Starting Proficiencies", value=_class.proficiencies, inline=False)
        embed.add_field(name="Starting Equipment", value=_class.equipment, inline=False)

        level_features_str = ""
        for i, l in enumerate(levels):
            level_features_str += f"`{i + 1}` {l}\n"
        embed.description = level_features_str
        await ctx.author.send(embed=embed)

        # level table
        embed = EmbedWithAuthor(ctx)
        embed.title = f"{_class.name}, Level {final_level}"

        for resource, value in zip(_class.table.headers, _class.table.levels[final_level - 1]):
            if value != '0':
                embed.add_field(name=resource, value=value)

        embed.colour = color
        await ctx.author.send(embed=embed)

        # features
        embed_queue = [EmbedWithAuthor(ctx)]
        num_fields = 0

        def inc_fields(ftext):
            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(ftext) > 5800:
                embed_queue.append(EmbedWithAuthor(ctx))
                num_fields = 0

        def add_levels(source):
            for level in range(1, final_level + 1):
                level_features = source.levels[level - 1]
                for f in level_features:
                    for field in embeds.get_long_field_args(f.text, f.name):
                        inc_fields(field['value'])
                        embed_queue[-1].add_field(**field)

        add_levels(_class)
        if subclass:
            add_levels(subclass)

        for embed in embed_queue:
            embed.colour = color
            await ctx.author.send(embed=embed)

        # Background Gen
        #    Inventory/Trait Gen
        background = background or random.choice(await available(ctx, compendium.backgrounds, 'background'))
        embed = EmbedWithAuthor(ctx)
        embed.title = background.name
        embed.set_footer(text=f"Background | {background.source_str()}")

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

        out = f"{ctx.author.mention}\n" \
              f"{name}, {race.name} {subclass.name if subclass else ''} {_class.name} {final_level}. " \
              f"{background.name} Background.\n" \
              f"Stat Array: `{stats}`\nI have PM'd you full character details."

        await loadingMessage.edit(content=out)
Exemple #13
0
class Lookup(commands.Cog):
    """Commands to help look up items, status effects, rules, etc."""
    def __init__(self, bot):
        self.bot = bot

    # ==== rules/references ====
    @staticmethod
    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)

    @staticmethod
    async def _show_action_options(ctx, actiontype, destination):
        embed = EmbedWithAuthor(ctx)
        embed.title = actiontype['fullName']

        actions = []
        for action in actiontype['items']:
            actions.append(f"**{action['name']}** - *{action['short']}*")

        embed.description = '\n'.join(actions)
        await destination.send(embed=embed)

    @commands.command(aliases=['status'])
    async def condition(self, ctx, *, name: str = None):
        """Looks up a condition."""
        if not name:
            name = 'condition'
        else:
            name = f"Condition: {name}"
        # this is an invoke instead of an alias to make more sense in docs
        await self.rule(ctx, name=name)

    @commands.command(aliases=['reference'])
    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)

    # ==== feats ====
    @commands.command()
    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)

    # ==== races / racefeats ====
    @commands.command()
    async def racefeat(self, ctx, *, name: str):
        """Looks up a racial feature."""
        result: RaceFeature = 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)

    @commands.command()
    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)

    # ==== classes / classfeats ====
    @commands.command()
    async def classfeat(self, ctx, *, name: str):
        """Looks up a class feature."""
        result: ClassFeature = await self._lookup_search3(
            ctx, {
                'class': compendium.cfeats,
                'class-feature': compendium.optional_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)

    @commands.command(name='class')
    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.")

        result: gamedata.Class = await self._lookup_search3(
            ctx, {'class': compendium.classes}, name)

        embed = EmbedWithAuthor(ctx)
        embed.url = result.url
        if level is None:
            embed.title = result.name
            embed.add_field(name="Hit Points", value=result.hit_points)

            levels = []
            for level in range(1, 21):
                level_features = result.levels[level - 1]
                feature_names = [feature.name for feature in level_features]
                if level in result.subclass_feature_levels:
                    feature_names.append(f"{result.subclass_title} Feature")
                levels.append(', '.join(feature_names))

            level_features_str = ""
            for i, l in enumerate(levels):
                level_features_str += f"`{i + 1}` {l}\n"
            embed.description = level_features_str

            available_ocfs = await available(ctx,
                                             result.optional_features,
                                             entity_type='class-feature')
            if available_ocfs:
                ocf_names = ', '.join(ocf.name for ocf in available_ocfs)
                embed.add_field(name="Optional Class Features",
                                value=ocf_names,
                                inline=False)

            embed.add_field(name="Starting Proficiencies",
                            value=result.proficiencies,
                            inline=False)
            embed.add_field(name="Starting Equipment",
                            value=result.equipment,
                            inline=False)

            handle_source_footer(
                embed,
                result,
                f"Use {ctx.prefix}classfeat to look up a feature.",
                add_source_str=False)
        else:
            embed.title = f"{result.name}, Level {level}"

            level_features = result.levels[level - 1]

            for resource, value in zip(result.table.headers,
                                       result.table.levels[level - 1]):
                if value != '0':
                    embed.add_field(name=resource, value=value)

            for f in level_features:
                embed.add_field(name=f.name,
                                value=trim_str(f.text, 1024),
                                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)

    @commands.command()
    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)

    # ==== backgrounds ====
    @commands.command()
    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)

    # ==== monsters ====
    @commands.command()
    async def monster(self, ctx, *, name: str):
        """Looks up a monster.
        Generally requires a Game Master role to show full stat block.
        Game Master Roles: GM, DM, Game Master, Dungeon Master
        __Valid Arguments__
        -h - Shows the obfuscated stat block, even if you can see the full stat block."""
        guild_settings = await self.get_settings(ctx.guild)
        pm = guild_settings.get("pm_result", False)
        pm_dm = guild_settings.get("pm_dm", False)
        req_dm_monster = guild_settings.get("req_dm_monster", True)

        visible_roles = {'gm', 'game master', 'dm', 'dungeon master'}
        if req_dm_monster and ctx.guild:
            visible = True if visible_roles.intersection(
                set(str(r).lower() for r in ctx.author.roles)) else False
        else:
            visible = True

        # #817 -h arg for monster lookup
        if name.endswith(' -h'):
            name = name[:-3]
            visible = False

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

        embed_queue = [EmbedWithAuthor(ctx)]
        color = embed_queue[-1].colour

        embed_queue[-1].title = monster.name
        embed_queue[-1].url = monster.url

        def safe_append(title, desc):
            if len(desc) < 1024:
                embed_queue[-1].add_field(name=title, value=desc, inline=False)
            elif len(desc) < 2048:
                # noinspection PyTypeChecker
                # I'm adding an Embed to a list of Embeds, shut up.
                embed_queue.append(
                    discord.Embed(colour=color, description=desc, title=title))
            else:
                # noinspection PyTypeChecker
                embed_queue.append(discord.Embed(colour=color, title=title))
                trait_all = chunk_text(desc, max_chunk_size=2048)
                embed_queue[-1].description = trait_all[0]
                for t in trait_all[1:]:
                    # noinspection PyTypeChecker
                    embed_queue.append(
                        discord.Embed(colour=color, description=t))

        if visible:
            embed_queue[-1].description = monster.get_meta()
            if monster.traits:
                trait = '\n\n'.join(f"**{a.name}:** {a.desc}"
                                    for a in monster.traits)
                if trait:
                    safe_append("Special Abilities", trait)
            if monster.actions:
                action = '\n\n'.join(f"**{a.name}:** {a.desc}"
                                     for a in monster.actions)
                if action:
                    safe_append("Actions", action)
            if monster.bonus_actions:
                bonus_action = '\n\n'.join(f"**{a.name}:** {a.desc}"
                                           for a in monster.bonus_actions)
                if bonus_action:
                    safe_append("Bonus Actions", bonus_action)
            if monster.reactions:
                reaction = '\n\n'.join(f"**{a.name}:** {a.desc}"
                                       for a in monster.reactions)
                if reaction:
                    safe_append("Reactions", reaction)
            if monster.legactions:
                proper_name = f'The {monster.name}' if not monster.proper else monster.name
                legendary = [
                    f"{proper_name} can take {monster.la_per_round} legendary actions, choosing from "
                    f"the options below. Only one legendary action can be used at a time and only at the "
                    f"end of another creature's turn. {proper_name} regains spent legendary actions at "
                    f"the start of its turn."
                ]
                for a in monster.legactions:
                    if a.name:
                        legendary.append(f"**{a.name}:** {a.desc}")
                    else:
                        legendary.append(a.desc)
                if legendary:
                    safe_append("Legendary Actions", '\n\n'.join(legendary))
            if monster.mythic_actions:
                mythic_action = '\n\n'.join(f"**{a.name}:** {a.desc}"
                                            for a in monster.mythic_actions)
                if mythic_action:
                    safe_append("Mythic Actions", mythic_action)

        else:
            hp = monster.hp
            ac = monster.ac
            size = monster.size
            _type = monster.creature_type
            if hp < 10:
                hp = "Very Low"
            elif 10 <= hp < 50:
                hp = "Low"
            elif 50 <= hp < 100:
                hp = "Medium"
            elif 100 <= hp < 200:
                hp = "High"
            elif 200 <= hp < 400:
                hp = "Very High"
            elif 400 <= hp:
                hp = "Ludicrous"

            if ac < 6:
                ac = "Very Low"
            elif 6 <= ac < 9:
                ac = "Low"
            elif 9 <= ac < 15:
                ac = "Medium"
            elif 15 <= ac < 17:
                ac = "High"
            elif 17 <= ac < 22:
                ac = "Very High"
            elif 22 <= ac:
                ac = "Untouchable"

            languages = len(monster.languages)

            embed_queue[-1].description = f"{size} {_type}.\n" \
                                          f"**AC:** {ac}.\n**HP:** {hp}.\n**Speed:** {monster.speed}\n" \
                                          f"{monster.get_hidden_stat_array()}\n" \
                                          f"**Languages:** {languages}\n"

            if monster.traits:
                embed_queue[-1].add_field(name="Special Abilities",
                                          value=str(len(monster.traits)))

            if monster.actions:
                embed_queue[-1].add_field(name="Actions",
                                          value=str(len(monster.actions)))

            if monster.bonus_actions:
                embed_queue[-1].add_field(name="Bonus Actions",
                                          value=str(len(
                                              monster.bonus_actions)))

            if monster.reactions:
                embed_queue[-1].add_field(name="Reactions",
                                          value=str(len(monster.reactions)))

            if monster.legactions:
                embed_queue[-1].add_field(name="Legendary Actions",
                                          value=str(len(monster.legactions)))

        handle_source_footer(embed_queue[-1], monster, "Creature")

        embed_queue[0].set_thumbnail(url=monster.get_image_url())
        await Stats.increase_stat(ctx, "monsters_looked_up_life")
        for embed in embed_queue:
            if pm or (visible and pm_dm and req_dm_monster):
                await ctx.author.send(embed=embed)
            else:
                await ctx.send(embed=embed)

    @commands.command()
    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)

    @commands.command()
    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)

    # ==== spells ====
    @commands.command()
    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)

        if higher_levels:
            add_fields_from_long_text(embed_queue[-1], "At Higher Levels",
                                      higher_levels)

        handle_source_footer(embed_queue[-1], spell, "Spell")
        if spell.image:
            embed_queue[0].set_thumbnail(url=spell.image)

        await Stats.increase_stat(ctx, "spells_looked_up_life")
        destination = await self._get_destination(ctx)
        for embed in embed_queue:
            await destination.send(embed=embed)
Exemple #14
0
    async def item_lookup(self, ctx, *, name):
        """Looks up an item."""
        try:
            pack = await Pack.from_ctx(ctx)
            custom_items = pack.get_search_formatted_items()
            pack_id = pack.id
        except NoActiveBrew:
            custom_items = []
            pack_id = None
        choices = list(itertools.chain(compendium.items, custom_items))
        if ctx.guild:
            async for servpack in ctx.bot.mdb.packs.find({"server_active": str(ctx.guild.id)}):
                if servpack['_id'] != pack_id:
                    choices.extend(Pack.from_dict(servpack).get_search_formatted_items())

        # #881 - display nSRD names
        choices.extend(compendium.nitem_names)
        result, metadata = await search_and_select(ctx, choices, name, lambda e: e['name'],
                                                   selectkey=self.nsrd_selectkey, return_metadata=True)
        metadata['homebrew'] = result.get('source') == 'homebrew'
        await self.add_training_data("item", name, result['name'], metadata=metadata, srd=result['srd'])
        if not (metadata['homebrew'] or result['srd']):
            return await self._non_srd(ctx, result, "item")

        embed = EmbedWithAuthor(ctx)
        item = result

        name = item['name']
        proptext = ""

        if not item.get('source') == 'homebrew':
            damage = ''
            extras = ''
            properties = []

            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()}**: {compendium.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 compendium.itemprops:
                    proptext += f"**{a.title()}**: {compendium.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

            meta = f"*{type_and_rarity}*\n{weight_and_value}{damage_and_properties}\n{extras}"
            text = item['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']}", inline=False)

            embed.set_footer(text=f"Item | {item.get('source', 'Unknown')} {item.get('page', 'Unknown')}")
        else:
            meta = item['meta']
            text = item['desc']
            if 'image' in item:
                embed.set_thumbnail(url=item['image'])
            add_homebrew_footer(embed)

        embed.title = name
        embed.description = meta  # no need to render, has been prerendered

        if proptext:
            text = f"{text}\n{proptext}"
        if len(text) > 5500:
            text = text[:5500] + "..."

        add_fields_from_long_text(embed, "Description", text)

        await Stats.increase_stat(ctx, "items_looked_up_life")
        await (await self._get_destination(ctx)).send(embed=embed)
Exemple #15
0
async def send_action_list(destination,
                           caster,
                           attacks=None,
                           actions=None,
                           embed=None,
                           args=None):
    """
    Sends the list of actions and attacks given to the given destination.

    :type destination: discord.abc.Messageable
    :type caster: cogs5e.models.sheet.statblock.StatBlock
    :type attacks: cogs5e.models.sheet.attack.AttackList
    :type actions: cogs5e.models.sheet.action.Actions
    :type embed: discord.Embed
    :type args: Iterable[str]
    """
    if embed is None:
        embed = discord.Embed(color=caster.get_color(),
                              title=f"{caster.get_title_name()}'s Actions")
    if args is None:
        args = ()

    # arg setup
    verbose = '-v' in args
    display_attacks = 'attacks' in args
    display_actions = 'actions' in args
    display_bonus = 'bonus' in args
    display_reactions = 'reactions' in args
    display_other = 'other' in args
    is_display_filtered = any((display_attacks, display_actions, display_bonus,
                               display_reactions, display_other))
    filtered_action_type_strs = list(
        itertools.compress(('attacks', 'actions', 'bonus actions', 'reactions',
                            'other actions'),
                           (display_attacks, display_actions, display_bonus,
                            display_reactions, display_other)))

    # action display
    if attacks and (display_attacks or not is_display_filtered):
        atk_str = attacks.build_str(caster)
        embeds.add_fields_from_long_text(embed,
                                         field_name="Attacks",
                                         text=atk_str)

    # since the sheet displays the description regardless of entitlements, we do here too
    def add_action_field(title, action_source):
        action_texts = (
            f"**{action.name}**: {action.build_str(caster=caster, automation_only=not verbose)}"
            for action in action_source)
        action_text = '\n'.join(action_texts)
        embeds.add_fields_from_long_text(embed,
                                         field_name=title,
                                         text=action_text)

    if actions is not None:
        if actions.full_actions and (display_actions
                                     or not is_display_filtered):
            add_action_field("Actions", actions.full_actions)
        if actions.bonus_actions and (display_bonus
                                      or not is_display_filtered):
            add_action_field("Bonus Actions", actions.bonus_actions)
        if actions.reactions and (display_reactions
                                  or not is_display_filtered):
            add_action_field("Reactions", actions.reactions)
        if actions.other_actions and (display_other
                                      or not is_display_filtered):
            add_action_field("Other", actions.other_actions)

    # misc helper displays
    if not embed.fields:
        if is_display_filtered:
            embed.description = f"{caster.get_title_name()} has no {natural_join(filtered_action_type_strs, 'or')}."
        else:
            embed.description = f"{caster.get_title_name()} has no actions."
    elif is_display_filtered:
        embed.description = f"Only displaying {natural_join(filtered_action_type_strs, 'and')}."

    if not verbose and actions:
        embed.set_footer(
            text="Use the -v argument to view each action's full description.")

    await destination.send(embed=embed)