Esempio n. 1
0
    async def game_deathsave(self, ctx, *args):
        """Commands to manage character death saves.
        __Valid Arguments__
        See `!help save`."""
        character = Character.from_ctx(ctx)
        args = parse_args_3(args)
        adv = 0 if args.get('adv', [False])[-1] and args.get('dis', [False])[-1] else \
            1 if args.get('adv', [False])[-1] else \
                -1 if args.get('dis', [False])[-1] else 0
        b = '+'.join(args.get('b', []))
        phrase = '\n'.join(args.get('phrase', []))

        if b:
            save_roll = roll('1d20+' + b, adv=adv, inline=True)
        else:
            save_roll = roll('1d20', adv=adv, inline=True)

        embed = discord.Embed()
        embed.title = args.get(
            'title', '').replace('[charname]', character.get_name()).replace(
                '[sname]', 'Death') or '{} makes {}!'.format(
                    character.get_name(), "a Death Save")
        embed.colour = character.get_color()

        death_phrase = ''
        if save_roll.crit == 1:
            character.set_hp(1)
            death_phrase = f"{character.get_name()} is UP with 1 HP!"
        elif save_roll.crit == 2:
            if character.add_failed_ds():
                death_phrase = f"{character.get_name()} is DEAD!"
            else:
                if character.add_failed_ds():
                    death_phrase = f"{character.get_name()} is DEAD!"
        elif save_roll.total >= 10:
            if character.add_successful_ds():
                death_phrase = f"{character.get_name()} is STABLE!"
        else:
            if character.add_failed_ds():
                death_phrase = f"{character.get_name()} is DEAD!"

        character.commit(ctx)
        embed.description = save_roll.skeleton + ('\n*' + phrase +
                                                  '*' if phrase else '')
        if death_phrase: embed.set_footer(text=death_phrase)

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

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

        await self.bot.say(embed=embed)
Esempio n. 2
0
 def attack_effects(self, attacks):
     at = copy.deepcopy(attacks)
     to_parse = ''
     for e in self.get_effects():
         if e.effect:
             to_parse += f' {e.effect}'
     args = parse_args_3(shlex.split(to_parse))
     for a in at:
         if a['attackBonus'] is not None:
             a['attackBonus'] += f' + {"+".join(args["b"])}' if 'b' in args else ''
         if a['damage'] is not None:
             a['damage'] += f' + {"+".join(args["d"])}' if 'd' in args else ''
     return at
Esempio n. 3
0
 def ac(self):
     _ac = self._ac
     for e in self.get_effects():
         if e.effect:
             args = parse_args_3(shlex.split(e.effect))
             if 'ac' in args:
                 modi = args['ac'][-1]
                 try:
                     if modi.startswith(('+', '-')):
                         _ac += int(modi)
                     else:
                         _ac = int(modi)
                 except (ValueError, TypeError):
                     continue
     return _ac
Esempio n. 4
0
 async def customcounter_create(self, ctx, name, *args):
     """Creates a new custom counter.
     __Valid Arguments__
     `-reset <short|long|none>` - Counter will reset to max on a short/long rest, or not ever when "none". Default - will reset on a call of `!cc reset`.
     `-max <max value>` - The maximum value of the counter.
     `-min <min value>` - The minimum value of the counter.
     `-type <bubble|default>` - Whether the counter displays bubbles to show remaining uses or numbers. Default - numbers."""
     character = Character.from_ctx(ctx)
     args = parse_args_3(args)
     _reset = args.get('reset', [None])[-1]
     _max = args.get('max', [None])[-1]
     _min = args.get('min', [None])[-1]
     _type = args.get('type', [None])[-1]
     try:
         character.create_consumable(name, maxValue=_max, minValue=_min, reset=_reset, displayType=_type).commit(ctx)
     except InvalidArgument as e:
         return await self.bot.say(f"Failed to create counter: {e}")
     else:
         await self.bot.say(f"Custom counter created.")
Esempio n. 5
0
    async def _old_cast(self, ctx, spell_name, args):
        spell = getSpell(spell_name)
        self.bot.db.incr('spells_looked_up_life')
        if spell is None:
            return await self.bot.say("Spell not found.", delete_after=15)
        if spell.get('source') == "UAMystic":
            return await self.bot.say("Mystic talents are not supported.")

        char = Character.from_ctx(ctx)

        args = parse_snippets(args, ctx)
        args = await char.parse_cvars(args, ctx)
        args = shlex.split(args)
        args = parse_args_3(args)

        can_cast = True
        spell_level = int(spell.get('level', 0))
        try:
            cast_level = int(args.get('l', [spell_level])[-1])
            assert spell_level <= cast_level <= 9
        except (AssertionError, ValueError):
            return await self.bot.say("Invalid spell level.")

        # make sure we can cast it
        try:
            assert char.get_remaining_slots(cast_level) > 0
            assert spell_name in char.get_spell_list()
        except AssertionError:
            can_cast = False

        if args.get('i'):
            can_cast = True

        if not can_cast:
            embed = EmbedWithCharacter(char)
            embed.title = "Cannot cast spell!"
            embed.description = "Not enough spell slots remaining, or spell not in known spell list!\n" \
                                "Use `!game longrest` to restore all spell slots, or pass `-i` to ignore restrictions."
            if cast_level > 0:
                embed.add_field(name="Spell Slots",
                                value=char.get_remaining_slots_str(cast_level))
            return await self.bot.say(embed=embed)

        if len(args) == 0:
            rolls = spell.get('roll', None)
            if isinstance(rolls, list):
                active_character = self.bot.db.not_json_get(
                    'active_characters',
                    {}).get(ctx.message.author.id)  # get user's active
                if active_character is not None:
                    rolls = '\n'.join(rolls).replace('SPELL', str(char.get_spell_ab() - char.get_prof_bonus())) \
                        .replace('PROF', str(char.get_prof_bonus()))
                    rolls = rolls.split('\n')
                out = "**{} casts {}:** ".format(
                    ctx.message.author.mention, spell['name']) + '\n'.join(
                        roll(r, inline=True).skeleton for r in rolls)
            elif rolls is not None:
                active_character = self.bot.db.not_json_get(
                    'active_characters',
                    {}).get(ctx.message.author.id)  # get user's active
                if active_character is not None:
                    rolls = rolls.replace('SPELL', str(char.get_spell_ab() - char.get_prof_bonus())) \
                        .replace('PROF', str(char.get_prof_bonus()))
                out = "**{} casts {}:** ".format(
                    ctx.message.author.mention, spell['name']) + roll(
                        rolls, inline=True).skeleton
            else:
                out = "**{} casts {}!** ".format(ctx.message.author.mention,
                                                 spell['name'])
        else:
            rolls = args.get('r', [])
            roll_results = ""
            for r in rolls:
                res = roll(r, inline=True)
                if res.total is not None:
                    roll_results += res.result + '\n'
                else:
                    roll_results += "**Effect:** " + r
            out = "**{} casts {}:**\n".format(ctx.message.author.mention,
                                              spell['name']) + roll_results

        if not args.get('i'):
            char.use_slot(cast_level)
        if cast_level > 0:
            out += f"\n**Remaining Spell Slots**: {char.get_remaining_slots_str(cast_level)}"

        out = "Spell not supported by new cast, falling back to old cast.\n" + out
        char.commit(ctx)  # make sure we save changes
        await self.bot.say(out)
        spell_cmd = self.bot.get_command('spell')
        if spell_cmd is None:
            return await self.bot.say("Lookup cog not loaded.")
        await ctx.invoke(spell_cmd, name=spell['name'])
Esempio n. 6
0
    async def cast(self, ctx, spell_name, *, args=''):
        """Casts a spell.
        __Valid Arguments:__
        -i - Ignores Spellbook restrictions, for demonstrations or rituals.
        -l [level] - Specifies the level to cast the spell at.
        **__Save Spells__**
        -dc [Save DC] - Default: Pulls a cvar called `dc`.
        -save [Save type] - Default: The spell's default save.
        -d [damage] - adds additional damage.
        **__Attack Spells__**
        See `!a`.
        **__All Spells__**
        -phrase [phrase] - adds flavor text."""
        try:
            await self.bot.delete_message(ctx.message)
        except:
            pass

        char = None
        if not '-i' in args:
            char = Character.from_ctx(ctx)
            spell_name = await searchCharacterSpellName(spell_name, ctx, char)
        else:
            spell_name = await searchSpellNameFull(spell_name, ctx)

        if spell_name is None: return

        spell = strict_search(c.autospells, 'name', spell_name)
        if spell is None:
            return await self._old_cast(ctx, spell_name,
                                        args)  # fall back to old cast

        if not char: char = Character.from_ctx(ctx)

        args = parse_snippets(args, ctx)
        args = await char.parse_cvars(args, ctx)
        args = shlex.split(args)
        args = parse_args_3(args)

        can_cast = True
        spell_level = int(spell.get('level', 0))
        try:
            cast_level = int(args.get('l', [spell_level])[-1])
            assert spell_level <= cast_level <= 9
        except (AssertionError, ValueError):
            return await self.bot.say("Invalid spell level.")

        # make sure we can cast it
        try:
            assert char.get_remaining_slots(cast_level) > 0
            assert spell_name in char.get_spell_list()
        except AssertionError:
            can_cast = False

        if args.get('i'):
            can_cast = True

        if not can_cast:
            embed = EmbedWithCharacter(char)
            embed.title = "Cannot cast spell!"
            embed.description = "Not enough spell slots remaining, or spell not in known spell list!\n" \
                                "Use `!game longrest` to restore all spell slots, or pass `-i` to ignore restrictions."
            if cast_level > 0:
                embed.add_field(name="Spell Slots",
                                value=char.get_remaining_slots_str(cast_level))
            return await self.bot.say(embed=embed)

        args['l'] = [cast_level]
        args['name'] = [char.get_name()]
        args['dc'] = [args.get('dc', [char.get_save_dc()])[-1]]
        args['casterlevel'] = [char.get_level()]
        args['crittype'] = [char.get_setting('crittype', 'default')]
        args['ab'] = [char.get_spell_ab()]
        args['SPELL'] = [
            str(
                char.evaluate_cvar("SPELL")
                or (char.get_spell_ab() - char.get_prof_bonus()))
        ]

        result = sheet_cast(spell, args, EmbedWithCharacter(char, name=False))

        embed = result['embed']

        _fields = args.get('f', [])
        if type(_fields) == list:
            for f in _fields:
                title = f.split('|')[0] if '|' in f else '\u200b'
                value = "|".join(f.split('|')[1:]) if '|' in f else f
                embed.add_field(name=title, value=value)

        if not args.get('i'):
            char.use_slot(cast_level)
        if cast_level > 0:
            embed.add_field(name="Spell Slots",
                            value=char.get_remaining_slots_str(cast_level))

        char.commit(ctx)  # make sure we save changes
        await self.bot.say(embed=embed)
Esempio n. 7
0
    async def monster_save(self, ctx, monster_name, save, *args):
        """Rolls a save for a monster.
        Args: adv/dis
              -b [conditional bonus]
              -phrase [flavor text]
              -title [title] *note: [mname] and [cname] will be replaced automatically*
              -dc [dc]
              -rr [iterations]"""

        monster: Monster = await select_monster_full(ctx, monster_name)
        self.bot.db.incr('monsters_looked_up_life')
        monster_name = monster.get_title_name()

        saves = monster.saves

        try:
            save = next(a for a in saves.keys() if save.lower() == a.lower())
        except StopIteration:
            try:
                save = next(a for a in saves.keys()
                            if save.lower() in a.lower())
            except StopIteration:
                return await self.bot.say('That\'s not a valid save.')

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

        args = parse_args_3(args)
        adv = 0 if args.get(
            'adv', []) and args.get('dis', []) else 1 if args.get(
                'adv', False) else -1 if args.get('dis', False) else 0
        b = "+".join(args.get('b', [])) or None
        phrase = '\n'.join(args.get('phrase', [])) or None
        iterations = min(int(args.get('rr', [1])[-1]), 25)
        try:
            dc = int(args.get('dc', [None])[-1])
        except (ValueError, TypeError):
            dc = None
        num_successes = 0

        if b is not None:
            roll_str = '1d20{:+}'.format(saves[save]) + '+' + b
        else:
            roll_str = '1d20{:+}'.format(saves[save])

        embed.title = args.get('title', [''])[-1].replace('[mname]', monster_name).replace('[sname]', re.sub(
            r'((?<=[a-z])[A-Z]|(?<!\A)[A-Z](?=[a-z]))', r' \1', save).title()) or \
                      '{} makes {}!'.format(monster_name,
                                            a_or_an(re.sub(
                                                r'((?<=[a-z])[A-Z]|(?<!\A)[A-Z](?=[a-z]))',
                                                r' \1',
                                                save).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, adv=adv, 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, adv=adv, 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.get('image') is not None:
            embed.set_thumbnail(url=args.get('image'))
        else:
            embed.set_thumbnail(url=monster.get_image_url())

        if monster.source == 'homebrew':
            embed.set_footer(text="Homebrew content.",
                             icon_url="https://avrae.io/static/homebrew.png")

        await self.bot.say(embed=embed)
        try:
            await self.bot.delete_message(ctx.message)
        except:
            pass
Esempio n. 8
0
    async def monster_check(self, ctx, monster_name, check, *args):
        """Rolls a check for a monster.
        Args: adv/dis
              -b [conditional bonus]
              -phrase [flavor text]
              -title [title] *note: [mname] and [cname] will be replaced automatically*
              -dc [dc]
              -rr [iterations]
              str/dex/con/int/wis/cha (different skill base; e.g. Strength (Intimidation))"""

        monster: Monster = await select_monster_full(ctx, monster_name)
        self.bot.db.incr('monsters_looked_up_life')

        monster_name = monster.get_title_name()
        skills = monster.skills

        try:
            skill = next(a for a in skills.keys() if check.lower() == a.lower())
        except StopIteration:
            try:
                skill = next(a for a in skills.keys() if check.lower() in a.lower())
            except StopIteration:
                return await self.bot.say('That\'s not a valid check.')

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

        args = parse_args_3(args)
        adv = 0 if args.get('adv', []) and args.get('dis', []) else 1 if args.get('adv', False) else -1 if args.get(
            'dis', False) else 0
        b = "+".join(args.get('b', [])) or None
        phrase = '\n'.join(args.get('phrase', [])) or None
        formatted_d20 = '1d20' if adv == 0 else '2d20' + ('kh1' if adv == 1 else 'kl1')
        iterations = min(int(args.get('rr', [1])[-1]), 25)
        try:
            dc = int(args.get('dc', [None])[-1])
        except (ValueError, TypeError):
            dc = None
        num_successes = 0

        mod = skills[skill]
        skill_name = skill
        if any(args.get(s) for s in ("str", "dex", "con", "int", "wis", "cha")):
            base = next(s for s in ("str", "dex", "con", "int", "wis", "cha") if args.get(s))
            mod = mod - monster.get_mod(SKILL_MAP[skill]) + monster.get_mod(base)
            skill_name = f"{verbose_stat(base)} ({skill})"

        skill_name = skill_name.title()
        default_title = '{} makes {} check!'.format(monster_name, a_or_an(skill_name))

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

        embed.title = args.get('title', '') \
                          .replace('[mname]', 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, adv=adv, 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, adv=adv, 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.get('image') is not None:
            embed.set_thumbnail(url=args.get('image'))
        else:
            embed.set_thumbnail(url=monster.get_image_url())

        if monster.source == 'homebrew':
            embed.set_footer(text="Homebrew content.", icon_url="https://avrae.io/static/homebrew.png")

        await self.bot.say(embed=embed)
        try:
            await self.bot.delete_message(ctx.message)
        except:
            pass