🎮 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>
192 lines
No EOL
8.5 KiB
Python
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}") |