diff --git a/cogs/guide.py b/cogs/guide.py index d4a4199..2e7eb81 100644 --- a/cogs/guide.py +++ b/cogs/guide.py @@ -22,8 +22,8 @@ class Guide(commands.Cog): embed.add_field( name="💰 Damage Formats", value="**Accepted formats:** `1500000`, `1.5M`, `500K`, `2B`\n" + - "**Suffixes:** K = thousands, M = millions, B = billions\n" + - "**Shortcuts:** `nm` = Nightmare, `unm` = Ultra Nightmare", + "**Suffixes:** K = thousands, M = millions, B = billions\n" + + "**Shortcuts:** `nm` = Nightmare, `unm` = Ultra Nightmare", inline=False ) @@ -31,9 +31,9 @@ class Guide(commands.Cog): embed.add_field( name="🐍 Hydra Commands", value="**Difficulties:** Normal | Hard | Brutal | Nightmare (nm)\n" + - "`!pbhydra ` - Submit PB + screenshot\n" + - "`!pbhydra ` - Show your PB\n" + - "`!pbhydra ` - Show user's PB", + "`!pbhydra ` - Submit PB + screenshot\n" + + "`!pbhydra ` - Show your PB\n" + + "`!pbhydra ` - Show user's PB", inline=False ) @@ -41,9 +41,9 @@ class Guide(commands.Cog): embed.add_field( name="🦁 Chimera Commands", value="**Difficulties:** Easy | Normal | Hard | Brutal | Nightmare (nm) | Ultra (unm)\n" + - "`!pbchimera ` - Submit PB + screenshot\n" + - "`!pbchimera ` - Show your PB\n" + - "`!pbchimera ` - Show user's PB", + "`!pbchimera ` - Submit PB + screenshot\n" + + "`!pbchimera ` - Show your PB\n" + + "`!pbchimera ` - Show user's PB", inline=False ) @@ -51,8 +51,18 @@ class Guide(commands.Cog): embed.add_field( name="⚔️ CvC Commands", value="`!pbcvc ` - Submit PB + screenshot\n" + - "`!pbcvc` - Show your PB\n" + - "`!pbcvc ` - Show user's PB", + "`!pbcvc` - Show your PB\n" + + "`!pbcvc ` - Show user's PB", + inline=False + ) + + # Commandes Mercy + embed.add_field( + name="🎲 Mercy Commands", + value="`!mercy show` - Show your current mercy pulls\n" + + "`!mercy add ` - Add pulls to a shard type\n" + + "`!mercy reset ` - Reset pulls for a shard type\n" + + "**Available types:** ancient, void, sacred, primal_legendary, primal_mythical, remnant", inline=False ) @@ -60,8 +70,8 @@ class Guide(commands.Cog): embed.add_field( name="🌍 Global Leaderboards", value="`!top10hydra ` - Global Hydra rankings\n" + - "`!top10chimera ` - Global Chimera rankings\n" + - "`!top10cvc` - Global CvC rankings", + "`!top10chimera ` - Global Chimera rankings\n" + + "`!top10cvc` - Global CvC rankings", inline=False ) @@ -69,8 +79,8 @@ class Guide(commands.Cog): embed.add_field( name="🏛️ Clan Leaderboards", value="**RTF:** `!rtfhydra ` `!rtfchimera ` `!rtfcvc`\n" + - "**RTFC:** `!rtfchydra ` `!rtfcchimera ` `!rtfccvc`\n" + - "**RTFR:** `!rtfrhydra ` `!rtfrchimera ` `!rtfrcvc`", + "**RTFC:** `!rtfchydra ` `!rtfcchimera ` `!rtfccvc`\n" + + "**RTFR:** `!rtfrhydra ` `!rtfrchimera ` `!rtfrcvc`", inline=False ) @@ -78,8 +88,8 @@ class Guide(commands.Cog): embed.add_field( name="📈 Stats & Info", value="`!mystats` - View all your PBs\n" + - "`!mystats ` - View someone's PBs\n" + - "`!guide` - Show this help message", + "`!mystats ` - View someone's PBs\n" + + "`!guide` - Show this help message", inline=False ) @@ -87,10 +97,12 @@ class Guide(commands.Cog): embed.add_field( name="💡 Examples", value="`!pbhydra brutal 1.5M` - Submit Brutal Hydra PB\n" + - "`!pbchimera unm 500K` - Submit Ultra Nightmare PB\n" + - "`!pbcvc 2.3M` - Submit CvC PB\n" + - "`!rtfhydra nm` - RTF clan Nightmare rankings\n" + - "**Always attach screenshot when submitting PBs!**", + "`!pbchimera unm 500K` - Submit Ultra Nightmare PB\n" + + "`!pbcvc 2.3M` - Submit CvC PB\n" + + "`!mercy add 50 ancient` - Add 50 pulls to Ancient shard\n" + + "`!mercy show` - Show your mercy pulls\n" + + "`!rtfhydra nm` - RTF clan Nightmare rankings\n" + + "**Always attach screenshot when submitting PBs!**", inline=False ) @@ -98,5 +110,5 @@ class Guide(commands.Cog): await ctx.send(embed=embed) -def setup(bot): - bot.add_cog(Guide(bot)) \ No newline at end of file +async def setup(bot): + await bot.add_cog(Guide(bot)) diff --git a/cogs/mercy.py b/cogs/mercy.py index 235b5ee..55d04b3 100644 --- a/cogs/mercy.py +++ b/cogs/mercy.py @@ -1,118 +1,94 @@ import discord from discord.ext import commands -from utils.DatabaseManager_class import DatabaseManager from config import AUTHORIZED_CHANNEL_ID -from datetime import datetime +from utils.MercyManager_class import MercyManager -# Règles de mercy -MERCY_RULES = { - "ancient": {"threshold": 200, "increment": 0.5, "base": 0}, - "void": {"threshold": 200, "increment": 0.5, "base": 0}, - "sacred": {"threshold": 12, "increment": 2, "base": 0}, - "primal_legendary": {"threshold": 75, "increment": 1, "base": 0}, - "primal_mythical": {"threshold": 200, "increment": 10, "base": 0}, - "remnant": {"threshold": 24, "increment": 1, "base": 0}, -} - -db_manager = DatabaseManager() +VALID_SHARDS = ["ancient", "void", "sacred", "primal", "remnant"] class Mercy(commands.Cog): def __init__(self, bot): self.bot = bot - # Création de la table si elle n’existe pas - db_manager.execute(""" - CREATE TABLE IF NOT EXISTS mercy_counters ( - user_id TEXT, - shard_type TEXT, - pulls INTEGER DEFAULT 0, - last_reset TIMESTAMP, - PRIMARY KEY(user_id, shard_type) - ) - """) + self.mercy_manager = MercyManager() - def get_mercy_chance(self, shard_type, pulls): - rule = MERCY_RULES[shard_type] - if pulls <= rule["threshold"]: - return rule["base"] - return rule["base"] + (pulls - rule["threshold"]) * rule["increment"] + def calc_chance_and_guarantee(self, shard_type, pulls): + """Calcule la chance actuelle et le pull garanti""" + rules = { + "ancient": {"start": 200, "increment": 5, "base": 0.5}, + "void": {"start": 200, "increment": 5, "base": 0.5}, + "sacred": {"start": 12, "increment": 2, "base": 6}, + "primal_legendary": {"start": 75, "increment": 1, "base": 1}, + "primal_mythical": {"start": 200, "increment": 10, "base": 0.5}, + "remnant": {"start": 24, "increment": 1, "base": 0}, + } + rule = rules[shard_type] + chance = rule["base"] if pulls < rule["start"] else rule["base"] + (pulls - rule["start"]) * rule["increment"] + guaranteed_at = None + if chance >= 100: + guaranteed_at = rule["start"] + (100 - rule["base"]) / rule["increment"] + return chance, guaranteed_at @commands.command(name="mercy") async def mercy(self, ctx, action: str = None, arg1: str = None, arg2: str = None): - """Gestion de la mercy : !mercy add , !mercy reset , !mercy show""" if ctx.channel.id != AUTHORIZED_CHANNEL_ID: return user_id = str(ctx.author.id) - # ----- SHOW ----- if action == "show": - rows = db_manager.fetchall("SELECT shard_type, pulls FROM mercy_counters WHERE user_id = ?", (user_id,)) - if not rows: + pulls_dict = self.mercy_manager.get_all_pulls(user_id) + if not pulls_dict: await ctx.send("❌ You don't have any mercy data yet.") return - embed = discord.Embed( - title=f"🎲 Mercy Status for {ctx.author.display_name}", - color=0x00bfff - ) + embed = discord.Embed(title=f"🎲 Mercy Status for {ctx.author.display_name}", color=0x00bfff) - for shard_type, pulls in rows: - chance = self.get_mercy_chance(shard_type, pulls) - embed.add_field( - name=shard_type.replace("_", " ").title(), - value=f"**{pulls} pulls** → {chance:.1f}% chance", - inline=False - ) + for shard_type, pulls in pulls_dict.items(): + if shard_type == "primal": + for sub_type in ["primal_legendary", "primal_mythical"]: + chance, guaranteed_at = self.calc_chance_and_guarantee(sub_type, pulls) + guaranteed_text = f" (Guaranteed at {int(guaranteed_at)} pulls)" if guaranteed_at else "" + embed.add_field( + name=sub_type.replace("_", " ").title(), + value=f"Pulled: **{pulls} shards** → {chance:.1f}% chance{guaranteed_text}", + inline=False + ) + else: + chance, guaranteed_at = self.calc_chance_and_guarantee(shard_type, pulls) + guaranteed_text = f" (Guaranteed at {int(guaranteed_at)} pulls)" if guaranteed_at else "" + embed.add_field( + name=shard_type.replace("_", " ").title(), + value=f"Pulled: **{pulls} shards** → {chance:.1f}% chance{guaranteed_text}", + inline=False + ) await ctx.send(embed=embed) - # ----- ADD ----- elif action == "add" and arg1 and arg2: try: - pulls = int(arg1) + pulls_to_add = int(arg1) except ValueError: await ctx.send("❌ Number of pulls must be an integer.") return shard_type = arg2.lower() - if shard_type not in MERCY_RULES: - await ctx.send(f"❌ Invalid shard type. Available: {', '.join(MERCY_RULES.keys())}") + if shard_type not in VALID_SHARDS: + await ctx.send(f"❌ Invalid shard type. Available: {', '.join(VALID_SHARDS)}") return - # Update DB - current = db_manager.fetchone( - "SELECT pulls FROM mercy_counters WHERE user_id = ? AND shard_type = ?", - (user_id, shard_type) - ) - if current: - new_value = current["pulls"] + pulls - db_manager.execute( - "UPDATE mercy_counters SET pulls = ?, last_reset = ? WHERE user_id = ? AND shard_type = ?", - (new_value, datetime.utcnow(), user_id, shard_type) - ) - else: - db_manager.execute( - "INSERT INTO mercy_counters (user_id, shard_type, pulls, last_reset) VALUES (?, ?, ?, ?)", - (user_id, shard_type, pulls, datetime.utcnow()) - ) + new_pulls = self.mercy_manager.add_pulls(user_id, shard_type, pulls_to_add) + await ctx.send(f"✅ Added {pulls_to_add} pulls to **{shard_type}** mercy. Total: {new_pulls}") - await ctx.send(f"✅ Added {pulls} pulls to **{shard_type}** mercy.") - - # ----- RESET ----- elif action == "reset" and arg1: shard_type = arg1.lower() - if shard_type not in MERCY_RULES: - await ctx.send(f"❌ Invalid shard type. Available: {', '.join(MERCY_RULES.keys())}") + if shard_type not in VALID_SHARDS: + await ctx.send(f"❌ Invalid shard type. Available: {', '.join(VALID_SHARDS)}") return - db_manager.execute( - "UPDATE mercy_counters SET pulls = 0, last_reset = ? WHERE user_id = ? AND shard_type = ?", - (datetime.utcnow(), user_id, shard_type) - ) + self.mercy_manager.reset_pulls(user_id, shard_type) await ctx.send(f"🔄 Mercy for **{shard_type}** has been reset.") else: await ctx.send("❌ Usage: `!mercy add `, `!mercy reset `, `!mercy show`") -def setup(bot): - bot.add_cog(Mercy(bot)) +async def setup(bot): + await bot.add_cog(Mercy(bot)) diff --git a/cogs/mystats.py b/cogs/mystats.py index bd459bc..2f142e2 100644 --- a/cogs/mystats.py +++ b/cogs/mystats.py @@ -85,5 +85,5 @@ class MyStats(commands.Cog): # Pour charger le Cog -def setup(bot): - bot.add_cog(MyStats(bot)) +async def setup(bot): + await bot.add_cog(MyStats(bot)) diff --git a/cogs/pbchimera.py b/cogs/pbchimera.py index c4a81fd..ff542a8 100644 --- a/cogs/pbchimera.py +++ b/cogs/pbchimera.py @@ -12,5 +12,5 @@ class Pbchimera(commands.Cog): """Commande !pbchimera avec gestion des difficultés""" await handle_pb_command(ctx, 'chimera', arg1, arg2) -def setup(bot): - bot.add_cog(Pbchimera(bot)) \ No newline at end of file +async def setup(bot): + await bot.add_cog(Pbchimera(bot)) \ No newline at end of file diff --git a/cogs/pbcvc.py b/cogs/pbcvc.py index ae2e438..ea2cdb2 100644 --- a/cogs/pbcvc.py +++ b/cogs/pbcvc.py @@ -12,5 +12,5 @@ class Pbcvc(commands.Cog): """Commande !pbcvc (sans difficultées)""" await handle_pb_command(ctx, 'cvc', target_user) -def setup(bot): - bot.add_cog(Pbcvc(bot)) \ No newline at end of file +async def setup(bot): + await bot.add_cog(Pbcvc(bot)) \ No newline at end of file diff --git a/cogs/pbhydra.py b/cogs/pbhydra.py index a1a5946..d4ea60f 100644 --- a/cogs/pbhydra.py +++ b/cogs/pbhydra.py @@ -12,5 +12,5 @@ class Pbhydra(commands.Cog): """Commande !pbhydra avec gestion des difficultés""" await handle_pb_command(ctx, 'hydra', arg1, arg2) -def setup(bot): - bot.add_cog(Pbhydra(bot)) \ No newline at end of file +async def setup(bot): + await bot.add_cog(Pbhydra(bot)) \ No newline at end of file diff --git a/cogs/top10.py b/cogs/top10.py index 8d71eea..6958499 100644 --- a/cogs/top10.py +++ b/cogs/top10.py @@ -1,7 +1,8 @@ import discord from discord.ext import commands from utils.leaderboard_handler import show_leaderboard -from utils.helpers import normalize_difficulty, BOSS_CONFIG +from utils.helpers import normalize_difficulty +from config import BOSS_CONFIG class Top10(commands.Cog): """Cog regroupant toutes les commandes de leaderboard globales et par clan""" @@ -80,5 +81,5 @@ class Top10(commands.Cog): else: await show_leaderboard(ctx, boss_type, clan=clan) -def setup(bot): - bot.add_cog(Top10(bot)) \ No newline at end of file +async def setup(bot): + await bot.add_cog(Top10(bot)) \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index b5e4b95..45ab03d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,17 +7,18 @@ services: env_file: - .env volumes: - - ./screenshots:/app/screenshots - - ./bot_data.db:/app/bot_data.db - - ./logs:/app/logs + # Montre le code source pour hot-reload + - ./:/app-dev + # Montre les dossiers de stockage persistants + - ./screenshots:/app-dev/screenshots + - ./bot_data.db:/app-dev/bot_data.db + - ./logs:/app-dev/logs environment: - TZ=Europe/Paris container_name: rtf-discord-bot - - # Optional: Resource limits deploy: resources: limits: memory: 256M reservations: - memory: 128M \ No newline at end of file + memory: 128M diff --git a/utils/MercyManager_class.py b/utils/MercyManager_class.py new file mode 100644 index 0000000..3525145 --- /dev/null +++ b/utils/MercyManager_class.py @@ -0,0 +1,104 @@ +import sqlite3 +from datetime import datetime +from config import DATABASE_PATH + +# Règles de mercy pour stockage +MERCY_RULES = { + "ancient": {"threshold": 200, "increment": 0.5, "base": 0}, + "void": {"threshold": 200, "increment": 0.5, "base": 0}, + "sacred": {"threshold": 12, "increment": 2, "base": 0}, + "primal_legendary": {"threshold": 75, "increment": 1, "base": 0}, + "primal_mythical": {"threshold": 200, "increment": 10, "base": 0}, + "remnant": {"threshold": 24, "increment": 1, "base": 0}, +} + +class MercyManager: + def __init__(self, db_path=DATABASE_PATH): + self.db_path = db_path + self.init_table() + + def init_table(self): + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + cursor.execute(""" + CREATE TABLE IF NOT EXISTS mercy_counters ( + user_id TEXT, + shard_type TEXT, + pulls INTEGER DEFAULT 0, + last_reset TIMESTAMP, + PRIMARY KEY(user_id, shard_type) + ) + """) + conn.commit() + conn.close() + + def get_pulls(self, user_id, shard_type): + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + cursor.execute( + "SELECT pulls FROM mercy_counters WHERE user_id = ? AND shard_type = ?", + (user_id, shard_type) + ) + row = cursor.fetchone() + conn.close() + return row[0] if row else 0 + + def add_pulls(self, user_id, shard_type, pulls): + current = self.get_pulls(user_id, shard_type) + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + if current: + cursor.execute( + "UPDATE mercy_counters SET pulls = ?, last_reset = ? WHERE user_id = ? AND shard_type = ?", + (current + pulls, datetime.utcnow(), user_id, shard_type) + ) + else: + cursor.execute( + "INSERT INTO mercy_counters (user_id, shard_type, pulls, last_reset) VALUES (?, ?, ?, ?)", + (user_id, shard_type, pulls, datetime.utcnow()) + ) + conn.commit() + conn.close() + return current + pulls + + def reset_pulls(self, user_id, shard_type): + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + cursor.execute( + "UPDATE mercy_counters SET pulls = 0, last_reset = ? WHERE user_id = ? AND shard_type = ?", + (datetime.utcnow(), user_id, shard_type) + ) + conn.commit() + conn.close() + + def get_all_pulls(self, user_id): + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + cursor.execute( + "SELECT shard_type, pulls FROM mercy_counters WHERE user_id = ?", + (user_id,) + ) + rows = cursor.fetchall() + conn.close() + return {shard_type: pulls for shard_type, pulls in rows} + + def get_mercy_chance(self, shard_type, pulls): + rule = MERCY_RULES[shard_type] + if pulls <= rule["threshold"]: + return rule["base"] + return rule["base"] + (pulls - rule["threshold"]) * rule["increment"] + + def get_guaranteed_pull(self, shard_type): + """Retourne le pull où la loot est garanti à 100%""" + rules = { + "ancient": {"start": 200, "increment": 5, "base": 0.5}, + "void": {"start": 200, "increment": 5, "base": 0.5}, + "sacred": {"start": 12, "increment": 2, "base": 6}, + "primal_legendary": {"start": 75, "increment": 1, "base": 1}, + "primal_mythical": {"start": 200, "increment": 10, "base": 0.5}, + "remnant": {"start": 24, "increment": 1, "base": 0}, + } + if shard_type not in rules: + return None + rule = rules[shard_type] + return int(rule["start"] + (100 - rule["base"]) / rule["increment"])