Chris4K's picture
Upload 201 files
6c6097e verified
"""
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('"', '&quot;').replace("'", '&#39;')
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('"', '&quot;').replace("'", '&#39;')
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]
)