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)
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
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
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.")
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'])
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)
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
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