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>
300 lines
No EOL
15 KiB
Python
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) |