Spaces:
Runtime error
Runtime error
| """ | |
| NPC management service implementation. | |
| """ | |
| import random | |
| import time | |
| import threading | |
| import os | |
| from typing import Dict, Optional, List | |
| from ..interfaces.service_interfaces import INPCService | |
| from ..interfaces.game_interfaces import IGameWorld | |
| from ..interfaces.npc_interfaces import INPCBehavior | |
| class DonaldBehavior(INPCBehavior): | |
| """Deal maker NPC behavior with funny responses.""" | |
| def __init__(self): | |
| self.responses = [ | |
| "Listen, I make the best deals. Nobody makes deals like me, believe me!", | |
| "This game is tremendous, absolutely tremendous. I helped design it, you know.", | |
| "I have the best NPCs, the most fantastic NPCs you've ever seen!", | |
| "Fake news! I never said that. Well, maybe I did, but it was taken out of context.", | |
| "You know what? You're fired! Just kidding, I can't fire you here.", | |
| "I built a wall around my shop. Mexico paid for it. Well, not really, but...", | |
| "My inventory is huge, absolutely huge. Some say it's the biggest ever.", | |
| "I tweeted about this earlier. Did you see my tweet? It was perfect.", | |
| "Nobody knows trading like I know trading. I wrote the book on it!", | |
| "This is a witch hunt! I've done nothing wrong! Perfect conversation!", | |
| "I have tremendous respect for you, tremendous. But you're wrong.", | |
| "Covfefe! Wait, wrong universe. But you get the idea, right?", | |
| "I'm a stable genius when it comes to trading. Very stable, very genius.", | |
| "That's fake gold! I only deal in real gold. 24 karat, the best gold.", | |
| "I love all my customers equally. Except the ones who don't buy anything." | |
| ] | |
| self.greetings = [ | |
| "Welcome to Donald's Tremendous Trading Post! The best shop, believe me!", | |
| "You've never seen deals like these. Nobody has deals like these!", | |
| "Step right up to the most incredible shop in the entire game world!", | |
| "I'm Donald, and I approve this shopping experience. It's tremendous!", | |
| "Welcome! I have the best items, folks. The absolute best!" | |
| ] | |
| def get_response(self, npc_id: str, message: str, player_id: str = None) -> str: | |
| """Generate Deal Maker-style response.""" | |
| # Check for keywords to give specific responses | |
| message_lower = message.lower() | |
| if any(word in message_lower for word in ['buy', 'sell', 'trade', 'shop', 'item']): | |
| trade_responses = [ | |
| "You want to make a deal? I love deals! What do you got?", | |
| "Trading is my specialty. I literally wrote 'The Art of the Deal'!", | |
| "Let me tell you about my inventory - it's incredible, absolutely incredible!", | |
| "Nobody, and I mean NOBODY, has better prices than me!", | |
| "This is the best trade you'll ever make, believe me!" | |
| ] | |
| return random.choice(trade_responses) | |
| elif any(word in message_lower for word in ['hello', 'hi', 'hey', 'greetings']): | |
| return random.choice(self.greetings) | |
| elif any(word in message_lower for word in ['help', 'assist', 'support']): | |
| help_responses = [ | |
| "Help? I don't need help! But I'll help you because I'm generous.", | |
| "The best help you can get is right here, from me, Donald!", | |
| "I'm always helping people. It's what I do. I'm very helpful.", | |
| "You came to the right NPC for help. The absolute right NPC!" | |
| ] | |
| return random.choice(help_responses) | |
| elif any(word in message_lower for word in ['wall', 'build', 'construction']): | |
| return "I built the most beautiful wall around my shop. It's incredible!" | |
| elif any(word in message_lower for word in ['fake', 'news', 'media']): | |
| return "Fake news! The media is so dishonest. But you seem trustworthy!" | |
| else: | |
| return random.choice(self.responses) | |
| def get_greeting(self, npc_id: str, player_id: str = None) -> str: | |
| """Get greeting message.""" | |
| return random.choice(self.greetings) | |
| def can_handle(self, npc_id: str) -> bool: | |
| """Check if this behavior handles Donald NPC.""" | |
| return npc_id == "donald" | |
| class GenericNPCBehavior(INPCBehavior): | |
| """Generic NPC behavior for standard NPCs.""" | |
| def __init__(self): | |
| self.personality_responses = { | |
| 'merchant': [ | |
| "Welcome to my shop! What would you like to buy?", | |
| "I have the finest items in the realm!", | |
| "Special discount today - 10% off all potions!" | |
| ], | |
| 'wise_teacher': [ | |
| "Ah, a curious mind! What would you like to learn?", | |
| "Knowledge is the greatest treasure. Ask me anything!", | |
| "I have studied the ancient texts for decades.", | |
| "Wisdom comes through questioning. What puzzles you?" | |
| ], | |
| 'comedian': [ | |
| "Haha! Want to hear a joke? Why don't skeletons fight? They don't have the guts!", | |
| "What do you call a sleeping bull? A bulldozer! *laughs*", | |
| "I've got jokes for days! Life's too short not to laugh!", | |
| "Why did the scarecrow win an award? He was outstanding in his field!" | |
| ], | |
| 'tough_trainer': [ | |
| "Ready for combat training? Show me your stance!", | |
| "A true warrior trains every day. Are you committed?", | |
| "Strength comes from discipline and practice!", | |
| "The blade is an extension of your will. Focus!" | |
| ], | |
| 'gentle_healer': [ | |
| "Blessings upon you, traveler. Do you need healing?", | |
| "The light guides my hands. I can mend your wounds.", | |
| "Health of body and spirit go hand in hand.", | |
| "May the divine light restore your vitality!" | |
| ], | |
| 'mystical_sage': [ | |
| "Magic flows through all things, young one.", | |
| "The arcane arts require patience and understanding.", | |
| "Ancient powers stir... can you feel them?", | |
| "Wisdom and magic are closely intertwined." | |
| ], | |
| 'traveler': [ | |
| "The road calls to me... always moving, always exploring!", | |
| "I've seen wonders beyond imagination in my travels.", | |
| "Adventure awaits around every corner!", | |
| "Sometimes the journey is more important than the destination." | |
| ] | |
| } | |
| def get_response(self, npc_id: str, message: str, player_id: str = None) -> str: | |
| """Generate generic NPC response based on personality.""" | |
| # This would typically get NPC data from the world | |
| # For now, we'll use a simple mapping | |
| personality_map = { | |
| 'merchant': 'merchant', | |
| 'scholar': 'wise_teacher', | |
| 'jester': 'comedian', | |
| 'warrior': 'tough_trainer', | |
| 'healer': 'gentle_healer', | |
| 'sage': 'mystical_sage', | |
| 'wanderer': 'traveler' | |
| } | |
| personality = personality_map.get(npc_id, 'merchant') | |
| responses = self.personality_responses.get(personality, [ | |
| "Hello there, traveler!", | |
| "How can I help you?", | |
| "Nice weather we're having." | |
| ]) | |
| return random.choice(responses) | |
| def get_greeting(self, npc_id: str, player_id: str = None) -> str: | |
| """Get greeting message.""" | |
| return self.get_response(npc_id, "hello", player_id) | |
| def can_handle(self, npc_id: str) -> bool: | |
| """This behavior can handle any NPC.""" | |
| return True | |
| class NPCService(INPCService): | |
| """Service for managing NPCs in the game world.""" | |
| def __init__(self, game_world: IGameWorld): | |
| self.game_world = game_world | |
| self.behaviors = [ | |
| DonaldBehavior(), | |
| GenericNPCBehavior() | |
| ] | |
| # Movement system | |
| self._movement_thread = None | |
| self._movement_active = False | |
| # Only start movement system if not in test environment | |
| if not os.environ.get('PYTEST_CURRENT_TEST') and not hasattr(game_world, '_is_test_instance'): | |
| self.start_movement_system() | |
| def start_movement_system(self): | |
| """Start the NPC movement system for moving NPCs like Roaming Rick.""" | |
| if self._movement_thread is None or not self._movement_thread.is_alive(): | |
| self._movement_active = True | |
| self._movement_thread = threading.Thread(target=self._movement_loop, daemon=True) | |
| self._movement_thread.start() | |
| def stop_movement_system(self): | |
| """Stop the NPC movement system.""" | |
| self._movement_active = False | |
| if self._movement_thread and self._movement_thread.is_alive(): | |
| self._movement_thread.join(timeout=1.0) | |
| self._movement_thread = None | |
| def _movement_loop(self): | |
| """Main movement loop for updating moving NPCs.""" | |
| while self._movement_active: | |
| try: | |
| self.update_moving_npcs() | |
| time.sleep(2.0) # Update every 2 seconds | |
| except Exception as e: | |
| print(f"[NPCService] Movement loop error: {e}") # Break the loop on critical errors during testing | |
| if "Mock" in str(e): | |
| print(f"[NPCService] Stopping movement loop due to mock error") | |
| break | |
| time.sleep(5.0) # Wait longer on error | |
| def update_moving_npcs(self): | |
| """Update positions of moving NPCs like Roaming Rick.""" | |
| current_time = time.time() | |
| try: | |
| npcs = self.get_all_npcs() | |
| # Handle case when npcs is a Mock object during testing | |
| if hasattr(npcs, '__iter__'): | |
| try: | |
| npc_items = npcs.items() if hasattr(npcs, 'items') else [] | |
| except Exception: | |
| # If iteration fails (e.g., with Mock), just return | |
| print("[NPCService] Error in update_moving_npcs: Mock object iteration failed") | |
| return | |
| else: | |
| print("[NPCService] Error in update_moving_npcs: npcs is not iterable") | |
| return | |
| for npc_id, npc in npc_items: | |
| # Check if this NPC has movement configuration | |
| if npc.get('type') == 'moving' and 'movement' in npc: | |
| movement = npc['movement'] | |
| # Check if enough time has passed for movement (based on speed) | |
| time_since_last_move = current_time - movement.get('last_move', 0) | |
| movement_interval = 2.0 / movement.get('speed', 1) # Faster speed = shorter interval | |
| if time_since_last_move >= movement_interval: | |
| # Store old position | |
| old_x, old_y = npc['x'], npc['y'] | |
| # Calculate new position | |
| move_distance = 25 # Same as player movement | |
| direction_x = movement.get('direction_x', 1) | |
| direction_y = movement.get('direction_y', 1) | |
| new_x = npc['x'] + (move_distance * direction_x) | |
| new_y = npc['y'] + (move_distance * direction_y) | |
| # Check boundaries and bounce if needed (assuming 800x600 world) | |
| world_width = getattr(self.game_world, 'world_width', 475) | |
| world_height = getattr(self.game_world, 'world_height', 375) | |
| if new_x <= 0 or new_x >= world_width: | |
| direction_x *= -1 # Reverse X direction | |
| new_x = max(0, min(world_width, new_x)) | |
| if new_y <= 0 or new_y >= world_height: | |
| direction_y *= -1 # Reverse Y direction | |
| new_y = max(0, min(world_height, new_y)) | |
| # Update NPC position and movement data | |
| npc['x'] = new_x | |
| npc['y'] = new_y | |
| movement['direction_x'] = direction_x | |
| movement['direction_y'] = direction_y | |
| movement['last_move'] = current_time | |
| # Add world event if NPC moved | |
| if old_x != new_x or old_y != new_y: | |
| if hasattr(self.game_world, 'add_world_event'): | |
| self.game_world.add_world_event(f"🚶 {npc['name']} roams to ({int(new_x)}, {int(new_y)})") | |
| # Check if any players are now near this moved NPC | |
| try: | |
| players = self.game_world.get_all_players() | |
| for player_id, player in players.items(): | |
| distance = ((player.x - new_x)**2 + (player.y - new_y)**2)**0.5 | |
| if distance < 50: # Close enough to notice | |
| if hasattr(self.game_world, 'add_world_event'): | |
| self.game_world.add_world_event(f"👀 {player.name} notices {npc['name']} nearby") | |
| except Exception as e: | |
| print(f"[NPCService] Error checking player proximity: {e}") | |
| except Exception as e: | |
| print(f"[NPCService] Error in update_moving_npcs: {e}") | |
| def get_npc_response(self, npc_id: str, message: str, player_id: str = None) -> str: | |
| """Get NPC response to player message.""" | |
| try: | |
| # Find appropriate behavior for this NPC | |
| for behavior in self.behaviors: | |
| if behavior.can_handle(npc_id): | |
| return behavior.get_response(npc_id, message, player_id) | |
| # Fallback response | |
| return "I don't understand what you're saying." | |
| except Exception as e: | |
| print(f"[NPCService] Error generating response: {e}") | |
| return "Sorry, I'm having trouble understanding you right now." | |
| def register_npc(self, npc_id: str, npc_data: Dict) -> bool: | |
| """Register a new NPC in the game world.""" | |
| try: | |
| if hasattr(self.game_world, 'add_npc'): | |
| self.game_world.add_npc(npc_id, npc_data) | |
| return True | |
| else: | |
| # Direct access to npcs dict | |
| npcs = getattr(self.game_world, 'npcs', {}) | |
| npcs[npc_id] = npc_data | |
| return True | |
| except Exception as e: | |
| print(f"[NPCService] Error registering NPC: {e}") | |
| return False | |
| def get_npc(self, npc_id: str) -> Optional[Dict]: | |
| """Get NPC data by ID.""" | |
| try: | |
| if hasattr(self.game_world, 'get_npc'): | |
| return self.game_world.get_npc(npc_id) | |
| else: | |
| npcs = getattr(self.game_world, 'npcs', {}) | |
| return npcs.get(npc_id) | |
| except Exception as e: | |
| print(f"[NPCService] Error getting NPC: {e}") | |
| return None | |
| def update_npc_position(self, npc_id: str, x: int, y: int) -> bool: | |
| """Update NPC position.""" | |
| try: | |
| npc = self.get_npc(npc_id) | |
| if npc: | |
| npc['x'] = x | |
| npc['y'] = y | |
| return True | |
| return False | |
| except Exception as e: | |
| print(f"[NPCService] Error updating NPC position: {e}") | |
| return False | |
| def get_npc_greeting(self, npc_id: str, player_id: str = None) -> str: | |
| """Get greeting message from NPC.""" | |
| for behavior in self.behaviors: | |
| if behavior.can_handle(npc_id): | |
| return behavior.get_greeting(npc_id, player_id) | |
| return "Hello, traveler!" | |
| def add_behavior(self, behavior: INPCBehavior) -> bool: | |
| """Add a new NPC behavior.""" | |
| try: | |
| # Insert before the generic behavior (which should be last) | |
| self.behaviors.insert(-1, behavior) | |
| return True | |
| except Exception as e: | |
| print(f"[NPCService] Error adding behavior: {e}") | |
| return False | |
| def remove_behavior(self, behavior_class: type) -> bool: | |
| """Remove an NPC behavior by class.""" | |
| try: | |
| self.behaviors = [b for b in self.behaviors if not isinstance(b, behavior_class)] | |
| return True | |
| except Exception as e: | |
| print(f"[NPCService] Error removing behavior: {e}") | |
| return False | |
| def get_all_npcs(self) -> Dict[str, Dict]: | |
| """Get all NPCs from the game world.""" | |
| try: | |
| if hasattr(self.game_world, 'get_all_npcs'): | |
| return self.game_world.get_all_npcs() | |
| else: | |
| return getattr(self.game_world, 'npcs', {}) | |
| except Exception as e: | |
| print(f"[NPCService] Error getting all NPCs: {e}") | |
| return {} | |