Initial commit: Complete PetBot IRC Game

🎮 Features implemented:
- Pokemon-style pet collection and battles
- Multi-location exploration system
- Dynamic weather with background updates
- Achievement system with location unlocks
- Web dashboard for player stats
- Modular command system
- Async database with SQLite
- PM flood prevention
- Persistent player data

🌤️ Weather System:
- 6 weather types with spawn modifiers
- 30min-3hour dynamic durations
- Background task for automatic updates
- Location-specific weather patterns

🐛 Recent Bug Fixes:
- Database persistence on restart
- Player page SQLite row conversion
- Achievement count calculations
- Travel requirement messages
- Battle move color coding
- Locations page display

🔧 Generated with Claude Code
🤖 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
megaproxy 2025-07-13 23:57:39 +01:00
commit 47f160a295
31 changed files with 6235 additions and 0 deletions

18
modules/__init__.py Normal file
View file

@ -0,0 +1,18 @@
#!/usr/bin/env python3
"""PetBot modules package"""
from .core_commands import CoreCommands
from .exploration import Exploration
from .battle_system import BattleSystem
from .pet_management import PetManagement
from .achievements import Achievements
from .admin import Admin
__all__ = [
'CoreCommands',
'Exploration',
'BattleSystem',
'PetManagement',
'Achievements',
'Admin'
]

32
modules/achievements.py Normal file
View file

@ -0,0 +1,32 @@
#!/usr/bin/env python3
"""Achievements commands module for PetBot"""
from .base_module import BaseModule
class Achievements(BaseModule):
"""Handles achievements display and tracking"""
def get_commands(self):
return ["achievements"]
async def handle_command(self, channel, nickname, command, args):
if command == "achievements":
await self.cmd_achievements(channel, nickname)
async def cmd_achievements(self, channel, nickname):
"""Show player achievements"""
player = await self.require_player(channel, nickname)
if not player:
return
achievements = await self.database.get_player_achievements(player["id"])
if achievements:
self.send_message(channel, f"🏆 {nickname}'s Achievements:")
for achievement in achievements[:5]: # Show last 5 achievements
self.send_message(channel, f"{achievement['name']}: {achievement['description']}")
if len(achievements) > 5:
self.send_message(channel, f"... and {len(achievements) - 5} more!")
else:
self.send_message(channel, f"{nickname}: No achievements yet! Keep exploring and catching pets to unlock new areas!")

30
modules/admin.py Normal file
View file

@ -0,0 +1,30 @@
#!/usr/bin/env python3
"""Admin commands module for PetBot"""
from .base_module import BaseModule
class Admin(BaseModule):
"""Handles admin-only commands like reload"""
def get_commands(self):
return ["reload"]
async def handle_command(self, channel, nickname, command, args):
if command == "reload":
await self.cmd_reload(channel, nickname)
async def cmd_reload(self, channel, nickname):
"""Reload bot modules (megasconed only)"""
if nickname.lower() != "megasconed":
self.send_message(channel, f"{nickname}: Access denied. Admin command.")
return
try:
# Trigger module reload in main bot
success = await self.bot.reload_modules()
if success:
self.send_message(channel, f"{nickname}: ✅ Modules reloaded successfully!")
else:
self.send_message(channel, f"{nickname}: ❌ Module reload failed!")
except Exception as e:
self.send_message(channel, f"{nickname}: ❌ Reload error: {str(e)}")

43
modules/base_module.py Normal file
View file

@ -0,0 +1,43 @@
#!/usr/bin/env python3
"""Base module class for PetBot command modules"""
import asyncio
from abc import ABC, abstractmethod
class BaseModule(ABC):
"""Base class for all PetBot modules"""
def __init__(self, bot, database, game_engine):
self.bot = bot
self.database = database
self.game_engine = game_engine
@abstractmethod
def get_commands(self):
"""Return list of commands this module handles"""
pass
@abstractmethod
async def handle_command(self, channel, nickname, command, args):
"""Handle a command for this module"""
pass
def send_message(self, target, message):
"""Send message through the bot"""
self.bot.send_message(target, message)
def send_pm(self, nickname, message):
"""Send private message to user"""
self.bot.send_message(nickname, message)
async def get_player(self, nickname):
"""Get player from database"""
return await self.database.get_player(nickname)
async def require_player(self, channel, nickname):
"""Get player or send start message if not found"""
player = await self.get_player(nickname)
if not player:
self.send_message(channel, f"{nickname}: Use !start to begin your journey first!")
return None
return player

192
modules/battle_system.py Normal file
View file

@ -0,0 +1,192 @@
#!/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(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"]:
if result["winner"] == "player":
self.send_message(channel, f"🎉 {nickname}: You won the battle!")
# Remove encounter since battle is over
if player["id"] in self.bot.active_encounters:
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
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}")

44
modules/core_commands.py Normal file
View file

@ -0,0 +1,44 @@
#!/usr/bin/env python3
"""Core commands module for PetBot"""
from .base_module import BaseModule
class CoreCommands(BaseModule):
"""Handles basic bot commands like start, help, stats"""
def get_commands(self):
return ["help", "start", "stats"]
async def handle_command(self, channel, nickname, command, args):
if command == "help":
await self.cmd_help(channel, nickname)
elif command == "start":
await self.cmd_start(channel, nickname)
elif command == "stats":
await self.cmd_stats(channel, nickname, args)
async def cmd_help(self, channel, nickname):
"""Send help URL to prevent rate limiting"""
self.send_message(channel, f"{nickname}: Complete command reference available at: http://localhost:8080/help")
async def cmd_start(self, channel, nickname):
"""Start a new player"""
player = await self.get_player(nickname)
if player:
self.send_message(channel, f"{nickname}: You already have an account! Use !team to see your pets.")
return
player_id = await self.database.create_player(nickname)
starter_pet = await self.game_engine.give_starter_pet(player_id)
self.send_message(channel,
f"🎉 {nickname}: Welcome to the world of pets! You received a Level {starter_pet['level']} {starter_pet['species_name']}! You are now in Starter Town.")
async def cmd_stats(self, channel, nickname, args):
"""Show player statistics"""
player = await self.require_player(channel, nickname)
if not player:
return
self.send_message(channel,
f"📊 {nickname}: Level {player['level']} | {player['experience']} XP | ${player['money']}")

225
modules/exploration.py Normal file
View file

@ -0,0 +1,225 @@
#!/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"] == "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']}"
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
destination = " ".join(args).title() # Normalize to Title Case
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
# Check if player can access this location
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(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)
# 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']}")
# 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:
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}")

149
modules/pet_management.py Normal file
View file

@ -0,0 +1,149 @@
#!/usr/bin/env python3
"""Pet management commands module for PetBot"""
from .base_module import BaseModule
class PetManagement(BaseModule):
"""Handles team, pets, and future pet management commands"""
def get_commands(self):
return ["team", "pets", "activate", "deactivate", "swap"]
async def handle_command(self, channel, nickname, command, args):
if command == "team":
await self.cmd_team(channel, nickname)
elif command == "pets":
await self.cmd_pets(channel, nickname)
elif command == "activate":
await self.cmd_activate(channel, nickname, args)
elif command == "deactivate":
await self.cmd_deactivate(channel, nickname, args)
elif command == "swap":
await self.cmd_swap(channel, nickname, args)
async def cmd_team(self, channel, nickname):
"""Show active pets (channel display)"""
player = await self.require_player(channel, nickname)
if not player:
return
pets = await self.database.get_player_pets(player["id"], active_only=False)
if not pets:
self.send_message(channel, f"{nickname}: You don't have any pets! Use !catch to find some.")
return
# Show active pets first, then others
active_pets = [pet for pet in pets if pet.get("is_active")]
inactive_pets = [pet for pet in pets if not pet.get("is_active")]
team_info = []
# Active pets with star
for pet in active_pets:
name = pet["nickname"] or pet["species_name"]
team_info.append(f"{name} (Lv.{pet['level']}) - {pet['hp']}/{pet['max_hp']} HP")
# Inactive pets
for pet in inactive_pets[:5]: # Show max 5 inactive
name = pet["nickname"] or pet["species_name"]
team_info.append(f"{name} (Lv.{pet['level']}) - {pet['hp']}/{pet['max_hp']} HP")
if len(inactive_pets) > 5:
team_info.append(f"... and {len(inactive_pets) - 5} more in storage")
self.send_message(channel, f"🐾 {nickname}'s team: " + " | ".join(team_info))
async def cmd_pets(self, channel, nickname):
"""Show link to pet collection web page"""
player = await self.require_player(channel, nickname)
if not player:
return
# Send URL to player's profile page instead of PM spam
self.send_message(channel, f"{nickname}: View your complete pet collection at: http://localhost:8080/player/{nickname}")
async def cmd_activate(self, channel, nickname, args):
"""Activate a pet for battle (PM only)"""
# Redirect to PM for privacy
if not args:
self.send_pm(nickname, "Usage: !activate <pet_name>")
self.send_message(channel, f"{nickname}: Pet activation instructions sent via PM!")
return
player = await self.require_player(channel, nickname)
if not player:
return
pet_name = " ".join(args)
result = await self.database.activate_pet(player["id"], pet_name)
if result["success"]:
pet = result["pet"]
display_name = pet["nickname"] or pet["species_name"]
self.send_pm(nickname, f"{display_name} is now active for battle!")
self.send_message(channel, f"{nickname}: Pet activated successfully!")
else:
self.send_pm(nickname, f"{result['error']}")
self.send_message(channel, f"{nickname}: Pet activation failed - check PM for details!")
async def cmd_deactivate(self, channel, nickname, args):
"""Deactivate a pet to storage (PM only)"""
# Redirect to PM for privacy
if not args:
self.send_pm(nickname, "Usage: !deactivate <pet_name>")
self.send_message(channel, f"{nickname}: Pet deactivation instructions sent via PM!")
return
player = await self.require_player(channel, nickname)
if not player:
return
pet_name = " ".join(args)
result = await self.database.deactivate_pet(player["id"], pet_name)
if result["success"]:
pet = result["pet"]
display_name = pet["nickname"] or pet["species_name"]
self.send_pm(nickname, f"📦 {display_name} moved to storage!")
self.send_message(channel, f"{nickname}: Pet deactivated successfully!")
else:
self.send_pm(nickname, f"{result['error']}")
self.send_message(channel, f"{nickname}: Pet deactivation failed - check PM for details!")
async def cmd_swap(self, channel, nickname, args):
"""Swap active/storage status of two pets (PM only)"""
# Redirect to PM for privacy
if len(args) < 2:
self.send_pm(nickname, "Usage: !swap <pet1> <pet2>")
self.send_pm(nickname, "Example: !swap Flamey Aqua")
self.send_message(channel, f"{nickname}: Pet swap instructions sent via PM!")
return
player = await self.require_player(channel, nickname)
if not player:
return
# Handle multi-word pet names by splitting on first space vs last space
if len(args) == 2:
pet1_name, pet2_name = args
else:
# For more complex parsing, assume equal split
mid_point = len(args) // 2
pet1_name = " ".join(args[:mid_point])
pet2_name = " ".join(args[mid_point:])
result = await self.database.swap_pets(player["id"], pet1_name, pet2_name)
if result["success"]:
pet1 = result["pet1"]
pet2 = result["pet2"]
pet1_display = pet1["nickname"] or pet1["species_name"]
pet2_display = pet2["nickname"] or pet2["species_name"]
self.send_pm(nickname, f"🔄 Swap complete!")
self.send_pm(nickname, f"{pet1_display}{result['pet1_now']}")
self.send_pm(nickname, f"{pet2_display}{result['pet2_now']}")
self.send_message(channel, f"{nickname}: Pet swap completed!")
else:
self.send_pm(nickname, f"{result['error']}")
self.send_message(channel, f"{nickname}: Pet swap failed - check PM for details!")