コード例 #1
0
    def run(self, autoctx):
        super(TempHP, self).run(autoctx)
        args = autoctx.args
        amount = self.amount
        maxdmg = args.last('max', None, bool, 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

        amount = autoctx.parse_annostr(amount)
        dice_ast = copy.copy(d20.parse(amount))
        dice_ast = _upcast_scaled_dice(self, autoctx, dice_ast)

        if maxdmg:
            dice_ast = d20.utils.tree_map(_max_mapper, dice_ast)

        dmgroll = roll(dice_ast)
        autoctx.queue(f"**THP**: {dmgroll.result}")

        if autoctx.target.combatant:
            autoctx.target.combatant.temp_hp = max(dmgroll.total, 0)
            autoctx.footer_queue("{}: {}".format(
                autoctx.target.combatant.name,
                autoctx.target.combatant.hp_str()))
        elif autoctx.target.character:
            autoctx.target.character.temp_hp = max(dmgroll.total, 0)
            autoctx.footer_queue("{}: {}".format(
                autoctx.target.character.name,
                autoctx.target.character.hp_str()))
コード例 #2
0
def _upcast_scaled_dice(effect, autoctx, dice_ast):
    """Scales the dice of the cast to its appropriate amount (handling cantrip scaling and higher level addition)."""
    if autoctx.is_spell:
        if effect.cantripScale:
            level = autoctx.caster.spellbook.caster_level
            if level < 5:
                level_dice = 1
            elif level < 11:
                level_dice = 2
            elif level < 17:
                level_dice = 3
            else:
                level_dice = 4

            def mapper(node):
                if isinstance(node, d20.ast.Dice):
                    node.num = level_dice
                return node

            dice_ast = d20.utils.tree_map(mapper, dice_ast)

        if effect.higher and not autoctx.get_cast_level(
        ) == autoctx.spell.level:
            higher = effect.higher.get(str(autoctx.get_cast_level()))
            if higher:
                higher_ast = d20.parse(higher)
                dice_ast.roll = d20.ast.BinOp(dice_ast.roll, '+',
                                              higher_ast.roll)

    return dice_ast
コード例 #3
0
ファイル: dice.py プロジェクト: veilheim/avrae
    async def _roll_many(self, ctx, iterations, roll_str, dc=None, adv=None):
        if iterations < 1 or iterations > 100:
            return await ctx.send("Too many or too few iterations.")
        if adv is None:
            adv = d20.AdvType.NONE
        results = []
        successes = 0
        ast = d20.parse(roll_str)
        roller = d20.Roller(context=PersistentRollContext())

        for _ in range(iterations):
            res = roller.roll(ast, advantage=adv)
            if dc is not None and res.total >= dc:
                successes += 1
            results.append(res)

        if dc is None:
            header = f"Rolling {iterations} iterations..."
            footer = f"{sum(o.total for o in results)} total."
        else:
            header = f"Rolling {iterations} iterations, DC {dc}..."
            footer = f"{successes} successes, {sum(o.total for o in results)} total."

        result_strs = '\n'.join([str(o) for o in results])

        out = f"{header}\n{result_strs}\n{footer}"

        if len(out) > 1500:
            one_result = str(results[0])[:100]
            one_result = f"{one_result}..." if len(one_result) > 100 else one_result
            out = f"{header}\n{one_result}\n{footer}"

        await try_delete(ctx.message)
        await ctx.send(f"{ctx.author.mention}\n{out}")
        await Stats.increase_stat(ctx, "dice_rolled_life")
コード例 #4
0
    def run(self, autoctx):
        super(Roll, self).run(autoctx)
        d = autoctx.args.join('d', '+', ephem=True)
        maxdmg = autoctx.args.last('max', None, bool, ephem=True)
        mi = autoctx.args.last('mi', None, int)

        # 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

        dice_ast = copy.copy(d20.parse(autoctx.parse_annostr(self.dice)))
        dice_ast = upcast_scaled_dice(self, autoctx, dice_ast)

        if not self.hidden:
            # -mi # (#527)
            if mi:
                dice_ast = d20.utils.tree_map(mi_mapper(mi), dice_ast)

            if d:
                d_ast = d20.parse(d)
                dice_ast.roll = d20.ast.BinOp(dice_ast.roll, '+', d_ast.roll)

            if maxdmg:
                dice_ast = d20.utils.tree_map(max_mapper, dice_ast)

        rolled = roll(dice_ast)
        if not self.hidden:
            autoctx.meta_queue(f"**{self.name.title()}**: {rolled.result}")

        simplified_expr = copy.deepcopy(rolled.expr)
        d20.utils.simplify_expr(simplified_expr)
        simplified = RerollableStringifier().stringify(simplified_expr.roll)
        autoctx.metavars[self.name] = simplified
        autoctx.metavars['lastRoll'] = rolled.total  # #1335
        return RollResult(result=rolled.total,
                          roll=rolled,
                          simplified=simplified,
                          hidden=self.hidden)
コード例 #5
0
ファイル: customization.py プロジェクト: avrae/avrae
async def _snippet_before_edit(ctx, name=None, delete=False):
    if delete:
        return
    confirmation = None
    # special arg checking
    if not name:
        return
    name = name.lower()
    if name in SPECIAL_ARGS or name.startswith('-'):
        confirmation = f"**Warning:** Creating a snippet named `{name}` will prevent you from using the built-in `{name}` argument in Avrae commands.\nAre you sure you want to create this snippet? (Reply with yes/no)"
    # roll string checking
    try:
        d20.parse(name)
    except d20.RollSyntaxError:
        pass
    else:
        confirmation = f"**Warning:** Creating a snippet named `{name}` might cause hidden problems if you try to use the same roll in other commands.\nAre you sure you want to create this snippet? (Reply with yes/no)"

    if confirmation is not None:
        if not await confirm(ctx, confirmation):
            raise InvalidArgument('Ok, cancelling.')
コード例 #6
0
    async def _roll_many(ctx, iterations, roll_str, dc=None, adv=None):
        if iterations < 1 or iterations > 100:
            return await ctx.send("Too many or too few iterations.")
        if adv is None:
            adv = d20.AdvType.NONE
        results = []
        successes = 0
        ast = d20.parse(roll_str, allow_comments=True)
        roller = d20.Roller(context=PersistentRollContext())

        for _ in range(iterations):
            res = roller.roll(ast, advantage=adv)
            if dc is not None and res.total >= dc:
                successes += 1
            results.append(res)

        if dc is None:
            header = f"Rolling {iterations} iterations..."
            footer = f"{sum(o.total for o in results)} total."
        else:
            header = f"Rolling {iterations} iterations, DC {dc}..."
            footer = f"{successes} successes, {sum(o.total for o in results)} total."

        if ast.comment:
            header = f"{ast.comment}: {header}"

        result_strs = '\n'.join(str(o) for o in results)

        out = f"{header}\n{result_strs}\n{footer}"

        if len(out) > 1500:
            one_result = str(results[0])
            out = f"{header}\n{one_result}\n[{len(results) - 1} results omitted for output size.]\n{footer}"

        await try_delete(ctx.message)
        await ctx.send(
            f"{ctx.author.mention}\n{out}",
            allowed_mentions=discord.AllowedMentions(users=[ctx.author]))
        await Stats.increase_stat(ctx, "dice_rolled_life")
コード例 #7
0
ファイル: temphp.py プロジェクト: storytellermahkasad/avrae
    def run(self, autoctx):
        super(TempHP, self).run(autoctx)
        if autoctx.target is None:
            raise TargetException(
                "Tried to add temp HP without a target! Make sure all TempHP effects are inside "
                "of a Target effect.")
        args = autoctx.args
        amount = self.amount
        maxdmg = args.last('max', None, bool, 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

        amount = autoctx.parse_annostr(amount)
        dice_ast = copy.copy(d20.parse(amount))
        dice_ast = utils.upcast_scaled_dice(self, autoctx, dice_ast)

        if maxdmg:
            dice_ast = d20.utils.tree_map(utils.max_mapper, dice_ast)

        dmgroll = d20.roll(dice_ast)
        thp_amount = max(dmgroll.total, 0)
        autoctx.queue(f"**THP**: {dmgroll.result}")
        autoctx.metavars['lastTempHp'] = thp_amount  # #1335

        if autoctx.target.combatant:
            autoctx.target.combatant.temp_hp = thp_amount
            autoctx.footer_queue("{}: {}".format(
                autoctx.target.combatant.name,
                autoctx.target.combatant.hp_str()))
        elif autoctx.target.character:
            autoctx.target.character.temp_hp = thp_amount
            autoctx.footer_queue("{}: {}".format(
                autoctx.target.character.name,
                autoctx.target.character.hp_str()))

        return TempHPResult(amount=thp_amount, amount_roll=dmgroll)
コード例 #8
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
        }
コード例 #9
0
ファイル: player.py プロジェクト: mhSangar/avrae
    def new(cls,
            character,
            name,
            minv=None,
            maxv=None,
            reset=None,
            display_type=None,
            live_id=None,
            reset_to=None,
            reset_by=None):
        if reset not in ('short', 'long', 'none', None):
            raise InvalidArgument("Invalid reset.")
        if any(c in name for c in ".$"):
            raise InvalidArgument("Invalid character in CC name.")
        if display_type == 'bubble' and (maxv is None or minv is None):
            raise InvalidArgument(
                "Bubble display requires a max and min value.")

        # sanity checks
        if maxv is None and reset not in ('none', None):
            raise InvalidArgument("Reset passed but no maximum passed.")
        if reset_to is not None and reset_by is not None:
            raise InvalidArgument(
                "Both `resetto` and `resetby` arguments found.")

        min_value = None
        if minv is not None:
            min_value = character.evaluate_math(minv)

        max_value = None
        if maxv is not None:
            max_value = character.evaluate_math(maxv)
            if min_value is not None and max_value < min_value:
                raise InvalidArgument("Max value is less than min value.")
            if max_value == 0:
                raise InvalidArgument("Max value cannot be 0.")

        reset_to_value = None
        if reset_to is not None:
            reset_to_value = character.evaluate_math(reset_to)
            if min_value is not None and reset_to_value < min_value:
                raise InvalidArgument("Reset to value is less than min value.")
            if max_value is not None and reset_to_value > max_value:
                raise InvalidArgument(
                    "Reset to value is greater than max value.")

        if reset_by is not None:
            try:
                d20.parse(reset_by)
            except d20.RollSyntaxError:
                raise InvalidArgument(
                    f"{reset_by} (`resetby`) cannot be interpreted as a number or dice string."
                )

        # set initial value
        initial_value = max(0, min_value or 0)
        if reset_to_value is not None:
            initial_value = reset_to_value
        elif max_value is not None:
            initial_value = max_value

        return cls(character, name.strip(), initial_value, minv, maxv, reset,
                   display_type, live_id, reset_to, reset_by)
コード例 #10
0
    def new(cls,
            character,
            name,
            minv=None,
            maxv=None,
            reset=None,
            display_type=None,
            live_id=None,
            reset_to=None,
            reset_by=None,
            title=None,
            desc=None):
        if reset not in ('short', 'long', 'none', None):
            raise InvalidArgument("Invalid reset.")
        if any(c in name for c in ".$"):
            raise InvalidArgument("Invalid character in CC name.")
        if display_type == 'bubble' and (maxv is None or minv is None):
            raise InvalidArgument(
                "Bubble display requires a max and min value.")

        # sanity checks
        if reset not in ('none', None) and (maxv is None and reset_to is None
                                            and reset_by is None):
            raise InvalidArgument(
                "Reset passed but no valid reset value (`max`, `resetto`, `resetby`) passed."
            )
        if reset_to is not None and reset_by is not None:
            raise InvalidArgument(
                "Both `resetto` and `resetby` arguments found.")
        if not name.strip():
            raise InvalidArgument("The name of the counter can not be empty.")

        min_value = None
        if minv is not None:
            min_value = character.evaluate_math(minv)

        max_value = None
        if maxv is not None:
            max_value = character.evaluate_math(maxv)
            if min_value is not None and max_value < min_value:
                raise InvalidArgument("Max value is less than min value.")
            if max_value == 0:
                raise InvalidArgument("Max value cannot be 0.")

        reset_to_value = None
        if reset_to is not None:
            reset_to_value = character.evaluate_math(reset_to)
            if min_value is not None and reset_to_value < min_value:
                raise InvalidArgument("Reset to value is less than min value.")
            if max_value is not None and reset_to_value > max_value:
                raise InvalidArgument(
                    "Reset to value is greater than max value.")

        if reset_by is not None:
            try:
                d20.parse(str(reset_by))
            except d20.RollSyntaxError:
                raise InvalidArgument(
                    f"{reset_by} (`resetby`) cannot be interpreted as a number or dice string."
                )

        # set initial value
        initial_value = max(0, min_value or 0)
        if reset_to_value is not None:
            initial_value = reset_to_value
        elif max_value is not None:
            initial_value = max_value

        # length checks
        if desc and len(desc) > 1024:
            raise InvalidArgument(
                'Description must be less than 1024 characters.')

        if title and len(title) >= 256:
            raise InvalidArgument('Title must be less than 256 characters.')

        if len(name) > 256:
            raise InvalidArgument('Name must be less than 256 characters.')

        return cls(character, name.strip(), initial_value, minv, maxv, reset,
                   display_type, live_id, reset_to, reset_by, title, desc)
コード例 #11
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)
コード例 #12
0
 def test_example1(slf):
     slf.assertEqual(water_roughness(parse(example1)), 273)
コード例 #13
0
 def test_example1(slf):
     slf.assertEqual(corner_ids(parse(example1)), 20899048083289)
コード例 #14
0
def get_roll_comment(expr):
    """Gets the dice and comment from a roll expression."""
    result = d20.parse(expr, allow_comments=True)
    return str(result.roll), (result.comment or '')