コード例 #1
0
    async def attack_add(self, ctx, name, *args):
        """
        Adds an attack to the active character.
        __Arguments__
        -d [damage]: How much damage the attack should do.
        -b [to-hit]: The to-hit bonus of the attack.
        -desc [description]: A description of the attack.
        """
        character: Character = await Character.from_ctx(ctx)
        parsed = argparse(args)

        attack = Attack.new(name,
                            bonus_calc=parsed.join('b', '+'),
                            damage_calc=parsed.join('d', '+'),
                            details=parsed.join('desc', '\n'))

        conflict = next((a for a in character.overrides.attacks
                         if a.name.lower() == attack.name.lower()), None)
        if conflict:
            character.overrides.attacks.remove(conflict)
        character.overrides.attacks.append(attack)
        await character.commit(ctx)

        out = f"Created attack {attack.name}!"
        if conflict:
            out += f" Removed a duplicate attack."
        await ctx.send(out)
コード例 #2
0
def getJSON_gsheet(url):
    parser = GoogleSheet(url)
    char = asyncio.get_event_loop().run_until_complete(parser.load_character("", argparse("")))
    '''print(json.dumps(parser.calculated_stats, indent=2))
    print(f"set: {parser.set_calculated_stats}")
    input("press enter to view character data")'''
    return json.dumps(char.to_dict(), indent=2)
コード例 #3
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)

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

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

        checkutils.run_save(save_stat, 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)
コード例 #4
0
async def maybe_combat(ctx, caster, args, allow_groups=True):
    """
    If channel not in combat: returns caster, target_list, None unmodified.
    If channel in combat but caster not: returns caster, list of combatants, combat.
    If channel in combat and caster in combat: returns caster as combatant, list of combatants, combat.
    """
    target_args = args.get('t')
    targets = []

    try:
        combat = await Combat.from_ctx(ctx)
    except CombatNotFound:
        for i, target in enumerate(target_args):
            if '|' in target:
                target, contextargs = target.split('|', 1)
                args.add_context(target, argparse(contextargs))
            targets.append(target)
        return caster, targets, None

    # get targets as Combatants
    targets = await definitely_combat(combat, args, allow_groups)

    # get caster as Combatant if caster in combat
    if isinstance(caster, Character):
        caster = next((c for c in combat.get_combatants()
                       if getattr(c, 'character_id', None) == caster.upstream),
                      caster)
    return caster, targets, combat
コード例 #5
0
async def definitely_combat(combat, args, allow_groups=True):
    target_args = args.get('t')
    targets = []

    for i, t in enumerate(target_args):
        contextargs = None
        if '|' in t:
            t, contextargs = t.split('|', 1)
            contextargs = argparse(contextargs)

        try:
            target = await combat.select_combatant(t,
                                                   f"Select target #{i + 1}.",
                                                   select_group=allow_groups)
        except SelectionException:
            raise InvalidArgument(f"Target {t} not found.")

        if isinstance(target, CombatantGroup):
            for combatant in target.get_combatants():
                if contextargs:
                    args.add_context(combatant, contextargs)
                targets.append(combatant)
        else:
            if contextargs:
                args.add_context(target, contextargs)
            targets.append(target)

    return targets
コード例 #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.
        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)

        char: Character = await Character.from_ctx(ctx)

        args = await helpers.parse_snippets(args, ctx)
        args = await char.parse_cvars(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 char.spellbook)
        else:
            spell = await select_spell_full(ctx, spell_name)

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

        embed = result['embed']
        embed.colour = char.get_color()
        embed.set_thumbnail(url=char.image)

        add_fields_from_args(embed, args.get('f'))
        if 'thumb' in args:
            embed.set_thumbnail(url=args.last('thumb'))

        # save changes: combat state, spell slot usage
        await char.commit(ctx)
        if combat:
            await combat.final()
        await ctx.send(embed=embed)
コード例 #7
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)
        args = argparse(args)
        adv = args.adv()
        b = args.join('b', '+')
        phrase = args.join('phrase', '\n')

        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.last('title', '') \
                          .replace('[charname]', character.name) \
                          .replace('[sname]', 'Death') \
                      or '{} makes {}!'.format(character.name, "a Death Save")
        embed.colour = character.get_color()

        death_phrase = ''
        if save_roll.crit == 1:
            character.hp = 1
        elif save_roll.crit == 2:
            character.death_saves.fail(2)
        elif save_roll.total >= 10:
            character.death_saves.succeed()
        else:
            character.death_saves.fail()

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

        await 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=str(character.death_saves))

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

        await ctx.send(embed=embed)
コード例 #8
0
    async def update(self, ctx, *args):
        """Updates the current character sheet, preserving all settings.
        __Valid Arguments__
        `-v` - Shows character sheet after update is complete.
        `-cc` - Updates custom counters from Dicecloud."""
        old_character: Character = await Character.from_ctx(ctx)
        url = old_character.upstream
        args = argparse(args)

        prefixes = 'dicecloud-', 'google-', 'beyond-'
        _id = url[:]
        for p in prefixes:
            if url.startswith(p):
                _id = url[len(p):]
                break
        sheet_type = old_character.sheet_type
        if sheet_type == 'dicecloud':
            parser = DicecloudParser(_id)
            loading = await ctx.send(
                'Updating character data from Dicecloud...')
        elif sheet_type == 'google':
            parser = GoogleSheet(_id)
            loading = await ctx.send('Updating character data from Google...')
        elif sheet_type == 'beyond':
            parser = BeyondSheetParser(_id)
            loading = await ctx.send('Updating character data from Beyond...')
        else:
            return await ctx.send(f"Error: Unknown sheet type {sheet_type}.")

        try:
            character = await parser.load_character(str(ctx.author.id), args)
        except ExternalImportError as eep:
            return await loading.edit(content=f"Error loading character: {eep}"
                                      )
        except Exception as eep:
            log.warning(f"Error importing character {old_character.upstream}")
            log.warning(traceback.format_exc())
            return await loading.edit(content=f"Error loading character: {eep}"
                                      )

        character.update(old_character)

        await character.commit(ctx)
        await character.set_active(ctx)
        await loading.edit(
            content=f"Updated and saved data for {character.name}!")
        if args.last('v'):
            await ctx.send(embed=character.get_sheet_embed())
コード例 #9
0
    async def _load_sheet(ctx, parser, args, loading):
        try:
            character = await parser.load_character(str(ctx.author.id),
                                                    argparse(args))
        except ExternalImportError as eep:
            return await loading.edit(content=f"Error loading character: {eep}"
                                      )
        except Exception as eep:
            log.warning(f"Error importing character {parser.url}")
            log.warning(traceback.format_exc())
            return await loading.edit(content=f"Error loading character: {eep}"
                                      )

        await loading.edit(
            content=f'Loaded and saved data for {character.name}!')

        await character.commit(ctx)
        await character.set_active(ctx)
        await ctx.send(embed=character.get_sheet_embed())
コード例 #10
0
    async def embed(self, ctx, *, args):
        """Creates and prints an Embed.
        __Valid Arguments__
        -title <title>
        -desc <description text>
        -thumb <image url>
        -image <image url>
        -footer <footer text>
        -f "<Field Title>|<Field Text>[|inline]"
            (e.g. "Donuts|I have 15 donuts|inline" for an inline field, or "Donuts|I have 15 donuts" for one with its own line.)
        -color <hex color>
        -t <timeout (0..600)>
        """
        await try_delete(ctx.message)

        embed = embeds.EmbedWithAuthor(ctx)
        args = argparse(args)
        embed.title = args.last('title')
        embed.description = args.last('desc')
        embed.set_thumbnail(url=args.last('thumb', '') if 'http' in
                            str(args.last('thumb')) else '')
        embed.set_image(url=args.last('image', '') if 'http' in
                        str(args.last('image')) else '')
        embed.set_footer(text=args.last('footer', ''))
        try:
            embed.colour = int(args.last('color', "0").strip('#'), base=16)
        except:
            pass

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

        timeout = 0
        if 't' in args:
            try:
                timeout = min(max(args.last('t', type_=int), 0), 600)
            except:
                pass

        if timeout:
            await ctx.send(embed=embed, delete_after=timeout)
        else:
            await ctx.send(embed=embed)
コード例 #11
0
    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

        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)
コード例 #12
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 = await Character.from_ctx(ctx)

        conflict = next(
            (c
             for c in character.consumables if c.name.lower() == name.lower()),
            None)
        if conflict:
            if await confirm(
                    ctx,
                    "Warning: This will overwrite an existing consumable. Continue?"
            ):
                character.consumables.remove(conflict)
            else:
                return await ctx.send("Overwrite unconfirmed. Aborting.")

        args = argparse(args)
        _reset = args.last('reset')
        _max = args.last('max')
        _min = args.last('min')
        _type = args.last('type')
        try:
            new_counter = CustomCounter.new(character,
                                            name,
                                            maxv=_max,
                                            minv=_min,
                                            reset=_reset,
                                            display_type=_type)
            character.consumables.append(new_counter)
            await character.commit(ctx)
        except InvalidArgument as e:
            return await ctx.send(f"Failed to create counter: {e}")
        else:
            await ctx.send(f"Custom counter created.")
コード例 #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)

        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)
コード例 #14
0
            if node.id in self.functions:
                return self.functions[node.id]
        raise NameNotDefined(node.id, self.expr)

    def _eval_call(self, node):
        if isinstance(node.func, ast.Attribute):
            func = self._eval(node.func)
        elif isinstance(node.func, ast.Num):
            func = lambda n: n * self._eval(node.func)
        else:
            try:
                func = self.functions[node.func.id]
            except KeyError:
                raise FunctionNotDefined(node.func.id, self.expr)

        return func(*(self._eval(a) for a in node.args),
                    **dict(self._eval(k) for k in node.keywords))


if __name__ == '__main__':
    import asyncio
    import json
    from api.avrae.utils.argparser import argparse

    while True:
        url_ = input("Dicecloud sheet ID: ")
        parser = DicecloudParser(url_)
        char = asyncio.get_event_loop().run_until_complete(
            parser.load_character('', argparse('')))
        print(json.dumps(char.to_dict(), indent=2))
コード例 #15
0
 async def new_arg_stuff(args, ctx, character):
     args = await helpers.parse_snippets(args, ctx)
     args = await character.parse_cvars(args, ctx)
     args = argparse(args)
     return args