Пример #1
0
    def parseDiceExpression_Dice(self, what: str) -> DiceExprParsed:
        split = what.split("d")
        if len(split) > 2:
            raise gb.GreedyCommandError('string_error_toomany_d')
        if len(split) == 1:
            raise gb.GreedyCommandError('string_error_not_XdY', (split[0], ))
        if split[0] == "":
            split[0] = "1"
        if not split[0].isdigit():
            raise gb.GreedyCommandError('string_error_not_positive_integer',
                                        (split[0], ))
        if split[1] == "":
            split[1] = "10"
        if not split[1].isdigit():
            raise gb.GreedyCommandError('string_error_not_positive_integer',
                                        (split[1], ))
        n = int(split[0])
        faces = int(split[1])
        if n == 0:
            raise gb.GreedyCommandError('string_error_not_gt0', (n, ))
        if faces == 0:
            raise gb.GreedyCommandError('string_error_not_gt0', (faces, ))
        if n > int(self.bot.config['BotOptions']['max_dice']):
            raise gb.GreedyCommandError('string_error_toomany_dice', (n, ))
        if faces > int(self.bot.config['BotOptions']['max_faces']):
            raise gb.GreedyCommandError('string_error_toomany_faces',
                                        (faces, ))

        return DiceExprParsed(n, n, 0, faces, None)
Пример #2
0
 async def convert(self, ctx: Context, argument: str) -> bool:
     stdarg = argument.lower()
     std = stdarg in ['y', 's', '1', 'true']
     if not std and not stdarg in ['n', '0', 'false']:
         raise gb.GreedyCommandError("'{}' non è un'opzione valida",
                                     (stdarg, ))
     return std
Пример #3
0
 async def roll_soak(self, ctx: gb.GreedyContext, parsed: dict) -> str:
     if RollArg.MULTI in parsed or RollArg.SPLIT in parsed or RollArg.ADD in parsed or parsed[
             RollArg.ROLLTYPE] != RollType.NORMALE:
         raise gb.GreedyCommandError(
             "string_error_roll_invalid_param_combination")
     lid = ctx.getLID()
     diff = parsed[RollArg.DIFF] if RollArg.DIFF in parsed else 6
     character = self.bot.dbm.getActiveChar(ctx)
     pool = self.bot.dbm.getTrait_LangSafe(character['id'], 'costituzione',
                                           lid)['cur_value']
     try:
         pool += self.bot.dbm.getTrait_LangSafe(character['id'],
                                                'robustezza',
                                                lid)['cur_value']
     except ghostDB.DBException:
         pass
     return self.rollAndFormatVTM(ctx,
                                  pool,
                                  10,
                                  diff,
                                  RollStatusSoak(self.bot.languageProvider,
                                                 lid),
                                  0,
                                  False,
                                  statistics=RollArg.STATS in parsed)
Пример #4
0
 async def roll_reflexes(self, ctx: gb.GreedyContext, parsed: dict) -> str:
     if RollArg.MULTI in parsed or RollArg.SPLIT in parsed or parsed[
             RollArg.
             ROLLTYPE] != RollType.NORMALE or RollArg.DIFF in parsed:
         raise gb.GreedyCommandError(
             "string_error_roll_invalid_param_combination")
     lid = ctx.getLID()
     add = parsed[RollArg.ADD] if RollArg.ADD in parsed else 0
     character = self.bot.dbm.getActiveChar(ctx)
     volonta = self.bot.dbm.getTrait_LangSafe(character['id'], 'volonta',
                                              lid)  #['cur_value']
     prontezza = self.bot.dbm.getTrait_LangSafe(character['id'],
                                                'prontezza',
                                                lid)  #['cur_value']
     diff = 10 - prontezza['cur_value']
     response = f'{volonta["traitName"]}: {volonta["cur_value"]}, {prontezza["traitName"]}: {prontezza["cur_value"]} -> {volonta["cur_value"]}d{10} {self.bot.getStringForUser(ctx, "string_diff")} ({diff} = {10}-{prontezza["cur_value"]})\n'
     response += self.rollAndFormatVTM(ctx,
                                       volonta['cur_value'],
                                       10,
                                       diff,
                                       RollStatusReflexes(
                                           self.bot.languageProvider, lid),
                                       add,
                                       statistics=RollArg.STATS in parsed)
     return response
Пример #5
0
 async def roll_initiative(self, ctx: gb.GreedyContext,
                           parsed: dict) -> str:
     if RollArg.MULTI in parsed or RollArg.SPLIT in parsed or parsed[
             RollArg.
             ROLLTYPE] != RollType.NORMALE or RollArg.DIFF in parsed:
         raise gb.GreedyCommandError(
             "string_error_roll_invalid_param_combination")
     lid = ctx.getLID()
     add = parsed[RollArg.ADD] if RollArg.ADD in parsed else 0
     raw_roll = random.randint(1, 10)
     bonuses_log = []
     bonus = add
     if add:
         bonuses_log.append(
             self.bot.getStringForUser(ctx, "string_bonus_X", add))
     try:
         character = self.bot.dbm.getActiveChar(ctx)
         for traitid in ['prontezza', 'destrezza',
                         'velocità']:  # TODO dehardcode?
             try:
                 val = self.bot.dbm.getTrait_LangSafe(
                     character['id'], traitid, lid)
                 bonus += val["cur_value"]
                 bonuses_log.append(
                     f'{val["traitName"]}: {val["cur_value"]}')
             except ghostDB.DBException:
                 pass
     except ghostDB.DBException:
         bonuses_log.append(
             self.bot.getStringForUser(ctx, "string_comment_no_pc"))
     details = ""
     if len(bonuses_log):
         details = ", ".join(bonuses_log)
     final_val = raw_roll + bonus
     return f'{self.bot.getStringForUser(ctx, "string_initiative")}: **{final_val}**\n{self.bot.getStringForUser(ctx, "string_roll")}: [{raw_roll}] + {bonus if bonus else 0} ({details})'
Пример #6
0
    def parseDiceExpressionIntoSummary(self,
                                       ctx: commands.Context,
                                       summary: dict,
                                       expression: str,
                                       firstNegative: bool = False) -> dict:
        parsed_expr = self.parseDiceExpression_Mixed(
            ctx,
            expression,
            firstNegative=firstNegative,
            character=summary[RollArg.CHARACTER])
        if RollArg.DADI in summary:
            summary[RollArg.DADI] += parsed_expr.n_dice
            summary[RollArg.DADI_PERMANENTI] += parsed_expr.n_dice_permanent
        else:
            summary[RollArg.DADI] = parsed_expr.n_dice
            summary[RollArg.DADI_PERMANENTI] = parsed_expr.n_dice_permanent

        if parsed_expr.n_extrasucc != 0:
            if RollArg.ADD in summary:
                summary[RollArg.ADD] += parsed_expr.n_extrasucc
            else:
                summary[RollArg.ADD] = parsed_expr.n_extrasucc

        if parsed_expr.character != None:
            summary[RollArg.CHARACTER] = parsed_expr.character

        # figure out if we are mixing faces
        if parsed_expr.n_faces != 0:  # if it's 0, it's just a flat number, compatible with all faces
            if RollArg.NFACES in summary and summary[
                    RollArg.NFACES] != parsed_expr.n_faces:
                raise gb.GreedyCommandError("string_error_face_mixing")
            summary[RollArg.NFACES] = parsed_expr.n_faces

        return summary
Пример #7
0
 async def convert(self, ctx: gb.GreedyContext, argument: str) -> Any:
     try:
         user = await super().convert(ctx, argument)
     except UserNotFound:
         raise gb.GreedyCommandError('string_error_usernotfound_discord',
                                     (argument, ))
     bot: gb.GreedyGhost = ctx.bot
     return bot.dbm.validators.getValidateBotUser(user.id).get()
Пример #8
0
 async def convert(self, ctx: Context, argument: str) -> int:
     error = gb.GreedyCommandError("'{}' non è tracker valido!",
                                   (argument, ))
     tracktype = 0
     try:
         tracktype = int(argument)
         if not tracktype in [0, 1, 2, 3]:  # TODO dehardcode
             raise error
     except ValueError:
         raise error
     return tracktype
Пример #9
0
    async def guild_authorization(self,
                                  ctx: commands.Context,
                                  authorize: NoYesConverter = None):
        guild_ctx = ctx.guild
        if guild_ctx is None:
            raise gb.GreedyCommandError(
                "string_error_not_available_outside_guild")

        await self.bot.checkAndJoinGuild(guild_ctx)

        guild_db = self.bot.dbm.validators.getValidateGuild(guild_ctx.id).get()

        if authorize is None:
            status_str = None
            if guild_db["authorized"]:
                status_str = "string_msg_guild_authstatus_enabled"
            else:
                status_str = "string_msg_guild_authstatus_disabled"
            status = self.bot.getStringForUser(ctx, status_str)
            await self.bot.atSendLang(ctx, "string_msg_guild_authstatus",
                                      status)
        elif authorize:
            self.bot.dbm.updateGuildAuthorization(guild_ctx.id, True)
            for member in guild_ctx.members:
                iu, _ = self.bot.dbm.validators.getValidateBotUser(
                    member.id).validate()
                if not iu:
                    self.bot.dbm.registerUser(
                        member.id, member.name,
                        self.bot.config['BotOptions']['default_language'])
            await self.bot.atSendLang(
                ctx, "string_msg_guild_authstatus",
                self.bot.getStringForUser(
                    ctx, "string_msg_guild_authstatus_enabled"))
        else:
            self.bot.dbm.updateGuildAuthorization(guild_ctx.id, False)
            guild_map = self.bot.getGuildActivationMap()
            for member in guild_ctx.members:
                self.bot.checkAndRemoveUser(member, guild_map)
            await self.bot.atSendLang(
                ctx, "string_msg_guild_authstatus",
                self.bot.getStringForUser(
                    ctx, "string_msg_guild_authstatus_disabled"))
Пример #10
0
 async def convert(self, ctx: Context, argument: str) -> Any:
     if validate_id(argument):
         return argument.lower()
     else:
         raise gb.GreedyCommandError('string_error_invalid_shortid',
                                     (argument, ))
Пример #11
0
    async def roll_dice(self, ctx: gb.GreedyContext, parsed: dict) -> str:
        lid = ctx.getLID()
        ndice = 0
        if RollArg.PERMANENTE in parsed:
            ndice = parsed[RollArg.DADI_PERMANENTI]
        else:
            ndice = parsed[RollArg.DADI]

        if not RollArg.NFACES in parsed:
            raise gb.GreedyCommandError("string_error_no_faces")
        nfaces = parsed[RollArg.NFACES]

        character = parsed[RollArg.CHARACTER]  # might be None

        # modifico il numero di dadi

        if RollArg.PENALITA in parsed:
            if not character:
                character = self.bot.dbm.getActiveChar(ctx)
            health = self.bot.dbm.getTrait_LangSafe(character['id'], 'salute',
                                                    lid)
            penalty, _ = utils.parseHealth(health)
            ndice += penalty[0]

        max_dice = int(self.bot.config['BotOptions']['max_dice'])
        if ndice > max_dice:
            raise gb.GreedyCommandError("string_error_toomany_dice",
                                        (max_dice, ))
        if ndice <= 0:
            raise gb.GreedyCommandError("string_error_toofew_dice", (ndice, ))

        # check n° di mosse per le multiple
        if RollArg.MULTI in parsed:
            multi = parsed[RollArg.MULTI]
            max_moves = int(
                ((ndice + 1) / 2) - 0.1
            )  # (ndice+1)/2 è il numero di mosse in cui si rompe, non il massimo. togliendo 0.1 e arrotondando per difetto copro sia il caso intero che il caso con .5
            if max_moves == 1:
                raise gb.GreedyCommandError(
                    "string_error_not_enough_dice_multi")
            elif multi > max_moves:
                raise gb.GreedyCommandError(
                    "string_error_not_enough_dice_multi_MAX_REQUESTED",
                    (max_moves, ndice))

        # decido cosa fare

        add = parsed[RollArg.ADD] if RollArg.ADD in parsed else 0

        # simple roll
        if not (RollArg.MULTI in parsed) and not (
                RollArg.DIFF in parsed) and not (RollArg.SPLIT in parsed) and (
                    parsed[RollArg.ROLLTYPE] == RollType.NORMALE
                    or parsed[RollArg.ROLLTYPE] == RollType.SOMMA):
            raw_roll = list(
                map(lambda x: random.randint(1, nfaces), range(ndice)))
            if add != 0 or parsed[RollArg.ROLLTYPE] == RollType.SOMMA:
                roll_sum = sum(raw_roll) + add
                return f'{repr(raw_roll)} {"+" if add >= 0 else "" }{add} = **{roll_sum}**'
            else:
                return repr(raw_roll)

        if nfaces != 10:
            raise gb.GreedyCommandError('string_error_not_d10')
        # past this point, we are in d10 territory

        stats = RollArg.STATS in parsed
        response = ''

        if RollArg.MULTI in parsed:
            multi = parsed[RollArg.MULTI]
            split = []
            if RollArg.SPLIT in parsed:
                split = parsed[RollArg.SPLIT]
            if parsed[RollArg.ROLLTYPE] == RollType.NORMALE:
                response = ""
                if not RollArg.DIFF in parsed:
                    raise gb.GreedyCommandError("string_error_missing_diff")
                for i in range(multi):
                    parziale = ''
                    ndadi = ndice - i - multi
                    split_diffs = findSplit(i, split)
                    if len(split_diffs):
                        pools = [(ndadi - ndadi // 2), ndadi // 2]
                        for j in range(len(pools)):
                            parziale += f'\n{self.bot.getStringForUser(ctx,  "string_roll")} {j+1}: ' + self.rollAndFormatVTM(
                                ctx,
                                pools[j],
                                nfaces,
                                split_diffs[j],
                                RollStatusNormal(self.bot.languageProvider,
                                                 lid, parsed[RollArg.MINSUCC]),
                                statistics=stats)
                    else:
                        parziale = self.rollAndFormatVTM(
                            ctx,
                            ndadi,
                            nfaces,
                            parsed[RollArg.DIFF],
                            RollStatusNormal(self.bot.languageProvider, lid,
                                             parsed[RollArg.MINSUCC]),
                            statistics=stats,
                            minsucc=parsed[RollArg.MINSUCC])
                    response += f'\n{self.bot.getStringForUser(ctx,  "string_action")} {i+1}: ' + parziale  # line break all'inizio tanto c'è il @mention
            else:
                raise gb.GreedyCommandError(
                    "string_error_roll_invalid_param_combination")
        else:  # 1 tiro solo
            if RollArg.SPLIT in parsed:
                split = parsed[RollArg.SPLIT]
                if parsed[RollArg.ROLLTYPE] == RollType.NORMALE:
                    pools = [(ndice - ndice // 2), ndice // 2]
                    response = ''
                    for i in range(len(pools)):
                        parziale = self.rollAndFormatVTM(
                            ctx,
                            pools[i],
                            nfaces,
                            split[0][i + 1],
                            RollStatusNormal(self.bot.languageProvider, lid,
                                             parsed[RollArg.MINSUCC]),
                            statistics=stats)
                        response += f'\n{self.bot.getStringForUser(ctx, "string_roll")} {i+1}: ' + parziale
                else:
                    raise gb.GreedyCommandError(
                        "string_error_roll_invalid_param_combination")
            else:
                if parsed[
                        RollArg.ROLLTYPE] == RollType.NORMALE:  # tiro normale
                    if not RollArg.DIFF in parsed:
                        raise gb.GreedyCommandError(
                            "string_error_missing_diff")
                    response = self.rollAndFormatVTM(
                        ctx,
                        ndice,
                        nfaces,
                        parsed[RollArg.DIFF],
                        RollStatusNormal(self.bot.languageProvider, lid,
                                         parsed[RollArg.MINSUCC]),
                        add,
                        statistics=stats,
                        minsucc=parsed[RollArg.MINSUCC])
                elif parsed[RollArg.ROLLTYPE] == RollType.DANNI:
                    diff = parsed[
                        RollArg.DIFF] if RollArg.DIFF in parsed else 6
                    response = self.rollAndFormatVTM(
                        ctx,
                        ndice,
                        nfaces,
                        diff,
                        RollStatusDMG(self.bot.languageProvider, lid),
                        add,
                        False,
                        statistics=stats)
                elif parsed[RollArg.ROLLTYPE] == RollType.PROGRESSI:
                    diff = parsed[
                        RollArg.DIFF] if RollArg.DIFF in parsed else 6
                    response = self.rollAndFormatVTM(
                        ctx,
                        ndice,
                        nfaces,
                        diff,
                        RollStatusProgress(self.bot.languageProvider, lid),
                        add,
                        False,
                        True,
                        statistics=stats)
                else:
                    raise gb.GreedyCommandError(
                        "string_error_unknown_rolltype", (RollArg.ROLLTYPE, ))
        return response
Пример #12
0
    def parseRollArgs(self, ctx: commands.Context, args_raw: tuple) -> dict:
        parsed = {  # TODO this should probably become a class
            RollArg.ROLLTYPE: RollType.NORMALE,  # default
            RollArg.MINSUCC: 1,
            RollArg.CHARACTER: None,
            RollArg.DADI: 0,
            RollArg.DADI_PERMANENTI: 0
        }
        args = list(args_raw)

        max_dice = int(self.bot.config['BotOptions']['max_dice'])

        # detaching + or - from the end of an expression needs to be done immediately
        i = 0
        while i < len(args):
            if args[i].endswith(ADD_CMD) and args[i] != ADD_CMD:
                args = args[:i] + [args[i][:-1], ADD_CMD] + args[i + 1:]
            if args[i].endswith(SUB_CMD) and args[i] != SUB_CMD:
                args = args[:i] + [args[i][:-1], SUB_CMD] + args[i + 1:]
            i += 1

        # do the actual parsing

        i = 0
        last_i = -1
        repeats = 0
        while i < len(args):
            # bit of safety code due to the fact that we decrement i sometimes
            if i == last_i:
                repeats += 1
            else:
                repeats = 0
            if repeats >= 2:
                raise gb.GreedyCommandError(
                    "string_arg_X_in_Y_notclear",
                    (args[i], utils.prettyHighlightError(args, i)))
            last_i = i

            if args[i] in SOMMA_CMD:
                parsed[RollArg.ROLLTYPE] = RollType.SOMMA
            elif args[i] in DIFF_CMD:
                if RollArg.DIFF in parsed:
                    raise gb.GreedyCommandError("string_error_multiple_diff")
                if len(args) == i + 1:
                    raise gb.GreedyCommandError("string_error_x_what",
                                                (args[i], ))
                i, diff = self.validateDifficulty(ctx, args, i + 1)
                parsed[RollArg.DIFF] = diff
            elif args[i] in MULTI_CMD:
                if RollArg.SPLIT in parsed:
                    raise gb.GreedyCommandError(
                        "string_error_split_before_multi")
                if RollArg.MULTI in parsed:
                    raise gb.GreedyCommandError("string_error_multiple_multi")
                if len(args) == i + 1:
                    raise gb.GreedyCommandError("string_error_x_what",
                                                (args[i], ))
                i, multi = self.validateBoundedNumber(
                    ctx, args, i + 1, 2, utils.INFINITY,
                    self.bot.getStringForUser(
                        ctx, "string_errorpiece_validarg_multi", args[i])
                )  # controlliamo il numero di mosse sotto, dopo aver applicato bonus o penalità al numero di dadi
                parsed[RollArg.MULTI] = multi
            elif args[i] in DANNI_CMD:
                parsed[RollArg.ROLLTYPE] = RollType.DANNI
            elif args[i] in PROGRESSI_CMD:
                parsed[RollArg.ROLLTYPE] = RollType.PROGRESSI
            elif args[i] in SPLIT_CMD:
                roll_index = 0
                split = []
                if RollArg.SPLIT in parsed:  # fetch previous splits
                    split = parsed[RollArg.SPLIT]
                if RollArg.MULTI in parsed:
                    if len(args) < i + 4:
                        raise ValueError(
                            self.bot.getStringForUser(
                                ctx, "string_error_X_takes_Y_params", args[i],
                                3) + " " + self.bot.getStringForUser(
                                    ctx, "string_errorpiece_with_multi"))
                    i, temp = self.validateIntegerGreatZero(ctx, args, i + 1)
                    roll_index = temp - 1
                    if roll_index >= parsed[RollArg.MULTI]:
                        raise gb.GreedyCommandError(
                            "string_error_split_X_higherthan_multi_Y",
                            (args[i + 1], multi))
                    if sum(filter(
                            lambda x: x[0] == roll_index,
                            split)):  # cerco se ho giò splittato questo tiro
                        raise gb.GreedyCommandError(
                            "string_error_already_splitting_X",
                            (roll_index + 1, ))
                else:  # not an elif because reasons
                    if len(args) < i + 3:
                        raise gb.GreedyCommandError(
                            "string_error_X_takes_Y_params", (args[i], 2))
                i, d1 = self.validateIntegerGreatZero(ctx, args, i + 1)
                i, d2 = self.validateIntegerGreatZero(ctx, args, i + 1)
                split.append([roll_index] + list(map(int, [d1, d2])))
                parsed[RollArg.SPLIT] = split  # save the new split
            elif args[i] in [ADD_CMD, SUB_CMD]:
                if len(args) == i + 1:
                    raise gb.GreedyCommandError("string_error_x_what",
                                                (args[i], ))
                # 3 options here: XdY (and variants), trait(s), integers.
                try:
                    sign = (1 - 2 * (args[i] == SUB_CMD)
                            )  # 1 or -1 depending on args[i]
                    i, add = self.validateIntegerGreatZero(
                        ctx, args,
                        i + 1)  # simple positive integer -> add as successes
                    if RollArg.ADD in parsed:
                        parsed[RollArg.ADD] += add * sign
                    else:
                        parsed[RollArg.ADD] = add * sign
                except ValueError as e_add:  # not an integer -> try to parse it as a dice expression
                    parsed = self.parseDiceExpressionIntoSummary(
                        ctx,
                        parsed,
                        args[i + 1],
                        firstNegative=args[i] == SUB_CMD)
                    i += 1
            elif args[i] in PENALITA_CMD:
                parsed[RollArg.PENALITA] = True
            elif args[i] in DADI_CMD:
                if len(args) == i + 1:
                    raise gb.GreedyCommandError("string_error_x_what",
                                                (args[i], ))
                i, val = self.validateBoundedInteger(
                    ctx, args, i + 1, -max_dice,
                    max_dice)  # this is also checked later on the final number
                if RollArg.DADI in parsed:
                    parsed[RollArg.DADI] += val
                    parsed[RollArg.DADI_PERMANENTI] += val
                else:
                    parsed[RollArg.DADI] = val
                    parsed[RollArg.DADI_PERMANENTI] = val
            elif args[i] in PERMANENTE_CMD:
                parsed[RollArg.PERMANENTE] = True
            elif args[i] in STATISTICS_CMD:
                parsed[RollArg.STATS] = True
            elif args[i] in MINSUCC_CMD:
                i, minsucc = self.validateIntegerGreatZero(
                    ctx, args,
                    i + 1)  # simple positive integer -> add as successes
                parsed[RollArg.MINSUCC] = minsucc
            else:
                #try parsing a dice expr
                try:
                    parsed = self.parseDiceExpressionIntoSummary(
                        ctx, parsed, args[i])
                except (gb.GreedyCommandError, gb.BotException,
                        lng.LangSupportErrorGroup) as e:
                    # provo a staccare parametri attaccati
                    did_split = False
                    idx = 0
                    tests = DIFF_CMD + MULTI_CMD + DADI_CMD + [
                        ADD_CMD, SUB_CMD
                    ]
                    while not did_split and idx < len(tests):
                        cmd = tests[idx]
                        if args[i].startswith(cmd):
                            args = args[:i] + [cmd, args[i][len(cmd):]
                                               ] + args[i + 1:]
                            did_split = True
                        idx += 1

                    if not did_split:  # F
                        raise lng.LangSupportErrorGroup(
                            "MultiError", [
                                gb.GreedyCommandError(
                                    "string_arg_X_in_Y_notclear",
                                    (args[i],
                                     utils.prettyHighlightError(args, i))), e
                            ])
                    else:
                        i -= 1  # forzo rilettura
            i += 1
        return parsed
Пример #13
0
    def parseDiceExpression_Mixed(self,
                                  ctx: gb.GreedyContext,
                                  what: str,
                                  firstNegative: bool = False,
                                  character=None) -> DiceExprParsed:
        lid = ctx.getLID()

        saw_trait = False
        saw_notd10 = False

        faces = 0
        n = 0
        n_perm = 0
        n_extrasucc = 0

        split_add_list = what.split(
            ADD_CMD
        )  # split on "+", so each of the results STARTS with something to add
        for i in range(0, len(split_add_list)):
            split_add = split_add_list[i]
            split_sub_list = split_add.split(
                SUB_CMD
            )  # split on "-", so the first element will be an addition (unless firstNegative is true and i == 0), and everything else is a subtraction

            for j in range(0, len(split_sub_list)):
                term = split_sub_list[j]
                n_term = 0
                n_term_perm = 0
                n_term_extra = 0
                new_faces = 0
                try:  # either a xdy expr
                    parsed_expr = self.parseDiceExpression_Dice(term)
                    n_term = parsed_expr.n_dice
                    n_term_perm = parsed_expr.n_dice_permanent
                    new_faces = parsed_expr.n_faces
                    saw_notd10 = saw_notd10 or (new_faces != 10)
                except gb.GreedyCommandError as e:  # or a trait
                    try:
                        if not character:
                            character = self.bot.dbm.getActiveChar(
                                ctx)  # can raise
                        temp = self.bot.dbm.getTrait_LangSafe(
                            character['id'], term, lid)
                        n_term = temp['cur_value']
                        n_term_perm = temp['max_value']
                        saw_trait = True
                        new_faces = 10
                    except ghostDB.DBException as edb:
                        try:
                            _, n_term_extra = self.validateNumber(
                                ctx, [term], 0)
                        except ValueError as ve:
                            raise lng.LangSupportErrorGroup(
                                "MultiError", [
                                    gb.GreedyCommandError(
                                        "string_error_notsure_whatroll"), e,
                                    edb,
                                    gb.GreedyCommandError(f'{ve}')
                                ])

                if new_faces:
                    if faces and (
                            faces != new_faces
                    ):  # we do not support mixing different face numbers for now
                        raise gb.GreedyCommandError("string_error_face_mixing")
                    faces = new_faces

                if saw_trait and saw_notd10:  # forced10 = false only lets through non d10 expressions that DO NOT use traits
                    raise gb.GreedyCommandError("string_error_not_d10")

                if j > 0 or (i == 0 and firstNegative):
                    n -= n_term
                    n_perm -= n_term_perm
                    n_extrasucc -= n_term_extra
                else:
                    n += n_term
                    n_perm += n_term_perm
                    n_extrasucc += n_term_extra

        # is it good that if the espression is just flat numbers we can parse it?
        # for example ".roll 3d10 7" will parse the same as ".roll 3d10 +7"

        return DiceExprParsed(n, n_perm, n_extrasucc, faces, character)