From 710ff5ac9c49d5d3b2de79b85d97eac1c58d0c14 Mon Sep 17 00:00:00 2001 From: megaproxy Date: Mon, 14 Jul 2025 13:08:25 +0100 Subject: [PATCH] Implement interactive gym battle system with full battle engine integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **NEW FEATURES:** - Full 3-pet gym battles with turn-based combat - Interactive attack selection (\!attack ) - Item usage during gym battles (\!use ) - Progressive battles through gym leader's team - Proper gym battle state tracking and advancement **BATTLE MECHANICS:** - Players fight through all 3 gym pets sequentially - Can use all battle commands: \!attack, \!moves, \!use - Cannot flee from gym battles (must \!forfeit instead) - Battle engine integration maintains all existing combat features - Automatic progression to next gym pet when one is defeated **GYM BATTLE FLOW:** 1. \!gym challenge "gym name" - starts battle with first pet 2. Standard turn-based combat using \!attack 3. When gym pet defeated, automatically advance to next pet 4. Complete victory after defeating all 3 gym pets 5. \!forfeit available to quit gym battle with honor **DATABASE UPDATES:** - Added active_gym_battles table for state tracking - Gym battle progression and team management - Integration with existing player_gym_battles for victory tracking **COMMANDS ADDED:** - \!forfeit - quit current gym battle - Enhanced \!gym challenge with full battle system - Battle system now handles gym vs wild battle contexts This creates the proper Pokemon-style gym experience where players strategically battle through the gym leader's team using their full arsenal of moves and items. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- modules/battle_system.py | 108 +++++++++++++++++++++++++++++--- modules/gym_battles.py | 84 +++++++++++++++++++++---- src/database.py | 130 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 298 insertions(+), 24 deletions(-) diff --git a/modules/battle_system.py b/modules/battle_system.py index 7e98d5b..d64783f 100644 --- a/modules/battle_system.py +++ b/modules/battle_system.py @@ -124,16 +124,23 @@ class BattleSystem(BaseModule): self.send_message(channel, battle_msg) if result["battle_over"]: - if result["winner"] == "player": - self.send_message(channel, f"🎉 {nickname}: You won the battle!") - # Remove encounter since battle is over - if player["id"] in self.bot.active_encounters: - del self.bot.active_encounters[player["id"]] + # Check if this is a gym battle + gym_battle = await self.database.get_active_gym_battle(player["id"]) + + if gym_battle: + await self.handle_gym_battle_completion(channel, nickname, player, result, gym_battle) else: - self.send_message(channel, f"💀 {nickname}: Your pet fainted! You lost the battle...") - # Remove encounter - if player["id"] in self.bot.active_encounters: - del self.bot.active_encounters[player["id"]] + # Regular wild battle + if result["winner"] == "player": + self.send_message(channel, f"🎉 {nickname}: You won the battle!") + # Remove encounter since battle is over + if player["id"] in self.bot.active_encounters: + del self.bot.active_encounters[player["id"]] + else: + self.send_message(channel, f"💀 {nickname}: Your pet fainted! You lost the battle...") + # Remove encounter + if player["id"] in self.bot.active_encounters: + del self.bot.active_encounters[player["id"]] else: # Battle continues - show available moves with type-based colors moves_colored = " | ".join([ @@ -148,6 +155,14 @@ class BattleSystem(BaseModule): if not player: return + # Check if this is a gym battle + gym_battle = await self.database.get_active_gym_battle(player["id"]) + + if gym_battle: + # Can't flee from gym battles + self.send_message(channel, f"❌ {nickname}: You can't flee from a gym battle! Fight or forfeit with your honor intact!") + return + success = await self.game_engine.battle_engine.flee_battle(player["id"]) if success: @@ -189,4 +204,77 @@ class BattleSystem(BaseModule): pet_name = active_pet["nickname"] or active_pet["species_name"] moves_line = " | ".join(move_info) - self.send_message(channel, f"🎯 {nickname}'s {pet_name}: {moves_line}") \ No newline at end of file + self.send_message(channel, f"🎯 {nickname}'s {pet_name}: {moves_line}") + + async def handle_gym_battle_completion(self, channel, nickname, player, battle_result, gym_battle): + """Handle completion of a gym battle turn""" + if battle_result["winner"] == "player": + # Player won this individual battle + current_pet_index = gym_battle["current_pet_index"] + gym_team = gym_battle["gym_team"] + defeated_pet = gym_team[current_pet_index] + + self.send_message(channel, f"🎉 {nickname}: You defeated {defeated_pet['species_name']}!") + + # Check if there are more pets + if await self.database.advance_gym_battle(player["id"]): + # More pets to fight + next_index = current_pet_index + 1 + next_pet = gym_team[next_index] + + self.send_message(channel, + f"🥊 {gym_battle['leader_name']} sends out {next_pet['species_name']} (Lv.{next_pet['level']})!") + + # Start battle with next gym pet + active_pets = await self.database.get_active_pets(player["id"]) + player_pet = active_pets[0] # Use first active pet + + # Create gym pet data for battle engine + next_gym_pet_data = { + "species_name": next_pet["species_name"], + "level": next_pet["level"], + "type1": next_pet["type1"], + "type2": next_pet["type2"], + "stats": { + "hp": next_pet["hp"], + "attack": next_pet["attack"], + "defense": next_pet["defense"], + "speed": next_pet["speed"] + } + } + + # Start next battle + battle = await self.game_engine.battle_engine.start_battle(player["id"], player_pet, next_gym_pet_data) + + self.send_message(channel, + f"⚔️ Your {player_pet['species_name']} (HP: {battle['player_hp']}/{player_pet['max_hp']}) vs {next_pet['species_name']} (HP: {battle['wild_hp']}/{next_pet['hp']})") + + # Show available moves + moves_colored = " | ".join([ + f"{self.get_move_color(move['type'])}{move['name']}\x0F" + for move in battle["available_moves"] + ]) + self.send_message(channel, f"🎯 Moves: {moves_colored} | Use !attack or !use ") + + else: + # All gym pets defeated - gym victory! + result = await self.database.end_gym_battle(player["id"], victory=True) + + self.send_message(channel, f"🏆 {nickname}: You defeated all of {gym_battle['leader_name']}'s pets!") + self.send_message(channel, + f"{gym_battle['badge_icon']} {gym_battle['leader_name']}: \"Impressive! You've earned the {gym_battle['gym_name']} badge!\"") + self.send_message(channel, f"🎉 {nickname} earned the {gym_battle['gym_name']} badge {gym_battle['badge_icon']}!") + + # Award rewards based on difficulty + money_reward = 500 + (result["difficulty_level"] * 100) + self.send_message(channel, f"💰 Rewards: ${money_reward} | 🌟 Gym mastery increased!") + + else: + # Player lost gym battle + result = await self.database.end_gym_battle(player["id"], victory=False) + + self.send_message(channel, f"💀 {nickname}: Your pet fainted!") + self.send_message(channel, + f"{gym_battle['badge_icon']} {gym_battle['leader_name']}: \"Good battle! Train more and come back stronger!\"") + self.send_message(channel, + f"💡 {nickname}: Try leveling up your pets or bringing items to heal during battle!") \ No newline at end of file diff --git a/modules/gym_battles.py b/modules/gym_battles.py index a4c05f6..57665c5 100644 --- a/modules/gym_battles.py +++ b/modules/gym_battles.py @@ -7,7 +7,7 @@ class GymBattles(BaseModule): """Handles gym challenges, battles, and badge tracking""" def get_commands(self): - return ["gym"] + return ["gym", "forfeit"] async def handle_command(self, channel, nickname, command, args): if command == "gym": @@ -23,6 +23,8 @@ class GymBattles(BaseModule): await self.cmd_gym_status(channel, nickname) else: await self.cmd_gym_list(channel, nickname) + elif command == "forfeit": + await self.cmd_forfeit(channel, nickname) async def cmd_gym_list(self, channel, nickname): """List gyms in current location""" @@ -139,6 +141,14 @@ class GymBattles(BaseModule): async def start_gym_battle(self, channel, nickname, player, gym, difficulty_level, difficulty_multiplier): """Start a gym battle""" + # Check if player is already in any battle + regular_battle = await self.game_engine.battle_engine.get_active_battle(player["id"]) + gym_battle = await self.database.get_active_gym_battle(player["id"]) + + if regular_battle or gym_battle: + self.send_message(channel, f"{nickname}: You're already in a battle! Finish your current battle first.") + return + # Get gym team with scaling gym_team = await self.database.get_gym_team(gym["id"], difficulty_multiplier) @@ -158,21 +168,45 @@ class GymBattles(BaseModule): self.send_message(channel, f"⚔️ Difficulty: {difficulty_name} ({len(gym_team)} pets)") - # For now, simulate battle result (we'll implement actual battle mechanics later) - import random + # Start gym battle state + battle_id = await self.database.start_gym_battle(player["id"], gym["id"], difficulty_level, gym_team) - # Simple win/loss calculation based on player's active pets + # Start battle with first gym pet + first_gym_pet = gym_team[0] active_pets = await self.database.get_active_pets(player["id"]) - player_strength = sum(pet["level"] * (pet["attack"] + pet["defense"]) for pet in active_pets) - gym_strength = sum(pet["level"] * (pet["attack"] + pet["defense"]) for pet in gym_team) + player_pet = active_pets[0] # Use first active pet - # Add some randomness but favor player slightly for now - win_chance = min(0.85, max(0.15, player_strength / (gym_strength * 0.8))) + # Create gym pet in wild pet format for battle engine + gym_pet_data = { + "species_name": first_gym_pet["species_name"], + "level": first_gym_pet["level"], + "type1": first_gym_pet["type1"], + "type2": first_gym_pet["type2"], + "stats": { + "hp": first_gym_pet["hp"], + "attack": first_gym_pet["attack"], + "defense": first_gym_pet["defense"], + "speed": first_gym_pet["speed"] + } + } - if random.random() < win_chance: - await self.handle_gym_victory(channel, nickname, player, gym, difficulty_level) - else: - await self.handle_gym_defeat(channel, nickname, gym, difficulty_level) + # Start the battle using existing battle engine + battle = await self.game_engine.battle_engine.start_battle(player["id"], player_pet, gym_pet_data) + + # Display first battle start + self.send_message(channel, + f"🥊 {leader} sends out {first_gym_pet['species_name']} (Lv.{first_gym_pet['level']})!") + self.send_message(channel, + f"⚔️ Your {player_pet['species_name']} (Lv.{player_pet['level']}, {battle['player_hp']}/{player_pet['max_hp']} HP) vs {first_gym_pet['species_name']} (Lv.{first_gym_pet['level']}, {battle['wild_hp']}/{first_gym_pet['hp']} HP)") + + # Show available moves + from .battle_system import BattleSystem + battle_system = BattleSystem(self.bot, self.database, self.game_engine) + moves_colored = " | ".join([ + f"{battle_system.get_move_color(move['type'])}{move['name']}\x0F" + for move in battle["available_moves"] + ]) + self.send_message(channel, f"🎯 Moves: {moves_colored} | Use !attack or !use ") async def handle_gym_victory(self, channel, nickname, player, gym, difficulty_level): """Handle gym battle victory""" @@ -277,4 +311,28 @@ class GymBattles(BaseModule): return # This will show a summary - for detailed view they can use !gym list - self.send_message(channel, f"🏆 {nickname}: Use !gym list to see all gym progress, or check your profile at: http://petz.rdx4.com/player/{nickname}") \ No newline at end of file + self.send_message(channel, f"🏆 {nickname}: Use !gym list to see all gym progress, or check your profile at: http://petz.rdx4.com/player/{nickname}") + + async def cmd_forfeit(self, channel, nickname): + """Forfeit the current gym battle""" + player = await self.require_player(channel, nickname) + if not player: + return + + # Check if player is in a gym battle + gym_battle = await self.database.get_active_gym_battle(player["id"]) + if not gym_battle: + self.send_message(channel, f"{nickname}: You're not currently in a gym battle!") + return + + # End any active regular battle first + await self.game_engine.battle_engine.end_battle(player["id"], "forfeit") + + # End gym battle + result = await self.database.end_gym_battle(player["id"], victory=False) + + self.send_message(channel, f"🏳️ {nickname} forfeited the gym battle!") + self.send_message(channel, + f"{gym_battle['badge_icon']} {gym_battle['leader_name']}: \"Sometimes retreat is the wisest strategy. Come back when you're ready!\"") + self.send_message(channel, + f"💡 {nickname}: Train your pets and try again. You can challenge the gym anytime!") \ No newline at end of file diff --git a/src/database.py b/src/database.py index e6a767a..cbc9437 100644 --- a/src/database.py +++ b/src/database.py @@ -256,6 +256,21 @@ class Database: ) """) + await db.execute(""" + CREATE TABLE IF NOT EXISTS active_gym_battles ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + player_id INTEGER NOT NULL, + gym_id INTEGER NOT NULL, + difficulty_level INTEGER NOT NULL, + current_pet_index INTEGER DEFAULT 0, + gym_team_data TEXT NOT NULL, + battle_status TEXT DEFAULT 'active', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (player_id) REFERENCES players (id), + FOREIGN KEY (gym_id) REFERENCES gyms (id) + ) + """) + await db.commit() async def get_player(self, nickname: str) -> Optional[Dict]: @@ -968,4 +983,117 @@ class Database: )) await db.commit() - print("Gyms initialized from config") \ No newline at end of file + print("Gyms initialized from config") + + async def start_gym_battle(self, player_id: int, gym_id: int, difficulty_level: int, gym_team: List[Dict]) -> int: + """Start a new gym battle""" + import json + gym_team_json = json.dumps(gym_team) + + async with aiosqlite.connect(self.db_path) as db: + # End any existing gym battle for this player + await db.execute(""" + UPDATE active_gym_battles + SET battle_status = 'ended' + WHERE player_id = ? AND battle_status = 'active' + """, (player_id,)) + + # Create new gym battle + cursor = await db.execute(""" + INSERT INTO active_gym_battles + (player_id, gym_id, difficulty_level, current_pet_index, gym_team_data) + VALUES (?, ?, ?, 0, ?) + """, (player_id, gym_id, difficulty_level, gym_team_json)) + + battle_id = cursor.lastrowid + await db.commit() + return battle_id + + async def get_active_gym_battle(self, player_id: int) -> Optional[Dict]: + """Get player's active gym battle""" + async with aiosqlite.connect(self.db_path) as db: + db.row_factory = aiosqlite.Row + cursor = await db.execute(""" + SELECT agb.*, g.name as gym_name, g.leader_name, g.badge_icon + FROM active_gym_battles agb + JOIN gyms g ON agb.gym_id = g.id + WHERE agb.player_id = ? AND agb.battle_status = 'active' + ORDER BY agb.id DESC LIMIT 1 + """, (player_id,)) + + row = await cursor.fetchone() + if row: + battle_data = dict(row) + # Parse gym team data + import json + battle_data["gym_team"] = json.loads(battle_data["gym_team_data"]) + return battle_data + return None + + async def advance_gym_battle(self, player_id: int) -> bool: + """Advance to next pet in gym battle""" + async with aiosqlite.connect(self.db_path) as db: + # Get current battle + cursor = await db.execute(""" + SELECT current_pet_index, gym_team_data + FROM active_gym_battles + WHERE player_id = ? AND battle_status = 'active' + """, (player_id,)) + + battle = await cursor.fetchone() + if not battle: + return False + + import json + gym_team = json.loads(battle[1]) + current_index = battle[0] + + # Check if there are more pets + if current_index + 1 >= len(gym_team): + return False # No more pets + + # Advance to next pet + await db.execute(""" + UPDATE active_gym_battles + SET current_pet_index = current_pet_index + 1 + WHERE player_id = ? AND battle_status = 'active' + """, (player_id,)) + + await db.commit() + return True + + async def end_gym_battle(self, player_id: int, victory: bool = False) -> Optional[Dict]: + """End gym battle and return final status""" + async with aiosqlite.connect(self.db_path) as db: + # Get battle info before ending it + cursor = await db.execute(""" + SELECT agb.*, g.name as gym_name + FROM active_gym_battles agb + JOIN gyms g ON agb.gym_id = g.id + WHERE agb.player_id = ? AND agb.battle_status = 'active' + """, (player_id,)) + + battle = await cursor.fetchone() + if not battle: + return None + + # End the battle + await db.execute(""" + UPDATE active_gym_battles + SET battle_status = 'completed' + WHERE player_id = ? AND battle_status = 'active' + """, (player_id,)) + + result = { + "gym_id": battle[2], + "gym_name": battle[9], + "difficulty_level": battle[3], + "victory": victory + } + + # Record victory if successful + if victory: + await self.record_gym_victory(player_id, battle[2]) + + await db.commit() + return result \ No newline at end of file