Esempio n. 1
0
File: spell.py Progetto: profK/avrae
    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)
Esempio n. 2
0
    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}
Esempio n. 3
0
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}
Esempio n. 4
0
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}
Esempio n. 5
0
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}