""" Interactive Gambling & RPG Cog - Casino Games with UI and Full RPG System """ from __future__ import annotations import random import asyncio from typing import List, Optional, Dict import discord from discord.ext import commands from bot.theme import NEON_CYAN, NEON_LIME, NEON_RED, NEON_GOLD, NEON_PURPLE, NEON_PINK, NEON_BLUE, panel_divider, add_banner_to_embed from bot.i18n import get_cmd_desc from bot.emojis import ui class Card: def __init__(self, suit: str, rank: str): self.suit = suit self.rank = rank self.value = self._get_value() def _get_value(self) -> int: if self.rank in ['J', 'Q', 'K']: return 10 elif self.rank == 'A': return 11 else: return int(self.rank) def __str__(self) -> str: return f"{self.rank}{self.suit}" class Deck: def __init__(self): self.cards = [] suits = ['♠', '♥', '♦', '♣'] ranks = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A'] for suit in suits: for rank in ranks: self.cards.append(Card(suit, rank)) random.shuffle(self.cards) def draw(self) -> Card: return self.cards.pop() class BlackjackView(discord.ui.View): def __init__(self, cog, guild_id: int, user_id: int, bet: int): super().__init__(timeout=300) self.cog = cog self.guild_id = guild_id self.user_id = user_id self.bet = bet self.deck = Deck() self.player_hand = [self.deck.draw(), self.deck.draw()] self.dealer_hand = [self.deck.draw(), self.deck.draw()] self.game_over = False self.message: Optional[discord.Message] = None def hand_value(self, hand: List[Card]) -> int: value = sum(card.value for card in hand) aces = sum(1 for card in hand if card.rank == 'A') while value > 21 and aces: value -= 10 aces -= 1 return value def format_hand(self, hand: List[Card], hide_first: bool = False) -> str: if hide_first and len(hand) >= 2: return f"[❓❓] [{hand[1]}]" return ' '.join([f"[{str(card)}]" for card in hand]) def build_embed(self, show_dealer_card: bool = True) -> discord.Embed: player_value = self.hand_value(self.player_hand) dealer_value = self.hand_value(self.dealer_hand) if show_dealer_card else '?' embed = discord.Embed( title="🃏 Blackjack Casino", description=panel_divider('purple'), color=NEON_PURPLE, ) dealer_display = self.format_hand(self.dealer_hand, hide_first=not show_dealer_card) embed.add_field( name="🎩 Dealer's Hand", value=f"{dealer_display}\n**Value:** {dealer_value}", inline=False, ) player_display = self.format_hand(self.player_hand) embed.add_field( name="👤 Your Hand", value=f"{player_display}\n**Value:** {player_value}", inline=False, ) embed.add_field(name="💰 Bet", value=f"`{self.bet:,}` coins", inline=False) if self.game_over: if player_value > 21: embed.description = f"❌ **BUST!** You lost `{self.bet:,}` coins." embed.color = NEON_RED elif dealer_value == 21 and len(self.dealer_hand) == 2: embed.description = f"❌ **Dealer Blackjack!** You lost `{self.bet:,}` coins." embed.color = NEON_RED elif dealer_value > 21: winnings = self.bet embed.description = f"✅ **Dealer Busts! You Win!** +`{winnings:,}` coins!" embed.color = NEON_LIME elif player_value > dealer_value: winnings = self.bet embed.description = f"✅ **You Win!** +`{winnings:,}` coins!" embed.color = NEON_LIME elif player_value < dealer_value: embed.description = f"❌ **Dealer Wins!** You lost `{self.bet:,}` coins." embed.color = NEON_RED else: embed.description = f"🤝 **Push!** Bet returned." embed.color = NEON_GOLD return embed async def end_game(self, interaction: discord.Interaction): self.game_over = True engagement_cog = interaction.client.get_cog("Engagement") if not engagement_cog: return player_value = self.hand_value(self.player_hand) dealer_value = self.hand_value(self.dealer_hand) if player_value > 21: await engagement_cog._remove_coins(self.guild_id, self.user_id, self.bet) elif dealer_value == 21 and len(self.dealer_hand) == 2: await engagement_cog._remove_coins(self.guild_id, self.user_id, self.bet) elif dealer_value > 21 or player_value > dealer_value: await engagement_cog._add_coins(self.guild_id, self.user_id, self.bet) elif player_value < dealer_value: await engagement_cog._remove_coins(self.guild_id, self.user_id, self.bet) embed = self.build_embed(show_dealer_card=True) for child in self.children: child.disabled = True if self.message: await self.message.edit(embed=embed, view=self) @discord.ui.button(label="Hit", emoji=ui("plus"), style=discord.ButtonStyle.primary, custom_id="bj_hit") async def hit(self, interaction: discord.Interaction, button: discord.ui.Button): if self.game_over or interaction.user.id != self.user_id: await interaction.response.send_message("❌ Not your game.", ephemeral=True) return self.player_hand.append(self.deck.draw()) player_value = self.hand_value(self.player_hand) if player_value > 21: await self.end_game(interaction) else: embed = self.build_embed(show_dealer_card=False) await interaction.response.edit_message(embed=embed, view=self) @discord.ui.button(label="Stand", emoji=ui("stop"), style=discord.ButtonStyle.danger, custom_id="bj_stand") async def stand(self, interaction: discord.Interaction, button: discord.ui.Button): if self.game_over or interaction.user.id != self.user_id: await interaction.response.send_message("❌ Not your game.", ephemeral=True) return while self.hand_value(self.dealer_hand) < 17: self.dealer_hand.append(self.deck.draw()) await self.end_game(interaction) @discord.ui.button(label="Double Down", emoji=ui("star"), style=discord.ButtonStyle.success, custom_id="bj_double") async def double_down(self, interaction: discord.Interaction, button: discord.ui.Button): if self.game_over or interaction.user.id != self.user_id: await interaction.response.send_message("❌ Not your game.", ephemeral=True) return if len(self.player_hand) != 2: await interaction.response.send_message("❌ Can only double on first turn.", ephemeral=True) return engagement_cog = interaction.client.get_cog("Engagement") if not engagement_cog: return row = await interaction.client.db.fetchone( "SELECT wallet FROM user_balance WHERE guild_id = ? AND user_id = ?", self.guild_id, self.user_id, ) wallet = row[0] if row else 0 if wallet < self.bet: await interaction.response.send_message("❌ Insufficient funds to double.", ephemeral=True) return self.bet *= 2 self.player_hand.append(self.deck.draw()) player_value = self.hand_value(self.player_hand) if player_value > 21: await self.end_game(interaction) else: while self.hand_value(self.dealer_hand) < 17: self.dealer_hand.append(self.deck.draw()) await self.end_game(interaction) class RouletteGameView(discord.ui.View): def __init__(self, cog, guild_id: int, user_id: int): super().__init__(timeout=300) self.cog = cog self.guild_id = guild_id self.user_id = user_id self.bet_amount = 0 self.bet_type = "" self.spinning = False RED_NUMBERS = [1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36] BLACK_NUMBERS = [2, 4, 6, 8, 10, 11, 13, 15, 17, 20, 22, 24, 26, 28, 29, 31, 33, 35] def get_color(self, number: int) -> str: if number == 0: return "<:animatedarrowgreen:1477261279428087979>" elif number in self.RED_NUMBERS: return "🔴" else: return "⚫" def build_embed(self, number: Optional[int] = None, won: bool = False, winnings: int = 0) -> discord.Embed: embed = discord.Embed( title="🎰 Roulette Casino", description=panel_divider('gold'), color=NEON_GOLD, ) if number is not None: color_emoji = self.get_color(number) embed.add_field(name="🎯 Result", value=f"{color_emoji} **{number}**", inline=False) embed.add_field(name="💰 Bet", value=f"`{self.bet_amount:,}` on {self.bet_type}", inline=True) embed.add_field(name="💵 Result", value=f"`{'+' if won else '-'}{abs(winnings):,}` coins", inline=True) if won: embed.description = f"✅ **You Won!** +`{winnings:,}` coins!" embed.color = NEON_LIME else: embed.description = f"❌ **You Lost!** -`{abs(winnings):,}` coins." embed.color = NEON_RED else: embed.description = "Place your bet and spin the wheel!" grid = "" for i in range(0, 37, 3): if i == 0: grid += "<:animatedarrowgreen:1477261279428087979> **0** " else: c1 = "🔴" if i in self.RED_NUMBERS else "⚫" c2 = "🔴" if (i+1) in self.RED_NUMBERS else "⚫" c3 = "🔴" if (i+2) in self.RED_NUMBERS else "⚫" grid += f"{c1} {i} {c2} {i+1} {c3} {i+2}\n" embed.add_field(name="🎡 Roulette Wheel", value=f"```\n{grid}```", inline=False) return embed @discord.ui.button(label="Bet Red 🔴", style=discord.ButtonStyle.danger, custom_id="roulette_red") async def bet_red(self, interaction: discord.Interaction, button: discord.ui.Button): await self.prompt_bet(interaction, "red") @discord.ui.button(label="Bet Black ⚫", style=discord.ButtonStyle.gray, custom_id="roulette_black") async def bet_black(self, interaction: discord.Interaction, button: discord.ui.Button): await self.prompt_bet(interaction, "black") @discord.ui.button(label="Bet Even", style=discord.ButtonStyle.primary, custom_id="roulette_even") async def bet_even(self, interaction: discord.Interaction, button: discord.ui.Button): await self.prompt_bet(interaction, "even") @discord.ui.button(label="Bet Odd", style=discord.ButtonStyle.primary, custom_id="roulette_odd") async def bet_odd(self, interaction: discord.Interaction, button: discord.ui.Button): await self.prompt_bet(interaction, "odd") async def prompt_bet(self, interaction: discord.Interaction, bet_type: str): if self.user_id and interaction.user.id != self.user_id: return await interaction.response.send_modal(RouletteBetModal(self.cog, self.guild_id, self.user_id, bet_type)) class RouletteBetModal(discord.ui.Modal, title="🎰 Place Your Bet"): bet_amount = discord.ui.TextInput( label="Bet Amount", placeholder="Enter amount (min 10)", required=True, min_length=1, max_length=10, ) def __init__(self, cog, guild_id: int, user_id: int, bet_type: str): super().__init__() self.cog = cog self.guild_id = guild_id self.user_id = user_id self.bet_type = bet_type async def on_submit(self, interaction: discord.Interaction) -> None: try: bet = int(self.bet_amount.value.strip()) except ValueError: await interaction.response.send_message("❌ Invalid bet amount.", ephemeral=True) return if bet < 10: await interaction.response.send_message("❌ Minimum bet is 10 coins.", ephemeral=True) return engagement_cog = interaction.client.get_cog("Engagement") if not engagement_cog: await interaction.response.send_message("❌ Economy not available.", ephemeral=True) return row = await interaction.client.db.fetchone( "SELECT wallet FROM user_balance WHERE guild_id = ? AND user_id = ?", self.guild_id, self.user_id, ) wallet = row[0] if row else 0 if wallet < bet: await interaction.response.send_message(f"❌ Insufficient funds. You have {wallet:,} coins.", ephemeral=True) return await interaction.response.defer() number = random.randint(0, 36) is_red = number in RouletteGameView.RED_NUMBERS is_black = number in RouletteGameView.BLACK_NUMBERS is_even = number != 0 and number % 2 == 0 is_odd = number != 0 and number % 2 == 1 won = False multiplier = 2 if self.bet_type == 'red' and is_red: won = True elif self.bet_type == 'black' and is_black: won = True elif self.bet_type == 'even' and is_even: won = True elif self.bet_type == 'odd' and is_odd: won = True winnings = bet * multiplier if won else -bet if won: await engagement_cog._add_coins(self.guild_id, self.user_id, winnings) else: await engagement_cog._remove_coins(self.guild_id, self.user_id, bet) view = RouletteGameView(self.cog, self.guild_id, self.user_id) view.bet_amount = bet view.bet_type = self.bet_type embed = view.build_embed(number=number, won=won, winnings=winnings) for child in view.children: child.disabled = False await interaction.followup.send(embed=embed, view=view) class RPGView(discord.ui.View): def __init__(self, cog, guild_id: int, user_id: int): super().__init__(timeout=None) self.cog = cog self.guild_id = guild_id self.user_id = user_id self.adventure_image = "https://images.unsplash.com/photo-1519074069444-1ba4fff66d16?w=500" def build_embed(self, title: str, description: str, color: discord.Color) -> discord.Embed: embed = discord.Embed( title=title, description=description, color=color, ) embed.set_image(url=self.adventure_image) return embed @discord.ui.button(label="⚔️ Fight Monster", emoji=ui("game"), style=discord.ButtonStyle.danger, custom_id="rpg_fight") async def fight_monster(self, interaction: discord.Interaction, button: discord.ui.Button): if self.user_id and interaction.user.id != self.user_id: return engagement_cog = interaction.client.get_cog("Engagement") if not engagement_cog: await interaction.response.send_message("❌ Economy not available.", ephemeral=True) return row = await interaction.client.db.fetchone( "SELECT level FROM user_xp WHERE guild_id = ? AND user_id = ?", self.guild_id, self.user_id, ) level = row[0] if row else 1 monsters = [ ("Goblin", 50, 100, 20, 40), ("Dragon", 200, 500, 50, 100), ("Skeleton", 30, 80, 15, 30), ("Orc", 80, 200, 25, 50), ("Troll", 150, 350, 40, 80), ("Demon", 300, 600, 60, 120), ] monster_name, min_reward, max_reward, min_xp, max_xp = random.choice(monsters) win_chance = min(0.8, 0.3 + (level * 0.05)) won = random.random() < win_chance if won: reward = random.randint(min_reward, max_reward) + (level * 10) xp_gain = random.randint(min_xp, max_xp) await engagement_cog._add_coins(self.guild_id, self.user_id, reward) await engagement_cog.add_xp(self.guild_id, self.user_id, xp_gain) embed = discord.Embed( title="⚔️ RPG Adventure - Victory!", description=f"✅ **You defeated the {monster_name}!**\n\n💰 **+{reward:,} coins**\n⭐ **+{xp_gain} XP**\n📈 Level {level} → {level + (1 if xp_gain > 40 else 0)}", color=NEON_LIME, ) embed.set_image(url="https://images.unsplash.com/photo-1535905557558-afc4877a26fc?w=500") else: penalty = random.randint(10, 50) + (level * 5) await engagement_cog._remove_coins(self.guild_id, self.user_id, penalty) embed = discord.Embed( title="⚔️ RPG Adventure - Defeat!", description=f"❌ **The {monster_name} defeated you!**\n\n💀 **-{penalty:,} coins**\n\n💡 Tip: Level up to increase win chance!", color=NEON_RED, ) embed.set_image(url="https://images.unsplash.com/photo-1519791883288-dc8bd696e667?w=500") if interaction.guild: await add_banner_to_embed(embed, interaction.guild) await interaction.response.send_message(embed=embed, ephemeral=True) @discord.ui.button(label="🎁 Find Treasure", emoji=ui("gift"), style=discord.ButtonStyle.success, custom_id="rpg_treasure") async def find_treasure(self, interaction: discord.Interaction, button: discord.ui.Button): if self.user_id and interaction.user.id != self.user_id: return engagement_cog = interaction.client.get_cog("Engagement") if not engagement_cog: await interaction.response.send_message("❌ Economy not available.", ephemeral=True) return found = random.random() < 0.4 if found: reward = random.randint(30, 150) await engagement_cog._add_coins(self.guild_id, self.user_id, reward) embed = discord.Embed( title="🎁 Treasure Hunt - Success!", description=f"✅ **You found a hidden treasure!**\n\n💰 **+{reward:,} coins**\n\n🗺️ The treasure map led you to riches!", color=NEON_GOLD, ) embed.set_image(url="https://images.unsplash.com/photo-1518709268805-4e9042af9f23?w=500") else: embed = discord.Embed( title="🎁 Treasure Hunt - Nothing Found", description="❌ **No treasure found in this area...**\n\n🗺️ Try exploring a different location next time!", color=NEON_RED, ) embed.set_image(url="https://images.unsplash.com/photo-1500353391678-d7b57970d9b2?w=500") if interaction.guild: await add_banner_to_embed(embed, interaction.guild) await interaction.response.send_message(embed=embed, ephemeral=True) @discord.ui.button(label="🏪 Shop", emoji=ui("cart"), style=discord.ButtonStyle.blurple, custom_id="rpg_shop") async def shop(self, interaction: discord.Interaction, button: discord.ui.Button): if interaction.user.id != self.user_id: return embed = discord.Embed( title="🏪 RPG Shop", description=( "Welcome to the shop! Buy items to help in your adventures.\n\n" "🗡️ **Sword** - Increase win chance (Coming Soon)\n" "🛡️ **Shield** - Reduce coin loss on defeat (Coming Soon)\n" "🧪 **Potion** - Heal after battle (Coming Soon)\n" "📜 **Map** - Better treasure finds (Coming Soon)" ), color=NEON_CYAN, ) embed.set_image(url="https://images.unsplash.com/photo-1589829545856-d10d557cf95f?w=500") if interaction.guild: await add_banner_to_embed(embed, interaction.guild) await interaction.response.send_message(embed=embed, ephemeral=True) @discord.ui.button(label="📊 Stats", emoji=ui("stats"), style=discord.ButtonStyle.secondary, custom_id="rpg_stats") async def stats(self, interaction: discord.Interaction, button: discord.ui.Button): if interaction.user.id != self.user_id: return engagement_cog = interaction.client.get_cog("Engagement") if not engagement_cog: return row = await interaction.client.db.fetchone( "SELECT wallet, bank FROM user_balance WHERE guild_id = ? AND user_id = ?", self.guild_id, self.user_id, ) wallet, bank = row if row else (0, 0) xp_row = await interaction.client.db.fetchone( "SELECT xp, level FROM user_xp WHERE guild_id = ? AND user_id = ?", self.guild_id, self.user_id, ) xp, level = xp_row if xp_row else (0, 1) embed = discord.Embed( title="📊 RPG Stats", description=( f"**Level:** {level}\n" f"**XP:** {xp:,}\n" f"**Wallet:** {wallet:,} coins\n" f"**Bank:** {bank:,} coins\n" f"**Total Wealth:** {wallet + bank:,} coins" ), color=NEON_PURPLE, ) if interaction.guild: await add_banner_to_embed(embed, interaction.guild) await interaction.response.send_message(embed=embed, ephemeral=True) class GamblingPanelView(discord.ui.View): def __init__(self, cog, guild_id: int, user_id: int): super().__init__(timeout=None) self.cog = cog self.guild_id = guild_id self.user_id = user_id @discord.ui.button(label="🃏 Play Blackjack", emoji=ui("game"), style=discord.ButtonStyle.danger, custom_id="gambling_blackjack") async def play_blackjack(self, interaction: discord.Interaction, button: discord.ui.Button): if interaction.user.id != self.user_id: return await interaction.response.send_modal(BlackjackBetModal(self.cog, self.guild_id, self.user_id)) @discord.ui.button(label="🎰 Play Roulette", emoji=ui("game"), style=discord.ButtonStyle.danger, custom_id="gambling_roulette") async def play_roulette(self, interaction: discord.Interaction, button: discord.ui.Button): if interaction.user.id != self.user_id: return view = RouletteGameView(self.cog, self.guild_id, self.user_id) embed = view.build_embed() await interaction.response.send_message(embed=embed, view=view, ephemeral=True) @discord.ui.button(label="⚔️ RPG Adventure", emoji=ui("game"), style=discord.ButtonStyle.success, custom_id="gambling_rpg") async def play_rpg(self, interaction: discord.Interaction, button: discord.ui.Button): if interaction.user.id != self.user_id: return view = RPGView(self.cog, self.guild_id, self.user_id) embed = view.build_embed( title="⚔️ RPG Adventure", description="Choose your action below to begin your adventure!", color=NEON_PURPLE, ) await interaction.response.send_message(embed=embed, view=view, ephemeral=True) class BlackjackBetModal(discord.ui.Modal, title="🃏 Place Your Bet"): bet_amount = discord.ui.TextInput( label="Bet Amount", placeholder="Enter amount (min 10)", required=True, min_length=1, max_length=10, ) def __init__(self, cog, guild_id: int, user_id: int): super().__init__() self.cog = cog self.guild_id = guild_id self.user_id = user_id async def on_submit(self, interaction: discord.Interaction) -> None: try: bet = int(self.bet_amount.value.strip()) except ValueError: await interaction.response.send_message("❌ Invalid bet amount.", ephemeral=True) return if bet < 10: await interaction.response.send_message("❌ Minimum bet is 10 coins.", ephemeral=True) return engagement_cog = interaction.client.get_cog("Engagement") if not engagement_cog: await interaction.response.send_message("❌ Economy not available.", ephemeral=True) return row = await interaction.client.db.fetchone( "SELECT wallet FROM user_balance WHERE guild_id = ? AND user_id = ?", self.guild_id, self.user_id, ) wallet = row[0] if row else 0 if wallet < bet: await interaction.response.send_message(f"❌ Insufficient funds. You have {wallet:,} coins.", ephemeral=True) return view = BlackjackView(self.cog, self.guild_id, self.user_id, bet) embed = view.build_embed(show_dealer_card=False) message = await interaction.channel.send(embed=embed, view=view) view.message = message await interaction.response.send_message("✅ Blackjack game started!", ephemeral=True) class Gambling(commands.Cog): def __init__(self, bot: commands.Bot) -> None: self.bot = bot @commands.hybrid_command(name="blackjack", description=get_cmd_desc("commands.economy.blackjack_desc")) async def blackjack(self, ctx: commands.Context, bet: int) -> None: if bet < 10: await ctx.send("❌ Minimum bet is 10 coins.", ephemeral=True) return engagement_cog = self.bot.get_cog("Engagement") if not engagement_cog: await ctx.send("❌ Economy not available.", ephemeral=True) return row = await self.bot.db.fetchone( "SELECT wallet FROM user_balance WHERE guild_id = ? AND user_id = ?", ctx.guild.id if ctx.guild else 0, ctx.author.id, ) wallet = row[0] if row else 0 if wallet < bet: await ctx.send(f"❌ Insufficient funds. You have {wallet:,} coins.", ephemeral=True) return view = BlackjackView(self, ctx.guild.id if ctx.guild else 0, ctx.author.id, bet) embed = view.build_embed(show_dealer_card=False) message = await ctx.send(embed=embed, view=view) view.message = message @commands.hybrid_command(name="roulette", description=get_cmd_desc("commands.economy.roulette_desc")) async def roulette(self, ctx: commands.Context) -> None: view = RouletteGameView(self, ctx.guild.id if ctx.guild else 0, ctx.author.id) embed = view.build_embed() await ctx.send(embed=embed, view=view) @commands.hybrid_command(name="rpg", description=get_cmd_desc("commands.economy.rpg_desc")) async def rpg(self, ctx: commands.Context) -> None: view = RPGView(self, ctx.guild.id if ctx.guild else 0, ctx.author.id) embed = view.build_embed( title="⚔️ RPG Adventure", description="Choose your action below to begin your adventure!", color=NEON_PURPLE, ) await ctx.send(embed=embed, view=view, ephemeral=True) @commands.hybrid_command(name="gambling_panel", description=get_cmd_desc("commands.economy.gambling_panel_desc")) async def gambling_panel(self, ctx: commands.Context) -> None: view = GamblingPanelView(self, ctx.guild.id if ctx.guild else 0, ctx.author.id) embed = discord.Embed( title="🎰 Casino & Gambling Panel", description=( "Welcome to the Casino! Choose a game to play:\n\n" "🃏 **Blackjack** - Beat the dealer to 21\n" "🎰 **Roulette** - Bet on numbers and colors\n" "⚔️ **RPG Adventure** - Fight monsters and find treasure" ), color=NEON_GOLD, ) if ctx.guild: from bot.theme import add_banner_to_embed await add_banner_to_embed(embed, ctx.guild) await ctx.send(embed=embed, view=view, ephemeral=True) async def setup(bot: commands.Bot) -> None: await bot.add_cog(Gambling(bot))