コード例 #1
0
    async def customcounter(self, ctx, name=None, *, modifier=None):
        """Commands to implement custom counters.
        When called on its own, if modifier is supplied, increases the counter *name* by *modifier*.
        If modifier is not supplied, prints the value and metadata of the counter *name*.
        """
        if name is None:
            return await self.customcounter_summary(ctx)
        character: Character = await Character.from_ctx(ctx)
        counter = await character.select_consumable(ctx, name)

        cc_embed_title = counter.title if counter.title is not None else counter.name

        # replace [name] in title
        cc_embed_title = cc_embed_title.replace('[name]', character.name)

        if modifier is None:  # display value
            counter_display_embed = EmbedWithCharacter(character)
            counter_display_embed.add_field(name=counter.name, value=counter.full_str())
            if counter.desc:
                counter_display_embed.add_field(name='Description', value=counter.desc, inline=False)
            return await ctx.send(embed=counter_display_embed)

        operator = None
        if ' ' in modifier:
            m = modifier.split(' ')
            operator = m[0]
            modifier = m[-1]

        change = ''
        old_value = counter.value
        try:
            modifier = int(modifier)
        except ValueError:
            return await ctx.send(f"Could not modify counter: {modifier} is not a number")
        result_embed = EmbedWithCharacter(character)
        if not operator or operator == 'mod':
            new_value = counter.value + modifier
        elif operator == 'set':
            new_value = modifier
        else:
            return await ctx.send("Invalid operator. Use mod or set.")

        counter.set(new_value)
        await character.commit(ctx)

        delta = f"({counter.value - old_value:+})"
        if new_value - counter.value:  # we overflowed somewhere
            out = f"{str(counter)} {delta}\n({abs(new_value - counter.value)} overflow)"
        else:
            out = f"{str(counter)} {delta}"

        result_embed.add_field(name=cc_embed_title, value=out)

        if counter.desc:
            result_embed.add_field(name='Description', value=counter.desc, inline=False)

        await try_delete(ctx.message)
        await ctx.send(embed=result_embed)
コード例 #2
0
ファイル: gametrack.py プロジェクト: kphillipsjr1101/avrae
    async def customcounter(self, ctx, name=None, *, modifier=None):
        """Commands to implement custom counters.
        When called on its own, if modifier is supplied, increases the counter *name* by *modifier*.
        If modifier is not supplied, prints the value and metadata of the counter *name*."""
        if name is None:
            return await ctx.invoke(self.bot.get_command("customcounter list"))
        character: Character = await Character.from_ctx(ctx)
        counter = await character.select_consumable(ctx, name)

        if modifier is None:  # display value
            counter_display_embed = EmbedWithCharacter(character)
            counter_display_embed.add_field(name=counter.name,
                                            value=counter.full_str())
            return await ctx.send(embed=counter_display_embed)

        operator = None
        if ' ' in modifier:
            m = modifier.split(' ')
            operator = m[0]
            modifier = m[-1]

        try:
            modifier = int(modifier)
        except ValueError:
            return await ctx.send(
                f"Could not modify counter: {modifier} is not a number")
        result_embed = EmbedWithCharacter(character)
        if not operator or operator == 'mod':
            new_value = counter.value + modifier
        elif operator == 'set':
            new_value = modifier
        else:
            return await ctx.send("Invalid operator. Use mod or set.")

        counter.set(new_value)
        await character.commit(ctx)

        if new_value - counter.value:
            out = f"{str(counter)}\n({abs(new_value - counter.value)} overflow)"
        else:
            out = str(counter)

        result_embed.add_field(name=counter.name, value=out)

        try:
            await ctx.message.delete()
        except:
            pass
        await ctx.send(embed=result_embed)
コード例 #3
0
 async def customcounter_summary(self, ctx, page: int = 0):
     """
     Prints a summary of all custom counters.
     Use `!cc list <page>` to view pages if you have more than 25 counters.
     """
     character: Character = await Character.from_ctx(ctx)
     embed = EmbedWithCharacter(character, title="Custom Counters")
     # Check that we're not over the field limit
     total = len(character.consumables)
     if total > 25:  # Discord Field limit
         page = max(0, page - 1)  # Humans count from 1
         maxpage = total // 25
         start = min(page * 25, total - 25)
         end = max(start + 25, total)
         # Build the current page
         embed.set_footer(
             text=
             f"Page [{page + 1}/{maxpage + 1}] | {ctx.prefix}cc list <page>"
         )
         for counter in character.consumables[start:end]:
             embed.add_field(name=counter.name, value=counter.full_str())
     else:
         for counter in character.consumables:
             embed.add_field(name=counter.name, value=counter.full_str())
     await ctx.send(embed=embed)
コード例 #4
0
ファイル: gametrack.py プロジェクト: veilheim/avrae
 async def game_spellslot(self, ctx, level: int = None, value: str = None):
     """Views or sets your remaining spell slots."""
     if level is not None:
         try:
             assert 0 < level < 10
         except AssertionError:
             return await ctx.send("Invalid spell level.")
     character: Character = await Character.from_ctx(ctx)
     embed = EmbedWithCharacter(character)
     embed.set_footer(text="\u25c9 = Available / \u3007 = Used")
     if level is None and value is None:  # show remaining
         embed.description = f"__**Remaining Spell Slots**__\n{character.spellbook.slots_str()}"
     elif value is None:
         embed.description = f"__**Remaining Level {level} Spell Slots**__\n" \
                             f"{character.spellbook.slots_str(level)}"
     else:
         try:
             if value.startswith(('+', '-')):
                 value = character.spellbook.get_slots(level) + int(value)
             else:
                 value = int(value)
         except ValueError:
             return await ctx.send(f"{value} is not a valid integer.")
         try:
             assert 0 <= value <= character.spellbook.get_max_slots(level)
         except AssertionError:
             raise CounterOutOfBounds()
         character.spellbook.set_slots(level, value)
         await character.commit(ctx)
         embed.description = f"__**Remaining Level {level} Spell Slots**__\n" \
                             f"{character.spellbook.slots_str(level)}"
     await ctx.send(embed=embed)
コード例 #5
0
ファイル: gametrack.py プロジェクト: veilheim/avrae
 async def customcounter_summary(self, ctx):
     """Prints a summary of all custom counters."""
     character: Character = await Character.from_ctx(ctx)
     embed = EmbedWithCharacter(character)
     for counter in character.consumables:
         embed.add_field(name=counter.name, value=counter.full_str())
     await ctx.send(embed=embed)
コード例 #6
0
    async def join(self, ctx, *, args: str = ''):
        """Adds the current active character to combat. A character must be loaded through the SheetManager module first.
        Args: adv/dis
              -b [conditional bonus]
              -phrase [flavor text]
              -p [init value]
              -h (same as !init add)
              --group (same as !init add)"""
        char: Character = await Character.from_ctx(ctx)

        embed = EmbedWithCharacter(char, False)
        embed.colour = char.get_color()

        args = shlex.split(args)
        args = argparse(args)
        adv = args.adv(boolwise=True)
        b = args.join('b', '+') or None
        p = args.last('p', type_=int)
        phrase = args.join('phrase', '\n') or None
        group = args.last('group')

        if p is None:
            roll_str = char.skills.initiative.d20(base_adv=adv)
            if b:
                roll_str = f"{roll_str}+{b}"
            check_roll = roll(roll_str, inline=True)

            embed.title = '{} makes an Initiative check!'.format(char.name)
            embed.description = check_roll.skeleton + ('\n*' + phrase + '*' if phrase is not None else '')
            init = check_roll.total
        else:
            init = p
            embed.title = "{} already rolled initiative!".format(char.name)
            embed.description = "Placed at initiative `{}`.".format(init)

        controller = str(ctx.author.id)
        private = args.last('h', type_=bool)
        bonus = char.skills.initiative.value

        combat = await Combat.from_ctx(ctx)

        me = await PlayerCombatant.from_character(char.name, controller, init, bonus, char.ac, private,
                                                  char.get_resists(), ctx, combat, char.upstream, str(ctx.author.id),
                                                  char)

        if combat.get_combatant(char.name) is not None:
            await ctx.send("Combatant already exists.")
            return

        if group is None:
            combat.add_combatant(me)
            embed.set_footer(text="Added to combat!")
        else:
            grp = combat.get_group(group, create=init)
            grp.add_combatant(me)
            embed.set_footer(text=f"Joined group {grp.name}!")

        await combat.final()
        await ctx.send(embed=embed)
        await char.commit(ctx)
コード例 #7
0
ファイル: sheetManager.py プロジェクト: akrib/avrae
    async def playertoken(self, ctx, *args):
        """
        Generates and sends a token for use on VTTs.
        __Valid Arguments__
        -border <gold|plain|none> - Chooses the token border.
        """

        char: Character = await Character.from_ctx(ctx)
        if not char.image:
            return await ctx.send("This character has no image.")

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

        try:
            processed = await img.generate_token(char.image, is_subscriber,
                                                 token_args)
        except Exception as e:
            return await ctx.send(f"Error generating token: {e}")

        file = discord.File(processed, filename="image.png")
        embed = EmbedWithCharacter(char, image=False)
        embed.set_image(url="attachment://image.png")
        await ctx.send(file=file, embed=embed)
        processed.close()
コード例 #8
0
ファイル: gametrack.py プロジェクト: neutralboolean/avrae
    async def spellbook(self, ctx):
        """Commands to display a character's known spells and metadata."""
        character: Character = await Character.from_ctx(ctx)
        embed = EmbedWithCharacter(character)
        embed.description = f"{character.name} knows {len(character.spellbook.spells)} spells."
        embed.add_field(name="DC", value=str(character.spellbook.dc))
        embed.add_field(name="Spell Attack Bonus", value=str(character.spellbook.sab))
        embed.add_field(name="Spell Slots", value=character.get_remaining_slots_str() or "None")
        spells_known = collections.defaultdict(lambda: [])
        choices = await get_spell_choices(ctx)
        for spell_ in character.spellbook.spells:
            spell = await get_castable_spell(ctx, spell_.name, choices)
            if spell is None and spell_.strict:
                continue
            elif spell is None:
                spells_known['unknown'].append(f"*{spell_.name}*")
            else:
                if spell.source == 'homebrew':
                    formatted = f"*{spell.name}*"
                else:
                    formatted = spell.name
                spells_known[str(spell.level)].append(formatted)

        level_name = {'0': 'Cantrips', '1': '1st Level', '2': '2nd Level', '3': '3rd Level',
                      '4': '4th Level', '5': '5th Level', '6': '6th Level',
                      '7': '7th Level', '8': '8th Level', '9': '9th Level'}
        for level, spells in sorted(list(spells_known.items()), key=lambda k: k[0]):
            if spells:
                spells.sort()
                embed.add_field(name=level_name.get(level, "Unknown"), value=', '.join(spells))
        await ctx.send(embed=embed)
コード例 #9
0
    async def save(self, ctx, skill, *args):
        """Rolls a save for your current active character.
        __Valid Arguments__
        *adv/dis*
        *-b [conditional bonus]*
        -phrase [flavor text]
        -title [title] *note: [name] and [sname] will be replaced automatically*
        -image [image URL]
        -dc [dc] (does not apply to Death Saves)
        -rr [iterations] (does not apply to Death Saves)

        An italicized argument means the argument supports ephemeral arguments - e.g. `-b1` applies a bonus to one save.
        """
        if skill == 'death':
            ds_cmd = self.bot.get_command('game deathsave')
            if ds_cmd is None:
                return await ctx.send("Error: GameTrack cog not loaded.")
            return await ctx.invoke(ds_cmd, *args)

        char: Character = await Character.from_ctx(ctx)

        embed = EmbedWithCharacter(char, name=False)

        args = await self.new_arg_stuff(args, ctx, char)

        checkutils.update_csetting_args(char, args)
        checkutils.run_save(skill, char, args, embed)

        if args.last('image') is not None:
            embed.set_thumbnail(url=args.last('image'))

        # send
        await ctx.send(embed=embed)
        await try_delete(ctx.message)
コード例 #10
0
    async def game_spellslot(self, ctx, level: int = None, value: str = None, *args):
        """
        Views or sets your remaining spell slots.
        __Valid Arguments__
        nopact - Modifies normal spell slots first instead of a Pact Magic slots, if applicable.
        """
        if level is not None:
            try:
                assert 0 < level < 10
            except AssertionError:
                return await ctx.send("Invalid spell level.")
        character: Character = await Character.from_ctx(ctx)
        embed = EmbedWithCharacter(character)

        if level is None and value is None:  # show remaining
            embed.description = f"__**Remaining Spell Slots**__\n{character.spellbook.slots_str()}"
        elif value is None:
            embed.description = f"__**Remaining Level {level} Spell Slots**__\n" \
                                f"{character.spellbook.slots_str(level)}"
        else:
            old_slots = character.spellbook.get_slots(level)
            value = maybe_mod(value, old_slots)
            character.spellbook.set_slots(level, value, pact='nopact' not in args)
            await character.commit(ctx)
            embed.description = f"__**Remaining Level {level} Spell Slots**__\n" \
                                f"{character.spellbook.slots_str(level)} ({(value - old_slots):+})"

        # footer - pact vs non pact
        if character.spellbook.max_pact_slots is not None:
            embed.set_footer(text=f"{constants.FILLED_BUBBLE} = Available / {constants.EMPTY_BUBBLE} = Used\n"
                                  f"{constants.FILLED_BUBBLE_ALT} / {constants.EMPTY_BUBBLE_ALT} = Pact Slot")
        else:
            embed.set_footer(text=f"{constants.FILLED_BUBBLE} = Available / {constants.EMPTY_BUBBLE} = Used")

        await ctx.send(embed=embed)
コード例 #11
0
ファイル: gametrack.py プロジェクト: aquisenberry/avrae
    async def spellbook(self, ctx):
        """Commands to display a character's known spells and metadata."""
        character = await Character.from_ctx(ctx)
        embed = EmbedWithCharacter(character)
        embed.description = f"{character.get_name()} knows {len(character.get_spell_list())} spells."
        embed.add_field(name="DC", value=str(character.get_save_dc()))
        embed.add_field(name="Spell Attack Bonus",
                        value=str(character.get_spell_ab()))
        embed.add_field(name="Spell Slots",
                        value=character.get_remaining_slots_str() or "None")
        spells_known = {}
        for spell_name in character.get_spell_list():
            spell = strict_search(c.spells, 'name', spell_name)
            spells_known[spell['level']] = spells_known.get(
                spell['level'], []) + [spell_name]

        level_name = {
            '0': 'Cantrips',
            '1': '1st Level',
            '2': '2nd Level',
            '3': '3rd Level',
            '4': '4th Level',
            '5': '5th Level',
            '6': '6th Level',
            '7': '7th Level',
            '8': '8th Level',
            '9': '9th Level'
        }
        for level, spells in sorted(list(spells_known.items()),
                                    key=lambda k: k[0]):
            if spells:
                embed.add_field(name=level_name.get(level, "Unknown Level"),
                                value=', '.join(spells))
        await self.bot.say(embed=embed)
コード例 #12
0
    async def check(self, ctx, check, *args):
        """Rolls a check for your current active character.
        __Valid Arguments__
        *adv/dis*
        *-b [conditional bonus]*
        -phrase [flavor text]
        -title [title] *note: [name] and [cname] will be replaced automatically*
        -dc [dc]
        -mc [minimum roll]
        -rr [iterations]
        str/dex/con/int/wis/cha (different skill base; e.g. Strength (Intimidation))

        An italicized argument means the argument supports ephemeral arguments - e.g. `-b1` applies a bonus to one check.
        """
        char: Character = await Character.from_ctx(ctx)
        skill_key = await search_and_select(ctx, SKILL_NAMES, check,
                                            lambda s: s)

        embed = EmbedWithCharacter(char, False)
        skill = char.skills[skill_key]

        args = await self.new_arg_stuff(args, ctx, char)

        checkutils.update_csetting_args(char, args, skill)
        checkutils.run_check(skill_key, char, args, embed)

        if args.last('image') is not None:
            embed.set_thumbnail(url=args.last('image'))

        await ctx.send(embed=embed)
        await try_delete(ctx.message)
コード例 #13
0
ファイル: gamelogutils.py プロジェクト: driesceuppens/avrae
def embed_for_caster(caster):
    if isinstance(caster, Character):
        return EmbedWithCharacter(character=caster, name=False)
    embed = discord.Embed()
    embed.colour = random.randint(0, 0xffffff)
    if isinstance(caster, Monster):
        embed.set_thumbnail(url=caster.get_image_url())
    return embed
コード例 #14
0
 async def customcounter_summary(self, ctx):
     """Prints a summary of all custom counters."""
     character = await Character.from_ctx(ctx)
     embed = EmbedWithCharacter(character)
     for name, counter in character.get_all_consumables().items():
         val = self._get_cc_value(character, counter)
         embed.add_field(name=name, value=val)
     await ctx.send(embed=embed)
コード例 #15
0
    def get_sheet_embed(self):
        embed = EmbedWithCharacter(self)
        desc_details = []

        # race/class (e.g. Tiefling Bard/Warlock)
        classes = '/'.join(f"{cls} {lvl}" for cls, lvl in self.levels)
        desc_details.append(f"{self.race} {classes}")

        # prof bonus
        desc_details.append(
            f"**Proficiency Bonus**: {self.stats.prof_bonus:+}")

        # combat details
        desc_details.append(f"**AC**: {self.ac}")
        desc_details.append(f"**HP**: {self.get_hp_str()}")
        desc_details.append(
            f"**Initiative**: {self.skills.initiative.value:+}")

        # stats
        desc_details.append(str(self.stats))
        save_profs = str(self.saves)
        if save_profs:
            desc_details.append(f"**Save Proficiencies**: {save_profs}")
        skill_profs = str(self.skills)
        if skill_profs:
            desc_details.append(f"**Skill Proficiencies**: {skill_profs}")
        desc_details.append(
            f"**Senses**: passive Perception {10 + self.skills.perception.value}"
        )

        # resists
        resists = str(self.resistances)
        if resists:
            desc_details.append(resists)

        embed.description = '\n'.join(desc_details)

        # attacks
        atks = self.attacks
        atk_str = ""
        for attack in atks:
            a = f"{str(attack)}\n"
            if len(atk_str) + len(a) > 1000:
                atk_str += "[...]"
                break
            atk_str += a
        atk_str = atk_str.strip()
        if atk_str:
            embed.add_field(name="Attacks", value=atk_str)

        # sheet url?
        if self._import_version < 15:
            embed.set_footer(
                text=
                f"You are using an old sheet version ({self.sheet_type} v{self._import_version}). "
                f"Please run !update.")

        return embed
コード例 #16
0
ファイル: gametrack.py プロジェクト: JulianGindi/avrae
 async def game_status(self, ctx):
     """Prints the status of the current active character."""
     character: Character = await Character.from_ctx(ctx)
     embed = EmbedWithCharacter(character)
     embed.add_field(name="Hit Points", value=character.hp_str())
     embed.add_field(name="Spell Slots", value=character.slots_str())
     for counter in character.consumables:
         embed.add_field(name=counter.name, value=counter.full_str())
     await ctx.send(embed=embed)
コード例 #17
0
ファイル: sheetManager.py プロジェクト: JulianGindi/avrae
    async def attack(self, ctx, atk_name=None, *, args: str = ''):
        """Rolls an attack for the current active character.
        __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*
        *ea* (Elven Accuracy double advantage)
        
        -ac [target ac]
        -t [target]
        
        *-b* [to hit bonus]
        -criton [a number to crit on if rolled on or above]
        *-d* [damage bonus]
        *-c* [damage bonus on crit]
        -rr [times to reroll]
        *-mi* [minimum weapon dice roll]
        
        *-resist* [damage resistance]
        *-immune* [damage immunity]
        *-vuln* [damage vulnerability]
        *-neutral* [damage non-resistance]
        
        *hit* (automatically hits)
        *miss* (automatically misses)
        *crit* (automatically crit)
        *max* (deals max damage)

        -h (hides name and rolled values)
        -phrase [flavor text]
        -title [title] *note: [name] and [aname] will be replaced automatically*
        -thumb [url]
        -f "Field Title|Field Text" (see !embed)
        [user 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:
            return await ctx.invoke(self.attack_list)

        char: Character = await Character.from_ctx(ctx)
        args = await self.new_arg_stuff(args, ctx, char)

        caster, targets, combat = await targetutils.maybe_combat(
            ctx, char, args)
        attack = await search_and_select(ctx, caster.attacks, atk_name,
                                         lambda a: a.name)

        embed = EmbedWithCharacter(char, name=False)
        await attackutils.run_attack(ctx, embed, args, caster, attack, targets,
                                     combat)

        await ctx.send(embed=embed)
        try:
            await ctx.message.delete()
        except:
            pass
コード例 #18
0
    async def attack(self, ctx, atk_name=None, *, args: str = ''):
        """Rolls an attack for the current active character.
        __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 and rolled values
        -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:
            return await self.attack_list(ctx)

        char: Character = await Character.from_ctx(ctx)
        args = await self.new_arg_stuff(args, ctx, char)

        caster, targets, combat = await targetutils.maybe_combat(
            ctx, char, args)
        attack = await search_and_select(ctx, caster.attacks, atk_name,
                                         lambda a: a.name)

        embed = EmbedWithCharacter(char, name=False)
        await attackutils.run_attack(ctx, embed, args, caster, attack, targets,
                                     combat)

        await ctx.send(embed=embed)
        await try_delete(ctx.message)
コード例 #19
0
 async def game_status(self, ctx):
     """Prints the status of the current active character."""
     character = await Character.from_ctx(ctx)
     embed = EmbedWithCharacter(character)
     embed.add_field(name="Hit Points", value=f"{character.get_current_hp()}/{character.get_max_hp()}")
     embed.add_field(name="Spell Slots", value=character.get_remaining_slots_str())
     for name, counter in character.get_all_consumables().items():
         val = self._get_cc_value(character, counter)
         embed.add_field(name=name, value=val)
     await ctx.send(embed=embed)
コード例 #20
0
    async def spellbook(self, ctx):
        """Commands to display a character's known spells and metadata."""
        character = await Character.from_ctx(ctx)
        embed = EmbedWithCharacter(character)
        embed.description = f"{character.get_name()} knows {len(character.get_spell_list())} spells."
        embed.add_field(name="DC", value=str(character.get_save_dc()))
        embed.add_field(name="Spell Attack Bonus",
                        value=str(character.get_spell_ab()))
        embed.add_field(name="Spell Slots",
                        value=character.get_remaining_slots_str() or "None")
        spells_known = {}
        choices = await get_spell_choices(ctx)
        for spell_ in character.get_raw_spells():
            if isinstance(spell_, str):
                spell, strict = search(c.spells, spell_, lambda sp: sp.name)
                if spell is None or not strict:
                    continue
                spells_known[str(spell.level)] = spells_known.get(
                    str(spell.level), []) + [spell.name]
            else:
                spellname = spell_['name']
                strict = spell_['strict']
                spell = await get_castable_spell(ctx, spellname, choices)
                if spell is None and strict:
                    continue
                elif spell is None:
                    spells_known['unknown'] = spells_known.get(
                        'unknown', []) + [f"*{spellname}*"]
                else:
                    if spell.source == 'homebrew':
                        formatted = f"*{spell.name}*"
                    else:
                        formatted = spell.name
                    spells_known[str(spell.level)] = spells_known.get(
                        str(spell.level), []) + [formatted]

        level_name = {
            '0': 'Cantrips',
            '1': '1st Level',
            '2': '2nd Level',
            '3': '3rd Level',
            '4': '4th Level',
            '5': '5th Level',
            '6': '6th Level',
            '7': '7th Level',
            '8': '8th Level',
            '9': '9th Level'
        }
        for level, spells in sorted(list(spells_known.items()),
                                    key=lambda k: k[0]):
            if spells:
                spells.sort()
                embed.add_field(name=level_name.get(level, "Unknown"),
                                value=', '.join(spells))
        await ctx.send(embed=embed)
コード例 #21
0
    async def spellbook(self, ctx):
        """Commands to display a character's known spells and metadata."""
        await ctx.trigger_typing()

        character: Character = await Character.from_ctx(ctx)
        embed = EmbedWithCharacter(character)
        embed.description = f"{character.name} knows {len(character.spellbook.spells)} spells."
        embed.add_field(name="DC", value=str(character.spellbook.dc))
        embed.add_field(name="Spell Attack Bonus", value=str(character.spellbook.sab))
        embed.add_field(name="Spell Slots", value=character.spellbook.slots_str() or "None")

        # dynamic help flags
        flag_show_multiple_source_help = False
        flag_show_homebrew_help = False

        spells_known = collections.defaultdict(lambda: [])
        choices = await get_spell_choices(ctx)
        for spell_ in character.spellbook.spells:
            results, strict = search(choices, spell_.name, lambda sp: sp.name, strict=True)
            if not strict:
                if len(results) > 1:
                    spells_known['unknown'].append(f"*{spell_.name} ({'*' * len(results)})*")
                    flag_show_multiple_source_help = True
                else:
                    spells_known['unknown'].append(f"*{spell_.name}*")
                flag_show_homebrew_help = True
            else:
                spell = results
                if spell.homebrew:
                    formatted = f"*{spell.name}*"
                    flag_show_homebrew_help = True
                else:
                    formatted = spell.name
                spells_known[str(spell.level)].append(formatted)

        level_name = {'0': 'Cantrips', '1': '1st Level', '2': '2nd Level', '3': '3rd Level',
                      '4': '4th Level', '5': '5th Level', '6': '6th Level',
                      '7': '7th Level', '8': '8th Level', '9': '9th Level'}
        for level, spells in sorted(list(spells_known.items()), key=lambda k: k[0]):
            if spells:
                spells.sort()
                embed.add_field(name=level_name.get(level, "Unknown"), value=', '.join(spells), inline=False)

        # dynamic help
        footer_out = []
        if flag_show_homebrew_help:
            footer_out.append("An italicized spell indicates that the spell is homebrew.")
        if flag_show_multiple_source_help:
            footer_out.append("Asterisks after a spell indicates that the spell is being provided by multiple sources.")

        if footer_out:
            embed.set_footer(text=' '.join(footer_out))

        await ctx.send(embed=embed)
コード例 #22
0
 async def game_status(self, ctx):
     """Prints the status of the current active character."""
     character: Character = await Character.from_ctx(ctx)
     embed = EmbedWithCharacter(character)
     embed.add_field(name="Hit Points", value=character.hp_str())
     embed.add_field(name="Spell Slots", value=character.spellbook.slots_str())
     if character.death_saves.successes != 0 or character.death_saves.fails != 0:
         embed.add_field(name="Death Saves", value=str(character.death_saves))
     for counter in character.consumables:
         embed.add_field(name=counter.name, value=counter.full_str())
     await ctx.send(embed=embed)
コード例 #23
0
ファイル: gametrack.py プロジェクト: veilheim/avrae
    async def game_deathsave_reset(self, ctx):
        """Resets all death saves."""
        character: Character = await Character.from_ctx(ctx)
        character.death_saves.reset()
        await character.commit(ctx)

        embed = EmbedWithCharacter(character)
        embed.title = f'{character.name} reset Death Saves!'
        embed.add_field(name="Death Saves", value=str(character.death_saves))

        await ctx.send(embed=embed)
コード例 #24
0
    def get_sheet_embed(self):
        embed = EmbedWithCharacter(self)
        # noinspection PyListCreation
        # this could be a list literal, but it's more readable this way
        desc_details = []

        # race/class (e.g. Tiefling Bard/Warlock)
        desc_details.append(f"{self.race} {str(self.levels)}")

        # prof bonus
        desc_details.append(
            f"**Proficiency Bonus**: {self.stats.prof_bonus:+}")

        # combat details
        desc_details.append(f"**AC**: {self.ac}")
        desc_details.append(f"**HP**: {self.hp_str()}")
        desc_details.append(
            f"**Initiative**: {self.skills.initiative.value:+}")

        # stats
        desc_details.append(str(self.stats))
        save_profs = str(self.saves)
        if save_profs:
            desc_details.append(f"**Save Proficiencies**: {save_profs}")
        skill_profs = str(self.skills)
        if skill_profs:
            desc_details.append(f"**Skill Proficiencies**: {skill_profs}")
        desc_details.append(
            f"**Senses**: passive Perception {10 + self.skills.perception.value}"
        )

        # resists
        resists = str(self.resistances)
        if resists:
            desc_details.append(resists)

        embed.description = '\n'.join(desc_details)

        # attacks
        atk_str = self.attacks.build_str(self)
        if len(atk_str) > 1000:
            atk_str = f"{atk_str[:1000]}\n[...]"
        if atk_str:
            embed.add_field(name="Attacks", value=atk_str)

        # sheet url?
        if self._import_version < SHEET_VERSION:
            embed.set_footer(
                text=
                f"You are using an old sheet version ({self.sheet_type} v{self._import_version}). "
                f"Please run !update.")

        return embed
コード例 #25
0
ファイル: gametrack.py プロジェクト: aquisenberry/avrae
    async def game_deathsave_reset(self, ctx):
        """Resets all death saves."""
        character = await Character.from_ctx(ctx)
        character.reset_death_saves()
        embed = EmbedWithCharacter(character)
        embed.title = f'{character.get_name()} reset Death Saves!'

        await character.commit(ctx)

        embed.add_field(name="Death Saves", value=character.get_ds_str())

        await self.bot.say(embed=embed)
コード例 #26
0
ファイル: gametrack.py プロジェクト: jbuiss0n/avrae
 async def game_shortrest(self, ctx, *args):
     """Performs a short rest, resetting applicable counters.
     __Valid Arguments__
     -h - Hides the character summary output."""
     character = Character.from_ctx(ctx)
     reset = character.short_rest()
     embed = EmbedWithCharacter(character, name=False)
     embed.title = f"{character.get_name()} took a Short Rest!"
     embed.add_field(name="Reset Values", value=', '.join(set(reset)))
     character.commit(ctx)
     await self.bot.say(embed=embed)
     if not '-h' in args:
         await ctx.invoke(self.game_status)
コード例 #27
0
ファイル: gametrack.py プロジェクト: kphillipsjr1101/avrae
 async def game_longrest(self, ctx, *args):
     """Performs a long rest, resetting applicable counters.
     __Valid Arguments__
     -h - Hides the character summary output."""
     character: Character = await Character.from_ctx(ctx)
     reset = character.long_rest()
     embed = EmbedWithCharacter(character, name=False)
     embed.title = f"{character.name} took a Long Rest!"
     embed.add_field(name="Reset Values", value=', '.join(set(reset)))
     await character.commit(ctx)
     await ctx.send(embed=embed)
     if not '-h' in args:
         await ctx.invoke(self.game_status)
コード例 #28
0
    async def game_deathsave(self, ctx, *args):
        """Commands to manage character death saves.
        __Valid Arguments__
        See `!help save`."""
        character: Character = await Character.from_ctx(ctx)

        embed = EmbedWithCharacter(character, name=False)

        args = await helpers.parse_snippets(args, ctx)
        args = await helpers.parse_with_character(ctx, character, args)
        args = argparse(args)
        checkutils.update_csetting_args(character, args)
        caster, _, _ = await targetutils.maybe_combat(ctx, character, args)
        result = checkutils.run_save('death', caster, args, embed)

        dc = result.skill_roll_result.dc or 10
        death_phrase = ''

        for save_roll in result.skill_roll_result.rolls:
            if save_roll.crit == d20.CritType.CRIT:
                character.hp = 1
            elif save_roll.crit == d20.CritType.FAIL:
                character.death_saves.fail(2)
            elif save_roll.total >= dc:
                character.death_saves.succeed()
            else:
                character.death_saves.fail()

            if save_roll.crit == d20.CritType.CRIT:
                death_phrase = f"{character.name} is UP with 1 HP!"
                break
            elif character.death_saves.is_dead():
                death_phrase = f"{character.name} is DEAD!"
                break
            elif character.death_saves.is_stable():
                death_phrase = f"{character.name} is STABLE!"
                break

        if death_phrase:
            embed.set_footer(text=death_phrase)
        embed.add_field(name="Death Saves",
                        value=str(character.death_saves),
                        inline=False)

        await character.commit(ctx)
        await ctx.send(embed=embed)
        await try_delete(ctx.message)
        if gamelog := self.bot.get_cog('GameLog'):
            await gamelog.send_save(ctx, character, result.skill_name,
                                    result.rolls)
コード例 #29
0
ファイル: gametrack.py プロジェクト: veilheim/avrae
    async def game_deathsave_save(self, ctx):
        """Adds a successful death save."""
        character: Character = await Character.from_ctx(ctx)

        embed = EmbedWithCharacter(character)
        embed.title = f'{character.name} succeeds a Death Save!'

        character.death_saves.succeed()
        await character.commit(ctx)

        if character.death_saves.is_stable():
            embed.set_footer(text=f"{character.name} is STABLE!")
        embed.description = "Added 1 successful death save."
        embed.add_field(name="Death Saves", value=str(character.death_saves))

        await ctx.send(embed=embed)
コード例 #30
0
ファイル: gametrack.py プロジェクト: veilheim/avrae
    async def game_deathsave_fail(self, ctx):
        """Adds a failed death save."""
        character: Character = await Character.from_ctx(ctx)

        embed = EmbedWithCharacter(character)
        embed.title = f'{character.name} fails a Death Save!'

        character.death_saves.fail()
        await character.commit(ctx)

        if character.death_saves.is_dead():
            embed.set_footer(text=f"{character.name} is DEAD!")
        embed.description = "Added 1 failed death save."
        embed.add_field(name="Death Saves", value=str(character.death_saves))

        await ctx.send(embed=embed)