Petbot/modules/battle_system.py
megaproxy 8e9ff2960f Implement case-insensitive command processing across all bot modules
Added normalize_input() function to BaseModule for consistent lowercase conversion of user input. Updated all command modules to use normalization for commands, arguments, pet names, location names, gym names, and item names. Players can now use any capitalization for commands and arguments.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-14 21:57:51 +01:00

367 lines
No EOL
17 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(self.normalize_input(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"]:
# Check if this is a gym battle
gym_battle = await self.database.get_active_gym_battle(player["id"])
if gym_battle:
print(f"DEBUG: Gym battle completion - player: {player['id']}, result: {result.get('winner')}")
await self.handle_gym_battle_completion(channel, nickname, player, result, gym_battle)
else:
# Regular wild battle
if result["winner"] == "player":
self.send_message(channel, f"🎉 {nickname}: You won the battle!")
# Award experience for victory
if player["id"] in self.bot.active_encounters:
wild_pet = self.bot.active_encounters[player["id"]]
await self.award_battle_experience(channel, nickname, player, wild_pet, "wild")
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
# 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:
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}")
async def handle_gym_battle_completion(self, channel, nickname, player, battle_result, gym_battle):
"""Handle completion of a gym battle turn"""
try:
if battle_result["winner"] == "player":
# Player won this individual battle
current_pet_index = gym_battle["current_pet_index"]
gym_team = gym_battle["gym_team"]
# Safety check for index bounds
if current_pet_index >= len(gym_team):
self.send_message(channel, f"{nickname}: Gym battle state error - please !forfeit and try again")
return
defeated_pet = gym_team[current_pet_index]
self.send_message(channel, f"🎉 {nickname}: You defeated {defeated_pet['species_name']}!")
# Award experience for defeating gym pet
await self.award_battle_experience(channel, nickname, player, defeated_pet, "gym")
# 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!")
except Exception as e:
self.send_message(channel, f"{nickname}: Gym battle error occurred - please !forfeit and try again")
print(f"Gym battle completion error: {e}")
import traceback
traceback.print_exc()
async def award_battle_experience(self, channel, nickname, player, defeated_pet, battle_type="wild"):
"""Award experience to active pets for battle victory"""
active_pets = await self.database.get_active_pets(player["id"])
if not active_pets:
return
# Calculate experience based on defeated pet and battle type
base_exp = self.calculate_battle_exp(defeated_pet, battle_type)
# Award to first active pet (the one that was battling)
main_pet = active_pets[0]
exp_result = await self.database.award_experience(main_pet["id"], base_exp)
if exp_result["success"]:
# Display experience gain
self.send_message(channel,
f"{exp_result['pet_name']} gained {exp_result['exp_gained']} EXP!")
# Handle level up
if exp_result["leveled_up"]:
await self.handle_level_up_display(channel, nickname, exp_result)
def calculate_battle_exp(self, defeated_pet, battle_type="wild"):
"""Calculate experience gain for defeating a pet"""
base_exp = defeated_pet["level"] * 10 # Base: 10 EXP per level
# Battle type multipliers
multipliers = {
"wild": 1.0,
"gym": 2.0, # Double EXP for gym battles
"trainer": 1.5 # Future: trainer battles
}
multiplier = multipliers.get(battle_type, 1.0)
return int(base_exp * multiplier)
async def handle_level_up_display(self, channel, nickname, exp_result):
"""Display level up information"""
levels_gained = exp_result["levels_gained"]
pet_name = exp_result["pet_name"]
if levels_gained == 1:
self.send_message(channel,
f"🎉 {pet_name} leveled up! Now level {exp_result['new_level']}!")
else:
self.send_message(channel,
f"🎉 {pet_name} gained {levels_gained} levels! Now level {exp_result['new_level']}!")
# Show stat increases
if "stat_increases" in exp_result:
stats = exp_result["stat_increases"]
stat_msg = f"📈 Stats increased: "
stat_parts = []
if stats["hp"] > 0:
stat_parts.append(f"HP +{stats['hp']}")
if stats["attack"] > 0:
stat_parts.append(f"ATK +{stats['attack']}")
if stats["defense"] > 0:
stat_parts.append(f"DEF +{stats['defense']}")
if stats["speed"] > 0:
stat_parts.append(f"SPD +{stats['speed']}")
if stat_parts:
stat_msg += " | ".join(stat_parts)
self.send_message(channel, stat_msg)