From d758d6b924f67fc1655b8c6a6004e971d4862ebd Mon Sep 17 00:00:00 2001 From: megaproxy Date: Wed, 16 Jul 2025 11:32:25 +0000 Subject: [PATCH] Add \!heal command and automatic pet recovery background system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- modules/admin.py | 194 ++++++++++++++++++++++++++++++++++++++++++++- src/game_engine.py | 55 ++++++++++++- 2 files changed, 246 insertions(+), 3 deletions(-) diff --git a/modules/admin.py b/modules/admin.py index 8631ade..0d9a36f 100644 --- a/modules/admin.py +++ b/modules/admin.py @@ -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)}") \ No newline at end of file + 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)}") \ No newline at end of file diff --git a/src/game_engine.py b/src/game_engine.py index dae192c..cc935f5 100644 --- a/src/game_engine.py +++ b/src/game_engine.py @@ -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() \ No newline at end of file + await self.stop_weather_system() + await self.stop_pet_recovery_system() \ No newline at end of file