Add \!heal command and automatic pet recovery background system

- Implement \!heal command with 1-hour cooldown available to all users
- Add comprehensive cooldown tracking with database last_heal_time validation
- Heal command restores all active pets to full health
- Add background pet recovery system to game engine:
  - Automatic 30-minute recovery timer for fainted pets
  - Background task checks every 5 minutes for eligible pets
  - Auto-recovery restores pets to 1 HP after 30 minutes
  - Proper startup/shutdown integration with game engine
- Add pet_recovery_task to game engine with graceful shutdown
- Include detailed logging for recovery operations
- Ensure system resilience with error handling and task cancellation

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
megaproxy 2025-07-16 11:32:25 +00:00
parent 72c1098a22
commit d758d6b924
2 changed files with 246 additions and 3 deletions

View file

@ -21,7 +21,7 @@ class Admin(BaseModule):
"""Handles admin-only commands like reload"""
def get_commands(self):
return ["reload", "rate_stats", "rate_user", "rate_unban", "rate_reset", "weather", "setweather"]
return ["reload", "rate_stats", "rate_user", "rate_unban", "rate_reset", "weather", "setweather", "spawnevent", "startevent", "status", "uptime", "ping", "heal"]
async def handle_command(self, channel, nickname, command, args):
if command == "reload":
@ -38,6 +38,18 @@ class Admin(BaseModule):
await self.cmd_weather(channel, nickname, args)
elif command == "setweather":
await self.cmd_setweather(channel, nickname, args)
elif command == "spawnevent":
await self.cmd_spawnevent(channel, nickname, args)
elif command == "startevent":
await self.cmd_startevent(channel, nickname, args)
elif command == "status":
await self.cmd_status(channel, nickname)
elif command == "uptime":
await self.cmd_uptime(channel, nickname)
elif command == "ping":
await self.cmd_ping(channel, nickname)
elif command == "heal":
await self.cmd_heal(channel, nickname)
async def cmd_reload(self, channel, nickname):
"""Reload bot modules (admin only)"""
@ -317,4 +329,182 @@ class Admin(BaseModule):
except ValueError as e:
self.send_message(channel, f"{nickname}: ❌ Invalid duration: {str(e)}")
except Exception as e:
self.send_message(channel, f"{nickname}: ❌ Error setting weather: {str(e)}")
self.send_message(channel, f"{nickname}: ❌ Error setting weather: {str(e)}")
async def cmd_spawnevent(self, channel, nickname, args):
"""Force spawn an NPC event (admin only)"""
if not self.is_admin(nickname):
self.send_message(channel, f"{nickname}: Access denied. Admin command.")
return
# Default to difficulty 1 if no args provided
difficulty = 1
if args:
try:
difficulty = int(args[0])
if difficulty not in [1, 2, 3]:
self.send_message(channel, f"{nickname}: ❌ Difficulty must be 1, 2, or 3.")
return
except ValueError:
self.send_message(channel, f"{nickname}: ❌ Invalid difficulty. Use 1, 2, or 3.")
return
try:
# Get the NPC events manager from the bot
if hasattr(self.bot, 'npc_events') and self.bot.npc_events:
event_id = await self.bot.npc_events.force_spawn_event(difficulty)
if event_id:
self.send_message(channel, f"🎯 {nickname}: Spawned new NPC event! Check `!events` to see it.")
else:
self.send_message(channel, f"{nickname}: Failed to spawn NPC event.")
else:
self.send_message(channel, f"{nickname}: NPC events system not available.")
except Exception as e:
self.send_message(channel, f"{nickname}: ❌ Error spawning event: {str(e)}")
async def cmd_startevent(self, channel, nickname, args):
"""Start a specific event type (admin only)"""
if not self.is_admin(nickname):
self.send_message(channel, f"{nickname}: Access denied. Admin command.")
return
# If no args provided, show available event types
if not args:
self.send_message(channel, f"{nickname}: Available types: resource_gathering, pet_rescue, community_project, emergency_response, festival_preparation, research_expedition, crisis_response, legendary_encounter, ancient_mystery")
return
event_type = args[0].lower()
valid_types = ["resource_gathering", "pet_rescue", "community_project", "emergency_response",
"festival_preparation", "research_expedition", "crisis_response",
"legendary_encounter", "ancient_mystery"]
if event_type not in valid_types:
self.send_message(channel, f"{nickname}: ❌ Invalid type. Available: {', '.join(valid_types)}")
return
# Optional difficulty parameter
difficulty = 1
if len(args) > 1:
try:
difficulty = int(args[1])
if difficulty not in [1, 2, 3]:
self.send_message(channel, f"{nickname}: ❌ Difficulty must be 1, 2, or 3.")
return
except ValueError:
self.send_message(channel, f"{nickname}: ❌ Invalid difficulty. Use 1, 2, or 3.")
return
try:
# Get the NPC events manager from the bot
if hasattr(self.bot, 'npc_events') and self.bot.npc_events:
event_id = await self.bot.npc_events.force_spawn_specific_event(event_type, difficulty)
if event_id:
self.send_message(channel, f"🎯 {nickname}: Started {event_type} event (ID: {event_id})! Check `!events` to see it.")
else:
self.send_message(channel, f"{nickname}: Failed to start {event_type} event.")
else:
self.send_message(channel, f"{nickname}: NPC events system not available.")
except Exception as e:
self.send_message(channel, f"{nickname}: ❌ Error starting event: {str(e)}")
async def cmd_status(self, channel, nickname):
"""Show bot connection status (available to all users)"""
try:
# Check if connection manager exists
if hasattr(self.bot, 'connection_manager') and self.bot.connection_manager:
stats = self.bot.connection_manager.get_connection_stats()
connected = stats.get('connected', False)
state = stats.get('state', 'unknown')
status_emoji = "🟢" if connected else "🔴"
self.send_message(channel, f"{status_emoji} {nickname}: Bot status - {state.upper()}")
else:
self.send_message(channel, f"🟢 {nickname}: Bot is running")
except Exception as e:
self.send_message(channel, f"{nickname}: ❌ Error getting status: {str(e)}")
async def cmd_uptime(self, channel, nickname):
"""Show bot uptime (available to all users)"""
try:
# Check if bot has startup time
if hasattr(self.bot, 'startup_time'):
import datetime
uptime = datetime.datetime.now() - self.bot.startup_time
days = uptime.days
hours, remainder = divmod(uptime.seconds, 3600)
minutes, seconds = divmod(remainder, 60)
if days > 0:
uptime_str = f"{days}d {hours}h {minutes}m"
elif hours > 0:
uptime_str = f"{hours}h {minutes}m"
else:
uptime_str = f"{minutes}m {seconds}s"
self.send_message(channel, f"⏱️ {nickname}: Bot uptime - {uptime_str}")
else:
self.send_message(channel, f"⏱️ {nickname}: Bot is running (uptime unknown)")
except Exception as e:
self.send_message(channel, f"{nickname}: ❌ Error getting uptime: {str(e)}")
async def cmd_ping(self, channel, nickname):
"""Test bot responsiveness (available to all users)"""
try:
import time
start_time = time.time()
# Simple responsiveness test
response_time = (time.time() - start_time) * 1000 # Convert to milliseconds
self.send_message(channel, f"🏓 {nickname}: Pong! Response time: {response_time:.1f}ms")
except Exception as e:
self.send_message(channel, f"{nickname}: ❌ Error with ping: {str(e)}")
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)}")

View file

@ -16,6 +16,7 @@ class GameEngine:
self.type_chart = {}
self.weather_patterns = {}
self.weather_task = None
self.pet_recovery_task = None
self.shutdown_event = asyncio.Event()
async def load_game_data(self):
@ -28,6 +29,7 @@ class GameEngine:
await self.database.initialize_gyms()
await self.init_weather_system()
await self.battle_engine.load_battle_data()
await self.start_pet_recovery_system()
async def load_pet_species(self):
try:
@ -705,7 +707,58 @@ class GameEngine:
else:
return f"🐾 {pet_name}"
async def start_pet_recovery_system(self):
"""Start the background pet recovery task"""
if self.pet_recovery_task is None or self.pet_recovery_task.done():
print("🏥 Starting pet recovery background task...")
self.pet_recovery_task = asyncio.create_task(self._pet_recovery_loop())
async def stop_pet_recovery_system(self):
"""Stop the background pet recovery task"""
print("🏥 Stopping pet recovery background task...")
if self.pet_recovery_task and not self.pet_recovery_task.done():
self.pet_recovery_task.cancel()
try:
await self.pet_recovery_task
except asyncio.CancelledError:
pass
async def _pet_recovery_loop(self):
"""Background task that checks for pets eligible for auto-recovery"""
try:
while not self.shutdown_event.is_set():
try:
# Check every 5 minutes for pets that need recovery
await asyncio.sleep(300) # 5 minutes
if self.shutdown_event.is_set():
break
# Get pets eligible for auto-recovery (fainted for 30+ minutes)
eligible_pets = await self.database.get_pets_for_auto_recovery()
if eligible_pets:
print(f"🏥 Auto-recovering {len(eligible_pets)} pet(s) after 30 minutes...")
for pet in eligible_pets:
success = await self.database.auto_recover_pet(pet["id"])
if success:
print(f" 🏥 Auto-recovered {pet['nickname'] or pet['species_name']} (ID: {pet['id']}) to 1 HP")
else:
print(f" ❌ Failed to auto-recover pet ID: {pet['id']}")
except asyncio.CancelledError:
break
except Exception as e:
print(f"Error in pet recovery loop: {e}")
# Continue the loop even if there's an error
await asyncio.sleep(60) # Wait a minute before retrying
except asyncio.CancelledError:
print("Pet recovery task cancelled")
async def shutdown(self):
"""Gracefully shutdown the game engine"""
print("🔄 Shutting down game engine...")
await self.stop_weather_system()
await self.stop_weather_system()
await self.stop_pet_recovery_system()