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.")
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)
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)
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 )
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." )
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)
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)
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)
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)
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)
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)
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}.")
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
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)
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
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
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
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)
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
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)
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.")
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)
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.")
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
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)
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)
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.")
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()