Petbot/modules/gym_battles.py
megaproxy 710ff5ac9c Implement interactive gym battle system with full battle engine integration
**NEW FEATURES:**
- Full 3-pet gym battles with turn-based combat
- Interactive attack selection (\!attack <move>)
- Item usage during gym battles (\!use <item>)
- 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 <move>
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 <noreply@anthropic.com>
2025-07-14 13:08:25 +01:00

338 lines
No EOL
15 KiB
Python

#!/usr/bin/env python3
"""Gym battle module for PetBot"""
from .base_module import BaseModule
class GymBattles(BaseModule):
"""Handles gym challenges, battles, and badge tracking"""
def get_commands(self):
return ["gym", "forfeit"]
async def handle_command(self, channel, nickname, command, args):
if command == "gym":
if not args:
await self.cmd_gym_list(channel, nickname)
elif args[0] == "list":
await self.cmd_gym_list_all(channel, nickname)
elif args[0] == "challenge":
await self.cmd_gym_challenge(channel, nickname, args[1:])
elif args[0] == "info":
await self.cmd_gym_info(channel, nickname, args[1:])
elif args[0] == "status":
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"""
player = await self.require_player(channel, nickname)
if not player:
return
# Get player's current location
location = await self.database.get_player_location(player["id"])
if not location:
self.send_message(channel, f"{nickname}: You are not in a valid location!")
return
# Get gyms in current location
gyms = await self.database.get_gyms_in_location(location["id"], player["id"])
if not gyms:
self.send_message(channel, f"🏛️ {nickname}: No gyms found in {location['name']}.")
return
self.send_message(channel, f"🏛️ Gyms in {location['name']}:")
for gym in gyms:
victories = gym.get("victories", 0)
if victories == 0:
status = "Not challenged"
difficulty = "Beginner"
else:
status = f"{victories} victories"
difficulty = f"Level {victories + 1}"
badge_icon = gym["badge_icon"]
leader = gym["leader_name"]
theme = gym["theme"]
self.send_message(channel,
f" {badge_icon} {gym['name']} - Leader: {leader} ({theme})")
self.send_message(channel,
f" Status: {status} | Next difficulty: {difficulty}")
self.send_message(channel,
f"💡 Use '!gym challenge \"<gym name>\"' to battle!")
async def cmd_gym_list_all(self, channel, nickname):
"""List all gyms across all locations"""
player = await self.require_player(channel, nickname)
if not player:
return
# Get all locations and their gyms
all_locations = await self.database.get_all_locations()
self.send_message(channel, f"🗺️ {nickname}: All Gym Locations:")
total_badges = 0
for location in all_locations:
gyms = await self.database.get_gyms_in_location(location["id"], player["id"])
if gyms:
for gym in gyms:
victories = gym.get("victories", 0)
status_icon = "" if victories > 0 else ""
if victories > 0:
total_badges += 1
self.send_message(channel,
f" {status_icon} {location['name']}: {gym['name']} ({gym['theme']}) - {victories} victories")
self.send_message(channel, f"🏆 Total badges earned: {total_badges}")
async def cmd_gym_challenge(self, channel, nickname, args):
"""Challenge a gym"""
if not args:
self.send_message(channel, f"{nickname}: Specify a gym to challenge! Example: !gym challenge \"Forest Guardian\"")
return
player = await self.require_player(channel, nickname)
if not player:
return
# Get player's current location first
location = await self.database.get_player_location(player["id"])
if not location:
self.send_message(channel, f"{nickname}: You are not in a valid location! Use !travel to go somewhere first.")
return
gym_name = " ".join(args).strip('"')
# Look for gym in player's current location (case-insensitive)
gym = await self.database.get_gym_by_name_in_location(gym_name, location["id"])
if not gym:
# List available gyms in current location for helpful error message
available_gyms = await self.database.get_gyms_in_location(location["id"])
if available_gyms:
gym_list = ", ".join([f'"{g["name"]}"' for g in available_gyms])
self.send_message(channel, f"{nickname}: No gym named '{gym_name}' found in {location['name']}! Available gyms: {gym_list}")
else:
self.send_message(channel, f"{nickname}: No gyms found in {location['name']}! Try traveling to a different location.")
return
# Check if player has active pets
active_pets = await self.database.get_active_pets(player["id"])
if not active_pets:
self.send_message(channel, f"{nickname}: You need at least one active pet to challenge a gym! Use !activate <pet> first.")
return
# Get player's gym progress
progress = await self.database.get_player_gym_progress(player["id"], gym["id"])
difficulty_level = (progress["victories"] if progress else 0) + 1
difficulty_multiplier = 1.0 + (difficulty_level - 1) * 0.2 # 20% increase per victory
# Start gym battle
await self.start_gym_battle(channel, nickname, player, gym, difficulty_level, difficulty_multiplier)
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)
if not gym_team:
self.send_message(channel, f"{nickname}: {gym['name']} gym has no team configured!")
return
# Display battle start
badge_icon = gym["badge_icon"]
leader = gym["leader_name"]
difficulty_name = f"Level {difficulty_level}" if difficulty_level > 1 else "Beginner"
self.send_message(channel,
f"🏛️ {nickname} challenges the {gym['name']} gym!")
self.send_message(channel,
f"{badge_icon} Leader {leader}: \"Welcome to my {gym['theme']}-type gym! Let's see what you've got!\"")
self.send_message(channel,
f"⚔️ Difficulty: {difficulty_name} ({len(gym_team)} pets)")
# Start gym battle state
battle_id = await self.database.start_gym_battle(player["id"], gym["id"], difficulty_level, gym_team)
# Start battle with first gym pet
first_gym_pet = gym_team[0]
active_pets = await self.database.get_active_pets(player["id"])
player_pet = active_pets[0] # Use first active pet
# 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"]
}
}
# 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 <move> or !use <item>")
async def handle_gym_victory(self, channel, nickname, player, gym, difficulty_level):
"""Handle gym battle victory"""
# Record victory in database
result = await self.database.record_gym_victory(player["id"], gym["id"])
badge_icon = gym["badge_icon"]
leader = gym["leader_name"]
self.send_message(channel, f"🎉 {nickname} defeats the {gym['name']} gym!")
if result["is_first_victory"]:
# First time victory - award badge
self.send_message(channel,
f"{badge_icon} Leader {leader}: \"Impressive! You've earned the {gym['badge_name']}!\"")
self.send_message(channel,
f"🏆 {nickname} earned the {gym['badge_name']} {badge_icon}!")
# Award bonus rewards for first victory
money_reward = 500 + (difficulty_level * 100)
exp_reward = 200 + (difficulty_level * 50)
else:
# Repeat victory
self.send_message(channel,
f"{badge_icon} Leader {leader}: \"Well fought! Your skills keep improving!\"")
money_reward = 200 + (difficulty_level * 50)
exp_reward = 100 + (difficulty_level * 25)
# Award rewards (we'll implement this when we have currency/exp systems)
victories = result["victories"]
next_difficulty = result["next_difficulty"]
self.send_message(channel,
f"💰 Rewards: ${money_reward} | 🌟 {exp_reward} EXP")
self.send_message(channel,
f"📊 {gym['name']} record: {victories} victories | Next challenge: Level {next_difficulty}")
async def handle_gym_defeat(self, channel, nickname, gym, difficulty_level):
"""Handle gym battle defeat"""
badge_icon = gym["badge_icon"]
leader = gym["leader_name"]
self.send_message(channel, f"💥 {nickname} was defeated by the {gym['name']} gym!")
self.send_message(channel,
f"{badge_icon} Leader {leader}: \"Good battle! Train more and come back stronger!\"")
self.send_message(channel,
f"💡 Tip: Level up your pets, get better items, or try a different strategy!")
async def cmd_gym_info(self, channel, nickname, args):
"""Get detailed information about a gym"""
if not args:
self.send_message(channel, f"{nickname}: Specify a gym name! Example: !gym info \"Forest Guardian\"")
return
player = await self.require_player(channel, nickname)
if not player:
return
gym_name = " ".join(args).strip('"')
# First try to find gym in player's current location
location = await self.database.get_player_location(player["id"])
gym = None
if location:
gym = await self.database.get_gym_by_name_in_location(gym_name, location["id"])
# If not found in current location, search globally
if not gym:
gym = await self.database.get_gym_by_name(gym_name)
if not gym:
self.send_message(channel, f"{nickname}: Gym '{gym_name}' not found!")
return
# Get gym team info
gym_team = await self.database.get_gym_team(gym["id"])
badge_icon = gym["badge_icon"]
self.send_message(channel, f"🏛️ {gym['name']} Gym Information:")
self.send_message(channel, f"📍 Location: {gym['location_name']}")
self.send_message(channel, f"👤 Leader: {gym['leader_name']}")
self.send_message(channel, f"🎯 Theme: {gym['theme']}-type")
self.send_message(channel, f"📝 {gym['description']}")
self.send_message(channel, f"🏆 Badge: {gym['badge_name']} {badge_icon}")
if gym_team:
self.send_message(channel, f"⚔️ Team ({len(gym_team)} pets):")
for pet in gym_team:
type_str = pet["type1"]
if pet["type2"]:
type_str += f"/{pet['type2']}"
self.send_message(channel,
f" • Level {pet['level']} {pet['species_name']} ({type_str})")
async def cmd_gym_status(self, channel, nickname):
"""Show player's overall gym progress"""
player = await self.require_player(channel, nickname)
if not player:
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}")
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!")