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