Petbot/modules/exploration.py
megaproxy e17705dc63 Implement comprehensive team management and fix critical bugs
Team Management Features:
- Added 6 new IRC commands: \!teamlist, \!activeteam, \!teamname, \!teamswap, \!heal, \!verifyteamswap
- \!teamlist shows teams with pet names in format "Team name - pet1 - pet2 - pet3"
- \!teamname redirects to web interface for secure PIN-based renaming
- \!teamswap enables team switching with PIN verification via IRC
- \!activeteam displays current team with health status indicators
- \!heal command with 1-hour cooldown for pet health restoration

Critical Bug Fixes:
- Fixed \!teamlist SQL binding error - handled new team data format correctly
- Fixed \!wild command duplicates - now shows unique species types only
- Removed all debug print statements and implemented proper logging
- Fixed data format inconsistencies in team management system

Production Improvements:
- Added logging infrastructure to BaseModule and core components
- Converted 45+ print statements to professional logging calls
- Database query optimization with DISTINCT for spawn deduplication
- Enhanced error handling and user feedback messages

Cross-platform Integration:
- Seamless sync between IRC commands and web interface
- PIN authentication leverages existing secure infrastructure
- Team operations maintain consistency across all interfaces

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-01 15:53:26 +00:00

352 lines
No EOL
17 KiB
Python

#!/usr/bin/env python3
"""Exploration commands module for PetBot"""
from .base_module import BaseModule
class Exploration(BaseModule):
"""Handles exploration, travel, location, weather, and wild commands"""
def get_commands(self):
return ["explore", "travel", "location", "where", "weather", "wild", "catch", "capture", "flee"]
async def handle_command(self, channel, nickname, command, args):
if command == "explore":
await self.cmd_explore(channel, nickname)
elif command == "travel":
await self.cmd_travel(channel, nickname, args)
elif command in ["location", "where"]:
await self.cmd_location(channel, nickname)
elif command == "weather":
await self.cmd_weather(channel, nickname)
elif command == "wild":
await self.cmd_wild(channel, nickname, args)
elif command in ["catch", "capture"]:
await self.cmd_catch(channel, nickname)
elif command == "flee":
await self.cmd_flee_encounter(channel, nickname)
async def cmd_explore(self, channel, nickname):
"""Explore current location"""
player = await self.require_player(channel, nickname)
if not player:
return
# Check if player has an active encounter that must be resolved first
if player["id"] in self.bot.active_encounters:
current_encounter = self.bot.active_encounters[player["id"]]
self.send_message(channel, f"{nickname}: You already have an active encounter with a wild {current_encounter['species_name']}! You must choose to !battle, !catch, or !flee before exploring again.")
return
# Check if player is in an active 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 currently in battle! Finish your battle before exploring.")
return
encounter = await self.game_engine.explore_location(player["id"])
if encounter["type"] == "error":
self.send_message(channel, f"{nickname}: {encounter['message']}")
elif encounter["type"] == "empty":
self.send_message(channel, f"🔍 {nickname}: {encounter['message']}")
elif encounter["type"] == "item_found":
self.send_message(channel, f"{nickname}: {encounter['message']}")
elif encounter["type"] == "encounter":
# Store the encounter for potential catching
self.bot.active_encounters[player["id"]] = encounter["pet"]
pet = encounter["pet"]
type_str = pet["type1"]
if pet["type2"]:
type_str += f"/{pet['type2']}"
# Record the encounter
await self.database.record_encounter(player["id"], pet["species_name"])
self.send_message(channel,
f"🐾 {nickname}: A wild Level {pet['level']} {pet['species_name']} ({type_str}) appeared in {encounter['location']}!")
self.send_message(channel, f"Choose your action: !battle to fight it, !catch to try catching it directly, or !flee to escape!")
async def cmd_travel(self, channel, nickname, args):
"""Travel to a different location"""
if not args:
self.send_message(channel, f"{nickname}: Specify where to travel! Available: Starter Town, Whispering Woods, Electric Canyon, Crystal Caves, Frozen Tundra, Dragon's Peak")
return
player = await self.require_player(channel, nickname)
if not player:
return
# Handle various input formats and normalize location names
destination_input = self.normalize_input(" ".join(args))
# Map common variations to exact location names
location_mappings = {
"starter town": "Starter Town",
"whispering woods": "Whispering Woods",
"electric canyon": "Electric Canyon",
"crystal caves": "Crystal Caves",
"frozen tundra": "Frozen Tundra",
"dragon's peak": "Dragon's Peak",
"dragons peak": "Dragon's Peak",
"dragon peak": "Dragon's Peak",
"dragons-peak": "Dragon's Peak"
}
destination = location_mappings.get(destination_input)
if not destination:
# Fall back to title case if no mapping found
destination = " ".join(self.normalize_input(args)).title()
location = await self.database.get_location_by_name(destination)
if not location:
self.send_message(channel, f"{nickname}: '{destination}' is not a valid location!")
return
# CRITICAL FIX: Check and award any outstanding achievements before checking travel requirements
# This ensures players get credit for achievements they've earned but haven't been awarded yet
# Check ALL possible achievements comprehensively
all_new_achievements = await self.game_engine.check_all_achievements(player["id"])
if all_new_achievements:
for achievement in all_new_achievements:
self.send_message(channel, f"🏆 {nickname}: Achievement unlocked: {achievement['name']}! {achievement['description']}")
# Now check if player can access this location (after awarding achievements)
missing_requirements = await self.database.get_missing_location_requirements(player["id"], location["id"])
if missing_requirements:
# Build specific message about required achievements
if len(missing_requirements) == 1:
achievement = missing_requirements[0]
self.send_message(channel, f"{nickname}: You cannot access {destination} yet! Required achievement: '{achievement['name']}' - {achievement['description']}")
else:
achievement_names = [f"'{req['name']}'" for req in missing_requirements]
self.send_message(channel, f"{nickname}: You cannot access {destination} yet! Required achievements: {', '.join(achievement_names)}. Use !achievements to see progress.")
return
# Clear any active encounters when traveling
if player["id"] in self.bot.active_encounters:
del self.bot.active_encounters[player["id"]]
await self.database.update_player_location(player["id"], location["id"])
# Show weather info
weather = await self.database.get_location_weather(location["id"])
weather_msg = ""
if weather:
weather_patterns = getattr(self.game_engine, 'weather_patterns', {})
weather_info = weather_patterns.get("weather_types", {}).get(weather["weather_type"], {})
weather_desc = weather_info.get("description", f"{weather['weather_type']} weather")
weather_msg = f" Weather: {weather['weather_type']} - {weather_desc}"
self.send_message(channel, f"🗺️ {nickname}: You traveled to {destination}. {location['description']}{weather_msg}")
async def cmd_location(self, channel, nickname):
"""Show current location"""
player = await self.require_player(channel, nickname)
if not player:
return
location = await self.database.get_player_location(player["id"])
if location:
self.send_message(channel, f"📍 {nickname}: You are currently in {location['name']}. {location['description']}")
else:
self.send_message(channel, f"{nickname}: You seem to be lost! Contact an admin.")
async def cmd_weather(self, channel, nickname):
"""Show current weather"""
player = await self.require_player(channel, nickname)
if not player:
return
location = await self.database.get_player_location(player["id"])
if not location:
self.send_message(channel, f"{nickname}: You seem to be lost!")
return
weather = await self.database.get_location_weather(location["id"])
if weather:
weather_patterns = getattr(self.game_engine, 'weather_patterns', {})
weather_info = weather_patterns.get("weather_types", {}).get(weather["weather_type"], {})
weather_desc = weather_info.get("description", f"{weather['weather_type']} weather")
self.send_message(channel, f"🌤️ {nickname}: Current weather in {location['name']}: {weather['weather_type']}")
self.send_message(channel, f"Effect: {weather_desc}")
else:
self.send_message(channel, f"🌤️ {nickname}: The weather in {location['name']} is calm with no special effects.")
async def cmd_wild(self, channel, nickname, args):
"""Show wild pets in location (defaults to current)"""
player = await self.require_player(channel, nickname)
if not player:
return
if args:
# Specific location requested
location_name = " ".join(self.normalize_input(args)).title()
else:
# Default to current location
current_location = await self.database.get_player_location(player["id"])
if not current_location:
self.send_message(channel, f"{nickname}: You seem to be lost!")
return
location_name = current_location["name"]
wild_pets = await self.game_engine.get_location_spawns(location_name)
if wild_pets:
pet_list = ", ".join([pet["name"] for pet in wild_pets])
self.send_message(channel, f"🌿 Wild pets in {location_name}: {pet_list}")
else:
self.send_message(channel, f"{nickname}: No location found called '{location_name}'")
async def cmd_catch(self, channel, nickname):
"""Catch a pet during exploration or battle"""
player = await self.require_player(channel, nickname)
if not player:
return
# Check if player is in an active battle
active_battle = await self.game_engine.battle_engine.get_active_battle(player["id"])
gym_battle = await self.database.get_active_gym_battle(player["id"])
if gym_battle:
# Can't catch pets during gym battles
self.send_message(channel, f"{nickname}: You can't catch pets during gym battles! Focus on the challenge!")
return
if active_battle:
# Catching during battle
wild_pet = active_battle["wild_pet"]
current_hp = active_battle["wild_hp"]
max_hp = wild_pet["max_hp"]
# Calculate catch rate based on remaining HP (lower HP = higher catch rate)
hp_percentage = current_hp / max_hp
base_catch_rate = 0.3 # Lower base rate than direct catch
hp_bonus = (1.0 - hp_percentage) * 0.5 # Up to 50% bonus for low HP
final_catch_rate = min(0.9, base_catch_rate + hp_bonus) # Cap at 90%
import random
if random.random() < final_catch_rate:
# Successful catch during battle
result = await self.game_engine.attempt_catch_current_location(player["id"], wild_pet)
# Record the successful catch
await self.database.record_encounter(player["id"], wild_pet["species_name"], was_caught=True)
# End the battle
await_result = await self.game_engine.battle_engine.end_battle(player["id"], "caught")
# Check for achievements
type_achievements = await self.game_engine.check_and_award_achievements(player["id"], "catch_type", "")
total_achievements = await self.game_engine.check_and_award_achievements(player["id"], "catch_total", "")
all_achievements = type_achievements + total_achievements
if all_achievements:
for achievement in all_achievements:
self.send_message(channel, f"🏆 {nickname}: Achievement unlocked: {achievement['name']}! {achievement['description']}")
# Award experience for successful catch
await self.award_catch_experience(channel, nickname, player, wild_pet)
# Remove encounter
if player["id"] in self.bot.active_encounters:
del self.bot.active_encounters[player["id"]]
self.send_message(channel, f"🎯 {nickname}: Caught the {wild_pet['species_name']} during battle! (HP: {current_hp}/{max_hp})")
else:
# Failed catch - battle continues
catch_percentage = int(final_catch_rate * 100)
self.send_message(channel, f"🎯 {nickname}: The {wild_pet['species_name']} broke free! ({catch_percentage}% catch rate) Battle continues!")
return
# Regular exploration catch (not in battle)
if player["id"] not in self.bot.active_encounters:
self.send_message(channel, f"{nickname}: You need to !explore first to find a pet, or be in battle to catch!")
return
target_pet = self.bot.active_encounters[player["id"]]
result = await self.game_engine.attempt_catch_current_location(player["id"], target_pet)
# Check for achievements after successful catch
if "Success!" in result:
# Record the successful catch
await self.database.record_encounter(player["id"], target_pet["species_name"], was_caught=True)
# Award experience for successful catch
await self.award_catch_experience(channel, nickname, player, target_pet)
type_achievements = await self.game_engine.check_and_award_achievements(player["id"], "catch_type", "")
total_achievements = await self.game_engine.check_and_award_achievements(player["id"], "catch_total", "")
all_achievements = type_achievements + total_achievements
if all_achievements:
for achievement in all_achievements:
self.send_message(channel, f"🏆 {nickname}: Achievement unlocked: {achievement['name']}! {achievement['description']}")
# Remove the encounter regardless of success
del self.bot.active_encounters[player["id"]]
self.send_message(channel, f"🎯 {nickname}: {result}")
async def award_catch_experience(self, channel, nickname, player, caught_pet):
"""Award experience to active pets for successful catch"""
active_pets = await self.database.get_active_pets(player["id"])
if not active_pets:
return
# Calculate experience for catch (less than battle victory)
base_exp = caught_pet["level"] * 5 # 5 EXP per level for catches
# Award to first active pet
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 for the catch!")
# Handle level up
if exp_result["leveled_up"]:
await self.handle_level_up_display(channel, nickname, exp_result)
async def handle_level_up_display(self, channel, nickname, exp_result):
"""Display level up information (shared with battle system)"""
from .battle_system import BattleSystem
battle_system = BattleSystem(self.bot, self.database, self.game_engine)
await battle_system.handle_level_up_display(channel, nickname, exp_result)
async def cmd_flee_encounter(self, channel, nickname):
"""Flee from an active encounter without battling"""
player = await self.require_player(channel, nickname)
if not player:
return
# Check if player has an active encounter to flee from
if player["id"] not in self.bot.active_encounters:
self.send_message(channel, f"{nickname}: You don't have an active encounter to flee from!")
return
# Check if player is in an active battle - can't flee from exploration if 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 in battle! Use the battle system's !flee command to escape combat.")
return
# Check if player is in a gym battle
gym_battle = await self.database.get_active_gym_battle(player["id"])
if gym_battle:
self.send_message(channel, f"{nickname}: You're in a gym battle! Use !forfeit to leave the gym challenge.")
return
# Get encounter details for message
encounter = self.bot.active_encounters[player["id"]]
# Remove the encounter
del self.bot.active_encounters[player["id"]]
self.send_message(channel, f"💨 {nickname}: You fled from the wild {encounter['species_name']}! You can now explore again.")
self.send_message(channel, f"💡 Use !explore to search for another encounter!")