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>
This commit is contained in:
megaproxy 2025-07-14 13:08:25 +01:00
parent dc49e5f9c9
commit 710ff5ac9c
3 changed files with 298 additions and 24 deletions

View file

@ -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}")
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 <move> or !use <item>")
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!")

View file

@ -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 <move> or !use <item>")
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}")
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!")

View file

@ -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")
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