Petbot/modules/battle_system.py
megaproxy 47f160a295 Initial commit: Complete PetBot IRC Game
🎮 Features implemented:
- Pokemon-style pet collection and battles
- Multi-location exploration system
- Dynamic weather with background updates
- Achievement system with location unlocks
- Web dashboard for player stats
- Modular command system
- Async database with SQLite
- PM flood prevention
- Persistent player data

🌤️ Weather System:
- 6 weather types with spawn modifiers
- 30min-3hour dynamic durations
- Background task for automatic updates
- Location-specific weather patterns

🐛 Recent Bug Fixes:
- Database persistence on restart
- Player page SQLite row conversion
- Achievement count calculations
- Travel requirement messages
- Battle move color coding
- Locations page display

🔧 Generated with Claude Code
🤖 Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-13 23:57:39 +01:00

192 lines
No EOL
8.5 KiB
Python

#!/usr/bin/env python3
"""Battle system commands module for PetBot"""
from .base_module import BaseModule
class BattleSystem(BaseModule):
"""Handles battle, attack, flee, and moves commands"""
# IRC color codes for different move types
MOVE_TYPE_COLORS = {
'Fire': '\x0304', # Red
'Water': '\x0312', # Light Blue
'Grass': '\x0303', # Green
'Electric': '\x0308', # Yellow
'Rock': '\x0307', # Orange/Brown
'Normal': '\x0314', # Gray
'Physical': '\x0313', # Pink/Magenta (fallback for category)
'Special': '\x0310' # Cyan (fallback for category)
}
def get_commands(self):
return ["battle", "attack", "flee", "moves"]
def get_move_color(self, move_type):
"""Get IRC color code for a move type"""
return self.MOVE_TYPE_COLORS.get(move_type, '\x0312') # Default to light blue
async def handle_command(self, channel, nickname, command, args):
if command == "battle":
await self.cmd_battle(channel, nickname)
elif command == "attack":
await self.cmd_attack(channel, nickname, args)
elif command == "flee":
await self.cmd_flee(channel, nickname)
elif command == "moves":
await self.cmd_moves(channel, nickname)
async def cmd_battle(self, channel, nickname):
"""Start a battle with encountered wild pet"""
player = await self.require_player(channel, nickname)
if not player:
return
# Check if player has an active encounter
if player["id"] not in self.bot.active_encounters:
self.send_message(channel, f"{nickname}: You need to !explore first to find a pet to battle!")
return
# Check if already in battle
active_battle = await self.game_engine.battle_engine.get_active_battle(player["id"])
if active_battle:
self.send_message(channel, f"{nickname}: You're already in battle! Use !attack <move> or !flee.")
return
# Get player's active pet
pets = await self.database.get_player_pets(player["id"], active_only=True)
if not pets:
self.send_message(channel, f"{nickname}: You need an active pet to battle! Use !team to check your pets.")
return
player_pet = pets[0] # Use first active pet
wild_pet = self.bot.active_encounters[player["id"]]
# Start battle
battle = await self.game_engine.battle_engine.start_battle(player["id"], player_pet, wild_pet)
# Condensed battle start message with all info
moves_colored = " | ".join([
f"{self.get_move_color(move['type'])}{move['name']}\x0F"
for move in battle["available_moves"]
])
battle_start_msg = (f"⚔️ {nickname}: Battle! Your {player_pet['species_name']} (Lv.{player_pet['level']}, "
f"{battle['player_hp']}/{player_pet['max_hp']} HP) vs Wild {wild_pet['species_name']} "
f"(Lv.{wild_pet['level']}, {battle['wild_hp']}/{wild_pet['max_hp']} HP)")
self.send_message(channel, battle_start_msg)
self.send_message(channel, f"🎯 Moves: {moves_colored} | Use !attack <move>")
async def cmd_attack(self, channel, nickname, args):
"""Use a move in battle"""
if not args:
self.send_message(channel, f"{nickname}: Specify a move to use! Example: !attack Tackle")
return
player = await self.require_player(channel, nickname)
if not player:
return
move_name = " ".join(args).title() # Normalize to Title Case
result = await self.game_engine.battle_engine.execute_battle_turn(player["id"], move_name)
if "error" in result:
self.send_message(channel, f"{nickname}: {result['error']}")
return
# Display battle results - condensed with player names for clarity
for action in result["results"]:
# Determine attacker and target with player context
if "Wild" in action['attacker']:
attacker = f"Wild {action['attacker']}"
target_context = f"{nickname}"
else:
attacker = f"{nickname}'s {action['attacker']}"
target_context = "wild pet"
# Build condensed message with all info on one line
if action["damage"] > 0:
effectiveness_msgs = {
"super_effective": "⚡ Super effective!",
"not_very_effective": "💫 Not very effective...",
"no_effect": "❌ No effect!",
"super_effective_critical": "💥 CRIT! Super effective!",
"normal_critical": "💥 Critical hit!",
"not_very_effective_critical": "💥 Crit, not very effective..."
}
effectiveness = effectiveness_msgs.get(action["effectiveness"], "")
battle_msg = f"⚔️ {attacker} used {action['move']}{action['damage']} damage to {target_context}! {effectiveness} (HP: {action['target_hp']})"
else:
# Status move or no damage
battle_msg = f"⚔️ {attacker} used {action['move']} on {target_context}! (HP: {action['target_hp']})"
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"]]
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([
f"{self.get_move_color(move['type'])}{move['name']}\x0F"
for move in result["available_moves"]
])
self.send_message(channel, f"🎯 {nickname}'s turn! Moves: {moves_colored}")
async def cmd_flee(self, channel, nickname):
"""Attempt to flee from battle"""
player = await self.require_player(channel, nickname)
if not player:
return
success = await self.game_engine.battle_engine.flee_battle(player["id"])
if success:
self.send_message(channel, f"💨 {nickname} successfully fled from battle!")
# Remove encounter
if player["id"] in self.bot.active_encounters:
del self.bot.active_encounters[player["id"]]
else:
self.send_message(channel, f"{nickname} couldn't escape! Battle continues!")
async def cmd_moves(self, channel, nickname):
"""Show active pet's available moves"""
player = await self.require_player(channel, nickname)
if not player:
return
# Get player's active pets
pets = await self.database.get_player_pets(player["id"], active_only=True)
if not pets:
self.send_message(channel, f"{nickname}: You don't have any active pets! Use !team to check your pets.")
return
active_pet = pets[0] # Use first active pet
available_moves = self.game_engine.battle_engine.get_available_moves(active_pet)
if not available_moves:
self.send_message(channel, f"{nickname}: Your {active_pet['species_name']} has no available moves!")
return
# Limit to 4 moves max and format compactly
moves_to_show = available_moves[:4]
move_info = []
for move in moves_to_show:
power = move.get('power', 'Status')
power_str = str(power) if power != 'Status' else 'Stat'
move_colored = f"{self.get_move_color(move['type'])}{move['name']}\x0F"
move_info.append(f"{move_colored} ({move['type']}, {power_str})")
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}")