Пример #1
0
 async def predicate(ctx: Context) -> bool:
     if not hasattr(ctx, "character_data"):
         ctx.character_data = await ctx.bot.cache.get_profile(ctx.author.id)
     classes = [
         c for i in ctx.character_data["class"]
         if (c := class_from_string(i))
     ]
Пример #2
0
    async def pet(self, ctx):
        _("""Interact with your pet. Be sure to see `{prefix}help pet`.

            Every two hours, your pet will lose 2 food points, 4 drink points, 1 joy point and 1 love point.

            If food or drink drop below zero, your pet dies and the ranger class is removed from you.
            If love sinks below 75, your pet has a chance to run away which increases the lower its love drops.

            Your pet's joy influences the items it hunts, acting as a multiplier for the item's stat.

            Only rangers can use this command.""")
        petlvl = 0
        for class_ in ctx.character_data["class"]:
            c = class_from_string(class_)
            if c and c.in_class_line(Ranger):
                petlvl = c.class_grade()
        em = discord.Embed(title=_("{user}'s pet").format(user=ctx.disp))
        em.add_field(name=_("Name"), value=ctx.pet_data["name"], inline=False)
        em.add_field(name=_("Level"), value=petlvl, inline=False)
        em.add_field(name=_("Food"),
                     value=f"{ctx.pet_data['food']}/100",
                     inline=False)
        em.add_field(name=_("Drinks"),
                     value=f"{ctx.pet_data['drink']}/100",
                     inline=False)
        em.add_field(name=_("Love"),
                     value=f"{ctx.pet_data['love']}/100",
                     inline=False)
        em.add_field(name=_("Joy"),
                     value=f"{ctx.pet_data['joy']}/100",
                     inline=False)
        em.set_thumbnail(url=ctx.author.display_avatar.url)
        em.set_image(url=ctx.pet_data["image"])
        await ctx.send(embed=em)
Пример #3
0
    async def get_damage_armor_for(self,
                                   user,
                                   items=None,
                                   classes=None,
                                   race=None,
                                   conn=None):
        user = user.id if isinstance(user,
                                     (discord.User, discord.Member)) else user
        if conn is None:
            conn = await self.pool.acquire()
            local = True
        else:
            local = False
        if items is None:
            items = await self.get_equipped_items_for(user, conn=conn)
        if not classes or not race:
            row = await conn.fetchrow('SELECT * FROM profile WHERE "user"=$1;',
                                      user)
            classes, race = row["class"], row["race"]

        if local:
            await self.pool.release(conn)

        damage = 0
        armor = 0

        classes = [i for c in classes if (i := class_from_string(c))]
Пример #4
0
 async def predicate(ctx: Context) -> bool:
     if not hasattr(ctx, "character_data"):
         ctx.character_data = await ctx.bot.pool.fetchrow(
             'SELECT * FROM profile WHERE "user"=$1;', ctx.author.id)
     classes = [
         c for i in ctx.character_data["class"]
         if (c := class_from_string(i))
     ]
Пример #5
0
    async def get_damage_armor_for(self,
                                   user,
                                   items=None,
                                   classes=None,
                                   race=None,
                                   conn=None):
        user = user.id if isinstance(user,
                                     (discord.User, discord.Member)) else user
        if items is None:
            items = await self.get_equipped_items_for(user, conn=conn)
        if not classes or not race:
            row = await self.cache.get_profile(user, conn=conn)
            classes, race = row["class"], row["race"]

        damage = 0
        armor = 0

        classes = [i for c in classes if (i := class_from_string(c))]
Пример #6
0
    async def evolve(self, ctx):
        _(
            # xgettext: no-python-format
            """Evolve your class, bringing it to the next level and giving better class bonuses.

            You can evolve every 5 levels, i.e. at level 5, level 10, level 15, level 20, level 25 and finally level 30.

            - Warriors gain +1 defense per evolution
            - Thieves gain +8% for their success chance per evolution
            - Mages gain +1 damage per evolution
            - Rangers' pets' hunted item get +3 minimum stat and +6 maximum stat per evolution
              - This means level 1 pets can hunt items from stat 3 to stat 6; level 2 pets from stat 6 to stat 12
            - Raiders gain +0.1 defense and damage raidstats
            - Ritualists gain +5% extra favor when sacrificing per evolution
            (- Paragons gain +1 damage *and* +1 defense per evolution)"""
        )
        level = rpgtools.xptolevel(ctx.character_data["xp"])
        if level < 5:
            return await ctx.send(_("Your level isn't high enough to evolve."))
        newindex = int(level / 5)
        updated = 0
        new_classes = []
        for class_ in ctx.character_data["class"]:
            c = class_from_string(class_)
            if c:
                evolves = get_class_evolves(c.get_class_line())
                new_classes.append(evolves[newindex].class_name())
                updated += 1
            else:
                new_classes.append("No Class")
        if updated == 0:
            return await ctx.send(_("You haven't got a class yet."))
        if ctx.character_data["class"] == new_classes:
            return await ctx.send(_("Nothing to evolve."))
        await self.bot.pool.execute(
            'UPDATE profile SET "class"=$1 WHERE "user"=$2;', new_classes, ctx.author.id
        )
        await self.bot.cache.update_profile_cols_abs(ctx.author.id, class_=new_classes)
        await ctx.send(
            _("You are now a `{class1}` and a `{class2}`.").format(
                class1=new_classes[0], class2=new_classes[1]
            )
        )
Пример #7
0
                embed=discord.Embed(
                    title=_("Adventure Failed"),
                    description=_("You died on your mission. Try again!"),
                    colour=0xff0000,
                )
            )

        gold = round(random.randint(20 * num, 60 * num) * luck_multiply)

        if await self.bot.get_booster(ctx.author, "money"):
            gold = int(gold * 1.25)

        xp = random.randint(250 * num, 500 * num)
        chance_of_loot = 5 if num == 1 else 5 + 1.5 * num

        classes = [class_from_string(c) for c in ctx.character_data["class"]]
        if any(c.in_class_line(Ritualist) for c in classes if c):
            chance_of_loot *= 2  # can be 100 in a 30

        async with self.bot.pool.acquire() as conn:
            if (random.randint(1, 1000)) > chance_of_loot * 10:
                minstat = round(num * luck_multiply)
                maxstat = round(5 + int(num * 1.5) * luck_multiply)

                item = await self.bot.create_random_item(
                    minstat=(minstat if minstat > 0 else 1) if minstat < 35 else 35,
                    maxstat=(maxstat if maxstat > 0 else 1) if maxstat < 35 else 35,
                    minvalue=round(num * luck_multiply),
                    maxvalue=round(num * 50 * luck_multiply),
                    owner=ctx.author,
                    conn=conn,
Пример #8
0
    async def hunt(self, ctx):
        _(
            # xgettext: no-python-format
            """Make your pet hunt an item for you.

            The items stat depends on your pet's level (determined by class evolution) as well as its joy score.
            The lowest base stat your pet can find is three times its level, the highest is 6 times its level.
            Your pet's joy score in percent is multiplied with these base stats.

            For example:
              - Your pet is on level 2, its  joy score is 50.
              - The item's base stats are (3x2) to (6x2), so 6 to 12.
              - Its joy score in percent is multiplied: 50% x 6 to 50% x 12, so 3 to 6

            In this example, your pet can hunt an item with stats 3 to 6. It has a hard cap at 30.
            The item's value will be between 0 and 250.

            Only rangers can use this command.
            (This command has a cooldown until 12am UTC.)""")
        petlvl = 0
        for class_ in ctx.character_data["class"]:
            c = class_from_string(class_)
            if c and c.in_class_line(Ranger):
                petlvl = c.class_grade()
        joy_multiply = Decimal(ctx.pet_data["joy"] / 100)
        luck_multiply = ctx.character_data["luck"]
        minstat = round(petlvl * 3 * luck_multiply * joy_multiply)
        maxstat = round(petlvl * 6 * luck_multiply * joy_multiply)
        if minstat < 1 or maxstat < 1:
            return await ctx.send(
                _("Your pet is not happy enough to hunt an item. Try making it joyful!"
                  ))
        item = await self.bot.create_random_item(
            minstat=minstat if minstat < 30 else 30,
            maxstat=maxstat if maxstat < 30 else 30,
            minvalue=1,
            maxvalue=250,
            owner=ctx.author,
        )
        embed = discord.Embed(
            title=_("You gained an item!"),
            description=_("Your pet found an item!"),
            color=0xFF0000,
        )
        embed.set_thumbnail(url=ctx.author.display_avatar.url)
        embed.add_field(name=_("ID"), value=item["id"], inline=False)
        embed.add_field(name=_("Name"), value=item["name"], inline=False)
        embed.add_field(name=_("Type"), value=item["type"], inline=False)
        if item["type"] == "Shield":
            embed.add_field(name=_("Armor"), value=item["armor"], inline=True)
        else:
            embed.add_field(name=_("Damage"),
                            value=item["damage"],
                            inline=True)
        embed.add_field(name=_("Value"),
                        value=f"${item['value']}",
                        inline=False)
        embed.set_footer(
            text=_("Your pet needs to recover, wait a day to retry"))
        await ctx.send(embed=embed)
        await self.bot.log_transaction(
            ctx,
            from_=1,
            to=ctx.author.id,
            subject="item",
            data={
                "Name": item["name"],
                "Value": item["value"]
            },
        )
Пример #9
0
    async def _class(self, ctx):
        _("""Change or select your primary or secondary class.

            - Warriors gain added defense
            - Thieves gain access to `{prefix}steal`
            - Mages gain added damage
            - Rangers gain access to a pet which can hunt for gear items
            - Raiders gain additional raidstats, used in raidbattles and raids
            - Ritualists gain additional favor from sacrificing items and are twice as likely to receive loot from adventures
            (- Paragons gain added damage *and* defense; the class is only available to donators)

            The second class unlocks at level 12. Selecting a class the first time is free (No Class -> Class), but changing it later will cost $5,000 (Class -> another Class)

            (This command has a cooldown of 24 hours)""")
        if rpgtools.xptolevel(ctx.character_data["xp"]) >= 12:
            val = await self.bot.paginator.Choose(
                title=_("Select class to change"),
                entries=[_("Primary Class"),
                         _("Secondary Class")],
                return_index=True,
            ).paginate(ctx)
        else:
            val = 0
        embeds = [
            discord.Embed(
                title=_("Warrior"),
                description=_(
                    "The tank class. Charge into battle with additional defense!\n+1"
                    " defense per evolution."),
                color=self.bot.config.game.primary_colour,
            ),
            discord.Embed(
                title=_("Thief"),
                description=_(
                    # xgettext: no-python-format
                    "The sneaky money stealer...\nGet access to `{prefix}steal` to"
                    " steal 10% of a random player's money, if successful.\n+8% success"
                    " chance per evolution.").format(prefix=ctx.prefix),
                color=self.bot.config.game.primary_colour,
            ),
            discord.Embed(
                title=_("Mage"),
                description=_(
                    "Utilise powerful magic for stronger attacks.\n+1 damage per"
                    " evolution."),
                color=self.bot.config.game.primary_colour,
            ),
            discord.Embed(
                title=_("Ranger"),
                description=
                _("Item hunter and trainer of their very own pet.\nGet access to"
                  " `{prefix}pet` to interact with your pet and let it get items for"
                  " you.\n+3 minimum stat and +6 maximum stat per evolution."
                  ).format(prefix=ctx.prefix),
                colour=self.bot.config.game.primary_colour,
            ),
            discord.Embed(
                title=_("Raider"),
                description=
                _("A strong warrior who gives their life for the fight against"
                  " Zerekiel.\nEvery evolution boosts your raidstats by an additional"
                  " 10%."),
                colour=self.bot.config.game.primary_colour,
            ),
            discord.Embed(
                title=_("Ritualist"),
                description=
                _("A seer, a sacrificer and a follower.\nThe Ritualist devotes their"
                  " life to the god they follow. For every evolution, their"
                  " sacrifices are 5% more effective. They have twice the chance to"
                  " get loot from adventures."),
                colour=self.bot.config.game.primary_colour,
            ),
        ]
        choices = [Warrior, Thief, Mage, Ranger, Raider, Ritualist]
        if await user_is_patron(self.bot, ctx.author):
            embeds.append(
                discord.Embed(
                    title=_("Paragon"),
                    description=_(
                        "Absorb the appreciation of the devs into your soul to power"
                        " up.\n+1 damage and defense per evolution."),
                    color=self.bot.config.game.primary_colour,
                ))
            choices.append(Paragon)
        classes = [class_from_string(c) for c in ctx.character_data["class"]]
        lines = [c.get_class_line() for c in classes if c]
        for line in lines:
            for e in embeds:
                if _(get_name(line)) == e.title:
                    embeds.remove(e)
            try:
                choices.remove(line)
            except ValueError:
                pass
        idx = await self.bot.paginator.ChoosePaginator(
            extras=embeds,
            placeholder=_("Choose a class"),
            choices=[line.__name__ for line in choices],
            return_index=True,
        ).paginate(ctx)
        profession = choices[idx]
        profession_ = get_first_evolution(profession).class_name()
        new_classes = copy(ctx.character_data["class"])
        new_classes[val] = profession_
        if not await ctx.confirm(
                _("You are about to select the `{profession}` class for yourself."
                  " {textaddon} Proceed?").
                format(
                    textaddon=
                    _("This **costs nothing**, but changing it later will cost **$5000**."
                      ) if ctx.character_data["class"][val] == "No Class" else
                    _("This will cost **$5000**."),
                    profession=get_name(profession),
                )):
            return await ctx.send(_("Class selection cancelled."))
        if ctx.character_data["class"][val] == "No Class":
            async with self.bot.pool.acquire() as conn:
                await conn.execute(
                    'UPDATE profile SET "class"=$1 WHERE "user"=$2;',
                    new_classes,
                    ctx.author.id,
                )
                if profession == Ranger:
                    await conn.execute(
                        'INSERT INTO pets ("user") VALUES ($1);',
                        ctx.author.id)
            await ctx.send(
                _("Your new class is now `{profession}`.").format(
                    profession=_(get_name(profession))))
        else:
            if not await self.bot.has_money(ctx.author.id, 5000):
                await self.bot.reset_cooldown(ctx)
                return await ctx.send(
                    _("You're too poor for a class change, it costs **$5000**."
                      ))

            async with self.bot.pool.acquire() as conn:
                await conn.execute(
                    'UPDATE profile SET "class"=$1, "money"="money"-$2 WHERE'
                    ' "user"=$3;',
                    new_classes,
                    5000,
                    ctx.author.id,
                )
                await conn.execute('DELETE FROM pets WHERE "user"=$1;',
                                   ctx.author.id)
                if profession == Ranger:
                    await conn.execute(
                        'INSERT INTO pets ("user") VALUES ($1);',
                        ctx.author.id)
                await self.bot.log_transaction(
                    ctx,
                    from_=ctx.author.id,
                    to=2,
                    subject="money",
                    data={"Amount": 5000},
                    conn=conn,
                )
            await ctx.send(
                _("You selected the class `{profession}`. **$5000** was taken off"
                  " your balance.").format(profession=_(get_name(profession))))
Пример #10
0
            Your steal chance is increased by evolving your class and your alliance's thief buildings, if you have an alliance that owns a city.
            If you succeed in stealing, you will steal 10% of a random player's money.

            You *cannot* choose your target, it is always a random player. If the bot can't find the player's name, it will be replaced with "a traveller passing by".
            The random player cannot be anyone with money less than $10, yourself, or one of the bot owners.

            Only thieves can use this command.
            (This command has a cooldown of 1 hour.)""")
        if buildings := await self.bot.get_city_buildings(
                ctx.character_data["guild"]):
            bonus = buildings["thief_building"] * 5
        else:
            bonus = 0
        grade = 0
        for class_ in ctx.character_data["class"]:
            c = class_from_string(class_)
            if c and c.in_class_line(Thief):
                grade = c.class_grade()
        if random.randint(0, 99) in range(
                1,
                grade * 8 + 1 + bonus,
        ):
            async with self.bot.pool.acquire() as conn:
                usr = await conn.fetchrow(
                    'SELECT "user", "money" FROM profile WHERE "money">=10 AND'
                    ' "user"!=$1 ORDER BY RANDOM() LIMIT 1;',
                    ctx.author.id,
                )

                if usr["user"] in self.bot.owner_ids:
                    return await ctx.send(
Пример #11
0
    async def activebattle(self,
                           ctx,
                           money: IntGreaterThan(-1) = 0,
                           enemy: discord.Member = None):
        _("""`[money]` - A whole number that can be 0 or greater; defaults to 0
            `[enemy]` - A user who has a profile; defaults to anyone

            Fight against another player while betting money.
            To decide players' stats, their items, race and class bonuses are evaluated.

            The money is removed from both players at the start of the battle. Once a winner has been decided, they will receive their money, plus the enemy's money.
            The battle takes place in rounds. Each round, both players have to choose their move using the reactions.
            Players can attack (тЪФя╕П), defend (ЁЯЫбя╕П) or recover HP (тЭдя╕П).

            The battle ends if one player's HP drops to 0 (winner decided), or a player does not move (forfeit).
            In case of a forfeit, neither of the players will get their money back.

            The battle's winner will receive a PvP win, which shows on their profile.
            (This command has a cooldown of 10 minutes.)""")
        if enemy == ctx.author:
            return await ctx.send(_("You can't battle yourself."))
        if ctx.character_data["money"] < money:
            return await ctx.send(_("You are too poor."))

        await self.bot.pool.execute(
            'UPDATE profile SET "money"="money"-$1 WHERE "user"=$2;',
            money,
            ctx.author.id,
        )

        if not enemy:
            text = _(
                "{author} seeks an active battle! The price is **${money}**."
            ).format(author=ctx.author.mention, money=money)
        else:
            text = _(
                "{author} seeks an active battle with {enemy}! The price is **${money}**."
            ).format(author=ctx.author.mention,
                     enemy=enemy.mention,
                     money=money)

        async def check(user: discord.User) -> bool:
            return await has_money(self.bot, user.id, money)

        future = asyncio.Future()
        view = SingleJoinView(
            future,
            Button(
                style=ButtonStyle.primary,
                label=_("Join the activebattle!"),
                emoji="\U00002694",
            ),
            allowed=enemy,
            prohibited=ctx.author,
            timeout=60,
            check=check,
            check_fail_message=_(
                "You don't have enough money to join the activebattle."),
        )

        await ctx.send(text, view=view)

        try:
            enemy_ = await future
        except asyncio.TimeoutError:
            await self.bot.reset_cooldown(ctx)
            await self.bot.pool.execute(
                'UPDATE profile SET "money"="money"+$1 WHERE "user"=$2;',
                money,
                ctx.author.id,
            )
            return await ctx.send(
                _("Noone wanted to join your activebattle, {author}!").format(
                    author=ctx.author.mention))

        players = {
            ctx.author: {
                "hp": 0,
                "damage": 0,
                "defense": 0,
                "lastmove": "",
                "action": None,
            },
            enemy_: {
                "hp": 0,
                "damage": 0,
                "defense": 0,
                "lastmove": "",
                "action": None,
            },
        }

        async with self.bot.pool.acquire() as conn:
            await conn.execute(
                'UPDATE profile SET "money"="money"-$1 WHERE "user"=$2;',
                money,
                enemy_.id,
            )

            for p in players:
                classes = [
                    class_from_string(i) for i in await conn.fetchval(
                        'SELECT class FROM profile WHERE "user"=$1;', p.id)
                ]
                if any(c.in_class_line(Ranger) for c in classes if c):
                    players[p]["hp"] = 120
                else:
                    players[p]["hp"] = 100

                attack, defense = await self.bot.get_damage_armor_for(
                    p, conn=conn)
                players[p]["damage"] = int(attack)
                players[p]["defense"] = int(defense)

        moves = {
            "\U00002694": "attack",
            "\U0001f6e1": "defend",
            "\U00002764": "recover",
        }

        msg = await ctx.send(
            _("Battle {p1} vs {p2}").format(p1=ctx.author.mention,
                                            p2=enemy_.mention),
            embed=discord.Embed(
                title=_("Let the battle begin!"),
                color=self.bot.config.game.primary_colour,
            ),
        )

        def is_valid_move(r, u):
            return str(
                r.emoji) in moves and u in players and r.message.id == msg.id

        for emoji in moves:
            await msg.add_reaction(emoji)

        while players[ctx.author]["hp"] > 0 and players[enemy_]["hp"] > 0:
            await msg.edit(embed=discord.Embed(description=_(
                "{prevaction}\n{player1}: **{hp1}** HP\n{player2}: **{hp2}**"
                " HP\nReact to play.").format(
                    prevaction="\n".join(
                        [i["lastmove"] for i in players.values()]),
                    player1=ctx.author.mention,
                    player2=enemy_.mention,
                    hp1=players[ctx.author]["hp"],
                    hp2=players[enemy_]["hp"],
                )))
            players[
                ctx.author]["action"], players[enemy_]["action"] = None, None
            players[ctx.author]["lastmove"], players[enemy_]["lastmove"] = (
                _("{user} does nothing...").format(user=ctx.author.mention),
                _("{user} does nothing...").format(user=enemy_.mention),
            )

            while (not players[ctx.author]["action"]) or (
                    not players[enemy_]["action"]):
                try:
                    r, u = await self.bot.wait_for("reaction_add",
                                                   timeout=30,
                                                   check=is_valid_move)
                    try:
                        await msg.remove_reaction(r.emoji, u)
                    except discord.Forbidden:
                        pass
                except asyncio.TimeoutError:
                    await self.bot.reset_cooldown(ctx)
                    await self.bot.pool.execute(
                        'UPDATE profile SET "money"="money"+$1 WHERE "user"=$2 or "user"=$3;',
                        money,
                        ctx.author.id,
                        enemy_.id,
                    )
                    return await ctx.send(
                        _("Someone refused to move. Activebattle stopped."))
                if not players[u]["action"]:
                    players[u]["action"] = moves[str(r.emoji)]
                else:
                    playerlist = list(players.keys())
                    await ctx.send(
                        _("{user}, you already moved! Waiting for {other}'s move..."
                          ).format(
                              user=u.mention,
                              other=playerlist[1 -
                                               playerlist.index(u)].mention,
                          ))
            plz = list(players.keys())
            for idx, user in enumerate(plz):
                other = plz[1 - idx]
                if players[user]["action"] == "recover":
                    heal_hp = round(players[user]["damage"] * 0.25) or 1
                    players[user]["hp"] += heal_hp
                    players[user]["lastmove"] = _(
                        "{user} healed themselves for **{hp} HP**.").format(
                            user=user.mention, hp=heal_hp)
                elif (players[user]["action"] == "attack"
                      and players[other]["action"] != "defend"):
                    eff = random.choice([
                        players[user]["damage"],
                        int(players[user]["damage"] * 0.5),
                        int(players[user]["damage"] * 0.2),
                        int(players[user]["damage"] * 0.8),
                    ])
                    players[other]["hp"] -= eff
                    players[user]["lastmove"] = _(
                        "{user} hit {enemy} for **{eff}** damage.").format(
                            user=user.mention, enemy=other.mention, eff=eff)
                elif (players[user]["action"] == "attack"
                      and players[other]["action"] == "defend"):
                    eff = random.choice([
                        int(players[user]["damage"]),
                        int(players[user]["damage"] * 0.5),
                        int(players[user]["damage"] * 0.2),
                        int(players[user]["damage"] * 0.8),
                    ])
                    eff2 = random.choice([
                        int(players[other]["defense"]),
                        int(players[other]["defense"] * 0.5),
                        int(players[other]["defense"] * 0.2),
                        int(players[other]["defense"] * 0.8),
                    ])
                    if eff - eff2 > 0:
                        players[other]["hp"] -= eff - eff2
                        players[user]["lastmove"] = _(
                            "{user} hit {enemy} for **{eff}** damage.").format(
                                user=user.mention,
                                enemy=other.mention,
                                eff=eff - eff2)
                        players[other]["lastmove"] = _(
                            "{enemy} tried to defend, but failed.".format(
                                enemy=other.mention))

                    else:
                        players[user]["lastmove"] = _(
                            "{user}'s attack on {enemy} failed!").format(
                                user=user.mention, enemy=other.mention)
                        players[other]["lastmove"] = _(
                            "{enemy} blocked {user}'s attack.".format(
                                enemy=other.mention, user=user.mention))
                elif players[user]["action"] == players[other][
                        "action"] == "defend":
                    players[ctx.author]["lastmove"] = _(
                        "You both tried to defend.")
                    players[enemy_]["lastmove"] = _(
                        "It was not very effective...")

        if players[ctx.author]["hp"] <= 0 and players[enemy_]["hp"] <= 0:
            await self.bot.pool.execute(
                'UPDATE profile SET "money"="money"+$1 WHERE "user"=$2 or "user"=$3;',
                money,
                ctx.author.id,
                enemy_.id,
            )
            return await ctx.send(_("You both died!"))
        if players[ctx.author]["hp"] > players[enemy_]["hp"]:
            winner, looser = ctx.author, enemy_
        else:
            looser, winner = ctx.author, enemy_
        async with self.bot.pool.acquire() as conn:
            await conn.execute(
                'UPDATE profile SET "pvpwins"="pvpwins"+1, "money"="money"+$1 WHERE'
                ' "user"=$2;',
                money * 2,
                winner.id,
            )
            await self.bot.log_transaction(
                ctx,
                from_=looser.id,
                to=winner.id,
                subject="money",
                data={"Amount": money},
                conn=conn,
            )
        await msg.edit(embed=discord.Embed(description=_(
            "{prevaction}\n{player1}: **{hp1}** HP\n{player2}: **{hp2}**"
            " HP\nReact to play.").format(
                prevaction="\n".join([i["lastmove"]
                                      for i in players.values()]),
                player1=ctx.author.mention,
                player2=enemy_.mention,
                hp1=players[ctx.author]["hp"],
                hp2=players[enemy_]["hp"],
            )))
        await ctx.send(
            _("{winner} won the active battle vs {looser}! Congratulations!").
            format(
                winner=winner.mention,
                looser=looser.mention,
            ))
Пример #12
0
class Bot(commands.AutoShardedBot):
    def __init__(self, **kwargs):
        self.cluster_name = kwargs.pop("cluster_name")
        self.cluster_id = kwargs.pop("cluster_id")
        self.config = ConfigLoader("config.toml")
        mentions = AllowedMentions.none()
        mentions.users = True
        super().__init__(
            allowed_mentions=mentions,
            command_prefix=self.config.bot.global_prefix,
            **kwargs,
        )  # we overwrite the prefix when it is connected
        # setup stuff
        self.queue = asyncio.Queue()  # global queue for ordered tasks
        self.schedule_manager = TimedScheduler()
        self.version = self.config.bot.version
        self.paginator = paginator
        self.BASE_URL = self.config.external.base_url
        self.bans = set(self.config.game.bans)
        self.support_server_id = self.config.game.support_server_id
        self.linecount = 0
        self.make_linecount()
        self.all_prefixes = {}
        self.activity = discord.Game(name=f"IdleRPG v{self.version}" if self.
                                     config.bot.is_beta else self.BASE_URL)
        self.logger = logging.getLogger()

        # global cooldown
        self.add_check(self.global_cooldown, call_once=True)

        # we assume the bot is created for use right now
        self.launch_time = datetime.datetime.now()
        self.eligible_for_cooldown_reduce = set()  # caching
        self.not_eligible_for_cooldown_reduce = set()  # caching

        self.normal_cooldown = commands.CooldownMapping.from_cooldown(
            1,
            self.config.bot.global_cooldown,
            commands.BucketType.user,
        )
        self.donator_cooldown = commands.CooldownMapping.from_cooldown(
            1,
            self.config.bot.donator_cooldown,
            commands.BucketType.user,
        )

    def __repr__(self):
        return "<Bot>"

    async def global_cooldown(self, ctx: commands.Context):
        """
        A function that enables a global per-user cooldown
        and raises a special exception based on CommandOnCooldown
        """
        if ctx.author.id in self.not_eligible_for_cooldown_reduce:
            bucket = self.normal_cooldown.get_bucket(ctx.message)
        elif ctx.author.id in self.eligible_for_cooldown_reduce:
            bucket = self.donator_cooldown.get_bucket(ctx.message)
        else:
            if await user_is_patron(self, ctx.author, "bronze"):
                self.eligible_for_cooldown_reduce.add(ctx.author.id)
                bucket = self.donator_cooldown.get_bucket(ctx.message)
            else:
                self.not_eligible_for_cooldown_reduce.add(ctx.author.id)
                bucket = self.normal_cooldown.get_bucket(ctx.message)
        retry_after = bucket.update_rate_limit()

        if retry_after:
            raise GlobalCooldown(bucket, retry_after)
        else:
            return True

    def make_linecount(self):
        """Generates a total linecount of all python files"""
        for root, _dirs, files in os.walk(os.getcwd()):
            root_parts = root.split(os.sep)
            if len(root_parts) > 2 and root_parts[2].startswith("."):
                continue
            for file_ in files:
                if file_.endswith(".py"):
                    with open(os.sep.join([root, file_]),
                              "r",
                              encoding="utf-8") as f:
                        self.linecount += len(f.readlines())

    async def connect_all(self):
        """Connects all databases and initializes sessions"""
        proxy_auth = self.config.external.proxy_auth
        proxy_url = self.config.external.proxy_url
        if proxy_auth is None or proxy_url is None:
            self.session = aiohttp.ClientSession()
        else:
            self.session = ProxiedClientSession(authorization=proxy_auth,
                                                proxy_url=proxy_url)
        self.trusted_session = aiohttp.ClientSession()
        self.redis = await aioredis.create_pool("redis://localhost",
                                                minsize=10,
                                                maxsize=20)
        database_creds = {
            "database": self.config.database.postgres_name,
            "user": self.config.database.postgres_user,
            "password": self.config.database.postgres_password,
            "host": self.config.database.postgres_host,
            "port": self.config.database.postgres_port,
        }
        self.pool = await asyncpg.create_pool(**database_creds,
                                              min_size=10,
                                              max_size=20,
                                              command_timeout=60.0)
        self.cache = RedisCache(self)

        for extension in self.config.bot.initial_extensions:
            try:
                self.load_extension(extension)
            except Exception:
                print(f"Failed to load extension {extension}.",
                      file=sys.stderr)
                traceback.print_exc()
        self.redis_version = await self.get_redis_version()
        await self.start(self.config.bot.token)

    async def get_redis_version(self):
        """Parses the Redis version out of the INFO command"""
        info = (await self.redis.execute("INFO")).decode()
        for line in info.split("\n"):
            if line.startswith("redis_version"):
                return line.split(":")[1]
        return None

    # https://github.com/Rapptz/discord.py/blob/master/discord/ext/commands/bot.py#L131
    def dispatch(self, event_name, *args, **kwargs):
        """Overriden version of Bot.dispatch to ignore reactions by banned users"""
        if (event_name == "raw_reaction_add" and args[0].user_id
                in self.bans) or (event_name == "message" and
                                  (args[0].author.id in self.bans
                                   or args[0].author.bot)):  # args[1] is user
            return
        super().dispatch(event_name, *args, **kwargs)

    async def on_message_edit(self, before, after):
        """Handler for edited messages, re-executes commands"""
        if before.content != after.content and after.author.id not in self.bans:
            await self.on_message(after)

    async def invoke(self, ctx):
        """Handler for i18n, executes before any other commands or checks run"""
        locale = await self.get_cog("Locale").locale(ctx.message.author.id)
        i18n.current_locale.set(locale)
        await super().invoke(ctx)

    @property
    def uptime(self):
        """Returns the current uptime of the bot"""
        return datetime.datetime.now() - self.launch_time

    async def get_ranks_for(self, thing, conn=None):
        """Returns the rank in money and xp for a user"""
        v = thing.id if isinstance(thing,
                                   (discord.Member, discord.User)) else thing
        if conn is None:
            conn = await self.pool.acquire()
            local = True
        else:
            local = False

        xp = await conn.fetchval(
            'SELECT COUNT(*) FROM profile WHERE "xp">=(SELECT "xp" FROM profile WHERE "user"=$1);',
            v,
        )
        money = await conn.fetchval(
            'SELECT COUNT(*) FROM profile WHERE "money">=(SELECT "money" FROM profile WHERE "user"=$1);',
            v,
        )
        if local:
            await self.pool.release(conn)
        return money, xp

    async def get_raidstats(
        self,
        thing,
        atkmultiply=None,
        defmultiply=None,
        classes=None,
        race=None,
        guild=None,
        god=None,
        conn=None,
    ):
        """Generates the raidstats for a user"""
        v = thing.id if isinstance(thing,
                                   (discord.Member, discord.User)) else thing
        local = False
        if conn is None:
            conn = await self.pool.acquire()
            local = True
        if (atkmultiply is None or defmultiply is None or classes is None
                or guild is None):
            row = await self.cache.get_profile(v, conn=conn)
            atkmultiply, defmultiply, classes, race, guild, user_god = (
                row["atkmultiply"],
                row["defmultiply"],
                row["class"],
                row["race"],
                row["guild"],
                row["god"],
            )
            if god is not None and god != user_god:
                raise ValueError()
        damage, armor = await self.get_damage_armor_for(v,
                                                        classes=classes,
                                                        race=race,
                                                        conn=conn)
        if (buildings := await self.get_city_buildings(guild, conn=conn)):
            atkmultiply += buildings["raid_building"] * Decimal("0.1")
            defmultiply += buildings["raid_building"] * Decimal("0.1")
        classes = [class_from_string(c) for c in classes]
        for c in classes:
            if c and c.in_class_line(Raider):
                grade = c.class_grade()
                atkmultiply = atkmultiply + Decimal("0.1") * grade
                defmultiply = defmultiply + Decimal("0.1") * grade
        dmg = damage * atkmultiply
        deff = armor * defmultiply
        if local:
            await self.pool.release(conn)
        return dmg, deff
Пример #13
0
    async def profile(self, ctx, *, person: discord.User = Author):
        _(
            """`[person]` - The person whose profile to view; defaults to oneself

            View someone's profile. This will send an image.
            For an explanation what all the fields mean, see [this picture](https://wiki.idlerpg.xyz/images/3/35/Profile_explained.png)"""
        )
        targetid = person.id

        async with self.bot.pool.acquire() as conn:
            profile = await conn.fetchrow(
                'SELECT p.*, g.name AS guild_name FROM profile p LEFT JOIN guild g ON (g."id"=p."guild") WHERE "user"=$1;',
                targetid,
            )

            if not profile:
                return await ctx.send(
                    _("**{person}** does not have a character.").format(person=person)
                )

            items = await self.bot.get_equipped_items_for(targetid, conn=conn)
            mission = await self.bot.get_adventure(targetid)

        right_hand = None
        left_hand = None

        any_count = sum(1 for i in items if i["hand"] == "any")
        if len(items) == 2 and any_count == 1 and items[0]["hand"] == "any":
            items = [items[1], items[0]]

        for i in items:
            stat = f"{int(i['damage'] + i['armor'])}"
            if i["hand"] == "both":
                right_hand = (i["type"], i["name"], stat)
            elif i["hand"] == "left":
                left_hand = (i["type"], i["name"], stat)
            elif i["hand"] == "right":
                right_hand = (i["type"], i["name"], stat)
            elif i["hand"] == "any":
                if right_hand is None:
                    right_hand = (i["type"], i["name"], stat)
                else:
                    left_hand = (i["type"], i["name"], stat)

        color = profile["colour"]
        color = [color["red"], color["green"], color["blue"], color["alpha"]]
        embed_color = discord.Colour.from_rgb(color[0], color[1], color[2])
        classes = [class_from_string(c) for c in profile["class"]]
        icons = [c.get_class_line_name().lower() if c else "none" for c in classes]

        guild_rank = None if not profile["guild"] else profile["guildrank"]

        marriage = (
            await rpgtools.lookup(self.bot, profile["marriage"], return_none=True)
            if profile["marriage"]
            else None
        )

        if mission:
            adventure_name = ADVENTURE_NAMES[mission[0]]
            adventure_time = f"{mission[1]}" if not mission[2] else _("Finished")
        else:
            adventure_name = None
            adventure_time = None

        badge_val = Badge.from_db(profile["badges"])
        if badge_val:
            badges = badge_val.to_items_lowercase()
        else:
            badges = []

        async with self.bot.trusted_session.post(
            f"{self.bot.config.external.okapi_url}/api/genprofile",
            json={
                "name": profile["name"],
                "color": color,
                "image": profile["background"],
                "race": profile["race"],
                "classes": profile["class"],
                "class_icons": icons,
                "left_hand_item": left_hand,
                "right_hand_item": right_hand,
                "level": f"{rpgtools.xptolevel(profile['xp'])}",
                "guild_rank": guild_rank,
                "guild_name": profile["guild_name"],
                "money": f"{profile['money']}",
                "pvp_wins": f"{profile['pvpwins']}",
                "marriage": marriage,
                "god": profile["god"] or _("No God"),
                "adventure_name": adventure_name,
                "adventure_time": adventure_time,
                "badges": badges,
            },
            headers={"Authorization": self.bot.config.external.okapi_token},
        ) as req:
            if req.status == 200:
                img = await req.text()
            else:
                # Error, means try reading the response JSON error
                try:
                    error_json = await req.json()
                    return await ctx.send(
                        _(
                            "There was an error processing your image. Reason: {reason} ({detail})"
                        ).format(
                            reason=error_json["reason"], detail=error_json["detail"]
                        )
                    )
                except ContentTypeError:
                    return await ctx.send(
                        _("Unexpected internal error when generating image.")
                    )
                except Exception:
                    return await ctx.send(_("Unexpected error when generating image."))

        await ctx.send(embed=discord.Embed(colour=embed_color).set_image(url=img))
Пример #14
0
    async def profile(self, ctx, *, person: User = Author):
        _("""`[person]` - The person whose profile to view; defaults to oneself

            View someone's profile. This will send an image.
            For an explanation what all the fields mean, see [this picture](https://wiki.idlerpg.xyz/images/3/35/Profile_explained.png)"""
          )
        await ctx.trigger_typing()
        targetid = person.id
        async with self.bot.pool.acquire() as conn:
            profile = await self.bot.cache.get_profile(targetid, conn=conn)
            if not profile:
                return await ctx.send(
                    _("**{person}** does not have a character.").format(
                        person=person))
            items = await self.bot.get_equipped_items_for(targetid, conn=conn)
            mission = await self.bot.get_adventure(targetid)
            guild = await conn.fetchval(
                'SELECT name FROM guild WHERE "id"=$1;', profile["guild"])
        v1 = sum(i["damage"] for i in items)
        v2 = sum(i["armor"] for i in items)
        damage, armor = await self.bot.get_damage_armor_for(
            targetid,
            items=items,
            classes=profile["class"],
            race=profile["race"])
        extras = (damage - v1, armor - v2)
        sworddmg = f"{v1}{' (+' + str(extras[0]) + ')' if extras[0] else ''}"
        shielddef = f"{v2}{' (+' + str(extras[1]) + ')' if extras[1] else ''}"

        right_hand = "None Equipped"
        left_hand = "None Equipped"

        any_count = sum(1 for i in items if i["hand"] == "any")
        if len(items) == 2 and any_count == 1 and items[0]["hand"] == "any":
            items = [items[1], items[0]]

        for i in items:
            if i["hand"] == "both":
                right_hand, left_hand = i["name"], i["name"]
            elif i["hand"] == "left":
                left_hand = i["name"]
            elif i["hand"] == "right":
                right_hand = i["name"]
            elif i["hand"] == "any":
                if right_hand == "None Equipped":
                    right_hand = i["name"]
                else:
                    left_hand = i["name"]

        color = profile["colour"]
        color = [color["red"], color["green"], color["blue"], color["alpha"]]
        classes = [class_from_string(c) for c in profile["class"]]
        icons = [
            c.get_class_line_name().lower() if c else "none" for c in classes
        ]

        url = f"{self.bot.config.external.okapi_url}/api/genprofile"

        async with self.bot.trusted_session.post(
                url,
                json={
                    "name":
                    profile["name"],
                    "color":
                    color,
                    "image":
                    profile["background"],
                    "race":
                    profile["race"],
                    "classes":
                    profile["class"],
                    "damage":
                    sworddmg,
                    "defense":
                    shielddef,
                    "sword_name":
                    right_hand,
                    "shield_name":
                    left_hand,
                    "level":
                    f"{rpgtools.xptolevel(profile['xp'])}",
                    "money":
                    f"{profile['money']}",
                    "pvp_wins":
                    f"{profile['pvpwins']}",
                    "marriage":
                    i if (i := await rpgtools.lookup(
                        self.bot, profile["marriage"], return_none=True)) else
                    _("Not Married"),
                    "guild":
                    guild or _("No Guild"),
                    "god":
                    profile["god"] or _("No God"),
                    "icons":
                    icons,
                    "adventure":
                    ("Adventure"
                     f" {mission[0]}\n{mission[1] if not mission[2] else _('Finished')}"
                     ) if mission else _("No Mission"),
                },
        ) as req:
            if req.status == 200:
                img = BytesIO(await req.read())
            else:
                # Error, means try reading the response JSON error
                try:
                    error_json = await req.json()
                    return await ctx.send(
                        _("There was an error processing your image. Reason: {reason} ({detail})"
                          ).format(reason=error_json["reason"],
                                   detail=error_json["detail"]))
                except ContentTypeError:
                    return await ctx.send(
                        _("Unexpected internal error when generating image."))
                except Exception:
                    return await ctx.send(
                        _("Unexpected error when generating image."))