Esempio n. 1
0
async def handle_alias_required_licenses(ctx, err):
    embed = EmbedWithAuthor(ctx)
    if not err.has_connected_ddb:
        # was the user blocked from nSRD by a feature flag?
        ddb_user = await ctx.bot.ddb.get_ddb_user(ctx, ctx.author.id)
        if ddb_user is None:
            blocked_by_ff = False
        else:
            blocked_by_ff = not (await ctx.bot.ldclient.variation(
                "entitlements-enabled", ddb_user.to_ld_dict(), False))

        if blocked_by_ff:
            embed.title = "D&D Beyond is currently unavailable"
            embed.description = f"I was unable to communicate with D&D Beyond to confirm access to:\n" \
                                f"{', '.join(e.name for e in err.entities)}"
        else:
            embed.title = f"Connect your D&D Beyond account to use this customization!"
            embed.url = "https://www.dndbeyond.com/account"
            embed.description = \
                "This customization requires access to one or more entities that are not in the SRD.\n" \
                "Linking your account means that you'll be able to use everything you own on " \
                "D&D Beyond in Avrae for free - you can link your accounts " \
                "[here](https://www.dndbeyond.com/account)."
            embed.set_footer(
                text=
                "Already linked your account? It may take up to a minute for Avrae to recognize the "
                "link.")
    else:
        missing_source_ids = {e.source for e in err.entities}
        if len(err.entities) == 1:  # 1 entity, display entity piecemeal
            embed.title = f"Purchase {err.entities[0].name} on D&D Beyond to use this customization!"
            marketplace_url = err.entities[0].marketplace_url
        elif len(missing_source_ids
                 ) == 1:  # 1 source, recommend purchasing source
            missing_source = next(iter(missing_source_ids))
            embed.title = f"Purchase {long_source_name(missing_source)} on D&D Beyond to use this customization!"
            marketplace_url = f"https://www.dndbeyond.com/marketplace?utm_source=avrae&utm_medium=marketplacelink"
        else:  # more than 1 source
            embed.title = f"Purchase {len(missing_source_ids)} sources on D&D Beyond to use this customization!"
            marketplace_url = "https://www.dndbeyond.com/marketplace?utm_source=avrae&utm_medium=marketplacelink"

        missing = natural_join(
            [f"[{e.name}]({e.marketplace_url})" for e in err.entities], "and")
        if len(missing) > 1400:
            missing = f"{len(err.entities)} items"
        missing_sources = natural_join(
            [long_source_name(e) for e in missing_source_ids], "and")

        embed.description = \
            f"To use this customization and gain access to more integrations in Avrae, unlock **{missing}** by " \
            f"purchasing {missing_sources} on D&D Beyond.\n\n" \
            f"[Go to Marketplace]({marketplace_url})"
        embed.url = marketplace_url

        embed.set_footer(
            text=
            "Already purchased? It may take up to a minute for Avrae to recognize the "
            "purchase.")
    await ctx.send(embed=embed)
Esempio n. 2
0
 async def get_content(self):
     embed = disnake.Embed(
         title=f"Server Settings ({self.guild.name}) / Lookup Settings",
         colour=disnake.Colour.blurple(),
         description=
         "These settings affect how lookup results are displayed on this server."
     )
     if not self.settings.dm_roles:
         embed.add_field(
             name="DM Roles",
             value=f"**Dungeon Master, DM, Game Master, or GM**\n"
             f"*Any user with a role named one of these will be considered a DM. This lets them look up a "
             f"monster's full stat block if `Monsters Require DM` is enabled, skip other players' turns in "
             f"initiative, and more.*",
             inline=False)
     else:
         dm_roles = natural_join(
             [f'<@&{role_id}>' for role_id in self.settings.dm_roles], 'or')
         embed.add_field(
             name="DM Roles",
             value=f"**{dm_roles}**\n"
             f"*Any user with at least one of these roles will be considered a DM. This lets them look up a "
             f"monster's full stat block if `Monsters Require DM` is enabled, skip turns in initiative, and "
             f"more.*",
             inline=False)
     embed.add_field(
         name="Monsters Require DM",
         value=f"**{self.settings.lookup_dm_required}**\n"
         f"*If this is enabled, monster lookups will display hidden stats for any user without "
         f"a role named DM, GM, Dungeon Master, Game Master, or the DM role configured above.*",
         inline=False)
     embed.add_field(
         name="Direct Message DMs",
         value=f"**{self.settings.lookup_pm_dm}**\n"
         f"*If this is enabled, the result of monster lookups will be direct messaged to the user who looked "
         f"it up, rather than being printed to the channel, if the user is a DM.*",
         inline=False)
     embed.add_field(
         name="Direct Message Results",
         value=f"**{self.settings.lookup_pm_result}**\n"
         f"*If this is enabled, the result of all lookups will be direct messaged to the user who looked "
         f"it up, rather than being printed to the channel.*",
         inline=False)
     return {"embed": embed}
Esempio n. 3
0
 async def get_content(self):
     embed = disnake.Embed(title=f"Server Settings for {self.guild.name}",
                           colour=disnake.Colour.blurple())
     if self.settings.dm_roles:
         dm_roles = natural_join(
             [f'<@&{role_id}>' for role_id in self.settings.dm_roles], 'or')
     else:
         dm_roles = "Dungeon Master, DM, Game Master, or GM"
     embed.add_field(
         name="Lookup Settings",
         value=f"**DM Roles**: {dm_roles}\n"
         f"**Monsters Require DM**: {self.settings.lookup_dm_required}\n"
         f"**Direct Message DM**: {self.settings.lookup_pm_dm}\n"
         f"**Direct Message Results**: {self.settings.lookup_pm_result}",
         inline=False)
     embed.add_field(name="Inline Rolling Settings",
                     value=await self.get_inline_rolling_desc(),
                     inline=False)
     return {"embed": embed}
Esempio n. 4
0
async def send_action_list(ctx,
                           caster,
                           destination=None,
                           attacks=None,
                           actions=None,
                           embed=None,
                           args=None):
    """
    Sends the list of actions and attacks given to the given destination.

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

    await destination.trigger_typing()

    # long embed builder
    ep = embeds.EmbedPaginator(embed)
    fields = []

    # arg setup
    verbose = '-v' in args
    display_attacks = 'attack' in args
    display_actions = 'action' in args
    display_bonus = 'bonus' in args
    display_reactions = 'reaction' 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)))

    # helpers
    non_automated_count = 0
    non_e10s_count = 0
    e10s_map = {}
    source_names = set()

    # action display
    if attacks and (display_attacks or not is_display_filtered):
        atk_str = attacks.build_str(caster)
        fields.append({'name': 'Attacks', 'value': atk_str})

    # since the sheet displays the description regardless of entitlements, we do here too
    async def add_action_field(title, action_source):
        nonlocal non_automated_count, non_e10s_count  # eh
        action_texts = []
        for action in sorted(action_source, key=lambda a: a.name):
            has_automation = action.gamedata is not None
            has_e10s = True
            # entitlement stuff
            if has_automation:
                source_feature = action.gamedata.source_feature
                if source_feature.entitlement_entity_type not in e10s_map:
                    e10s_map[
                        source_feature.
                        entitlement_entity_type] = await ctx.bot.ddb.get_accessible_entities(
                            ctx, ctx.author.id,
                            source_feature.entitlement_entity_type)
                source_feature_type_e10s = e10s_map[
                    source_feature.entitlement_entity_type]
                has_e10s = lookuputils.can_access(source_feature,
                                                  source_feature_type_e10s)
                if not has_e10s:
                    non_e10s_count += 1
                    source_names.add(source_feature.source)

            if verbose:
                name = f"**{action.name}**" if has_automation and has_e10s else f"***{action.name}***"
                action_texts.append(
                    f"{name}: {action.build_str(caster=caster, snippet=True)}")
            elif has_automation:
                name = f"**{action.name}**" if has_e10s else f"***{action.name}***"
                action_texts.append(
                    f"**{name}**: {action.build_str(caster=caster, snippet=False)}"
                )

            # count these for extra display
            if not has_automation:
                non_automated_count += 1
        if not action_texts:
            return
        action_text = '\n'.join(action_texts)
        fields.append({'name': title, 'value': action_text})

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

    # build embed

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

    # description: entitlements help
    if not verbose and actions and non_e10s_count:
        has_ddb_link = any(v is not None for v in e10s_map.values())
        if not has_ddb_link:
            description = (
                f"{description}\nItalicized actions are for display only and cannot be run. "
                f"Connect your D&D Beyond account to unlock the full potential of these actions!"
            )
        else:
            source_names = natural_join(
                [lookuputils.long_source_name(s) for s in source_names], 'and')
            description = (
                f"{description}\nItalicized actions are for display only and cannot be run. Unlock "
                f"{source_names} on your D&D Beyond account to unlock the full potential of these actions!"
            )
    if description:
        ep.add_description(description.strip())

    # fields
    for field in fields:
        ep.add_field(**field)

    # footer
    if not verbose and actions:
        if non_automated_count:
            ep.set_footer(
                value=
                f"Use the -v argument to view each action's full description "
                f"and {non_automated_count} display-only actions.")
        else:
            ep.set_footer(
                value=
                f"Use the -v argument to view each action's full description.")
    elif verbose and (non_automated_count or non_e10s_count):
        ep.set_footer(
            value="Italicized actions are for display only and cannot be run.")

    await ep.send_to(destination)