Petbot/modules/exploration.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

300 lines
No EOL
15 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"]
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)
async def cmd_explore(self, channel, nickname):
"""Explore current location"""
player = await self.require_player(channel, nickname)
if not player:
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, or !catch to try catching it directly!")
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
print(f"🔄 Checking all achievements for {nickname} before travel...")
# 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"])
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)