Spaces:
Runtime error
Runtime error
| """ | |
| HuggingFace-style UI Implementation | |
| This module creates a modern, HuggingFace-inspired user interface for the MMORPG | |
| with organized tabs, clean design, and intuitive navigation. | |
| """ | |
| import gradio as gr | |
| from typing import Dict, Any, Optional, List | |
| from ..core.game_engine import GameEngine | |
| from ..facades.game_facade import GameFacade | |
| from ..interfaces.npc_addon import get_registered_addons | |
| class HuggingFaceUI: | |
| """HuggingFace-style UI manager for the MMORPG application.""" | |
| def __init__(self, game_facade: GameFacade): | |
| self.game_facade = game_facade | |
| from ..core.game_engine import get_game_engine | |
| self.game_engine = get_game_engine() | |
| # UI state management | |
| self.player_state = None | |
| self.chat_tabs_state = None | |
| # Theme configuration | |
| self.theme = gr.themes.Soft( | |
| primary_hue="blue", | |
| secondary_hue="slate", | |
| neutral_hue="slate", | |
| radius_size="md", | |
| spacing_size="md" | |
| ) | |
| # Custom CSS for HuggingFace-like styling with enhanced design | |
| self.custom_css = """ | |
| /* HuggingFace-inspired styling with modern enhancements */ | |
| .main-container { | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| } | |
| .hero-section { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| padding: 30px; | |
| border-radius: 12px; | |
| margin-bottom: 30px; | |
| text-align: center; | |
| box-shadow: 0 4px 20px rgba(0,0,0,0.1); | |
| } | |
| .hero-title { | |
| font-size: 2.5em; | |
| font-weight: bold; | |
| margin-bottom: 10px; | |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.3); | |
| } | |
| .hero-subtitle { | |
| font-size: 1.2em; | |
| opacity: 0.9; | |
| margin-bottom: 15px; | |
| } | |
| .hero-features { | |
| font-size: 1em; | |
| opacity: 0.8; | |
| } | |
| .game-panel { | |
| background: white; | |
| border-radius: 12px; | |
| padding: 20px; | |
| box-shadow: 0 4px 15px rgba(0,0,0,0.1); | |
| margin-bottom: 20px; | |
| border: 1px solid #e9ecef; | |
| } | |
| .stats-card { | |
| background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); | |
| border: 1px solid #dee2e6; | |
| border-radius: 12px; | |
| padding: 20px; | |
| margin: 15px 0; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.08); | |
| } | |
| .movement-grid { | |
| display: grid; | |
| grid-template-columns: repeat(3, 60px); | |
| gap: 12px; | |
| max-width: 220px; | |
| margin: 15px auto; | |
| justify-content: center; | |
| } | |
| .movement-btn { | |
| width: 60px; | |
| height: 60px; | |
| border-radius: 12px; | |
| border: 2px solid #007bff; | |
| background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); | |
| font-size: 18px; | |
| font-weight: bold; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| box-shadow: 0 2px 8px rgba(0,123,255,0.2); | |
| } | |
| .movement-btn:hover { | |
| background: linear-gradient(135deg, #007bff 0%, #0056b3 100%); | |
| color: white; | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 12px rgba(0,123,255,0.4); | |
| } | |
| .movement-btn:active { | |
| transform: translateY(0px); | |
| box-shadow: 0 2px 6px rgba(0,123,255,0.3); | |
| } | |
| .keyboard-status { | |
| background: linear-gradient(135deg, #e8f5e8 0%, #d4edda 100%); | |
| border: 2px solid #4caf50; | |
| border-radius: 8px; | |
| padding: 12px; | |
| margin: 10px 0; | |
| text-align: center; | |
| font-weight: bold; | |
| box-shadow: 0 2px 8px rgba(76,175,80,0.2); | |
| } | |
| .tab-content { | |
| padding: 25px; | |
| min-height: 600px; | |
| background: #fafafa; | |
| border-radius: 8px; | |
| } | |
| .doc-section { | |
| margin-bottom: 30px; | |
| padding: 25px; | |
| border-left: 4px solid #007bff; | |
| background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); | |
| border-radius: 0 12px 12px 0; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.05); | |
| } | |
| .feature-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); | |
| gap: 20px; | |
| margin: 20px 0; | |
| } | |
| .feature-card { | |
| background: white; | |
| border: 1px solid #e9ecef; | |
| border-radius: 12px; | |
| padding: 25px; | |
| box-shadow: 0 4px 15px rgba(0,0,0,0.08); | |
| transition: all 0.3s ease; | |
| } | |
| .feature-card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 8px 25px rgba(0,0,0,0.15); | |
| } | |
| .feature-icon { | |
| font-size: 2.5em; | |
| margin-bottom: 15px; | |
| display: block; | |
| text-align: center; | |
| } | |
| .chat-tab { | |
| background: linear-gradient(135deg, #f5f5f5 0%, #e9ecef 100%); | |
| border: 2px solid #dee2e6; | |
| border-radius: 8px; | |
| padding: 10px 16px; | |
| margin: 3px; | |
| cursor: pointer; | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 8px; | |
| transition: all 0.3s ease; | |
| font-weight: 500; | |
| } | |
| .chat-tab.active { | |
| background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%); | |
| border-color: #2196f3; | |
| color: #1976d2; | |
| box-shadow: 0 2px 8px rgba(33,150,243,0.3); | |
| } | |
| .chat-tab:hover { | |
| background: linear-gradient(135deg, #e9ecef 0%, #dee2e6 100%); | |
| transform: translateY(-1px); | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |
| } | |
| .npc-addon-panel { | |
| border: 2px solid #e9ecef; | |
| border-radius: 12px; | |
| padding: 20px; | |
| margin: 15px 0; | |
| background: linear-gradient(135deg, #fafafa 0%, #ffffff 100%); | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.05); | |
| } | |
| .plugin-card { | |
| background: white; | |
| border: 2px solid #e9ecef; | |
| border-radius: 12px; | |
| padding: 20px; | |
| margin: 15px 0; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.08); | |
| transition: all 0.3s ease; | |
| } | |
| .plugin-card:hover { | |
| border-color: #007bff; | |
| box-shadow: 0 4px 15px rgba(0,123,255,0.15); | |
| } | |
| .status-indicator { | |
| display: inline-block; | |
| width: 12px; | |
| height: 12px; | |
| border-radius: 50%; | |
| margin-right: 8px; | |
| box-shadow: 0 0 0 2px white, 0 0 0 3px currentColor; | |
| } | |
| .status-online { | |
| background-color: #4caf50; | |
| animation: pulse-green 2s infinite; | |
| } | |
| .status-offline { | |
| background-color: #f44336; | |
| } | |
| .status-idle { | |
| background-color: #ff9800; | |
| } | |
| @keyframes pulse-green { | |
| 0% { box-shadow: 0 0 0 2px white, 0 0 0 3px #4caf50; } | |
| 50% { box-shadow: 0 0 0 2px white, 0 0 0 6px rgba(76,175,80,0.5); } | |
| 100% { box-shadow: 0 0 0 2px white, 0 0 0 3px #4caf50; } | |
| } | |
| /* Enhanced buttons */ | |
| .gradio-button { | |
| border-radius: 8px !important; | |
| font-weight: 600 !important; | |
| transition: all 0.3s ease !important; | |
| } | |
| .gradio-button:hover { | |
| transform: translateY(-1px) !important; | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.15) !important; | |
| } | |
| /* Enhanced form controls */ | |
| .gradio-textbox, .gradio-dropdown, .gradio-slider { | |
| border-radius: 8px !important; | |
| border: 2px solid #e9ecef !important; | |
| transition: all 0.3s ease !important; | |
| } | |
| .gradio-textbox:focus, .gradio-dropdown:focus { | |
| border-color: #007bff !important; | |
| box-shadow: 0 0 0 3px rgba(0,123,255,0.1) !important; | |
| } | |
| /* Responsive design */ | |
| @media (max-width: 768px) { | |
| .main-container { | |
| padding: 15px; | |
| } | |
| .hero-section { | |
| padding: 20px; | |
| } | |
| .hero-title { | |
| font-size: 2em; | |
| } | |
| .feature-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .movement-grid { | |
| grid-template-columns: repeat(3, 50px); | |
| gap: 8px; | |
| } | |
| .movement-btn { | |
| width: 50px; | |
| height: 50px; | |
| font-size: 16px; | |
| } | |
| } | |
| /* Dark mode support */ | |
| @media (prefers-color-scheme: dark) { | |
| .doc-section { | |
| background: linear-gradient(135deg, #2d3748 0%, #4a5568 100%); | |
| color: white; | |
| } | |
| .feature-card { | |
| background: #2d3748; | |
| border-color: #4a5568; | |
| color: white; | |
| } | |
| .npc-addon-panel, .plugin-card { | |
| background: #2d3748; | |
| border-color: #4a5568; | |
| color: white; | |
| } | |
| } | |
| """ | |
| def create_hero_section(self) -> gr.HTML: | |
| """Create the hero section with branding and overview.""" | |
| hero_html = """ | |
| <div class="hero-section"> | |
| <div class="hero-title">🎮 MMORPG with MCP Integration</div> | |
| <div class="hero-subtitle">Modern Multiplayer Online Role-Playing Game</div> | |
| <div class="hero-features"> | |
| 🌟 Real-time multiplayer • 🤖 AI agent support • 🔥 Read2Burn messaging • 🔌 MCP add-ons • ⌨️ Keyboard controls | |
| </div> </div> """ | |
| return gr.HTML(hero_html) | |
| def create_keyboard_status(self) -> gr.HTML: | |
| """Create keyboard status indicator.""" | |
| return gr.HTML( | |
| # value="<div class='keyboard-status' id='keyboard-status'>🎮 Initializing keyboard controls...</div>" | |
| ) | |
| def get_keyboard_script(self) -> str: | |
| """Get the keyboard control script for injection into <head>.""" | |
| keyboard_js = """ | |
| <script> | |
| let gameKeyboard = { | |
| enabled: false, | |
| buttons: {}, | |
| init: function() { | |
| // Find movement buttons after DOM loads | |
| setTimeout(() => { | |
| const buttons = document.querySelectorAll('button'); | |
| buttons.forEach(btn => { | |
| const text = btn.textContent.trim(); | |
| if (text === '↑') this.buttons.up = btn; | |
| else if (text === '↓') this.buttons.down = btn; | |
| else if (text === '←') this.buttons.left = btn; | |
| else if (text === '→') this.buttons.right = btn; | |
| else if (text === '⚔️' || text.includes('Action')) this.buttons.action = btn; | |
| }); | |
| console.log('🎮 Game keyboard initialized'); | |
| this.enabled = true; | |
| }, 1000); | |
| }, | |
| handleKey: function(event) { | |
| if (!this.enabled) return; | |
| // Only capture keys when not typing in inputs | |
| if (event.target.tagName.toLowerCase() === 'input' || | |
| event.target.tagName.toLowerCase() === 'textarea') { | |
| return; | |
| } | |
| let button = null; | |
| switch(event.code) { | |
| case 'ArrowUp': | |
| case 'KeyW': | |
| button = this.buttons.up; | |
| break; | |
| case 'ArrowDown': | |
| case 'KeyS': | |
| button = this.buttons.down; | |
| break; | |
| case 'ArrowLeft': | |
| case 'KeyA': | |
| button = this.buttons.left; | |
| break; | |
| case 'ArrowRight': | |
| case 'KeyD': | |
| button = this.buttons.right; | |
| break; | |
| case 'Space': | |
| button = this.buttons.action; | |
| break; | |
| } | |
| if (button) { | |
| event.preventDefault(); | |
| button.click(); | |
| console.log('🎯 Key pressed:', event.code); | |
| } | |
| } | |
| }; | |
| // Initialize when page loads | |
| document.addEventListener('DOMContentLoaded', () => gameKeyboard.init()); | |
| // Add keyboard listener | |
| document.addEventListener('keydown', (e) => gameKeyboard.handleKey(e)); | |
| // Reinitialize when Gradio updates (for dynamic content) | |
| setInterval(() => { | |
| if (!gameKeyboard.enabled) { | |
| gameKeyboard.init(); | |
| } | |
| }, 2000); | |
| </script> | |
| """ | |
| return keyboard_js | |
| def create_movement_controls(self) -> tuple: | |
| """Create enhanced movement control buttons.""" | |
| with gr.Group(): | |
| gr.Markdown("### 🕹️ Movement Controls") | |
| gr.Markdown("**Keyboard:** WASD or Arrow Keys | **Mouse:** Click buttons below") | |
| with gr.Row(): | |
| gr.HTML("") | |
| move_up = gr.Button("↑", size="lg", elem_classes=["movement-btn"]) | |
| gr.HTML("") | |
| with gr.Row(): | |
| move_left = gr.Button("←", size="lg", elem_classes=["movement-btn"]) | |
| action_btn = gr.Button("⚔️", size="lg", elem_classes=["movement-btn"], variant="secondary") | |
| move_right = gr.Button("→", size="lg", elem_classes=["movement-btn"]) | |
| with gr.Row(): | |
| gr.HTML("") | |
| move_down = gr.Button("↓", size="lg", elem_classes=["movement-btn"]) | |
| gr.HTML("") | |
| return move_up, move_down, move_left, move_right, action_btn | |
| def create_player_stats_panel(self) -> gr.JSON: | |
| """Create player statistics display panel.""" | |
| return gr.JSON( | |
| label="🧝♂️ Player Stats", | |
| value={"status": "Not connected", "info": "Join the game to see your stats"}, | |
| elem_classes=["stats-card"] | |
| ) | |
| def create_online_players_panel(self) -> gr.Dataframe: | |
| """Create online players display panel.""" | |
| return gr.Dataframe( | |
| headers=["Name", "Type", "Level"], | |
| label="👥 Online Players", | |
| elem_classes=["stats-card"] | |
| ) | |
| def create_game_world_view(self) -> gr.HTML: | |
| """Create the game world visualization.""" | |
| return gr.HTML( | |
| value=self._generate_world_html(), | |
| label="🌍 Game World" | |
| ) | |
| def create_chat_system(self) -> tuple: | |
| """Create the enhanced chat system with tabs.""" | |
| # Public chat | |
| chat_display = gr.Chatbot( | |
| label="💬 Game Chat", | |
| height=300, | |
| type='messages', | |
| value=[{"role": "assistant", "content": "Welcome! Join the game to start chatting!"}], | |
| elem_classes=["game-panel"] | |
| ) | |
| with gr.Row(): | |
| chat_input = gr.Textbox( | |
| placeholder="Type your message... (use /help for commands)", | |
| scale=4, | |
| container=False | |
| ) | |
| chat_send = gr.Button("Send", scale=1, variant="primary") | |
| # Private chat system | |
| with gr.Group(): | |
| gr.Markdown("### 🔒 Private Messaging") | |
| proximity_info = gr.HTML( | |
| value="<div style='text-align: center; color: #666;'>🔍 Move near NPCs or players to chat privately</div>", | |
| label="📱 Nearby Entities" | |
| ) | |
| with gr.Group(visible=False) as private_chat_group: | |
| nearby_entities = gr.Dropdown( | |
| label="💬 Start new chat with", | |
| choices=[], | |
| interactive=True | |
| ) | |
| with gr.Row(): | |
| start_chat_btn = gr.Button("Start Chat", variant="primary", scale=1) | |
| clear_all_tabs_btn = gr.Button("Clear All", variant="secondary", scale=1) | |
| # Chat tabs display | |
| chat_tabs_state = gr.State({}) | |
| active_tabs_display = gr.HTML( | |
| value="<div class='chat-tab'>No active chats</div>", | |
| label="Active Chats" | |
| ) | |
| # Current chat display | |
| current_chat_display = gr.Chatbot( | |
| label="🔒 Private Messages", | |
| height=200, | |
| type='messages', | |
| value=[], | |
| visible=False | |
| ) | |
| with gr.Row(visible=False) as chat_input_row: | |
| private_message_input = gr.Textbox( | |
| placeholder="Type private message...", | |
| scale=4, | |
| container=False | |
| ) | |
| private_send_btn = gr.Button("Send", scale=1, variant="secondary") | |
| return (chat_display, chat_input, chat_send, proximity_info, private_chat_group, | |
| nearby_entities, start_chat_btn, clear_all_tabs_btn, chat_tabs_state, | |
| active_tabs_display, current_chat_display, chat_input_row, | |
| private_message_input, private_send_btn) | |
| def create_world_events_panel(self) -> gr.Textbox: | |
| """Create world events display panel.""" | |
| return gr.Textbox( | |
| label="🌍 World Events & NPC Interactions", | |
| lines=4, | |
| interactive=False, | |
| placeholder="World events will appear here...\n\n💡 Tip: Walk near NPCs (📮🏪🌤️) to interact with them!\nThen visit the 'NPC Add-ons' tab to use their features.", | |
| elem_classes=["game-panel"] | |
| ) | |
| def _generate_world_html(self, width: int = 800, height: int = 600) -> str: | |
| """Generate HTML for the game world visualization with enhanced tooltips.""" | |
| # Enhanced game world with tree collision detection | |
| # Dynamically generate NPCs from GameFacade with enhanced tooltips from addons | |
| npc_html = "" | |
| for npc in self.game_facade.get_all_npcs().values(): | |
| try: | |
| # Get rich tooltip information from addon system | |
| tooltip_info = self._get_npc_tooltip_info(npc) | |
| # Ensure tooltip_info is a string | |
| if not isinstance(tooltip_info, str): | |
| print(f"[UI] Warning: tooltip_info for {npc.get('name', 'Unknown')} is {type(tooltip_info)}: {tooltip_info}") | |
| tooltip_info = str(tooltip_info) | |
| # Escape any HTML characters in tooltip | |
| tooltip_info = tooltip_info.replace('"', '"').replace("'", ''') | |
| npc_html += f""" | |
| <div style="position: absolute; left: {npc['x']}px; top: {npc['y']}px; text-align: center;" | |
| title="{tooltip_info}" | |
| class="npc-hover-element"> | |
| <div style="font-size: 25px; line-height: 1;">{npc['char']}</div> | |
| <div style="font-size: 7px; font-weight: bold; color: #333; background: rgba(255,255,255,0.8); | |
| padding: 1px 3px; border-radius: 3px; margin-top: 2px; white-space: nowrap;">{npc['name']}</div> | |
| </div>""" | |
| except Exception as npc_e: | |
| print(f"[UI] Error generating HTML for NPC {npc.get('name', 'Unknown')}: {npc_e}") | |
| print(f"[UI] NPC data: {npc}") | |
| # Add a basic NPC without tooltip | |
| npc_html += f""" | |
| <div style="position: absolute; left: {npc['x']}px; top: {npc['y']}px; text-align: center;" | |
| title="Interactive NPC" | |
| class="npc-hover-element"> | |
| <div style="font-size: 25px; line-height: 1;">{npc['char']}</div> | |
| <div style="font-size: 7px; font-weight: bold; color: #333; background: rgba(255,255,255,0.8); | |
| padding: 1px 3px; border-radius: 3px; margin-top: 2px; white-space: nowrap;">{npc['name']}</div> | |
| </div>""" | |
| # Generate player tooltips with enhanced information | |
| players_html = "" | |
| try: | |
| players = self.game_facade.get_all_players() | |
| for player_id, player in players.items(): | |
| try: | |
| player_tooltip = self._get_player_tooltip_info(player) | |
| # Ensure player_tooltip is a string | |
| if not isinstance(player_tooltip, str): | |
| print(f"[UI] Warning: player_tooltip for {player.name} is {type(player_tooltip)}: {player_tooltip}") | |
| player_tooltip = str(player_tooltip) | |
| # Escape any HTML characters in tooltip | |
| player_tooltip = player_tooltip.replace('"', '"').replace("'", ''') | |
| players_html += f""" | |
| <div style="position: absolute; left: {player.x}px; top: {player.y}px; | |
| font-size: 20px; z-index: 10; border: 2px solid yellow; border-radius: 50%; | |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.5);" | |
| title="{player_tooltip}" | |
| class="player-hover-element"> | |
| 🧑🦰 | |
| </div> | |
| <div style="position: absolute; left: {player.x - 15}px; top: {player.y - 15}px; | |
| background: rgba(255,215,0,0.9); color: black; padding: 1px 4px; | |
| border-radius: 3px; font-size: 8px; font-weight: bold; z-index: 11;"> | |
| {player.name} (Lv.{player.level}) | |
| </div>""" | |
| except Exception as player_e: | |
| print(f"[UI] Error generating player tooltip for {player.name}: {player_e}") | |
| # Add basic player without tooltip | |
| players_html += f""" | |
| <div style="position: absolute; left: {player.x}px; top: {player.y}px; | |
| font-size: 20px; z-index: 10; border: 2px solid yellow; border-radius: 50%; | |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.5);" | |
| title="Player: {player.name}" | |
| class="player-hover-element"> | |
| 🧑🦰 | |
| </div> | |
| <div style="position: absolute; left: {player.x - 15}px; top: {player.y - 15}px; | |
| background: rgba(255,215,0,0.9); color: black; padding: 1px 4px; | |
| border-radius: 3px; font-size: 8px; font-weight: bold; z-index: 11;"> | |
| {player.name} (Lv.{player.level}) | |
| </div>""" | |
| except Exception as e: | |
| print(f"[UI] Error generating player tooltips: {e}") | |
| # Base world template with enhanced tooltip styling | |
| world_html = f""" | |
| <div style="position: relative; width: {width}px; height: {height}px; | |
| border: 3px solid #333; border-radius: 12px; | |
| background: linear-gradient(45deg, #4CAF50 0%, #8BC34A 50%, #4CAF50 100%); | |
| margin: 20px auto; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.2);"> | |
| <!-- Trees for collision detection --> | |
| <div style="position: absolute; left: 150px; top: 100px; font-size: 30px;" title="Ancient Oak - Blocks movement">🌳</div> | |
| <div style="position: absolute; left: 300px; top: 200px; font-size: 30px;" title="Forest Pine - Collision object">🌳</div> | |
| <div style="position: absolute; left: 500px; top: 150px; font-size: 30px;" title="Mystical Tree - Impassable">🌳</div> | |
| <div style="position: absolute; left: 650px; top: 300px; font-size: 30px;" title="Old Growth Tree - Blocks path">🌳</div> | |
| <div style="position: absolute; left: 200px; top: 400px; font-size: 30px;" title="Willow Tree - Natural barrier">🌳</div> | |
| <div style="position: absolute; left: 450px; top: 450px; font-size: 30px;" title="Sacred Grove Tree - Protected">🌳</div> | |
| <div style="position: absolute; left: 600px; top: 500px; font-size: 30px;" title="Elder Tree - Wise and immovable">🌳</div> | |
| <!-- NPCs with enhanced tooltips --> | |
| {npc_html} | |
| <!-- Decorative elements with tooltips --> | |
| <div style="position: absolute; left: 50px; top: 50px; font-size: 20px;" title="Northern Mountains - Unexplored peaks">🏔️</div> | |
| <div style="position: absolute; right: 50px; top: 50px; font-size: 20px;" title="Eastern Peaks - Dragon territory">🏔️</div> | |
| <div style="position: absolute; left: 50px; bottom: 50px; font-size: 20px;" title="Western Waters - Crystal clear lakes">🌊</div> | |
| <div style="position: absolute; right: 50px; bottom: 50px; font-size: 20px;" title="Southern Seas - Endless horizon">🌊</div> | |
| <!-- Players will be dynamically added here --> | |
| <div id="players-container"> | |
| {players_html} | |
| </div> | |
| <!-- Enhanced tooltip styling --> | |
| <style> | |
| .npc-hover-element, .player-hover-element {{ | |
| cursor: pointer; | |
| transition: transform 0.2s ease; | |
| }} | |
| .npc-hover-element:hover, .player-hover-element:hover {{ | |
| transform: scale(1.1); | |
| z-index: 100; | |
| }} | |
| /* Enhanced tooltip styling */ | |
| [title] {{ | |
| position: relative; | |
| }} | |
| /* Custom tooltip appearance - wider and more readable */ | |
| div[title]:hover::after {{ | |
| content: attr(title); | |
| position: absolute; | |
| bottom: 100%; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| background: rgba(0, 0, 0, 0.9); | |
| color: white; | |
| padding: 12px 16px; | |
| border-radius: 8px; | |
| font-size: 14px; | |
| white-space: pre-line; | |
| min-width: 200px; | |
| max-width: 400px; | |
| z-index: 1000; | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.3); | |
| border: 1px solid #555; | |
| text-align: center; | |
| line-height: 1.4; | |
| }} | |
| </style> | |
| <div style="position: absolute; top: 10px; left: 10px; background: rgba(255,255,255,0.9); | |
| padding: 10px; border-radius: 8px; font-size: 12px; box-shadow: 0 2px 5px rgba(0,0,0,0.2);"> | |
| 🌍 <strong>Fantasy Realm</strong> | | |
| Players: {len(players) if 'players' in locals() else 0}/20 | | |
| Hover over NPCs and players for information | |
| <br> | |
| 🧑🦰 Players | 📮🏪🚶🧙🌤️ NPCs | 🌳 Trees | |
| </div> </div> | |
| """ | |
| return world_html | |
| def _get_npc_tooltip_info(self, npc: dict) -> str: | |
| """Generate simple tooltip information for an NPC from addon data.""" | |
| try: | |
| # Get addon information if available | |
| from ..interfaces.npc_addon import get_registered_addons | |
| registered_addons = get_registered_addons() | |
| # Try to find matching addon | |
| addon = None | |
| npc_id = npc.get('id', '') | |
| # Search for addon by NPC ID or name matching | |
| for addon_id, registered_addon in registered_addons.items(): | |
| if hasattr(registered_addon, 'npc_config') and registered_addon.npc_config: | |
| try: | |
| npc_config = registered_addon.npc_config | |
| if isinstance(npc_config, dict): | |
| addon_npc_id = npc_config.get('id', '') | |
| # Ensure both values are strings before comparison | |
| if isinstance(addon_npc_id, str) and isinstance(npc_id, str): | |
| if addon_npc_id == npc_id or addon_npc_id in npc_id: | |
| addon = registered_addon | |
| break | |
| except Exception as config_e: | |
| print(f"[UI] Error accessing npc_config for {addon_id}: {config_e}") | |
| continue | |
| # Use description from addon's npc_config if available | |
| if addon and hasattr(addon, 'npc_config') and addon.npc_config: | |
| description = addon.npc_config.get('description', '') | |
| if description: | |
| return description | |
| # Fallback to basic NPC description | |
| return npc.get('description', f"{npc['name']} - Interactive NPC") | |
| except Exception as e: | |
| print(f"[UI] Error generating NPC tooltip for {npc.get('name', 'Unknown')}: {e}") | |
| return f"{npc['name']} - Interactive NPC" | |
| def _get_player_tooltip_info(self, player) -> str: | |
| """Generate rich tooltip information for a player.""" | |
| try: | |
| tooltip = f"👤 {player.name}\n" | |
| tooltip += f"⭐ Level: {player.level}\n" | |
| tooltip += f"❤️ Health: {player.hp}/{player.max_hp}\n" | |
| tooltip += f"💰 Gold: {getattr(player, 'gold', 0)}\n" | |
| tooltip += f"🎯 XP: {player.experience}\n" | |
| tooltip += f"📍 Position: ({player.x}, {player.y})\n" | |
| # Add status information | |
| if hasattr(player, 'last_active'): | |
| import time | |
| time_diff = time.time() - player.last_active | |
| if time_diff < 30: | |
| tooltip += "🟢 Status: Active" | |
| elif time_diff < 300: | |
| tooltip += "🟡 Status: Away" | |
| else: | |
| tooltip += "🔴 Status: Idle" | |
| tooltip += "\n\n💬 Walk near to start private chat" | |
| return tooltip | |
| except Exception as e: | |
| print(f"[UI] Error generating player tooltip: {e}") | |
| return f"{getattr(player, 'name', 'Player')}\n💬 Interactive player - Walk near to chat" | |
| def create_interface(self) -> gr.Blocks: | |
| """Create the complete HuggingFace-style interface.""" | |
| with gr.Blocks( | |
| title="🎮 MMORPG with MCP Integration", | |
| theme=self.theme, | |
| css=self.custom_css | |
| ) as interface: | |
| # Hero section | |
| self.create_hero_section() | |
| # Keyboard status | |
| keyboard_status = self.create_keyboard_status() | |
| # Player state management | |
| self.player_state = gr.State({}) | |
| with gr.Tabs(): | |
| # Main Game Tab | |
| with gr.Tab("🌍 Game World", elem_classes=["tab-content"]): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| # Player registration | |
| with gr.Group(): | |
| gr.Markdown("### 🎮 Join the Adventure") | |
| with gr.Row(): | |
| player_name = gr.Textbox( | |
| label="Player Name", | |
| placeholder="Enter your character name", | |
| scale=3 | |
| ) | |
| join_btn = gr.Button("Join Game", variant="primary", scale=1) | |
| leave_btn = gr.Button("Leave Game", variant="secondary", scale=1) | |
| # Enhanced controls info | |
| gr.Markdown(""" | |
| ### ⌨️ Enhanced Controls | |
| **Mouse:** Click movement buttons below | |
| **Keyboard:** Use **WASD** or **Arrow Keys** for movement | |
| **Action:** **Spacebar** for special action | |
| 💡 *Keyboard controls automatically activate when buttons are ready* | |
| """) | |
| # Game world view with bigger map | |
| game_view = self.create_game_world_view() | |
| # Enhanced movement controls | |
| move_up, move_down, move_left, move_right, action_btn = self.create_movement_controls() | |
| with gr.Column(scale=1): | |
| # Player stats | |
| player_info = self.create_player_stats_panel() | |
| # Online players | |
| online_players = self.create_online_players_panel() | |
| # World events | |
| world_events = self.create_world_events_panel() | |
| # Enhanced chat system | |
| chat_components = self.create_chat_system() | |
| (chat_display, chat_input, chat_send, proximity_info, private_chat_group, | |
| nearby_entities, start_chat_btn, clear_all_tabs_btn, chat_tabs_state, | |
| active_tabs_display, current_chat_display, chat_input_row, | |
| private_message_input, private_send_btn) = chat_components | |
| # Documentation Tab | |
| with gr.Tab("📚 Documentation", elem_classes=["tab-content"]): | |
| self._create_documentation_content() | |
| # NPC Add-ons Tab | |
| with gr.Tab("🤖 NPC Add-ons", elem_classes=["tab-content"]): | |
| self._create_npc_addons_content() | |
| # Plugin System Tab | |
| with gr.Tab("🔌 Plugin System", elem_classes=["tab-content"]): | |
| self._create_plugin_system_content() | |
| # MCP Integration Tab | |
| with gr.Tab("🔗 MCP Integration", elem_classes=["tab-content"]): | |
| self._create_mcp_integration_content() | |
| # Architecture Tab with gr.Tab("🏗️ Architecture", elem_classes=["tab-content"]): | |
| self._create_architecture_content() | |
| # Event handlers will be wired up by the InterfaceManager | |
| return interface | |
| def _create_documentation_content(self) -> None: | |
| """Create comprehensive documentation content.""" | |
| gr.Markdown(""" | |
| <div class="doc-section"> | |
| # 📚 MMORPG Documentation | |
| Welcome to the comprehensive guide for our modern MMORPG with MCP integration! | |
| </div> | |
| """) | |
| with gr.Tabs(): | |
| with gr.Tab("🚀 Quick Start"): | |
| gr.Markdown(""" | |
| ## 🚀 Quick Start Guide | |
| ### Getting Started | |
| 1. **Join the Game**: Enter your character name and click "Join Game" | |
| 2. **Move Around**: Use WASD keys or arrow keys to move your character | |
| 3. **Interact**: Walk near NPCs (📮🏪🌤️) to interact with them | |
| 4. **Chat**: Use the chat system to communicate with other players | |
| 5. **Explore**: Check out the NPC Add-ons tab for special features | |
| ### Basic Controls | |
| - **Movement**: WASD or Arrow Keys | |
| - **Action**: Spacebar | |
| - **Chat**: Type in the chat box and press Enter | |
| - **Private Messages**: Move near players/NPCs and use the private chat system | |
| ### Game Features | |
| - **Real-time Multiplayer**: Up to 20 players can join simultaneously | |
| - **AI Agent Support**: AI agents can connect via MCP protocol | |
| - **NPC Interactions**: Each NPC has unique features and personalities | |
| - **Plugin System**: Extensible architecture for custom add-ons | |
| - **Tree Collision**: Navigate around trees in the expanded world | |
| """) | |
| with gr.Tab("🎮 Game Mechanics"): | |
| gr.Markdown(""" | |
| ## 🎮 Game Mechanics | |
| ### Player System | |
| - **Health Points (HP)**: Start with 100 HP, maximum 100 HP | |
| - **Experience Points**: Gain XP through interactions and activities | |
| - **Leveling**: Level up as you gain experience | |
| - **Gold**: Virtual currency for trading with NPCs | |
| ### World Interaction | |
| - **NPC Proximity**: Walk within range of NPCs to interact | |
| - **Collision Detection**: Trees and obstacles block movement | |
| - **Chat System**: Public and private messaging | |
| - **Real-time Updates**: All actions update in real-time | |
| ### NPCs Available | |
| - **📮 Secure Mailbox**: Read2Burn messaging system | |
| - **🏪 Donald the Trader**: Nice trading NPC with funny responses | |
| - **🚶 Roaming Rick**: Moving NPC that wanders the world | |
| - **🧙 Ancient Sage**: Mystical NPC with wisdom and magic | |
| - **🌤️ Weather Oracle**: MCP-powered weather information | |
| """) | |
| with gr.Tab("💬 Chat System"): | |
| gr.Markdown(""" | |
| ## 💬 Advanced Chat System | |
| ### Public Chat | |
| - Visible to all players in the game | |
| - Supports emojis and basic formatting | |
| - Command system with /help for available commands | |
| ### Private Messaging | |
| - **Tab-based Interface**: Multiple simultaneous conversations | |
| - **Proximity-based**: Start chats with nearby players/NPCs | |
| - **Pin/Unpin Tabs**: Keep important conversations open | |
| - **Auto-refresh**: Real-time message updates | |
| ### Chat Commands | |
| - `/help` - Show available commands | |
| - `/players` - List online players | |
| - `/stats` - Show your character stats | |
| - `/clear` - Clear chat history | |
| - `/whisper <player> <message>` - Send private message | |
| ### Chat Features | |
| - **Message History**: Persistent chat history per conversation | |
| - **Unread Indicators**: Visual badges for new messages | |
| - **Emoji Support**: Full emoji support in messages | |
| - **Timestamps**: All messages include timestamps | |
| """) | |
| with gr.Tab("🔧 Technical"): | |
| gr.Markdown(""" | |
| ## 🔧 Technical Details | |
| ### Architecture | |
| - **Clean Architecture**: Separation of concerns with interfaces, services, and facades | |
| - **Domain Models**: Core game entities (Player, GameWorld) | |
| - **Service Layer**: Business logic separated into focused services | |
| - **Facade Pattern**: Simplified interface for complex operations | |
| ### Technologies Used | |
| - **Frontend**: Gradio with custom HuggingFace-style UI | |
| - **Backend**: Python with asyncio for real-time features | |
| - **MCP Integration**: Model Context Protocol for AI agent connectivity | |
| - **Plugin System**: Hot-reloadable plugin architecture | |
| ### Performance Features | |
| - **Thread Safety**: Proper locking mechanisms throughout - **Efficient Updates**: Optimized real-time data synchronization | |
| - **Memory Management**: Careful resource management for long-running sessions | |
| - **Error Handling**: Comprehensive exception handling and recovery | |
| """) | |
| def _create_npc_addons_content(self) -> None: | |
| """Create NPC add-ons content.""" | |
| gr.Markdown(""" | |
| <div class="doc-section"> | |
| # 🤖 NPC Add-ons System | |
| Each NPC in the game has unique functionality through our extensible add-on system. | |
| Walk near NPCs in the game world to unlock their features! | |
| </div> | |
| """) | |
| with gr.Tabs(): | |
| # Auto-discover and create tabs for registered addons | |
| self._create_dynamic_addon_tabs() | |
| # Legacy static tabs for backward compatibility | |
| with gr.Tab("🔥 Read2Burn Mailbox"): | |
| gr.Markdown(""" | |
| ## 🔥 Secure Read2Burn Messaging | |
| The Read2Burn Mailbox provides secure, self-destructing messaging between players. | |
| ### Features | |
| - **Self-Destructing Messages**: Messages automatically delete after reading | |
| - **Secure Delivery**: End-to-end message protection | |
| - **Anonymous Options**: Send messages without revealing identity | |
| - **Time-based Expiry**: Messages expire after set time limits | |
| ### How to Use | |
| 1. Walk near the 📮 Secure Mailbox NPC | |
| 2. Visit this NPC Add-ons tab | |
| 3. Select the Read2Burn tab | |
| 4. Compose and send secure messages | |
| *Note: This feature requires proximity to the Secure Mailbox NPC* | |
| """) | |
| # Read2Burn interface | |
| self._create_read2burn_interface() | |
| with gr.Tab("🏪 Donald the Trader"): | |
| gr.Markdown(""" | |
| ## 🏪 Donald the Trader | |
| Meet Donald, our charismatic trader with a unique personality and the best deals! | |
| ### Personality | |
| Donald speaks in a distinctive style with phrases like: | |
| - "Listen, I make the best deals. Nobody makes deals like me, believe me!" | |
| - "You're looking at tremendous value here. Tremendous!" | |
| - "This item? Fantastic. The best. Everyone says so." | |
| ### Trading Features | |
| - **Unique Items**: Exclusive merchandise with "tremendous" quality | |
| - **Negotiation**: Engage in deal making deal-making | |
| - **Special Offers**: Limited-time deals with signature flair | |
| - **Personality Responses**: Context-aware, entertaining interactions | |
| ### How to Interact | |
| 1. Walk near the 🏪 Donald the Trader NPC | |
| 2. Start a private chat or use the public chat | |
| 3. Mention trading, deals, or items to trigger responses | |
| 4. Enjoy the unique personality and humor! | |
| """) | |
| with gr.Tab("🌤️ Weather Oracle"): | |
| gr.Markdown(""" | |
| ## 🌤️ Weather Oracle (MCP) | |
| The Weather Oracle demonstrates MCP (Model Context Protocol) integration. | |
| ### MCP Features | |
| - **External Service**: Connects to external weather APIs | |
| - **Real-time Data**: Live weather information | |
| - **Protocol Demo**: Shows MCP integration capabilities | |
| - **Extensible**: Template for other MCP services | |
| ### Available Commands | |
| - Weather forecasts for any location | |
| - Current conditions and alerts | |
| - Historical weather data | |
| - Climate information | |
| *Note: This is a demonstration of MCP protocol integration* | |
| """) | |
| with gr.Tab("🏪 Example Merchant"): | |
| # Get the example merchant addon interface | |
| try: | |
| from ..addons.example_npc_addon import ExampleNPCAddon | |
| example_addon = ExampleNPCAddon() | |
| example_addon.get_interface() | |
| except Exception as e: | |
| gr.Markdown(f""" | |
| ## 🏪 Example Merchant | |
| *Interface temporarily unavailable: {str(e)}* | |
| Please check that the Example Merchant addon is properly installed. | |
| """) | |
| gr.Markdown(""" | |
| ### 🛠️ Add-on Development | |
| Want to create your own NPC add-on? Check out the Plugin System tab for development guidelines! """) | |
| def _create_dynamic_addon_tabs(self): | |
| """Create tabs for all registered addons that have ui_tab_name.""" | |
| try: | |
| # Get registered addons from the global registry | |
| registered_addons = get_registered_addons() | |
| for addon_id, addon in registered_addons.items(): | |
| # Check if addon wants a UI tab | |
| if hasattr(addon, 'ui_tab_name') and addon.ui_tab_name: | |
| try: | |
| with gr.Tab(f"{addon.ui_tab_name}"): | |
| # Create a header with addon info | |
| gr.Markdown(f""" | |
| ## {addon.addon_name} | |
| *Self-contained addon with auto-registration* | |
| **Addon ID:** `{addon.addon_id}` | |
| **NPC Location:** {f"({addon.npc_config['x']}, {addon.npc_config['y']})" if addon.npc_config else "No NPC"} | |
| --- | |
| """) | |
| # Render the addon's interface | |
| addon.get_interface() | |
| except Exception as e: | |
| # Create error tab for broken addons | |
| with gr.Tab(f"❌ {addon.ui_tab_name}"): | |
| gr.Markdown(f""" | |
| ## ❌ {addon.addon_name} | |
| *This addon interface failed to load* | |
| **Error:** {str(e)} | |
| **Addon ID:** `{addon.addon_id}` | |
| Please check the addon configuration and try restarting the server. | |
| """) | |
| except Exception as e: | |
| print(f"[UI] Error creating dynamic addon tabs: {e}") | |
| # Fallback: create a single tab explaining the issue | |
| with gr.Tab("⚠️ Addon System"): | |
| gr.Markdown(f""" | |
| ## ⚠️ Addon System Error | |
| Failed to load registered addons: {str(e)} | |
| Please check that the game engine is running and addons are properly registered. | |
| """) | |
| def _create_plugin_system_content(self) -> None: | |
| """Create plugin system documentation.""" | |
| gr.Markdown(""" | |
| <div class="doc-section"> | |
| # 🔌 Plugin System Architecture | |
| Our game features a sophisticated plugin system that allows for easy extension and customization. | |
| Plugins are automatically discovered and can be hot-reloaded without restarting the server. | |
| </div> | |
| """) | |
| with gr.Tabs(): | |
| with gr.Tab("📋 Overview"): | |
| gr.Markdown(""" | |
| ## 🔌 Plugin System Overview | |
| ### Key Features | |
| - **Hot Reload**: Load/unload plugins without restarting | |
| - **Dependency Management**: Automatic dependency resolution | |
| - **Event System**: Plugin communication through events | |
| - **Type Safety**: Full interface-based architecture | |
| - **Error Handling**: Robust error recovery and logging | |
| ### Plugin Types | |
| 1. **Game Plugins**: Extend core game functionality | |
| 2. **NPC Plugins**: Add new NPC behaviors and interactions | |
| 3. **UI Plugins**: Custom user interface components | |
| 4. **Service Plugins**: External service integrations | |
| 5. **Tool Plugins**: Development and administrative tools | |
| ### Plugin Directory Structure | |
| ``` | |
| plugins/ | |
| ├── my_plugin.py # Single-file plugin | |
| ├── complex_plugin/ # Multi-file plugin | |
| │ ├── __init__.py | |
| │ ├── plugin.py | |
| │ ├── config.json | |
| │ └── assets/ | |
| └── examples/ # Example plugins | |
| ├── hello_world.py | |
| ├── custom_npc.py | |
| └── weather_service.py | |
| ``` | |
| """) | |
| with gr.Tab("💻 Development"): | |
| gr.Markdown(""" | |
| ## 💻 Plugin Development Guide | |
| ### Simple Plugin Example | |
| ```python | |
| from src.interfaces.plugin_interfaces import IGamePlugin, PluginMetadata | |
| class HelloWorldPlugin(IGamePlugin): | |
| @property | |
| def metadata(self) -> PluginMetadata: | |
| return PluginMetadata( | |
| name="Hello World", | |
| version="1.0.0", | |
| description="A simple greeting plugin", | |
| author="Your Name" | |
| ) | |
| def initialize(self) -> bool: | |
| print("Hello, World!") | |
| return True | |
| def cleanup(self) -> bool: | |
| print("Goodbye, World!") | |
| return True | |
| # Plugin instance | |
| plugin = HelloWorldPlugin() | |
| ``` | |
| ### NPC Plugin Example | |
| ```python | |
| from src.interfaces.plugin_interfaces import INPCPlugin | |
| class CustomNPCPlugin(INPCPlugin): | |
| @property | |
| def npc_id(self) -> str: | |
| return "custom_trader" | |
| def handle_interaction(self, player_id: str, message: str) -> str: | |
| return f"Hello {player_id}! You said: {message}" | |
| def get_npc_data(self) -> dict: | |
| return { | |
| "name": "Custom Trader", | |
| "position": {"x": 100, "y": 100}, | |
| "sprite": "🛒" | |
| } | |
| ``` | |
| ### Installation | |
| 1. Create your plugin file in the `plugins/` directory | |
| 2. The plugin will be automatically discovered | |
| 3. Check the Plugin Manager for status | |
| 4. Use hot-reload to activate without restart | |
| """) | |
| with gr.Tab("🔧 Plugin Manager"): | |
| gr.Markdown("## 🔧 Plugin Management Interface") | |
| # Plugin status display | |
| gr.HTML(""" | |
| <div class="feature-grid"> | |
| <div class="plugin-card"> | |
| <h4>📂 Plugin Discovery</h4> | |
| <p>Automatically scans the plugins/ directory for new plugins</p> | |
| <div class="status-indicator status-online"></div> Active | |
| </div> | |
| <div class="plugin-card"> | |
| <h4>🔄 Hot Reload</h4> | |
| <p>Load and unload plugins without server restart</p> | |
| <div class="status-indicator status-online"></div> Available | |
| </div> | |
| <div class="plugin-card"> | |
| <h4>📊 Dependency Management</h4> | |
| <p>Automatic dependency resolution and loading order</p> | |
| <div class="status-indicator status-online"></div> Active | |
| </div> | |
| <div class="plugin-card"> | |
| <h4>⚠️ Error Handling</h4> | |
| <p>Robust error recovery and detailed logging</p> | |
| <div class="status-indicator status-online"></div> Active | |
| </div> | |
| </div> | |
| """) | |
| # Plugin actions | |
| with gr.Row(): | |
| refresh_plugins_btn = gr.Button("🔄 Refresh Plugins", variant="primary") | |
| reload_all_btn = gr.Button("🔄 Reload All", variant="secondary") | |
| plugin_status = gr.JSON( | |
| label="Plugin Status", | |
| value={ | |
| "total_plugins": 0, | |
| "active_plugins": 0, | |
| "failed_plugins": 0, | |
| "last_scan": "Never" | |
| } | |
| ) | |
| def _create_mcp_integration_content(self) -> None: | |
| """Create MCP integration documentation.""" | |
| gr.Markdown(""" | |
| <div class="doc-section"> | |
| # 🔗 MCP Integration | |
| Model Context Protocol (MCP) integration allows AI agents to connect and interact with the game world. | |
| </div> | |
| """) | |
| with gr.Tabs(): | |
| with gr.Tab("📡 Server Status"): | |
| gr.Markdown("## 📡 MCP Server Status") | |
| mcp_status = gr.JSON( | |
| label="Server Information", | |
| value={ | |
| "server_status": "🟢 Active", | |
| "server_url": "This Gradio app serves as MCP server", | |
| "tools_available": [ | |
| "register_ai_agent", | |
| "move_agent", | |
| "send_chat", | |
| "get_game_state", | |
| "interact_with_npc" | |
| ], | |
| "active_ai_agents": 0, | |
| "total_players": 0 | |
| } | |
| ) | |
| with gr.Tab("🧪 AI Agent Testing"): | |
| self._create_ai_testing_tab() | |
| def _create_ai_testing_tab(self): | |
| """Create enhanced AI testing tab.""" | |
| gr.Markdown("## 🧪 Test AI Agent Integration") | |
| gr.Markdown("*Use this to simulate AI agent connections for testing*") | |
| with gr.Row(): | |
| ai_name = gr.Textbox( | |
| label="AI Agent Name", | |
| placeholder="Claude the Explorer", | |
| scale=3 | |
| ) | |
| register_ai_btn = gr.Button("Register AI Agent", variant="primary", scale=1) | |
| with gr.Row(): | |
| ai_action = gr.Dropdown( | |
| choices=["move up", "move down", "move left", "move right", "chat"], | |
| label="AI Action", | |
| scale=2 | |
| ) | |
| ai_message = gr.Textbox( | |
| label="AI Message (for chat)", | |
| placeholder="Hello humans!", | |
| scale=3 | |
| ) | |
| execute_ai_btn = gr.Button("Execute AI Action", variant="secondary") | |
| ai_result = gr.Textbox(label="AI Action Result", interactive=False, lines=5) | |
| # Raw Data Testing Section | |
| gr.Markdown("### 🧪 Test Raw Data Access") | |
| gr.Markdown("*Test accessing comprehensive world data without HTML parsing*") | |
| with gr.Row(): | |
| test_raw_data_btn = gr.Button("Get Raw World Data", variant="secondary") | |
| test_available_npcs_btn = gr.Button("Get Available NPCs", variant="secondary") | |
| raw_data_result = gr.JSON(label="Raw Data Output", visible=True) | |
| # Wire up handlers (simplified versions of original handlers) | |
| register_ai_btn.click( | |
| self._handle_register_ai_agent, | |
| inputs=[ai_name], | |
| outputs=[ai_result] | |
| ) | |
| execute_ai_btn.click( | |
| self._handle_execute_ai_action, | |
| inputs=[ai_action, ai_message], | |
| outputs=[ai_result] | |
| ) | |
| test_raw_data_btn.click( | |
| self._handle_test_raw_world_data, | |
| inputs=[], | |
| outputs=[raw_data_result] | |
| ) | |
| test_available_npcs_btn.click( | |
| self._handle_test_available_npcs, | |
| inputs=[], | |
| outputs=[raw_data_result] | |
| ) | |
| # ================================================================= | |
| def _handle_register_ai_agent(self, ai_name: str) -> str: | |
| """Handle AI agent registration.""" | |
| if not ai_name or not ai_name.strip(): | |
| return "❌ Please enter a valid AI agent name" | |
| try: | |
| agent_id = f"test_agent_{uuid.uuid4().hex[:8]}" | |
| result = self.game_facade.register_ai_agent(agent_id, ai_name.strip()) | |
| if result: | |
| if not hasattr(self, 'registered_agents'): | |
| self.registered_agents = {} | |
| self.registered_agents[agent_id] = ai_name.strip() | |
| return f"✅ AI agent '{ai_name.strip()}' registered successfully!\nAgent ID: {agent_id}" | |
| else: | |
| return f"❌ Failed to register AI agent '{ai_name.strip()}'" | |
| except Exception as e: | |
| return f"❌ Error registering AI agent: {str(e)}" | |
| def _handle_execute_ai_action(self, action: str, message: str) -> str: | |
| """Handle AI agent action execution.""" | |
| if not hasattr(self, 'registered_agents') or not self.registered_agents: | |
| return "❌ No AI agents registered. Please register an AI agent first!" | |
| if not action: | |
| return "❌ Please select an action to execute" | |
| try: | |
| agent_id = list(self.registered_agents.keys())[0] | |
| agent_name = self.registered_agents[agent_id] | |
| if action.startswith("move"): | |
| direction = action.split()[1] if len(action.split()) > 1 else action | |
| result = self.game_facade.move_ai_agent(agent_id, direction) | |
| if result.get("success"): | |
| position = result.get("position", {}) | |
| return f"🤖 {agent_name} moved {direction} successfully!\nNew position: ({position.get('x', '?')}, {position.get('y', '?')})" | |
| else: | |
| error_msg = result.get("error", "Movement failed") | |
| return f"❌ {agent_name} movement failed: {error_msg}" | |
| elif action == "chat": | |
| if not message or not message.strip(): | |
| return "❌ Please enter a chat message" | |
| result = self.game_facade.ai_agent_chat(agent_id, message.strip()) | |
| if result.get("success"): | |
| return f"💬 {agent_name} sent chat message: '{message.strip()}'" | |
| else: | |
| error_msg = result.get("error", "Chat failed") | |
| return f"❌ {agent_name} chat failed: {error_msg}" | |
| else: | |
| return f"❓ Unknown action: {action}" | |
| except Exception as e: | |
| return f"❌ Error executing AI action: {str(e)}" | |
| except Exception as e: | |
| return f"❌ Error executing AI action: {str(e)}" | |
| def _handle_test_raw_world_data(self) -> Dict: | |
| """Handle testing raw world data access.""" | |
| try: | |
| raw_data = self.game_facade.get_raw_world_data() | |
| return raw_data | |
| except Exception as e: | |
| return {"error": f"Failed to get raw world data: {str(e)}"} | |
| def _handle_test_available_npcs(self) -> List[Dict]: | |
| """Handle testing available NPCs data access.""" | |
| try: | |
| npcs_data = self.game_facade.get_available_npcs() | |
| return npcs_data | |
| except Exception as e: | |
| return [{"error": f"Failed to get NPCs data: {str(e)}"}] | |
| def _create_architecture_content(self) -> None: | |
| """Create architecture documentation.""" | |
| gr.Markdown(""" | |
| <div class="doc-section"> | |
| # 🏗️ System Architecture | |
| Our MMORPG follows clean architecture principles with clear separation of concerns. | |
| </div> | |
| """) | |
| with gr.Tabs(): | |
| with gr.Tab("🏛️ Clean Architecture"): | |
| gr.Markdown(""" | |
| ## 🏛️ Clean Architecture Implementation | |
| ### Architecture Layers | |
| ``` | |
| ┌─────────────────────────────────────┐ | |
| │ UI Layer │ | |
| │ (Gradio Interface, HF-UI) │ | |
| └─────────────────────────────────────┘ | |
| ↓ Depends on | |
| ┌─────────────────────────────────────┐ | |
| │ Facade Layer │ | |
| │ (GameFacade) │ | |
| └─────────────────────────────────────┘ | |
| ↓ Depends on | |
| ┌─────────────────────────────────────┐ | |
| │ Service Layer │ | |
| │ (PlayerService, ChatService, etc) │ | |
| └─────────────────────────────────────┘ | |
| ↓ Depends on | |
| ┌─────────────────────────────────────┐ | |
| │ Core Layer │ | |
| │ (Player, GameWorld, Engine) │ | |
| └─────────────────────────────────────┘ | |
| ``` | |
| ### Key Principles | |
| - **Dependency Inversion**: High-level modules don't depend on low-level modules | |
| - **Interface Segregation**: Small, focused interfaces | |
| - **Single Responsibility**: Each class has one reason to change | |
| - **Open/Closed**: Open for extension, closed for modification | |
| """) | |
| with gr.Tab("🔧 Services"): | |
| gr.Markdown(""" | |
| ## 🔧 Service Architecture | |
| ### Core Services | |
| **PlayerService** | |
| - Player management and lifecycle | |
| - Movement and position tracking | |
| - Health and experience management | |
| - Statistics and progression | |
| **ChatService** | |
| - Public and private messaging | |
| - Message history and persistence | |
| - Command processing | |
| - Real-time communication | |
| **NPCService** | |
| - NPC behavior management | |
| - Interaction handling | |
| - Personality systems (like Donald's responses) | |
| - Movement and AI logic | |
| **MCPService** | |
| - AI agent registration and management | |
| - MCP protocol implementation | |
| - External service integration | |
| - Tool and capability exposure | |
| **PluginService** | |
| - Plugin discovery and loading | |
| - Dependency management | |
| - Hot-reload functionality | |
| - Event system coordination | |
| ### Service Communication | |
| - Services communicate through well-defined interfaces | |
| - Event-driven architecture for loose coupling | |
| - Centralized coordination through GameEngine | |
| - Thread-safe operations with proper locking | |
| """) | |
| with gr.Tab("💾 Data Flow"): | |
| gr.Markdown(""" | |
| ## 💾 Data Flow Architecture | |
| ### Request Flow | |
| ``` | |
| User Input → UI Layer → Facade → Service → Core → Response | |
| ``` | |
| ### Real-time Updates | |
| ``` | |
| Game State Change → Service → Event System → UI Update | |
| ``` | |
| ### Plugin Integration | |
| ``` | |
| Plugin Event → Plugin Service → Core Services → Game State | |
| ``` | |
| ### MCP Agent Flow | |
| ``` | |
| External Agent → MCP Protocol → MCP Service → Game Services | |
| ``` | |
| ### Error Handling | |
| - Comprehensive exception handling at each layer | |
| - Graceful degradation for non-critical failures | |
| - Detailed logging for debugging and monitoring | |
| - Recovery mechanisms for transient failures | |
| ### Performance Optimizations | |
| - Efficient state updates with minimal data transfer | |
| - Optimized rendering for game world visualization | |
| - Smart caching for frequently accessed data | |
| - Asynchronous operations where beneficial | |
| """) | |
| def create_join_section(self) -> tuple: | |
| """Create the join game section with input and buttons.""" | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| join_name = gr.Textbox( | |
| label="Player Name", | |
| placeholder="Enter your player name...", | |
| elem_id="join-name-input" | |
| ) | |
| with gr.Column(scale=1): | |
| join_btn = gr.Button("Join Game 🎮", variant="primary", elem_id="join-btn") | |
| leave_btn = gr.Button("Leave Game 🚪", variant="secondary", elem_id="leave-btn") | |
| # Player list display | |
| player_list = gr.Dataframe( | |
| headers=["Name", "Level", "Position", "Status"], | |
| label="Online Players", | |
| elem_id="player-list" | |
| ) | |
| return (join_name, join_btn, leave_btn, player_list) | |
| def create_game_display(self) -> tuple: | |
| """Create the main game display section.""" | |
| with gr.Row(): | |
| with gr.Column(scale=3): | |
| game_canvas = gr.HTML( | |
| value=self._get_initial_game_view(), | |
| label="Game World", | |
| elem_id="game-canvas" | |
| ) | |
| with gr.Column(scale=1): | |
| status_display = gr.JSON( | |
| label="Game Status", | |
| elem_id="status-display" | |
| ) | |
| return (game_canvas, status_display) | |
| def create_documentation_tabs(self): | |
| """Create documentation tabs using the DocumentationTabs component.""" | |
| from .documentation_tabs import DocumentationTabs | |
| doc_tabs = DocumentationTabs() | |
| return doc_tabs.create_documentation_tabs() | |
| def _get_initial_game_view(self) -> str: | |
| """Get the initial HTML for the game view.""" | |
| return """ | |
| <div id="game-world" style="width: 800px; height: 600px; border: 2px solid #333; | |
| position: relative; background: linear-gradient(135deg, #2d5a27, #4a7c40); | |
| border-radius: 8px; overflow: hidden;"> | |
| <div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); | |
| color: white; text-align: center; font-size: 18px;"> | |
| 🎮 Join the game to start your adventure! | |
| </div> | |
| </div> | |
| """ | |
| def _create_read2burn_interface(self) -> None: | |
| """Create the Read2Burn mailbox interface.""" | |
| from ..addons.read2burn_addon import read2burn_service | |
| with gr.Column(): | |
| gr.Markdown(""" | |
| ### 🔥 Read2Burn Commands | |
| **Commands:** | |
| - `create Your secret message here` - Create new message | |
| - `read MESSAGE_ID` - Read message (destroys it!) | |
| - `list` - Show your created messages | |
| - `help` - Show detailed help | |
| """) | |
| with gr.Row(): | |
| command_input = gr.Textbox( | |
| label="Command", | |
| placeholder="create Hello, this message will self-destruct!", | |
| scale=3 | |
| ) | |
| send_btn = gr.Button("Send", variant="primary", scale=1) | |
| result_output = gr.Textbox( | |
| label="Mailbox Response", | |
| lines=6, | |
| interactive=False | |
| ) | |
| # Message history | |
| message_history = gr.Dataframe( | |
| headers=["Message ID", "Created", "Status", "Reads Left"], | |
| label="Your Messages", | |
| interactive=False | |
| ) | |
| def handle_mailbox_command(command: str): | |
| from ..core.game_engine import get_game_engine | |
| if not command.strip(): | |
| return "❌ Please enter a command", [] | |
| try: | |
| # Get current player from game engine | |
| engine = get_game_engine() | |
| players = engine.get_world().get_all_players() | |
| if not players: | |
| return "❌ No players in the game! Please join the game first.", [] | |
| # Use the most recently active player | |
| player_id = max(players.keys(), key=lambda pid: players[pid].last_active) | |
| player_name = players[player_id].name | |
| print(f"[Read2Burn] Command '{command}' from player {player_name} ({player_id})") | |
| result = read2burn_service.handle_command(player_id, command) | |
| history = read2burn_service.get_player_message_history(player_id) | |
| # Add player info to the result | |
| result = f"**Player:** {player_name}\n\n{result}" | |
| return result, history | |
| except Exception as e: | |
| print(f"[Read2Burn] Error: {e}") | |
| return f"❌ Error processing command: {str(e)}", [] | |
| send_btn.click( | |
| handle_mailbox_command, | |
| inputs=[command_input], | |
| outputs=[result_output, message_history] | |
| ) | |
| command_input.submit( | |
| handle_mailbox_command, | |
| inputs=[command_input], | |
| outputs=[result_output, message_history] | |
| ) | |