Пример #1
0
class GreedyGhostCog_Misc(gb.GreedyGhostCog):
    @commands.command(name='coin', help='Testa o Croce.')
    @commands.before_invoke(gs.command_security(gs.basicRegisteredUser))
    async def coin(self, ctx: commands.Context):
        moneta = ["string_heads", "string_tails"]
        await self.bot.atSendLang(ctx, random.choice(moneta))

    @commands.command(name='salut',
                      brief='Lascia che il Greedy Ghost ti saluti.')
    @commands.before_invoke(gs.command_security(gs.basicRegisteredUser))
    async def salut(self, ctx: commands.Context):
        await self.bot.atSend(ctx, 'Shalom!')

    @commands.command(name='respect', brief='Pay respect.')
    @commands.before_invoke(gs.command_security(gs.basicRegisteredUser))
    async def respect(self, ctx: commands.Context):
        await self.bot.atSend(ctx, ':regional_indicator_f:')

    @commands.command(name='ping', brief='Fa sapere il ping del Bot')
    @commands.before_invoke(gs.command_security(gs.basicRegisteredUser))
    async def ping(self, ctx: commands.Context):
        await self.bot.atSend(ctx, f' Ping: {int(self.bot.latency * 1000)}ms')

    @commands.command(name='divina',
                      aliases=['divinazione', 'div'],
                      brief='Presagire il futuro con una domanda',
                      help='Inserire comando + domanda')
    @commands.before_invoke(gs.command_security(gs.basicRegisteredUser))
    async def divina(self, ctx: commands.Context, *, question: str):
        responses = [
            'Certamente.', 'Sicuramente.', 'Probabilmente si.', 'Forse.',
            'Mi sa di no.', 'Probabilmente no.', 'Sicuramente no.',
            'Per come la vedo io, si.', 'Non è scontato.',
            'Meglio chiedere a Rossellini.', 'Le prospettive sono buone.',
            'Ci puoi contare.', 'Difficile dare una risposta.',
            'Sarebbe meglio non risponderti adesso.',
            'Sarebbe uno spoiler troppo grosso.', 'Non ci contare.',
            'I miei contatti mi dicono di no.'
        ]
        await self.bot.atSend(
            ctx,
            f'Domanda: {question}\n\nRisposta: {random.choice(responses)}')
Пример #2
0
class GreedyGhostCog_Admin(gb.GreedyGhostCog):
    @commands.command(name='sql', brief='a bad idea.', help="no.", hidden=True)
    @commands.before_invoke(gs.command_security(sec.IsAdmin))
    async def sql(self, ctx: commands.Context, *args):
        query = " ".join(args)
        try:
            query_result_raw = self.bot.dbm.db.query(query)
            # check for integer -> delete statements return an int (and update statements aswell?)
            if isinstance(query_result_raw, int):
                await self.bot.atSend(
                    ctx, f"righe interessate: {query_result_raw}")
                return

            query_result = query_result_raw.list()
            if len(query_result) == 0:
                await self.bot.atSend(ctx, "nessun risultato")
                return

            column_names = list(query_result[0].keys())
            col_widths = list(map(len, column_names))
            for r in query_result:
                for i in range(len(column_names)):
                    length = len(str(r[column_names[i]]))
                    if col_widths[i] < length:
                        col_widths[i] = length
            table_delim = '+' + '+'.join(
                map(lambda x: '-' * (x + 2), col_widths)) + '+'
            out = table_delim + "\n|"
            for i in range(len(column_names)):
                out += " " + column_names[i] + " " * (
                    col_widths[i] - len(column_names[i])) + " |"
            out += "\n" + table_delim + "\n"
            for r in query_result:
                out += "|"
                for i in range(len(column_names)):
                    data = str(r[column_names[i]])
                    out += " " + data + " " * (col_widths[i] -
                                               len(data)) + " |"
                out += "\n"
            out += table_delim
            await self.bot.atSend(ctx, "```\n" + out + "```")
        except MySQLdb.OperationalError as e:
            await self.bot.atSend(ctx,
                                  f"```Errore {e.args[0]}\n{e.args[1]}```")
        except MySQLdb.ProgrammingError as e:
            await self.bot.atSend(ctx,
                                  f"```Errore {e.args[0]}\n{e.args[1]}```")
Пример #3
0
class GreedyGhostCog_PCmgmt(gb.GreedyGhostCog):
    def _getTraitFormatterClass(self, trait) -> type[TraitFormatter]:
        # formattatori specifici
        if trait['id'] == 'generazione':
            return GenerationTraitFormatter
        # formattatori generici
        if trait['textbased']:
            return TextTraitFormatter
        elif trait['trackertype'] == 0:
            return DotTraitFormatter
        elif trait['trackertype'] == 1:
            if trait['id'] == 'sangue':
                return BloodpointTraitFormatter
            else:
                return WillpowerTraitFormatter
        elif trait['trackertype'] == 2:
            return VampireHealthFormatter
        elif trait['trackertype'] == 3:
            return PointAccumulatorTraitFormatter
        else:
            return DefaultTraitFormatter

    def getFormattedTrait(self, ctx: gb.GreedyContext, trait):
        traitFormatterClass = self._getTraitFormatterClass(trait)
        formatter = traitFormatterClass(ctx.getLID(),
                                        self.bot.languageProvider)
        return formatter.format(trait)

    async def pc_interact(self, ctx: gb.GreedyContext, pc: object,
                          can_edit: bool, *args_tuple) -> str:
        lid = ctx.getLID()

        args = list(args_tuple)

        response = ''
        if len(args) == 0:
            parsed = list(
                urllib.parse.urlparse(self.bot.config['Website']
                                      ['website_url']))  # load website url
            parsed[4] = urllib.parse.urlencode({'character':
                                                pc['id']})  # fill query
            unparsed = urllib.parse.urlunparse(tuple(parsed))  # recreate url
            return f"Personaggio: {pc['fullname']}\nScheda: {unparsed}"

        # detach stuff like ["exp+1"] to ["exp", "+1"]" or ["exp-", "1"] to ["exp", "-", "1"] in args
        for op in ["+", "-", "="]:
            idx = args[0].find(op)
            if idx > 0:
                args = [args[0][:idx]] + [args[0][idx:]] + args[1:]
                break

        trait_id = args[0].lower()
        if len(args) == 1:
            trait = self.bot.dbm.getTrait_LangSafe(pc['id'], trait_id, lid)
            return self.getFormattedTrait(ctx, trait)

        # qui siamo sicuri che c'è un'operazione (o spazzatura)
        if not can_edit:
            return f'A sessione spenta puoi solo consultare le tue statistiche'

        param = "".join(args[1:])  # squish
        operazione = None
        if param in reset_aliases:
            operazione = "r"
        else:
            operazione = param[0]
            if not operazione in ["+", "-", "="]:
                return f'Operazione "{operazione}" non supportata'

        trait = self.bot.dbm.getTrait_LangSafe(pc['id'], trait_id, lid)
        #trait = dbm.getTrait(pc['id'], trait_id)
        if trait['pimp_max'] == 0 and trait['trackertype'] == 0:
            raise gb.BotException(
                f"Non puoi modificare il valore corrente di {trait['traitName']}"
            )
        if trait['trackertype'] != 2:
            n = None
            if operazione != "r":
                n = param[1:]
                if not (n.isdigit()):
                    return f'"{n}" non è un intero >= 0'

            if operazione == "r":
                if trait['trackertype'] == 1 or trait['trackertype'] == 0:
                    n = trait['max_value'] - trait['cur_value']
                elif trait['trackertype'] == 3:
                    n = -trait['cur_value']
                else:
                    raise gb.BotException(
                        f"Tracker {trait['trackertype']} non supportato")
            elif operazione == "=":
                n = int(param[1:]) - trait['cur_value']  # tricks
            else:
                n = int(param)
            new_val = trait['cur_value'] + n
            max_val = max(trait['max_value'], trait['pimp_max'])
            if new_val < 0:
                raise gb.BotException(
                    f'Non hai abbastanza {trait["traitName"].lower()}!')
            elif new_val > max_val and trait['trackertype'] != 3:
                raise gb.BotException(
                    f"Non puoi avere {new_val} {trait['traitName'].lower()}. Valore massimo: {max_val}"
                )
            #
            u = self.bot.dbm.db.update(
                'CharacterTrait',
                where='trait = $trait and playerchar = $pc',
                vars=dict(trait=trait['id'], pc=pc['id']),
                cur_value=new_val)
            self.bot.dbm.log(ctx.message.author.id, pc['id'], trait['trait'],
                             ghostDB.LogType.CUR_VALUE, new_val,
                             trait['cur_value'], ctx.message.content)
            if u == 1:
                trait = self.bot.dbm.getTrait_LangSafe(pc['id'], trait_id, lid)
                return self.getFormattedTrait(ctx, trait)
            elif u == 0:
                trait = self.bot.dbm.getTrait_LangSafe(pc['id'], trait_id, lid)
                return self.getFormattedTrait(ctx, trait)
            else:
                return f'Qualcosa è andato storto, righe aggiornate:  {u}'

        # salute
        response = ''
        n = param[
            1:-1]  # 1st char is the operation, last char is the damaage type
        if n == '':
            n = 1
        elif n.isdigit():
            n = int(n)
        elif operazione == "=" or operazione == "r":
            pass
        else:
            raise gb.BotException(f'"{n}" non è un parametro valido!')
        dmgtype = param[-1].lower()
        new_health = trait['text_value']
        if (not dmgtype in damage_types) and operazione != "r":
            raise gb.BotException(f'"{dmgtype}" non è un tipo di danno valido')
        if operazione == "r":
            new_health = ""

            u = self.bot.dbm.db.update(
                'CharacterTrait',
                where='trait = $trait and playerchar = $pc',
                vars=dict(trait=trait['id'], pc=pc['id']),
                text_value=new_health,
                cur_value=trait['max_value'])
            self.bot.dbm.log(ctx.message.author.id, pc['id'], trait['trait'],
                             ghostDB.LogType.CUR_VALUE, new_health,
                             trait['text_value'], ctx.message.content)
            if u != 1:
                raise gb.BotException(
                    f'Qualcosa è andato storto, righe aggiornate: {u}')
            trait = self.bot.dbm.getTrait_LangSafe(pc['id'], trait_id, lid)
            response = self.getFormattedTrait(ctx, trait)
        elif operazione == "+":
            rip = False
            for i in range(
                    n):  # applico i danni uno alla volta perchè sono un nabbo
                if dmgtype == "c" and new_health.endswith(
                        "c"):  # non rischio di cambiare la lunghezza
                    new_health = new_health[:-1] + "l"
                else:
                    if len(new_health) < trait[
                            'max_value']:  # non ho già raggiunto il massimo
                        if dmgtype == "c":
                            new_health += "c"
                        elif dmgtype == "a":
                            new_health = "a" + new_health
                        else:
                            la = new_health.rfind("a") + 1
                            new_health = new_health[:la] + "l" + new_health[la:]
                    else:  # oh no
                        convert = False
                        if dmgtype == "c":
                            if trait['cur_value'] > 0:  # trick per salvarsi mezzo aggravato
                                trait['cur_value'] = 0
                            else:
                                convert = True
                                trait['cur_value'] = trait['max_value']
                        elif dmgtype == 'l':
                            convert = True
                        else:
                            rip = True

                        if convert:
                            fl = new_health.find('l')
                            if fl >= 0:
                                new_health = new_health[:fl] + 'a' + new_health[
                                    fl + 1:]
                            else:
                                rip = True
                        if rip:
                            break
            if new_health.count("a") == trait['max_value']:
                rip = True

            u = self.bot.dbm.db.update(
                'CharacterTrait',
                where='trait = $trait and playerchar = $pc',
                vars=dict(trait=trait['id'], pc=pc['id']),
                text_value=new_health,
                cur_value=trait['cur_value'])
            self.bot.dbm.log(ctx.message.author.id, pc['id'], trait['trait'],
                             ghostDB.LogType.CUR_VALUE, new_health,
                             trait['text_value'], ctx.message.content)
            if u != 1 and not rip:
                raise gb.BotException(
                    f'Qualcosa è andato storto, righe aggiornate: {u}')
            trait = self.bot.dbm.getTrait_LangSafe(pc['id'], trait_id, lid)
            response = self.getFormattedTrait(ctx, trait)
            if rip:
                response += "\n\n RIP"
        elif operazione == "-":
            if dmgtype == "a":
                if new_health.count(dmgtype) < n:
                    raise gb.BotException("Non hai tutti quei danni aggravati")
                else:
                    new_health = new_health[n:]
            elif dmgtype == "l":
                if new_health.count(dmgtype) < n:
                    raise gb.BotException("Non hai tutti quei danni letali")
                else:
                    fl = new_health.find(dmgtype)
                    new_health = new_health[:fl] + new_health[fl + n:]
            else:  # dio can
                if ((int(trait['cur_value']) == 0) + new_health.count(dmgtype)
                        + new_health.count("l") * 2) < n:
                    raise gb.BotException(
                        "Non hai tutti quei danni contundenti")
                for i in range(n):
                    if trait['cur_value'] == 0:
                        trait['cur_value'] = trait[
                            'max_value']  # togli il mezzo aggravato
                    else:
                        if new_health[-1] == 'c':
                            new_health = new_health[:-1]
                        elif new_health[-1] == 'l':
                            new_health = new_health[:-1] + 'c'
                        else:
                            raise gb.BotException(
                                "Non hai tutti quei danni contundenti"
                            )  # non dovrebbe mai succedere
            u = self.bot.dbm.db.update(
                'CharacterTrait',
                where='trait = $trait and playerchar = $pc',
                vars=dict(trait=trait['id'], pc=pc['id']),
                text_value=new_health,
                cur_value=trait['cur_value'])
            self.bot.dbm.log(ctx.message.author.id, pc['id'], trait['trait'],
                             ghostDB.LogType.CUR_VALUE, new_health,
                             trait['text_value'], ctx.message.content)
            if u != 1:
                raise gb.BotException(
                    f'Qualcosa è andato storto, righe aggiornate: {u}')
            trait = self.bot.dbm.getTrait_LangSafe(pc['id'], trait_id, lid)
            response = self.getFormattedTrait(ctx, trait)
        else:  # =
            full = param[1:]
            counts = list(map(lambda x: full.count(x), damage_types))
            if sum(counts) != len(full):
                raise gb.BotException(f'"{full}" non è un parametro valido!')
            new_health = "".join(
                list(map(lambda x: x[0] * x[1],
                         zip(damage_types,
                             counts))))  # siamo generosi e riordiniamo l'input

            u = self.bot.dbm.db.update(
                'CharacterTrait',
                where='trait = $trait and playerchar = $pc',
                vars=dict(trait=trait['id'], pc=pc['id']),
                text_value=new_health,
                cur_value=1)
            self.bot.dbm.log(ctx.message.author.id, pc['id'], trait['trait'],
                             ghostDB.LogType.CUR_VALUE, new_health,
                             trait['text_value'], ctx.message.content)
            if u != 1:
                raise gb.BotException(
                    f'Qualcosa è andato storto, righe aggiornate: {u}')
            trait = self.bot.dbm.getTrait_LangSafe(pc['id'], trait_id, lid)
            response = self.getFormattedTrait(ctx, trait)

        return response

    @commands.command(
        name='me',
        brief=
        'Permette ai giocatori di interagire col proprio personaggio durante le sessioni',
        help=me_description)
    @commands.before_invoke(gs.command_security(gs.basicRegisteredUser))
    async def me(self, ctx: commands.Context, *args):
        pc = self.bot.dbm.getActiveChar(ctx)
        response = await self.pc_interact(ctx, pc, True, *args)
        await self.bot.atSend(ctx, response)

    @commands.command(
        name='pgmanage',
        brief=
        'Permette ai giocatori di interagire col proprio personaggio durante le sessioni',
        help=pgmanage_description)
    @commands.before_invoke(gs.command_security(gs.basicRegisteredUser))
    async def pgmanage(self, ctx: commands.Context, *args):
        if len(args) == 0:
            raise gb.BotException('Specifica un pg!')

        charid = args[0].lower()
        isChar, character = self.bot.dbm.validators.getValidateCharacter(
            charid).validate()
        if not isChar:
            raise gb.BotException(f"Il personaggio {charid} non esiste!")

        # TODO move to security. The reason why we do this here is because "ce" is computed in security checks, but is needed in the command (to be passed to pc_interact)
        # we need a way for security checks to pass on some stuff to the command (maybe with the ctx object?)

        # permission checks
        issuer = str(ctx.message.author.id)
        playerid = character['player']

        st, _ = self.bot.dbm.isStorytellerForCharacter(issuer, charid)
        ba, _ = self.bot.dbm.validators.getValidateBotAdmin(issuer).validate()
        co = playerid == issuer
        ce = st or ba  # can edit
        if co and (not ce):
            #1: unlinked
            cl, _ = self.bot.dbm.isCharacterLinked(charid)
            #2 active session
            sa, _ = self.bot.dbm.isSessionActiveForCharacter(
                charid, ctx.channel.id)
            ce = (not cl) or sa
        if not (st or ba or co):
            return  # non vogliamo che .rossellini faccia cose
            #raise BotException("Per modificare un personaggio è necessario esserne proprietari e avere una sessione aperta, oppure essere Admin o Storyteller")

        response = await self.pc_interact(ctx, character, ce, *args[1:])
        await self.bot.atSend(ctx, response)
Пример #4
0
class GreedyGhostCog_PCMod(gb.GreedyGhostCog):

    @commands.group(brief='Gestione personaggi')
    @commands.before_invoke(gs.command_security(gs.basicRegisteredUser))
    async def pgmod(self, ctx: commands.Context):
        if ctx.invoked_subcommand is None:
            response = utils.discord_text_format_mono('Azioni disponibili:\n\n' + '\n'.join(list(map(lambda k: f'{k} - {pgmod_help[k][1]}', pgmod_help))))
            await self.bot.atSend(ctx, response)

    @pgmod.command(name = 'create', brief = "Crea un personaggio", description = create_description)
    @commands.before_invoke(gs.command_security(sec.OR(sec.IsAdmin, sec.AND( sec.OR(sec.IsActiveOnGuild, sec.IsPrivateChannelWithRegisteredUser), sec.OR(sec.IsStoryteller, sec.genIsSelf(optional_target_user = 3))))))
    async def create(self, ctx : commands.Context, character_id: gc.GreedyShortIdConverter, user: gc.RegisteredUserConverter, name, *args):
        fullname = " ".join(list([name]+list(args)))

        self.bot.dbm.newCharacter(character_id, fullname, user['userid'])
        await self.bot.atSend(ctx, f'Il personaggio {fullname} è stato inserito!')

    @pgmod.command(name = 'link', brief = "Aggiunge un personaggio ad una cronaca", description = link_description)
    @commands.before_invoke(gs.command_security(sec.OR(sec.IsAdmin, sec.AND( sec.OR(sec.IsActiveOnGuild, sec.IsPrivateChannelWithRegisteredUser), sec.genIsChronicleStoryteller(target_chronicle = 3)))))
    async def link(self, ctx: commands.Context, character: gc.CharacterConverter, chronicle: gc.ChronicleConverter):
        is_linked, _ = self.bot.dbm.isCharacterLinkedToChronicle(character['id'], chronicle['id'])
        if is_linked:
            await self.bot.atSend(ctx, f"C'è già un'associazione tra {character['fullname']} e {chronicle['name']}")
        else:
            self.bot.dbm.db.insert("ChronicleCharacterRel", chronicle=chronicle['id'], playerchar=character['id'])
            await self.bot.atSend(ctx, f"{character['fullname']} ora gioca a {chronicle['name']}")

    @pgmod.command(name = 'unlink', brief = "Disassocia un personaggio da una cronaca", description = unlink_description)
    @commands.before_invoke(gs.command_security(sec.OR(sec.IsAdmin, sec.AND( sec.OR(sec.IsActiveOnGuild, sec.IsPrivateChannelWithRegisteredUser), sec.genIsChronicleStoryteller(target_chronicle = 3)))))
    async def unlink(self, ctx: commands.Context, character: gc.CharacterConverter, chronicle: gc.ChronicleConverter):
        is_linked, _ = self.bot.dbm.isCharacterLinkedToChronicle(character['id'], chronicle['id'])
        if is_linked:
            self.bot.dbm.db.delete("ChronicleCharacterRel", where = 'playerchar=$playerchar and chronicle=$chronicleid', vars=dict(chronicleid=chronicle['id'], playerchar=character['id']))
            await self.bot.atSend(ctx, f"{character['fullname']} ora non gioca più a  {chronicle['name']}")
        else:
            await self.bot.atSend(ctx, f"Non c\'è un\'associazione tra {character['fullname']} e {chronicle['name']}")

    @pgmod.command(name = 'addt', brief = "Aggiunge tratto ad un personaggio", description = addt_description)
    @commands.before_invoke(gs.command_security(sec.OR(sec.IsAdmin, sec.AND( sec.OR(sec.IsActiveOnGuild, sec.IsPrivateChannelWithRegisteredUser), sec.genCanEditCharacter(target_character=2)))))
    async def addt(self, ctx: gb.GreedyContext, character: gc.CharacterConverter, trait: gc.TraitConverter, value, *args):
        issuer = str(ctx.message.author.id)
        
        try:
            lid = ctx.getLID()
            ptrait = self.bot.dbm.getTrait_LangSafe(character['id'], trait['id'], lid)
            raise gb.BotException(f"{character['fullname']} ha già il tratto {ptrait['name']} ")
        except ghostDB.DBException:
            pass
        
        ttype = self.bot.dbm.db.select('TraitType', where='id=$id', vars=dict(id=trait['traittype']))[0]
        val = None
        if ttype['textbased']:
            val = " ".join([value]+list(args))
            self.bot.dbm.db.insert("CharacterTrait", trait=trait['id'], playerchar=character['id'], cur_value = 0, max_value = 0, text_value = val, pimp_max = 0)
            self.bot.dbm.log(issuer, character['id'], trait['id'], ghostDB.LogType.TEXT_VALUE, val, '', ctx.message.content)
        else:
            val = int(value)
            pimp = 6 if trait['traittype'] in ['fisico', 'sociale', 'mentale'] else 0
            self.bot.dbm.db.insert("CharacterTrait", trait=trait['id'], playerchar=character['id'], cur_value = val, max_value = val, text_value = "", pimp_max = pimp)
            self.bot.dbm.log(issuer, character['id'], trait['id'], ghostDB.LogType.MAX_VALUE, val, '', ctx.message.content)
        
        await self.bot.atSend(ctx, f"{character['fullname']} ora ha {trait['name']} {val}")

    @pgmod.command(name = 'modt', brief = "Modifica un tratto di un personaggio", description = modt_description)
    @commands.before_invoke(gs.command_security(sec.OR(sec.IsAdmin, sec.AND( sec.OR(sec.IsActiveOnGuild, sec.IsPrivateChannelWithRegisteredUser), sec.genCanEditCharacter(target_character = 2)))))
    async def modt(self, ctx: gb.GreedyContext, character: gc.CharacterConverter, trait: gc.TraitConverter, value, *args):
        issuer = str(ctx.message.author.id)
        
        chartrait = self.bot.dbm.getTrait_LangSafe(character['id'], trait['id'], ctx.getLID())
                
        ttype = self.bot.dbm.db.select('TraitType', where='id=$id', vars=dict(id=trait['traittype']))[0]
        val = None
        if ttype['textbased']:
            val = " ".join([value]+list(args))
            self.bot.dbm.db.update("CharacterTrait", where='trait = $trait and playerchar = $pc', vars=dict(trait=trait['id'], pc=character['id']), text_value = val)
            self.bot.dbm.log(issuer, character['id'], trait['id'], ghostDB.LogType.TEXT_VALUE, val, chartrait['text_value'], ctx.message.content)
        else:
            val = int(value)
            self.bot.dbm.db.update("CharacterTrait", where='trait = $trait and playerchar = $pc', vars=dict(trait=trait['id'], pc=character['id']), cur_value = val, max_value = val)
            self.bot.dbm.log(issuer, character['id'], trait['id'], ghostDB.LogType.MAX_VALUE, val, chartrait['max_value'], ctx.message.content)
            self.bot.dbm.log(issuer, character['id'], trait['id'], ghostDB.LogType.CUR_VALUE, val, chartrait['cur_value'], ctx.message.content)
        
        await self.bot.atSend(ctx, f"{character['fullname']} ora ha {trait['name']} {val}")
    
    
    @pgmod.command(name = 'rmt', brief = "Rimuovi un tratto ad un personaggio", description = rmt_description)
    @commands.before_invoke(gs.command_security(sec.OR(sec.IsAdmin, sec.AND( sec.OR(sec.IsActiveOnGuild, sec.IsPrivateChannelWithRegisteredUser), sec.genCanEditCharacter(target_character = 2)))))
    async def rmt(self, ctx: gb.GreedyContext, character: gc.CharacterConverter, trait: gc.TraitConverter):
        issuer = str(ctx.message.author.id)
        chartrait = self.bot.dbm.getTrait_LangSafe(character['id'], trait['id'], ctx.getLID())
        ttype = self.bot.dbm.db.select('TraitType', where='id=$id', vars=dict(id=trait['traittype']))[0]
        
        updated_rows = self.bot.dbm.db.delete("CharacterTrait", where='trait = $trait and playerchar = $pc', vars=dict(trait=trait['id'], pc=character['id']))
        if ttype['textbased']:
            self.bot.dbm.log(issuer, character['id'], trait['id'], ghostDB.LogType.DELETE, "", chartrait['text_value'], ctx.message.content)
        else:
            self.bot.dbm.log(issuer, character['id'], trait['id'], ghostDB.LogType.DELETE, "", f"{chartrait['cur_value']}/{chartrait['max_value']}", ctx.message.content)
        if updated_rows > 0:
            await self.bot.atSend(ctx, f"Rimosso {trait['name']} da {character['fullname']} ({updated_rows})")
        else:
            await self.bot.atSend(ctx, f"Nessun tratto rimosso")

    @pgmod.command(name = 'reassign', brief = pgmod_help["reassign"][1], description = pgmod_help["reassign"][0])
    @commands.before_invoke(gs.command_security(sec.OR(sec.IsAdmin, sec.AND( sec.OR(sec.IsActiveOnGuild, sec.IsPrivateChannelWithRegisteredUser), sec.genCanEditCharacter(target_character = 2)))))
    async def reassign(self, ctx: commands.Context, character: gc.CharacterConverter, user: gc.RegisteredUserConverter):
        self.bot.dbm.reassignCharacter(character['id'], user['userid'])        
        await self.bot.atSendLang(ctx, "string_msg_character_reassigned_to_user", character["fullname"], user['name'])
Пример #5
0
class GreedyGhostCog_Lang(gb.GreedyGhostCog):
    @commands.command(
        name='lang',
        brief='Impostazioni di lingua',
        description="Permette di cambiare impostazioni di lingua del bot")
    @commands.before_invoke(gs.command_security(gs.basicRegisteredUser))
    async def lang(self,
                   ctx: gb.GreedyContext,
                   language: gc.LanguageConverter = None):
        issuer = ctx.message.author.id
        if not language:
            await self.bot.atSendLang(ctx, "string_your_lang_is", ctx.getLID())
        else:
            langId = language['langId']
            _ = ctx.getUserInfo()
            self.bot.dbm.db.update("People",
                                   where='userid  = $userid',
                                   vars=dict(userid=issuer),
                                   langId=langId)
            ctx._loadUserInfo(
            )  # we need to reload the language id for the user
            lid = language['langId']
            await self.bot.atSendLang(ctx, "string_lang_updated_to", lid)

    @commands.command(
        name='translate',
        brief='Permette di aggiornare la traduzione di un tratto in una lingua'
    )
    @commands.before_invoke(gs.command_security(gs.basicStoryTeller))
    async def translate(self,
                        ctx: commands.Context,
                        language: gc.LanguageConverter,
                        trait: gc.TraitConverter,
                        traitlang: gc.GreedyShortIdConverter = None,
                        *args):
        traitId = trait['id']
        langId = language['langId']

        #TODO: use getters for searching LangTrait

        if traitlang == None:  # consulto
            traits = self.bot.dbm.db.select(
                "LangTrait",
                where='traitId = $traitId and langId = $langId',
                vars=dict(traitId=traitId, langId=langId))
            if len(traits):
                trait = traits[0]
                await self.bot.atSendLang(
                    ctx, "string_msg_trait_X_in_Y_has_translation_Z1_Z2",
                    traitId, language['langName'], trait['traitName'],
                    trait['traitShort'])
            else:
                await self.bot.atSendLang(
                    ctx, "string_msg_trait_X_not_translated_Y", traitId,
                    language['langName'])
        elif len(args) != 0:  # update/inserimento
            traits = self.bot.dbm.db.select(
                "LangTrait",
                where='traitId = $traitId and langId = $langId',
                vars=dict(traitId=traitId, langId=langId))
            tratiNameLang = " ".join(list(args))
            if len(traits):  # update
                trait = traits[0]
                u = self.bot.dbm.db.update(
                    "LangTrait",
                    where='traitId = $traitId and langId = $langId',
                    vars=dict(traitId=traitId, langId=langId),
                    traitShort=traitlang,
                    traitName=tratiNameLang)
                if u == 1:
                    await self.bot.atSendLang(
                        ctx, "string_msg_trait_X_in_Y_has_translation_Z1_Z2",
                        traitId, language['langName'], tratiNameLang,
                        traitlang)
                else:
                    await self.bot.atSendLang(
                        ctx, "string_error_update_wrong_X_rows_affected", u)
            else:
                self.bot.dbm.db.insert("LangTrait",
                                       langId=langId,
                                       traitId=traitId,
                                       traitShort=traitlang,
                                       traitName=tratiNameLang)
                await self.bot.atSendLang(
                    ctx, "string_msg_trait_X_in_Y_has_translation_Z1_Z2",
                    traitId, language['langName'], tratiNameLang, traitlang)
        else:
            await self.bot.atSendLang(ctx, "string_help_translate")
Пример #6
0
class GreedyGhostCog_Session(gb.GreedyGhostCog):
    @commands.group(
        name='session',
        brief='Controlla le sessioni di gioco',
        description=
        "Le sessioni sono basate sui canali: un canale può ospitare una sessione alla volta, ma la stessa cronaca può avere sessioni attive in più canali."
    )
    @commands.before_invoke(gs.command_security(gs.basicStoryTeller))
    async def session(self, ctx: commands.Context):
        if ctx.invoked_subcommand is None:
            sessions = self.bot.dbm.db.select(
                'GameSession',
                where='channel=$channel',
                vars=dict(channel=ctx.channel.id))
            if len(sessions):
                chronicle = self.bot.dbm.db.select(
                    'Chronicle',
                    where='id=$chronicle',
                    vars=dict(chronicle=sessions[0]['chronicle']))
                cn = chronicle[0]['name']
                await self.bot.atSend(ctx, f"Sessione attiva: {cn}")
            else:
                await self.bot.atSend(
                    ctx, "Nessuna sessione attiva in questo canale!")

    @session.command(
        name='start',
        brief='Inizia una sessione',
        description=
        '.session start <nomecronaca>: inizia una sessione per <nomecronaca> (richiede essere admin o storyteller della cronaca da iniziare) (richiede essere admin o storyteller della cronaca da iniziare)'
    )
    @commands.before_invoke(
        gs.command_security(
            sec.OR(
                sec.IsAdmin,
                sec.AND(
                    sec.OR(sec.IsActiveOnGuild,
                           sec.IsPrivateChannelWithRegisteredUser),
                    sec.genIsChronicleStoryteller(target_chronicle=2)))))
    async def start(self, ctx: commands.Context,
                    chronicle: gc.ChronicleConverter):
        chronicleid = chronicle['id'].lower()
        response = ''

        sessions = self.bot.dbm.db.select('GameSession',
                                          where='channel=$channel',
                                          vars=dict(channel=ctx.channel.id))
        if len(sessions):
            response = f"C'è già una sessione in corso in questo canale: {sessions[0]['chronicle']}"
        else:
            self.bot.dbm.db.insert('GameSession',
                                   chronicle=chronicleid,
                                   channel=ctx.channel.id)
            response = f"Sessione iniziata per la cronaca {chronicle['name']}"
            # TODO lista dei pg?
            # TODO notifica i giocatori di pimp attivi

        await self.bot.atSend(ctx, response)

    @session.command(
        name='list',
        brief='Elenca le sessioni aperte',
        description=
        'Elenca le sessioni aperte. richiede di essere admin o storyteller')
    @commands.before_invoke(gs.command_security(gs.basicStoryTeller))
    async def session_list(self, ctx: commands.Context):
        sessions = self.bot.dbm.db.select('GameSession').list()
        channels = []
        lines = []
        for s in sessions:
            try:
                ch = await self.bot.fetch_channel(int(s['channel']))
                channels.append(ch)
            except discord.errors.Forbidden as e:
                lines.append(f"**{s['chronicle']}** in: UNKNOWN")
                channels.append(None)
        #pvt = 0
        for session, channel in zip(sessions, channels):
            if isinstance(channel, discord.abc.GuildChannel):
                lines.append(
                    f"**{session['chronicle']}** in: {channel.guild.name}/{channel.category}/{channel.name}"
                )
            elif isinstance(channel, discord.abc.PrivateChannel):
                lines.append(
                    f"**{session['chronicle']}** in un canale privato")
        if not len(lines):
            lines.append("Nessuna!")
        response = "Sessioni attive:\n" + ("\n".join(lines))
        await self.bot.atSend(ctx, response)

    @session.command(
        name='end',
        brief='Termina la sessione corrente',
        description=
        'Termina la sessione corrente. Richiede di essere admin o storyteller della sessione in corso.'
    )
    @commands.before_invoke(
        gs.command_security(
            sec.OR(
                sec.IsAdmin,
                sec.AND(
                    sec.OR(sec.IsActiveOnGuild,
                           sec.IsPrivateChannelWithRegisteredUser),
                    sec.CanEditRunningSession))))
    async def end(self, ctx: commands.Context):
        response = ''

        n = self.bot.dbm.db.delete('GameSession',
                                   where='channel=$channel',
                                   vars=dict(channel=ctx.channel.id))
        if n:
            response = f'sessione terminata'
        else:  # non dovrebbe mai accadere
            response = f'Non c\'è una sessione aperta in questo canale'

        await self.bot.atSend(ctx, response)
Пример #7
0
class GreedyGhostCog_GMadm(gb.GreedyGhostCog):
        
    @commands.group(brief='Gestione sistema di gioco')
    @commands.before_invoke(gs.command_security(gs.basicStoryTeller))
    async def gmadm(self, ctx: commands.Context):
        if ctx.invoked_subcommand is None:
            response = utils.discord_text_format_mono('Azioni disponibili:\n\n' + '\n'.join(list(map(lambda k: f'{k} - {gmadm_help[k][1]}', gmadm_help))))
            await self.bot.atSend(ctx, response)

    @gmadm.command(name = 'listChronicles', brief = gmadm_help['listChronicles'][1], description = listChronicles_description)
    @commands.before_invoke(gs.command_security(gs.basicStoryTeller))
    async def listChronicles(self, ctx: commands.Context):

        query = """
    SELECT cr.id as cid, cr.name as cname, p.name as pname
    FROM Chronicle cr
    JOIN StoryTellerChronicleRel stcr on (cr.id = stcr.chronicle)
    JOIN People p on (stcr.storyteller = p.userid)
"""
        results = self.bot.dbm.db.query(query)
        if len(results) == 0:
            await self.bot.atSend(ctx, "Nessuna cronaca trovata!")
            return

        chronicles = {}
        crst = {}
        for c in results:
            chronicles[c['cid']] = c['cname']
            if not c['cid'] in crst:
                crst[c['cid']] = []
            crst[c['cid']].append(c['pname'])

        await self.bot.atSend(ctx, "Cronache:\n" + "\n".join(list(map(lambda x: f"**{chronicles[x]}** ({x}) (storyteller: {', '.join(crst[x])})", chronicles))))

    @gmadm.command(name = 'newChronicle', brief = gmadm_help['newChronicle'][1], description = newChronicle_description)
    @commands.before_invoke(gs.command_security(gs.basicStoryTeller))
    async def newChronicle(self, ctx: commands.Context, shortname: gc.GreedyShortIdConverter,  *args):
        if len(args) == 0:
            self.bot.atSend(ctx, newChronicle_description)
            return 

        fullname = " ".join(list(args)) # squish

        issuer = str(ctx.message.author.id)

        # todo existence
        t = self.bot.dbm.db.transaction()
        try:
            self.bot.dbm.db.insert("Chronicle", id=shortname, name = fullname)
            self.bot.dbm.db.insert("StoryTellerChronicleRel", storyteller=issuer, chronicle=shortname)
        except:
            t.rollback()
            raise
        else:
            t.commit()
            issuer_user = await self.bot.fetch_user(issuer)
        
        await self.bot.atSend(ctx, f"Cronaca {fullname} inserita ed associata a {issuer_user}")

    @gmadm.command(name = 'newTrait', brief = gmadm_help['newTrait'][1], description = newTrait_description)
    @commands.before_invoke(gs.command_security(gs.basicStoryTeller))
    async def newTrait(self, ctx: commands.Context, traitid: gc.GreedyShortIdConverter, traittype: gc.TraitTypeConverter, tracktype: gc.TrackerTypeConverter, std: gc.NoYesConverter, *args):
        if len(args) == 0:
            await self.bot.atSend(ctx, newTrait_description)
            return
                
        istrait, _ = self.bot.dbm.validators.getValidateTrait(traitid).validate()
        if istrait:
            raise gb.BotException(f"Il tratto {traitid} esiste già!")
        
        traittypeid = traittype['id']

        traitname = " ".join(args)

        response = ""
        t = self.bot.dbm.db.transaction()
        try:
            self.bot.dbm.db.insert("Trait", id = traitid, name = traitname, traittype = traittypeid, trackertype = tracktype, standard = std, ordering = 1.0)
            # we insert it in all available languages and we assume that it will be translated later:
            # better have it in the wrong language than not having it at all
            self.bot.dbm.db.query(query_addTraitLangs, vars = dict(traitid=traitid, traitname=traitname))
            response = f'Il tratto {traitname} è stato inserito'
            if std:
                self.bot.dbm.db.query(query_addTraitToPCs, vars = dict(traitid=traitid))
                response +=  f'\nIl nuovo tratto standard {traitname} è stato assegnato ai personaggi!'
        except:
            t.rollback()
            raise
        else:
            t.commit()

        await self.bot.atSend(ctx, response)

    @gmadm.command(name = 'updt', brief = gmadm_help['updt'][1], description = updt_description)
    @commands.before_invoke(gs.command_security(gs.basicStoryTeller))
    async def updt(self, ctx: gb.GreedyContext, old_traitid: gc.GreedyShortIdConverter, new_traitid: gc.GreedyShortIdConverter, traittype: gc.TraitTypeConverter, tracktype: gc.TrackerTypeConverter, std: gc.NoYesConverter, *args):    
        if len(args) == 0:
            await self.bot.atSend(ctx, newTrait_description)
            return
        
        istrait, old_trait = self.bot.dbm.validators.getValidateTrait( old_traitid).validate()
        if not istrait:
            raise gb.BotException(f"Il tratto {old_traitid} non esiste!")
        
        istrait, new_trait = self.bot.dbm.validators.getValidateTrait(new_traitid).validate()
        if istrait and (old_traitid!=new_traitid):
            raise gb.BotException(f"Il tratto {new_traitid} esiste già!")

        traittypeid = traittype['id']

        traitname = " ".join(list(args))
        
        response = f'Il tratto {traitname} è stato aggiornato'
        t = self.bot.dbm.db.transaction()
        try:
            self.bot.dbm.db.update("Trait", where= 'id = $oldid' , vars=dict(oldid = old_traitid), id = new_traitid, name = traitname, traittype = traittypeid, trackertype = tracktype, standard = std, ordering = 1.0)
            # now we update the language description, but only of the current language
            self.bot.dbm.db.update("LangTrait", where= 'traitId = $traitid and langId = $lid' , vars=dict(traitid=new_traitid, lid = ctx.getLID()), traitName = traitname)
            if std and not old_trait['standard']:
                self.bot.dbm.db.query(query_addTraitToPCs_safe, vars = dict(traitid=new_traitid))
                response +=  f'\nIl nuovo talento standard {traitname} è stato assegnato ai personaggi!'
            elif not std and old_trait['standard']:
                self.bot.dbm.db.query("""
    delete from CharacterTrait
    where trait = $traitid and max_value = 0 and cur_value = 0 and text_value = '';
    """, vars = dict(traitid=new_traitid))
                response += f'\nIl talento {traitname} è stato rimosso dai personaggi che non avevano pallini'
        except:
            t.rollback()
            raise
        else:
            t.commit()

        await self.bot.atSend(ctx, response)

    @gmadm.command(name = 'link', brief = gmadm_help['link'][1], description = link_description)
    @commands.before_invoke(gs.command_security(sec.OR(sec.IsAdmin, sec.AND( sec.OR(sec.IsActiveOnGuild, sec.IsPrivateChannelWithRegisteredUser), sec.genIsChronicleStoryteller(target_chronicle=2)))))
    async def link(self, ctx: commands.Context, chronicle: gc.ChronicleConverter, storyteller: gc.StorytellerConverter = None):
        issuer = str(ctx.message.author.id)
        
        chronid = chronicle['id']

        target_st = None
        if storyteller == None:
            storyteller = await gc.StorytellerConverter().convert(ctx, issuer)
        target_st = storyteller['userid']
        
        t_stc, _ = self.bot.dbm.isChronicleStoryteller(target_st, chronid)
        if t_stc:
            raise gb.BotException(f"L'utente selezionato è già Storyteller per {chronid}")  

        # link
        self.bot.dbm.db.insert("StoryTellerChronicleRel", storyteller=target_st, chronicle=chronid)
        await self.bot.atSend(ctx, f"Cronaca associata")

    @gmadm.command(name = 'unlink', brief = gmadm_help['unlink'][1], description = unlink_description)
    @commands.before_invoke(gs.command_security(sec.OR(sec.IsAdmin, sec.AND( sec.OR(sec.IsActiveOnGuild, sec.IsPrivateChannelWithRegisteredUser), sec.genIsChronicleStoryteller(target_chronicle=2), sec.genIsSelf(optional_target_user=3)))))
    async def unlink(self, ctx: context.Context, chronicle: gc.ChronicleConverter, storyteller: gc.StorytellerConverter = None):

        issuer = str(ctx.message.author.id)
        chronid = chronicle['id']
        
        target_st = None
        if storyteller == None:
            storyteller = await gc.StorytellerConverter().convert(ctx, issuer)
        target_st = storyteller['userid'] 

        # link
        n = self.bot.dbm.db.delete('StoryTellerChronicleRel', where='storyteller=$storyteller and chronicle=$chronicle', vars=dict(storyteller=target_st, chronicle=chronid))
        if n:
            await self.bot.atSend(ctx, f"Cronaca disassociata")
        else:
            await self.bot.atSend(ctx, f"Nessuna cronaca da disassociare")

    @gmadm.command(name = 'name', brief = gmadm_help['name'][1], description = name_description)
    @commands.before_invoke(gs.command_security(sec.IsAdmin))
    async def name(self, ctx: commands.Context, user: gc.RegisteredUserConverter):
        
        target_st = user['userid'] 
        name = user['name']

        t_st, _ = self.bot.dbm.validators.getValidateBotStoryTeller(target_st).validate()
        if t_st:
            raise gb.BotException(f"L'utente selezionato è già uno storyteller")
        
        self.bot.dbm.db.insert("Storyteller",  userid=target_st)
        await self.bot.atSend(ctx, f"{name} ora è Storyteller")
        
    @gmadm.command(name = 'unname', brief = gmadm_help['unname'][1], description = unname_description)
    @commands.before_invoke(gs.command_security(sec.IsAdmin))
    async def unname(self, ctx: commands.Context, storyteller: gc.StorytellerConverter):

        target_st = storyteller['userid'] 
        name = storyteller['name']
        
        n = self.bot.dbm.unnameStoryTeller(target_st)
        if n:
            await self.bot.atSend(ctx, f"{name} non è più Storyteller")
        else:
            await self.bot.atSend(ctx, f"Nessuna modifica fatta")
    
    @gmadm.command(name = 'traittypes', brief = gmadm_help['traittypes'][1], description = traittypes_description)
    @commands.before_invoke(gs.command_security(gs.basicStoryTeller))
    async def traittypes(self, ctx: commands.Context):
        
        ttypesl = self.bot.dbm.db.select('TraitType', what = "id, name").list()
        response = "Tipi di tratto: \n"
        response += "\n".join(list(map(lambda x : f"\t**{x['id']}**: {x['name']}", ttypesl)))
        
        await self.bot.atSend(ctx, response)
Пример #8
0
class GreedyGhostCog_Roller(gb.GreedyGhostCog):
    def rollAndFormatVTM(self,
                         ctx: commands.Context,
                         ndice: int,
                         nfaces: int,
                         diff: int,
                         statusFunc: RollStatusFormatter,
                         extra_succ: int = 0,
                         canceling: bool = True,
                         spec: bool = False,
                         statistics: bool = False,
                         minsucc: int = 1) -> str:
        if statistics:
            statistics_samples = int(
                self.bot.config['BotOptions']['stat_samples'])
            total_successes = 0  # total successes, even when the roll is a fail
            pass_successes = 0  # total of successes only when the roll succeeds
            passes = 0
            fails = 0
            critfails = 0
            for i in range(statistics_samples):
                successi, _, _ = vtm_res.roller(ndice, nfaces, diff, canceling,
                                                spec, extra_succ)
                if successi > 0:
                    if successi >= minsucc:
                        passes += 1
                        pass_successes += successi
                    else:
                        fails += 1
                    total_successes += successi
                elif successi == 0 or successi == -2:
                    fails += 1
                else:
                    critfails += 1
            response = self.bot.getStringForUser(
                ctx, 'roll_status_statistics_info', statistics_samples, ndice,
                nfaces, diff, extra_succ,
                self.bot.getStringForUser(ctx, 'roll_status_with') if canceling
                else self.bot.getStringForUser(ctx, 'roll_status_without'),
                self.bot.getStringForUser(ctx, 'roll_status_with') if spec else
                self.bot.getStringForUser(ctx, 'roll_status_without'),
                round(100 * passes / statistics_samples, 2),
                round(100 * (fails + critfails) / statistics_samples, 2),
                round(100 * fails / statistics_samples, 2),
                round(100 * critfails / statistics_samples, 2),
                round(total_successes / statistics_samples, 2),
                round(pass_successes / passes, 2))
            return response
        else:
            successi, tiro, cancels = vtm_res.roller(ndice, nfaces, diff,
                                                     canceling, spec,
                                                     extra_succ)
            pretty = prettyRoll(tiro, diff, cancels)
            status = statusFunc.format(successi)
            response = status + f' (diff {diff}, min. {minsucc}): {pretty}'
            if extra_succ > 0:
                response += f' **+{extra_succ}**'
            if extra_succ < 0:
                response += f' **{extra_succ}**'
            return response

    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)

    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)

    # TODO: redo these validators!
    def validateInteger(self,
                        ctx: commands.Context,
                        args: list,
                        i: int,
                        err_msg: str = None) -> utils.ValidatedIntSeq:
        try:
            return i, int(args[i])
        except ValueError:
            if err_msg == None:
                err_msg = self.bot.getStringForUser(
                    ctx, "string_errorpiece_integer")
            raise ValueError(
                self.bot.getStringForUser(ctx, "string_error_x_isnot_y",
                                          args[i], err_msg))

    def validateBoundedInteger(self,
                               ctx: commands.Context,
                               args: list,
                               i: int,
                               min_val: int,
                               max_val: int,
                               err_msg: str = None) -> utils.ValidatedIntSeq:
        j, val = self.validateInteger(ctx, args, i)
        if val < min_val or val > max_val:
            if err_msg == None:
                err_msg = self.bot.getStringForUser(
                    ctx, "string_errorpiece_number_in_range", min_val, max_val)
            raise ValueError(
                self.bot.getStringForUser(ctx, "string_error_x_isnot_y",
                                          args[i], err_msg))
        return j, val

    def validateNumber(self,
                       ctx: commands.Context,
                       args: list,
                       i: int,
                       err_msg: str = None) -> utils.ValidatedIntSeq:
        if not args[i].isdigit():
            if err_msg == None:
                err_msg = self.bot.getStringForUser(
                    ctx, "string_errorpiece_positive_integer")
            raise ValueError(
                self.bot.getStringForUser(ctx, "string_error_x_isnot_y",
                                          args[i], err_msg))
        return i, int(args[i])

    def validateBoundedNumber(self,
                              ctx: commands.Context,
                              args,
                              i,
                              min_bound,
                              max_bound,
                              err_msg=None) -> utils.ValidatedIntSeq:
        _, num = self.validateNumber(ctx, args, i)
        if num > max_bound or num < min_bound:
            if err_msg == None:
                err_msg = self.bot.getStringForUser(
                    ctx, "string_errorpiece_number_in_range", min_bound,
                    max_bound)
            raise ValueError(
                self.bot.getStringForUser(ctx, "string_error_x_isnot_y", num,
                                          err_msg))
        return i, num

    def validateIntegerGreatZero(self, ctx: commands.Context, args: list,
                                 i: int) -> utils.ValidatedIntSeq:
        return self.validateBoundedNumber(
            ctx, args, i, 1, utils.INFINITY,
            self.bot.getStringForUser(ctx, "string_errorpiece_integer_gt0"))

    def validateDifficulty(self, ctx: commands.Context, args: list,
                           i: int) -> utils.ValidatedIntSeq:
        return self.validateBoundedNumber(
            ctx, args, i, 2, 10,
            self.bot.getStringForUser(ctx, "string_errorpiece_valid_diff"))

    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

    # input: sequenza di argomenti per .roll
    # output: dizionario popolato con gli argomenti validati
    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

    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})'

    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

    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)

    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

    @commands.command(name='roll',
                      aliases=['r', 'tira', 'lancia', 'rolla'],
                      brief='Tira dadi',
                      description=roll_longdescription)
    @commands.before_invoke(gs.command_security(gs.basicRegisteredUser))
    async def roll(self, ctx: commands.Context, *args):
        if len(args) == 0:
            raise gb.BotException(
                self.bot.getStringForUser(ctx, "string_error_x_what", "roll") +
                " diomadonna")  # xd
        args_list = list(args)

        # capisco che tipo di tiro ho di fronte
        what = args_list[0].lower()

        action = None
        if what in INIZIATIVA_CMD:
            action = RollCat.INITIATIVE
        elif what in RIFLESSI_CMD:
            action = RollCat.REFLEXES
        elif what in SOAK_CMD:
            action = RollCat.SOAK
        else:
            action = RollCat.DICE

        # leggo e imposto le varie opzioni
        parsed = None
        start_arg = 0 if action == RollCat.DICE else 1
        try:
            parsed = self.parseRollArgs(ctx, args_list[start_arg:])
        except ValueError as e:
            await self.bot.atSend(ctx, str(e))
            return

        # gestisco i tiri specifici
        response = ''
        if action == RollCat.INITIATIVE:
            response = await self.roll_initiative(ctx, parsed)
        elif action == RollCat.REFLEXES:
            response = await self.roll_reflexes(ctx, parsed)
        elif action == RollCat.SOAK:
            response = await self.roll_soak(ctx, parsed)
        else:
            response = await self.roll_dice(ctx, parsed)
        await self.bot.atSend(ctx, response)

    @commands.command(
        name='search',
        brief="Cerca un tratto",
        description=
        "Cerca un tratto:\n\n .search <termine di ricerca> -> elenco dei risultati"
    )
    @commands.before_invoke(gs.command_security(gs.basicRegisteredUser))
    async def search_trait(self, ctx: gb.GreedyContext, *args):
        if len(args) == 0:
            await self.bot.atSendLang("string_error_no_searchterm")
            return

        searchstring = "%" + (" ".join(args)) + "%"
        lower_version = searchstring.lower()
        traits = self.bot.dbm.db.select(
            "LangTrait",
            where=
            "langId=$langid and (traitId like $search_lower or traitShort like $search_lower or traitName like $search_string)",
            vars=dict(search_lower=lower_version,
                      search_string=searchstring,
                      langid=ctx.getLID()))

        if not len(traits):
            await self.bot.atSendLang("string_msg_no_match")
            return

        response = self.bot.getStringForUser(ctx,
                                             "string_msg_found_traits") + ":\n"
        for trait in traits:
            response += f"\n {trait['traitShort']} ({trait['traitId']}): {trait['traitName']}"
        await self.bot.atSend(ctx, response)

    @commands.command(
        name='call',
        brief="Richiama l'attenzione dello storyteller",
        description=
        "Richiama l'attenzione dello storyteller della cronaca attiva nel canale in cui viene invocato"
    )
    @commands.before_invoke(gs.command_security(gs.basicRegisteredUser))
    async def call(self, ctx: commands.Context):
        character = self.bot.dbm.getActiveChar(ctx)
        sts = self.bot.dbm.getChannelStoryTellers(ctx.channel.id)
        response = f"{character['fullname']} ({ctx.message.author}) richiede la tua attenzione!"
        for st in sts:
            stuser = await self.bot.fetch_user(st['storyteller'])
            response += f' {stuser.mention}'
        await self.bot.atSend(ctx, response)

    @commands.command(name='start',
                      brief="Tira 1d100 per l'inizio giocata",
                      description="Tira 1d100 per l'inizio giocata")
    @commands.before_invoke(gs.command_security(gs.basicRegisteredUser))
    async def start(self, ctx: commands.Context):
        await self.bot.atSend(ctx, f'{random.randint(1, 100)}')

    @commands.command(
        name='strat',
        aliases=strat_list,
        brief="Tira 1d100 per l'inizio giocata",
        description=
        "Tira 1d100 per l'inizio giocata anche se l'invocatore è ubriaco")
    @commands.before_invoke(gs.command_security(gs.basicRegisteredUser))
    async def strat(self, ctx: commands.Context):
        await self.bot.atSend(
            ctx,
            f'{random.randint(1, 100)}, però la prossima volta scrivilo giusto <3'
        )
Пример #9
0
class GreedyGhostCog_Basic(gb.GreedyGhostCog):
    """ Does basic functionality for the bot:
        Error handling
        Member syncing
        Guild syncing
    """
    #executed once on bot boot
    #@bot.event
    @commands.Cog.listener()
    async def on_ready(self):
        print(f'{self.bot.user} is connected to the following guilds:\n')
        for guild in self.bot.guilds:
            print(f'{guild.name} (id: {guild.id})')
        #add self to user list
        iu, _ = self.bot.dbm.validators.getValidateBotUser(
            self.bot.user.id).validate()
        if not iu:
            self.bot.dbm.registerUser(
                self.bot.user.id, self.bot.user.name,
                self.bot.config['BotOptions']['default_language'])
        # notify debug user that bot is online
        await self.bot.logToDebugUser("Bot is Online!")

    @commands.Cog.listener()
    async def on_command_error(self, ctx: gb.GreedyContext, error: Exception):
        lid = ctx.getLID()
        error = getattr(error, 'original', error)
        #ignored = (commands.CommandNotFound, )
        #if isinstance(error, ignored):
        #    print(error)
        if isinstance(error, commands.CommandNotFound):
            try:
                msgsplit = ctx.message.content.split(" ")
                msgsplit[0] = msgsplit[0][1:]  # toglie prefisso
                charid = msgsplit[0]
                ic, _ = self.bot.dbm.validators.getValidateCharacter(
                    charid).validate()
                if ic:
                    pgmanage_cog: cogPCmgmt.GreedyGhostCog_PCmgmt = self.bot.get_cog(
                        cogPCmgmt.GreedyGhostCog_PCmgmt.__name__)
                    if pgmanage_cog is not None:
                        await ctx.invoke(
                            pgmanage_cog.bot.get_command('pgmanage'),
                            *msgsplit)
                    else:
                        await self.bot.atSendLang(
                            ctx,
                            "string_error_wat")  # TODO error cog not loaded
                else:
                    await self.bot.atSendLang(ctx, "string_error_wat")
                return
            except Exception as e:
                error = e
        if isinstance(error, gb.BotException):
            await self.bot.atSend(ctx, f'{error}')
        elif isinstance(error, lng.LangSupportErrorGroup):
            await self.bot.atSend(ctx, self.bot.formatException(ctx, error))
        elif isinstance(error, lng.LangSupportException):
            await self.bot.atSend(
                ctx, self.bot.languageProvider.formatException(lid, error))
        elif isinstance(error, gb.GreedyCommandError):
            await self.bot.atSend(
                ctx, self.bot.languageProvider.formatException(lid, error))
        elif isinstance(error, commands.MissingRequiredArgument):
            await ctx.send_help(ctx.command)
        elif isinstance(error, lng.LangException):
            await self.bot.atSend(ctx, f'{error}')
        else:
            if isinstance(error, MySQLdb.OperationalError):
                if error.args[0] == 2006:
                    await self.bot.atSendLang(
                        ctx, "string_error_database_noanswer")
                    self.bot.dbm.reconnect()
                else:
                    await self.bot.atSendLang(ctx,
                                              "string_error_database_generic")
            elif isinstance(error, MySQLdb.IntegrityError):
                await self.bot.atSendLang(
                    ctx, "string_error_database_dataviolation")
            else:
                await self.bot.atSendLang(ctx,
                                          "string_error_unhandled_exception")
            #print("debug user:"******"string_error_details", ctx.message.content, type(error),
                error)
            # figure out where to send the error
            if debug_userid and debug_userid != '':
                debug_user = await self.bot.fetch_user(int(debug_userid))
            if debug_user != '':
                await debug_user.send(error_details)
            else:
                print(error_details)  # TODO logs

    # member monitoring

    @commands.Cog.listener()
    async def on_member_join(self, member: discord.Member):
        iu, _ = self.bot.dbm.validators.getValidateBotUser(
            member.id).validate()
        ig, guild = self.bot.dbm.validators.getValidateGuild(
            member.guild.id).validate()
        if not iu and ig and guild["authorized"]:
            self.bot.dbm.registerUser(
                member.id, member.name,
                self.bot.config['BotOptions']['default_language'])

    @commands.Cog.listener()
    async def on_member_remove(self, member: discord.Member):
        self.bot.checkAndRemoveUser(member)

    @commands.Cog.listener()
    async def on_member_update(self, member_before: discord.Member,
                               member_after: discord.Member):
        iu, _ = self.bot.dbm.validators.getValidateBotUser(
            member_after.id).validate()
        if iu:
            self.bot.dbm.updateUser(member_after.id, member_after.name)

    # this command is not needed anymore and is here only in case the bot misses someone joining and we don't want to wait up to 1 day for the user maintenance task to catch up
    # remember: if we register a User that is not actually in a guild that the bot can see, the registration will be removed when the maintenance task runs
    @commands.command(name='register', brief='Registra un utente nel database')
    @commands.before_invoke(gs.command_security(gs.basicStoryTeller))
    async def register(self, ctx: commands.Context, user: UserConverter):

        userid = user.id

        iu, _ = self.bot.dbm.validators.getValidateBotUser(userid).validate()
        if not iu:
            self.bot.dbm.registerUser(
                userid, user.name,
                self.bot.config['BotOptions']['default_language'])
            await self.bot.atSendLang(ctx, "string_mgs_user_registered")
        else:
            await self.bot.atSendLang(ctx,
                                      "string_mgs_user_already_registered")

    # guild monitoring

    @commands.Cog.listener()
    async def on_guild_join(self, guild: discord.Guild):
        await self.bot.checkAndJoinGuild(guild)

    @commands.Cog.listener()
    async def on_guild_remove(self, guild: discord.Guild):
        guild_map = self.bot.getGuildActivationMap()
        for member in guild.members:
            self.bot.checkAndRemoveUser(member, guild_map)
        self.bot.dbm.removeGuild(guild.id)

    @commands.Cog.listener()
    async def on_guild_update(self, before: discord.Guild,
                              after: discord.Guild):
        ig, _ = self.bot.dbm.validators.getValidateGuild(after.id).validate()
        if ig:
            self.bot.dbm.updateGuildName(after.id, after.name)

    @commands.command(
        name='gauth',
        brief='Abilita il server ad utilizzare le funzionalità del bot')
    @commands.before_invoke(gs.command_security(sec.IsAdmin))
    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"))