def run(self, autoctx): super(Damage, self).run(autoctx) args = autoctx.args damage = self.damage d = args.join('d', '+') c = args.join('c', '+') resist = args.get('resist', []) immune = args.get('immune', []) vuln = args.get('vuln', []) neutral = args.get('neutral', []) crit = args.last('crit', None, bool) maxdmg = args.last('max', None, bool) mi = args.last('mi', None, int) if autoctx.target.target: resist = resist or autoctx.target.get_resist() immune = immune or autoctx.target.get_immune() vuln = vuln or autoctx.target.get_vuln() neutral = neutral or autoctx.target.get_neutral() # check if we actually need to run this damage roll (not in combat and roll is redundant) if not autoctx.target.target and self.is_meta(autoctx, True): return # add on combatant damage effects (#224) if autoctx.combatant: effect_d = '+'.join(autoctx.combatant.active_effects('d')) if effect_d: if d: d = f"{d}+{effect_d}" else: d = effect_d # check if we actually need to care about the -d tag if self.is_meta(autoctx): d = None # d was likely applied in the Roll effect already damage = autoctx.parse_annostr(damage) if self.cantripScale: damage = autoctx.cantrip_scale(damage) if self.higher and not autoctx.get_cast_level() == autoctx.spell.level: higher = self.higher.get(str(autoctx.get_cast_level())) if higher: damage = f"{damage}+{higher}" # -mi # (#527) if mi: damage = re.sub(r'(\d+d\d+)', rf'\1mi{mi}', damage) if d: damage = f"{damage}+{d}" roll_for = "Damage" if autoctx.in_crit or crit: def critSub(matchobj): return f"{int(matchobj.group(1)) * 2}d{matchobj.group(2)}" damage = re.sub(r'(\d+)d(\d+)', critSub, damage) roll_for = "Damage (CRIT!)" if c: damage = f"{damage}+{c}" if maxdmg: def maxSub(matchobj): return f"{matchobj.group(1)}d{matchobj.group(2)}mi{matchobj.group(2)}" damage = re.sub(r'(\d+)d(\d+)', maxSub, damage) damage = parse_resistances(damage, resist, immune, vuln, neutral) dmgroll = roll(damage, rollFor=roll_for, inline=True, show_blurbs=False) autoctx.queue(dmgroll.result) autoctx.target.damage(autoctx, dmgroll.total)
def run(self, autoctx): super(Damage, self).run(autoctx) # general arguments args = autoctx.args damage = self.damage d = args.join('d', '+', ephem=True) c = args.join('c', '+', ephem=True) resist = args.get('resist', [], ephem=True) immune = args.get('immune', [], ephem=True) vuln = args.get('vuln', [], ephem=True) neutral = args.get('neutral', [], ephem=True) crit = args.last('crit', None, bool, ephem=True) maxdmg = args.last('max', None, bool, ephem=True) mi = args.last('mi', None, int) critdice = args.last('critdice', 0, int) hide = args.last('h', type_=bool) # character-specific arguments if autoctx.character: critdice = autoctx.character.get_setting('critdice') or critdice # combat-specific arguments if not autoctx.target.is_simple: resist = resist or autoctx.target.get_resist() immune = immune or autoctx.target.get_immune() vuln = vuln or autoctx.target.get_vuln() neutral = neutral or autoctx.target.get_neutral() # check if we actually need to run this damage roll (not in combat and roll is redundant) if autoctx.target.is_simple and self.is_meta(autoctx, True): return # add on combatant damage effects (#224) if autoctx.combatant: effect_d = '+'.join(autoctx.combatant.active_effects('d')) if effect_d: if d: d = f"{d}+{effect_d}" else: d = effect_d # check if we actually need to care about the -d tag if self.is_meta(autoctx): d = None # d was likely applied in the Roll effect already damage = autoctx.parse_annostr(damage) if autoctx.is_spell: if self.cantripScale: damage = autoctx.cantrip_scale(damage) if self.higher and not autoctx.get_cast_level() == autoctx.spell.level: higher = self.higher.get(str(autoctx.get_cast_level())) if higher: damage = f"{damage}+{higher}" # crit in_crit = autoctx.in_crit or crit roll_for = "Damage" if not in_crit else "Damage (CRIT!)" def parsecrit(damage_dice, wep=False): if in_crit: def critSub(matchobj): extracritdice = critdice if (critdice and wep) else 0 return f"{int(matchobj.group(1)) * 2 + extracritdice}d{matchobj.group(2)}" damage_dice = re.sub(r'(\d+)d(\d+)', critSub, damage_dice) return damage_dice # -mi # (#527) if mi: damage = re.sub(r'(\d+d\d+)', rf'\1mi{mi}', damage) # -d # if d: damage = parsecrit(damage, wep=not autoctx.is_spell) + '+' + parsecrit(d) else: damage = parsecrit(damage, wep=not autoctx.is_spell) # -c # if c and in_crit: damage = f"{damage}+{c}" # max if maxdmg: def maxSub(matchobj): return f"{matchobj.group(1)}d{matchobj.group(2)}mi{matchobj.group(2)}" damage = re.sub(r'(\d+)d(\d+)', maxSub, damage) damage = parse_resistances(damage, resist, immune, vuln, neutral) dmgroll = roll(damage, rollFor=roll_for, inline=True, show_blurbs=False) # output if not hide: autoctx.queue(dmgroll.result) else: autoctx.queue(f"**{roll_for}**: {dmgroll.consolidated()} = `{dmgroll.total}`") autoctx.add_pm(str(autoctx.ctx.author.id), dmgroll.result) autoctx.target.damage(autoctx, dmgroll.total, allow_overheal=self.overheal) # return metadata for scripting return {'damage': dmgroll.result, 'total': dmgroll.total, 'roll': dmgroll}
def sheet_attack(attack, args, embed=None): """ :param attack: (dict) The attack to roll :param args: (dict) Metadata arguments :param embed: (discord.Embed) if supplied, will use as base. If None, will create one. :returns: a dict with structure {"embed": discord.Embed(), "result": {metadata}}""" # print(args) if embed is None: embed = discord.Embed() total_damage = 0 dnum_keys = [k for k in args.keys() if re.match(r'd\d+', k)] # ['d1', 'd2'...] dnum = {} for k in dnum_keys: # parse d# args for dmg in args[k].split('|'): try: dnum[dmg] = int(k.split('d')[-1]) except ValueError: embed.title = "Error" embed.colour = 0xff0000 embed.description = "Malformed tag: {}".format(k) return {"embed": embed, "total_damage": 0} advnum_keys = [k for k in args.keys() if re.match(r'(adv|dis)\d+', k)] advnum = {} for k in advnum_keys: # parse adv# args m = re.match(r'(adv|dis)(\d+)', k) _adv = m.group(1) num = int(m.group(2)) advnum[_adv] = num if args.get('phrase') is not None: # parse phrase embed.description = '*' + args.get('phrase') + '*' else: embed.description = '~~' + ' ' * 500 + '~~' if args.get('title') is not None: embed.title = args.get('title').replace( '[charname]', args.get('name')).replace( '[aname]', attack.get('name')).replace('[target]', args.get('t', '')) elif args.get('t') is not None: # parse target embed.title = '{} attacks with {} at {}!'.format( args.get('name'), a_or_an(attack.get('name')), args.get('t')) else: embed.title = '{} attacks with {}!'.format(args.get('name'), a_or_an(attack.get('name'))) for arg in ('rr', 'ac'): # parse reroll/ac try: args[arg] = int(args.get(arg, None)) except (ValueError, TypeError): args[arg] = None args['adv'] = 0 if args.get( 'adv', False) and args.get('dis', False) else 1 if args.get( 'adv', False) else -1 if args.get('dis', False) else 0 args['adv'] = 2 if args.get( 'ea', False) and not args.get('dis', False) else args['adv'] args['crit'] = 1 if args.get('crit', False) else None args['hit'] = 1 if args.get('hit', False) else None for r in range(args.get('rr', 1) or 1): # start rolling attacks out = '' itercrit = 0 if attack.get('attackBonus') is None and args.get('b') is not None: attack['attackBonus'] = '0' if attack.get('attackBonus') is not None and not args.get('hit'): adv = args.get('adv') for _adv, numHits in advnum.items(): if numHits > 0: adv = 1 if _adv == 'adv' else -1 advnum[_adv] -= 1 formatted_d20 = ('1d20' if adv == 0 else '2d20' + ( 'kh1' if adv == 1 else 'kl1') if not adv == 2 else '3d20kh1') \ + ('ro{}'.format(args.get('reroll', 0)) if int(args.get('reroll', 0)) else '') if args.get('b') is not None: toHit = roll(f'{formatted_d20}+' + attack.get('attackBonus') + '+' + args.get('b'), rollFor='To Hit', inline=True, show_blurbs=False) else: toHit = roll(f'{formatted_d20}+' + attack.get('attackBonus'), rollFor='To Hit', inline=True, show_blurbs=False) try: parts = len(toHit.raw_dice.parts) except: parts = 0 if parts > 0: out += toHit.result + '\n' try: raw = next(p for p in toHit.raw_dice.parts if isinstance(p, SingleDiceGroup) and p.max_value == 20).get_total() except StopIteration: raw = 0 if raw >= (int(args.get('criton', 20)) or 20): itercrit = 1 else: itercrit = toHit.crit if args.get('ac') is not None: if toHit.total < args.get('ac') and itercrit == 0: itercrit = 2 # miss! if args.get('crit') and itercrit < 2: itercrit = args.get('crit', 0) else: # output wherever was there if error out += "**To Hit**: " + attack.get('attackBonus') + '\n' else: if args.get('hit'): out += "**To Hit**: Automatic hit!\n" if args.get('crit'): itercrit = args.get('crit', 0) else: itercrit = 0 if attack.get('damage') is None and args.get('d') is not None: attack['damage'] = '0' if attack.get('damage') is not None: def parsecrit(damage_str, wep=False): if itercrit == 1: if args.get('crittype') == '2x': critDice = f"({damage_str})*2" if args.get('c') is not None: critDice += '+' + args.get('c', '') else: def critSub(matchobj): hocrit = 1 if args.get('hocrit') and wep else 0 return str(int(matchobj.group(1)) * 2 + hocrit) + 'd' + matchobj.group(2) critDice = re.sub(r'(\d+)d(\d+)', critSub, damage_str) else: critDice = damage_str return critDice # -d, -d# parsing if args.get('d') is not None: damage = parsecrit(attack.get('damage'), wep=True) + '+' + parsecrit(args.get('d')) else: damage = parsecrit(attack.get('damage'), wep=True) for dice, numHits in dnum.items(): if not itercrit == 2 and numHits > 0: damage += '+' + parsecrit(dice) dnum[dice] -= 1 # crit parsing rollFor = "Damage" if itercrit == 1: if args.get('c') is not None: damage += '+' + args.get('c', '') rollFor = "Damage (CRIT!)" elif itercrit == 2: rollFor = "Damage (Miss!)" # resist parsing if 'resist' in args or 'immune' in args or 'vuln' in args: resistances = args.get('resist', '').split('|') immunities = args.get('immune', '').split('|') vulnerabilities = args.get('vuln', '').split('|') damage = parse_resistances(damage, resistances, immunities, vulnerabilities) # actual roll if itercrit == 2 and not args.get('showmiss', False): out += '**Miss!**\n' else: dmgroll = roll(damage, rollFor=rollFor, inline=True, show_blurbs=False) out += dmgroll.result + '\n' if not itercrit == 2: # if we actually hit total_damage += dmgroll.total if out is not '': if (args.get('rr', 1) or 1) > 1: embed.add_field(name='Attack {}'.format(r + 1), value=out, inline=False) else: embed.add_field(name='Attack', value=out, inline=False) if (args.get('rr', 1) or 1) > 1 and attack.get('damage') is not None: embed.add_field(name='Total Damage', value=str(total_damage)) if attack.get('details') is not None: embed.add_field(name='Effect', value=(attack.get('details', ''))) if args.get('image') is not None: embed.set_thumbnail(url=args.get('image')) return {'embed': embed, 'total_damage': total_damage}
def sheet_damage(damage_str, args, itercrit=0, dnum=None): total_damage = 0 out = "" if dnum is None: dnum = {} d = args.join('d', '+') c = args.join('c', '+') critdice = args.last('critdice', 0, int) showmiss = args.last('showmiss', False, bool) resist = args.get('resist') immune = args.get('immune') vuln = args.get('vuln') neutral = args.get('neutral') maxdmg = args.last('max', None, bool) mi = args.last('mi', None, int) if damage_str is None and d: damage_str = '0' dmgroll = None if damage_str is not None: def parsecrit(damage_str, wep=False): if itercrit == 1: def critSub(matchobj): extracritdice = critdice if critdice and wep else 0 return f"{int(matchobj.group(1)) * 2 + extracritdice}d{matchobj.group(2)}" critDice = re.sub(r'(\d+)d(\d+)', critSub, damage_str) else: critDice = damage_str return critDice if mi: damage_str = re.sub(r'(\d+d\d+)', rf'\1mi{mi}', damage_str) # -d, -d# parsing if d: damage = parsecrit(damage_str, wep=True) + '+' + parsecrit(d) else: damage = parsecrit(damage_str, wep=True) for dice, numHits in dnum.items(): if not itercrit == 2 and numHits > 0: damage += '+' + parsecrit(dice) dnum[dice] -= 1 if maxdmg: def maxSub(matchobj): return f"{matchobj.group(1)}d{matchobj.group(2)}mi{matchobj.group(2)}" damage = re.sub(r'(\d+)d(\d+)', maxSub, damage) # crit parsing rollFor = "Damage" if itercrit == 1: if c: damage += '+' + c rollFor = "Damage (CRIT!)" elif itercrit == 2: rollFor = "Damage (Miss!)" # resist parsing damage = parse_resistances(damage, resist, immune, vuln, neutral) # actual roll if itercrit == 2 and not showmiss: out = '**Miss!**\n' else: dmgroll = roll(damage, rollFor=rollFor, inline=True, show_blurbs=False) out = dmgroll.result + '\n' if not itercrit == 2: # if we actually hit total_damage += dmgroll.total return {'damage': out, 'total': total_damage, 'roll': dmgroll}
def sheet_damage(damage_str, args, itercrit=0, dnum=None): total_damage = 0 out = "" if dnum is None: dnum = {} if damage_str is None and args.get('d') is not None: damage_str = '0' if damage_str is not None: def parsecrit(damage_str, wep=False): if itercrit == 1: if args.get('crittype') == '2x': critDice = f"({damage_str})*2" if args.get('c') is not None: critDice += '+' + args.get('c', '') else: def critSub(matchobj): critdice = args.get( 'critdice') if args.get('critdice') and wep else 0 return str(int(matchobj.group(1)) * 2 + critdice) + 'd' + matchobj.group(2) critDice = re.sub(r'(\d+)d(\d+)', critSub, damage_str) else: critDice = damage_str return critDice # -d, -d# parsing if args.get('d') is not None: damage = parsecrit(damage_str, wep=True) + '+' + parsecrit( args.get('d')) else: damage = parsecrit(damage_str, wep=True) for dice, numHits in dnum.items(): if not itercrit == 2 and numHits > 0: damage += '+' + parsecrit(dice) dnum[dice] -= 1 # crit parsing rollFor = "Damage" if itercrit == 1: if args.get('c') is not None: damage += '+' + args.get('c', '') rollFor = "Damage (CRIT!)" elif itercrit == 2: rollFor = "Damage (Miss!)" # resist parsing if 'resist' in args or 'immune' in args or 'vuln' in args: resistances = args.get('resist', '').split('|') immunities = args.get('immune', '').split('|') vulnerabilities = args.get('vuln', '').split('|') damage = parse_resistances(damage, resistances, immunities, vulnerabilities) # actual roll if itercrit == 2 and not args.get('showmiss', False): out = '**Miss!**\n' else: dmgroll = roll(damage, rollFor=rollFor, inline=True, show_blurbs=False) out = dmgroll.result + '\n' if not itercrit == 2: # if we actually hit total_damage += dmgroll.total return {'damage': out, 'total': total_damage}
def sheet_damage(damage_str, args, itercrit=0, dnum=None): total_damage = 0 out = "" if dnum is None: dnum = {} d = args.join('d', '+') crittype = args.last('crittype', 'default') c = args.join('c', '+') critdice = args.last('critdice', 0, int) showmiss = args.last('showmiss', False, bool) resist = args.get('resist') immune = args.get('immune') vuln = args.get('vuln') if damage_str is None and d: damage_str = '0' if damage_str is not None: def parsecrit(damage_str, wep=False): if itercrit == 1: if crittype == '2x': critDice = f"({damage_str})*2" if c: critDice += '+' + c else: def critSub(matchobj): extracritdice = critdice if critdice and wep else 0 return str(int(matchobj.group(1)) * 2 + extracritdice) + 'd' + matchobj.group(2) critDice = re.sub(r'(\d+)d(\d+)', critSub, damage_str) else: critDice = damage_str return critDice # -d, -d# parsing if d: damage = parsecrit(damage_str, wep=True) + '+' + parsecrit(d) else: damage = parsecrit(damage_str, wep=True) for dice, numHits in dnum.items(): if not itercrit == 2 and numHits > 0: damage += '+' + parsecrit(dice) dnum[dice] -= 1 # crit parsing rollFor = "Damage" if itercrit == 1: if c: damage += '+' + c rollFor = "Damage (CRIT!)" elif itercrit == 2: rollFor = "Damage (Miss!)" # resist parsing damage = parse_resistances(damage, resist, immune, vuln) # actual roll if itercrit == 2 and not showmiss: out = '**Miss!**\n' else: dmgroll = roll(damage, rollFor=rollFor, inline=True, show_blurbs=False) out = dmgroll.result + '\n' if not itercrit == 2: # if we actually hit total_damage += dmgroll.total return {'damage': out, 'total': total_damage}