|
"""
|
|
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()
|
|
|
|
|
|
self.player_state = None
|
|
self.chat_tabs_state = None
|
|
|
|
|
|
self.theme = gr.themes.Soft(
|
|
primary_hue="blue",
|
|
secondary_hue="slate",
|
|
neutral_hue="slate",
|
|
radius_size="md",
|
|
spacing_size="md"
|
|
)
|
|
|
|
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(
|
|
|
|
|
|
)
|
|
|
|
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."""
|
|
|
|
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")
|
|
|
|
|
|
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_state = gr.State({})
|
|
active_tabs_display = gr.HTML(
|
|
value="<div class='chat-tab'>No active chats</div>",
|
|
label="Active Chats"
|
|
)
|
|
|
|
|
|
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."""
|
|
|
|
|
|
npc_html = ""
|
|
for npc in self.game_facade.get_all_npcs().values():
|
|
try:
|
|
|
|
tooltip_info = self._get_npc_tooltip_info(npc)
|
|
|
|
|
|
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)
|
|
|
|
|
|
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}")
|
|
|
|
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>"""
|
|
|
|
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)
|
|
|
|
|
|
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)
|
|
|
|
|
|
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}")
|
|
|
|
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}")
|
|
|
|
|
|
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:
|
|
|
|
from ..interfaces.npc_addon import get_registered_addons
|
|
registered_addons = get_registered_addons()
|
|
|
|
|
|
addon = None
|
|
npc_id = npc.get('id', '')
|
|
|
|
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', '')
|
|
|
|
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
|
|
|
|
|
|
if addon and hasattr(addon, 'npc_config') and addon.npc_config:
|
|
description = addon.npc_config.get('description', '')
|
|
if description:
|
|
return 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"
|
|
|
|
|
|
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:
|
|
|
|
|
|
self.create_hero_section()
|
|
|
|
|
|
keyboard_status = self.create_keyboard_status()
|
|
|
|
|
|
self.player_state = gr.State({})
|
|
|
|
with gr.Tabs():
|
|
|
|
with gr.Tab("🌍 Game World", elem_classes=["tab-content"]):
|
|
with gr.Row():
|
|
with gr.Column(scale=2):
|
|
|
|
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)
|
|
|
|
|
|
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_view = self.create_game_world_view()
|
|
|
|
|
|
move_up, move_down, move_left, move_right, action_btn = self.create_movement_controls()
|
|
|
|
with gr.Column(scale=1):
|
|
|
|
player_info = self.create_player_stats_panel()
|
|
|
|
|
|
online_players = self.create_online_players_panel()
|
|
|
|
|
|
world_events = self.create_world_events_panel()
|
|
|
|
|
|
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
|
|
|
|
|
|
with gr.Tab("📚 Documentation", elem_classes=["tab-content"]):
|
|
self._create_documentation_content()
|
|
|
|
|
|
with gr.Tab("🤖 NPC Add-ons", elem_classes=["tab-content"]):
|
|
self._create_npc_addons_content()
|
|
|
|
|
|
with gr.Tab("🔌 Plugin System", elem_classes=["tab-content"]):
|
|
self._create_plugin_system_content()
|
|
|
|
|
|
with gr.Tab("🔗 MCP Integration", elem_classes=["tab-content"]):
|
|
self._create_mcp_integration_content()
|
|
|
|
|
|
self._create_architecture_content()
|
|
|
|
|
|
|
|
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():
|
|
|
|
self._create_dynamic_addon_tabs()
|
|
|
|
|
|
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*
|
|
""")
|
|
|
|
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"):
|
|
|
|
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:
|
|
|
|
registered_addons = get_registered_addons()
|
|
|
|
for addon_id, addon in registered_addons.items():
|
|
|
|
if hasattr(addon, 'ui_tab_name') and addon.ui_tab_name:
|
|
try:
|
|
with gr.Tab(f"{addon.ui_tab_name}"):
|
|
|
|
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"}
|
|
|
|
---
|
|
""")
|
|
|
|
|
|
addon.get_interface()
|
|
|
|
except Exception as e:
|
|
|
|
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}")
|
|
|
|
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")
|
|
|
|
|
|
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>
|
|
""")
|
|
|
|
|
|
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)
|
|
|
|
|
|
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)
|
|
|
|
|
|
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 = 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 = 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:
|
|
|
|
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.", []
|
|
|
|
|
|
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)
|
|
|
|
|
|
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]
|
|
)
|
|
|