async def gsheet(self, ctx, url: str):
        """Loads a character sheet from [GSheet v2.0](http://gsheet2.avrae.io) (auto) or [GSheet v1.3](http://gsheet.avrae.io) (manual), resetting all settings.
        The sheet must be shared with Avrae for this to work.
        Avrae's google account is `[email protected]`."""

        loading = await ctx.send('Loading character data from Google... (This usually takes ~30 sec)')
        try:
            url = extract_gsheet_id_from_url(url)
        except NoValidUrlKeyFound:
            return await loading.edit(content="This is not a Google Sheets link.")

        override = await self._confirm_overwrite(ctx, f"google-{url}")
        if not override: return await ctx.send("Character overwrite unconfirmed. Aborting.")

        try:
            parser = GoogleSheet(url, self.gsheet_client)
        except AssertionError:
            await self.init_gsheet_client()  # hmm.
            return await loading.edit(content="I am still connecting to Google. Try again in 15-30 seconds.")

        try:
            await parser.get_character()
        except (KeyError, SpreadsheetNotFound):
            return await loading.edit(content=
                                      "Invalid character sheet. Make sure you've shared it with me at "
                                      "`[email protected]`!")
        except HttpError:
            return await loading.edit(content=
                                      "Error: Google returned an error. Please ensure your sheet is shared with "
                                      "`[email protected]` and try again in a few minutes.")
        except Exception as e:
            return await loading.edit(content='Error: Could not load character sheet.\n' + str(e))

        try:
            sheet = await parser.get_sheet()
        except Exception as e:
            traceback.print_exception(type(e), e, e.__traceback__, file=sys.stderr)
            return await loading.edit(content='Error: Invalid character sheet.\n' + str(e))

        try:
            await loading.edit(content=
                               'Loaded and saved data for {}!'.format(sheet['sheet']['stats']['name']))
        except TypeError as e:
            traceback.print_exception(type(e), e, e.__traceback__, file=sys.stderr)
            return await loading.edit(content=
                                      'Invalid character sheet. Make sure you have shared the sheet so that anyone with the link can view.')

        char = Character(sheet['sheet'], f"google-{url}").initialize_consumables()
        await char.commit(ctx)
        await char.set_active(ctx)

        try:
            await ctx.send(embed=char.get_sheet_embed())
        except:
            await ctx.send(
                "...something went wrong generating your character sheet. Don't worry, your character has been saved. "
                "This is usually due to an invalid image.")
示例#2
0
 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 self.bot.say("Invalid spell level.")
     character = 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.get_remaining_slots_str()}"
     elif value is None:
         embed.description = f"__**Remaining Level {level} Spell Slots**__\n{character.get_remaining_slots_str(level)}"
     else:
         try:
             if value.startswith(('+', '-')):
                 value = character.get_remaining_slots(level) + int(value)
             else:
                 value = int(value)
         except ValueError:
             return await self.bot.say(f"{value} is not a valid integer.")
         try:
             assert 0 <= value <= character.get_max_spellslots(level)
         except AssertionError:
             raise CounterOutOfBounds()
         character.set_remaining_slots(level, value).commit(ctx)
         embed.description = f"__**Remaining Level {level} Spell Slots**__\n{character.get_remaining_slots_str(level)}"
     await self.bot.say(embed=embed)
示例#3
0
    async def character(self, ctx, *, name: str = None):
        """Switches the active character."""
        user_characters = await self.bot.mdb.characters.find({
            "owner":
            str(ctx.author.id)
        }).to_list(None)
        if not user_characters:
            return await ctx.send('You have no characters.')

        if name is None:
            active_character: Character = await Character.from_ctx(ctx)
            return await ctx.send(f'Currently active: {active_character.name}')

        selected_char = await search_and_select(
            ctx,
            user_characters,
            name,
            lambda e: e['name'],
            selectkey=lambda e: f"{e['name']} (`{e['upstream']}`)")

        char = Character.from_dict(selected_char)
        await char.set_active(ctx)

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

        await ctx.send(f"Active character changed to {char.name}.",
                       delete_after=20)
示例#4
0
    def sync_hp(
        char: Character,
        hp_info: scds_types.SimplifiedHitPointInfo
    ) -> SyncHPResult:
        old_hp = new_hp = char.hp
        old_max = new_max = char.max_hp
        old_temp = char.temp_hp

        # if the character's current hp is greater than its canonical max (i.e. not considering combats) and ddb says
        # the character is at full, skip hp sync - the character's hp may have been updated in some combat somewhere
        # which may or may not be the combat in the sync channel (which is the *combat* local here)
        # (demorgans: char.hp > char.max_hp and hp_info.current == hp_info.maximum)
        if char.hp <= char.max_hp or hp_info.current != hp_info.maximum:
            # otherwise, we can sync it up
            char.hp = new_hp = hp_info.current
            char.max_hp = new_max = hp_info.maximum
        char.temp_hp = hp_info.temp

        # build display message
        delta = new_hp - old_hp
        deltaend = f" ({delta:+})" if delta else ""
        message = f"{char.hp_str()}{deltaend}"

        return SyncHPResult(
            changed=any((old_hp != new_hp, old_max != new_max, old_temp != hp_info.temp)),
            old_hp=old_hp, old_max=old_max, old_temp=old_temp,
            delta=delta, message=message
        )
示例#5
0
 async def spellbook_addall(self, ctx, _class, level: int, spell_list=None):
     """Adds all spells of a given level from a given class list to the spellbook override. Requires live sheet.
     If `spell_list` is passed, will add these spells to the list named so in Dicecloud."""
     character = Character.from_ctx(ctx)
     if not character.live:
         return await self.bot.say(
             "This command requires a live Dicecloud sheet. To set up, share your Dicecloud "
             "sheet with `avrae` with edit permissions, then `!update`.")
     if not 0 <= level < 10:
         return await self.bot.say("Invalid spell level.")
     class_spells = [
         sp for sp in c.spells if _class.lower() in
         [cl.lower() for cl in sp['classes'].split(', ') if not '(' in cl]
     ]
     if len(class_spells) == 0:
         return await self.bot.say("No spells for that class found.")
     level_spells = [s for s in class_spells if str(level) == s['level']]
     try:
         await DicecloudClient.getInstance().sync_add_mass_spells(
             character, [dicecloud_parse(s) for s in level_spells],
             spell_list)
         character.commit(ctx)
     except MeteorClient.MeteorClientException:
         return await self.bot.say(
             "Error: Failed to connect to Dicecloud. The site may be down.")
     await self.bot.say(
         f"{len(level_spells)} spells added to {character.get_name()}'s spell list on Dicecloud."
     )
示例#6
0
    async def spellbook(self, ctx):
        """Commands to display a character's known spells and metadata."""
        character = 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)
示例#7
0
    async def spellbook_add(self, ctx, *, spell_name):
        """Adds a spell to the spellbook override. If character is live, will add to sheet as well."""
        result = searchSpell(spell_name)
        if result is None:
            return await self.bot.say('Spell not found.')
        strict = result[1]
        results = result[0]

        if strict:
            result = results
        else:
            if len(results) == 1:
                result = results[0]
            else:
                result = await get_selection(ctx, [(r, r) for r in results])
                if result is None:
                    return await self.bot.say(
                        'Selection timed out or was cancelled.')
        spell = getSpell(result)
        character = Character.from_ctx(ctx)
        if character.live:
            try:
                await DicecloudClient.getInstance().sync_add_spell(
                    character, dicecloud_parse(spell))
            except MeteorClient.MeteorClientException:
                return await self.bot.say(
                    "Error: Failed to connect to Dicecloud. The site may be down."
                )
        character.add_known_spell(spell).commit(ctx)
        live = "Spell added to Dicecloud!" if character.live else ''
        await self.bot.say(
            f"{spell['name']} added to known spell list!\n{live}")
    async def character(self, ctx, *, name: str = None):
        """Switches the active character.
        Breaks for characters created before Jan. 20, 2017."""
        user_characters = await self.bot.mdb.characters.find({"owner": str(ctx.author.id)}).to_list(None)
        active_character = next((c for c in user_characters if c['active']), None)
        if not user_characters:
            return await ctx.send('You have no characters.')

        if name is None:
            if active_character is None:
                return await ctx.send('You have no character active.')
            return await ctx.send(
                'Currently active: {}'.format(active_character.get('stats', {}).get('name')))

        _character = await search_and_select(ctx, user_characters, name, lambda e: e.get('stats', {}).get('name', ''),
                                             selectkey=lambda
                                                 e: f"{e.get('stats', {}).get('name', '')} (`{e['upstream']}`)")

        char_name = _character.get('stats', {}).get('name', 'Unnamed')
        char_url = _character['upstream']

        name = char_name

        char = Character(_character, char_url)
        await char.set_active(ctx)

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

        await ctx.send("Active character changed to {}.".format(name), delete_after=20)
示例#9
0
    async def game_hp(self, ctx, operator='', *, hp=''):
        """Modifies the HP of a the current active character. Synchronizes live with Dicecloud.
        If operator is not passed, assumes `mod`.
        Operators: `mod`, `set`."""
        character = Character.from_ctx(ctx)

        if not operator == '':
            hp_roll = roll(hp, inline=True, show_blurbs=False)

            if 'mod' in operator.lower():
                character.modify_hp(hp_roll.total)
            elif 'set' in operator.lower():
                character.set_hp(hp_roll.total, True)
            elif 'max' in operator.lower() and not hp:
                character.set_hp(character.get_max_hp(), True)
            elif hp == '':
                hp_roll = roll(operator, inline=True, show_blurbs=False)
                hp = operator
                character.modify_hp(hp_roll.total)
            else:
                await self.bot.say("Incorrect operator. Use mod or set.")
                return

            character.commit(ctx)
            out = "{}: {}".format(character.get_name(), character.get_hp_str())
            if 'd' in hp: out += '\n' + hp_roll.skeleton
        else:
            out = "{}: {}".format(character.get_name(), character.get_hp_str())

        await self.bot.say(out)
示例#10
0
    async def character(self, ctx, *, name: str = None):
        """Switches the active character."""
        if name is None:
            embed = await self._active_character_embed(ctx)
            await ctx.send(embed=embed)
            return

        user_characters = await self.bot.mdb.characters.find({
            "owner":
            str(ctx.author.id)
        }).to_list(None)
        if not user_characters:
            return await ctx.send('You have no characters.')

        selected_char = await search_and_select(
            ctx,
            user_characters,
            name,
            lambda e: e['name'],
            selectkey=lambda e: f"{e['name']} (`{e['upstream']}`)")

        char = Character.from_dict(selected_char)
        result = await char.set_active(ctx)
        await try_delete(ctx.message)
        if result.did_unset_server_active:
            await ctx.send(
                f"Active character changed to {char.name}. Your server active character has been unset.",
                delete_after=30)
        else:
            await ctx.send(f"Active character changed to {char.name}.",
                           delete_after=15)
示例#11
0
 async def customcounter_reset(self, ctx, *args):
     """Resets custom counters, hp, death saves, and spell slots.
     Will reset all if name is not passed, otherwise the specific passed one.
     A counter can only be reset if it has a maximum value.
     Reset hierarchy: short < long < default < none
     __Valid Arguments__
     -h - Hides the character summary output."""
     character = Character.from_ctx(ctx)
     try:
         name = args[0]
     except IndexError:
         name = None
     else:
         if name == '-h': name = None
     if name:
         try:
             character.reset_consumable(name).commit(ctx)
         except ConsumableException as e:
             return await self.bot.say(f"Counter could not be reset: {e}")
         else:
             return await self.bot.say(
                 f"Counter reset to {character.get_consumable(name)['value']}."
             )
     else:
         reset_consumables = character.reset_all_consumables()
         character.commit(ctx)
         await self.bot.say(
             f"Reset counters: {', '.join(set(reset_consumables)) or 'none'}"
         )
     if not '-h' in args:
         await ctx.invoke(self.game_status)
示例#12
0
 async def customcounter_summary(self, ctx):
     """Prints a summary of all custom counters."""
     character = 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 self.bot.say(embed=embed)
示例#13
0
 async def customcounter_delete(self, ctx, name):
     """Deletes a custom counter."""
     character = Character.from_ctx(ctx)
     try:
         character.delete_consumable(name).commit(ctx)
     except ConsumableNotFound:
         return await self.bot.say("Counter not found. Make sure you're using the full name, case-sensitive.")
     await self.bot.say(f"Deleted counter {name}.")
示例#14
0
    async def load_character(self, ctx, args):
        """
        Downloads and parses the character data, returning a fully-formed Character object.
        :raises ExternalImportError if something went wrong during the import that we can expect
        :raises Exception if something weirder happened
        """
        self.ctx = ctx

        owner_id = str(ctx.author.id)
        await self._get_character()

        upstream = f"beyond-{self.url}"
        active = False
        sheet_type = "beyond"
        import_version = SHEET_VERSION
        name = self.character_data['name'].strip()
        description = self.character_data['description']
        image = self.character_data['image'] or ''

        stats = self._get_stats()
        levels = self._get_levels()
        attacks = self._get_attacks()
        skills = self._get_skills()
        saves = self._get_saves()

        resistances = self._get_resistances()
        ac = self._get_ac()
        max_hp, hp, temp_hp = self._get_hp()

        cvars = {}
        options = {}
        overrides = {}
        death_saves = {}

        consumables = []
        if not args.last('nocc'):
            consumables = self._get_custom_counters()

        spellbook = self._get_spellbook()
        live = None  # todo
        race = self._get_race()
        background = self._get_background()

        # ddb campaign
        campaign = self.character_data.get('campaign')
        campaign_id = None
        if campaign is not None:
            campaign_id = str(campaign['id'])

        character = Character(
            owner_id, upstream, active, sheet_type, import_version, name, description, image, stats, levels, attacks,
            skills, resistances, saves, ac, max_hp, hp, temp_hp, cvars, options, overrides, consumables, death_saves,
            spellbook, live, race, background,
            ddb_campaign_id=campaign_id
        )
        return character
示例#15
0
 async def game_status(self, ctx):
     """Prints the status of the current active character."""
     character = 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 self.bot.say(embed=embed)
示例#16
0
    async def load_character(self, owner_id: str, args):
        """
        Downloads and parses the character data, returning a fully-formed Character object.
        :raises ExternalImportError if something went wrong during the import that we can expect
        :raises Exception if something weirder happened
        """
        try:
            await self.get_character()
        except DicecloudException as e:
            raise ExternalImportError(f"Dicecloud returned an error: {e}")

        upstream = f"dicecloud-{self.url}"
        active = False
        sheet_type = "dicecloud"
        import_version = 15
        name = self.character_data['characters'][0]['name'].strip()
        description = self.character_data['characters'][0]['description']
        image = self.character_data['characters'][0]['picture']

        stats = self.get_stats().to_dict()
        levels = self.get_levels().to_dict()
        attacks = self.get_attacks()

        skls, svs = self.get_skills_and_saves()
        skills = skls.to_dict()
        saves = svs.to_dict()

        resistances = self.get_resistances().to_dict()
        ac = self.get_ac()
        max_hp = int(self.calculate_stat('hitPoints'))
        hp = max_hp
        temp_hp = 0

        cvars = {}
        options = {}
        overrides = {}
        death_saves = {}

        consumables = []
        if args.last('cc'):
            consumables = self.get_custom_counters()

        spellbook = self.get_spellbook().to_dict()
        live = self.is_live()
        race = self.character_data['characters'][0]['race'].strip()
        background = self.character_data['characters'][0]['backstory'].strip()

        character = Character(owner_id, upstream, active, sheet_type,
                              import_version, name, description, image, stats,
                              levels, attacks, skills, resistances, saves, ac,
                              max_hp, hp, temp_hp, cvars, options, overrides,
                              consumables, death_saves, spellbook, live, race,
                              background)
        return character
示例#17
0
    def character(self):
        if self._character is None:
            from cogs5e.models.character import Character
            c = Character.from_bot_and_ids(self.ctx.bot, self.character_owner, self.character_id)

            def new_commit(ctx):
                c.manual_commit(ctx.bot, self.character_owner)

            c.commit = new_commit
            self._character = c
        return self._character
示例#18
0
    def from_dict_sync(cls, raw, ctx, combat):
        inst = super(PlayerCombatant, cls).from_dict(raw, ctx, combat)
        inst.character_id = raw['character_id']
        inst.character_owner = raw['character_owner']

        from cogs5e.models.character import Character
        char = ctx.bot.mdb.characters.delegate.find_one({"owner": inst.character_owner, "upstream": inst.character_id})
        if char is None:
            raise CombatException(f"A character in combat was deleted. "
                                  f"Please run `{ctx.prefix}init end -force` to end combat.")
        inst._character = Character.from_dict(char)
        return inst
示例#19
0
    async def game_deathsave_reset(self, ctx):
        """Resets all death saves."""
        character = Character.from_ctx(ctx)
        character.reset_death_saves()
        embed = EmbedWithCharacter(character)
        embed.title = f'{character.get_name()} reset Death Saves!'

        character.commit(ctx)

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

        await self.bot.say(embed=embed)
示例#20
0
    def from_dict_sync(cls, raw, ctx, combat):
        inst = super().from_dict(raw, ctx, combat)
        inst.character_id = raw['character_id']
        inst.character_owner = raw['character_owner']

        try:
            from cogs5e.models.character import Character
            inst._character = Character.from_bot_and_ids_sync(ctx.bot, inst.character_owner, inst.character_id)
        except NoCharacter:
            raise CombatException(f"A character in combat was deleted. "
                                  f"Please run `{ctx.prefix}init end -force` to end combat.")
        return inst
示例#21
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)
示例#22
0
 async def game_longrest(self, ctx, *args):
     """Performs a long rest, resetting applicable counters.
     __Valid Arguments__
     -h - Hides the character summary output."""
     character = Character.from_ctx(ctx)
     reset = character.long_rest()
     embed = EmbedWithCharacter(character, name=False)
     embed.title = f"{character.get_name()} took a Long 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)
    async def beyond(self, ctx, url: str):
        """Loads a character sheet from D&D Beyond, resetting all settings."""

        loading = await ctx.send('Loading character data from Beyond...')
        url = re.search(r"/characters/(\d+)", url)
        if url is None:
            return await loading.edit(content="This is not a D&D Beyond link.")
        url = url.group(1)

        override = await self._confirm_overwrite(ctx, f"beyond-{url}")
        if not override: return await ctx.send("Character overwrite unconfirmed. Aborting.")

        parser = BeyondSheetParser(url)

        try:
            character = await parser.get_character()
        except Exception as e:
            return await loading.edit(content='Error: Could not load character sheet.\n' + str(e))

        try:
            sheet = parser.get_sheet()
        except Exception as e:
            traceback.print_exception(type(e), e, e.__traceback__, file=sys.stderr)
            return await loading.edit(content='Error: Invalid character sheet.\n' + str(e))

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

        char = Character(sheet['sheet'], f"beyond-{url}").initialize_consumables()
        await char.commit(ctx)
        await char.set_active(ctx)

        try:
            await ctx.send(embed=char.get_sheet_embed())
        except:
            await ctx.send(
                "...something went wrong generating your character sheet. Don't worry, your character has been saved. "
                "This is usually due to an invalid image.")
示例#24
0
    async def game_thp(self, ctx, thp: int = None):
        """Modifies the temp HP of a the current active character.
        If positive, assumes set; if negative, assumes mod."""
        character = Character.from_ctx(ctx)

        if thp is not None:
            if thp >= 0:
                character.set_temp_hp(thp)
            else:
                character.set_temp_hp(character.get_temp_hp() + thp)

            character.commit(ctx)

        out = "{}: {}".format(character.get_name(), character.get_hp_str())
        await self.bot.say(out)
示例#25
0
 async def spellbook_remove(self, ctx, *, spell_name):
     """
     Removes a spell from the spellbook override. Must type in full name.
     """
     character = Character.from_ctx(ctx)
     if character.live:
         return await self.bot.say("Just delete the spell from your character sheet!")
     spell = character.remove_known_spell(spell_name)
     if spell:
         character.commit(ctx)
         await self.bot.say(f"{spell} removed from spellbook override.")
     else:
         await self.bot.say(
             f"Spell not in spellbook override. Make sure you typed the full spell name. "
             f"To remove a spell on your sheet, just delete it from your sheet.")
示例#26
0
    async def load_character(self, owner_id: str, args):
        """
        Downloads and parses the character data, returning a fully-formed Character object.
        :raises ExternalImportError if something went wrong during the import that we can expect
        :raises Exception if something weirder happened
        """
        await self.get_character()

        upstream = f"beyond-{self.url}"
        active = False
        sheet_type = "beyond"
        import_version = 15
        name = self.character_data['name'].strip()
        description = self.character_data['traits']['appearance']
        image = self.character_data.get('avatarUrl') or ''

        stats = self.get_stats().to_dict()
        levels = self.get_levels().to_dict()
        attacks = self.get_attacks()

        skls, svs = self.get_skills_and_saves()
        skills = skls.to_dict()
        saves = svs.to_dict()

        resistances = self.get_resistances().to_dict()
        ac = self.get_ac()
        max_hp = self.get_hp()
        hp = max_hp
        temp_hp = 0

        cvars = {}
        options = {}
        overrides = {}
        death_saves = {}
        consumables = []

        spellbook = self.get_spellbook().to_dict()
        live = None
        race = self.character_data['race']['fullName']
        background = self.get_background()

        character = Character(owner_id, upstream, active, sheet_type,
                              import_version, name, description, image, stats,
                              levels, attacks, skills, resistances, saves, ac,
                              max_hp, hp, temp_hp, cvars, options, overrides,
                              consumables, death_saves, spellbook, live, race,
                              background)
        return character
示例#27
0
    async def game_deathsave_fail(self, ctx):
        """Adds a failed death save."""
        character = Character.from_ctx(ctx)

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

        death_phrase = ''
        if character.add_failed_ds(): death_phrase = f"{character.get_name()} is DEAD!"

        character.commit(ctx)
        embed.description = "Added 1 failed death save."
        if death_phrase: embed.set_footer(text=death_phrase)

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

        await self.bot.say(embed=embed)
示例#28
0
    async def handle_aliases(self, message):
        if message.content.startswith(self.bot.prefix):
            alias = self.bot.prefix.join(
                message.content.split(self.bot.prefix)[1:]).split(' ')[0]
            if not message.channel.is_private:
                command = self.aliases.get(message.author.id, {}).get(alias) or \
                          self.serv_aliases.get(message.server.id, {}).get(alias)
            else:
                command = self.aliases.get(message.author.id, {}).get(alias)
            if command:
                try:
                    message.content = self.handle_alias_arguments(
                        command, message)
                except UserInputError as e:
                    return await self.bot.send_message(message.channel,
                                                       f"Invalid input: {e}")
                # message.content = message.content.replace(alias, command, 1)
                ctx = Context(self.bot, message)
                char = None
                try:
                    char = Character.from_ctx(ctx)
                except NoCharacter:
                    pass

                try:
                    if char:
                        message.content = await char.parse_cvars(
                            message.content, ctx)
                    else:
                        message.content = await self.parse_no_char(
                            message.content, ctx)
                except EvaluationError as err:
                    e = err.original
                    if not isinstance(e, AvraeException):
                        tb = f"```py\n{''.join(traceback.format_exception(type(e), e, e.__traceback__, limit=0, chain=False))}\n```"
                        try:
                            await self.bot.send_message(message.author, tb)
                        except Exception:
                            pass
                    return await self.bot.send_message(message.channel, err)
                except Exception as e:
                    return await self.bot.send_message(message.channel, e)
                await self.bot.process_commands(message)
示例#29
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.")
示例#30
0
def character(request, avrae):
    """Sets up an active character in the user's context, to be used in tests. Cleans up after itself."""
    filename = os.path.join(dir_path, "static", f"char-{request.param}.json")
    with open(filename) as f:
        char = Character.from_dict(json.load(f))
    char.owner = DEFAULT_USER_ID
    char._active = True
    avrae.mdb.characters.delegate.update_one(
        {"owner": char.owner, "upstream": char.upstream},
        {"$set": char.to_dict()},
        upsert=True
    )
    if request.cls is not None:
        request.cls.character = char
    yield char
    avrae.mdb.characters.delegate.delete_one(
        {"owner": char.owner, "upstream": char.upstream}
    )
    Character._cache.clear()