Ejemplo n.º 1
0
    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
        }
Ejemplo n.º 2
0
    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)