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>
367 lines
No EOL
17 KiB
Python
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) |