コード例 #1
0
ファイル: dice.py プロジェクト: chadyehia/avrae
    async def monster_save(self, ctx, monster_name, save_stat, *args):
        """Rolls a save for a monster.
        __Valid Arguments__
        adv/dis
        -b [conditional bonus]
        -phrase [flavor text]
        -title [title] *note: [name] and [cname] will be replaced automatically*
        -thumb [thumbnail URL]
        -dc [dc]
        -rr [iterations]
        -h (hides name and image of monster)"""

        monster: Monster = await select_monster_full(ctx, monster_name)

        embed = discord.Embed()
        embed.colour = random.randint(0, 0xffffff)

        args = await helpers.parse_snippets(args, ctx)
        args = argparse(args)

        if not args.last('h', type_=bool):
            embed.set_thumbnail(url=monster.get_image_url())

        checkutils.run_save(save_stat, monster, args, embed)

        if monster.source == 'homebrew':
            embeds.add_homebrew_footer(embed)

        await ctx.send(embed=embed)
        await try_delete(ctx.message)
コード例 #2
0
ファイル: lookup.py プロジェクト: veilheim/avrae
    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)

        embed.set_footer(text=f"Item | {item.source_str()}")
        if item.homebrew:
            add_homebrew_footer(embed)

        await Stats.increase_stat(ctx, "items_looked_up_life")
        await (await self._get_destination(ctx)).send(embed=embed)
コード例 #3
0
ファイル: lookup.py プロジェクト: veilheim/avrae
    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)
コード例 #4
0
ファイル: dice.py プロジェクト: coderpillr/avrae
    async def monster_atk(self, ctx, monster_name, atk_name=None, *, args=''):
        """Rolls a monster's attack.
        __Valid Arguments__
        -t "<target>" - Sets targets for the attack. You can pass as many as needed. Will target combatants if channel is in initiative.
        -t "<target>|<args>" - Sets a target, and also allows for specific args to apply to them. (e.g, -t "OR1|hit" to force the attack against OR1 to hit)

        adv/dis
        -ac [target ac]
        -b [to hit bonus]
        -d [damage bonus]
        -d# [applies damage to the first # hits]
        -rr [times to reroll]
        -t [target]
        -phrase [flavor text]
        crit (automatically crit)
        -h (hides monster name, image, and rolled values)
        """
        if atk_name is None or atk_name == 'list':
            return await ctx.invoke(self.monster_atk_list, monster_name)

        await try_delete(ctx.message)

        monster = await select_monster_full(ctx, monster_name)
        attacks = monster.attacks
        monster_name = monster.get_title_name()

        attack = await search_and_select(ctx, attacks, atk_name, lambda a: a.name)
        args = await helpers.parse_snippets(args, ctx)
        args = argparse(args)
        if not args.last('h', type_=bool):
            name = monster_name
            image = args.last('thumb') or monster.get_image_url()
        else:
            name = "An unknown creature"
            image = None

        embed = discord.Embed()
        if args.last('title') is not None:
            embed.title = args.last('title') \
                .replace('[name]', name) \
                .replace('[aname]', attack.name)
        else:
            embed.title = '{} attacks with {}!'.format(name, a_or_an(attack.name))

        if image:
            embed.set_thumbnail(url=image)

        caster, targets, combat = await targetutils.maybe_combat(ctx, monster, args)
        await attack.automation.run(ctx, embed, caster, targets, args, combat=combat, title=embed.title)
        if combat:
            await combat.final()

        _fields = args.get('f')
        embeds.add_fields_from_args(embed, _fields)
        embed.colour = random.randint(0, 0xffffff)

        if monster.source == 'homebrew':
            embeds.add_homebrew_footer(embed)

        await ctx.send(embed=embed)
コード例 #5
0
ファイル: dice.py プロジェクト: chadyehia/avrae
    async def monster_cast(self, ctx, monster_name, spell_name, *args):
        """
        Casts a spell as a monster.
        __Valid Arguments__
        -i - Ignores Spellbook restrictions, for demonstrations or rituals.
        -l <level> - Specifies the level to cast the spell at.
        noconc - Ignores concentration requirements.
        -h - Hides rolled values.
        **__Save Spells__**
        -dc <Save DC> - Overrides the spell save DC.
        -save <Save type> - Overrides the spell save type.
        -d <damage> - Adds additional damage.
        pass - Target automatically succeeds save.
        fail - Target automatically fails save.
        adv/dis - Target makes save at advantage/disadvantage.
        **__Attack Spells__**
        See `!a`.
        **__All Spells__**
        -phrase <phrase> - adds flavor text.
        -title <title> - changes the title of the cast. Replaces [sname] with spell name.
        -thumb <url> - adds an image to the cast.
        -dur <duration> - changes the duration of any effect applied by the spell.
        -mod <spellcasting mod> - sets the value of the spellcasting ability modifier.
        int/wis/cha - different skill base for DC/AB (will not account for extra bonuses)
        """
        await try_delete(ctx.message)
        monster: Monster = await select_monster_full(ctx, monster_name)

        args = await helpers.parse_snippets(args, ctx)
        args = argparse(args)

        if not args.last('i', type_=bool):
            spell = await select_spell_full(
                ctx,
                spell_name,
                list_filter=lambda s: s.name in monster.spellbook)
        else:
            spell = await select_spell_full(ctx, spell_name)

        caster, targets, combat = await targetutils.maybe_combat(
            ctx, monster, args)
        result = await spell.cast(ctx, caster, targets, args, combat=combat)

        # embed display
        embed = result['embed']
        embed.colour = random.randint(0, 0xffffff)

        if not args.last('h', type_=bool) and 'thumb' not in args:
            embed.set_thumbnail(url=monster.get_image_url())

        if monster.source == 'homebrew':
            embeds.add_homebrew_footer(embed)

        # save changes: combat state
        if combat:
            await combat.final()
        await ctx.send(embed=embed)
コード例 #6
0
ファイル: dice.py プロジェクト: chadyehia/avrae
    async def monster_atk(self, ctx, monster_name, atk_name=None, *, args=''):
        """Rolls a monster's attack.
        __Valid Arguments__
        -t "<target>" - Sets targets for the attack. You can pass as many as needed. Will target combatants if channel is in initiative.
        -t "<target>|<args>" - Sets a target, and also allows for specific args to apply to them. (e.g, -t "OR1|hit" to force the attack against OR1 to hit)

        adv/dis
        -ac [target ac]
        -b [to hit bonus]
        -d [damage bonus]
        -d# [applies damage to the first # hits]
        -rr [times to reroll]
        -t [target]
        -phrase [flavor text]
        crit (automatically crit)
        -h (hides monster name, image, and rolled values)
        """
        if atk_name is None or atk_name == 'list':
            return await self.monster_atk_list(ctx, monster_name)

        await try_delete(ctx.message)

        monster = await select_monster_full(ctx, monster_name)
        attacks = monster.attacks

        attack = await search_and_select(ctx, attacks, atk_name,
                                         lambda a: a.name)
        args = await helpers.parse_snippets(args, ctx)
        args = argparse(args)

        embed = discord.Embed()
        if not args.last('h', type_=bool):
            embed.set_thumbnail(url=monster.get_image_url())

        caster, targets, combat = await targetutils.maybe_combat(
            ctx, monster, args)
        await attackutils.run_attack(ctx, embed, args, caster, attack, targets,
                                     combat)

        embed.colour = random.randint(0, 0xffffff)
        if monster.source == 'homebrew':
            embeds.add_homebrew_footer(embed)

        await ctx.send(embed=embed)
コード例 #7
0
    async def monster_check(self, ctx, monster_name, check, *args):
        """Rolls a check for a monster.
        __Valid Arguments__
        *adv/dis*
        *-b [conditional bonus]*
        -phrase [flavor text]
        -title [title] *note: [name] and [cname] will be replaced automatically*
        -dc [dc]
        -rr [iterations]
        str/dex/con/int/wis/cha (different skill base; e.g. Strength (Intimidation))
        -h (hides name and image of monster)

        An italicized argument means the argument supports ephemeral arguments - e.g. `-b1` applies a bonus to one check.
        """

        monster: Monster = await select_monster_full(ctx, monster_name)

        skill_key = await search_and_select(ctx, SKILL_NAMES, check,
                                            lambda s: s)

        embed = discord.Embed()
        embed.colour = random.randint(0, 0xffffff)

        args = await helpers.parse_snippets(args, ctx)
        args = argparse(args)

        checkutils.run_check(skill_key, monster, args, embed)

        if args.last('image') is not None:
            embed.set_thumbnail(url=args.last('image'))
        elif not args.last('h', type_=bool):
            embed.set_thumbnail(url=monster.get_image_url())

        if monster.source == 'homebrew':
            embeds.add_homebrew_footer(embed)

        await ctx.send(embed=embed)
        await try_delete(ctx.message)
コード例 #8
0
ファイル: spell.py プロジェクト: thomasmckay/avrae
    async def cast(self, ctx, caster, targets, args, combat=None):
        """
        Casts this spell.

        :param ctx: The context of the casting.
        :param caster: The caster of this spell.
        :type caster: :class:`~cogs5e.models.sheet.statblock.StatBlock`
        :param targets: A list of targets
        :type targets: list of :class:`~cogs5e.models.sheet.statblock.StatBlock`
        :param args: Args
        :type args: :class:`~utils.argparser.ParsedArguments`
        :param combat: The combat the spell was cast in, if applicable.
        :return: {embed: Embed}
        """

        # generic args
        l = args.last('l', self.level, int)
        i = args.last('i', type_=bool)
        title = args.last('title')

        # meta checks
        if not self.level <= l <= 9:
            raise SpellException("Invalid spell level.")

        # caster spell-specific overrides
        dc_override = None
        ab_override = None
        spell_override = None
        spellbook_spell = caster.spellbook.get_spell(self)
        if spellbook_spell is not None:
            dc_override = spellbook_spell.dc
            ab_override = spellbook_spell.sab
            spell_override = spellbook_spell.mod

        if not i:
            # if I'm a warlock, and I didn't have any slots of this level anyway (#655)
            # automatically scale up to the next level s.t. our slots are not 0
            if l > 0 \
                    and l == self.level \
                    and not caster.spellbook.get_max_slots(l) \
                    and not caster.spellbook.can_cast(self, l):
                l = next((sl for sl in range(l, 6)
                          if caster.spellbook.get_max_slots(sl)),
                         l)  # only scale up to l5
                args['l'] = l

            # can I cast this spell?
            if not caster.spellbook.can_cast(self, l):
                embed = EmbedWithAuthor(ctx)
                embed.title = "Cannot cast spell!"
                if not caster.spellbook.get_slots(l):
                    # out of spell slots
                    err = f"You don't have enough level {l} slots left! Use `-l <level>` to cast at a different level, " \
                          f"`{ctx.prefix}g lr` to take a long rest, or `-i` to ignore spell slots!"
                elif self.name not in caster.spellbook:
                    # don't know spell
                    err = f"You don't know this spell! Use `{ctx.prefix}sb add {self.name}` to add it to your spellbook, " \
                          f"or pass `-i` to ignore restrictions."
                else:
                    # ?
                    err = "Not enough spell slots remaining, or spell not in known spell list!\n" \
                          f"Use `{ctx.prefix}game longrest` to restore all spell slots if this is a character, " \
                          f"or pass `-i` to ignore restrictions."
                embed.description = err
                if l > 0:
                    embed.add_field(name="Spell Slots",
                                    value=caster.spellbook.remaining_casts_of(
                                        self, l))
                return {"embed": embed}

            # use resource
            caster.spellbook.cast(self, l)

        # character setup
        character = None
        if isinstance(caster, PlayerCombatant):
            character = caster.character
        elif isinstance(caster, Character):
            character = caster

        # base stat stuff
        mod_arg = args.last("mod", type_=int)
        stat_override = ''
        if mod_arg is not None:
            mod = mod_arg
            if character:
                prof_bonus = character.stats.prof_bonus
            else:
                prof_bonus = 0
            dc_override = 8 + mod + prof_bonus
            ab_override = mod + prof_bonus
            spell_override = mod
        elif character and any(
                args.last(s, type_=bool) for s in STAT_ABBREVIATIONS):
            base = next(s for s in STAT_ABBREVIATIONS
                        if args.last(s, type_=bool))
            mod = character.stats.get_mod(base)
            dc_override = 8 + mod + character.stats.prof_bonus
            ab_override = mod + character.stats.prof_bonus
            spell_override = mod
            stat_override = f" with {verbose_stat(base)}"

        if spell_override is None and (caster.spellbook.sab is None
                                       or caster.spellbook.dc is None):
            raise SpellException(
                "This caster does not have the ability to cast spells.")

        # begin setup
        embed = discord.Embed()
        if title:
            embed.title = title.replace('[sname]', self.name)
        else:
            embed.title = f"{caster.get_title_name()} casts {self.name}{stat_override}!"
        if targets is None:
            targets = [None]

        # concentration
        noconc = args.last("noconc", type_=bool)
        conc_conflict = None
        conc_effect = None
        if all(
            (self.concentration, isinstance(caster,
                                            Combatant), combat, not noconc)):
            duration = args.last('dur', self.get_combat_duration(), int)
            conc_effect = initiative.Effect.new(combat, caster, self.name,
                                                duration, "", True)
            effect_result = caster.add_effect(conc_effect)
            conc_conflict = effect_result['conc_conflict']

        if self.automation and self.automation.effects:
            title = f"{caster.name} cast {self.name}!"
            await self.automation.run(ctx,
                                      embed,
                                      caster,
                                      targets,
                                      args,
                                      combat,
                                      self,
                                      conc_effect=conc_effect,
                                      ab_override=ab_override,
                                      dc_override=dc_override,
                                      spell_override=spell_override,
                                      title=title)
        else:
            phrase = args.join('phrase', '\n')
            if phrase:
                embed.description = f"*{phrase}*"

            text = self.description
            if len(text) > 1020:
                text = f"{text[:1020]}..."
            embed.add_field(name="Description", value=text, inline=False)
            if l != self.level and self.higherlevels:
                embed.add_field(name="At Higher Levels",
                                value=self.higherlevels,
                                inline=False)
            embed.set_footer(text="No spell automation found.")

        if l > 0 and not i:
            embed.add_field(name="Spell Slots",
                            value=caster.spellbook.remaining_casts_of(self, l))

        if conc_conflict:
            conflicts = ', '.join(e.name for e in conc_conflict)
            embed.add_field(name="Concentration",
                            value=f"Dropped {conflicts} due to concentration.")

        if 'thumb' in args:
            embed.set_thumbnail(url=args.last('thumb'))
        elif self.image:
            embed.set_thumbnail(url=self.image)

        add_fields_from_args(embed, args.get('f'))

        if self.source == 'homebrew':
            add_homebrew_footer(embed)

        return {"embed": embed}
コード例 #9
0
ファイル: spell.py プロジェクト: profK/avrae
    async def cast(self, ctx, caster, targets, args, combat=None):
        """
        Casts this spell.
        :param ctx: The context of the casting.
        :param caster: The caster of this spell.
        :type caster: cogs5e.models.caster.Spellcaster
        :param targets: A list of targets (Combatants)
        :param args: Args
        :param combat: The combat the spell was cast in, if applicable.
        :return: {embed: Embed}
        """

        # generic args
        l = args.last('l', self.level, int)
        i = args.last('i', type_=bool)
        phrase = args.join('phrase', '\n')
        title = args.last('title')

        # meta checks
        if not self.level <= l <= 9:
            raise SpellException("Invalid spell level.")

        if not (caster.can_cast(self, l) or i):
            embed = EmbedWithAuthor(ctx)
            embed.title = "Cannot cast spell!"
            embed.description = "Not enough spell slots remaining, or spell not in known spell list!\n" \
                f"Use `{ctx.prefix}game longrest` to restore all spell slots if this is a character, " \
                                "or pass `-i` to ignore restrictions."
            if l > 0:
                embed.add_field(name="Spell Slots", value=caster.remaining_casts_of(self, l))
            return {"embed": embed}

        if not i:
            caster.cast(self, l)

        # character setup
        character = None
        if isinstance(caster, PlayerCombatant):
            character = caster.character
        elif isinstance(caster, Character):
            character = caster

        # base stat stuff
        mod_arg = args.last("mod", type_=int)
        dc_override = None
        ab_override = None
        spell_override = None
        stat_override = ''
        if mod_arg is not None:
            mod = mod_arg
            dc_override = 8 + mod + character.get_prof_bonus()
            ab_override = mod + character.get_prof_bonus()
            spell_override = mod
        elif character and any(args.last(s, type_=bool) for s in ("str", "dex", "con", "int", "wis", "cha")):
            base = next(s for s in ("str", "dex", "con", "int", "wis", "cha") if args.last(s, type_=bool))
            mod = character.get_mod(base)
            dc_override = 8 + mod + character.get_prof_bonus()
            ab_override = mod + character.get_prof_bonus()
            spell_override = mod
            stat_override = f" with {verbose_stat(base)}"

        # begin setup
        embed = discord.Embed()
        if title:
            embed.title = title.replace('[sname]', self.name)
        elif targets:
            embed.title = f"{caster.get_name()} casts {self.name}{stat_override} at..."
        else:
            embed.title = f"{caster.get_name()} casts {self.name}{stat_override}!"
        if targets is None:
            targets = [None]

        if phrase:
            embed.description = f"*{phrase}*"

        conc_conflict = None
        conc_effect = None
        if self.concentration and isinstance(caster, Combatant) and combat:
            duration = args.last('dur', self.get_combat_duration(), int)
            conc_effect = initiative.Effect.new(combat, caster, self.name, duration, "", True)
            effect_result = caster.add_effect(conc_effect)
            conc_conflict = effect_result['conc_conflict']

        if self.automation and self.automation.effects:
            await self.automation.run(ctx, embed, caster, targets, args, combat, self, conc_effect=conc_effect,
                                      ab_override=ab_override, dc_override=dc_override, spell_override=spell_override)
        else:
            text = self.description
            if len(text) > 1020:
                text = f"{text[:1020]}..."
            embed.add_field(name="Description", value=text)
            if l != self.level and self.higherlevels:
                embed.add_field(name="At Higher Levels", value=self.higherlevels)
            embed.set_footer(text="No spell automation found.")

        if l > 0 and not i:
            embed.add_field(name="Spell Slots", value=caster.remaining_casts_of(self, l))

        if conc_conflict:
            conflicts = ', '.join(e.name for e in conc_conflict)
            embed.add_field(name="Concentration",
                            value=f"Dropped {conflicts} due to concentration.")

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

        if self.source == 'homebrew':
            add_homebrew_footer(embed)

        return {"embed": embed}
コード例 #10
0
    async def item_lookup(self, ctx, *, name):
        """Looks up an item."""
        guild_settings = await self.get_settings(ctx.guild)
        pm = guild_settings.get("pm_result", False)
        srd = guild_settings.get("srd", False)

        self.bot.rdb.incr('items_looked_up_life')

        try:
            pack = await Pack.from_ctx(ctx)
            custom_items = pack.get_search_formatted_items()
        except NoActiveBrew:
            custom_items = []
        choices = list(itertools.chain(c.items, custom_items))
        if ctx.guild:
            async for servpack in ctx.bot.mdb.packs.find({"server_active": str(ctx.guild.id)}):
                choices.extend(Pack.from_dict(servpack).get_search_formatted_items())

        def get_homebrew_formatted_name(_item):
            if _item.get('source') == 'homebrew':
                return f"{_item['name']} ({HOMEBREW_EMOJI})"
            return _item['name']

        result, metadata = await search_and_select(ctx, choices, name, lambda e: e['name'], srd=srd,
                                                   selectkey=get_homebrew_formatted_name, return_metadata=True)

        metadata['srd'] = srd
        metadata['homebrew'] = result.get('source') == 'homebrew'
        await self.add_training_data("item", name, result['name'], metadata=metadata)

        embed = EmbedWithAuthor(ctx)
        item = result

        if not item['srd'] and srd:
            return await self.send_srd_error(ctx, 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()}**: {c.itemprops[p]}\n"
                        extras = f"Speed: {item.get('speed')}\nCarrying Capacity: {item.get('carryingcapacity')}\n" \
                                 f"Crew {item.get('crew')}, AC {item.get('vehAc')}, HP {item.get('vehHp')}"
                        if 'vehDmgThresh' in item:
                            extras += f", Damage Threshold {item['vehDmgThresh']}"
                    if iType == 'siege weapon':
                        extras = f"Size: {SIZES.get(item.get('size'), 'Unknown')}\n" \
                                 f"AC {item.get('ac')}, HP {item.get('hp')}\n" \
                                 f"Immunities: {item.get('immune')}"
            else:
                type_ = ', '.join(
                    i for i in ("Wondrous Item" if item.get('wondrous') else '', item.get('technology')) if i)
            rarity = str(item.get('rarity')).replace('None', '')
            if 'tier' in item:
                if rarity:
                    rarity += f', {item["tier"]}'
                else:
                    rarity = item['tier']
            type_and_rarity = type_ + (f", {rarity}" if rarity else '')
            value = (item.get('value', 'n/a') + (', ' if 'weight' in item else '')) if 'value' in item else ''
            weight = (item.get('weight', 'n/a') + (' lb.' if item.get('weight') == '1' else ' lbs.')) \
                if 'weight' in item else ''
            weight_and_value = value + weight
            for prop in item.get('property', []):
                if not prop: continue
                a = b = prop
                a = PROPS.get(a, 'n/a')
                if b in c.itemprops:
                    proptext += f"**{a.title()}**: {c.itemprops[b]}\n"
                if b == 'V': a += " (" + item.get('dmg2', 'n/a') + ")"
                if b in ('T', 'A'): a += " (" + item.get('range', 'n/a') + "ft.)"
                if b == 'RLD': a += " (" + item.get('reload', 'n/a') + " shots)"
                properties.append(a)
            properties = ', '.join(properties)
            damage_and_properties = f"{damage} - {properties}" if properties else damage
            damage_and_properties = (' --- ' + damage_and_properties) if weight_and_value and damage_and_properties else \
                damage_and_properties

            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']}")

            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] + "..."

        field_name = "Description"
        for piece in [text[i:i + 1024] for i in range(0, len(text), 1024)]:
            embed.add_field(name=field_name, value=piece)
            field_name = "** **"

        if pm:
            await ctx.author.send(embed=embed)
        else:
            await ctx.send(embed=embed)
コード例 #11
0
ファイル: lookup.py プロジェクト: thomasmckay/avrae
    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 Pack.server_active(ctx):
                if servpack.id != pack_id:
                    choices.extend(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)
コード例 #12
0
    async def monster_save(self, ctx, monster_name, save_stat, *args):
        """Rolls a save for a monster.
        __Valid Arguments__
        adv/dis
        -b [conditional bonus]
        -phrase [flavor text]
        -title [title] *note: [name] and [cname] will be replaced automatically*
        -dc [dc]
        -rr [iterations]
        -h (hides name and image of monster)"""

        monster: Monster = await select_monster_full(ctx, monster_name)
        monster_name = monster.get_title_name()

        try:
            save = monster.saves.get(save_stat)
            save_name = f"{verbose_stat(save_stat[:3]).title()} Save"
        except ValueError:
            return await ctx.send('That\'s not a valid save.')

        embed = discord.Embed()
        embed.colour = random.randint(0, 0xffffff)

        args = await scripting.parse_snippets(args, ctx)
        args = argparse(args)
        adv = args.adv(boolwise=True)
        b = args.join('b', '+')
        phrase = args.join('phrase', '\n')
        iterations = min(args.last('rr', 1, int), 25)
        dc = args.last('dc', type_=int)
        num_successes = 0

        formatted_d20 = save.d20(base_adv=adv)

        if b:
            roll_str = f"{formatted_d20}+{b}"
        else:
            roll_str = formatted_d20

        if not args.last('h', type_=bool):
            default_title = f'{monster_name} makes {a_or_an(save_name)}!'
        else:
            default_title = f"An unknown creature makes {a_or_an(save_name)}!"

        embed.title = args.last('title', '') \
                          .replace('[name]', monster_name) \
                          .replace('[sname]', save_name) \
                      or default_title

        if iterations > 1:
            embed.description = (f"**DC {dc}**\n" if dc else '') + (
                '*' + phrase + '*' if phrase is not None else '')
            for i in range(iterations):
                result = roll(roll_str, inline=True)
                if dc and result.total >= dc:
                    num_successes += 1
                embed.add_field(name=f"Check {i + 1}", value=result.skeleton)
            if dc:
                embed.set_footer(
                    text=
                    f"{num_successes} Successes | {iterations - num_successes} Failues"
                )
        else:
            result = roll(roll_str, inline=True)
            if dc:
                embed.set_footer(
                    text="Success!" if result.total >= dc else "Failure!")
            embed.description = (
                f"**DC {dc}**\n" if dc else '') + result.skeleton + (
                    '\n*' + phrase + '*' if phrase is not None else '')

        embeds.add_fields_from_args(embed, args.get('f'))

        if args.last('image') is not None:
            embed.set_thumbnail(url=args.last('image'))
        elif not args.last('h', type_=bool):
            embed.set_thumbnail(url=monster.get_image_url())

        if monster.source == 'homebrew':
            embeds.add_homebrew_footer(embed)

        await ctx.send(embed=embed)
        try:
            await ctx.message.delete()
        except:
            pass
コード例 #13
0
    async def monster_check(self, ctx, monster_name, check, *args):
        """Rolls a check for a monster.
        __Valid Arguments__
        adv/dis
        -b [conditional bonus]
        -phrase [flavor text]
        -title [title] *note: [name] and [cname] will be replaced automatically*
        -dc [dc]
        -rr [iterations]
        str/dex/con/int/wis/cha (different skill base; e.g. Strength (Intimidation))
        -h (hides name and image of monster)"""

        monster: Monster = await select_monster_full(ctx, monster_name)

        monster_name = monster.get_title_name()
        skill_key = await search_and_select(ctx, SKILL_NAMES, check,
                                            lambda s: s)
        skill_name = camel_to_title(skill_key)

        embed = discord.Embed()
        embed.colour = random.randint(0, 0xffffff)

        args = await scripting.parse_snippets(args, ctx)
        args = argparse(args)

        adv = args.adv(boolwise=True)
        b = args.join('b', '+')
        phrase = args.join('phrase', '\n')
        iterations = min(args.last('rr', 1, int), 25)
        dc = args.last('dc', type_=int)
        num_successes = 0

        skill = monster.skills[skill_key]
        mod = skill.value
        formatted_d20 = skill.d20(base_adv=adv, base_only=True)

        if any(args.last(s, type_=bool) for s in STAT_ABBREVIATIONS):
            base = next(s for s in STAT_ABBREVIATIONS
                        if args.last(s, type_=bool))
            mod = mod - monster.get_mod(
                SKILL_MAP[skill_key]) + monster.get_mod(base)
            skill_name = f"{verbose_stat(base)} ({skill_name})"

        skill_name = skill_name.title()
        if not args.last('h', type_=bool):
            default_title = '{} makes {} check!'.format(
                monster_name, a_or_an(skill_name))
        else:
            default_title = f"An unknown creature makes {a_or_an(skill_name)} check!"

        if b is not None:
            roll_str = formatted_d20 + '{:+}'.format(mod) + '+' + b
        else:
            roll_str = formatted_d20 + '{:+}'.format(mod)

        embed.title = args.last('title', '') \
                          .replace('[name]', monster_name) \
                          .replace('[cname]', skill_name) \
                      or default_title

        if iterations > 1:
            embed.description = (f"**DC {dc}**\n" if dc else '') + (
                '*' + phrase + '*' if phrase is not None else '')
            for i in range(iterations):
                result = roll(roll_str, inline=True)
                if dc and result.total >= dc:
                    num_successes += 1
                embed.add_field(name=f"Check {i + 1}", value=result.skeleton)
            if dc:
                embed.set_footer(
                    text=
                    f"{num_successes} Successes | {iterations - num_successes} Failues"
                )
        else:
            result = roll(roll_str, inline=True)
            if dc:
                embed.set_footer(
                    text="Success!" if result.total >= dc else "Failure!")
            embed.description = (
                f"**DC {dc}**\n" if dc else '') + result.skeleton + (
                    '\n*' + phrase + '*' if phrase is not None else '')

        embeds.add_fields_from_args(embed, args.get('f'))

        if args.last('image') is not None:
            embed.set_thumbnail(url=args.last('image'))
        elif not args.last('h', type_=bool):
            embed.set_thumbnail(url=monster.get_image_url())

        if monster.source == 'homebrew':
            embeds.add_homebrew_footer(embed)

        await ctx.send(embed=embed)
        try:
            await ctx.message.delete()
        except:
            pass
コード例 #14
0
    async def monster_atk(self, ctx, monster_name, atk_name=None, *, args=''):
        """Rolls a monster's attack.
        __Valid Arguments__
        adv/dis
        -ac [target ac]
        -b [to hit bonus]
        -d [damage bonus]
        -d# [applies damage to the first # hits]
        -rr [times to reroll]
        -t [target]
        -phrase [flavor text]
        crit (automatically crit)
        -h (hides monster name, image, and attack details)"""
        if atk_name is None or atk_name == 'list':
            return await ctx.invoke(self.monster_atk_list, monster_name)

        try:
            await ctx.message.delete()
        except:
            pass

        monster = await select_monster_full(ctx, monster_name)
        attacks = monster.attacks
        monster_name = monster.get_title_name()

        attack = await search_and_select(ctx, attacks, atk_name,
                                         lambda a: a['name'])
        args = await scripting.parse_snippets(args, ctx)
        args = argparse(args)
        if not args.last('h', type_=bool):
            name = monster_name
            image = args.get('image') or monster.get_image_url()
        else:
            name = "An unknown creature"
            image = None

        attack = Attack.from_old(attack)

        embed = discord.Embed()
        if args.last('title') is not None:
            embed.title = args.last('title') \
                .replace('[name]', name) \
                .replace('[aname]', attack.name)
        else:
            embed.title = '{} attacks with {}!'.format(name,
                                                       a_or_an(attack.name))

        if image:
            embed.set_thumbnail(url=image)

        caster, targets, combat = await targetutils.maybe_combat(
            ctx, monster, args.get('t'))
        await Automation.from_attack(attack).run(ctx,
                                                 embed,
                                                 caster,
                                                 targets,
                                                 args,
                                                 combat=combat,
                                                 title=embed.title)
        if combat:
            await combat.final()

        _fields = args.get('f')
        embeds.add_fields_from_args(embed, _fields)
        embed.colour = random.randint(0, 0xffffff)

        if monster.source == 'homebrew':
            embeds.add_homebrew_footer(embed)

        await ctx.send(embed=embed)
コード例 #15
0
ファイル: lookup.py プロジェクト: veilheim/avrae
    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 = [
                    desc[i:i + 2040] for i in range(0, len(desc), 2040)
                ]
                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.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))

        else:
            hp = monster.hp
            ac = monster.ac
            size = monster.size
            _type = monster.race
            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.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)))

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

        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)
コード例 #16
0
ファイル: dice.py プロジェクト: veilheim/avrae
    async def monster_atk(self, ctx, monster_name, atk_name=None, *, args=''):
        """Rolls a monster's attack.
        __Valid Arguments__
        -t "<target>" - Sets targets for the attack. You can pass as many as needed. Will target combatants if channel is in initiative.
        -t "<target>|<args>" - Sets a target, and also allows for specific args to apply to them. (e.g, -t "OR1|hit" to force the attack against OR1 to hit)

        *adv/dis* - Advantage or Disadvantage
        *ea* - Elven Accuracy double advantage

        -ac <target ac> - overrides target AC
        *-b* <to hit bonus> - adds a bonus to hit
        -criton <num> - a number to crit on if rolled on or above
        *-d* <damage bonus> - adds a bonus to damage
        *-c* <damage bonus on crit> - adds a bonus to crit damage
        -rr <times> - number of times to roll the attack against each target
        *-mi <value>* - minimum value of each die on the damage roll

        *-resist* <damage resistance>
        *-immune* <damage immunity>
        *-vuln* <damage vulnerability>
        *-neutral* <damage type> - ignores this damage type in resistance calculations
        -dtype <damage type> - replaces all damage types with this damage type
        -dtype <old>new> - replaces all of one damage type with another (e.g. `-dtype fire>cold`)

        *hit* - automatically hits
        *miss* - automatically misses
        *crit* - automatically crits if hit
        *max* - deals max damage
        *magical* - makes the damage type magical

        -h - hides name, rolled values, and monster details
        -phrase <text> - adds flavour text
        -title <title> - changes the result title *note: `[name]` and `[aname]` will be replaced automatically*
        -thumb <url> - adds flavour image
        -f "Field Title|Field Text" - see `!help embed`
        <user snippet> - see `!help snippet`

        An italicized argument means the argument supports ephemeral arguments - e.g. `-d1` applies damage to the first hit, `-b1` applies a bonus to one attack, and so on.
        """
        if atk_name is None or atk_name == 'list':
            return await self.monster_atk_list(ctx, monster_name)

        await try_delete(ctx.message)

        monster = await select_monster_full(ctx, monster_name)
        attacks = monster.attacks

        attack = await search_and_select(ctx, attacks, atk_name, lambda a: a.name)
        args = await helpers.parse_snippets(args, ctx)
        args = argparse(args)

        embed = discord.Embed()
        if not args.last('h', type_=bool):
            embed.set_thumbnail(url=monster.get_image_url())

        caster, targets, combat = await targetutils.maybe_combat(ctx, monster, args)
        await attackutils.run_attack(ctx, embed, args, caster, attack, targets, combat)

        embed.colour = random.randint(0, 0xffffff)
        if monster.homebrew:
            embeds.add_homebrew_footer(embed)

        await ctx.send(embed=embed)