def run(self, autoctx): super(Damage, self).run(autoctx) # general arguments args = autoctx.args damage = self.damage resistances = Resistances() d_args = args.get('d', [], ephem=True) c_args = args.get('c', [], ephem=True) crit_arg = args.last('crit', None, bool, ephem=True) nocrit = args.last('nocrit', default=False, type_=bool, ephem=True) max_arg = args.last('max', None, bool, ephem=True) magic_arg = args.last('magical', None, bool, ephem=True) mi_arg = args.last('mi', None, int) dtype_args = args.get('dtype', [], ephem=True) 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: resistances = autoctx.target.get_resists().copy() resistances.update(Resistances.from_args(args, ephem=True)) # 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: d_args.extend(autoctx.combatant.active_effects('d')) # check if we actually need to care about the -d tag if self.is_meta(autoctx): d_args = [] # d was likely applied in the Roll effect already # set up damage AST damage = autoctx.parse_annostr(damage) dice_ast = copy.copy(d20.parse(damage)) dice_ast = _upcast_scaled_dice(self, autoctx, dice_ast) # -mi # (#527) if mi_arg: dice_ast = d20.utils.tree_map(_mi_mapper(mi_arg), dice_ast) # -d # for d_arg in d_args: d_ast = d20.parse(d_arg) dice_ast.roll = d20.ast.BinOp(dice_ast.roll, '+', d_ast.roll) # crit # nocrit (#1216) in_crit = (autoctx.in_crit or crit_arg) and not nocrit roll_for = "Damage" if not in_crit else "Damage (CRIT!)" if in_crit: dice_ast = d20.utils.tree_map(_crit_mapper, dice_ast) if critdice and not autoctx.is_spell: # add X critdice to the leftmost node if it's dice left = d20.utils.leftmost(dice_ast) if isinstance(left, d20.ast.Dice): left.num += int(critdice) # -c # if in_crit: for c_arg in c_args: c_ast = d20.parse(c_arg) dice_ast.roll = d20.ast.BinOp(dice_ast.roll, '+', c_ast.roll) # max if max_arg: dice_ast = d20.utils.tree_map(_max_mapper, dice_ast) # evaluate damage dmgroll = roll(dice_ast) # magic arg (#853) always = {'magical'} if (autoctx.is_spell or magic_arg) else None # dtype transforms/overrides (#876) transforms = {} for dtype in dtype_args: if '>' in dtype: *froms, to = dtype.split('>') for frm in froms: transforms[frm.strip()] = to.strip() else: transforms[None] = dtype # display damage transforms (#1103) if None in transforms: autoctx.meta_queue(f"**Damage Type**: {transforms[None]}") elif transforms: for frm in transforms: autoctx.meta_queue( f"**Damage Change**: {frm} > {transforms[frm]}") # evaluate resistances do_resistances(dmgroll.expr, resistances, always, transforms) # generate output result = d20.MarkdownStringifier().stringify(dmgroll.expr) # output if not hide: autoctx.queue(f"**{roll_for}**: {result}") else: d20.utils.simplify_expr(dmgroll.expr) autoctx.queue( f"**{roll_for}**: {d20.MarkdownStringifier().stringify(dmgroll.expr)}" ) autoctx.add_pm(str(autoctx.ctx.author.id), f"**{roll_for}**: {result}") autoctx.target.damage(autoctx, dmgroll.total, allow_overheal=self.overheal) # return metadata for scripting return { 'damage': f"**{roll_for}**: {result}", 'total': dmgroll.total, 'roll': dmgroll }
def run(self, autoctx): super().run(autoctx) if autoctx.target is None: raise TargetException( "Tried to do damage without a target! Make sure all Damage effects are inside " "of a Target effect." ) # general arguments args = autoctx.args damage = self.damage resistances = Resistances() d_args = args.get('d', [], ephem=True) c_args = args.get('c', [], ephem=True) crit_arg = args.last('crit', None, bool, ephem=True) nocrit = args.last('nocrit', default=False, type_=bool, ephem=True) max_arg = args.last('max', None, bool, ephem=True) magic_arg = args.last('magical', None, bool, ephem=True) silvered_arg = args.last('silvered', None, bool, ephem=True) mi_arg = args.last('mi', None, int) dtype_args = args.get('dtype', [], ephem=True) critdice = sum(args.get('critdice', type_=int)) hide = args.last('h', type_=bool) # character-specific arguments if autoctx.character and 'critdice' not in args: critdice = autoctx.character.options.extra_crit_dice # combat-specific arguments if not autoctx.target.is_simple: resistances = autoctx.target.get_resists().copy() resistances.update(Resistances.from_args(args, ephem=True)) # 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): return # add on combatant damage effects (#224) if autoctx.combatant: d_args.extend(autoctx.combatant.active_effects('d')) # check if we actually need to care about the -d tag if self.contains_roll_meta(autoctx): d_args = [] # d was likely applied in the Roll effect already # set up damage AST damage = autoctx.parse_annostr(damage) dice_ast = copy.copy(d20.parse(damage)) dice_ast = utils.upcast_scaled_dice(self, autoctx, dice_ast) # -mi # (#527) if mi_arg: dice_ast = d20.utils.tree_map(utils.mi_mapper(mi_arg), dice_ast) # -d # for d_arg in d_args: d_ast = d20.parse(d_arg) dice_ast.roll = d20.ast.BinOp(dice_ast.roll, '+', d_ast.roll) # crit # nocrit (#1216) # Disable critical damage in saves (#1556) in_crit = (autoctx.in_crit or crit_arg) and not (nocrit or autoctx.in_save) if in_crit: dice_ast = d20.utils.tree_map(utils.crit_mapper, dice_ast) if critdice and not autoctx.is_spell: # add X critdice to the leftmost node if it's dice left = d20.utils.leftmost(dice_ast) if isinstance(left, d20.ast.Dice): left.num += int(critdice) # -c # if in_crit: for c_arg in c_args: c_ast = d20.parse(c_arg) dice_ast.roll = d20.ast.BinOp(dice_ast.roll, '+', c_ast.roll) # max if max_arg: dice_ast = d20.utils.tree_map(utils.max_mapper, dice_ast) # evaluate damage dmgroll = d20.roll(dice_ast) # magic arg (#853), magical effect (#1063) # silvered arg (#1544) always = set() magical_effect = autoctx.combatant and autoctx.combatant.active_effects('magical') if magical_effect or autoctx.is_spell or magic_arg: always.add('magical') silvered_effect = autoctx.combatant and autoctx.combatant.active_effects('silvered') if silvered_effect or silvered_arg: always.add('silvered') # dtype transforms/overrides (#876) transforms = {} for dtype in dtype_args: if '>' in dtype: *froms, to = dtype.split('>') for frm in froms: transforms[frm.strip()] = to.strip() else: transforms[None] = dtype # display damage transforms (#1103) if None in transforms: autoctx.meta_queue(f"**Damage Type**: {transforms[None]}") elif transforms: for frm in transforms: autoctx.meta_queue(f"**Damage Change**: {frm} > {transforms[frm]}") # evaluate resistances do_resistances(dmgroll.expr, resistances, always, transforms) # determine healing/damage, stringify expr result = d20.MarkdownStringifier().stringify(dmgroll.expr) if dmgroll.total < 0: roll_for = "Healing" else: roll_for = "Damage" # output roll_for = roll_for if not in_crit else f"{roll_for} (CRIT!)" if not hide: autoctx.queue(f"**{roll_for}**: {result}") else: d20.utils.simplify_expr(dmgroll.expr) autoctx.queue(f"**{roll_for}**: {d20.MarkdownStringifier().stringify(dmgroll.expr)}") autoctx.add_pm(str(autoctx.ctx.author.id), f"**{roll_for}**: {result}") autoctx.target.damage(autoctx, dmgroll.total, allow_overheal=self.overheal) # #1335 autoctx.metavars['lastDamage'] = dmgroll.total return DamageResult(damage=dmgroll.total, damage_roll=dmgroll, in_crit=in_crit)