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

394 lines
No EOL
18 KiB
Python

#!/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", "nickname", "heal", "teamname", "teamswap", "teamlist", "activeteam", "verifyteamswap"]
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 == "nickname":
await self.cmd_nickname(channel, nickname, args)
elif command == "heal":
await self.cmd_heal(channel, nickname)
elif command == "teamname":
await self.cmd_teamname(channel, nickname, args)
elif command == "teamswap":
await self.cmd_teamswap(channel, nickname, args)
elif command == "teamlist":
await self.cmd_teamlist(channel, nickname)
elif command == "activeteam":
await self.cmd_activeteam(channel, nickname)
elif command == "verifyteamswap":
await self.cmd_verifyteamswap(channel, nickname, args)
async def cmd_team(self, channel, nickname):
"""Redirect player to their team builder page"""
player = await self.require_player(channel, nickname)
if not player:
return
# Redirect to web interface for team management
self.send_message(channel, f"⚔️ {nickname}: Manage your team at: http://petz.rdx4.com/teambuilder/{nickname}")
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://petz.rdx4.com/player/{nickname}#pets")
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(self.normalize_input(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"]
position = result.get("team_position", "?")
self.send_pm(nickname, f"{display_name} is now active for battle! Team position: {position}")
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(self.normalize_input(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_nickname(self, channel, nickname, args):
"""Set a nickname for a pet"""
if len(args) < 2:
self.send_message(channel, f"{nickname}: Usage: !nickname <pet> <new_nickname>")
self.send_message(channel, f"Example: !nickname Charmander Flamey")
return
player = await self.require_player(channel, nickname)
if not player:
return
# Split args into pet identifier and new nickname
pet_identifier = self.normalize_input(args[0])
new_nickname = " ".join(args[1:])
result = await self.database.set_pet_nickname(player["id"], pet_identifier, new_nickname)
if result["success"]:
old_name = result["old_name"]
new_name = result["new_nickname"]
self.send_message(channel, f"{nickname}: {old_name} is now nicknamed '{new_name}'!")
else:
self.send_message(channel, f"{nickname}: {result['error']}")
async def cmd_heal(self, channel, nickname):
"""Heal active pets (available to all users with 1-hour cooldown)"""
try:
player = await self.require_player(channel, nickname)
if not player:
return
# Check cooldown
from datetime import datetime, timedelta
last_heal = await self.database.get_last_heal_time(player["id"])
if last_heal:
time_since_heal = datetime.now() - last_heal
if time_since_heal < timedelta(hours=1):
remaining = timedelta(hours=1) - time_since_heal
minutes_remaining = int(remaining.total_seconds() / 60)
self.send_message(channel, f"{nickname}: Heal command is on cooldown! {minutes_remaining} minutes remaining.")
return
# Get active pets
active_pets = await self.database.get_active_pets(player["id"])
if not active_pets:
self.send_message(channel, f"{nickname}: You don't have any active pets to heal!")
return
# Count how many pets need healing
pets_healed = 0
for pet in active_pets:
if pet["hp"] < pet["max_hp"]:
# Heal pet to full HP
await self.database.update_pet_hp(pet["id"], pet["max_hp"])
pets_healed += 1
if pets_healed == 0:
self.send_message(channel, f"{nickname}: All your active pets are already at full health!")
return
# Update cooldown
await self.database.update_last_heal_time(player["id"])
self.send_message(channel, f"💊 {nickname}: Healed {pets_healed} pet{'s' if pets_healed != 1 else ''} to full health! Next heal available in 1 hour.")
except Exception as e:
self.send_message(channel, f"{nickname}: ❌ Error with heal command: {str(e)}")
async def cmd_teamname(self, channel, nickname, args):
"""Redirect player to team builder for team naming"""
player = await self.require_player(channel, nickname)
if not player:
return
# Redirect to web interface for team management
self.send_message(channel, f"🏷️ {nickname}: Rename your teams at: http://petz.rdx4.com/teambuilder/{nickname}")
self.send_message(channel, f"💡 Click on team names to rename them with secure PIN verification!")
async def cmd_teamswap(self, channel, nickname, args):
"""Switch active team to specified slot with PIN verification"""
if not args:
self.send_message(channel, f"{nickname}: Usage: !teamswap <slot>")
self.send_message(channel, f"Example: !teamswap 2")
self.send_message(channel, f"Slots: 1-3")
return
player = await self.require_player(channel, nickname)
if not player:
return
try:
slot = int(args[0])
if slot < 1 or slot > 3:
self.send_message(channel, f"{nickname}: Invalid slot! Must be 1, 2, or 3")
return
except ValueError:
self.send_message(channel, f"{nickname}: Slot must be a number (1-3)")
return
try:
# Check if team slot exists and has pets
config = await self.database.load_team_configuration(player["id"], slot)
if not config:
self.send_message(channel, f"{nickname}: Team slot {slot} is empty! Create a team first via the web interface")
return
# Parse team data to check if it has pets
import json
team_data = json.loads(config["team_data"]) if config["team_data"] else []
if not team_data:
self.send_message(channel, f"{nickname}: Team slot {slot} '{config['config_name']}' has no pets!")
return
# Check if this team is already active
current_active_slot = await self.database.get_active_team_slot(player["id"])
if current_active_slot == slot:
self.send_message(channel, f"{nickname}: Team slot {slot} '{config['config_name']}' is already active!")
return
# Get team management service for PIN-based swap
from src.team_management import TeamManagementService
from src.pin_authentication import PinAuthenticationService
pin_service = PinAuthenticationService(self.database, self.bot)
team_service = TeamManagementService(self.database, pin_service)
# Request team swap with PIN verification
swap_result = await team_service.request_team_swap(player["id"], nickname, slot)
if swap_result["success"]:
pet_count = len(team_data)
self.send_message(channel, f"🔐 {nickname}: PIN sent for swapping to '{config['config_name']}' ({pet_count} pets). Check your PM!")
else:
self.send_message(channel, f"{nickname}: {swap_result.get('error', 'Failed to request team swap')}")
except Exception as e:
self.logger.error(f"Error in teamswap command: {e}")
self.send_message(channel, f"{nickname}: Error processing team swap request")
async def cmd_teamlist(self, channel, nickname):
"""Show all team slots with names and pet names"""
player = await self.require_player(channel, nickname)
if not player:
return
try:
import json
# Get team configurations directly from database
team_configs = await self.database.get_player_team_configurations(player["id"])
current_active_slot = await self.database.get_active_team_slot(player["id"])
# Build team list display
team_lines = [f"📋 {nickname}'s Teams:"]
for slot in range(1, 4):
# Find config for this slot
config = next((c for c in team_configs if c.get("slot") == slot), None)
if config and config.get("team_data"):
team_name = config["name"]
team_data = config["team_data"]
# Get pet names from team data
pet_names = []
if isinstance(team_data, list):
# New format: list of pet objects (already fetched by get_player_team_configurations)
for pet in team_data:
if pet and isinstance(pet, dict):
display_name = pet.get("nickname") or pet.get("species_name", "Unknown")
pet_names.append(display_name)
elif isinstance(team_data, dict):
# Old format: dict with positions containing pet IDs
for pos in sorted(team_data.keys()):
pet_id = team_data[pos]
if pet_id:
pet = await self.database.get_pet_by_id(pet_id)
if pet:
display_name = pet.get("nickname") or pet.get("species_name", "Unknown")
pet_names.append(display_name)
# Mark active team
active_marker = " 🟢" if current_active_slot == slot else ""
if pet_names:
pets_text = " - ".join(pet_names)
team_lines.append(f" {slot}. {team_name} - {pets_text}{active_marker}")
else:
team_lines.append(f" {slot}. {team_name} - empty{active_marker}")
else:
team_lines.append(f" {slot}. Team {slot} - empty")
team_lines.append("")
team_lines.append("Commands: !teamswap <slot> | Web: http://petz.rdx4.com/teambuilder/" + nickname)
# Send each line separately to avoid IRC length limits
for line in team_lines:
self.send_message(channel, line)
except Exception as e:
self.logger.error(f"Error in teamlist command: {e}")
self.send_message(channel, f"{nickname}: Error loading team list")
async def cmd_activeteam(self, channel, nickname):
"""Show current active team details"""
player = await self.require_player(channel, nickname)
if not player:
return
try:
# Get active team
active_pets = await self.database.get_active_team(player["id"])
current_slot = await self.database.get_active_team_slot(player["id"])
if not active_pets:
self.send_message(channel, f"{nickname}: You don't have an active team! Use !teamswap or the web interface to set one")
return
# Get team name if it's from a saved configuration
team_name = "Active Team"
if current_slot:
config = await self.database.load_team_configuration(player["id"], current_slot)
if config:
team_name = config["config_name"]
# Build active team display
team_lines = [f"⚔️ {nickname}'s {team_name}:"]
for i, pet in enumerate(active_pets, 1):
display_name = pet.get("nickname") or pet.get("species_name", "Unknown")
level = pet.get("level", 1)
hp = pet.get("hp", 0)
max_hp = pet.get("max_hp", 100)
# Health status indicator
health_pct = (hp / max_hp * 100) if max_hp > 0 else 0
if health_pct >= 75:
health_icon = "💚"
elif health_pct >= 50:
health_icon = "💛"
elif health_pct >= 25:
health_icon = "🧡"
else:
health_icon = "❤️"
team_lines.append(f" {i}. {display_name} (Lv.{level}) {health_icon} {hp}/{max_hp}")
team_lines.append("")
team_lines.append(f"Use !heal to restore health (1hr cooldown)")
team_lines.append(f"Manage teams: http://petz.rdx4.com/teambuilder/{nickname}")
# Send team info
for line in team_lines:
self.send_message(channel, line)
except Exception as e:
self.logger.error(f"Error in activeteam command: {e}")
self.send_message(channel, f"{nickname}: Error loading active team")
async def cmd_verifyteamswap(self, channel, nickname, args):
"""Verify PIN and execute team swap"""
if not args:
self.send_message(channel, f"{nickname}: Usage: !verifyteamswap <pin>")
return
player = await self.require_player(channel, nickname)
if not player:
return
pin_code = args[0].strip()
try:
# Get team management service
from src.team_management import TeamManagementService
from src.pin_authentication import PinAuthenticationService
pin_service = PinAuthenticationService(self.database, self.bot)
team_service = TeamManagementService(self.database, pin_service)
# Verify PIN and execute team swap
result = await team_service.verify_team_swap(player["id"], pin_code)
if result["success"]:
self.send_message(channel, f"{nickname}: {result['message']}")
if 'pets_applied' in result:
self.send_message(channel, f"🔄 {result['pets_applied']} pets are now active for battle!")
else:
self.send_message(channel, f"{nickname}: {result.get('error', 'Team swap failed')}")
except Exception as e:
self.logger.error(f"Error in verifyteamswap command: {e}")
self.send_message(channel, f"{nickname}: Error processing team swap verification")