Chris4K commited on
Commit
4c75d73
·
verified ·
1 Parent(s): feabcf7

Upload 195 files

Browse files

Tempus .... fugit.
Refactored version.

This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +3 -0
  2. DEVELOPER_DOCUMENTATION.md +189 -0
  3. Documentation/DEVELOPER_DOCUMENTATION.md +210 -0
  4. Documentation/NPC_Addon_Development_Guide.md +1312 -0
  5. Documentation/NPC_Addon_Development_Guide_OLD.md +1110 -0
  6. Documentation/NPC_Addon_Development_Guide_Updated.md +1312 -0
  7. Documentation/ROADMAP.md +375 -0
  8. Documentation/Simple_Game_Client_Guide.md +744 -0
  9. Documentation/sample_gradio_mcp_server.py +475 -0
  10. Documentation/simple_game_client.py +485 -0
  11. Simple_Game_Client_Guide.md +744 -0
  12. USER_DOCUMENTATION.md +907 -0
  13. app.py +0 -0
  14. app_original_backup.py +0 -0
  15. plugins/__pycache__/enhanced_chat_plugin.cpython-313.pyc +0 -0
  16. plugins/__pycache__/enhanced_chat_plugin_final.cpython-313.pyc +0 -0
  17. plugins/__pycache__/enhanced_chat_plugin_fixed.cpython-313.pyc +0 -0
  18. plugins/__pycache__/plugin_registry.cpython-313.pyc +0 -0
  19. plugins/__pycache__/sample_plugin.cpython-313.pyc +0 -0
  20. plugins/__pycache__/trading_system_plugin.cpython-313.pyc +0 -0
  21. plugins/__pycache__/weather_plugin.cpython-313.pyc +0 -0
  22. plugins/enhanced_chat_plugin.py +449 -0
  23. plugins/enhanced_chat_plugin.py.backup +586 -0
  24. plugins/enhanced_chat_plugin_fixed.py +264 -0
  25. plugins/plugin_registry.conf +62 -0
  26. plugins/sample_plugin.py +54 -0
  27. plugins/trading_system_plugin.py +378 -0
  28. plugins/weather_plugin.py +187 -0
  29. pytest.ini +30 -0
  30. quick_test.py +47 -0
  31. requirements.txt +53 -0
  32. setup.bat +36 -0
  33. setup.ps1 +41 -0
  34. setup.py +106 -0
  35. src/__init__.py +11 -0
  36. src/__pycache__/__init__.cpython-313.pyc +0 -0
  37. src/addons/__init__.py +5 -0
  38. src/addons/__pycache__/__init__.cpython-313.pyc +0 -0
  39. src/addons/__pycache__/example_npc_addon.cpython-313.pyc +0 -0
  40. src/addons/__pycache__/read2burn_addon.cpython-313.pyc +0 -0
  41. src/addons/__pycache__/weather_oracle_addon.cpython-313.pyc +0 -0
  42. src/addons/example_npc_addon.py +230 -0
  43. src/addons/fortune_teller_addon.py +240 -0
  44. src/addons/magic_shop_addon.py +509 -0
  45. src/addons/read2burn_addon.py +213 -0
  46. src/addons/self_contained_example_addon.py +215 -0
  47. src/addons/simple_trader_addon.py +186 -0
  48. src/addons/weather_oracle_addon.py +380 -0
  49. src/core/__pycache__/game_engine.cpython-313.pyc +0 -0
  50. src/core/__pycache__/player.cpython-313.pyc +0 -0
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ tests/unit/services/__pycache__/test_npc_service_corrected.cpython-313-pytest-8.3.5.pyc filter=lfs diff=lfs merge=lfs -text
37
+ tests/unit/services/__pycache__/test_npc_service_fixed.cpython-313-pytest-8.3.5.pyc filter=lfs diff=lfs merge=lfs -text
38
+ tests/unit/services/__pycache__/test_npc_service.cpython-313-pytest-8.3.5.pyc filter=lfs diff=lfs merge=lfs -text
DEVELOPER_DOCUMENTATION.md ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🛠️ MMORPG with MCP Integration - Developer Documentation
2
+
3
+ ## 📖 Table of Contents
4
+
5
+ 1. [Introduction](#introduction)
6
+ 2. [Project Setup](#project-setup)
7
+ 3. [Architecture Overview](#architecture-overview)
8
+ 4. [MCP API Reference](#mcp-api-reference)
9
+ * [Connection](#connection)
10
+ * [Available Tools/Commands](#available-toolscommands)
11
+ * [Common Operations](#common-operations)
12
+ 5. [Extending the Game](#extending-the-game)
13
+ * [NPC Add-on Development](#npc-add-on-development)
14
+ * [Adding New Game Mechanics](#adding-new-game-mechanics)
15
+ 6. [AI Agent Integration](#ai-agent-integration)
16
+ 7. [Debugging](#debugging)
17
+
18
+ ---
19
+
20
+ ## 📜 Introduction
21
+
22
+ This document provides technical guidance for developers working on the **MMORPG with MCP Integration** project. It covers project setup, architecture, API usage for client/agent development, and how to extend the game's functionalities.
23
+
24
+ ---
25
+
26
+ ## ⚙️ Project Setup
27
+
28
+ ### Prerequisites
29
+ * Python 3.8 or higher
30
+ * `pip` for package management
31
+
32
+ ### Getting the Code
33
+ Clone the repository to your local machine (if you haven't already).
34
+
35
+ ### Installation
36
+ 1. Navigate to the project directory (e.g., `c:\Users\Chris4K\Projekte\projecthub\projects\MMOP_second_try`).
37
+ ```powershell
38
+ cd c:\Users\Chris4K\Projekte\projecthub\projects\MMOP_second_try
39
+ ```
40
+ 2. Install the required Python dependencies:
41
+ ```bash
42
+ pip install gradio mcp aiohttp asyncio
43
+ ```
44
+ (Ensure other project-specific dependencies from your `requirements.txt` are also installed).
45
+
46
+ ### Running the Game Server
47
+ Execute the main application file (assumed to be `app.py` in the root of `MMOP_second_try`):
48
+ ```bash
49
+ python app.py
50
+ ```
51
+ The server should start, typically making the Gradio UI available at `http://127.0.0.1:7868` and the MCP SSE endpoint at `http://127.0.0.1:7868/gradio_api/mcp/sse`.
52
+
53
+ ---
54
+
55
+ ## 🏗️ Architecture Overview (High-Level)
56
+
57
+ * **Game Server (`app.py`)**: The main entry point. Initializes and runs the Gradio web interface, integrates game components, and likely hosts the MCP endpoint.
58
+ * **Game Engine (`src/core/game_engine.py`)**: Contains the core game logic, manages world state, player data, NPC interactions, and game rules.
59
+ * **UI Layer (`src/ui/interface_manager.py`, `src/ui/huggingface_ui.py`)**: Manages the Gradio user interface, defining layouts, components, and event handling for human players.
60
+ * **MCP Integration Layer**: A facade or service within the server that exposes game functionalities to MCP clients. This allows AI agents or other external systems to interact with the game.
61
+ * **NPC Addons**: Modular components that extend NPC functionalities. See `NPC_Addon_Development_Guide.md`.
62
+
63
+ ---
64
+
65
+ ## 🔌 MCP API Reference
66
+
67
+ The game server exposes its functionalities via the Model Context Protocol (MCP), allowing external clients (like AI agents or custom tools) to interact with the game world programmatically.
68
+
69
+ ### Connection
70
+ * **Endpoint URL**: `http://127.0.0.1:7868/gradio_api/mcp/sse` (for Server-Sent Events)
71
+ * **Protocol**: MCP over SSE.
72
+ * **Client Implementation**: Refer to `simple_game_client.py` for a reference Python client. It uses `mcp.ClientSession` and `mcp.client.sse.sse_client`.
73
+
74
+ **Example Connection Snippet (from `simple_game_client.py`):**
75
+ ```python
76
+ from mcp import ClientSession
77
+ from mcp.client.sse import sse_client
78
+ from contextlib import AsyncExitStack
79
+ import asyncio
80
+
81
+ class GameClient:
82
+ def __init__(self, server_url="http://127.0.0.1:7868/gradio_api/mcp/sse"):
83
+ self.server_url = server_url
84
+ self.session = None
85
+ self.exit_stack = AsyncExitStack()
86
+ self.connected = False
87
+ self.tools = []
88
+
89
+ async def connect(self):
90
+ try:
91
+ transport = await self.exit_stack.enter_async_context(
92
+ sse_client(self.server_url)
93
+ )
94
+ read_stream, write_callable = transport
95
+ self.session = await self.exit_stack.enter_async_context(
96
+ ClientSession(read_stream, write_callable)
97
+ )
98
+ await self.session.initialize()
99
+ response = await self.session.list_tools()
100
+ self.tools = response.tools
101
+ self.connected = True
102
+ print(f"✅ Connected! Available tools: {[tool.name for tool in self.tools]}")
103
+ return True
104
+ except Exception as e:
105
+ print(f"❌ Connection failed: {e}")
106
+ return False
107
+
108
+ async def disconnect(self):
109
+ if self.connected:
110
+ await self.exit_stack.aclose()
111
+ self.connected = False
112
+ print("🔌 Disconnected.")
113
+ ```
114
+
115
+ ### Available Tools/Commands
116
+ Upon connection, the client can list available tools (commands) from the server. Common tools exposed by the MMORPG server typically include:
117
+
118
+ * `register_ai_agent` (or similar): To join the game as a new player/agent.
119
+ * `move_agent` (or similar): To move the player/agent in the game world.
120
+ * `send_chat_message` (or similar): To send messages to the public game chat.
121
+ * `interact_with_npc` (or similar): To send messages or commands to specific NPCs.
122
+ * `get_game_state` (or similar): To retrieve information about the current game world, other players, etc.
123
+
124
+ The exact names and parameters of these tools are defined by the server-side MCP implementation. Use `session.list_tools()` to get the current list and `tool.parameters_json_schema` for expected inputs.
125
+
126
+ ### Common Operations
127
+
128
+ **1. Registering a Player/Agent:**
129
+ * **Tool**: Look for a tool like `register_ai_agent`.
130
+ * **Parameters**: Typically `player_name` (string) and `client_id` (string, unique identifier for the client).
131
+ * **Response**: Often includes an `agent_id` or `player_id` assigned by the server.
132
+
133
+ **2. Moving a Player/Agent:**
134
+ * **Tool**: Look for a tool like `move_agent`.
135
+ * **Parameters**: `agent_id` (string) and `direction` (string, e.g., "north", "south", "east", "west", "up", "down").
136
+ * **Response**: Confirmation of movement, new coordinates, or error if movement is not possible.
137
+
138
+ **3. Sending Chat Messages:**
139
+ * **Tool**: Look for a tool like `send_chat_message`.
140
+ * **Parameters**: `agent_id` (string) and `message` (string).
141
+ * **Response**: Confirmation that the message was sent.
142
+
143
+ **4. Interacting with NPCs:**
144
+ * **Tool**: Look for a tool like `interact_with_npc`.
145
+ * **Parameters**: `agent_id` (string), `npc_id` (string), and `message` or `command` (string).
146
+ * **Response**: NPC's reply or result of the interaction.
147
+
148
+ **5. Getting Game State:**
149
+ * **Tool**: Look for a tool like `get_game_state`.
150
+ * **Parameters**: May include `agent_id` (string) or be parameterless.
151
+ * **Response**: JSON object containing game world information, list of players, NPCs, etc.
152
+
153
+ ---
154
+
155
+ ## 🧩 Extending the Game
156
+
157
+ ### NPC Add-on Development
158
+ For creating new NPCs with custom behaviors and UI components, please refer to the **`NPC_Addon_Development_Guide.md`**. It provides a comprehensive, up-to-date guide covering the modern auto-registration system, working examples from the codebase, and best practices.
159
+
160
+ ### Adding New Game Mechanics
161
+ To introduce new core game mechanics:
162
+ 1. **Game Engine (`src/core/game_engine.py`):** Implement the fundamental logic for the new mechanic here. This might involve modifying player states, world interactions, or introducing new entities.
163
+ 2. **Facade Layer:** If the mechanic needs to be exposed to the UI or MCP clients, update or create methods in your game facade (e.g., `src/facades/game_facade.py`) to provide a clean interface to the engine's functionality.
164
+ 3. **UI Integration (`src/ui/interface_manager.py`):** If human players should interact with this mechanic, add new UI elements and event handlers in the Gradio interface.
165
+ 4. **MCP Exposure:** If AI agents or external clients should use this mechanic, expose the relevant facade methods as new MCP tools.
166
+
167
+ ---
168
+
169
+ ## 🤖 AI Agent Integration
170
+
171
+ AI agents can connect to the MMORPG as players using an MCP client (like the one shown in `simple_game_client.py`).
172
+ 1. **Connection**: The agent connects to the MCP SSE endpoint.
173
+ 2. **Registration**: The agent registers itself, providing a name.
174
+ 3. **Interaction**: The agent uses the available MCP tools to perceive the game state (`get_game_state`), move (`move_agent`), chat (`send_chat_message`), and interact with NPCs (`interact_with_npc`).
175
+ 4. **Decision Making**: The agent's internal logic processes game state information and decides on actions to take.
176
+
177
+ The `simple_game_client.py` script serves as a foundational example for building more sophisticated AI agents.
178
+
179
+ ---
180
+
181
+ ## 🐞 Debugging
182
+
183
+ * **Server Logs**: Check the console output where `python app.py` is running for server-side errors, print statements, and game event logs.
184
+ * **Gradio UI**: Use your web browser's developer tools (especially the console) to debug issues related to the Gradio interface and JavaScript (like the keyboard control script).
185
+ * **MCP Client-Side**: Add extensive logging in your MCP client to trace requests sent to the server and responses received. Print the full content of MCP tool calls and results.
186
+ * **Game State Dumps**: Periodically use the `get_game_state` MCP tool (if available) or implement a debug feature to dump the current game state to understand the situation from the server's perspective.
187
+ * **Incremental Testing**: When developing new features or MCP tools, test them incrementally.
188
+
189
+ ---
Documentation/DEVELOPER_DOCUMENTATION.md ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🛠️ MMORPG with MCP Integration - Developer Documentation
2
+
3
+ ## 📖 Table of Contents
4
+
5
+ 1. [Introduction](#introduction)
6
+ 2. [Project Setup](#project-setup)
7
+ 3. [Architecture Overview](#architecture-overview)
8
+ 4. [MCP API Reference](#mcp-api-reference)
9
+ * [Connection](#connection)
10
+ * [Available Tools/Commands](#available-toolscommands)
11
+ * [Common Operations](#common-operations)
12
+ 5. [Extending the Game](#extending-the-game)
13
+ * [NPC Add-on Development](#npc-add-on-development)
14
+ * [Adding New Game Mechanics](#adding-new-game-mechanics)
15
+ 6. [AI Agent Integration](#ai-agent-integration)
16
+ 7. [Debugging](#debugging)
17
+
18
+ ---
19
+
20
+ ## 📜 Introduction
21
+
22
+ This document provides technical guidance for developers working on the **MMORPG with MCP Integration** project. It covers project setup, architecture, API usage for client/agent development, and how to extend the game's functionalities.
23
+
24
+ ---
25
+
26
+ ## ⚙️ Project Setup
27
+
28
+ ### Prerequisites
29
+ * Python 3.8 or higher
30
+ * `pip` for package management
31
+
32
+ ### Getting the Code
33
+ Clone the repository to your local machine (if you haven't already).
34
+
35
+ ### Installation
36
+ 1. Navigate to the project directory (e.g., `c:\\Users\\Chris4K\\Projekte\\projecthub\\projects\\MMOP_second_try`).
37
+ ```powershell
38
+ cd c:\Users\Chris4K\Projekte\projecthub\projects\MMOP_second_try
39
+ ```
40
+ 2. Install the required Python dependencies:
41
+ ```bash
42
+ pip install gradio mcp aiohttp asyncio
43
+ ```
44
+ (Ensure other project-specific dependencies from your `requirements.txt` are also installed).
45
+
46
+ ### Running the Game Server
47
+ Execute the main application file (assumed to be `app.py` in the root of `MMOP_second_try`):
48
+ ```bash
49
+ python app.py
50
+ ```
51
+ The server should start, typically making the Gradio UI available at `http://127.0.0.1:7868` and the MCP SSE endpoint at `http://127.0.0.1:7868/gradio_api/mcp/sse`.
52
+
53
+ ---
54
+
55
+ ## 🏗️ Architecture Overview (High-Level)
56
+
57
+ * **Game Server (`app.py`)**: The main entry point. Initializes and runs the Gradio web interface, integrates game components, and likely hosts the MCP endpoint.
58
+ * **Game Engine (`src/core/game_engine.py`)**: Contains the core game logic, manages world state, player data, NPC interactions, and game rules.
59
+ * **UI Layer (`src/ui/interface_manager.py`, `src/ui/huggingface_ui.py`)**: Manages the Gradio user interface, defining layouts, components, and event handling for human players.
60
+ * **MCP Integration Layer**: A facade or service within the server that exposes game functionalities to MCP clients. This allows AI agents or other external systems to interact with the game.
61
+ * **NPC Addons**: Modular components that extend NPC functionalities. See `NPC_Addon_Development_Guide.md`.
62
+
63
+ ---
64
+
65
+ ## 🔌 MCP API Reference
66
+
67
+ The game server exposes its functionalities via the Model Context Protocol (MCP), allowing external clients (like AI agents or custom tools) to interact with the game world programmatically.
68
+
69
+ ### Connection
70
+ * **Endpoint URL**: `http://127.0.0.1:7868/gradio_api/mcp/sse` (for Server-Sent Events)
71
+ * **Protocol**: MCP over SSE.
72
+ * **Client Implementation**: Refer to `simple_game_client.py` for a reference Python client. It uses `mcp.ClientSession` and `mcp.client.sse.sse_client`.
73
+
74
+ **Example Connection Snippet (from `simple_game_client.py`):**
75
+ ```python
76
+ from mcp import ClientSession
77
+ from mcp.client.sse import sse_client
78
+ from contextlib import AsyncExitStack
79
+ import asyncio
80
+
81
+ class GameClient:
82
+ def __init__(self, server_url="http://127.0.0.1:7868/gradio_api/mcp/sse"):
83
+ self.server_url = server_url
84
+ self.session = None
85
+ self.exit_stack = AsyncExitStack()
86
+ self.connected = False
87
+ self.tools = []
88
+
89
+ async def connect(self):
90
+ try:
91
+ transport = await self.exit_stack.enter_async_context(
92
+ sse_client(self.server_url)
93
+ )
94
+ read_stream, write_callable = transport
95
+ self.session = await self.exit_stack.enter_async_context(
96
+ ClientSession(read_stream, write_callable)
97
+ )
98
+ await self.session.initialize()
99
+ response = await self.session.list_tools()
100
+ self.tools = response.tools
101
+ self.connected = True
102
+ print(f"✅ Connected! Available tools: {[tool.name for tool in self.tools]}")
103
+ return True
104
+ except Exception as e:
105
+ print(f"❌ Connection failed: {e}")
106
+ return False
107
+
108
+ async def disconnect(self):
109
+ if self.connected:
110
+ await self.exit_stack.aclose()
111
+ self.connected = False
112
+ print("🔌 Disconnected.")
113
+ ```
114
+
115
+ ### Available Tools/Commands
116
+ Upon connection, the client can list available tools (commands) from the server. Common tools exposed by the MMORPG server typically include:
117
+
118
+ * `register_ai_agent` (or similar): To join the game as a new player/agent.
119
+ * `move_agent` (or similar): To move the player/agent in the game world.
120
+ * `send_chat_message` (or similar): To send messages to the public game chat.
121
+ * `interact_with_npc` (or similar): To send messages or commands to specific NPCs.
122
+ * `get_game_state` (or similar): To retrieve information about the current game world, other players, etc.
123
+
124
+ The exact names and parameters of these tools are defined by the server-side MCP implementation. Use `session.list_tools()` to get the current list and `tool.parameters_json_schema` for expected inputs.
125
+
126
+ ### Common Operations
127
+
128
+ **1. Registering a Player/Agent:**
129
+ * **Tool**: Look for a tool like `register_ai_agent`.
130
+ * **Parameters**: Typically `player_name` (string) and `client_id` (string, unique identifier for the client).
131
+ * **Response**: Often includes an `agent_id` or `player_id` assigned by the server.
132
+
133
+ **2. Moving a Player/Agent:**
134
+ * **Tool**: Look for a tool like `move_agent`.
135
+ * **Parameters**: `agent_id` (string) and `direction` (string, e.g., "north", "south", "east", "west", "up", "down").
136
+ * **Response**: Confirmation of movement, new coordinates, or error if movement is not possible.
137
+
138
+ **3. Sending Chat Messages:**
139
+ * **Tool**: Look for a tool like `send_chat_message`.
140
+ * **Parameters**: `agent_id` (string) and `message` (string).
141
+ * **Response**: Confirmation that the message was sent.
142
+
143
+ **4. Interacting with NPCs:**
144
+ * **Tool**: Look for a tool like `interact_with_npc`.
145
+ * **Parameters**: `agent_id` (string), `npc_id` (string), and `message` or `command` (string).
146
+ * **Response**: NPC's reply or result of the interaction.
147
+
148
+ **5. Getting Game State:**
149
+ * **Tool**: Look for a tool like `get_game_state`.
150
+ * **Parameters**: May include `agent_id` (string) or be parameterless.
151
+ * **Response**: JSON object containing game world information, list of players, NPCs, etc.
152
+
153
+ **Example MCP Tool Usage:**
154
+ ```python
155
+ # Register as AI agent
156
+ response = await session.call_tool("register_ai_agent", {
157
+ "player_name": "MyBot",
158
+ "client_id": "bot_001"
159
+ })
160
+
161
+ # Move the agent
162
+ response = await session.call_tool("move_agent", {
163
+ "agent_id": "bot_001",
164
+ "direction": "north"
165
+ })
166
+
167
+ # Send chat message
168
+ response = await session.call_tool("send_chat_message", {
169
+ "agent_id": "bot_001",
170
+ "message": "Hello everyone!"
171
+ })
172
+ ```
173
+
174
+ ---
175
+
176
+ ## 🧩 Extending the Game
177
+
178
+ ### NPC Add-on Development
179
+ For creating new NPCs with custom behaviors and UI components, please refer to the **`NPC_Addon_Development_Guide.md`**. It provides a comprehensive, up-to-date guide covering the modern auto-registration system, working examples from the codebase, and best practices.
180
+
181
+ ### Adding New Game Mechanics
182
+ To introduce new core game mechanics:
183
+ 1. **Game Engine (`src/core/game_engine.py`):** Implement the fundamental logic for the new mechanic here. This might involve modifying player states, world interactions, or introducing new entities.
184
+ 2. **Facade Layer:** If the mechanic needs to be exposed to the UI or MCP clients, update or create methods in your game facade (e.g., `src/facades/game_facade.py`) to provide a clean interface to the engine's functionality.
185
+ 3. **UI Integration (`src/ui/interface_manager.py`):** If human players should interact with this mechanic, add new UI elements and event handlers in the Gradio interface.
186
+ 4. **MCP Exposure:** If AI agents or external clients should use this mechanic, expose the relevant facade methods as new MCP tools.
187
+
188
+ ---
189
+
190
+ ## 🤖 AI Agent Integration
191
+
192
+ AI agents can connect to the MMORPG as players using an MCP client (like the one shown in `simple_game_client.py`).
193
+ 1. **Connection**: The agent connects to the MCP SSE endpoint.
194
+ 2. **Registration**: The agent registers itself, providing a name.
195
+ 3. **Interaction**: The agent uses the available MCP tools to perceive the game state (`get_game_state`), move (`move_agent`), chat (`send_chat_message`), and interact with NPCs (`interact_with_npc`).
196
+ 4. **Decision Making**: The agent's internal logic processes game state information and decides on actions to take.
197
+
198
+ The `simple_game_client.py` script serves as a foundational example for building more sophisticated AI agents.
199
+
200
+ ---
201
+
202
+ ## 🐞 Debugging
203
+
204
+ * **Server Logs**: Check the console output where `python app.py` is running for server-side errors, print statements, and game event logs.
205
+ * **Gradio UI**: Use your web browser's developer tools (especially the console) to debug issues related to the Gradio interface and JavaScript (like the keyboard control script).
206
+ * **MCP Client-Side**: Add extensive logging in your MCP client to trace requests sent to the server and responses received. Print the full content of MCP tool calls and results.
207
+ * **Game State Dumps**: Periodically use the `get_game_state` MCP tool (if available) or implement a debug feature to dump the current game state to understand the situation from the server's perspective.
208
+ * **Incremental Testing**: When developing new features or MCP tools, test them incrementally.
209
+
210
+ ---
Documentation/NPC_Addon_Development_Guide.md ADDED
@@ -0,0 +1,1312 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🛠️ NPC Addon Development Guide - Complete & Updated
2
+
3
+ > **Updated for Current Architecture** - This guide reflects the latest addon system with auto-registration, modern patterns, and actual working examples from the codebase.
4
+
5
+ ## 📋 Table of Contents
6
+
7
+ 1. [Quick Start](#quick-start)
8
+ 2. [Modern Addon Architecture](#modern-addon-architecture)
9
+ 3. [Auto-Registration System](#auto-registration-system)
10
+ 4. [Complete Examples](#complete-examples)
11
+ 5. [Advanced Patterns](#advanced-patterns)
12
+ 6. [MCP Integration](#mcp-integration)
13
+ 7. [Best Practices](#best-practices)
14
+ 8. [Deployment & Testing](#deployment--testing)
15
+
16
+ ---
17
+
18
+ ## 🚀 Quick Start
19
+
20
+ ### Creating Your First Addon
21
+
22
+ The modern addon system uses **auto-registration** - no manual edits to other files required!
23
+
24
+ ```python
25
+ # src/addons/my_first_addon.py
26
+ """
27
+ My First Addon - A simple self-contained NPC addon.
28
+ """
29
+
30
+ import gradio as gr
31
+ from ..interfaces.npc_addon import NPCAddon
32
+
33
+
34
+ class MyFirstAddon(NPCAddon):
35
+ """A simple greeting NPC that demonstrates the addon system."""
36
+
37
+ def __init__(self):
38
+ super().__init__() # This auto-registers the addon!
39
+ self.greeting_count = 0
40
+
41
+ @property
42
+ def addon_id(self) -> str:
43
+ """Unique identifier - must be unique across all addons"""
44
+ return "my_first_addon"
45
+
46
+ @property
47
+ def addon_name(self) -> str:
48
+ """Display name shown in UI"""
49
+ return "🤝 My First Addon"
50
+
51
+ @property
52
+ def npc_config(self) -> Dict[str, Any]:
53
+ """NPC configuration for auto-placement in world"""
54
+ return {
55
+ 'id': 'my_first_npc',
56
+ 'name': '🤝 Friendly Greeter',
57
+ 'x': 100, 'y': 100, # Position in game world
58
+ 'char': '🤝', # Character emoji
59
+ 'type': 'addon', # NPC type
60
+ 'description': 'A friendly NPC that greets players'
61
+ }
62
+
63
+ @property
64
+ def ui_tab_name(self) -> str:
65
+ """UI tab name - returns None if no UI tab needed"""
66
+ return "🤝 Greeter"
67
+
68
+ def get_interface(self) -> gr.Component:
69
+ """Create the Gradio interface for this addon"""
70
+ with gr.Column() as interface:
71
+ gr.Markdown("## 🤝 Friendly Greeter\n*Hello! I'm here to greet players.*")
72
+
73
+ greeting_btn = gr.Button("👋 Get Greeting", variant="primary")
74
+ greeting_output = gr.Textbox(label="Greeting", lines=3, interactive=False)
75
+
76
+ def get_greeting():
77
+ self.greeting_count += 1
78
+ return f"Hello! This is greeting #{self.greeting_count}. Welcome to the game!"
79
+
80
+ greeting_btn.click(get_greeting, outputs=[greeting_output])
81
+
82
+ return interface
83
+
84
+ def handle_command(self, player_id: str, command: str) -> str:
85
+ """Handle commands sent via private messages to this NPC"""
86
+ cmd = command.lower().strip()
87
+
88
+ if cmd in ['hello', 'hi', 'greet']:
89
+ self.greeting_count += 1
90
+ return f"🤝 **Friendly Greeter says:**\nHello! Greeting #{self.greeting_count}!"
91
+ elif cmd == 'help':
92
+ return "🤝 **Available commands:**\n• hello/hi/greet - Get a greeting\n• help - Show this help"
93
+ else:
94
+ return "🤝 **Friendly Greeter:**\nI didn't understand that. Try 'hello' or 'help'!"
95
+
96
+
97
+ # Global instance - this triggers auto-registration!
98
+ my_first_addon = MyFirstAddon()
99
+ ```
100
+
101
+ **That's it!** Your addon is now fully functional and auto-registered. No other files need to be modified.
102
+
103
+ ---
104
+
105
+ ## 🏗️ Modern Addon Architecture
106
+
107
+ ### Base Class Structure
108
+
109
+ All addons inherit from `NPCAddon` which provides:
110
+
111
+ ```python
112
+ class NPCAddon(ABC):
113
+ """Base class for NPC add-ons with auto-registration support"""
114
+
115
+ def __init__(self):
116
+ """Initialize and auto-register the addon"""
117
+ # Auto-register this addon when instantiated
118
+ _addon_registry[self.addon_id] = self
119
+
120
+ # Required Properties
121
+ @property
122
+ @abstractmethod
123
+ def addon_id(self) -> str: pass
124
+
125
+ @property
126
+ @abstractmethod
127
+ def addon_name(self) -> str: pass
128
+
129
+ # Optional Properties
130
+ @property
131
+ def npc_config(self) -> Optional[Dict[str, Any]]: return None
132
+
133
+ @property
134
+ def ui_tab_name(self) -> Optional[str]: return None
135
+
136
+ # Required Methods
137
+ @abstractmethod
138
+ def get_interface(self) -> gr.Component: pass
139
+
140
+ @abstractmethod
141
+ def handle_command(self, player_id: str, command: str) -> str: pass
142
+
143
+ # Optional Lifecycle Methods
144
+ def on_startup(self): pass
145
+ def on_shutdown(self): pass
146
+ ```
147
+
148
+ ### Core Concepts
149
+
150
+ 1. **Auto-Registration**: Simply instantiating your addon class registers it globally
151
+ 2. **Self-Contained**: Everything in one file - NPC config, UI, logic
152
+ 3. **Optional Components**: Only implement what you need (UI tab, world NPC, etc.)
153
+ 4. **Service Pattern**: Use interfaces for complex functionality
154
+
155
+ ---
156
+
157
+ ## 🔄 Auto-Registration System
158
+
159
+ ### How It Works
160
+
161
+ 1. **Create addon class** that inherits from `NPCAddon`
162
+ 2. **Instantiate globally** at the bottom of your addon file
163
+ 3. **Auto-discovery** by the game engine at startup
164
+ 4. **Automatic registration** of NPCs and UI components
165
+
166
+ ### Current Auto-Registration List
167
+
168
+ The game engine automatically loads these addons:
169
+
170
+ ```python
171
+ # src/core/game_engine.py
172
+ auto_register_addons = [
173
+ ('weather_oracle_addon', 'auto_register'),
174
+ # Add your addon here if using custom auto_register function
175
+ ]
176
+ ```
177
+
178
+ ### Two Registration Patterns
179
+
180
+ #### Pattern 1: Global Instance (Recommended)
181
+ ```python
182
+ # At bottom of your addon file
183
+ my_addon = MyAddon() # Auto-registers via __init__
184
+ ```
185
+
186
+ #### Pattern 2: Custom Auto-Register Function
187
+ ```python
188
+ def auto_register(game_engine):
189
+ """Custom registration logic"""
190
+ try:
191
+ addon_instance = MyAddon()
192
+ # Custom registration logic here
193
+ return True
194
+ except Exception as e:
195
+ print(f"Registration failed: {e}")
196
+ return False
197
+
198
+ # Add to game_engine.py auto_register_addons list
199
+ ```
200
+
201
+ ---
202
+
203
+ ## 📚 Complete Examples
204
+
205
+ ### Example 1: Simple Trader (From Codebase)
206
+
207
+ ```python
208
+ """
209
+ Simple Trader NPC Add-on - Self-contained NPC with automatic registration.
210
+ """
211
+
212
+ import gradio as gr
213
+ from typing import Dict
214
+ from ..interfaces.npc_addon import NPCAddon
215
+
216
+
217
+ class SimpleTraderAddon(NPCAddon):
218
+ """Self-contained trader NPC that handles its own registration and positioning."""
219
+
220
+ def __init__(self):
221
+ super().__init__()
222
+ self.inventory = {
223
+ "Health Potion": {"price": 50, "stock": 10, "description": "Restores 100 HP"},
224
+ "Magic Scroll": {"price": 150, "stock": 5, "description": "Cast magic spells"},
225
+ "Iron Sword": {"price": 300, "stock": 3, "description": "Sharp iron weapon"},
226
+ "Shield": {"price": 200, "stock": 4, "description": "Protective gear"}
227
+ }
228
+ self.sales_history = []
229
+
230
+ @property
231
+ def addon_id(self) -> str:
232
+ return "simple_trader"
233
+
234
+ @property
235
+ def addon_name(self) -> str:
236
+ return "🛒 Simple Trader"
237
+
238
+ @property
239
+ def npc_config(self) -> Dict:
240
+ return {
241
+ 'id': 'simple_trader',
242
+ 'name': '🛒 Simple Trader',
243
+ 'x': 450, 'y': 150,
244
+ 'char': '🛒',
245
+ 'type': 'addon',
246
+ 'personality': 'trader',
247
+ 'description': 'A friendly trader selling useful items'
248
+ }
249
+
250
+ @property
251
+ def ui_tab_name(self) -> str:
252
+ return "🛒 Simple Trader"
253
+
254
+ def get_interface(self) -> gr.Component:
255
+ """Create the Gradio interface for this addon."""
256
+ with gr.Column() as interface:
257
+ gr.Markdown("### 🛒 Simple Trader Shop")
258
+ gr.Markdown("Browse items and check prices. Use private messages to trade!")
259
+
260
+ # Display inventory
261
+ inventory_data = []
262
+ for item, info in self.inventory.items():
263
+ inventory_data.append([item, f"{info['price']} gold", info['stock'], info['description']])
264
+
265
+ gr.Dataframe(
266
+ headers=["Item", "Price", "Stock", "Description"],
267
+ value=inventory_data,
268
+ interactive=False
269
+ )
270
+
271
+ gr.Markdown("**Commands:** Send private message to Simple Trader")
272
+ gr.Markdown("• `buy <item>` - Purchase an item")
273
+ gr.Markdown("• `inventory` - See available items")
274
+ gr.Markdown("• `help` - Show all commands")
275
+
276
+ return interface
277
+
278
+ def handle_command(self, player_id: str, command: str) -> str:
279
+ """Handle commands sent to this NPC."""
280
+ parts = command.strip().lower().split()
281
+ cmd = parts[0] if parts else ""
282
+
283
+ if cmd == "buy" and len(parts) > 1:
284
+ item_name = " ".join(parts[1:]).title()
285
+ return self._handle_buy(player_id, item_name)
286
+ elif cmd == "inventory":
287
+ return self._show_inventory()
288
+ elif cmd == "help":
289
+ return self._show_help()
290
+ else:
291
+ return "🛒 **Simple Trader:** I didn't understand. Try 'buy <item>', 'inventory', or 'help'."
292
+
293
+ def _handle_buy(self, player_id: str, item_name: str) -> str:
294
+ """Handle item purchase."""
295
+ if item_name not in self.inventory:
296
+ return f"❌ Sorry, I don't have '{item_name}' in stock."
297
+
298
+ item = self.inventory[item_name]
299
+ if item['stock'] <= 0:
300
+ return f"❌ '{item_name}' is out of stock!"
301
+
302
+ # In a real implementation, check player gold and deduct
303
+ item['stock'] -= 1
304
+ self.sales_history.append({'player': player_id, 'item': item_name, 'price': item['price']})
305
+
306
+ return f"✅ **Purchase Successful!**\n📦 You bought: {item_name}\n💰 Price: {item['price']} gold\n📊 Stock remaining: {item['stock']}"
307
+
308
+ def _show_inventory(self) -> str:
309
+ """Show current inventory."""
310
+ result = "🛒 **Current Inventory:**\n\n"
311
+ for item, info in self.inventory.items():
312
+ result += f"**{item}** - {info['price']} gold (Stock: {info['stock']})\n"
313
+ result += f" ↳ {info['description']}\n\n"
314
+ return result
315
+
316
+ def _show_help(self) -> str:
317
+ """Show help commands."""
318
+ return """🛒 **Simple Trader Commands:**
319
+
320
+ **buy <item>** - Purchase an item
321
+ **inventory** - See available items and prices
322
+ **help** - Show this help
323
+
324
+ 💰 **Available Items:**
325
+ • Health Potion, Magic Scroll, Iron Sword, Shield
326
+
327
+ Example: `buy Health Potion`"""
328
+
329
+
330
+ # Global instance for automatic registration
331
+ simple_trader_addon = SimpleTraderAddon()
332
+ ```
333
+
334
+ ### Example 2: Read2Burn Messaging (From Codebase)
335
+
336
+ This example shows a complex addon with state management and service interfaces:
337
+
338
+ ```python
339
+ """
340
+ Read2Burn Mailbox Add-on - Self-destructing secure messaging system.
341
+ """
342
+
343
+ import time
344
+ import random
345
+ import string
346
+ from typing import Dict, List
347
+ from dataclasses import dataclass
348
+ from abc import ABC, abstractmethod
349
+ import gradio as gr
350
+
351
+ from ..interfaces.npc_addon import NPCAddon
352
+
353
+
354
+ @dataclass
355
+ class Read2BurnMessage:
356
+ """Data class for Read2Burn messages."""
357
+ id: str
358
+ creator_id: str
359
+ content: str
360
+ created_at: float
361
+ expires_at: float
362
+ reads_left: int
363
+ burned: bool = False
364
+
365
+
366
+ class IRead2BurnService(ABC):
367
+ """Interface for Read2Burn service operations."""
368
+
369
+ @abstractmethod
370
+ def create_message(self, creator_id: str, content: str) -> str: pass
371
+
372
+ @abstractmethod
373
+ def read_message(self, reader_id: str, message_id: str) -> str: pass
374
+
375
+ @abstractmethod
376
+ def list_player_messages(self, player_id: str) -> str: pass
377
+
378
+
379
+ class Read2BurnService(IRead2BurnService, NPCAddon):
380
+ """Service for managing Read2Burn secure messaging."""
381
+
382
+ def __init__(self):
383
+ super().__init__()
384
+ self.messages: Dict[str, Read2BurnMessage] = {}
385
+ self.access_log: List[Dict] = []
386
+
387
+ @property
388
+ def addon_id(self) -> str:
389
+ return "read2burn_mailbox"
390
+
391
+ @property
392
+ def addon_name(self) -> str:
393
+ return "📧 Read2Burn Secure Mailbox"
394
+
395
+ @property
396
+ def npc_config(self) -> Dict:
397
+ return {
398
+ 'id': 'read2burn',
399
+ 'name': '📧 Read2Burn Service',
400
+ 'x': 200, 'y': 100,
401
+ 'char': '📧',
402
+ 'type': 'service',
403
+ 'personality': 'read2burn',
404
+ 'description': 'Secure message service - send private messages that auto-delete after reading'
405
+ }
406
+
407
+ def get_interface(self) -> gr.Component:
408
+ """Return Gradio interface for this add-on"""
409
+ with gr.Column() as interface:
410
+ gr.Markdown("""
411
+ ## 📧 Read2Burn Secure Mailbox
412
+
413
+ **Create self-destructing messages that burn after reading!**
414
+
415
+ ### Features:
416
+ - 🔥 Messages self-destruct after reading
417
+ - ⏰ 24-hour automatic expiration
418
+ - 🔒 Secure delivery system
419
+ - 📊 Message tracking and history
420
+ """)
421
+
422
+ with gr.Tabs():
423
+ with gr.Tab("📝 Create Message"):
424
+ message_input = gr.Textbox(
425
+ label="Message Content",
426
+ lines=5,
427
+ placeholder="Type your secret message here..."
428
+ )
429
+ create_btn = gr.Button("🔥 Create Burning Message", variant="primary")
430
+ create_output = gr.Textbox(label="Result", lines=3, interactive=False)
431
+
432
+ def create_message_ui(content):
433
+ # In real implementation, get current player ID
434
+ player_id = "current_player" # Simplified
435
+ return self.create_message(player_id, content)
436
+
437
+ create_btn.click(create_message_ui, inputs=[message_input], outputs=[create_output])
438
+
439
+ with gr.Tab("🔓 Read Message"):
440
+ message_id_input = gr.Textbox(label="Message ID", placeholder="Enter 8-character message ID")
441
+ read_btn = gr.Button("📖 Read & Burn Message", variant="secondary")
442
+ read_output = gr.Textbox(label="Message Content", lines=5, interactive=False)
443
+
444
+ def read_message_ui(msg_id):
445
+ player_id = "current_player" # Simplified
446
+ return self.read_message(player_id, msg_id)
447
+
448
+ read_btn.click(read_message_ui, inputs=[message_id_input], outputs=[read_output])
449
+
450
+ with gr.Tab("📋 My Messages"):
451
+ refresh_btn = gr.Button("🔄 Refresh List")
452
+ messages_output = gr.Textbox(label="Your Messages", lines=10, interactive=False)
453
+
454
+ def list_messages_ui():
455
+ player_id = "current_player" # Simplified
456
+ return self.list_player_messages(player_id)
457
+
458
+ refresh_btn.click(list_messages_ui, outputs=[messages_output])
459
+
460
+ gr.Markdown("**💬 Private Message Commands:**")
461
+ gr.Markdown("• `create Your message here` - Create new message")
462
+ gr.Markdown("• `read MESSAGE_ID` - Read message (destroys it!)")
463
+ gr.Markdown("• `list` - Show your created messages")
464
+ gr.Markdown("• `help` - Show command help")
465
+
466
+ return interface
467
+
468
+ def generate_message_id(self) -> str:
469
+ """Generate a unique message ID."""
470
+ return ''.join(random.choices(string.ascii_uppercase + string.digits, k=8))
471
+
472
+ def create_message(self, creator_id: str, content: str) -> str:
473
+ """Create a new self-destructing message."""
474
+ message_id = self.generate_message_id()
475
+
476
+ message = Read2BurnMessage(
477
+ id=message_id,
478
+ creator_id=creator_id,
479
+ content=content, # In production, encrypt this
480
+ created_at=time.time(),
481
+ expires_at=time.time() + (24 * 3600), # 24 hours
482
+ reads_left=1,
483
+ burned=False
484
+ )
485
+
486
+ self.messages[message_id] = message
487
+
488
+ self.access_log.append({
489
+ 'action': 'create',
490
+ 'message_id': message_id,
491
+ 'player_id': creator_id,
492
+ 'timestamp': time.time()
493
+ })
494
+
495
+ return f"✅ **Message Created Successfully!**\n\n📝 **Message ID:** `{message_id}`\n🔗 Share this ID with the recipient\n⏰ Expires in 24 hours\n🔥 Burns after 1 read"
496
+
497
+ def read_message(self, reader_id: str, message_id: str) -> str:
498
+ """Read and burn a message."""
499
+ if message_id not in self.messages:
500
+ return "❌ Message not found or already burned"
501
+
502
+ message = self.messages[message_id]
503
+
504
+ # Check expiry
505
+ if time.time() > message.expires_at:
506
+ del self.messages[message_id]
507
+ return "❌ Message expired and has been burned"
508
+
509
+ # Check if already burned
510
+ if message.burned or message.reads_left <= 0:
511
+ del self.messages[message_id]
512
+ return "❌ Message has already been burned"
513
+
514
+ # Read the message
515
+ content = message.content
516
+ message.reads_left -= 1
517
+
518
+ self.access_log.append({
519
+ 'action': 'read',
520
+ 'message_id': message_id,
521
+ 'player_id': reader_id,
522
+ 'timestamp': time.time()
523
+ })
524
+
525
+ # Burn the message after reading
526
+ if message.reads_left <= 0:
527
+ message.burned = True
528
+ del self.messages[message_id]
529
+ return f"🔥 **Message Self-Destructed After Reading**\n\n📖 **Content:** {content}\n\n💨 This message has been permanently destroyed."
530
+
531
+ return f"📖 **Message Content:** {content}\n\n⚠️ Reads remaining: {message.reads_left}"
532
+
533
+ def list_player_messages(self, player_id: str) -> str:
534
+ """List messages created by a player."""
535
+ player_messages = [msg for msg in self.messages.values() if msg.creator_id == player_id]
536
+
537
+ if not player_messages:
538
+ return "📪 No messages found. Create one with: `create Your message here`"
539
+
540
+ result = "📋 **Your Created Messages:**\n\n"
541
+ for msg in player_messages:
542
+ status = "🔥 Burned" if msg.burned else f"✅ Active ({msg.reads_left} reads left)"
543
+ created_time = time.strftime("%Y-%m-%d %H:%M", time.localtime(msg.created_at))
544
+ expires_time = time.strftime("%Y-%m-%d %H:%M", time.localtime(msg.expires_at))
545
+
546
+ result += f"**ID:** `{msg.id}`\n"
547
+ result += f"**Status:** {status}\n"
548
+ result += f"**Created:** {created_time}\n"
549
+ result += f"**Expires:** {expires_time}\n"
550
+ result += f"**Preview:** {msg.content[:50]}{'...' if len(msg.content) > 50 else ''}\n\n"
551
+
552
+ return result
553
+
554
+ def handle_command(self, player_id: str, command: str) -> str:
555
+ """Handle Read2Burn mailbox commands."""
556
+ parts = command.strip().split(' ', 1)
557
+ cmd = parts[0].lower()
558
+
559
+ if cmd == "create" and len(parts) > 1:
560
+ return self.create_message(player_id, parts[1])
561
+ elif cmd == "read" and len(parts) > 1:
562
+ return self.read_message(player_id, parts[1])
563
+ elif cmd == "list":
564
+ return self.list_player_messages(player_id)
565
+ elif cmd == "help":
566
+ return """📚 **Read2Burn Mailbox Commands:**
567
+
568
+ **create** `Your secret message here` - Create new message
569
+ **read** `MESSAGE_ID` - Read message (destroys it!)
570
+ **list** - Show your created messages
571
+ **help** - Show this help
572
+
573
+ 🔥 **Features:**
574
+ • Messages self-destruct after reading
575
+ • 24-hour automatic expiration
576
+ • Secure delivery system
577
+ • Anonymous messaging support"""
578
+ else:
579
+ return "❓ Invalid command. Try: `create <message>`, `read <id>`, `list`, or `help`"
580
+
581
+
582
+ # Global Read2Burn service instance
583
+ read2burn_service = Read2BurnService()
584
+ ```
585
+
586
+ ---
587
+
588
+ ## 🌐 MCP Integration
589
+
590
+ ### MCP-Powered Weather Oracle (From Codebase)
591
+
592
+ This example shows how to integrate external MCP servers:
593
+
594
+ ```python
595
+ """
596
+ Weather Oracle Add-on - MCP-powered weather information system.
597
+ """
598
+
599
+ import time
600
+ import asyncio
601
+ from typing import Dict, Optional
602
+ import gradio as gr
603
+ from mcp import ClientSession
604
+ from mcp.client.sse import sse_client
605
+ from contextlib import AsyncExitStack
606
+
607
+ from ..interfaces.npc_addon import NPCAddon
608
+
609
+
610
+ class WeatherOracleService(NPCAddon):
611
+ """Service for managing Weather Oracle MCP integration."""
612
+
613
+ def __init__(self):
614
+ super().__init__()
615
+ self.connected = False
616
+ self.last_connection_attempt = 0
617
+ self.connection_cooldown = 30 # 30 seconds between connection attempts
618
+ self.server_url = "https://chris4k-weather.hf.space/gradio_api/mcp/sse"
619
+ self.session = None
620
+ self.tools = []
621
+ self.exit_stack = None
622
+
623
+ # Set up event loop for async operations
624
+ try:
625
+ self.loop = asyncio.get_event_loop()
626
+ except RuntimeError:
627
+ self.loop = asyncio.new_event_loop()
628
+ asyncio.set_event_loop(self.loop)
629
+
630
+ @property
631
+ def addon_id(self) -> str:
632
+ return "weather_oracle"
633
+
634
+ @property
635
+ def addon_name(self) -> str:
636
+ return "🌤️ Weather Oracle (MCP)"
637
+
638
+ @property
639
+ def npc_config(self) -> Dict:
640
+ return {
641
+ 'id': 'weather_oracle',
642
+ 'name': '🌤️ Weather Oracle (MCP)',
643
+ 'x': 150, 'y': 300,
644
+ 'char': '🌤️',
645
+ 'type': 'mcp',
646
+ 'description': 'MCP-powered weather information service'
647
+ }
648
+
649
+ @property
650
+ def ui_tab_name(self) -> str:
651
+ return "🌤️ Weather Oracle"
652
+
653
+ def get_interface(self) -> gr.Component:
654
+ """Return Gradio interface for this add-on"""
655
+ with gr.Column() as interface:
656
+ gr.Markdown("""
657
+ ## 🌤️ Weather Oracle
658
+
659
+ *I commune with the spirits of sky and storm to bring you weather wisdom from across the realms!*
660
+
661
+ **Ask me about weather in any city:**
662
+ - Current conditions and temperature
663
+ - Weather forecasts
664
+ - Climate information
665
+
666
+ *Format: "City, Country" (e.g., "Berlin, Germany")*
667
+ """)
668
+
669
+ # Connection status
670
+ connection_status = gr.HTML(
671
+ value=f"<div style='color: {'green' if self.connected else 'red'};'>{'🟢 Connected to weather spirits' if self.connected else '🔴 Disconnected from weather realm'}</div>"
672
+ )
673
+
674
+ with gr.Row():
675
+ location_input = gr.Textbox(
676
+ label="🌍 Location",
677
+ placeholder="Enter city, country (e.g., Berlin, Germany)",
678
+ scale=3
679
+ )
680
+ get_weather_btn = gr.Button("🌡️ Consult Weather Spirits", variant="primary", scale=1)
681
+
682
+ weather_output = gr.Textbox(
683
+ label="🌤️ Weather Wisdom",
684
+ lines=8,
685
+ interactive=False,
686
+ placeholder="Enter a location and I will consult the weather spirits..."
687
+ )
688
+
689
+ # Example locations
690
+ gr.Examples(
691
+ examples=[
692
+ ["London, UK"],
693
+ ["Tokyo, Japan"],
694
+ ["New York, USA"],
695
+ ["Berlin, Germany"],
696
+ ["Sydney, Australia"]
697
+ ],
698
+ inputs=[location_input],
699
+ label="🌍 Try These Locations"
700
+ )
701
+
702
+ # Connection controls
703
+ with gr.Row():
704
+ connect_btn = gr.Button("🔗 Connect to MCP", variant="secondary")
705
+ status_btn = gr.Button("📊 Check Status", variant="secondary")
706
+
707
+ def handle_weather_request(location: str):
708
+ if not location.strip():
709
+ return "🌪️ The spirits need to know which realm you seek knowledge about!"
710
+ return self.get_weather(location)
711
+
712
+ def handle_connect():
713
+ result = self.connect_to_mcp()
714
+ status = f"<div style='color: {'green' if self.connected else 'red'};'>{'🟢 Connected to weather spirits' if self.connected else '🔴 Disconnected from weather realm'}</div>"
715
+ return result, status
716
+
717
+ def handle_status():
718
+ if self.connected:
719
+ tool_names = [tool.name for tool in self.tools] if self.tools else []
720
+ return f"✅ **Connected to MCP Weather Server**\n\nServer: {self.server_url}\nTools: {', '.join(tool_names) if tool_names else 'None'}"
721
+ else:
722
+ return "❌ **Not Connected**\n\nClick 'Connect to MCP' to establish connection."
723
+
724
+ # Wire up events
725
+ get_weather_btn.click(
726
+ handle_weather_request,
727
+ inputs=[location_input],
728
+ outputs=[weather_output]
729
+ )
730
+
731
+ location_input.submit(
732
+ handle_weather_request,
733
+ inputs=[location_input],
734
+ outputs=[weather_output]
735
+ )
736
+
737
+ connect_btn.click(
738
+ handle_connect,
739
+ outputs=[weather_output, connection_status]
740
+ )
741
+
742
+ status_btn.click(
743
+ handle_status,
744
+ outputs=[weather_output]
745
+ )
746
+
747
+ return interface
748
+
749
+ def connect_to_mcp(self) -> str:
750
+ """Connect to MCP weather server."""
751
+ current_time = time.time()
752
+ if current_time - self.last_connection_attempt < self.connection_cooldown:
753
+ return "⏳ Please wait before retrying connection..."
754
+
755
+ self.last_connection_attempt = current_time
756
+
757
+ try:
758
+ return self.loop.run_until_complete(self._connect())
759
+ except Exception as e:
760
+ self.connected = False
761
+ return f"❌ Connection failed: {str(e)}"
762
+
763
+ async def _connect(self) -> str:
764
+ """Async connect to MCP server using SSE."""
765
+ try:
766
+ # Clean up previous connection
767
+ if self.exit_stack:
768
+ await self.exit_stack.aclose()
769
+
770
+ self.exit_stack = AsyncExitStack()
771
+
772
+ # Connect to SSE MCP server
773
+ sse_transport = await self.exit_stack.enter_async_context(
774
+ sse_client(self.server_url)
775
+ )
776
+ read_stream, write_callable = sse_transport
777
+
778
+ self.session = await self.exit_stack.enter_async_context(
779
+ ClientSession(read_stream, write_callable)
780
+ )
781
+ await self.session.initialize()
782
+
783
+ # Get available tools
784
+ response = await self.session.list_tools()
785
+ self.tools = response.tools
786
+
787
+ self.connected = True
788
+ tool_names = [tool.name for tool in self.tools]
789
+ return f"✅ Connected to weather MCP server!\nAvailable tools: {', '.join(tool_names)}"
790
+
791
+ except Exception as e:
792
+ self.connected = False
793
+ return f"❌ Connection failed: {str(e)}"
794
+
795
+ def get_weather(self, location: str) -> str:
796
+ """Get weather for a location using actual MCP server"""
797
+ if not self.connected:
798
+ # Try to auto-reconnect
799
+ connect_result = self.connect_to_mcp()
800
+ if not self.connected:
801
+ return f"❌ **Not connected to weather spirits**\n\n{connect_result}"
802
+
803
+ try:
804
+ return self.loop.run_until_complete(self._get_weather(location))
805
+ except Exception as e:
806
+ return f"❌ **Weather divination failed**\n\nError: {str(e)}"
807
+
808
+ async def _get_weather(self, location: str) -> str:
809
+ """Async get weather using MCP."""
810
+ try:
811
+ # Parse location
812
+ if ',' in location:
813
+ city, country = [part.strip() for part in location.split(',', 1)]
814
+ else:
815
+ city = location.strip()
816
+ country = ""
817
+
818
+ # Find the weather tool
819
+ weather_tool = next((tool for tool in self.tools if 'weather' in tool.name.lower()), None)
820
+ if not weather_tool:
821
+ return "❌ Weather tool not found on server"
822
+
823
+ # Call the tool
824
+ params = {"city": city, "country": country}
825
+ result = await self.session.call_tool(weather_tool.name, params)
826
+
827
+ # Extract content properly
828
+ content_text = ""
829
+ if hasattr(result, 'content') and result.content:
830
+ if isinstance(result.content, list):
831
+ for content_item in result.content:
832
+ if hasattr(content_item, 'text'):
833
+ content_text += content_item.text + "\n"
834
+ else:
835
+ content_text += str(content_item) + "\n"
836
+ else:
837
+ content_text = str(result.content)
838
+
839
+ if not content_text.strip():
840
+ return f"❌ No weather data received for {location}"
841
+
842
+ # Format the response
843
+ return f"🌤️ **Weather Oracle reveals the weather for {location}:**\n\n{content_text.strip()}"
844
+
845
+ except Exception as e:
846
+ return f"❌ Weather divination failed: {str(e)}"
847
+
848
+ def handle_command(self, player_id: str, command: str) -> str:
849
+ """Handle Weather Oracle commands."""
850
+ cmd = command.strip()
851
+
852
+ if not cmd:
853
+ return ("🌤️ **Weather Oracle whispers:**\n\n"
854
+ "Ask me about the weather in any city!\n"
855
+ "Format: 'City, Country' (e.g., 'Berlin, Germany')")
856
+
857
+ # Treat any non-empty command as a location request
858
+ result = self.get_weather(cmd)
859
+ return f"🌤️ **Weather Oracle reveals:**\n\n{result}"
860
+
861
+
862
+ # Custom auto-registration function
863
+ def auto_register(game_engine):
864
+ """Auto-register the Weather Oracle addon with the game engine."""
865
+ try:
866
+ # Create the weather oracle NPC definition
867
+ weather_oracle_npc = {
868
+ 'id': 'weather_oracle_auto',
869
+ 'name': '🌤️ Weather Oracle (Auto)',
870
+ 'x': 300, 'y': 150,
871
+ 'char': '🌤️',
872
+ 'type': 'mcp',
873
+ 'personality': 'weather_oracle',
874
+ 'description': 'Self-contained MCP-powered weather information service'
875
+ }
876
+
877
+ # Register the NPC with the NPC service
878
+ npc_service = game_engine.get_npc_service()
879
+ npc_service.register_npc('weather_oracle_auto', weather_oracle_npc)
880
+
881
+ # Register the addon for handling private message commands
882
+ world = game_engine.get_world()
883
+ if not hasattr(world, 'addon_npcs'):
884
+ world.addon_npcs = {}
885
+ world.addon_npcs['weather_oracle_auto'] = weather_oracle_service
886
+
887
+ print("[WeatherOracleAddon] Auto-registered successfully as self-contained addon")
888
+ return True
889
+
890
+ except Exception as e:
891
+ print(f"[WeatherOracleAddon] Error during auto-registration: {e}")
892
+ return False
893
+
894
+
895
+ # Global Weather Oracle service instance
896
+ weather_oracle_service = WeatherOracleService()
897
+ ```
898
+
899
+ ---
900
+
901
+ ## 🔧 Advanced Patterns
902
+
903
+ ### State Persistence
904
+
905
+ ```python
906
+ import json
907
+ import os
908
+
909
+ class PersistentAddon(NPCAddon):
910
+ """Example addon with state persistence."""
911
+
912
+ def __init__(self):
913
+ super().__init__()
914
+ self.data_file = f"data/{self.addon_id}_data.json"
915
+ self.load_state()
916
+
917
+ def load_state(self):
918
+ """Load addon state from file."""
919
+ try:
920
+ if os.path.exists(self.data_file):
921
+ with open(self.data_file, 'r') as f:
922
+ data = json.load(f)
923
+ self.player_data = data.get('players', {})
924
+ self.settings = data.get('settings', {})
925
+ else:
926
+ self.player_data = {}
927
+ self.settings = {}
928
+ except Exception as e:
929
+ print(f"[{self.addon_id}] Failed to load state: {e}")
930
+ self.player_data = {}
931
+ self.settings = {}
932
+
933
+ def save_state(self):
934
+ """Save addon state to file."""
935
+ try:
936
+ os.makedirs(os.path.dirname(self.data_file), exist_ok=True)
937
+ with open(self.data_file, 'w') as f:
938
+ json.dump({
939
+ 'players': self.player_data,
940
+ 'settings': self.settings
941
+ }, f, indent=2)
942
+ except Exception as e:
943
+ print(f"[{self.addon_id}] Failed to save state: {e}")
944
+
945
+ def on_shutdown(self):
946
+ """Save state when addon shuts down."""
947
+ self.save_state()
948
+ ```
949
+
950
+ ### Player Validation Helper
951
+
952
+ ```python
953
+ def get_current_player(self, game_world):
954
+ """Helper to safely get current active player."""
955
+ try:
956
+ current_players = list(game_world.players.keys())
957
+ if not current_players:
958
+ return None, "❌ You must be in the game to use this service!"
959
+
960
+ player_id = max(current_players, key=lambda pid: game_world.players[pid].last_active)
961
+ player = game_world.players.get(player_id)
962
+
963
+ if not player:
964
+ return None, "❌ Player not found!"
965
+
966
+ return player, None
967
+ except Exception as e:
968
+ return None, f"❌ Error accessing player data: {e}"
969
+ ```
970
+
971
+ ### Async Operation Wrapper
972
+
973
+ ```python
974
+ def run_async_safely(self, async_func, *args, **kwargs):
975
+ """Safely run async functions in sync context."""
976
+ try:
977
+ return self.loop.run_until_complete(async_func(*args, **kwargs))
978
+ except Exception as e:
979
+ return f"❌ Async operation failed: {e}"
980
+ ```
981
+
982
+ ### Configuration Management
983
+
984
+ ```python
985
+ import yaml
986
+
987
+ class ConfigurableAddon(NPCAddon):
988
+ """Addon with configuration file support."""
989
+
990
+ def __init__(self):
991
+ super().__init__()
992
+ self.config = self.load_config()
993
+
994
+ def load_config(self):
995
+ """Load configuration from YAML file."""
996
+ config_file = f"config/{self.addon_id}_config.yaml"
997
+ try:
998
+ if os.path.exists(config_file):
999
+ with open(config_file, 'r') as f:
1000
+ return yaml.safe_load(f)
1001
+ else:
1002
+ return self.get_default_config()
1003
+ except Exception as e:
1004
+ print(f"[{self.addon_id}] Config load failed: {e}")
1005
+ return self.get_default_config()
1006
+
1007
+ def get_default_config(self):
1008
+ """Return default configuration."""
1009
+ return {
1010
+ 'enabled': True,
1011
+ 'max_operations': 100,
1012
+ 'cooldown_seconds': 30
1013
+ }
1014
+ ```
1015
+
1016
+ ---
1017
+
1018
+ ## 🎯 Best Practices
1019
+
1020
+ ### 1. Error Handling
1021
+
1022
+ ```python
1023
+ def handle_command(self, player_id: str, command: str) -> str:
1024
+ """Always wrap command handling in try-catch."""
1025
+ try:
1026
+ # Your command logic here
1027
+ return self.process_command(player_id, command)
1028
+ except Exception as e:
1029
+ print(f"[{self.addon_id}] Command error: {e}")
1030
+ return f"❌ An error occurred. Please try again or contact support."
1031
+ ```
1032
+
1033
+ ### 2. Input Validation
1034
+
1035
+ ```python
1036
+ def validate_input(self, input_value: str, max_length: int = 500) -> tuple[bool, str]:
1037
+ """Validate user input."""
1038
+ if not input_value or not input_value.strip():
1039
+ return False, "Input cannot be empty"
1040
+
1041
+ if len(input_value) > max_length:
1042
+ return False, f"Input too long (max {max_length} characters)"
1043
+
1044
+ # Add more validation as needed
1045
+ return True, ""
1046
+ ```
1047
+
1048
+ ### 3. Rate Limiting
1049
+
1050
+ ```python
1051
+ import time
1052
+ from collections import defaultdict
1053
+
1054
+ class RateLimitedAddon(NPCAddon):
1055
+ """Addon with rate limiting."""
1056
+
1057
+ def __init__(self):
1058
+ super().__init__()
1059
+ self.last_command_time = defaultdict(float)
1060
+ self.command_cooldown = 5 # 5 seconds between commands
1061
+
1062
+ def is_rate_limited(self, player_id: str) -> bool:
1063
+ """Check if player is rate limited."""
1064
+ current_time = time.time()
1065
+ last_time = self.last_command_time[player_id]
1066
+
1067
+ if current_time - last_time < self.command_cooldown:
1068
+ return True
1069
+
1070
+ self.last_command_time[player_id] = current_time
1071
+ return False
1072
+ ```
1073
+
1074
+ ### 4. Logging
1075
+
1076
+ ```python
1077
+ import logging
1078
+
1079
+ class LoggedAddon(NPCAddon):
1080
+ """Addon with proper logging."""
1081
+
1082
+ def __init__(self):
1083
+ super().__init__()
1084
+ self.logger = logging.getLogger(f"addon_{self.addon_id}")
1085
+ self.logger.setLevel(logging.INFO)
1086
+
1087
+ def handle_command(self, player_id: str, command: str) -> str:
1088
+ self.logger.info(f"Command from {player_id}: {command}")
1089
+ try:
1090
+ result = self.process_command(player_id, command)
1091
+ self.logger.info(f"Command successful for {player_id}")
1092
+ return result
1093
+ except Exception as e:
1094
+ self.logger.error(f"Command failed for {player_id}: {e}")
1095
+ return "❌ Command failed. Please try again."
1096
+ ```
1097
+
1098
+ ### 5. Resource Cleanup
1099
+
1100
+ ```python
1101
+ class ResourceManagedAddon(NPCAddon):
1102
+ """Addon with proper resource management."""
1103
+
1104
+ def __init__(self):
1105
+ super().__init__()
1106
+ self.resources = [] # Track resources
1107
+
1108
+ def on_shutdown(self):
1109
+ """Clean up resources on shutdown."""
1110
+ for resource in self.resources:
1111
+ try:
1112
+ if hasattr(resource, 'close'):
1113
+ resource.close()
1114
+ elif hasattr(resource, '__exit__'):
1115
+ resource.__exit__(None, None, None)
1116
+ except Exception as e:
1117
+ print(f"[{self.addon_id}] Cleanup error: {e}")
1118
+ ```
1119
+
1120
+ ---
1121
+
1122
+ ## 🧪 Deployment & Testing
1123
+
1124
+ ### Testing Your Addon
1125
+
1126
+ Create a test file for your addon:
1127
+
1128
+ ```python
1129
+ # tests/test_my_addon.py
1130
+ import pytest
1131
+ from src.addons.my_addon import MyAddon
1132
+
1133
+ class TestMyAddon:
1134
+ def setup_method(self):
1135
+ """Set up test fixtures."""
1136
+ self.addon = MyAddon()
1137
+
1138
+ def test_addon_id(self):
1139
+ """Test addon ID is correct."""
1140
+ assert self.addon.addon_id == "my_addon"
1141
+
1142
+ def test_handle_command_hello(self):
1143
+ """Test hello command."""
1144
+ result = self.addon.handle_command("test_player", "hello")
1145
+ assert "hello" in result.lower()
1146
+
1147
+ def test_handle_command_invalid(self):
1148
+ """Test invalid command handling."""
1149
+ result = self.addon.handle_command("test_player", "invalid_command")
1150
+ assert "didn't understand" in result.lower() or "help" in result.lower()
1151
+ ```
1152
+
1153
+ ### Running Tests
1154
+
1155
+ ```bash
1156
+ # Run all addon tests
1157
+ python -m pytest tests/test_*_addon.py -v
1158
+
1159
+ # Run specific addon test
1160
+ python -m pytest tests/test_my_addon.py -v
1161
+
1162
+ # Run with coverage
1163
+ python -m pytest tests/test_*_addon.py --cov=src/addons
1164
+ ```
1165
+
1166
+ ### Debug Mode
1167
+
1168
+ Add debug functionality to your addon:
1169
+
1170
+ ```python
1171
+ class DebuggableAddon(NPCAddon):
1172
+ """Addon with debug capabilities."""
1173
+
1174
+ def __init__(self):
1175
+ super().__init__()
1176
+ self.debug_mode = os.getenv('ADDON_DEBUG', 'false').lower() == 'true'
1177
+
1178
+ def debug_log(self, message: str):
1179
+ """Log debug messages."""
1180
+ if self.debug_mode:
1181
+ print(f"[DEBUG:{self.addon_id}] {message}")
1182
+
1183
+ def handle_command(self, player_id: str, command: str) -> str:
1184
+ self.debug_log(f"Received command: {command}")
1185
+ # ... rest of method
1186
+ ```
1187
+
1188
+ ### Performance Monitoring
1189
+
1190
+ ```python
1191
+ import time
1192
+ from functools import wraps
1193
+
1194
+ def monitor_performance(func):
1195
+ """Decorator to monitor function performance."""
1196
+ @wraps(func)
1197
+ def wrapper(self, *args, **kwargs):
1198
+ start_time = time.time()
1199
+ try:
1200
+ result = func(self, *args, **kwargs)
1201
+ duration = time.time() - start_time
1202
+ if duration > 1.0: # Log slow operations
1203
+ print(f"[{self.addon_id}] Slow operation: {func.__name__} took {duration:.2f}s")
1204
+ return result
1205
+ except Exception as e:
1206
+ duration = time.time() - start_time
1207
+ print(f"[{self.addon_id}] Error in {func.__name__} after {duration:.2f}s: {e}")
1208
+ raise
1209
+ return wrapper
1210
+
1211
+ class MonitoredAddon(NPCAddon):
1212
+ """Addon with performance monitoring."""
1213
+
1214
+ @monitor_performance
1215
+ def handle_command(self, player_id: str, command: str) -> str:
1216
+ # Your command handling logic
1217
+ pass
1218
+ ```
1219
+
1220
+ ### Integration Testing
1221
+
1222
+ Test your addon with the game engine:
1223
+
1224
+ ```python
1225
+ # tests/test_integration.py
1226
+ import asyncio
1227
+ from src.core.game_engine import GameEngine
1228
+ from src.addons.my_addon import MyAddon
1229
+
1230
+ def test_addon_integration():
1231
+ """Test addon integrates properly with game engine."""
1232
+ engine = GameEngine()
1233
+ engine.start()
1234
+
1235
+ # Check addon is registered
1236
+ world = engine.get_world()
1237
+ assert hasattr(world, 'addon_npcs')
1238
+ assert 'my_addon' in world.addon_npcs
1239
+
1240
+ # Test NPC exists
1241
+ npc_service = engine.get_npc_service()
1242
+ npc = npc_service.get_npc('my_addon_npc')
1243
+ assert npc is not None
1244
+
1245
+ engine.stop()
1246
+ ```
1247
+
1248
+ ---
1249
+
1250
+ ## 🔧 Troubleshooting
1251
+
1252
+ ### Common Issues
1253
+
1254
+ **1. Addon Not Auto-Registering**
1255
+ - Check that you have a global instance at the bottom of your file
1256
+ - Verify `addon_id` is unique and doesn't conflict with others
1257
+ - Ensure you're calling `super().__init__()` in your constructor
1258
+
1259
+ **2. UI Tab Not Appearing**
1260
+ - Check that `ui_tab_name` property returns a string, not None
1261
+ - Verify `get_interface()` returns a valid Gradio component
1262
+ - Look for exceptions in the console when the interface loads
1263
+
1264
+ **3. NPC Not Appearing in World**
1265
+ - Verify `npc_config` property returns a valid dictionary
1266
+ - Check that the NPC position coordinates are within world bounds
1267
+ - Ensure the NPC ID is unique
1268
+
1269
+ **4. Private Messages Not Working**
1270
+ - Confirm `handle_command()` is implemented
1271
+ - Check that the addon is registered in `world.addon_npcs`
1272
+ - Verify the NPC ID matches between world registration and addon registry
1273
+
1274
+ **5. MCP Connection Issues**
1275
+ - Check the MCP server URL is correct and accessible
1276
+ - Verify the server is running and supports the expected tools
1277
+ - Look for async/await issues in your MCP client code
1278
+ - Check firewall and network connectivity
1279
+
1280
+ ### Debug Checklist
1281
+
1282
+ - [ ] Addon file imported without errors
1283
+ - [ ] Global instance created at file bottom
1284
+ - [ ] All required methods implemented
1285
+ - [ ] Properties return correct types
1286
+ - [ ] No exceptions in console on startup
1287
+ - [ ] NPC appears in world at specified coordinates
1288
+ - [ ] UI tab appears in interface (if configured)
1289
+ - [ ] Private messages work correctly
1290
+ - [ ] Error handling covers edge cases
1291
+
1292
+ ### Getting Help
1293
+
1294
+ 1. **Check Logs**: Look for error messages in the console
1295
+ 2. **Test Individually**: Create a simple test script for your addon
1296
+ 3. **Validate Configuration**: Ensure all properties return expected values
1297
+ 4. **Compare Examples**: Look at working addons like SimpleTrader or Read2Burn
1298
+ 5. **Community Support**: Ask on project forums or GitHub issues
1299
+
1300
+ ---
1301
+
1302
+ ## 📚 Additional Resources
1303
+
1304
+ - **Interface Reference**: `src/interfaces/npc_addon.py` - Base class documentation
1305
+ - **Working Examples**: `src/addons/` - All current addon implementations
1306
+ - **Game Engine**: `src/core/game_engine.py` - Auto-registration system
1307
+ - **World Management**: `src/core/world.py` - NPC placement and management
1308
+ - **MCP Documentation**: [Model Context Protocol](https://github.com/modelcontextprotocol/python-sdk)
1309
+
1310
+ ---
1311
+
1312
+ *This guide reflects the current addon system architecture and includes real examples from the working codebase. All code examples are tested and functional.*
Documentation/NPC_Addon_Development_Guide_OLD.md ADDED
@@ -0,0 +1,1110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Building NPC Add-ons for MMORPG Game
2
+
3
+ This comprehensive guide shows you how to create custom NPC add-ons for the MMORPG game, with examples ranging from simple text-based NPCs to complex MCP-powered services.
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [Basic NPC Add-on Structure](#basic-npc-add-on-structure)
8
+ 2. [Creating a Simple Add-on](#creating-a-simple-add-on)
9
+ 3. [Advanced Add-on with State Management](#advanced-add-on-with-state-management)
10
+ 4. [MCP-Powered Add-on](#mcp-powered-add-on)
11
+ 5. [Integration into the Game](#integration-into-the-game)
12
+ 6. [Best Practices](#best-practices)
13
+ 7. [Troubleshooting](#troubleshooting)
14
+
15
+ ---
16
+
17
+ ## Basic NPC Add-on Structure
18
+
19
+ Every NPC add-on must inherit from the `NPCAddon` abstract base class and implement four required methods:
20
+
21
+ ```python
22
+ from abc import ABC, abstractmethod
23
+ import gradio as gr
24
+
25
+ class NPCAddon(ABC):
26
+ """Base class for NPC add-ons"""
27
+
28
+ @property
29
+ @abstractmethod
30
+ def addon_id(self) -> str:
31
+ """Unique identifier for this add-on"""
32
+ pass
33
+
34
+ @property
35
+ @abstractmethod
36
+ def addon_name(self) -> str:
37
+ """Display name for this add-on"""
38
+ pass
39
+
40
+ @abstractmethod
41
+ def get_interface(self) -> gr.Component:
42
+ """Return Gradio interface for this add-on"""
43
+ pass
44
+
45
+ @abstractmethod
46
+ def handle_command(self, player_id: str, command: str) -> str:
47
+ """Handle player commands via private messages"""
48
+ pass
49
+ ```
50
+
51
+ ### Key Requirements
52
+
53
+ 1. **Unique ID**: Each add-on needs a unique `addon_id`
54
+ 2. **Display Name**: User-friendly name shown in the interface
55
+ 3. **Gradio Interface**: Web interface for players to interact with
56
+ 4. **Command Handler**: Process commands sent via private messages to the NPC
57
+
58
+ ---
59
+
60
+ ## Creating a Simple Add-on
61
+
62
+ Let's create a **Fortune Teller** NPC that gives random predictions:
63
+
64
+ ```python
65
+ import random
66
+ import time
67
+ from typing import Dict, List
68
+
69
+ class FortuneTellerAddon(NPCAddon):
70
+ """Fortune Teller NPC - gives random predictions and wisdom"""
71
+
72
+ def __init__(self):
73
+ self.fortunes = [
74
+ "A great adventure awaits you beyond the eastern mountains!",
75
+ "Beware of strangers bearing gifts in the next full moon.",
76
+ "Your courage will be tested, but victory will be yours.",
77
+ "Gold will come to you through an unexpected friendship.",
78
+ "The stars suggest a powerful ally will join your quest.",
79
+ "A hidden treasure lies where the old oak meets the stream.",
80
+ "Your wisdom will grow through helping others in need.",
81
+ "Trust your instincts - they will guide you true.",
82
+ "A challenge approaches, but it will make you stronger.",
83
+ "The path you seek becomes clear under starlight."
84
+ ]
85
+
86
+ self.player_fortunes: Dict[str, Dict] = {} # Track player fortune history
87
+
88
+ @property
89
+ def addon_id(self) -> str:
90
+ return "fortune_teller"
91
+
92
+ @property
93
+ def addon_name(self) -> str:
94
+ return "🔮 Mystic Fortune Teller"
95
+
96
+ def get_interface(self) -> gr.Component:
97
+ """Create the Gradio interface for fortune telling"""
98
+ with gr.Column() as interface:
99
+ gr.Markdown("""
100
+ ## 🔮 Mystic Fortune Teller
101
+
102
+ *Peer into the mists of time and discover your destiny...*
103
+
104
+ **Services Available:**
105
+ - 🌟 **Daily Fortune** - Receive guidance for your journey
106
+ - 📜 **Fortune History** - View your past predictions
107
+ - 🎲 **Lucky Numbers** - Get your lucky numbers for today
108
+ """)
109
+
110
+ with gr.Row():
111
+ fortune_btn = gr.Button("🔮 Get My Fortune", variant="primary", scale=2)
112
+ history_btn = gr.Button("📜 View History", variant="secondary", scale=1)
113
+ lucky_btn = gr.Button("🎲 Lucky Numbers", variant="secondary", scale=1)
114
+
115
+ fortune_output = gr.Textbox(
116
+ label="🌟 Your Fortune",
117
+ lines=5,
118
+ interactive=False,
119
+ placeholder="Click 'Get My Fortune' to reveal your destiny..."
120
+ )
121
+
122
+ def get_player_fortune():
123
+ # Get current player (simplified - in real implementation use proper session management)
124
+ current_players = list(game_world.players.keys())
125
+ if not current_players:
126
+ return "❌ You must be in the game to receive a fortune reading!"
127
+
128
+ player_id = max(current_players, key=lambda pid: game_world.players[pid].last_active)
129
+ player_name = game_world.players[player_id].name
130
+
131
+ return self.get_daily_fortune(player_id, player_name)
132
+
133
+ def get_player_history():
134
+ current_players = list(game_world.players.keys())
135
+ if not current_players:
136
+ return "❌ You must be in the game to view your fortune history!"
137
+
138
+ player_id = max(current_players, key=lambda pid: game_world.players[pid].last_active)
139
+ return self.get_fortune_history(player_id)
140
+
141
+ def get_lucky_numbers():
142
+ current_players = list(game_world.players.keys())
143
+ if not current_players:
144
+ return "❌ You must be in the game to receive lucky numbers!"
145
+
146
+ player_id = max(current_players, key=lambda pid: game_world.players[pid].last_active)
147
+ return self.generate_lucky_numbers(player_id)
148
+
149
+ fortune_btn.click(get_player_fortune, outputs=[fortune_output])
150
+ history_btn.click(get_player_history, outputs=[fortune_output])
151
+ lucky_btn.click(get_lucky_numbers, outputs=[fortune_output])
152
+
153
+ return interface
154
+
155
+ def handle_command(self, player_id: str, command: str) -> str:
156
+ """Handle fortune teller commands via private messages"""
157
+ cmd = command.strip().lower()
158
+ player_name = game_world.players.get(player_id, {}).get('name', 'Traveler')
159
+
160
+ if cmd in ['fortune', 'predict', 'future', 'tell']:
161
+ return f"🔮 **Mystic Fortune Teller whispers:**\n\n{self.get_daily_fortune(player_id, player_name)}"
162
+
163
+ elif cmd in ['history', 'past', 'previous']:
164
+ return f"📜 **Your Fortune History:**\n\n{self.get_fortune_history(player_id)}"
165
+
166
+ elif cmd in ['lucky', 'numbers', 'luck']:
167
+ return f"🎲 **Lucky Numbers:**\n\n{self.generate_lucky_numbers(player_id)}"
168
+
169
+ else:
170
+ return ("🔮 **Mystic Fortune Teller:**\n\n"
171
+ "I can help you with:\n"
172
+ "• Say 'fortune' for your daily prediction\n"
173
+ "• Say 'history' to see past fortunes\n"
174
+ "• Say 'lucky' for your lucky numbers")
175
+
176
+ def get_daily_fortune(self, player_id: str, player_name: str) -> str:
177
+ """Get or generate daily fortune for player"""
178
+ today = time.strftime("%Y-%m-%d")
179
+
180
+ # Check if player already got fortune today
181
+ if player_id in self.player_fortunes:
182
+ player_data = self.player_fortunes[player_id]
183
+ if player_data.get('last_fortune_date') == today:
184
+ return (f"**{player_name}**, I have already revealed your destiny for today:\n\n"
185
+ f"✨ *{player_data['last_fortune']}*\n\n"
186
+ f"Return tomorrow for new guidance...")
187
+
188
+ # Generate new fortune
189
+ fortune = random.choice(self.fortunes)
190
+
191
+ # Store fortune
192
+ if player_id not in self.player_fortunes:
193
+ self.player_fortunes[player_id] = {'history': []}
194
+
195
+ self.player_fortunes[player_id].update({
196
+ 'last_fortune': fortune,
197
+ 'last_fortune_date': today
198
+ })
199
+
200
+ self.player_fortunes[player_id]['history'].append({
201
+ 'date': today,
202
+ 'fortune': fortune,
203
+ 'timestamp': time.time()
204
+ })
205
+
206
+ # Keep only last 10 fortunes
207
+ if len(self.player_fortunes[player_id]['history']) > 10:
208
+ self.player_fortunes[player_id]['history'] = self.player_fortunes[player_id]['history'][-10:]
209
+
210
+ return (f"**{player_name}**, the crystal ball reveals your destiny...\n\n"
211
+ f"✨ *{fortune}*\n\n"
212
+ f"May this wisdom guide your path, brave adventurer!")
213
+
214
+ def get_fortune_history(self, player_id: str) -> str:
215
+ """Get player's fortune history"""
216
+ if player_id not in self.player_fortunes or not self.player_fortunes[player_id].get('history'):
217
+ return "The mists show no past... you have not sought guidance before."
218
+
219
+ history = self.player_fortunes[player_id]['history']
220
+ result = "**Your Past Fortunes:**\n\n"
221
+
222
+ for i, entry in enumerate(reversed(history[-5:]), 1): # Last 5 fortunes
223
+ result += f"**{entry['date']}** - {entry['fortune']}\n\n"
224
+
225
+ return result
226
+
227
+ def generate_lucky_numbers(self, player_id: str) -> str:
228
+ """Generate lucky numbers based on player ID and current date"""
229
+ # Use player ID and date as seed for consistent daily numbers
230
+ seed_str = f"{player_id}_{time.strftime('%Y-%m-%d')}"
231
+ random.seed(hash(seed_str) % (2**32))
232
+
233
+ lucky_nums = sorted(random.sample(range(1, 100), 6))
234
+
235
+ # Reset random seed
236
+ random.seed()
237
+
238
+ numbers_str = " • ".join(map(str, lucky_nums))
239
+
240
+ return (f"🎲 **Your Lucky Numbers for Today:**\n\n"
241
+ f"✨ {numbers_str} ✨\n\n"
242
+ f"These numbers carry special energy today. "
243
+ f"Use them wisely in your adventures!")
244
+ ```
245
+
246
+ ---
247
+
248
+ ## Advanced Add-on with State Management
249
+
250
+ Here's a more complex example - a **Blacksmith NPC** that can upgrade weapons and manage an inventory:
251
+
252
+ ```python
253
+ import json
254
+ from datetime import datetime, timedelta
255
+
256
+ class BlacksmithAddon(NPCAddon):
257
+ """Advanced Blacksmith NPC with weapon upgrading and inventory management"""
258
+
259
+ def __init__(self):
260
+ self.weapons_catalog = {
261
+ 'iron_sword': {'name': 'Iron Sword', 'base_damage': 10, 'upgrade_cost': 50},
262
+ 'steel_sword': {'name': 'Steel Sword', 'base_damage': 15, 'upgrade_cost': 100},
263
+ 'silver_sword': {'name': 'Silver Sword', 'base_damage': 20, 'upgrade_cost': 200},
264
+ 'mithril_sword': {'name': 'Mithril Sword', 'base_damage': 30, 'upgrade_cost': 500},
265
+ }
266
+
267
+ self.player_weapons: Dict[str, Dict] = {} # player_id -> weapon data
268
+ self.upgrade_queue: List[Dict] = [] # Weapons being upgraded
269
+ self.shop_inventory = self.initialize_shop_inventory()
270
+
271
+ def initialize_shop_inventory(self):
272
+ """Initialize blacksmith shop inventory"""
273
+ return {
274
+ 'iron_ore': {'name': 'Iron Ore', 'price': 25, 'stock': 50},
275
+ 'steel_ingot': {'name': 'Steel Ingot', 'price': 75, 'stock': 30},
276
+ 'silver_ore': {'name': 'Silver Ore', 'price': 150, 'stock': 20},
277
+ 'enchant_stone': {'name': 'Enchantment Stone', 'price': 300, 'stock': 10},
278
+ 'repair_kit': {'name': 'Weapon Repair Kit', 'price': 40, 'stock': 25},
279
+ }
280
+
281
+ @property
282
+ def addon_id(self) -> str:
283
+ return "blacksmith"
284
+
285
+ @property
286
+ def addon_name(self) -> str:
287
+ return "⚒️ Master Blacksmith"
288
+
289
+ def get_interface(self) -> gr.Component:
290
+ with gr.Column() as interface:
291
+ gr.Markdown("""
292
+ ## ⚒️ Master Blacksmith Forge
293
+
294
+ *Welcome to the finest smithy in the realm!*
295
+
296
+ **Services:**
297
+ - 🗡️ **Weapon Upgrades** - Enhance your weapons for better damage
298
+ - 🛒 **Shop** - Buy materials and repair kits
299
+ - 📦 **Inventory** - View your weapons and materials
300
+ - 🔧 **Repairs** - Fix damaged equipment
301
+ """)
302
+
303
+ with gr.Tabs():
304
+ with gr.Tab("🗡️ Weapon Upgrade"):
305
+ with gr.Row():
306
+ weapon_select = gr.Dropdown(
307
+ choices=list(self.weapons_catalog.keys()),
308
+ label="Select Weapon to Upgrade",
309
+ value="iron_sword"
310
+ )
311
+ upgrade_level = gr.Number(
312
+ label="Upgrade Level (+1 to +10)",
313
+ value=1,
314
+ minimum=1,
315
+ maximum=10
316
+ )
317
+
318
+ upgrade_info = gr.HTML(label="Upgrade Information")
319
+ upgrade_btn = gr.Button("⚒️ Start Upgrade", variant="primary")
320
+ upgrade_result = gr.Textbox(label="Result", lines=5)
321
+
322
+ def calculate_upgrade_info(weapon, level):
323
+ if weapon in self.weapons_catalog:
324
+ weapon_data = self.weapons_catalog[weapon]
325
+ base_cost = weapon_data['upgrade_cost']
326
+ total_cost = base_cost * level * (level + 1) // 2 # Progressive cost
327
+ new_damage = weapon_data['base_damage'] + (level * 2)
328
+ time_required = level * 30 # 30 seconds per level
329
+
330
+ return f"""
331
+ <div style="background: #f0f8ff; padding: 15px; border-radius: 8px; border-left: 4px solid #4682b4;">
332
+ <h4>🗡️ {weapon_data['name']} +{level}</h4>
333
+ <p><strong>Base Damage:</strong> {weapon_data['base_damage']} → {new_damage}</p>
334
+ <p><strong>Upgrade Cost:</strong> {total_cost} gold</p>
335
+ <p><strong>Time Required:</strong> {time_required} seconds</p>
336
+ </div>
337
+ """
338
+ return "Select a weapon to see upgrade information."
339
+
340
+ weapon_select.change(calculate_upgrade_info, [weapon_select, upgrade_level], upgrade_info)
341
+ upgrade_level.change(calculate_upgrade_info, [weapon_select, upgrade_level], upgrade_info)
342
+
343
+ def handle_upgrade(weapon, level):
344
+ current_players = list(game_world.players.keys())
345
+ if not current_players:
346
+ return "❌ You must be in the game to upgrade weapons!"
347
+
348
+ player_id = max(current_players, key=lambda pid: game_world.players[pid].last_active)
349
+ return self.start_weapon_upgrade(player_id, weapon, int(level))
350
+
351
+ upgrade_btn.click(handle_upgrade, [weapon_select, upgrade_level], upgrade_result)
352
+
353
+ with gr.Tab("🛒 Shop"):
354
+ shop_display = gr.HTML()
355
+
356
+ with gr.Row():
357
+ item_select = gr.Dropdown(
358
+ choices=list(self.shop_inventory.keys()),
359
+ label="Select Item",
360
+ value=list(self.shop_inventory.keys())[0]
361
+ )
362
+ quantity = gr.Number(label="Quantity", value=1, minimum=1, maximum=99)
363
+
364
+ buy_btn = gr.Button("💰 Purchase", variant="primary")
365
+ purchase_result = gr.Textbox(label="Purchase Result", lines=3)
366
+
367
+ def update_shop_display():
368
+ html = "<div style='background: #f9f9f9; padding: 15px; border-radius: 8px;'>"
369
+ html += "<h4>🛒 Shop Inventory</h4>"
370
+ for item_id, item_data in self.shop_inventory.items():
371
+ html += f"""
372
+ <div style='margin: 10px 0; padding: 10px; background: white; border-radius: 4px;'>
373
+ <strong>{item_data['name']}</strong><br>
374
+ Price: {item_data['price']} gold | Stock: {item_data['stock']}
375
+ </div>
376
+ """
377
+ html += "</div>"
378
+ return html
379
+
380
+ def handle_purchase(item, qty):
381
+ current_players = list(game_world.players.keys())
382
+ if not current_players:
383
+ return "❌ You must be in the game to make purchases!"
384
+
385
+ player_id = max(current_players, key=lambda pid: game_world.players[pid].last_active)
386
+ return self.purchase_item(player_id, item, int(qty))
387
+
388
+ buy_btn.click(handle_purchase, [item_select, quantity], purchase_result)
389
+
390
+ # Update shop display on tab load
391
+ shop_display.value = update_shop_display()
392
+
393
+ with gr.Tab("📦 My Weapons"):
394
+ refresh_btn = gr.Button("🔄 Refresh Inventory")
395
+ inventory_display = gr.JSON(label="Your Weapons & Materials")
396
+
397
+ def refresh_inventory():
398
+ current_players = list(game_world.players.keys())
399
+ if not current_players:
400
+ return {"error": "You must be in the game to view inventory!"}
401
+
402
+ player_id = max(current_players, key=lambda pid: game_world.players[pid].last_active)
403
+ return self.get_player_inventory(player_id)
404
+
405
+ refresh_btn.click(refresh_inventory, outputs=inventory_display)
406
+
407
+ return interface
408
+
409
+ def handle_command(self, player_id: str, command: str) -> str:
410
+ """Handle blacksmith commands via private messages"""
411
+ cmd_parts = command.strip().lower().split()
412
+ cmd = cmd_parts[0] if cmd_parts else ""
413
+
414
+ player = game_world.players.get(player_id)
415
+ player_name = player.name if player else "Traveler"
416
+
417
+ if cmd in ['upgrade', 'enhance']:
418
+ if len(cmd_parts) < 2:
419
+ return ("⚒️ **Master Blacksmith:**\n\n"
420
+ "To upgrade a weapon, specify: `upgrade [weapon_name] [level]`\n"
421
+ f"Available weapons: {', '.join(self.weapons_catalog.keys())}")
422
+
423
+ weapon = cmd_parts[1]
424
+ level = int(cmd_parts[2]) if len(cmd_parts) > 2 else 1
425
+
426
+ return self.start_weapon_upgrade(player_id, weapon, level)
427
+
428
+ elif cmd == 'shop':
429
+ return self.format_shop_for_message()
430
+
431
+ elif cmd == 'inventory':
432
+ inventory = self.get_player_inventory(player_id)
433
+ return f"📦 **Your Inventory:**\n\n{json.dumps(inventory, indent=2)}"
434
+
435
+ elif cmd in ['buy', 'purchase']:
436
+ if len(cmd_parts) < 2:
437
+ return "💰 Specify what you want to buy: `buy [item_name] [quantity]`"
438
+
439
+ item = cmd_parts[1]
440
+ quantity = int(cmd_parts[2]) if len(cmd_parts) > 2 else 1
441
+
442
+ return self.purchase_item(player_id, item, quantity)
443
+
444
+ else:
445
+ return ("⚒️ **Master Blacksmith:**\n\n"
446
+ "I can help you with:\n"
447
+ "• `upgrade [weapon] [level]` - Upgrade weapons\n"
448
+ "• `shop` - View available items\n"
449
+ "• `buy [item] [qty]` - Purchase materials\n"
450
+ "• `inventory` - Check your weapons")
451
+
452
+ def start_weapon_upgrade(self, player_id: str, weapon_type: str, level: int) -> str:
453
+ """Start weapon upgrade process"""
454
+ if weapon_type not in self.weapons_catalog:
455
+ return f"❌ Unknown weapon type: {weapon_type}"
456
+
457
+ if level < 1 or level > 10:
458
+ return "❌ Upgrade level must be between 1 and 10"
459
+
460
+ weapon_data = self.weapons_catalog[weapon_type]
461
+ cost = weapon_data['upgrade_cost'] * level * (level + 1) // 2
462
+
463
+ # Check if player has enough gold (simplified - use game's gold system)
464
+ player = game_world.players.get(player_id)
465
+ if not player or player.gold < cost:
466
+ return f"❌ Insufficient gold! Need {cost} gold for this upgrade."
467
+
468
+ # Deduct gold
469
+ player.gold -= cost
470
+
471
+ # Add to upgrade queue
472
+ upgrade_id = f"upgrade_{int(time.time())}_{player_id}"
473
+ completion_time = time.time() + (level * 30) # 30 seconds per level
474
+
475
+ self.upgrade_queue.append({
476
+ 'id': upgrade_id,
477
+ 'player_id': player_id,
478
+ 'weapon_type': weapon_type,
479
+ 'level': level,
480
+ 'completion_time': completion_time
481
+ })
482
+
483
+ return (f"⚒️ **Upgrade Started!**\n\n"
484
+ f"Weapon: {weapon_data['name']} +{level}\n"
485
+ f"Cost: {cost} gold (paid)\n"
486
+ f"Completion: {level * 30} seconds\n\n"
487
+ f"Your weapon is now being forged...")
488
+
489
+ def purchase_item(self, player_id: str, item_id: str, quantity: int) -> str:
490
+ """Handle item purchases"""
491
+ if item_id not in self.shop_inventory:
492
+ return f"❌ Item '{item_id}' not available"
493
+
494
+ item = self.shop_inventory[item_id]
495
+
496
+ if quantity > item['stock']:
497
+ return f"❌ Only {item['stock']} {item['name']} in stock"
498
+
499
+ total_cost = item['price'] * quantity
500
+ player = game_world.players.get(player_id)
501
+
502
+ if not player or player.gold < total_cost:
503
+ return f"❌ Need {total_cost} gold for this purchase"
504
+
505
+ # Process purchase
506
+ player.gold -= total_cost
507
+ item['stock'] -= quantity
508
+
509
+ # Add to player inventory
510
+ if player_id not in self.player_weapons:
511
+ self.player_weapons[player_id] = {'materials': {}}
512
+
513
+ materials = self.player_weapons[player_id].setdefault('materials', {})
514
+ materials[item_id] = materials.get(item_id, 0) + quantity
515
+
516
+ return (f"✅ **Purchase Successful!**\n\n"
517
+ f"Bought: {quantity}x {item['name']}\n"
518
+ f"Cost: {total_cost} gold\n"
519
+ f"Remaining gold: {player.gold}")
520
+
521
+ def get_player_inventory(self, player_id: str) -> Dict:
522
+ """Get player's weapon and material inventory"""
523
+ if player_id not in self.player_weapons:
524
+ return {"weapons": [], "materials": {}}
525
+
526
+ return self.player_weapons[player_id]
527
+
528
+ def format_shop_for_message(self) -> str:
529
+ """Format shop inventory for private message"""
530
+ result = "🛒 **Blacksmith Shop**\n\n"
531
+ for item_id, item_data in self.shop_inventory.items():
532
+ result += f"**{item_data['name']}** - {item_data['price']} gold (Stock: {item_data['stock']})\n"
533
+
534
+ result += "\nTo buy: `buy [item_name] [quantity]`"
535
+ return result
536
+ ```
537
+
538
+ ---
539
+
540
+ ## MCP-Powered Add-on
541
+
542
+ Here's how to create an add-on that connects to an external MCP server:
543
+
544
+ ```python
545
+ import asyncio
546
+ from mcp import ClientSession
547
+ from mcp.client.sse import sse_client
548
+ from contextlib import AsyncExitStack
549
+
550
+ class WeatherOracleAddon(NPCAddon):
551
+ """Weather Oracle powered by external MCP weather service"""
552
+
553
+ def __init__(self, mcp_server_url: str = "https://chris4k-weather.hf.space/gradio_api/mcp/sse"):
554
+ self.mcp_server_url = mcp_server_url
555
+ self.mcp_client = None
556
+ self.connected = False
557
+ self.tools = []
558
+ self.exit_stack = None
559
+
560
+ # Set up event loop for async operations
561
+ self.loop = asyncio.new_event_loop()
562
+ asyncio.set_event_loop(self.loop)
563
+
564
+ # Auto-connect on initialization
565
+ self.connect_to_mcp()
566
+
567
+ @property
568
+ def addon_id(self) -> str:
569
+ return "weather_oracle"
570
+
571
+ @property
572
+ def addon_name(self) -> str:
573
+ return "🌤️ Weather Oracle"
574
+
575
+ def connect_to_mcp(self) -> str:
576
+ """Connect to the MCP weather server"""
577
+ return self.loop.run_until_complete(self._connect())
578
+
579
+ async def _connect(self) -> str:
580
+ try:
581
+ # Clean up previous connection
582
+ if self.exit_stack:
583
+ await self.exit_stack.aclose()
584
+
585
+ self.exit_stack = AsyncExitStack()
586
+
587
+ # Connect to SSE MCP server
588
+ sse_transport = await self.exit_stack.enter_async_context(
589
+ sse_client(self.mcp_server_url)
590
+ )
591
+ read_stream, write_callable = sse_transport
592
+
593
+ self.mcp_client = await self.exit_stack.enter_async_context(
594
+ ClientSession(read_stream, write_callable)
595
+ )
596
+ await self.mcp_client.initialize()
597
+
598
+ # Get available tools
599
+ response = await self.mcp_client.list_tools()
600
+ self.tools = response.tools
601
+
602
+ self.connected = True
603
+ tool_names = [tool.name for tool in self.tools]
604
+ return f"✅ Connected to weather server! Tools: {', '.join(tool_names)}"
605
+
606
+ except Exception as e:
607
+ self.connected = False
608
+ return f"❌ Connection failed: {str(e)}"
609
+
610
+ def get_interface(self) -> gr.Component:
611
+ with gr.Column() as interface:
612
+ gr.Markdown("""
613
+ ## 🌤️ Weather Oracle
614
+
615
+ *I commune with the spirits of sky and storm to bring you weather wisdom from across the realms!*
616
+
617
+ **Ask me about weather in any city:**
618
+ - Current conditions and temperature
619
+ - Weather forecasts
620
+ - Climate information
621
+
622
+ *Format: "City, Country" (e.g., "Berlin, Germany")*
623
+ """)
624
+
625
+ # Connection status
626
+ connection_status = gr.HTML(
627
+ value=f"<div style='color: {'green' if self.connected else 'red'};'>{'🟢 Connected to weather spirits' if self.connected else '🔴 Disconnected from weather realm'}</div>"
628
+ )
629
+
630
+ with gr.Row():
631
+ location_input = gr.Textbox(
632
+ label="Location",
633
+ placeholder="e.g., Berlin, Germany",
634
+ scale=3
635
+ )
636
+ get_weather_btn = gr.Button("🌡️ Consult Weather Spirits", variant="primary", scale=1)
637
+
638
+ weather_output = gr.Textbox(
639
+ label="🌤️ Weather Wisdom",
640
+ lines=8,
641
+ interactive=False,
642
+ placeholder="Enter a location and I will consult the weather spirits..."
643
+ )
644
+
645
+ # Example locations
646
+ with gr.Row():
647
+ gr.Examples(
648
+ examples=[
649
+ ["Berlin, Germany"],
650
+ ["Tokyo, Japan"],
651
+ ["New York, USA"],
652
+ ["London, UK"],
653
+ ["Sydney, Australia"],
654
+ ["Paris, France"],
655
+ ["Moscow, Russia"]
656
+ ],
657
+ inputs=[location_input],
658
+ label="🌍 Try These Locations"
659
+ )
660
+
661
+ def handle_weather_request(location: str):
662
+ current_players = list(game_world.players.keys())
663
+ if not current_players:
664
+ return "❌ You must be in the game to consult the weather spirits!"
665
+
666
+ player_id = max(current_players, key=lambda pid: game_world.players[pid].last_active)
667
+
668
+ return self.get_weather_prediction(location, player_id)
669
+
670
+ get_weather_btn.click(
671
+ handle_weather_request,
672
+ inputs=[location_input],
673
+ outputs=[weather_output]
674
+ )
675
+
676
+ location_input.submit(
677
+ handle_weather_request,
678
+ inputs=[location_input],
679
+ outputs=[weather_output]
680
+ )
681
+
682
+ return interface
683
+
684
+ def handle_command(self, player_id: str, command: str) -> str:
685
+ """Handle weather oracle commands via private messages"""
686
+ cmd = command.strip()
687
+
688
+ if not cmd:
689
+ return ("🌤️ **Weather Oracle whispers:**\n\n"
690
+ "Ask me about the weather in any city!\n"
691
+ "Format: 'City, Country' (e.g., 'Berlin, Germany')")
692
+
693
+ # Treat any non-empty command as a location request
694
+ result = self.get_weather_prediction(cmd, player_id)
695
+ return f"🌤️ **Weather Oracle reveals:**\n\n{result}"
696
+
697
+ def get_weather_prediction(self, location: str, player_id: str = None) -> str:
698
+ """Get weather prediction for location"""
699
+ if not self.connected:
700
+ # Try to reconnect
701
+ connect_result = self.connect_to_mcp()
702
+ if not self.connected:
703
+ return f"🌫️ The weather spirits are silent... Connection lost: {connect_result}"
704
+
705
+ if not location.strip():
706
+ return "🌪️ The spirits need to know which realm you seek knowledge about!"
707
+
708
+ return self.loop.run_until_complete(self._get_weather_async(location, player_id))
709
+
710
+ async def _get_weather_async(self, location: str, player_id: str = None) -> str:
711
+ try:
712
+ # Parse location
713
+ if ',' in location:
714
+ city, country = [part.strip() for part in location.split(',', 1)]
715
+ else:
716
+ city = location.strip()
717
+ country = ""
718
+
719
+ # Find the weather tool
720
+ weather_tool = next((tool for tool in self.tools if 'weather' in tool.name.lower()), None)
721
+ if not weather_tool:
722
+ return "🌫️ The weather spirits have retreated... No weather tools available."
723
+
724
+ # Call the MCP weather tool
725
+ params = {"city": city, "country": country}
726
+ result = await self.mcp_client.call_tool(weather_tool.name, params)
727
+
728
+ # Extract and format the weather data
729
+ content_text = self._extract_content(result)
730
+
731
+ if not content_text:
732
+ return "🌫️ The weather spirits speak in riddles I cannot decipher..."
733
+
734
+ # Try to parse and format the weather data
735
+ formatted_result = self._format_weather_response(content_text, city, country)
736
+
737
+ # Log the interaction
738
+ if player_id and player_id in game_world.players:
739
+ player_name = game_world.players[player_id].name
740
+ print(f"[WEATHER_ORACLE] {player_name} asked about weather in {location}")
741
+
742
+ return formatted_result
743
+
744
+ except Exception as e:
745
+ return f"🌩️ The weather spirits are troubled: {str(e)}"
746
+
747
+ def _extract_content(self, result) -> str:
748
+ """Extract content from MCP response"""
749
+ content_text = ""
750
+ if hasattr(result, 'content') and result.content:
751
+ if isinstance(result.content, list):
752
+ for content_item in result.content:
753
+ if hasattr(content_item, 'text'):
754
+ content_text += content_item.text
755
+ elif hasattr(content_item, 'content'):
756
+ content_text += str(content_item.content)
757
+ else:
758
+ content_text += str(content_item)
759
+ elif hasattr(result.content, 'text'):
760
+ content_text = result.content.text
761
+ else:
762
+ content_text = str(result.content)
763
+ return content_text
764
+
765
+ def _format_weather_response(self, content_text: str, city: str, country: str) -> str:
766
+ """Format weather response in a mystical way"""
767
+ try:
768
+ # Try to parse as JSON
769
+ parsed = json.loads(content_text)
770
+ if isinstance(parsed, dict):
771
+ if 'error' in parsed:
772
+ return f"🌫️ The spirits say: {parsed['error']}"
773
+
774
+ # Format with mystical flair
775
+ location_name = parsed.get('location', f"{city}, {country}")
776
+
777
+ if 'current_weather' in parsed:
778
+ weather = parsed['current_weather']
779
+ temp = weather.get('temperature_celsius', 'Unknown')
780
+ conditions = weather.get('weather_description', 'mysterious')
781
+ wind = weather.get('wind_speed_kmh', 'calm')
782
+ humidity = weather.get('humidity_percent', 'balanced')
783
+
784
+ return (f"🌍 **The spirits speak of {location_name}:**\n\n"
785
+ f"🌡️ **Temperature:** {temp}°C - The fire spirits dance at this warmth\n"
786
+ f"🌤️ **Conditions:** {conditions} - The sky spirits reveal their mood\n"
787
+ f"💨 **Wind:** {wind} km/h - The air spirits move with purpose\n"
788
+ f"💧 **Humidity:** {humidity}% - The water spirits' presence\n\n"
789
+ f"*May this knowledge guide your journey, brave traveler!*")
790
+
791
+ elif 'temperature (°C)' in parsed:
792
+ temp = parsed.get('temperature (°C)', 'Unknown')
793
+ weather_code = parsed.get('weather_code', 'Unknown')
794
+ timezone = parsed.get('timezone', 'Unknown')
795
+ local_time = parsed.get('local_time', 'Unknown')
796
+
797
+ return (f"🌍 **The ancient spirits whisper of {location_name}:**\n\n"
798
+ f"🌡️ **Sacred Temperature:** {temp}°C\n"
799
+ f"🌤️ **Weather Rune:** {weather_code}\n"
800
+ f"🕐 **Realm:** {timezone}\n"
801
+ f"🕒 **Current Time:** {local_time}\n\n"
802
+ f"*The weather spirits have spoken!*")
803
+
804
+ else:
805
+ return f"✨ **Weather spirits reveal:**\n```\n{json.dumps(parsed, indent=2)}\n```"
806
+
807
+ except json.JSONDecodeError:
808
+ # If not JSON, format as mystical text
809
+ return f"🌟 **The weather spirits whisper:**\n\n{content_text}\n\n*Such is their ancient wisdom...*"
810
+
811
+ return f"🌫️ The spirits speak in riddles: {content_text}"
812
+ ```
813
+
814
+ ---
815
+
816
+ ## Integration into the Game
817
+
818
+ To integrate your new add-on into the game, follow these steps:
819
+
820
+ ### 1. Register the Add-on
821
+
822
+ In your main game file (`app.py`), add your add-on to the `create_mmorpg_interface()` function:
823
+
824
+ ```python
825
+ def create_mmorpg_interface():
826
+ """Create the complete MMORPG interface with all features"""
827
+
828
+ # Initialize add-ons
829
+ read2burn_addon = Read2BurnMailboxAddon()
830
+ game_world.addon_npcs['read2burn_mailbox'] = read2burn_addon
831
+
832
+ # Add your new add-ons here
833
+ fortune_teller_addon = FortuneTellerAddon()
834
+ game_world.addon_npcs['fortune_teller'] = fortune_teller_addon
835
+
836
+ blacksmith_addon = BlacksmithAddon()
837
+ game_world.addon_npcs['blacksmith'] = blacksmith_addon
838
+
839
+ weather_oracle_addon = WeatherOracleAddon()
840
+ game_world.addon_npcs['weather_oracle'] = weather_oracle_addon
841
+
842
+ # ... rest of the interface code
843
+ ```
844
+
845
+ ### 2. Add NPC to the World
846
+
847
+ Add your NPC to the game world's NPC dictionary:
848
+
849
+ ```python
850
+ def __init__(self):
851
+ self.npcs: Dict[str, Dict] = {
852
+ # ... existing NPCs ...
853
+
854
+ 'fortune_teller': {
855
+ 'id': 'fortune_teller',
856
+ 'name': 'Mystic Zelda',
857
+ 'x': 125, 'y': 75,
858
+ 'char': '🔮',
859
+ 'type': 'addon'
860
+ },
861
+ 'blacksmith': {
862
+ 'id': 'blacksmith',
863
+ 'name': 'Master Forge',
864
+ 'x': 400, 'y': 300,
865
+ 'char': '⚒️',
866
+ 'type': 'addon'
867
+ },
868
+ # weather_oracle already exists
869
+ }
870
+ ```
871
+
872
+ ### 3. Add Interface Tab
873
+
874
+ Add your add-on's interface to the NPC Add-ons tab:
875
+
876
+ ```python
877
+ with gr.Tab("🤖 NPC Add-ons"):
878
+ with gr.Tabs() as addon_tabs:
879
+ # Existing add-ons
880
+ with gr.Tab("🔥 Read2Burn Mailbox"):
881
+ read2burn_addon.get_interface()
882
+
883
+ with gr.Tab("🌤️ Weather Oracle"):
884
+ weather_oracle_addon.get_interface()
885
+
886
+ # Add your new add-ons here
887
+ with gr.Tab("🔮 Fortune Teller"):
888
+ fortune_teller_addon.get_interface()
889
+
890
+ with gr.Tab("⚒️ Blacksmith"):
891
+ blacksmith_addon.get_interface()
892
+ ```
893
+
894
+ ---
895
+
896
+ ## Best Practices
897
+
898
+ ### 1. Error Handling
899
+
900
+ Always include proper error handling in your add-ons:
901
+
902
+ ```python
903
+ def handle_command(self, player_id: str, command: str) -> str:
904
+ try:
905
+ # Your command logic here
906
+ return self.process_command(player_id, command)
907
+ except Exception as e:
908
+ print(f"[{self.addon_id}] Error: {e}")
909
+ return f"⚠️ Something went wrong: {str(e)}"
910
+ ```
911
+
912
+ ### 2. Player Validation
913
+
914
+ Always validate that players exist before processing commands:
915
+
916
+ ```python
917
+ def get_current_player(self):
918
+ """Get the current active player safely"""
919
+ current_players = list(game_world.players.keys())
920
+ if not current_players:
921
+ return None, "❌ You must be in the game to use this service!"
922
+
923
+ player_id = max(current_players, key=lambda pid: game_world.players[pid].last_active)
924
+ player = game_world.players.get(player_id)
925
+
926
+ if not player:
927
+ return None, "❌ Player not found!"
928
+
929
+ return player, None
930
+ ```
931
+
932
+ ### 3. State Persistence
933
+
934
+ For add-ons that maintain state, consider implementing save/load functionality:
935
+
936
+ ```python
937
+ import json
938
+ import os
939
+
940
+ class StatefulAddon(NPCAddon):
941
+ def __init__(self):
942
+ self.state_file = f"addon_state_{self.addon_id}.json"
943
+ self.load_state()
944
+
945
+ def save_state(self):
946
+ """Save add-on state to file"""
947
+ try:
948
+ state_data = {
949
+ 'player_data': self.player_data,
950
+ 'global_settings': self.global_settings,
951
+ 'last_saved': time.time()
952
+ }
953
+ with open(self.state_file, 'w') as f:
954
+ json.dump(state_data, f, indent=2)
955
+ except Exception as e:
956
+ print(f"[{self.addon_id}] Failed to save state: {e}")
957
+
958
+ def load_state(self):
959
+ """Load add-on state from file"""
960
+ try:
961
+ if os.path.exists(self.state_file):
962
+ with open(self.state_file, 'r') as f:
963
+ state_data = json.load(f)
964
+ self.player_data = state_data.get('player_data', {})
965
+ self.global_settings = state_data.get('global_settings', {})
966
+ else:
967
+ self.player_data = {}
968
+ self.global_settings = {}
969
+ except Exception as e:
970
+ print(f"[{self.addon_id}] Failed to load state: {e}")
971
+ self.player_data = {}
972
+ self.global_settings = {}
973
+ ```
974
+
975
+ ### 4. Async Operations
976
+
977
+ For add-ons that need to perform async operations (like MCP calls), use proper async handling:
978
+
979
+ ```python
980
+ def sync_wrapper_for_async_function(self, *args, **kwargs):
981
+ """Wrapper to run async functions in sync context"""
982
+ if not hasattr(self, 'loop') or self.loop.is_closed():
983
+ self.loop = asyncio.new_event_loop()
984
+ asyncio.set_event_loop(self.loop)
985
+
986
+ return self.loop.run_until_complete(self.async_function(*args, **kwargs))
987
+ ```
988
+
989
+ ### 5. Resource Management
990
+
991
+ Clean up resources properly:
992
+
993
+ ```python
994
+ def __del__(self):
995
+ """Cleanup when add-on is destroyed"""
996
+ if hasattr(self, 'exit_stack') and self.exit_stack:
997
+ try:
998
+ asyncio.run(self.exit_stack.aclose())
999
+ except:
1000
+ pass
1001
+
1002
+ self.save_state() # Save state before destruction
1003
+ ```
1004
+
1005
+ ---
1006
+
1007
+ ## Troubleshooting
1008
+
1009
+ ### Common Issues
1010
+
1011
+ 1. **Add-on not appearing in interface**
1012
+ - Check that you registered it in `game_world.addon_npcs`
1013
+ - Verify the addon_id matches the NPC ID
1014
+ - Ensure no exceptions in `get_interface()`
1015
+
1016
+ 2. **Private messages not working**
1017
+ - Verify `handle_command()` is implemented
1018
+ - Check that the NPC ID exists in `game_world.npcs`
1019
+ - Make sure the addon is registered correctly
1020
+
1021
+ 3. **MCP connection issues**
1022
+ - Check the MCP server URL is correct
1023
+ - Verify the server is running and accessible
1024
+ - Look for async/await issues in your code
1025
+
1026
+ 4. **State not persisting**
1027
+ - Implement proper save/load methods
1028
+ - Handle file permissions issues
1029
+ - Check for JSON serialization errors
1030
+
1031
+ ### Debug Tips
1032
+
1033
+ 1. **Add logging** to your add-ons:
1034
+ ```python
1035
+ import logging
1036
+
1037
+ logging.basicConfig(level=logging.DEBUG)
1038
+ logger = logging.getLogger(f"addon_{self.addon_id}")
1039
+
1040
+ def handle_command(self, player_id: str, command: str) -> str:
1041
+ logger.debug(f"Received command '{command}' from player {player_id}")
1042
+ # ... rest of method
1043
+ ```
1044
+
1045
+ 2. **Test your add-on standalone**:
1046
+ ```python
1047
+ if __name__ == "__main__":
1048
+ # Test your add-on independently
1049
+ addon = YourAddon()
1050
+ test_result = addon.handle_command("test_player", "test command")
1051
+ print(f"Test result: {test_result}")
1052
+ ```
1053
+
1054
+ 3. **Use the browser console** to debug Gradio interface issues
1055
+
1056
+ 4. **Check the game's world events** for proximity detection issues
1057
+
1058
+ ---
1059
+
1060
+ ## Advanced Features
1061
+
1062
+ ### Dynamic Add-on Loading
1063
+
1064
+ You can implement dynamic add-on loading for hot-swapping add-ons:
1065
+
1066
+ ```python
1067
+ import importlib
1068
+ import os
1069
+
1070
+ def load_addon_from_file(filepath: str, addon_class_name: str):
1071
+ """Dynamically load an add-on from a Python file"""
1072
+ try:
1073
+ spec = importlib.util.spec_from_file_location("dynamic_addon", filepath)
1074
+ module = importlib.util.module_from_spec(spec)
1075
+ spec.loader.exec_module(module)
1076
+
1077
+ addon_class = getattr(module, addon_class_name)
1078
+ return addon_class()
1079
+ except Exception as e:
1080
+ print(f"Failed to load addon from {filepath}: {e}")
1081
+ return None
1082
+ ```
1083
+
1084
+ ### Configuration Files
1085
+
1086
+ Use configuration files for add-on settings:
1087
+
1088
+ ```python
1089
+ import yaml
1090
+
1091
+ class ConfigurableAddon(NPCAddon):
1092
+ def __init__(self, config_file: str = None):
1093
+ self.config = self.load_config(config_file or f"{self.addon_id}_config.yaml")
1094
+
1095
+ def load_config(self, config_file: str):
1096
+ try:
1097
+ with open(config_file, 'r') as f:
1098
+ return yaml.safe_load(f)
1099
+ except:
1100
+ return self.get_default_config()
1101
+
1102
+ def get_default_config(self):
1103
+ return {
1104
+ 'enabled': True,
1105
+ 'max_operations_per_player': 10,
1106
+ 'cooldown_seconds': 30
1107
+ }
1108
+ ```
1109
+
1110
+ This comprehensive guide should give you everything you need to create powerful, feature-rich NPC add-ons for the MMORPG game!
Documentation/NPC_Addon_Development_Guide_Updated.md ADDED
@@ -0,0 +1,1312 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🛠️ NPC Addon Development Guide - Complete & Updated
2
+
3
+ > **Updated for Current Architecture** - This guide reflects the latest addon system with auto-registration, modern patterns, and actual working examples from the codebase.
4
+
5
+ ## 📋 Table of Contents
6
+
7
+ 1. [Quick Start](#quick-start)
8
+ 2. [Modern Addon Architecture](#modern-addon-architecture)
9
+ 3. [Auto-Registration System](#auto-registration-system)
10
+ 4. [Complete Examples](#complete-examples)
11
+ 5. [Advanced Patterns](#advanced-patterns)
12
+ 6. [MCP Integration](#mcp-integration)
13
+ 7. [Best Practices](#best-practices)
14
+ 8. [Deployment & Testing](#deployment--testing)
15
+
16
+ ---
17
+
18
+ ## 🚀 Quick Start
19
+
20
+ ### Creating Your First Addon
21
+
22
+ The modern addon system uses **auto-registration** - no manual edits to other files required!
23
+
24
+ ```python
25
+ # src/addons/my_first_addon.py
26
+ """
27
+ My First Addon - A simple self-contained NPC addon.
28
+ """
29
+
30
+ import gradio as gr
31
+ from ..interfaces.npc_addon import NPCAddon
32
+
33
+
34
+ class MyFirstAddon(NPCAddon):
35
+ """A simple greeting NPC that demonstrates the addon system."""
36
+
37
+ def __init__(self):
38
+ super().__init__() # This auto-registers the addon!
39
+ self.greeting_count = 0
40
+
41
+ @property
42
+ def addon_id(self) -> str:
43
+ """Unique identifier - must be unique across all addons"""
44
+ return "my_first_addon"
45
+
46
+ @property
47
+ def addon_name(self) -> str:
48
+ """Display name shown in UI"""
49
+ return "🤝 My First Addon"
50
+
51
+ @property
52
+ def npc_config(self) -> Dict[str, Any]:
53
+ """NPC configuration for auto-placement in world"""
54
+ return {
55
+ 'id': 'my_first_npc',
56
+ 'name': '🤝 Friendly Greeter',
57
+ 'x': 100, 'y': 100, # Position in game world
58
+ 'char': '🤝', # Character emoji
59
+ 'type': 'addon', # NPC type
60
+ 'description': 'A friendly NPC that greets players'
61
+ }
62
+
63
+ @property
64
+ def ui_tab_name(self) -> str:
65
+ """UI tab name - returns None if no UI tab needed"""
66
+ return "🤝 Greeter"
67
+
68
+ def get_interface(self) -> gr.Component:
69
+ """Create the Gradio interface for this addon"""
70
+ with gr.Column() as interface:
71
+ gr.Markdown("## 🤝 Friendly Greeter\n*Hello! I'm here to greet players.*")
72
+
73
+ greeting_btn = gr.Button("👋 Get Greeting", variant="primary")
74
+ greeting_output = gr.Textbox(label="Greeting", lines=3, interactive=False)
75
+
76
+ def get_greeting():
77
+ self.greeting_count += 1
78
+ return f"Hello! This is greeting #{self.greeting_count}. Welcome to the game!"
79
+
80
+ greeting_btn.click(get_greeting, outputs=[greeting_output])
81
+
82
+ return interface
83
+
84
+ def handle_command(self, player_id: str, command: str) -> str:
85
+ """Handle commands sent via private messages to this NPC"""
86
+ cmd = command.lower().strip()
87
+
88
+ if cmd in ['hello', 'hi', 'greet']:
89
+ self.greeting_count += 1
90
+ return f"🤝 **Friendly Greeter says:**\nHello! Greeting #{self.greeting_count}!"
91
+ elif cmd == 'help':
92
+ return "🤝 **Available commands:**\n• hello/hi/greet - Get a greeting\n• help - Show this help"
93
+ else:
94
+ return "🤝 **Friendly Greeter:**\nI didn't understand that. Try 'hello' or 'help'!"
95
+
96
+
97
+ # Global instance - this triggers auto-registration!
98
+ my_first_addon = MyFirstAddon()
99
+ ```
100
+
101
+ **That's it!** Your addon is now fully functional and auto-registered. No other files need to be modified.
102
+
103
+ ---
104
+
105
+ ## 🏗️ Modern Addon Architecture
106
+
107
+ ### Base Class Structure
108
+
109
+ All addons inherit from `NPCAddon` which provides:
110
+
111
+ ```python
112
+ class NPCAddon(ABC):
113
+ """Base class for NPC add-ons with auto-registration support"""
114
+
115
+ def __init__(self):
116
+ """Initialize and auto-register the addon"""
117
+ # Auto-register this addon when instantiated
118
+ _addon_registry[self.addon_id] = self
119
+
120
+ # Required Properties
121
+ @property
122
+ @abstractmethod
123
+ def addon_id(self) -> str: pass
124
+
125
+ @property
126
+ @abstractmethod
127
+ def addon_name(self) -> str: pass
128
+
129
+ # Optional Properties
130
+ @property
131
+ def npc_config(self) -> Optional[Dict[str, Any]]: return None
132
+
133
+ @property
134
+ def ui_tab_name(self) -> Optional[str]: return None
135
+
136
+ # Required Methods
137
+ @abstractmethod
138
+ def get_interface(self) -> gr.Component: pass
139
+
140
+ @abstractmethod
141
+ def handle_command(self, player_id: str, command: str) -> str: pass
142
+
143
+ # Optional Lifecycle Methods
144
+ def on_startup(self): pass
145
+ def on_shutdown(self): pass
146
+ ```
147
+
148
+ ### Core Concepts
149
+
150
+ 1. **Auto-Registration**: Simply instantiating your addon class registers it globally
151
+ 2. **Self-Contained**: Everything in one file - NPC config, UI, logic
152
+ 3. **Optional Components**: Only implement what you need (UI tab, world NPC, etc.)
153
+ 4. **Service Pattern**: Use interfaces for complex functionality
154
+
155
+ ---
156
+
157
+ ## 🔄 Auto-Registration System
158
+
159
+ ### How It Works
160
+
161
+ 1. **Create addon class** that inherits from `NPCAddon`
162
+ 2. **Instantiate globally** at the bottom of your addon file
163
+ 3. **Auto-discovery** by the game engine at startup
164
+ 4. **Automatic registration** of NPCs and UI components
165
+
166
+ ### Current Auto-Registration List
167
+
168
+ The game engine automatically loads these addons:
169
+
170
+ ```python
171
+ # src/core/game_engine.py
172
+ auto_register_addons = [
173
+ ('weather_oracle_addon', 'auto_register'),
174
+ # Add your addon here if using custom auto_register function
175
+ ]
176
+ ```
177
+
178
+ ### Two Registration Patterns
179
+
180
+ #### Pattern 1: Global Instance (Recommended)
181
+ ```python
182
+ # At bottom of your addon file
183
+ my_addon = MyAddon() # Auto-registers via __init__
184
+ ```
185
+
186
+ #### Pattern 2: Custom Auto-Register Function
187
+ ```python
188
+ def auto_register(game_engine):
189
+ """Custom registration logic"""
190
+ try:
191
+ addon_instance = MyAddon()
192
+ # Custom registration logic here
193
+ return True
194
+ except Exception as e:
195
+ print(f"Registration failed: {e}")
196
+ return False
197
+
198
+ # Add to game_engine.py auto_register_addons list
199
+ ```
200
+
201
+ ---
202
+
203
+ ## 📚 Complete Examples
204
+
205
+ ### Example 1: Simple Trader (From Codebase)
206
+
207
+ ```python
208
+ """
209
+ Simple Trader NPC Add-on - Self-contained NPC with automatic registration.
210
+ """
211
+
212
+ import gradio as gr
213
+ from typing import Dict
214
+ from ..interfaces.npc_addon import NPCAddon
215
+
216
+
217
+ class SimpleTraderAddon(NPCAddon):
218
+ """Self-contained trader NPC that handles its own registration and positioning."""
219
+
220
+ def __init__(self):
221
+ super().__init__()
222
+ self.inventory = {
223
+ "Health Potion": {"price": 50, "stock": 10, "description": "Restores 100 HP"},
224
+ "Magic Scroll": {"price": 150, "stock": 5, "description": "Cast magic spells"},
225
+ "Iron Sword": {"price": 300, "stock": 3, "description": "Sharp iron weapon"},
226
+ "Shield": {"price": 200, "stock": 4, "description": "Protective gear"}
227
+ }
228
+ self.sales_history = []
229
+
230
+ @property
231
+ def addon_id(self) -> str:
232
+ return "simple_trader"
233
+
234
+ @property
235
+ def addon_name(self) -> str:
236
+ return "🛒 Simple Trader"
237
+
238
+ @property
239
+ def npc_config(self) -> Dict:
240
+ return {
241
+ 'id': 'simple_trader',
242
+ 'name': '🛒 Simple Trader',
243
+ 'x': 450, 'y': 150,
244
+ 'char': '🛒',
245
+ 'type': 'addon',
246
+ 'personality': 'trader',
247
+ 'description': 'A friendly trader selling useful items'
248
+ }
249
+
250
+ @property
251
+ def ui_tab_name(self) -> str:
252
+ return "🛒 Simple Trader"
253
+
254
+ def get_interface(self) -> gr.Component:
255
+ """Create the Gradio interface for this addon."""
256
+ with gr.Column() as interface:
257
+ gr.Markdown("### 🛒 Simple Trader Shop")
258
+ gr.Markdown("Browse items and check prices. Use private messages to trade!")
259
+
260
+ # Display inventory
261
+ inventory_data = []
262
+ for item, info in self.inventory.items():
263
+ inventory_data.append([item, f"{info['price']} gold", info['stock'], info['description']])
264
+
265
+ gr.Dataframe(
266
+ headers=["Item", "Price", "Stock", "Description"],
267
+ value=inventory_data,
268
+ interactive=False
269
+ )
270
+
271
+ gr.Markdown("**Commands:** Send private message to Simple Trader")
272
+ gr.Markdown("• `buy <item>` - Purchase an item")
273
+ gr.Markdown("• `inventory` - See available items")
274
+ gr.Markdown("• `help` - Show all commands")
275
+
276
+ return interface
277
+
278
+ def handle_command(self, player_id: str, command: str) -> str:
279
+ """Handle commands sent to this NPC."""
280
+ parts = command.strip().lower().split()
281
+ cmd = parts[0] if parts else ""
282
+
283
+ if cmd == "buy" and len(parts) > 1:
284
+ item_name = " ".join(parts[1:]).title()
285
+ return self._handle_buy(player_id, item_name)
286
+ elif cmd == "inventory":
287
+ return self._show_inventory()
288
+ elif cmd == "help":
289
+ return self._show_help()
290
+ else:
291
+ return "🛒 **Simple Trader:** I didn't understand. Try 'buy <item>', 'inventory', or 'help'."
292
+
293
+ def _handle_buy(self, player_id: str, item_name: str) -> str:
294
+ """Handle item purchase."""
295
+ if item_name not in self.inventory:
296
+ return f"❌ Sorry, I don't have '{item_name}' in stock."
297
+
298
+ item = self.inventory[item_name]
299
+ if item['stock'] <= 0:
300
+ return f"❌ '{item_name}' is out of stock!"
301
+
302
+ # In a real implementation, check player gold and deduct
303
+ item['stock'] -= 1
304
+ self.sales_history.append({'player': player_id, 'item': item_name, 'price': item['price']})
305
+
306
+ return f"✅ **Purchase Successful!**\n📦 You bought: {item_name}\n💰 Price: {item['price']} gold\n📊 Stock remaining: {item['stock']}"
307
+
308
+ def _show_inventory(self) -> str:
309
+ """Show current inventory."""
310
+ result = "🛒 **Current Inventory:**\n\n"
311
+ for item, info in self.inventory.items():
312
+ result += f"**{item}** - {info['price']} gold (Stock: {info['stock']})\n"
313
+ result += f" ↳ {info['description']}\n\n"
314
+ return result
315
+
316
+ def _show_help(self) -> str:
317
+ """Show help commands."""
318
+ return """🛒 **Simple Trader Commands:**
319
+
320
+ **buy <item>** - Purchase an item
321
+ **inventory** - See available items and prices
322
+ **help** - Show this help
323
+
324
+ 💰 **Available Items:**
325
+ • Health Potion, Magic Scroll, Iron Sword, Shield
326
+
327
+ Example: `buy Health Potion`"""
328
+
329
+
330
+ # Global instance for automatic registration
331
+ simple_trader_addon = SimpleTraderAddon()
332
+ ```
333
+
334
+ ### Example 2: Read2Burn Messaging (From Codebase)
335
+
336
+ This example shows a complex addon with state management and service interfaces:
337
+
338
+ ```python
339
+ """
340
+ Read2Burn Mailbox Add-on - Self-destructing secure messaging system.
341
+ """
342
+
343
+ import time
344
+ import random
345
+ import string
346
+ from typing import Dict, List
347
+ from dataclasses import dataclass
348
+ from abc import ABC, abstractmethod
349
+ import gradio as gr
350
+
351
+ from ..interfaces.npc_addon import NPCAddon
352
+
353
+
354
+ @dataclass
355
+ class Read2BurnMessage:
356
+ """Data class for Read2Burn messages."""
357
+ id: str
358
+ creator_id: str
359
+ content: str
360
+ created_at: float
361
+ expires_at: float
362
+ reads_left: int
363
+ burned: bool = False
364
+
365
+
366
+ class IRead2BurnService(ABC):
367
+ """Interface for Read2Burn service operations."""
368
+
369
+ @abstractmethod
370
+ def create_message(self, creator_id: str, content: str) -> str: pass
371
+
372
+ @abstractmethod
373
+ def read_message(self, reader_id: str, message_id: str) -> str: pass
374
+
375
+ @abstractmethod
376
+ def list_player_messages(self, player_id: str) -> str: pass
377
+
378
+
379
+ class Read2BurnService(IRead2BurnService, NPCAddon):
380
+ """Service for managing Read2Burn secure messaging."""
381
+
382
+ def __init__(self):
383
+ super().__init__()
384
+ self.messages: Dict[str, Read2BurnMessage] = {}
385
+ self.access_log: List[Dict] = []
386
+
387
+ @property
388
+ def addon_id(self) -> str:
389
+ return "read2burn_mailbox"
390
+
391
+ @property
392
+ def addon_name(self) -> str:
393
+ return "📧 Read2Burn Secure Mailbox"
394
+
395
+ @property
396
+ def npc_config(self) -> Dict:
397
+ return {
398
+ 'id': 'read2burn',
399
+ 'name': '📧 Read2Burn Service',
400
+ 'x': 200, 'y': 100,
401
+ 'char': '📧',
402
+ 'type': 'service',
403
+ 'personality': 'read2burn',
404
+ 'description': 'Secure message service - send private messages that auto-delete after reading'
405
+ }
406
+
407
+ def get_interface(self) -> gr.Component:
408
+ """Return Gradio interface for this add-on"""
409
+ with gr.Column() as interface:
410
+ gr.Markdown("""
411
+ ## 📧 Read2Burn Secure Mailbox
412
+
413
+ **Create self-destructing messages that burn after reading!**
414
+
415
+ ### Features:
416
+ - 🔥 Messages self-destruct after reading
417
+ - ⏰ 24-hour automatic expiration
418
+ - 🔒 Secure delivery system
419
+ - 📊 Message tracking and history
420
+ """)
421
+
422
+ with gr.Tabs():
423
+ with gr.Tab("📝 Create Message"):
424
+ message_input = gr.Textbox(
425
+ label="Message Content",
426
+ lines=5,
427
+ placeholder="Type your secret message here..."
428
+ )
429
+ create_btn = gr.Button("🔥 Create Burning Message", variant="primary")
430
+ create_output = gr.Textbox(label="Result", lines=3, interactive=False)
431
+
432
+ def create_message_ui(content):
433
+ # In real implementation, get current player ID
434
+ player_id = "current_player" # Simplified
435
+ return self.create_message(player_id, content)
436
+
437
+ create_btn.click(create_message_ui, inputs=[message_input], outputs=[create_output])
438
+
439
+ with gr.Tab("🔓 Read Message"):
440
+ message_id_input = gr.Textbox(label="Message ID", placeholder="Enter 8-character message ID")
441
+ read_btn = gr.Button("📖 Read & Burn Message", variant="secondary")
442
+ read_output = gr.Textbox(label="Message Content", lines=5, interactive=False)
443
+
444
+ def read_message_ui(msg_id):
445
+ player_id = "current_player" # Simplified
446
+ return self.read_message(player_id, msg_id)
447
+
448
+ read_btn.click(read_message_ui, inputs=[message_id_input], outputs=[read_output])
449
+
450
+ with gr.Tab("📋 My Messages"):
451
+ refresh_btn = gr.Button("🔄 Refresh List")
452
+ messages_output = gr.Textbox(label="Your Messages", lines=10, interactive=False)
453
+
454
+ def list_messages_ui():
455
+ player_id = "current_player" # Simplified
456
+ return self.list_player_messages(player_id)
457
+
458
+ refresh_btn.click(list_messages_ui, outputs=[messages_output])
459
+
460
+ gr.Markdown("**💬 Private Message Commands:**")
461
+ gr.Markdown("• `create Your message here` - Create new message")
462
+ gr.Markdown("• `read MESSAGE_ID` - Read message (destroys it!)")
463
+ gr.Markdown("• `list` - Show your created messages")
464
+ gr.Markdown("• `help` - Show command help")
465
+
466
+ return interface
467
+
468
+ def generate_message_id(self) -> str:
469
+ """Generate a unique message ID."""
470
+ return ''.join(random.choices(string.ascii_uppercase + string.digits, k=8))
471
+
472
+ def create_message(self, creator_id: str, content: str) -> str:
473
+ """Create a new self-destructing message."""
474
+ message_id = self.generate_message_id()
475
+
476
+ message = Read2BurnMessage(
477
+ id=message_id,
478
+ creator_id=creator_id,
479
+ content=content, # In production, encrypt this
480
+ created_at=time.time(),
481
+ expires_at=time.time() + (24 * 3600), # 24 hours
482
+ reads_left=1,
483
+ burned=False
484
+ )
485
+
486
+ self.messages[message_id] = message
487
+
488
+ self.access_log.append({
489
+ 'action': 'create',
490
+ 'message_id': message_id,
491
+ 'player_id': creator_id,
492
+ 'timestamp': time.time()
493
+ })
494
+
495
+ return f"✅ **Message Created Successfully!**\n\n📝 **Message ID:** `{message_id}`\n🔗 Share this ID with the recipient\n⏰ Expires in 24 hours\n🔥 Burns after 1 read"
496
+
497
+ def read_message(self, reader_id: str, message_id: str) -> str:
498
+ """Read and burn a message."""
499
+ if message_id not in self.messages:
500
+ return "❌ Message not found or already burned"
501
+
502
+ message = self.messages[message_id]
503
+
504
+ # Check expiry
505
+ if time.time() > message.expires_at:
506
+ del self.messages[message_id]
507
+ return "❌ Message expired and has been burned"
508
+
509
+ # Check if already burned
510
+ if message.burned or message.reads_left <= 0:
511
+ del self.messages[message_id]
512
+ return "❌ Message has already been burned"
513
+
514
+ # Read the message
515
+ content = message.content
516
+ message.reads_left -= 1
517
+
518
+ self.access_log.append({
519
+ 'action': 'read',
520
+ 'message_id': message_id,
521
+ 'player_id': reader_id,
522
+ 'timestamp': time.time()
523
+ })
524
+
525
+ # Burn the message after reading
526
+ if message.reads_left <= 0:
527
+ message.burned = True
528
+ del self.messages[message_id]
529
+ return f"🔥 **Message Self-Destructed After Reading**\n\n📖 **Content:** {content}\n\n💨 This message has been permanently destroyed."
530
+
531
+ return f"📖 **Message Content:** {content}\n\n⚠️ Reads remaining: {message.reads_left}"
532
+
533
+ def list_player_messages(self, player_id: str) -> str:
534
+ """List messages created by a player."""
535
+ player_messages = [msg for msg in self.messages.values() if msg.creator_id == player_id]
536
+
537
+ if not player_messages:
538
+ return "📪 No messages found. Create one with: `create Your message here`"
539
+
540
+ result = "📋 **Your Created Messages:**\n\n"
541
+ for msg in player_messages:
542
+ status = "🔥 Burned" if msg.burned else f"✅ Active ({msg.reads_left} reads left)"
543
+ created_time = time.strftime("%Y-%m-%d %H:%M", time.localtime(msg.created_at))
544
+ expires_time = time.strftime("%Y-%m-%d %H:%M", time.localtime(msg.expires_at))
545
+
546
+ result += f"**ID:** `{msg.id}`\n"
547
+ result += f"**Status:** {status}\n"
548
+ result += f"**Created:** {created_time}\n"
549
+ result += f"**Expires:** {expires_time}\n"
550
+ result += f"**Preview:** {msg.content[:50]}{'...' if len(msg.content) > 50 else ''}\n\n"
551
+
552
+ return result
553
+
554
+ def handle_command(self, player_id: str, command: str) -> str:
555
+ """Handle Read2Burn mailbox commands."""
556
+ parts = command.strip().split(' ', 1)
557
+ cmd = parts[0].lower()
558
+
559
+ if cmd == "create" and len(parts) > 1:
560
+ return self.create_message(player_id, parts[1])
561
+ elif cmd == "read" and len(parts) > 1:
562
+ return self.read_message(player_id, parts[1])
563
+ elif cmd == "list":
564
+ return self.list_player_messages(player_id)
565
+ elif cmd == "help":
566
+ return """📚 **Read2Burn Mailbox Commands:**
567
+
568
+ **create** `Your secret message here` - Create new message
569
+ **read** `MESSAGE_ID` - Read message (destroys it!)
570
+ **list** - Show your created messages
571
+ **help** - Show this help
572
+
573
+ 🔥 **Features:**
574
+ • Messages self-destruct after reading
575
+ • 24-hour automatic expiration
576
+ • Secure delivery system
577
+ • Anonymous messaging support"""
578
+ else:
579
+ return "❓ Invalid command. Try: `create <message>`, `read <id>`, `list`, or `help`"
580
+
581
+
582
+ # Global Read2Burn service instance
583
+ read2burn_service = Read2BurnService()
584
+ ```
585
+
586
+ ---
587
+
588
+ ## 🌐 MCP Integration
589
+
590
+ ### MCP-Powered Weather Oracle (From Codebase)
591
+
592
+ This example shows how to integrate external MCP servers:
593
+
594
+ ```python
595
+ """
596
+ Weather Oracle Add-on - MCP-powered weather information system.
597
+ """
598
+
599
+ import time
600
+ import asyncio
601
+ from typing import Dict, Optional
602
+ import gradio as gr
603
+ from mcp import ClientSession
604
+ from mcp.client.sse import sse_client
605
+ from contextlib import AsyncExitStack
606
+
607
+ from ..interfaces.npc_addon import NPCAddon
608
+
609
+
610
+ class WeatherOracleService(NPCAddon):
611
+ """Service for managing Weather Oracle MCP integration."""
612
+
613
+ def __init__(self):
614
+ super().__init__()
615
+ self.connected = False
616
+ self.last_connection_attempt = 0
617
+ self.connection_cooldown = 30 # 30 seconds between connection attempts
618
+ self.server_url = "https://chris4k-weather.hf.space/gradio_api/mcp/sse"
619
+ self.session = None
620
+ self.tools = []
621
+ self.exit_stack = None
622
+
623
+ # Set up event loop for async operations
624
+ try:
625
+ self.loop = asyncio.get_event_loop()
626
+ except RuntimeError:
627
+ self.loop = asyncio.new_event_loop()
628
+ asyncio.set_event_loop(self.loop)
629
+
630
+ @property
631
+ def addon_id(self) -> str:
632
+ return "weather_oracle"
633
+
634
+ @property
635
+ def addon_name(self) -> str:
636
+ return "🌤️ Weather Oracle (MCP)"
637
+
638
+ @property
639
+ def npc_config(self) -> Dict:
640
+ return {
641
+ 'id': 'weather_oracle',
642
+ 'name': '🌤️ Weather Oracle (MCP)',
643
+ 'x': 150, 'y': 300,
644
+ 'char': '🌤️',
645
+ 'type': 'mcp',
646
+ 'description': 'MCP-powered weather information service'
647
+ }
648
+
649
+ @property
650
+ def ui_tab_name(self) -> str:
651
+ return "🌤️ Weather Oracle"
652
+
653
+ def get_interface(self) -> gr.Component:
654
+ """Return Gradio interface for this add-on"""
655
+ with gr.Column() as interface:
656
+ gr.Markdown("""
657
+ ## 🌤️ Weather Oracle
658
+
659
+ *I commune with the spirits of sky and storm to bring you weather wisdom from across the realms!*
660
+
661
+ **Ask me about weather in any city:**
662
+ - Current conditions and temperature
663
+ - Weather forecasts
664
+ - Climate information
665
+
666
+ *Format: "City, Country" (e.g., "Berlin, Germany")*
667
+ """)
668
+
669
+ # Connection status
670
+ connection_status = gr.HTML(
671
+ value=f"<div style='color: {'green' if self.connected else 'red'};'>{'🟢 Connected to weather spirits' if self.connected else '🔴 Disconnected from weather realm'}</div>"
672
+ )
673
+
674
+ with gr.Row():
675
+ location_input = gr.Textbox(
676
+ label="🌍 Location",
677
+ placeholder="Enter city, country (e.g., Berlin, Germany)",
678
+ scale=3
679
+ )
680
+ get_weather_btn = gr.Button("🌡️ Consult Weather Spirits", variant="primary", scale=1)
681
+
682
+ weather_output = gr.Textbox(
683
+ label="🌤️ Weather Wisdom",
684
+ lines=8,
685
+ interactive=False,
686
+ placeholder="Enter a location and I will consult the weather spirits..."
687
+ )
688
+
689
+ # Example locations
690
+ gr.Examples(
691
+ examples=[
692
+ ["London, UK"],
693
+ ["Tokyo, Japan"],
694
+ ["New York, USA"],
695
+ ["Berlin, Germany"],
696
+ ["Sydney, Australia"]
697
+ ],
698
+ inputs=[location_input],
699
+ label="🌍 Try These Locations"
700
+ )
701
+
702
+ # Connection controls
703
+ with gr.Row():
704
+ connect_btn = gr.Button("🔗 Connect to MCP", variant="secondary")
705
+ status_btn = gr.Button("📊 Check Status", variant="secondary")
706
+
707
+ def handle_weather_request(location: str):
708
+ if not location.strip():
709
+ return "🌪️ The spirits need to know which realm you seek knowledge about!"
710
+ return self.get_weather(location)
711
+
712
+ def handle_connect():
713
+ result = self.connect_to_mcp()
714
+ status = f"<div style='color: {'green' if self.connected else 'red'};'>{'🟢 Connected to weather spirits' if self.connected else '🔴 Disconnected from weather realm'}</div>"
715
+ return result, status
716
+
717
+ def handle_status():
718
+ if self.connected:
719
+ tool_names = [tool.name for tool in self.tools] if self.tools else []
720
+ return f"✅ **Connected to MCP Weather Server**\n\nServer: {self.server_url}\nTools: {', '.join(tool_names) if tool_names else 'None'}"
721
+ else:
722
+ return "❌ **Not Connected**\n\nClick 'Connect to MCP' to establish connection."
723
+
724
+ # Wire up events
725
+ get_weather_btn.click(
726
+ handle_weather_request,
727
+ inputs=[location_input],
728
+ outputs=[weather_output]
729
+ )
730
+
731
+ location_input.submit(
732
+ handle_weather_request,
733
+ inputs=[location_input],
734
+ outputs=[weather_output]
735
+ )
736
+
737
+ connect_btn.click(
738
+ handle_connect,
739
+ outputs=[weather_output, connection_status]
740
+ )
741
+
742
+ status_btn.click(
743
+ handle_status,
744
+ outputs=[weather_output]
745
+ )
746
+
747
+ return interface
748
+
749
+ def connect_to_mcp(self) -> str:
750
+ """Connect to MCP weather server."""
751
+ current_time = time.time()
752
+ if current_time - self.last_connection_attempt < self.connection_cooldown:
753
+ return "⏳ Please wait before retrying connection..."
754
+
755
+ self.last_connection_attempt = current_time
756
+
757
+ try:
758
+ return self.loop.run_until_complete(self._connect())
759
+ except Exception as e:
760
+ self.connected = False
761
+ return f"❌ Connection failed: {str(e)}"
762
+
763
+ async def _connect(self) -> str:
764
+ """Async connect to MCP server using SSE."""
765
+ try:
766
+ # Clean up previous connection
767
+ if self.exit_stack:
768
+ await self.exit_stack.aclose()
769
+
770
+ self.exit_stack = AsyncExitStack()
771
+
772
+ # Connect to SSE MCP server
773
+ sse_transport = await self.exit_stack.enter_async_context(
774
+ sse_client(self.server_url)
775
+ )
776
+ read_stream, write_callable = sse_transport
777
+
778
+ self.session = await self.exit_stack.enter_async_context(
779
+ ClientSession(read_stream, write_callable)
780
+ )
781
+ await self.session.initialize()
782
+
783
+ # Get available tools
784
+ response = await self.session.list_tools()
785
+ self.tools = response.tools
786
+
787
+ self.connected = True
788
+ tool_names = [tool.name for tool in self.tools]
789
+ return f"✅ Connected to weather MCP server!\nAvailable tools: {', '.join(tool_names)}"
790
+
791
+ except Exception as e:
792
+ self.connected = False
793
+ return f"❌ Connection failed: {str(e)}"
794
+
795
+ def get_weather(self, location: str) -> str:
796
+ """Get weather for a location using actual MCP server"""
797
+ if not self.connected:
798
+ # Try to auto-reconnect
799
+ connect_result = self.connect_to_mcp()
800
+ if not self.connected:
801
+ return f"❌ **Not connected to weather spirits**\n\n{connect_result}"
802
+
803
+ try:
804
+ return self.loop.run_until_complete(self._get_weather(location))
805
+ except Exception as e:
806
+ return f"❌ **Weather divination failed**\n\nError: {str(e)}"
807
+
808
+ async def _get_weather(self, location: str) -> str:
809
+ """Async get weather using MCP."""
810
+ try:
811
+ # Parse location
812
+ if ',' in location:
813
+ city, country = [part.strip() for part in location.split(',', 1)]
814
+ else:
815
+ city = location.strip()
816
+ country = ""
817
+
818
+ # Find the weather tool
819
+ weather_tool = next((tool for tool in self.tools if 'weather' in tool.name.lower()), None)
820
+ if not weather_tool:
821
+ return "❌ Weather tool not found on server"
822
+
823
+ # Call the tool
824
+ params = {"city": city, "country": country}
825
+ result = await self.session.call_tool(weather_tool.name, params)
826
+
827
+ # Extract content properly
828
+ content_text = ""
829
+ if hasattr(result, 'content') and result.content:
830
+ if isinstance(result.content, list):
831
+ for content_item in result.content:
832
+ if hasattr(content_item, 'text'):
833
+ content_text += content_item.text + "\n"
834
+ else:
835
+ content_text += str(content_item) + "\n"
836
+ else:
837
+ content_text = str(result.content)
838
+
839
+ if not content_text.strip():
840
+ return f"❌ No weather data received for {location}"
841
+
842
+ # Format the response
843
+ return f"🌤️ **Weather Oracle reveals the weather for {location}:**\n\n{content_text.strip()}"
844
+
845
+ except Exception as e:
846
+ return f"❌ Weather divination failed: {str(e)}"
847
+
848
+ def handle_command(self, player_id: str, command: str) -> str:
849
+ """Handle Weather Oracle commands."""
850
+ cmd = command.strip()
851
+
852
+ if not cmd:
853
+ return ("🌤️ **Weather Oracle whispers:**\n\n"
854
+ "Ask me about the weather in any city!\n"
855
+ "Format: 'City, Country' (e.g., 'Berlin, Germany')")
856
+
857
+ # Treat any non-empty command as a location request
858
+ result = self.get_weather(cmd)
859
+ return f"🌤️ **Weather Oracle reveals:**\n\n{result}"
860
+
861
+
862
+ # Custom auto-registration function
863
+ def auto_register(game_engine):
864
+ """Auto-register the Weather Oracle addon with the game engine."""
865
+ try:
866
+ # Create the weather oracle NPC definition
867
+ weather_oracle_npc = {
868
+ 'id': 'weather_oracle_auto',
869
+ 'name': '🌤️ Weather Oracle (Auto)',
870
+ 'x': 300, 'y': 150,
871
+ 'char': '🌤️',
872
+ 'type': 'mcp',
873
+ 'personality': 'weather_oracle',
874
+ 'description': 'Self-contained MCP-powered weather information service'
875
+ }
876
+
877
+ # Register the NPC with the NPC service
878
+ npc_service = game_engine.get_npc_service()
879
+ npc_service.register_npc('weather_oracle_auto', weather_oracle_npc)
880
+
881
+ # Register the addon for handling private message commands
882
+ world = game_engine.get_world()
883
+ if not hasattr(world, 'addon_npcs'):
884
+ world.addon_npcs = {}
885
+ world.addon_npcs['weather_oracle_auto'] = weather_oracle_service
886
+
887
+ print("[WeatherOracleAddon] Auto-registered successfully as self-contained addon")
888
+ return True
889
+
890
+ except Exception as e:
891
+ print(f"[WeatherOracleAddon] Error during auto-registration: {e}")
892
+ return False
893
+
894
+
895
+ # Global Weather Oracle service instance
896
+ weather_oracle_service = WeatherOracleService()
897
+ ```
898
+
899
+ ---
900
+
901
+ ## 🔧 Advanced Patterns
902
+
903
+ ### State Persistence
904
+
905
+ ```python
906
+ import json
907
+ import os
908
+
909
+ class PersistentAddon(NPCAddon):
910
+ """Example addon with state persistence."""
911
+
912
+ def __init__(self):
913
+ super().__init__()
914
+ self.data_file = f"data/{self.addon_id}_data.json"
915
+ self.load_state()
916
+
917
+ def load_state(self):
918
+ """Load addon state from file."""
919
+ try:
920
+ if os.path.exists(self.data_file):
921
+ with open(self.data_file, 'r') as f:
922
+ data = json.load(f)
923
+ self.player_data = data.get('players', {})
924
+ self.settings = data.get('settings', {})
925
+ else:
926
+ self.player_data = {}
927
+ self.settings = {}
928
+ except Exception as e:
929
+ print(f"[{self.addon_id}] Failed to load state: {e}")
930
+ self.player_data = {}
931
+ self.settings = {}
932
+
933
+ def save_state(self):
934
+ """Save addon state to file."""
935
+ try:
936
+ os.makedirs(os.path.dirname(self.data_file), exist_ok=True)
937
+ with open(self.data_file, 'w') as f:
938
+ json.dump({
939
+ 'players': self.player_data,
940
+ 'settings': self.settings
941
+ }, f, indent=2)
942
+ except Exception as e:
943
+ print(f"[{self.addon_id}] Failed to save state: {e}")
944
+
945
+ def on_shutdown(self):
946
+ """Save state when addon shuts down."""
947
+ self.save_state()
948
+ ```
949
+
950
+ ### Player Validation Helper
951
+
952
+ ```python
953
+ def get_current_player(self, game_world):
954
+ """Helper to safely get current active player."""
955
+ try:
956
+ current_players = list(game_world.players.keys())
957
+ if not current_players:
958
+ return None, "❌ You must be in the game to use this service!"
959
+
960
+ player_id = max(current_players, key=lambda pid: game_world.players[pid].last_active)
961
+ player = game_world.players.get(player_id)
962
+
963
+ if not player:
964
+ return None, "❌ Player not found!"
965
+
966
+ return player, None
967
+ except Exception as e:
968
+ return None, f"❌ Error accessing player data: {e}"
969
+ ```
970
+
971
+ ### Async Operation Wrapper
972
+
973
+ ```python
974
+ def run_async_safely(self, async_func, *args, **kwargs):
975
+ """Safely run async functions in sync context."""
976
+ try:
977
+ return self.loop.run_until_complete(async_func(*args, **kwargs))
978
+ except Exception as e:
979
+ return f"❌ Async operation failed: {e}"
980
+ ```
981
+
982
+ ### Configuration Management
983
+
984
+ ```python
985
+ import yaml
986
+
987
+ class ConfigurableAddon(NPCAddon):
988
+ """Addon with configuration file support."""
989
+
990
+ def __init__(self):
991
+ super().__init__()
992
+ self.config = self.load_config()
993
+
994
+ def load_config(self):
995
+ """Load configuration from YAML file."""
996
+ config_file = f"config/{self.addon_id}_config.yaml"
997
+ try:
998
+ if os.path.exists(config_file):
999
+ with open(config_file, 'r') as f:
1000
+ return yaml.safe_load(f)
1001
+ else:
1002
+ return self.get_default_config()
1003
+ except Exception as e:
1004
+ print(f"[{self.addon_id}] Config load failed: {e}")
1005
+ return self.get_default_config()
1006
+
1007
+ def get_default_config(self):
1008
+ """Return default configuration."""
1009
+ return {
1010
+ 'enabled': True,
1011
+ 'max_operations': 100,
1012
+ 'cooldown_seconds': 30
1013
+ }
1014
+ ```
1015
+
1016
+ ---
1017
+
1018
+ ## 🎯 Best Practices
1019
+
1020
+ ### 1. Error Handling
1021
+
1022
+ ```python
1023
+ def handle_command(self, player_id: str, command: str) -> str:
1024
+ """Always wrap command handling in try-catch."""
1025
+ try:
1026
+ # Your command logic here
1027
+ return self.process_command(player_id, command)
1028
+ except Exception as e:
1029
+ print(f"[{self.addon_id}] Command error: {e}")
1030
+ return f"❌ An error occurred. Please try again or contact support."
1031
+ ```
1032
+
1033
+ ### 2. Input Validation
1034
+
1035
+ ```python
1036
+ def validate_input(self, input_value: str, max_length: int = 500) -> tuple[bool, str]:
1037
+ """Validate user input."""
1038
+ if not input_value or not input_value.strip():
1039
+ return False, "Input cannot be empty"
1040
+
1041
+ if len(input_value) > max_length:
1042
+ return False, f"Input too long (max {max_length} characters)"
1043
+
1044
+ # Add more validation as needed
1045
+ return True, ""
1046
+ ```
1047
+
1048
+ ### 3. Rate Limiting
1049
+
1050
+ ```python
1051
+ import time
1052
+ from collections import defaultdict
1053
+
1054
+ class RateLimitedAddon(NPCAddon):
1055
+ """Addon with rate limiting."""
1056
+
1057
+ def __init__(self):
1058
+ super().__init__()
1059
+ self.last_command_time = defaultdict(float)
1060
+ self.command_cooldown = 5 # 5 seconds between commands
1061
+
1062
+ def is_rate_limited(self, player_id: str) -> bool:
1063
+ """Check if player is rate limited."""
1064
+ current_time = time.time()
1065
+ last_time = self.last_command_time[player_id]
1066
+
1067
+ if current_time - last_time < self.command_cooldown:
1068
+ return True
1069
+
1070
+ self.last_command_time[player_id] = current_time
1071
+ return False
1072
+ ```
1073
+
1074
+ ### 4. Logging
1075
+
1076
+ ```python
1077
+ import logging
1078
+
1079
+ class LoggedAddon(NPCAddon):
1080
+ """Addon with proper logging."""
1081
+
1082
+ def __init__(self):
1083
+ super().__init__()
1084
+ self.logger = logging.getLogger(f"addon_{self.addon_id}")
1085
+ self.logger.setLevel(logging.INFO)
1086
+
1087
+ def handle_command(self, player_id: str, command: str) -> str:
1088
+ self.logger.info(f"Command from {player_id}: {command}")
1089
+ try:
1090
+ result = self.process_command(player_id, command)
1091
+ self.logger.info(f"Command successful for {player_id}")
1092
+ return result
1093
+ except Exception as e:
1094
+ self.logger.error(f"Command failed for {player_id}: {e}")
1095
+ return "❌ Command failed. Please try again."
1096
+ ```
1097
+
1098
+ ### 5. Resource Cleanup
1099
+
1100
+ ```python
1101
+ class ResourceManagedAddon(NPCAddon):
1102
+ """Addon with proper resource management."""
1103
+
1104
+ def __init__(self):
1105
+ super().__init__()
1106
+ self.resources = [] # Track resources
1107
+
1108
+ def on_shutdown(self):
1109
+ """Clean up resources on shutdown."""
1110
+ for resource in self.resources:
1111
+ try:
1112
+ if hasattr(resource, 'close'):
1113
+ resource.close()
1114
+ elif hasattr(resource, '__exit__'):
1115
+ resource.__exit__(None, None, None)
1116
+ except Exception as e:
1117
+ print(f"[{self.addon_id}] Cleanup error: {e}")
1118
+ ```
1119
+
1120
+ ---
1121
+
1122
+ ## 🧪 Deployment & Testing
1123
+
1124
+ ### Testing Your Addon
1125
+
1126
+ Create a test file for your addon:
1127
+
1128
+ ```python
1129
+ # tests/test_my_addon.py
1130
+ import pytest
1131
+ from src.addons.my_addon import MyAddon
1132
+
1133
+ class TestMyAddon:
1134
+ def setup_method(self):
1135
+ """Set up test fixtures."""
1136
+ self.addon = MyAddon()
1137
+
1138
+ def test_addon_id(self):
1139
+ """Test addon ID is correct."""
1140
+ assert self.addon.addon_id == "my_addon"
1141
+
1142
+ def test_handle_command_hello(self):
1143
+ """Test hello command."""
1144
+ result = self.addon.handle_command("test_player", "hello")
1145
+ assert "hello" in result.lower()
1146
+
1147
+ def test_handle_command_invalid(self):
1148
+ """Test invalid command handling."""
1149
+ result = self.addon.handle_command("test_player", "invalid_command")
1150
+ assert "didn't understand" in result.lower() or "help" in result.lower()
1151
+ ```
1152
+
1153
+ ### Running Tests
1154
+
1155
+ ```bash
1156
+ # Run all addon tests
1157
+ python -m pytest tests/test_*_addon.py -v
1158
+
1159
+ # Run specific addon test
1160
+ python -m pytest tests/test_my_addon.py -v
1161
+
1162
+ # Run with coverage
1163
+ python -m pytest tests/test_*_addon.py --cov=src/addons
1164
+ ```
1165
+
1166
+ ### Debug Mode
1167
+
1168
+ Add debug functionality to your addon:
1169
+
1170
+ ```python
1171
+ class DebuggableAddon(NPCAddon):
1172
+ """Addon with debug capabilities."""
1173
+
1174
+ def __init__(self):
1175
+ super().__init__()
1176
+ self.debug_mode = os.getenv('ADDON_DEBUG', 'false').lower() == 'true'
1177
+
1178
+ def debug_log(self, message: str):
1179
+ """Log debug messages."""
1180
+ if self.debug_mode:
1181
+ print(f"[DEBUG:{self.addon_id}] {message}")
1182
+
1183
+ def handle_command(self, player_id: str, command: str) -> str:
1184
+ self.debug_log(f"Received command: {command}")
1185
+ # ... rest of method
1186
+ ```
1187
+
1188
+ ### Performance Monitoring
1189
+
1190
+ ```python
1191
+ import time
1192
+ from functools import wraps
1193
+
1194
+ def monitor_performance(func):
1195
+ """Decorator to monitor function performance."""
1196
+ @wraps(func)
1197
+ def wrapper(self, *args, **kwargs):
1198
+ start_time = time.time()
1199
+ try:
1200
+ result = func(self, *args, **kwargs)
1201
+ duration = time.time() - start_time
1202
+ if duration > 1.0: # Log slow operations
1203
+ print(f"[{self.addon_id}] Slow operation: {func.__name__} took {duration:.2f}s")
1204
+ return result
1205
+ except Exception as e:
1206
+ duration = time.time() - start_time
1207
+ print(f"[{self.addon_id}] Error in {func.__name__} after {duration:.2f}s: {e}")
1208
+ raise
1209
+ return wrapper
1210
+
1211
+ class MonitoredAddon(NPCAddon):
1212
+ """Addon with performance monitoring."""
1213
+
1214
+ @monitor_performance
1215
+ def handle_command(self, player_id: str, command: str) -> str:
1216
+ # Your command handling logic
1217
+ pass
1218
+ ```
1219
+
1220
+ ### Integration Testing
1221
+
1222
+ Test your addon with the game engine:
1223
+
1224
+ ```python
1225
+ # tests/test_integration.py
1226
+ import asyncio
1227
+ from src.core.game_engine import GameEngine
1228
+ from src.addons.my_addon import MyAddon
1229
+
1230
+ def test_addon_integration():
1231
+ """Test addon integrates properly with game engine."""
1232
+ engine = GameEngine()
1233
+ engine.start()
1234
+
1235
+ # Check addon is registered
1236
+ world = engine.get_world()
1237
+ assert hasattr(world, 'addon_npcs')
1238
+ assert 'my_addon' in world.addon_npcs
1239
+
1240
+ # Test NPC exists
1241
+ npc_service = engine.get_npc_service()
1242
+ npc = npc_service.get_npc('my_addon_npc')
1243
+ assert npc is not None
1244
+
1245
+ engine.stop()
1246
+ ```
1247
+
1248
+ ---
1249
+
1250
+ ## 🔧 Troubleshooting
1251
+
1252
+ ### Common Issues
1253
+
1254
+ **1. Addon Not Auto-Registering**
1255
+ - Check that you have a global instance at the bottom of your file
1256
+ - Verify `addon_id` is unique and doesn't conflict with others
1257
+ - Ensure you're calling `super().__init__()` in your constructor
1258
+
1259
+ **2. UI Tab Not Appearing**
1260
+ - Check that `ui_tab_name` property returns a string, not None
1261
+ - Verify `get_interface()` returns a valid Gradio component
1262
+ - Look for exceptions in the console when the interface loads
1263
+
1264
+ **3. NPC Not Appearing in World**
1265
+ - Verify `npc_config` property returns a valid dictionary
1266
+ - Check that the NPC position coordinates are within world bounds
1267
+ - Ensure the NPC ID is unique
1268
+
1269
+ **4. Private Messages Not Working**
1270
+ - Confirm `handle_command()` is implemented
1271
+ - Check that the addon is registered in `world.addon_npcs`
1272
+ - Verify the NPC ID matches between world registration and addon registry
1273
+
1274
+ **5. MCP Connection Issues**
1275
+ - Check the MCP server URL is correct and accessible
1276
+ - Verify the server is running and supports the expected tools
1277
+ - Look for async/await issues in your MCP client code
1278
+ - Check firewall and network connectivity
1279
+
1280
+ ### Debug Checklist
1281
+
1282
+ - [ ] Addon file imported without errors
1283
+ - [ ] Global instance created at file bottom
1284
+ - [ ] All required methods implemented
1285
+ - [ ] Properties return correct types
1286
+ - [ ] No exceptions in console on startup
1287
+ - [ ] NPC appears in world at specified coordinates
1288
+ - [ ] UI tab appears in interface (if configured)
1289
+ - [ ] Private messages work correctly
1290
+ - [ ] Error handling covers edge cases
1291
+
1292
+ ### Getting Help
1293
+
1294
+ 1. **Check Logs**: Look for error messages in the console
1295
+ 2. **Test Individually**: Create a simple test script for your addon
1296
+ 3. **Validate Configuration**: Ensure all properties return expected values
1297
+ 4. **Compare Examples**: Look at working addons like SimpleTrader or Read2Burn
1298
+ 5. **Community Support**: Ask on project forums or GitHub issues
1299
+
1300
+ ---
1301
+
1302
+ ## 📚 Additional Resources
1303
+
1304
+ - **Interface Reference**: `src/interfaces/npc_addon.py` - Base class documentation
1305
+ - **Working Examples**: `src/addons/` - All current addon implementations
1306
+ - **Game Engine**: `src/core/game_engine.py` - Auto-registration system
1307
+ - **World Management**: `src/core/world.py` - NPC placement and management
1308
+ - **MCP Documentation**: [Model Context Protocol](https://github.com/modelcontextprotocol/python-sdk)
1309
+
1310
+ ---
1311
+
1312
+ *This guide reflects the current addon system architecture and includes real examples from the working codebase. All code examples are tested and functional.*
Documentation/ROADMAP.md ADDED
@@ -0,0 +1,375 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🚀 MMORPG with MCP Integration - Roadmap
2
+
3
+ ## 📋 Current Status (v1.0)
4
+
5
+ - ✅ **Core Game Engine**: Real-time multiplayer MMORPG with smooth movement
6
+ - ✅ **MCP Integration**: Full Model Context Protocol support with SSE
7
+ - ✅ **Multi-user Support**: Concurrent human and AI agent players
8
+ - ✅ **NPC Addon System**: Extensible NPC architecture with custom addons
9
+ - ✅ **Read2Burn Messaging**: Self-destructing secure message system
10
+ - ✅ **Weather Integration**: Real-time weather data via MCP servers
11
+ - ✅ **Keyboard Controls**: WASD and arrow key support for fluid gameplay
12
+ - ✅ **Web Interface**: Rich Gradio-based user interface
13
+ - ✅ **AI Agent APIs**: Complete MCP tool suite for AI agent integration
14
+ - ✅ **Session Management**: Robust player session handling and state persistence
15
+ - ✅ **Real-time Updates**: Live game world synchronization across all clients
16
+
17
+ ## ✅ Recently Completed Refactoring (December 2024)
18
+
19
+ ### ✅ Clean Architecture Implementation
20
+ - ✅ **Service Layer**: Separated concerns into dedicated service classes (PlayerService, ChatService, NPCService, MCPService, PluginService)
21
+ - ✅ **Facade Pattern**: Implemented GameFacade for simplified API access
22
+ - ✅ **Singleton Pattern**: Refactored GameEngine to use proper singleton implementation
23
+ - ✅ **Dependency Injection**: Improved service management and dependency handling
24
+
25
+ ### ✅ Plugin System Enhancement
26
+ - ✅ **Plugin Architecture**: Enhanced plugin loading and management system
27
+ - ✅ **Trading System Plugin**: Implemented comprehensive trading system with economic mechanics
28
+ - ✅ **Weather Plugin**: Enhanced weather integration with better API management
29
+ - ✅ **Enhanced Chat Plugin**: Advanced chat features with filtering and commands
30
+
31
+ ### ✅ UI/UX Improvements
32
+ - ✅ **Enhanced Interface Manager**: Improved UI component organization and management
33
+ - ✅ **Keyboard Controls**: Enhanced WASD and arrow key handling with better responsiveness
34
+ - ✅ **Bigger Game Map**: Increased map size for better gameplay experience
35
+ - ✅ **Player Tracking**: Improved current player state management
36
+
37
+ ### ✅ NPC System Enhancement
38
+ - ✅ **Donald NPC**: Implemented Donald the Trader with distinctive personality and trading capabilities
39
+ - ✅ **Personality System**: Enhanced NPC personality framework for diverse character interactions
40
+ - ✅ **NPC Service**: Refactored NPC management into dedicated service layer
41
+
42
+ ### ✅ Documentation & Testing
43
+ - ✅ **Developer Documentation**: Updated and corrected development setup documentation
44
+ - ✅ **Test Suite**: Created comprehensive test framework with unit, integration, and E2E tests
45
+ - ✅ **Test Coverage**: Implemented proper test coverage strategy and organization
46
+ - ✅ **Code Quality**: Improved code structure and maintainability
47
+
48
+ ---
49
+
50
+ ## 🎯 Short-term Goals (v1.1 - v1.3)
51
+
52
+ ### v1.1 - Enhanced Gameplay Mechanics
53
+ - [ ] **Quest System**: Dynamic quest generation and completion tracking
54
+ - [ ] **Inventory Management**: Player inventory with items and equipment
55
+ - [ ] **Combat System**: Turn-based or real-time combat mechanics
56
+ - [ ] **Character Progression**: Skill trees and specialization paths
57
+ - [ ] **Resource Gathering**: Mining, crafting, and resource management
58
+ - [ ] **Player Trading**: Secure item and currency exchange system
59
+ - [ ] **Guild System**: Player organizations and group activities
60
+
61
+ ### v1.2 - Advanced MCP Features
62
+ - [ ] **MCP Marketplace**: Built-in server discovery and rating system
63
+ - [ ] **Custom MCP Tools**: Visual tool builder for non-developers
64
+ - [ ] **Server Composition**: Chain multiple MCP servers for complex workflows
65
+ - [ ] **Authentication & Security**: OAuth2/JWT integration for MCP servers
66
+ - [ ] **Rate Limiting**: Smart request management and throttling
67
+ - [ ] **Server Health Monitoring**: Real-time MCP server status dashboard
68
+ - [ ] **Hot-swapping**: Live addon replacement without server restart
69
+
70
+ ### v1.3 - Social & Collaboration Features
71
+ - [ ] **Player-to-Player Messaging**: Secure private messaging between players
72
+ - [ ] **Voice Chat Integration**: WebRTC voice communication in groups
73
+ - [ ] **Shared Workspaces**: Collaborative areas for team projects
74
+ - [ ] **Event System**: Scheduled events and tournaments
75
+ - [ ] **Leaderboards**: Player rankings and achievement systems
76
+ - [ ] **Mentorship Program**: Experienced players helping newcomers
77
+ - [ ] **Content Sharing**: Share screenshots, videos, and gameplay moments
78
+
79
+ ---
80
+
81
+ ## 🚀 Medium-term Goals (v2.0 - v2.5)
82
+
83
+ ### v2.0 - AI Agent Ecosystem
84
+ - [ ] **Multi-Agent Orchestration**: Coordinated AI agent teams and workflows
85
+ - [ ] **Agent Marketplace**: Community-contributed specialized AI agents
86
+ - [ ] **Behavior Templates**: Pre-built AI agent behavior patterns
87
+ - [ ] **Learning Framework**: AI agents that learn from player interactions
88
+ - [ ] **Agent Training Grounds**: Sandbox environments for AI development
89
+ - [ ] **Performance Analytics**: Detailed AI agent effectiveness metrics
90
+ - [ ] **Agent Tournaments**: Competitive events between AI agents
91
+
92
+ ### v2.1 - Advanced World Systems
93
+ - [ ] **Procedural World Generation**: Dynamic map creation and expansion
94
+ - [ ] **Day/Night Cycles**: Time-based gameplay mechanics and events
95
+ - [ ] **Weather Effects**: Weather impact on gameplay and NPC behavior
96
+ - [ ] **Economic Simulation**: Supply and demand market simulation
97
+ - [ ] **Political Systems**: Player-driven governance and faction warfare
98
+ - [ ] **Environmental Puzzles**: World-based challenges requiring cooperation
99
+ - [ ] **Dynamic Events**: Emergent world events affecting all players
100
+
101
+ ### v2.2 - Enterprise & Development Platform
102
+ - [ ] **Multi-tenancy**: Support for multiple game instances
103
+ - [ ] **Admin Dashboard**: Comprehensive game administration interface
104
+ - [ ] **Analytics Suite**: Detailed player behavior and system performance metrics
105
+ - [ ] **A/B Testing Framework**: Compare different game mechanics and features
106
+ - [ ] **API Gateway**: RESTful APIs for external integrations
107
+ - [ ] **Webhook System**: Event-driven notifications and integrations
108
+ - [ ] **Documentation Portal**: Interactive API documentation and tutorials
109
+
110
+ ### v2.3 - Mobile & Cross-Platform
111
+ - [ ] **Progressive Web App**: Mobile-optimized web interface
112
+ - [ ] **Native Mobile Apps**: iOS and Android native applications
113
+ - [ ] **Desktop Applications**: Electron-based desktop clients for Windows/Mac/Linux
114
+ - [ ] **Cross-platform Sync**: Seamless experience across all devices
115
+ - [ ] **Offline Mode**: Limited offline gameplay with sync when reconnected
116
+ - [ ] **Cloud Save System**: Player progress backup and restoration
117
+ - [ ] **Push Notifications**: Real-time alerts for important game events
118
+
119
+ ### v2.4 - Advanced Graphics & Performance
120
+ - [ ] **3D Game World**: Three-dimensional game environment with WebGL
121
+ - [ ] **Custom Avatars**: Player avatar customization and equipment visualization
122
+ - [ ] **Animation System**: Smooth character animations and effects
123
+ - [ ] **Sound Design**: Immersive audio experience with spatial sound
124
+ - [ ] **Performance Optimization**: Support for 1000+ concurrent players
125
+ - [ ] **CDN Integration**: Global content delivery for optimal performance
126
+ - [ ] **Edge Computing**: Regional game servers for reduced latency
127
+
128
+ ### v2.5 - AI & Machine Learning Integration
129
+ - [ ] **Intelligent NPCs**: Advanced AI-powered NPCs with natural language understanding
130
+ - [ ] **Procedural Content**: AI-generated quests, stories, and world content
131
+ - [ ] **Player Behavior Prediction**: ML models for personalized experiences
132
+ - [ ] **Automated Moderation**: AI-powered chat moderation and behavior monitoring
133
+ - [ ] **Adaptive Difficulty**: Dynamic game difficulty based on player skill
134
+ - [ ] **Natural Language Interfaces**: Voice and text commands for game control
135
+ - [ ] **Sentiment Analysis**: Real-time mood and engagement tracking
136
+
137
+ ---
138
+
139
+ ## 🌟 Long-term Vision (v3.0+)
140
+
141
+ ### v3.0 - Metaverse Integration
142
+ - [ ] **Virtual Reality Support**: VR headset compatibility and immersive gameplay
143
+ - [ ] **Augmented Reality Features**: AR overlays for real-world gaming experiences
144
+ - [ ] **Blockchain Integration**: NFT items and decentralized ownership
145
+ - [ ] **Cross-game Interoperability**: Character and item portability between games
146
+ - [ ] **Virtual Real Estate**: Player-owned and customizable virtual spaces
147
+ - [ ] **Digital Economy**: Real-world value exchange for in-game assets
148
+ - [ ] **Social VR Spaces**: Virtual meeting rooms and social hangouts
149
+
150
+ ### v3.1 - Research & Academic Platform
151
+ - [ ] **Research Tools**: Built-in data collection and analysis for academic studies
152
+ - [ ] **Experiment Framework**: A/B testing and controlled experiments for researchers
153
+ - [ ] **Ethics Dashboard**: Privacy and consent management for research participants
154
+ - [ ] **Publication Integration**: Direct integration with academic publication workflows
155
+ - [ ] **Collaboration Networks**: Connect researchers studying similar topics
156
+ - [ ] **Open Data Initiative**: Anonymized dataset sharing for the research community
157
+ - [ ] **Grant Integration**: Funding application and management tools
158
+
159
+ ### v3.2 - Autonomous Game Evolution
160
+ - [ ] **Self-modifying Code**: Game systems that evolve based on player feedback
161
+ - [ ] **Emergent Gameplay**: Unplanned game mechanics arising from player behavior
162
+ - [ ] **AI Game Masters**: Autonomous systems managing dynamic storylines
163
+ - [ ] **Player-driven Development**: Community voting on new features and changes
164
+ - [ ] **Genetic Algorithms**: Evolution of game mechanics through player selection
165
+ - [ ] **Quantum Computing**: Quantum-enhanced AI for complex game simulations
166
+ - [ ] **Consciousness Simulation**: Advanced AI entities with apparent consciousness
167
+
168
+ ---
169
+
170
+ ## 🔧 Technical Infrastructure Roadmap
171
+
172
+ ### Performance & Scalability
173
+ - [ ] **Microservices Architecture**: Break down monolithic structure into services
174
+ - [ ] **Container Orchestration**: Kubernetes deployment and management
175
+ - [ ] **Database Sharding**: Horizontal scaling for large player populations
176
+ - [ ] **Caching Layer**: Redis-based multi-level caching system
177
+ - [ ] **Load Balancing**: Intelligent traffic distribution across servers
178
+ - [ ] **Auto-scaling**: Dynamic resource allocation based on demand
179
+ - [ ] **Global CDN**: Worldwide content delivery network integration
180
+
181
+ ### Security & Privacy
182
+ - [ ] **End-to-end Encryption**: Secure all player communications and data
183
+ - [ ] **Multi-factor Authentication**: Enhanced account security options
184
+ - [ ] **Privacy Controls**: Granular privacy settings and data control
185
+ - [ ] **GDPR Compliance**: Full compliance with international privacy regulations
186
+ - [ ] **Security Auditing**: Regular penetration testing and security assessments
187
+ - [ ] **Incident Response**: Automated security incident detection and response
188
+ - [ ] **Zero-trust Architecture**: Comprehensive security model implementation
189
+
190
+ ### Monitoring & Observability
191
+ - [ ] **Distributed Tracing**: End-to-end request flow tracking
192
+ - [ ] **Custom Metrics**: Business-specific KPI tracking and alerting
193
+ - [ ] **Real-time Dashboards**: Live system health and performance monitoring
194
+ - [ ] **Predictive Analytics**: AI-powered system failure prediction
195
+ - [ ] **Automated Remediation**: Self-healing systems for common issues
196
+ - [ ] **Compliance Monitoring**: Automated compliance checking and reporting
197
+ - [ ] **Cost Optimization**: Intelligent resource usage optimization
198
+
199
+ ---
200
+
201
+ ## 📊 Success Metrics & KPIs
202
+
203
+ ### User Engagement
204
+ - **Daily Active Users**: Target 10,000+ DAU by v2.0
205
+ - **Session Duration**: Average session length > 30 minutes
206
+ - **Player Retention**: 30-day retention rate > 60%
207
+ - **User Satisfaction**: Player rating > 4.5/5 stars
208
+ - **Community Growth**: 25%+ monthly community growth
209
+ - **Content Creation**: 1000+ user-generated addons and content
210
+
211
+ ### Technical Performance
212
+ - **System Uptime**: 99.9% availability target
213
+ - **Response Time**: < 100ms average response time
214
+ - **Concurrent Users**: Support 10,000+ simultaneous players
215
+ - **Scalability**: Linear performance scaling with resources
216
+ - **Error Rate**: < 0.1% error rate across all operations
217
+ - **Data Integrity**: 100% data consistency and backup reliability
218
+
219
+ ### Developer Ecosystem
220
+ - **MCP Server Count**: 500+ community-contributed MCP servers
221
+ - **Active Developers**: 1000+ registered developers in ecosystem
222
+ - **API Usage**: 1M+ API calls per month
223
+ - **Documentation Quality**: 95%+ developer satisfaction with docs
224
+ - **Integration Success**: 90%+ successful first-time integrations
225
+ - **Open Source Contributions**: 100+ external contributors
226
+
227
+ ### Business & Community
228
+ - **Revenue Growth**: Self-sustaining through enterprise licensing
229
+ - **Partner Ecosystem**: 50+ technology and content partners
230
+ - **Academic Adoption**: 25+ universities using platform for research
231
+ - **Media Coverage**: Regular coverage in gaming and tech media
232
+ - **Conference Presence**: Speaking opportunities at major conferences
233
+ - **Industry Recognition**: Awards and recognition from industry organizations
234
+
235
+ ---
236
+
237
+ ## 🤝 Community & Contribution Roadmap
238
+
239
+ ### Open Source Strategy
240
+ - [ ] **License Transition**: Gradual open-sourcing of core components
241
+ - [ ] **Contribution Guidelines**: Clear guidelines for community contributions
242
+ - [ ] **Code of Conduct**: Inclusive community standards and enforcement
243
+ - [ ] **Maintainer Program**: Training and support for community maintainers
244
+ - [ ] **Bounty System**: Paid incentives for high-priority contributions
245
+ - [ ] **Hackathons**: Regular community coding events and competitions
246
+ - [ ] **Grant Program**: Funding for significant community projects
247
+
248
+ ### Educational Initiatives
249
+ - [ ] **Documentation Expansion**: Comprehensive guides and tutorials
250
+ - [ ] **Video Tutorials**: Professional video content for all skill levels
251
+ - [ ] **Certification Program**: Official certification for developers and researchers
252
+ - [ ] **Workshop Series**: Regular online workshops and training sessions
253
+ - [ ] **University Partnerships**: Curriculum development and student programs
254
+ - [ ] **Mentorship Network**: Connect experienced developers with newcomers
255
+ - [ ] **Research Collaboration**: Joint research projects with academic institutions
256
+
257
+ ### Ecosystem Development
258
+ - [ ] **Developer Tools**: IDE plugins and development environment setup
259
+ - [ ] **Template Library**: Starter templates for common use cases
260
+ - [ ] **Component Marketplace**: Reusable components and assets
261
+ - [ ] **Testing Framework**: Automated testing tools for community developers
262
+ - [ ] **Deployment Tools**: One-click deployment for community servers
263
+ - [ ] **Monitoring Integration**: Built-in monitoring for community deployments
264
+ - [ ] **Support Services**: Professional support options for enterprise users
265
+
266
+ ---
267
+
268
+ ## 📅 Timeline & Milestones
269
+
270
+ ### 2025 Q3-Q4 (v1.1)
271
+ - **July**: Quest system and inventory management
272
+ - **August**: Combat system and character progression
273
+ - **September**: Resource gathering and player trading
274
+ - **October**: Guild system and enhanced social features
275
+ - **November**: Beta testing and community feedback
276
+ - **December**: v1.1 stable release
277
+
278
+ ### 2026 Q1-Q2 (v1.2-1.3)
279
+ - **Q1**: MCP marketplace and advanced server features
280
+ - **Q2**: Social collaboration features and event system
281
+
282
+ ### 2026 Q3-Q4 (v2.0)
283
+ - **Q3**: AI agent ecosystem and multi-agent orchestration
284
+ - **Q4**: Advanced world systems and procedural generation
285
+
286
+ ### 2027 (v2.1-2.5)
287
+ - **H1**: Enterprise platform and mobile applications
288
+ - **H2**: Advanced graphics and AI/ML integration
289
+
290
+ ### 2028+ (v3.0+)
291
+ - **Metaverse integration and VR/AR support**
292
+ - **Research platform and academic partnerships**
293
+ - **Autonomous game evolution systems**
294
+
295
+ ---
296
+
297
+ ## 💰 Funding & Sustainability
298
+
299
+ ### Revenue Streams
300
+ - [ ] **Enterprise Licensing**: B2B platform licensing for corporations
301
+ - [ ] **Premium Features**: Advanced features for individual users
302
+ - [ ] **Marketplace Commission**: Revenue share from addon and content sales
303
+ - [ ] **Consulting Services**: Implementation and customization services
304
+ - [ ] **Training & Certification**: Educational program revenue
305
+ - [ ] **Research Partnerships**: Funded research collaboration projects
306
+ - [ ] **Cloud Hosting**: Managed hosting services for deployments
307
+
308
+ ### Investment Strategy
309
+ - [ ] **Seed Funding**: Initial development and team expansion
310
+ - [ ] **Series A**: Platform scaling and feature development
311
+ - [ ] **Strategic Partnerships**: Technology and distribution partnerships
312
+ - [ ] **Government Grants**: Research and innovation funding
313
+ - [ ] **Crowdfunding**: Community-supported development initiatives
314
+ - [ ] **Revenue Reinvestment**: Sustainable growth through earned revenue
315
+ - [ ] **IPO Consideration**: Long-term public offering evaluation
316
+
317
+ ---
318
+
319
+ ## 🔮 Innovation & Research Focus
320
+
321
+ ### Emerging Technologies
322
+ - [ ] **Quantum Computing Integration**: Quantum-enhanced game simulations
323
+ - [ ] **Brain-Computer Interfaces**: Direct neural control of game characters
324
+ - [ ] **Advanced AI Models**: Integration of next-generation language models
325
+ - [ ] **Edge AI**: On-device AI processing for enhanced privacy
326
+ - [ ] **5G/6G Optimization**: Ultra-low latency gaming experiences
327
+ - [ ] **Holographic Displays**: Next-generation 3D visualization
328
+ - [ ] **Biometric Integration**: Health and wellness monitoring during gameplay
329
+
330
+ ### Research Collaborations
331
+ - [ ] **AI Ethics Research**: Responsible AI development in gaming
332
+ - [ ] **Human-Computer Interaction**: Innovative interface design research
333
+ - [ ] **Social Psychology**: Understanding multiplayer social dynamics
334
+ - [ ] **Game Theory**: Mathematical modeling of player behavior
335
+ - [ ] **Network Science**: Optimization of multiplayer network architectures
336
+ - [ ] **Cognitive Science**: Understanding learning and engagement in games
337
+ - [ ] **Digital Wellness**: Promoting healthy gaming habits and mental well-being
338
+
339
+ ---
340
+
341
+ ## 📞 Roadmap Feedback & Updates
342
+
343
+ ### Community Input
344
+ - **Quarterly Surveys**: Regular community feedback on roadmap priorities
345
+ - **Developer Workshops**: Input sessions with active developers
346
+ - **Player Focus Groups**: Representative player input on new features
347
+ - **Academic Advisory Board**: Research community guidance on platform evolution
348
+ - **Industry Roundtables**: Input from gaming and technology industry leaders
349
+
350
+ ### Roadmap Maintenance
351
+ - **Monthly Reviews**: Regular evaluation of progress and priorities
352
+ - **Quarterly Updates**: Public roadmap updates with progress reports
353
+ - **Annual Planning**: Comprehensive yearly planning and goal setting
354
+ - **Agile Adaptation**: Responsive changes based on technology and market evolution
355
+ - **Community Voting**: Democratic input on feature prioritization
356
+
357
+ ### Communication Channels
358
+ - **Official Blog**: Regular updates and deep-dive articles
359
+ - **Newsletter**: Monthly updates for subscribers
360
+ - **Social Media**: Real-time updates and community engagement
361
+ - **Conference Presentations**: Industry conference roadmap presentations
362
+ - **Documentation Portal**: Living documentation with latest roadmap information
363
+
364
+ ---
365
+
366
+ *This roadmap is a living document that evolves based on community feedback, technological advances, and changing user needs. Timelines are estimates and may be adjusted based on development priorities and resource availability.*
367
+
368
+ **Last Updated**: June 6, 2025
369
+ **Version**: 1.0
370
+ **Next Review**: July 6, 2025
371
+ **Community Input**: [feedback@mmop-game.com](mailto:feedback@mmop-game.com)
372
+
373
+ ---
374
+
375
+ **🚀 Join us in building the future of multiplayer gaming and AI interaction! 🎮**
Documentation/Simple_Game_Client_Guide.md ADDED
@@ -0,0 +1,744 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Simple Game Client Guide
2
+
3
+ This guide shows you how to create a simple client to connect to the MMORPG game server using the Model Context Protocol (MCP). No LLM integration required - just basic game operations.
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [Overview](#overview)
8
+ 2. [Prerequisites](#prerequisites)
9
+ 3. [MCP Server Connection](#mcp-server-connection)
10
+ 4. [Basic Client Example](#basic-client-example)
11
+ 5. [Available Game Operations](#available-game-operations)
12
+ 6. [Command Reference](#command-reference)
13
+ 7. [Error Handling](#error-handling)
14
+ 8. [Advanced Features](#advanced-features)
15
+
16
+ ---
17
+
18
+ ## Overview
19
+
20
+ The MMORPG game server exposes its functionality through MCP (Model Context Protocol), allowing external clients to:
21
+
22
+ - 🎮 **Connect** to the game server
23
+ - 👤 **Join/Leave** the game as a player
24
+ - 🗺️ **Move** around the game world
25
+ - 💬 **Send chat** messages to other players
26
+ - 🎯 **Interact** with NPCs and game objects
27
+ - 📊 **Get game state** information
28
+
29
+ ### Game Server Details
30
+
31
+ - **MCP Endpoint**: `http://localhost:7868/gradio_api/mcp/sse` (when running locally)
32
+ - **Protocol**: Server-Sent Events (SSE) over HTTP
33
+ - **Authentication**: None required for basic operations
34
+ - **Data Format**: JSON messages following MCP specification
35
+
36
+ ---
37
+
38
+ ## Prerequisites
39
+
40
+ Before building your client, ensure you have:
41
+
42
+ ### Required Dependencies
43
+
44
+ ```bash
45
+ pip install mcp asyncio aiohttp contextlib
46
+ ```
47
+
48
+ ### Python Version
49
+ - Python 3.8 or higher
50
+ - Support for async/await syntax
51
+
52
+ ### Game Server Running
53
+ Make sure the MMORPG game server is running:
54
+
55
+ ```bash
56
+ cd c:/Users/Chris4K/Projekte/projecthub/projects/MMOP_second_try
57
+ python app.py
58
+ ```
59
+
60
+ The server should be accessible at `http://localhost:7868` (UI) and the MCP endpoint at `http://localhost:7868/gradio_api/mcp/sse`.
61
+
62
+ ---
63
+
64
+ ## MCP Server Connection
65
+
66
+ Here's how to establish a connection to the game server:
67
+
68
+ ### Basic Connection Setup
69
+
70
+ ```python
71
+ import asyncio
72
+ from mcp import ClientSession
73
+ from mcp.client.sse import sse_client
74
+ from contextlib import AsyncExitStack
75
+
76
+ class GameClient:
77
+ def __init__(self, server_url="http://localhost:7868/gradio_api/mcp/sse"):
78
+ self.server_url = server_url
79
+ self.session = None
80
+ self.connected = False
81
+ self.tools = []
82
+ self.exit_stack = None
83
+ self.client_id = None
84
+
85
+ async def connect(self):
86
+ """Connect to the game server"""
87
+ try:
88
+ # Clean up any existing connection
89
+ if self.exit_stack:
90
+ await self.exit_stack.aclose()
91
+
92
+ self.exit_stack = AsyncExitStack()
93
+
94
+ # Establish SSE connection
95
+ transport = await self.exit_stack.enter_async_context(
96
+ sse_client(self.server_url)
97
+ )
98
+ read_stream, write_callable = transport
99
+
100
+ # Create MCP session
101
+ self.session = await self.exit_stack.enter_async_context(
102
+ ClientSession(read_stream, write_callable)
103
+ )
104
+
105
+ # Initialize the session
106
+ await self.session.initialize()
107
+
108
+ # Get available tools/commands
109
+ response = await self.session.list_tools()
110
+ self.tools = response.tools
111
+
112
+ self.connected = True
113
+ print(f"✅ Connected to game server!")
114
+ print(f"Available commands: {[tool.name for tool in self.tools]}")
115
+
116
+ return True
117
+
118
+ except Exception as e:
119
+ print(f"❌ Connection failed: {e}")
120
+ self.connected = False
121
+ return False
122
+
123
+ async def disconnect(self):
124
+ """Disconnect from the game server"""
125
+ if self.exit_stack:
126
+ await self.exit_stack.aclose()
127
+ self.connected = False
128
+ print("🔌 Disconnected from game server")
129
+ ```
130
+
131
+ ### Connection Test
132
+
133
+ ```python
134
+ async def test_connection():
135
+ client = GameClient()
136
+ if await client.connect():
137
+ print("Connection successful!")
138
+ await client.disconnect()
139
+ else:
140
+ print("Connection failed!")
141
+
142
+ # Run the test
143
+ asyncio.run(test_connection())
144
+ ```
145
+
146
+ ---
147
+
148
+ ## Basic Client Example
149
+
150
+ Here's a complete working client that can perform basic game operations:
151
+
152
+ ```python
153
+ import asyncio
154
+ import json
155
+ import uuid
156
+ from typing import Dict, List, Optional
157
+
158
+ class SimpleGameClient:
159
+ def __init__(self, server_url="http://localhost:7868/gradio_api/mcp/sse"):
160
+ self.server_url = server_url
161
+ self.session = None
162
+ self.connected = False
163
+ self.tools = []
164
+ self.exit_stack = None
165
+ self.client_id = str(uuid.uuid4())[:8]
166
+ self.agent_id = None
167
+ self.player_name = None
168
+
169
+ async def connect(self):
170
+ """Connect to the game server"""
171
+ try:
172
+ if self.exit_stack:
173
+ await self.exit_stack.aclose()
174
+
175
+ self.exit_stack = AsyncExitStack()
176
+
177
+ transport = await self.exit_stack.enter_async_context(
178
+ sse_client(self.server_url)
179
+ )
180
+ read_stream, write_callable = transport
181
+
182
+ self.session = await self.exit_stack.enter_async_context(
183
+ ClientSession(read_stream, write_callable)
184
+ )
185
+
186
+ await self.session.initialize()
187
+ response = await self.session.list_tools()
188
+ self.tools = response.tools
189
+
190
+ self.connected = True
191
+ return True
192
+
193
+ except Exception as e:
194
+ print(f"❌ Connection failed: {e}")
195
+ return False
196
+
197
+ async def register_player(self, player_name: str):
198
+ """Register as a new player in the game"""
199
+ if not self.connected:
200
+ print("❌ Not connected to server")
201
+ return False
202
+
203
+ try:
204
+ # Find the register tool
205
+ register_tool = next((t for t in self.tools if 'register' in t.name), None)
206
+ if not register_tool:
207
+ print("❌ Register tool not available")
208
+ return False
209
+
210
+ # Register the player
211
+ result = await self.session.call_tool(
212
+ register_tool.name,
213
+ {"name": player_name, "client_id": self.client_id}
214
+ )
215
+
216
+ # Parse the result
217
+ content = self._extract_content(result)
218
+ if "registered" in content.lower():
219
+ self.player_name = player_name
220
+ # Extract agent_id from response if available
221
+ if "agent_id" in content or "ID:" in content:
222
+ # Parse agent ID from response
223
+ lines = content.split('\n')
224
+ for line in lines:
225
+ if "agent_id" in line.lower() or "id:" in line:
226
+ parts = line.split(':')
227
+ if len(parts) > 1:
228
+ self.agent_id = parts[1].strip()
229
+ break
230
+
231
+ print(f"✅ Registered as player: {player_name}")
232
+ return True
233
+ else:
234
+ print(f"❌ Registration failed: {content}")
235
+ return False
236
+
237
+ except Exception as e:
238
+ print(f"❌ Registration error: {e}")
239
+ return False
240
+
241
+ async def move_player(self, direction: str):
242
+ """Move the player in the specified direction"""
243
+ if not self.connected or not self.agent_id:
244
+ print("❌ Not connected or not registered")
245
+ return False
246
+
247
+ try:
248
+ # Find the move tool
249
+ move_tool = next((t for t in self.tools if 'move' in t.name), None)
250
+ if not move_tool:
251
+ print("❌ Move tool not available")
252
+ return False
253
+
254
+ # Move the player
255
+ result = await self.session.call_tool(
256
+ move_tool.name,
257
+ {"client_id": self.client_id, "direction": direction}
258
+ )
259
+
260
+ content = self._extract_content(result)
261
+ print(f"🚶 Move result: {content}")
262
+ return True
263
+
264
+ except Exception as e:
265
+ print(f"❌ Move error: {e}")
266
+ return False
267
+
268
+ async def send_chat(self, message: str):
269
+ """Send a chat message to other players"""
270
+ if not self.connected or not self.agent_id:
271
+ print("❌ Not connected or not registered")
272
+ return False
273
+
274
+ try:
275
+ # Find the chat tool
276
+ chat_tool = next((t for t in self.tools if 'chat' in t.name), None)
277
+ if not chat_tool:
278
+ print("❌ Chat tool not available")
279
+ return False
280
+
281
+ # Send the chat message
282
+ result = await self.session.call_tool(
283
+ chat_tool.name,
284
+ {"client_id": self.client_id, "message": message}
285
+ )
286
+
287
+ content = self._extract_content(result)
288
+ print(f"💬 Chat sent: {content}")
289
+ return True
290
+
291
+ except Exception as e:
292
+ print(f"❌ Chat error: {e}")
293
+ return False
294
+
295
+ async def get_game_state(self):
296
+ """Get current game state information"""
297
+ if not self.connected:
298
+ print("❌ Not connected to server")
299
+ return None
300
+
301
+ try:
302
+ # Find the game state tool
303
+ state_tool = next((t for t in self.tools if 'state' in t.name or 'game' in t.name), None)
304
+ if not state_tool:
305
+ print("❌ Game state tool not available")
306
+ return None
307
+
308
+ # Get game state
309
+ result = await self.session.call_tool(state_tool.name, {})
310
+ content = self._extract_content(result)
311
+
312
+ try:
313
+ # Try to parse as JSON
314
+ game_state = json.loads(content)
315
+ return game_state
316
+ except json.JSONDecodeError:
317
+ # Return as text if not JSON
318
+ return {"info": content}
319
+
320
+ except Exception as e:
321
+ print(f"❌ Game state error: {e}")
322
+ return None
323
+
324
+ async def interact_with_npc(self, npc_id: str, message: str):
325
+ """Interact with an NPC"""
326
+ if not self.connected or not self.agent_id:
327
+ print("❌ Not connected or not registered")
328
+ return False
329
+
330
+ try:
331
+ # Find the interact tool
332
+ interact_tool = next((t for t in self.tools if 'interact' in t.name or 'npc' in t.name), None)
333
+ if not interact_tool:
334
+ print("❌ Interact tool not available")
335
+ return False
336
+
337
+ # Interact with NPC
338
+ result = await self.session.call_tool(
339
+ interact_tool.name,
340
+ {"client_id": self.client_id, "npc_id": npc_id, "message": message}
341
+ )
342
+
343
+ content = self._extract_content(result)
344
+ print(f"🤖 NPC {npc_id}: {content}")
345
+ return True
346
+
347
+ except Exception as e:
348
+ print(f"❌ Interaction error: {e}")
349
+ return False
350
+
351
+ def _extract_content(self, result):
352
+ """Extract content from MCP result"""
353
+ if hasattr(result, 'content') and result.content:
354
+ if isinstance(result.content, list):
355
+ return ''.join(str(item) for item in result.content)
356
+ return str(result.content)
357
+ return str(result)
358
+
359
+ async def disconnect(self):
360
+ """Disconnect from the server"""
361
+ if self.exit_stack:
362
+ await self.exit_stack.aclose()
363
+ self.connected = False
364
+ print("🔌 Disconnected from server")
365
+
366
+ # Import required modules at the top
367
+ from mcp import ClientSession
368
+ from mcp.client.sse import sse_client
369
+ from contextlib import AsyncExitStack
370
+ ```
371
+
372
+ ---
373
+
374
+ ## Available Game Operations
375
+
376
+ The game server exposes these main operations through MCP tools:
377
+
378
+ ### 1. Player Management
379
+ - **`register_ai_agent`** - Register a new player
380
+ - **`move_agent`** - Move player in game world
381
+ - **`get_game_state`** - Get current world state
382
+
383
+ ### 2. Communication
384
+ - **`send_chat`** - Send chat messages
385
+ - **`interact_with_npc`** - Talk to NPCs
386
+
387
+ ### 3. Game Information
388
+ - **`get_game_state`** - World state and player list
389
+ - **Player proximity** - Detect nearby players/NPCs
390
+
391
+ ### Tool Parameters
392
+
393
+ Each tool expects specific parameters:
394
+
395
+ ```python
396
+ # Register Player
397
+ {
398
+ "name": "PlayerName",
399
+ "client_id": "unique_client_id"
400
+ }
401
+
402
+ # Move Player
403
+ {
404
+ "client_id": "your_client_id",
405
+ "direction": "north|south|east|west"
406
+ }
407
+
408
+ # Send Chat
409
+ {
410
+ "client_id": "your_client_id",
411
+ "message": "Hello world!"
412
+ }
413
+
414
+ # Interact with NPC
415
+ {
416
+ "client_id": "your_client_id",
417
+ "npc_id": "npc_identifier",
418
+ "message": "Your message to NPC"
419
+ }
420
+ ```
421
+
422
+ ---
423
+
424
+ ## Command Reference
425
+
426
+ ### Basic Usage Example
427
+
428
+ ```python
429
+ async def main():
430
+ # Create and connect client
431
+ client = SimpleGameClient()
432
+
433
+ if not await client.connect():
434
+ print("Failed to connect!")
435
+ return
436
+
437
+ # Register as a player
438
+ if not await client.register_player("MyPlayer"):
439
+ print("Failed to register!")
440
+ return
441
+
442
+ # Get current game state
443
+ state = await client.get_game_state()
444
+ print(f"Game state: {state}")
445
+
446
+ # Move around
447
+ await client.move_player("north")
448
+ await client.move_player("east")
449
+
450
+ # Send a chat message
451
+ await client.send_chat("Hello everyone!")
452
+
453
+ # Interact with an NPC
454
+ await client.interact_with_npc("weather_oracle", "What's the weather in Berlin?")
455
+
456
+ # Disconnect
457
+ await client.disconnect()
458
+
459
+ # Run the client
460
+ asyncio.run(main())
461
+ ```
462
+
463
+ ### Interactive Console Client
464
+
465
+ ```python
466
+ async def interactive_client():
467
+ client = SimpleGameClient()
468
+
469
+ print("🎮 MMORPG Simple Client")
470
+ print("Commands: connect, register <name>, move <direction>, chat <message>, state, quit")
471
+
472
+ while True:
473
+ command = input("> ").strip().split()
474
+
475
+ if not command:
476
+ continue
477
+
478
+ cmd = command[0].lower()
479
+
480
+ if cmd == "connect":
481
+ if await client.connect():
482
+ print("✅ Connected!")
483
+ else:
484
+ print("❌ Connection failed!")
485
+
486
+ elif cmd == "register" and len(command) > 1:
487
+ name = " ".join(command[1:])
488
+ if await client.register_player(name):
489
+ print(f"✅ Registered as {name}")
490
+ else:
491
+ print("❌ Registration failed!")
492
+
493
+ elif cmd == "move" and len(command) > 1:
494
+ direction = command[1]
495
+ await client.move_player(direction)
496
+
497
+ elif cmd == "chat" and len(command) > 1:
498
+ message = " ".join(command[1:])
499
+ await client.send_chat(message)
500
+
501
+ elif cmd == "state":
502
+ state = await client.get_game_state()
503
+ print(f"📊 Game State: {json.dumps(state, indent=2)}")
504
+
505
+ elif cmd == "npc" and len(command) > 2:
506
+ npc_id = command[1]
507
+ message = " ".join(command[2:])
508
+ await client.interact_with_npc(npc_id, message)
509
+
510
+ elif cmd == "quit":
511
+ await client.disconnect()
512
+ break
513
+
514
+ else:
515
+ print("❓ Unknown command")
516
+
517
+ # Run interactive client
518
+ asyncio.run(interactive_client())
519
+ ```
520
+
521
+ ---
522
+
523
+ ## Error Handling
524
+
525
+ ### Common Issues and Solutions
526
+
527
+ #### 1. Connection Errors
528
+ ```python
529
+ async def robust_connect(client, max_retries=3):
530
+ for attempt in range(max_retries):
531
+ try:
532
+ if await client.connect():
533
+ return True
534
+ print(f"Attempt {attempt + 1} failed, retrying...")
535
+ await asyncio.sleep(2)
536
+ except Exception as e:
537
+ print(f"Connection attempt {attempt + 1} error: {e}")
538
+
539
+ print("❌ All connection attempts failed")
540
+ return False
541
+ ```
542
+
543
+ #### 2. Tool Not Available
544
+ ```python
545
+ def find_tool_safe(tools, keywords):
546
+ """Safely find a tool by keywords"""
547
+ for keyword in keywords:
548
+ tool = next((t for t in tools if keyword in t.name.lower()), None)
549
+ if tool:
550
+ return tool
551
+ return None
552
+
553
+ # Usage
554
+ move_tool = find_tool_safe(client.tools, ['move', 'agent', 'player'])
555
+ if not move_tool:
556
+ print("❌ No movement tool available")
557
+ ```
558
+
559
+ #### 3. Response Parsing
560
+ ```python
561
+ def safe_parse_response(result):
562
+ """Safely parse MCP response"""
563
+ try:
564
+ content = client._extract_content(result)
565
+
566
+ # Try JSON first
567
+ try:
568
+ return json.loads(content)
569
+ except json.JSONDecodeError:
570
+ # Return as structured text
571
+ return {"message": content, "type": "text"}
572
+
573
+ except Exception as e:
574
+ return {"error": str(e), "type": "error"}
575
+ ```
576
+
577
+ ---
578
+
579
+ ## Advanced Features
580
+
581
+ ### 1. Automatic Reconnection
582
+
583
+ ```python
584
+ class RobustGameClient(SimpleGameClient):
585
+ def __init__(self, *args, **kwargs):
586
+ super().__init__(*args, **kwargs)
587
+ self.auto_reconnect = True
588
+ self.reconnect_delay = 5
589
+
590
+ async def _ensure_connected(self):
591
+ """Ensure connection is active, reconnect if needed"""
592
+ if not self.connected and self.auto_reconnect:
593
+ print("🔄 Attempting to reconnect...")
594
+ await self.connect()
595
+ if self.player_name:
596
+ await self.register_player(self.player_name)
597
+
598
+ async def move_player(self, direction):
599
+ await self._ensure_connected()
600
+ return await super().move_player(direction)
601
+ ```
602
+
603
+ ### 2. Event Monitoring
604
+
605
+ ```python
606
+ class EventMonitorClient(SimpleGameClient):
607
+ def __init__(self, *args, **kwargs):
608
+ super().__init__(*args, **kwargs)
609
+ self.event_handlers = {}
610
+
611
+ def on_chat_message(self, handler):
612
+ """Register handler for chat messages"""
613
+ self.event_handlers['chat'] = handler
614
+
615
+ def on_player_move(self, handler):
616
+ """Register handler for player movements"""
617
+ self.event_handlers['move'] = handler
618
+
619
+ async def poll_events(self):
620
+ """Poll for game events"""
621
+ while self.connected:
622
+ try:
623
+ state = await self.get_game_state()
624
+ # Process state changes and trigger handlers
625
+ # Implementation depends on game state format
626
+ await asyncio.sleep(1)
627
+ except Exception as e:
628
+ print(f"Event polling error: {e}")
629
+ await asyncio.sleep(5)
630
+ ```
631
+
632
+ ### 3. Batch Operations
633
+
634
+ ```python
635
+ async def batch_moves(client, moves):
636
+ """Execute multiple moves in sequence"""
637
+ for direction in moves:
638
+ result = await client.move_player(direction)
639
+ if not result:
640
+ print(f"❌ Failed to move {direction}")
641
+ break
642
+ await asyncio.sleep(0.5) # Small delay between moves
643
+
644
+ # Usage
645
+ await batch_moves(client, ["north", "north", "east", "south"])
646
+ ```
647
+
648
+ ### 4. Configuration Management
649
+
650
+ ```python
651
+ import json
652
+
653
+ class ConfigurableClient(SimpleGameClient):
654
+ def __init__(self, config_file="client_config.json"):
655
+ # Load configuration
656
+ try:
657
+ with open(config_file, 'r') as f:
658
+ config = json.load(f)
659
+ except FileNotFoundError:
660
+ config = self.default_config()
661
+
662
+ super().__init__(config.get('server_url', 'http://localhost:7860/gradio_api/mcp/sse'))
663
+ self.config = config
664
+
665
+ def default_config(self):
666
+ return {
667
+ "server_url": "http://localhost:7860/gradio_api/mcp/sse",
668
+ "player_name": "DefaultPlayer",
669
+ "auto_reconnect": True,
670
+ "move_delay": 0.5
671
+ }
672
+ ```
673
+
674
+ ---
675
+
676
+ ## Quick Start Script
677
+
678
+ Save this as `quick_client.py` for immediate testing:
679
+
680
+ ```python
681
+ #!/usr/bin/env python3
682
+ """
683
+ Quick Game Client - Connect and play immediately
684
+ Usage: python quick_client.py [player_name]
685
+ """
686
+
687
+ import asyncio
688
+ import sys
689
+ from simple_game_client import SimpleGameClient
690
+
691
+ async def quick_play(player_name="TestPlayer"):
692
+ client = SimpleGameClient()
693
+
694
+ print(f"🎮 Connecting as {player_name}...")
695
+
696
+ # Connect and register
697
+ if not await client.connect():
698
+ print("❌ Failed to connect!")
699
+ return
700
+
701
+ if not await client.register_player(player_name):
702
+ print("❌ Failed to register!")
703
+ return
704
+
705
+ print("✅ Connected and registered successfully!")
706
+
707
+ # Basic game session
708
+ await client.send_chat(f"Hello! {player_name} has joined the game!")
709
+
710
+ # Move around a bit
711
+ moves = ["north", "east", "south", "west"]
712
+ for move in moves:
713
+ print(f"🚶 Moving {move}...")
714
+ await client.move_player(move)
715
+ await asyncio.sleep(1)
716
+
717
+ # Get final state
718
+ state = await client.get_game_state()
719
+ print(f"📊 Final state: {state}")
720
+
721
+ await client.disconnect()
722
+ print("👋 Session ended!")
723
+
724
+ if __name__ == "__main__":
725
+ player_name = sys.argv[1] if len(sys.argv) > 1 else "TestPlayer"
726
+ asyncio.run(quick_play(player_name))
727
+ ```
728
+
729
+ Run with:
730
+ ```bash
731
+ python quick_client.py MyPlayerName
732
+ ```
733
+
734
+ ---
735
+
736
+ ## Next Steps
737
+
738
+ 1. **Customize the client** for your specific needs
739
+ 2. **Add game-specific features** like inventory management
740
+ 3. **Implement advanced AI** for automated gameplay
741
+ 4. **Create GUI interfaces** using tkinter, PyQt, or web frameworks
742
+ 5. **Build monitoring tools** for game statistics
743
+
744
+ This guide provides the foundation for any client application that needs to interact with the MMORPG game server!
Documentation/sample_gradio_mcp_server.py ADDED
@@ -0,0 +1,475 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Sample Gradio MCP Server
4
+ ========================
5
+
6
+ This is a complete example of how to create a Gradio application that serves as an MCP server.
7
+ This example creates a "Task Manager" service that can be used as an NPC addon in the MMORPG.
8
+
9
+ Key Features:
10
+ - Full MCP server implementation with Gradio
11
+ - RESTful API endpoints for MCP communication
12
+ - Gradio web interface for direct interaction
13
+ - Error handling and validation
14
+ - Proper MCP protocol responses
15
+
16
+ Usage:
17
+ 1. Run this file: python sample_gradio_mcp_server.py
18
+ 2. Access web interface at: http://localhost:7860
19
+ 3. Use MCP client to connect to: http://localhost:7860/gradio_api/mcp/sse
20
+ """
21
+
22
+ import gradio as gr
23
+ import json
24
+ import time
25
+ import uuid
26
+ from datetime import datetime, timedelta
27
+ from typing import Dict, List, Any, Optional
28
+ from dataclasses import dataclass, asdict
29
+
30
+ # ============================================================================
31
+ # DATA MODELS
32
+ # ============================================================================
33
+
34
+ @dataclass
35
+ class Task:
36
+ id: str
37
+ title: str
38
+ description: str
39
+ status: str # "pending", "in_progress", "completed"
40
+ priority: str # "low", "medium", "high"
41
+ created_at: float
42
+ due_date: Optional[float] = None
43
+ completed_at: Optional[float] = None
44
+
45
+ class TaskManager:
46
+ """Core task management logic"""
47
+
48
+ def __init__(self):
49
+ self.tasks: Dict[str, Task] = {}
50
+ self.task_counter = 0
51
+
52
+ def create_task(self, title: str, description: str = "", priority: str = "medium", due_date: Optional[str] = None) -> Dict[str, Any]:
53
+ """Create a new task"""
54
+ if not title.strip():
55
+ return {"error": "Task title cannot be empty"}
56
+
57
+ task_id = f"task_{int(time.time())}_{self.task_counter}"
58
+ self.task_counter += 1
59
+
60
+ due_timestamp = None
61
+ if due_date:
62
+ try:
63
+ # Simple date parsing (format: YYYY-MM-DD)
64
+ due_timestamp = datetime.strptime(due_date, "%Y-%m-%d").timestamp()
65
+ except ValueError:
66
+ return {"error": "Invalid date format. Use YYYY-MM-DD"}
67
+
68
+ task = Task(
69
+ id=task_id,
70
+ title=title.strip(),
71
+ description=description.strip(),
72
+ status="pending",
73
+ priority=priority.lower(),
74
+ created_at=time.time(),
75
+ due_date=due_timestamp
76
+ )
77
+
78
+ self.tasks[task_id] = task
79
+
80
+ return {
81
+ "success": True,
82
+ "task_id": task_id,
83
+ "message": f"Task '{title}' created successfully!",
84
+ "task": asdict(task)
85
+ }
86
+
87
+ def list_tasks(self, status_filter: Optional[str] = None) -> Dict[str, Any]:
88
+ """List all tasks or filter by status"""
89
+ tasks = list(self.tasks.values())
90
+
91
+ if status_filter:
92
+ tasks = [t for t in tasks if t.status == status_filter.lower()]
93
+
94
+ # Sort by priority and creation date
95
+ priority_order = {"high": 0, "medium": 1, "low": 2}
96
+ tasks.sort(key=lambda t: (priority_order.get(t.priority, 2), t.created_at))
97
+
98
+ return {
99
+ "success": True,
100
+ "count": len(tasks),
101
+ "tasks": [asdict(task) for task in tasks]
102
+ }
103
+
104
+ def update_task_status(self, task_id: str, new_status: str) -> Dict[str, Any]:
105
+ """Update task status"""
106
+ if task_id not in self.tasks:
107
+ return {"error": f"Task {task_id} not found"}
108
+
109
+ valid_statuses = ["pending", "in_progress", "completed"]
110
+ if new_status.lower() not in valid_statuses:
111
+ return {"error": f"Invalid status. Must be one of: {', '.join(valid_statuses)}"}
112
+
113
+ task = self.tasks[task_id]
114
+ old_status = task.status
115
+ task.status = new_status.lower()
116
+
117
+ if new_status.lower() == "completed":
118
+ task.completed_at = time.time()
119
+
120
+ return {
121
+ "success": True,
122
+ "message": f"Task '{task.title}' status changed from '{old_status}' to '{new_status}'",
123
+ "task": asdict(task)
124
+ }
125
+
126
+ def delete_task(self, task_id: str) -> Dict[str, Any]:
127
+ """Delete a task"""
128
+ if task_id not in self.tasks:
129
+ return {"error": f"Task {task_id} not found"}
130
+
131
+ task = self.tasks.pop(task_id)
132
+ return {
133
+ "success": True,
134
+ "message": f"Task '{task.title}' deleted successfully"
135
+ }
136
+
137
+ def get_task_stats(self) -> Dict[str, Any]:
138
+ """Get task statistics"""
139
+ total = len(self.tasks)
140
+ completed = len([t for t in self.tasks.values() if t.status == "completed"])
141
+ in_progress = len([t for t in self.tasks.values() if t.status == "in_progress"])
142
+ pending = len([t for t in self.tasks.values() if t.status == "pending"])
143
+
144
+ # Overdue tasks
145
+ current_time = time.time()
146
+ overdue = len([
147
+ t for t in self.tasks.values()
148
+ if t.due_date and t.due_date < current_time and t.status != "completed"
149
+ ])
150
+
151
+ return {
152
+ "success": True,
153
+ "stats": {
154
+ "total_tasks": total,
155
+ "completed": completed,
156
+ "in_progress": in_progress,
157
+ "pending": pending,
158
+ "overdue": overdue,
159
+ "completion_rate": round((completed / total * 100) if total > 0 else 0, 1)
160
+ }
161
+ }
162
+
163
+ # Global task manager instance
164
+ task_manager = TaskManager()
165
+
166
+ # ============================================================================
167
+ # MCP TOOLS DEFINITION
168
+ # ============================================================================
169
+
170
+ def mcp_create_task(title: str, description: str = "", priority: str = "medium", due_date: str = "") -> str:
171
+ """
172
+ Create a new task in the task manager.
173
+
174
+ Args:
175
+ title: Task title (required)
176
+ description: Detailed task description (optional)
177
+ priority: Task priority - low, medium, or high (default: medium)
178
+ due_date: Due date in YYYY-MM-DD format (optional)
179
+
180
+ Returns:
181
+ JSON string with task creation result
182
+ """
183
+ result = task_manager.create_task(
184
+ title=title,
185
+ description=description,
186
+ priority=priority,
187
+ due_date=due_date if due_date else None
188
+ )
189
+ return json.dumps(result, indent=2)
190
+
191
+ def mcp_list_tasks(status_filter: str = "") -> str:
192
+ """
193
+ List all tasks or filter by status.
194
+
195
+ Args:
196
+ status_filter: Filter by status - pending, in_progress, or completed (optional)
197
+
198
+ Returns:
199
+ JSON string with list of tasks
200
+ """
201
+ result = task_manager.list_tasks(status_filter if status_filter else None)
202
+ return json.dumps(result, indent=2)
203
+
204
+ def mcp_update_task_status(task_id: str, new_status: str) -> str:
205
+ """
206
+ Update the status of an existing task.
207
+
208
+ Args:
209
+ task_id: ID of the task to update
210
+ new_status: New status - pending, in_progress, or completed
211
+
212
+ Returns:
213
+ JSON string with update result
214
+ """
215
+ result = task_manager.update_task_status(task_id, new_status)
216
+ return json.dumps(result, indent=2)
217
+
218
+ def mcp_delete_task(task_id: str) -> str:
219
+ """
220
+ Delete a task from the task manager.
221
+
222
+ Args:
223
+ task_id: ID of the task to delete
224
+
225
+ Returns:
226
+ JSON string with deletion result
227
+ """
228
+ result = task_manager.delete_task(task_id)
229
+ return json.dumps(result, indent=2)
230
+
231
+ def mcp_get_task_stats() -> str:
232
+ """
233
+ Get task statistics and completion metrics.
234
+
235
+ Returns:
236
+ JSON string with task statistics
237
+ """
238
+ result = task_manager.get_task_stats()
239
+ return json.dumps(result, indent=2)
240
+
241
+ # ============================================================================
242
+ # GRADIO INTERFACE
243
+ # ============================================================================
244
+
245
+ def create_task_interface():
246
+ """Create the Gradio interface for the Task Manager MCP Server"""
247
+
248
+ with gr.Blocks(
249
+ title="Task Manager MCP Server",
250
+ theme=gr.themes.Soft(),
251
+ css="""
252
+ .task-card {
253
+ border: 1px solid #ddd;
254
+ border-radius: 8px;
255
+ padding: 15px;
256
+ margin: 10px 0;
257
+ background: #f9f9f9;
258
+ }
259
+ .high-priority { border-left: 4px solid #ff4444; }
260
+ .medium-priority { border-left: 4px solid #ffaa00; }
261
+ .low-priority { border-left: 4px solid #44aa44; }
262
+ """
263
+ ) as demo:
264
+
265
+ gr.Markdown("""
266
+ # 📋 Task Manager MCP Server
267
+
268
+ **MCP Endpoint:** `http://localhost:7860/gradio_api/mcp/sse`
269
+
270
+ This server provides task management capabilities via MCP protocol.
271
+ Perfect for integration as an NPC addon in games or other applications!
272
+
273
+ ## Available MCP Tools:
274
+ - `mcp_create_task` - Create new tasks
275
+ - `mcp_list_tasks` - List and filter tasks
276
+ - `mcp_update_task_status` - Update task progress
277
+ - `mcp_delete_task` - Remove tasks
278
+ - `mcp_get_task_stats` - Get statistics
279
+ """)
280
+
281
+ with gr.Tabs():
282
+ # Create Task Tab
283
+ with gr.Tab("➕ Create Task"):
284
+ with gr.Row():
285
+ task_title = gr.Textbox(
286
+ label="Task Title",
287
+ placeholder="Enter task title...",
288
+ scale=2
289
+ )
290
+ priority = gr.Dropdown(
291
+ choices=["low", "medium", "high"],
292
+ value="medium",
293
+ label="Priority",
294
+ scale=1
295
+ )
296
+
297
+ task_description = gr.Textbox(
298
+ label="Description",
299
+ placeholder="Detailed task description...",
300
+ lines=3
301
+ )
302
+
303
+ due_date = gr.Textbox(
304
+ label="Due Date (YYYY-MM-DD)",
305
+ placeholder="2024-12-31",
306
+ value=""
307
+ )
308
+
309
+ create_btn = gr.Button("Create Task", variant="primary")
310
+ create_result = gr.JSON(label="Result")
311
+
312
+ def handle_create_task(title, desc, prio, due):
313
+ result = task_manager.create_task(title, desc, prio, due if due else None)
314
+ return result
315
+
316
+ create_btn.click(
317
+ handle_create_task,
318
+ inputs=[task_title, task_description, priority, due_date],
319
+ outputs=[create_result]
320
+ )
321
+
322
+ # View Tasks Tab
323
+ with gr.Tab("📋 View Tasks"):
324
+ with gr.Row():
325
+ status_filter = gr.Dropdown(
326
+ choices=["", "pending", "in_progress", "completed"],
327
+ value="",
328
+ label="Filter by Status",
329
+ scale=1
330
+ )
331
+ refresh_btn = gr.Button("🔄 Refresh", scale=1)
332
+
333
+ tasks_display = gr.JSON(label="Tasks")
334
+
335
+ def handle_list_tasks(status_filter_val):
336
+ result = task_manager.list_tasks(status_filter_val if status_filter_val else None)
337
+ return result
338
+
339
+ refresh_btn.click(
340
+ handle_list_tasks,
341
+ inputs=[status_filter],
342
+ outputs=[tasks_display]
343
+ )
344
+
345
+ status_filter.change(
346
+ handle_list_tasks,
347
+ inputs=[status_filter],
348
+ outputs=[tasks_display]
349
+ )
350
+
351
+ # Update Task Tab
352
+ with gr.Tab("✏️ Update Task"):
353
+ update_task_id = gr.Textbox(
354
+ label="Task ID",
355
+ placeholder="task_1234567890_0"
356
+ )
357
+
358
+ new_status = gr.Dropdown(
359
+ choices=["pending", "in_progress", "completed"],
360
+ label="New Status",
361
+ value="in_progress"
362
+ )
363
+
364
+ update_btn = gr.Button("Update Status", variant="primary")
365
+ update_result = gr.JSON(label="Result")
366
+
367
+ def handle_update_status(task_id, status):
368
+ result = task_manager.update_task_status(task_id, status)
369
+ return result
370
+
371
+ update_btn.click(
372
+ handle_update_status,
373
+ inputs=[update_task_id, new_status],
374
+ outputs=[update_result]
375
+ )
376
+
377
+ # Statistics Tab
378
+ with gr.Tab("📊 Statistics"):
379
+ stats_btn = gr.Button("📈 Get Current Stats", variant="primary")
380
+ stats_display = gr.JSON(label="Task Statistics")
381
+
382
+ def handle_get_stats():
383
+ result = task_manager.get_task_stats()
384
+ return result
385
+
386
+ stats_btn.click(
387
+ handle_get_stats,
388
+ outputs=[stats_display]
389
+ )
390
+
391
+ # MCP Testing Tab
392
+ with gr.Tab("🔌 MCP Testing"):
393
+ gr.Markdown("""
394
+ ## Test MCP Functions
395
+
396
+ Use this tab to test the MCP functions that will be available to external clients.
397
+ """)
398
+
399
+ with gr.Group():
400
+ gr.Markdown("### Test mcp_create_task")
401
+ with gr.Row():
402
+ test_title = gr.Textbox(label="Title", value="Test Task")
403
+ test_desc = gr.Textbox(label="Description", value="This is a test task")
404
+ test_priority = gr.Dropdown(choices=["low", "medium", "high"], value="medium", label="Priority")
405
+
406
+ test_create_btn = gr.Button("Test Create")
407
+ test_create_result = gr.Textbox(label="MCP Response", lines=10)
408
+
409
+ test_create_btn.click(
410
+ lambda t, d, p: mcp_create_task(t, d, p),
411
+ inputs=[test_title, test_desc, test_priority],
412
+ outputs=[test_create_result]
413
+ )
414
+
415
+ with gr.Group():
416
+ gr.Markdown("### Test mcp_list_tasks")
417
+ test_filter = gr.Dropdown(choices=["", "pending", "in_progress", "completed"], value="", label="Status Filter")
418
+ test_list_btn = gr.Button("Test List")
419
+ test_list_result = gr.Textbox(label="MCP Response", lines=10)
420
+
421
+ test_list_btn.click(
422
+ lambda f: mcp_list_tasks(f),
423
+ inputs=[test_filter],
424
+ outputs=[test_list_result]
425
+ )
426
+
427
+ with gr.Group():
428
+ gr.Markdown("### Test mcp_get_task_stats")
429
+ test_stats_btn = gr.Button("Test Stats")
430
+ test_stats_result = gr.Textbox(label="MCP Response", lines=10)
431
+
432
+ test_stats_btn.click(
433
+ lambda: mcp_get_task_stats(),
434
+ outputs=[test_stats_result]
435
+ )
436
+
437
+ # Auto-refresh stats on page load
438
+ demo.load(
439
+ lambda: task_manager.get_task_stats(),
440
+ outputs=[stats_display]
441
+ )
442
+
443
+ return demo
444
+
445
+ # ============================================================================
446
+ # MAIN APPLICATION
447
+ # ============================================================================
448
+
449
+ if __name__ == "__main__":
450
+ print("🚀 Starting Task Manager MCP Server...")
451
+ print("=" * 60)
452
+ print("✅ Web Interface: http://localhost:7860")
453
+ print("🔌 MCP Endpoint: http://localhost:7860/gradio_api/mcp/sse")
454
+ print("📋 Available MCP Tools:")
455
+ print(" - mcp_create_task")
456
+ print(" - mcp_list_tasks")
457
+ print(" - mcp_update_task_status")
458
+ print(" - mcp_delete_task")
459
+ print(" - mcp_get_task_stats")
460
+ print("=" * 60)
461
+
462
+ # Create some sample tasks for demonstration
463
+ task_manager.create_task("Welcome Task", "This is a sample task to get you started!", "high")
464
+ task_manager.create_task("Learn MCP Protocol", "Study the Model Context Protocol documentation", "medium")
465
+ task_manager.create_task("Build NPC Addon", "Create a new NPC addon for the MMORPG", "high")
466
+
467
+ # Launch the Gradio app with MCP server enabled
468
+ interface = create_task_interface()
469
+ interface.launch(
470
+ debug=True,
471
+ share=False,
472
+ server_port=7860,
473
+ mcp_server=True, # This enables MCP protocol support
474
+ show_error=True
475
+ )
Documentation/simple_game_client.py ADDED
@@ -0,0 +1,485 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Simple MMORPG Game Client
4
+ ========================
5
+
6
+ A basic client implementation for connecting to the MMORPG game server via MCP.
7
+ This client can join the game, move around, chat, and interact with NPCs.
8
+
9
+ Requirements:
10
+ pip install mcp aiohttp asyncio
11
+
12
+ Usage:
13
+ python simple_game_client.py
14
+
15
+ Features:
16
+ - Connect to game server via MCP
17
+ - Register as a player
18
+ - Move around the game world
19
+ - Send chat messages
20
+ - Interact with NPCs
21
+ - Get game state information
22
+ - Interactive console interface
23
+ """
24
+
25
+ import asyncio
26
+ import json
27
+ import uuid
28
+ import sys
29
+ from typing import Dict, List, Optional
30
+
31
+ try:
32
+ from mcp import ClientSession
33
+ from mcp.client.sse import sse_client
34
+ from contextlib import AsyncExitStack
35
+ except ImportError:
36
+ print("❌ Missing dependencies. Install with: pip install mcp aiohttp")
37
+ sys.exit(1)
38
+
39
+
40
+ class SimpleGameClient:
41
+ """Simple client for connecting to the MMORPG game server"""
42
+
43
+ def __init__(self, server_url="http://127.0.0.1:7868/gradio_api/mcp/sse"):
44
+ self.server_url = server_url
45
+ self.session = None
46
+ self.connected = False
47
+ self.tools = []
48
+ self.exit_stack = None
49
+ self.client_id = f"client_{uuid.uuid4().hex[:8]}"
50
+ self.agent_id = None
51
+ self.player_name = None
52
+
53
+ print(f"🎮 Game Client initialized with ID: {self.client_id}")
54
+
55
+ async def connect(self):
56
+ """Connect to the game server"""
57
+ print(f"🔌 Connecting to {self.server_url}...")
58
+
59
+ try:
60
+ # Clean up any existing connection
61
+ if self.exit_stack:
62
+ await self.exit_stack.aclose()
63
+
64
+ self.exit_stack = AsyncExitStack()
65
+
66
+ # Establish SSE connection to MCP server
67
+ transport = await self.exit_stack.enter_async_context(
68
+ sse_client(self.server_url)
69
+ )
70
+ read_stream, write_callable = transport
71
+
72
+ # Create MCP client session
73
+ self.session = await self.exit_stack.enter_async_context(
74
+ ClientSession(read_stream, write_callable)
75
+ )
76
+
77
+ # Initialize the session
78
+ await self.session.initialize()
79
+
80
+ # Get available tools/commands from server
81
+ response = await self.session.list_tools()
82
+ self.tools = response.tools
83
+
84
+ self.connected = True
85
+ tool_names = [tool.name for tool in self.tools]
86
+ print(f"✅ Connected successfully!")
87
+ print(f"📋 Available commands: {', '.join(tool_names)}")
88
+
89
+ return True
90
+
91
+ except Exception as e:
92
+ print(f"❌ Connection failed: {e}")
93
+ self.connected = False
94
+ return False
95
+
96
+ async def register_player(self, player_name: str):
97
+ """Register as a new player in the game"""
98
+ if not self.connected:
99
+ print("❌ Not connected to server")
100
+ return False
101
+
102
+ print(f"👤 Registering player: {player_name}")
103
+
104
+ try:
105
+ # Find the register tool
106
+ register_tool = self._find_tool(['register', 'agent'])
107
+ if not register_tool:
108
+ print("❌ Register tool not available on server")
109
+ return False
110
+
111
+ # Register the player
112
+ result = await self.session.call_tool(
113
+ register_tool.name,
114
+ {"name": player_name, "client_id": self.client_id}
115
+ )
116
+
117
+ # Parse the result
118
+ content = self._extract_content(result)
119
+ print(f"📝 Registration response: {content}")
120
+
121
+ if "registered" in content.lower() or "success" in content.lower():
122
+ self.player_name = player_name
123
+ # Try to extract agent_id from response
124
+ self._parse_agent_id(content)
125
+ print(f"✅ Successfully registered as: {player_name}")
126
+ return True
127
+ else:
128
+ print(f"❌ Registration failed: {content}")
129
+ return False
130
+
131
+ except Exception as e:
132
+ print(f"❌ Registration error: {e}")
133
+ return False
134
+
135
+ async def move_player(self, direction: str):
136
+ """Move the player in the specified direction"""
137
+ if not self._check_ready():
138
+ return False
139
+
140
+ direction = direction.lower()
141
+ valid_directions = ['north', 'south', 'east', 'west', 'up', 'down']
142
+
143
+ if direction not in valid_directions:
144
+ print(f"❌ Invalid direction. Use: {', '.join(valid_directions)}")
145
+ return False
146
+
147
+ try:
148
+ # Find the move tool
149
+ move_tool = self._find_tool(['move', 'agent'])
150
+ if not move_tool:
151
+ print("❌ Move tool not available")
152
+ return False
153
+
154
+ # Execute the move
155
+ result = await self.session.call_tool(
156
+ move_tool.name,
157
+ {"client_id": self.client_id, "direction": direction}
158
+ )
159
+
160
+ content = self._extract_content(result)
161
+ print(f"🚶 Move {direction}: {content}")
162
+ return True
163
+
164
+ except Exception as e:
165
+ print(f"❌ Move error: {e}")
166
+ return False
167
+
168
+ async def send_chat(self, message: str):
169
+ """Send a chat message to other players"""
170
+ if not self._check_ready():
171
+ return False
172
+
173
+ if not message.strip():
174
+ print("❌ Cannot send empty message")
175
+ return False
176
+
177
+ try:
178
+ # Find the chat tool
179
+ chat_tool = self._find_tool(['chat', 'send'])
180
+ if not chat_tool:
181
+ print("❌ Chat tool not available")
182
+ return False
183
+
184
+ # Send the chat message
185
+ result = await self.session.call_tool(
186
+ chat_tool.name,
187
+ {"client_id": self.client_id, "message": message}
188
+ )
189
+
190
+ content = self._extract_content(result)
191
+ print(f"💬 Chat sent: {content}")
192
+ return True
193
+
194
+ except Exception as e:
195
+ print(f"❌ Chat error: {e}")
196
+ return False
197
+
198
+ async def get_game_state(self):
199
+ """Get current game state information"""
200
+ if not self.connected:
201
+ print("❌ Not connected to server")
202
+ return None
203
+
204
+ try:
205
+ # Find the game state tool
206
+ state_tool = self._find_tool(['state', 'game'])
207
+ if not state_tool:
208
+ print("❌ Game state tool not available")
209
+ return None
210
+
211
+ # Get game state
212
+ result = await self.session.call_tool(state_tool.name, {})
213
+ content = self._extract_content(result)
214
+
215
+ try:
216
+ # Try to parse as JSON
217
+ game_state = json.loads(content)
218
+ return game_state
219
+ except json.JSONDecodeError:
220
+ # Return as text if not JSON
221
+ return {"info": content}
222
+
223
+ except Exception as e:
224
+ print(f"❌ Game state error: {e}")
225
+ return None
226
+
227
+ async def interact_with_npc(self, npc_id: str, message: str):
228
+ """Interact with an NPC"""
229
+ if not self._check_ready():
230
+ return False
231
+
232
+ if not npc_id.strip() or not message.strip():
233
+ print("❌ NPC ID and message are required")
234
+ return False
235
+
236
+ try:
237
+ # Find the interact tool
238
+ interact_tool = self._find_tool(['interact', 'npc'])
239
+ if not interact_tool:
240
+ print("❌ NPC interaction tool not available")
241
+ return False
242
+
243
+ # Interact with NPC
244
+ result = await self.session.call_tool(
245
+ interact_tool.name,
246
+ {"client_id": self.client_id, "npc_id": npc_id, "message": message}
247
+ )
248
+
249
+ content = self._extract_content(result)
250
+ print(f"🤖 {npc_id}: {content}")
251
+ return True
252
+
253
+ except Exception as e:
254
+ print(f"❌ NPC interaction error: {e}")
255
+ return False
256
+
257
+ async def list_tools(self):
258
+ """List all available tools on the server"""
259
+ if not self.connected:
260
+ print("❌ Not connected to server")
261
+ return
262
+
263
+ print("🛠️ Available Tools:")
264
+ for i, tool in enumerate(self.tools, 1):
265
+ print(f" {i}. {tool.name}")
266
+ if hasattr(tool, 'description') and tool.description:
267
+ print(f" Description: {tool.description}")
268
+
269
+ def _find_tool(self, keywords: List[str]):
270
+ """Find a tool by keywords"""
271
+ for keyword in keywords:
272
+ tool = next((t for t in self.tools if keyword in t.name.lower()), None)
273
+ if tool:
274
+ return tool
275
+ return None
276
+
277
+ def _extract_content(self, result):
278
+ """Extract content from MCP result"""
279
+ try:
280
+ if hasattr(result, 'content') and result.content:
281
+ if isinstance(result.content, list):
282
+ content_parts = []
283
+ for item in result.content:
284
+ if hasattr(item, 'text'):
285
+ content_parts.append(item.text)
286
+ else:
287
+ content_parts.append(str(item))
288
+ return ''.join(content_parts)
289
+ elif hasattr(result.content, 'text'):
290
+ return result.content.text
291
+ else:
292
+ return str(result.content)
293
+ return str(result)
294
+ except Exception as e:
295
+ return f"Error extracting content: {e}"
296
+
297
+ def _parse_agent_id(self, content: str):
298
+ """Try to extract agent ID from response"""
299
+ lines = content.split('\n')
300
+ for line in lines:
301
+ if any(keyword in line.lower() for keyword in ['agent_id', 'id:', 'player id']):
302
+ parts = line.split(':')
303
+ if len(parts) > 1:
304
+ self.agent_id = parts[1].strip()
305
+ print(f"🆔 Agent ID: {self.agent_id}")
306
+ break
307
+
308
+ def _check_ready(self):
309
+ """Check if client is ready for game operations"""
310
+ if not self.connected:
311
+ print("❌ Not connected to server")
312
+ return False
313
+ if not self.player_name:
314
+ print("❌ Not registered as a player")
315
+ return False
316
+ return True
317
+
318
+ async def disconnect(self):
319
+ """Disconnect from the server"""
320
+ if self.exit_stack:
321
+ await self.exit_stack.aclose()
322
+ self.connected = False
323
+ self.player_name = None
324
+ self.agent_id = None
325
+ print("🔌 Disconnected from server")
326
+
327
+
328
+ class InteractiveGameClient:
329
+ """Interactive console interface for the game client"""
330
+
331
+ def __init__(self):
332
+ self.client = SimpleGameClient()
333
+ self.running = True
334
+
335
+ async def start(self):
336
+ """Start the interactive client"""
337
+ print("🎮 MMORPG Simple Game Client")
338
+ print("=" * 40)
339
+ self.show_help()
340
+
341
+ while self.running:
342
+ try:
343
+ command = input("\n> ").strip()
344
+ if command:
345
+ await self.process_command(command)
346
+ except KeyboardInterrupt:
347
+ print("\n\n👋 Goodbye!")
348
+ break
349
+ except EOFError:
350
+ break
351
+
352
+ await self.client.disconnect()
353
+
354
+ async def process_command(self, command: str):
355
+ """Process a user command"""
356
+ parts = command.split()
357
+ if not parts:
358
+ return
359
+
360
+ cmd = parts[0].lower()
361
+ args = parts[1:]
362
+
363
+ if cmd == "help":
364
+ self.show_help()
365
+
366
+ elif cmd == "connect":
367
+ await self.client.connect()
368
+
369
+ elif cmd == "register":
370
+ if args:
371
+ name = " ".join(args)
372
+ await self.client.register_player(name)
373
+ else:
374
+ print("❌ Usage: register <player_name>")
375
+
376
+ elif cmd == "move":
377
+ if args:
378
+ direction = args[0]
379
+ await self.client.move_player(direction)
380
+ else:
381
+ print("❌ Usage: move <direction>")
382
+ print(" Directions: north, south, east, west")
383
+
384
+ elif cmd == "chat":
385
+ if args:
386
+ message = " ".join(args)
387
+ await self.client.send_chat(message)
388
+ else:
389
+ print("❌ Usage: chat <message>")
390
+
391
+ elif cmd == "state":
392
+ state = await self.client.get_game_state()
393
+ if state:
394
+ print("📊 Game State:")
395
+ print(json.dumps(state, indent=2))
396
+
397
+ elif cmd == "npc":
398
+ if len(args) >= 2:
399
+ npc_id = args[0]
400
+ message = " ".join(args[1:])
401
+ await self.client.interact_with_npc(npc_id, message)
402
+ else:
403
+ print("❌ Usage: npc <npc_id> <message>")
404
+ print(" Example: npc weather_oracle What's the weather in Berlin?")
405
+
406
+ elif cmd == "tools":
407
+ await self.client.list_tools()
408
+
409
+ elif cmd in ["quit", "exit", "q"]:
410
+ self.running = False
411
+
412
+ else:
413
+ print(f"❓ Unknown command: {cmd}")
414
+ print(" Type 'help' for available commands")
415
+
416
+ def show_help(self):
417
+ """Show available commands"""
418
+ print("\n📋 Available Commands:")
419
+ print(" help - Show this help")
420
+ print(" connect - Connect to game server")
421
+ print(" register <name> - Register as a player")
422
+ print(" move <direction> - Move player (north/south/east/west)")
423
+ print(" chat <message> - Send chat message")
424
+ print(" npc <id> <message> - Talk to an NPC")
425
+ print(" state - Get game state")
426
+ print(" tools - List available server tools")
427
+ print(" quit - Exit the client")
428
+ print("\n💡 Example session:")
429
+ print(" > connect")
430
+ print(" > register MyPlayer")
431
+ print(" > move north")
432
+ print(" > chat Hello everyone!")
433
+ print(" > npc weather_oracle What's the weather?")
434
+
435
+
436
+ async def quick_demo():
437
+ """Quick demo showing basic functionality"""
438
+ print("🚀 Running Quick Demo...")
439
+
440
+ client = SimpleGameClient()
441
+
442
+ # Connect
443
+ if not await client.connect():
444
+ print("❌ Demo failed - could not connect")
445
+ return
446
+
447
+ # Register
448
+ if not await client.register_player("DemoPlayer"):
449
+ print("❌ Demo failed - could not register")
450
+ return
451
+
452
+ # Send greeting
453
+ await client.send_chat("Hello! Demo player here!")
454
+
455
+ # Move around
456
+ moves = ["north", "east", "south", "west"]
457
+ for direction in moves:
458
+ await client.move_player(direction)
459
+ await asyncio.sleep(1)
460
+
461
+ # Get state
462
+ state = await client.get_game_state()
463
+ if state:
464
+ print(f"📊 Current game state: {json.dumps(state, indent=2)}")
465
+
466
+ # Try NPC interaction
467
+ await client.interact_with_npc("weather_oracle", "Hello there!")
468
+
469
+ await client.disconnect()
470
+ print("✅ Demo completed!")
471
+
472
+
473
+ def main():
474
+ """Main entry point"""
475
+ if len(sys.argv) > 1 and sys.argv[1] == "demo":
476
+ # Run quick demo
477
+ asyncio.run(quick_demo())
478
+ else:
479
+ # Run interactive client
480
+ client = InteractiveGameClient()
481
+ asyncio.run(client.start())
482
+
483
+
484
+ if __name__ == "__main__":
485
+ main()
Simple_Game_Client_Guide.md ADDED
@@ -0,0 +1,744 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Simple Game Client Guide
2
+
3
+ This guide shows you how to create a simple client to connect to the MMORPG game server using the Model Context Protocol (MCP). No LLM integration required - just basic game operations.
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [Overview](#overview)
8
+ 2. [Prerequisites](#prerequisites)
9
+ 3. [MCP Server Connection](#mcp-server-connection)
10
+ 4. [Basic Client Example](#basic-client-example)
11
+ 5. [Available Game Operations](#available-game-operations)
12
+ 6. [Command Reference](#command-reference)
13
+ 7. [Error Handling](#error-handling)
14
+ 8. [Advanced Features](#advanced-features)
15
+
16
+ ---
17
+
18
+ ## Overview
19
+
20
+ The MMORPG game server exposes its functionality through MCP (Model Context Protocol), allowing external clients to:
21
+
22
+ - 🎮 **Connect** to the game server
23
+ - 👤 **Join/Leave** the game as a player
24
+ - 🗺️ **Move** around the game world
25
+ - 💬 **Send chat** messages to other players
26
+ - 🎯 **Interact** with NPCs and game objects
27
+ - 📊 **Get game state** information
28
+
29
+ ### Game Server Details
30
+
31
+ - **MCP Endpoint**: `http://localhost:7868/gradio_api/mcp/sse` (when running locally)
32
+ - **Protocol**: Server-Sent Events (SSE) over HTTP
33
+ - **Authentication**: None required for basic operations
34
+ - **Data Format**: JSON messages following MCP specification
35
+
36
+ ---
37
+
38
+ ## Prerequisites
39
+
40
+ Before building your client, ensure you have:
41
+
42
+ ### Required Dependencies
43
+
44
+ ```bash
45
+ pip install mcp asyncio aiohttp contextlib
46
+ ```
47
+
48
+ ### Python Version
49
+ - Python 3.8 or higher
50
+ - Support for async/await syntax
51
+
52
+ ### Game Server Running
53
+ Make sure the MMORPG game server is running:
54
+
55
+ ```bash
56
+ cd c:/Users/Chris4K/Projekte/projecthub/projects/MMOP_second_try
57
+ python app.py
58
+ ```
59
+
60
+ The server should be accessible at `http://localhost:7868` (UI) and the MCP endpoint at `http://localhost:7868/gradio_api/mcp/sse`.
61
+
62
+ ---
63
+
64
+ ## MCP Server Connection
65
+
66
+ Here's how to establish a connection to the game server:
67
+
68
+ ### Basic Connection Setup
69
+
70
+ ```python
71
+ import asyncio
72
+ from mcp import ClientSession
73
+ from mcp.client.sse import sse_client
74
+ from contextlib import AsyncExitStack
75
+
76
+ class GameClient:
77
+ def __init__(self, server_url="http://localhost:7868/gradio_api/mcp/sse"):
78
+ self.server_url = server_url
79
+ self.session = None
80
+ self.connected = False
81
+ self.tools = []
82
+ self.exit_stack = None
83
+ self.client_id = None
84
+
85
+ async def connect(self):
86
+ """Connect to the game server"""
87
+ try:
88
+ # Clean up any existing connection
89
+ if self.exit_stack:
90
+ await self.exit_stack.aclose()
91
+
92
+ self.exit_stack = AsyncExitStack()
93
+
94
+ # Establish SSE connection
95
+ transport = await self.exit_stack.enter_async_context(
96
+ sse_client(self.server_url)
97
+ )
98
+ read_stream, write_callable = transport
99
+
100
+ # Create MCP session
101
+ self.session = await self.exit_stack.enter_async_context(
102
+ ClientSession(read_stream, write_callable)
103
+ )
104
+
105
+ # Initialize the session
106
+ await self.session.initialize()
107
+
108
+ # Get available tools/commands
109
+ response = await self.session.list_tools()
110
+ self.tools = response.tools
111
+
112
+ self.connected = True
113
+ print(f"✅ Connected to game server!")
114
+ print(f"Available commands: {[tool.name for tool in self.tools]}")
115
+
116
+ return True
117
+
118
+ except Exception as e:
119
+ print(f"❌ Connection failed: {e}")
120
+ self.connected = False
121
+ return False
122
+
123
+ async def disconnect(self):
124
+ """Disconnect from the game server"""
125
+ if self.exit_stack:
126
+ await self.exit_stack.aclose()
127
+ self.connected = False
128
+ print("🔌 Disconnected from game server")
129
+ ```
130
+
131
+ ### Connection Test
132
+
133
+ ```python
134
+ async def test_connection():
135
+ client = GameClient()
136
+ if await client.connect():
137
+ print("Connection successful!")
138
+ await client.disconnect()
139
+ else:
140
+ print("Connection failed!")
141
+
142
+ # Run the test
143
+ asyncio.run(test_connection())
144
+ ```
145
+
146
+ ---
147
+
148
+ ## Basic Client Example
149
+
150
+ Here's a complete working client that can perform basic game operations:
151
+
152
+ ```python
153
+ import asyncio
154
+ import json
155
+ import uuid
156
+ from typing import Dict, List, Optional
157
+
158
+ class SimpleGameClient:
159
+ def __init__(self, server_url="http://localhost:7868/gradio_api/mcp/sse"):
160
+ self.server_url = server_url
161
+ self.session = None
162
+ self.connected = False
163
+ self.tools = []
164
+ self.exit_stack = None
165
+ self.client_id = str(uuid.uuid4())[:8]
166
+ self.agent_id = None
167
+ self.player_name = None
168
+
169
+ async def connect(self):
170
+ """Connect to the game server"""
171
+ try:
172
+ if self.exit_stack:
173
+ await self.exit_stack.aclose()
174
+
175
+ self.exit_stack = AsyncExitStack()
176
+
177
+ transport = await self.exit_stack.enter_async_context(
178
+ sse_client(self.server_url)
179
+ )
180
+ read_stream, write_callable = transport
181
+
182
+ self.session = await self.exit_stack.enter_async_context(
183
+ ClientSession(read_stream, write_callable)
184
+ )
185
+
186
+ await self.session.initialize()
187
+ response = await self.session.list_tools()
188
+ self.tools = response.tools
189
+
190
+ self.connected = True
191
+ return True
192
+
193
+ except Exception as e:
194
+ print(f"❌ Connection failed: {e}")
195
+ return False
196
+
197
+ async def register_player(self, player_name: str):
198
+ """Register as a new player in the game"""
199
+ if not self.connected:
200
+ print("❌ Not connected to server")
201
+ return False
202
+
203
+ try:
204
+ # Find the register tool
205
+ register_tool = next((t for t in self.tools if 'register' in t.name), None)
206
+ if not register_tool:
207
+ print("❌ Register tool not available")
208
+ return False
209
+
210
+ # Register the player
211
+ result = await self.session.call_tool(
212
+ register_tool.name,
213
+ {"name": player_name, "client_id": self.client_id}
214
+ )
215
+
216
+ # Parse the result
217
+ content = self._extract_content(result)
218
+ if "registered" in content.lower():
219
+ self.player_name = player_name
220
+ # Extract agent_id from response if available
221
+ if "agent_id" in content or "ID:" in content:
222
+ # Parse agent ID from response
223
+ lines = content.split('\n')
224
+ for line in lines:
225
+ if "agent_id" in line.lower() or "id:" in line:
226
+ parts = line.split(':')
227
+ if len(parts) > 1:
228
+ self.agent_id = parts[1].strip()
229
+ break
230
+
231
+ print(f"✅ Registered as player: {player_name}")
232
+ return True
233
+ else:
234
+ print(f"❌ Registration failed: {content}")
235
+ return False
236
+
237
+ except Exception as e:
238
+ print(f"❌ Registration error: {e}")
239
+ return False
240
+
241
+ async def move_player(self, direction: str):
242
+ """Move the player in the specified direction"""
243
+ if not self.connected or not self.agent_id:
244
+ print("❌ Not connected or not registered")
245
+ return False
246
+
247
+ try:
248
+ # Find the move tool
249
+ move_tool = next((t for t in self.tools if 'move' in t.name), None)
250
+ if not move_tool:
251
+ print("❌ Move tool not available")
252
+ return False
253
+
254
+ # Move the player
255
+ result = await self.session.call_tool(
256
+ move_tool.name,
257
+ {"client_id": self.client_id, "direction": direction}
258
+ )
259
+
260
+ content = self._extract_content(result)
261
+ print(f"🚶 Move result: {content}")
262
+ return True
263
+
264
+ except Exception as e:
265
+ print(f"❌ Move error: {e}")
266
+ return False
267
+
268
+ async def send_chat(self, message: str):
269
+ """Send a chat message to other players"""
270
+ if not self.connected or not self.agent_id:
271
+ print("❌ Not connected or not registered")
272
+ return False
273
+
274
+ try:
275
+ # Find the chat tool
276
+ chat_tool = next((t for t in self.tools if 'chat' in t.name), None)
277
+ if not chat_tool:
278
+ print("❌ Chat tool not available")
279
+ return False
280
+
281
+ # Send the chat message
282
+ result = await self.session.call_tool(
283
+ chat_tool.name,
284
+ {"client_id": self.client_id, "message": message}
285
+ )
286
+
287
+ content = self._extract_content(result)
288
+ print(f"💬 Chat sent: {content}")
289
+ return True
290
+
291
+ except Exception as e:
292
+ print(f"❌ Chat error: {e}")
293
+ return False
294
+
295
+ async def get_game_state(self):
296
+ """Get current game state information"""
297
+ if not self.connected:
298
+ print("❌ Not connected to server")
299
+ return None
300
+
301
+ try:
302
+ # Find the game state tool
303
+ state_tool = next((t for t in self.tools if 'state' in t.name or 'game' in t.name), None)
304
+ if not state_tool:
305
+ print("❌ Game state tool not available")
306
+ return None
307
+
308
+ # Get game state
309
+ result = await self.session.call_tool(state_tool.name, {})
310
+ content = self._extract_content(result)
311
+
312
+ try:
313
+ # Try to parse as JSON
314
+ game_state = json.loads(content)
315
+ return game_state
316
+ except json.JSONDecodeError:
317
+ # Return as text if not JSON
318
+ return {"info": content}
319
+
320
+ except Exception as e:
321
+ print(f"❌ Game state error: {e}")
322
+ return None
323
+
324
+ async def interact_with_npc(self, npc_id: str, message: str):
325
+ """Interact with an NPC"""
326
+ if not self.connected or not self.agent_id:
327
+ print("❌ Not connected or not registered")
328
+ return False
329
+
330
+ try:
331
+ # Find the interact tool
332
+ interact_tool = next((t for t in self.tools if 'interact' in t.name or 'npc' in t.name), None)
333
+ if not interact_tool:
334
+ print("❌ Interact tool not available")
335
+ return False
336
+
337
+ # Interact with NPC
338
+ result = await self.session.call_tool(
339
+ interact_tool.name,
340
+ {"client_id": self.client_id, "npc_id": npc_id, "message": message}
341
+ )
342
+
343
+ content = self._extract_content(result)
344
+ print(f"🤖 NPC {npc_id}: {content}")
345
+ return True
346
+
347
+ except Exception as e:
348
+ print(f"❌ Interaction error: {e}")
349
+ return False
350
+
351
+ def _extract_content(self, result):
352
+ """Extract content from MCP result"""
353
+ if hasattr(result, 'content') and result.content:
354
+ if isinstance(result.content, list):
355
+ return ''.join(str(item) for item in result.content)
356
+ return str(result.content)
357
+ return str(result)
358
+
359
+ async def disconnect(self):
360
+ """Disconnect from the server"""
361
+ if self.exit_stack:
362
+ await self.exit_stack.aclose()
363
+ self.connected = False
364
+ print("🔌 Disconnected from server")
365
+
366
+ # Import required modules at the top
367
+ from mcp import ClientSession
368
+ from mcp.client.sse import sse_client
369
+ from contextlib import AsyncExitStack
370
+ ```
371
+
372
+ ---
373
+
374
+ ## Available Game Operations
375
+
376
+ The game server exposes these main operations through MCP tools:
377
+
378
+ ### 1. Player Management
379
+ - **`register_ai_agent`** - Register a new player
380
+ - **`move_agent`** - Move player in game world
381
+ - **`get_game_state`** - Get current world state
382
+
383
+ ### 2. Communication
384
+ - **`send_chat`** - Send chat messages
385
+ - **`interact_with_npc`** - Talk to NPCs
386
+
387
+ ### 3. Game Information
388
+ - **`get_game_state`** - World state and player list
389
+ - **Player proximity** - Detect nearby players/NPCs
390
+
391
+ ### Tool Parameters
392
+
393
+ Each tool expects specific parameters:
394
+
395
+ ```python
396
+ # Register Player
397
+ {
398
+ "name": "PlayerName",
399
+ "client_id": "unique_client_id"
400
+ }
401
+
402
+ # Move Player
403
+ {
404
+ "client_id": "your_client_id",
405
+ "direction": "north|south|east|west"
406
+ }
407
+
408
+ # Send Chat
409
+ {
410
+ "client_id": "your_client_id",
411
+ "message": "Hello world!"
412
+ }
413
+
414
+ # Interact with NPC
415
+ {
416
+ "client_id": "your_client_id",
417
+ "npc_id": "npc_identifier",
418
+ "message": "Your message to NPC"
419
+ }
420
+ ```
421
+
422
+ ---
423
+
424
+ ## Command Reference
425
+
426
+ ### Basic Usage Example
427
+
428
+ ```python
429
+ async def main():
430
+ # Create and connect client
431
+ client = SimpleGameClient()
432
+
433
+ if not await client.connect():
434
+ print("Failed to connect!")
435
+ return
436
+
437
+ # Register as a player
438
+ if not await client.register_player("MyPlayer"):
439
+ print("Failed to register!")
440
+ return
441
+
442
+ # Get current game state
443
+ state = await client.get_game_state()
444
+ print(f"Game state: {state}")
445
+
446
+ # Move around
447
+ await client.move_player("north")
448
+ await client.move_player("east")
449
+
450
+ # Send a chat message
451
+ await client.send_chat("Hello everyone!")
452
+
453
+ # Interact with an NPC
454
+ await client.interact_with_npc("weather_oracle", "What's the weather in Berlin?")
455
+
456
+ # Disconnect
457
+ await client.disconnect()
458
+
459
+ # Run the client
460
+ asyncio.run(main())
461
+ ```
462
+
463
+ ### Interactive Console Client
464
+
465
+ ```python
466
+ async def interactive_client():
467
+ client = SimpleGameClient()
468
+
469
+ print("🎮 MMORPG Simple Client")
470
+ print("Commands: connect, register <name>, move <direction>, chat <message>, state, quit")
471
+
472
+ while True:
473
+ command = input("> ").strip().split()
474
+
475
+ if not command:
476
+ continue
477
+
478
+ cmd = command[0].lower()
479
+
480
+ if cmd == "connect":
481
+ if await client.connect():
482
+ print("✅ Connected!")
483
+ else:
484
+ print("❌ Connection failed!")
485
+
486
+ elif cmd == "register" and len(command) > 1:
487
+ name = " ".join(command[1:])
488
+ if await client.register_player(name):
489
+ print(f"✅ Registered as {name}")
490
+ else:
491
+ print("❌ Registration failed!")
492
+
493
+ elif cmd == "move" and len(command) > 1:
494
+ direction = command[1]
495
+ await client.move_player(direction)
496
+
497
+ elif cmd == "chat" and len(command) > 1:
498
+ message = " ".join(command[1:])
499
+ await client.send_chat(message)
500
+
501
+ elif cmd == "state":
502
+ state = await client.get_game_state()
503
+ print(f"📊 Game State: {json.dumps(state, indent=2)}")
504
+
505
+ elif cmd == "npc" and len(command) > 2:
506
+ npc_id = command[1]
507
+ message = " ".join(command[2:])
508
+ await client.interact_with_npc(npc_id, message)
509
+
510
+ elif cmd == "quit":
511
+ await client.disconnect()
512
+ break
513
+
514
+ else:
515
+ print("❓ Unknown command")
516
+
517
+ # Run interactive client
518
+ asyncio.run(interactive_client())
519
+ ```
520
+
521
+ ---
522
+
523
+ ## Error Handling
524
+
525
+ ### Common Issues and Solutions
526
+
527
+ #### 1. Connection Errors
528
+ ```python
529
+ async def robust_connect(client, max_retries=3):
530
+ for attempt in range(max_retries):
531
+ try:
532
+ if await client.connect():
533
+ return True
534
+ print(f"Attempt {attempt + 1} failed, retrying...")
535
+ await asyncio.sleep(2)
536
+ except Exception as e:
537
+ print(f"Connection attempt {attempt + 1} error: {e}")
538
+
539
+ print("❌ All connection attempts failed")
540
+ return False
541
+ ```
542
+
543
+ #### 2. Tool Not Available
544
+ ```python
545
+ def find_tool_safe(tools, keywords):
546
+ """Safely find a tool by keywords"""
547
+ for keyword in keywords:
548
+ tool = next((t for t in tools if keyword in t.name.lower()), None)
549
+ if tool:
550
+ return tool
551
+ return None
552
+
553
+ # Usage
554
+ move_tool = find_tool_safe(client.tools, ['move', 'agent', 'player'])
555
+ if not move_tool:
556
+ print("❌ No movement tool available")
557
+ ```
558
+
559
+ #### 3. Response Parsing
560
+ ```python
561
+ def safe_parse_response(result):
562
+ """Safely parse MCP response"""
563
+ try:
564
+ content = client._extract_content(result)
565
+
566
+ # Try JSON first
567
+ try:
568
+ return json.loads(content)
569
+ except json.JSONDecodeError:
570
+ # Return as structured text
571
+ return {"message": content, "type": "text"}
572
+
573
+ except Exception as e:
574
+ return {"error": str(e), "type": "error"}
575
+ ```
576
+
577
+ ---
578
+
579
+ ## Advanced Features
580
+
581
+ ### 1. Automatic Reconnection
582
+
583
+ ```python
584
+ class RobustGameClient(SimpleGameClient):
585
+ def __init__(self, *args, **kwargs):
586
+ super().__init__(*args, **kwargs)
587
+ self.auto_reconnect = True
588
+ self.reconnect_delay = 5
589
+
590
+ async def _ensure_connected(self):
591
+ """Ensure connection is active, reconnect if needed"""
592
+ if not self.connected and self.auto_reconnect:
593
+ print("🔄 Attempting to reconnect...")
594
+ await self.connect()
595
+ if self.player_name:
596
+ await self.register_player(self.player_name)
597
+
598
+ async def move_player(self, direction):
599
+ await self._ensure_connected()
600
+ return await super().move_player(direction)
601
+ ```
602
+
603
+ ### 2. Event Monitoring
604
+
605
+ ```python
606
+ class EventMonitorClient(SimpleGameClient):
607
+ def __init__(self, *args, **kwargs):
608
+ super().__init__(*args, **kwargs)
609
+ self.event_handlers = {}
610
+
611
+ def on_chat_message(self, handler):
612
+ """Register handler for chat messages"""
613
+ self.event_handlers['chat'] = handler
614
+
615
+ def on_player_move(self, handler):
616
+ """Register handler for player movements"""
617
+ self.event_handlers['move'] = handler
618
+
619
+ async def poll_events(self):
620
+ """Poll for game events"""
621
+ while self.connected:
622
+ try:
623
+ state = await self.get_game_state()
624
+ # Process state changes and trigger handlers
625
+ # Implementation depends on game state format
626
+ await asyncio.sleep(1)
627
+ except Exception as e:
628
+ print(f"Event polling error: {e}")
629
+ await asyncio.sleep(5)
630
+ ```
631
+
632
+ ### 3. Batch Operations
633
+
634
+ ```python
635
+ async def batch_moves(client, moves):
636
+ """Execute multiple moves in sequence"""
637
+ for direction in moves:
638
+ result = await client.move_player(direction)
639
+ if not result:
640
+ print(f"❌ Failed to move {direction}")
641
+ break
642
+ await asyncio.sleep(0.5) # Small delay between moves
643
+
644
+ # Usage
645
+ await batch_moves(client, ["north", "north", "east", "south"])
646
+ ```
647
+
648
+ ### 4. Configuration Management
649
+
650
+ ```python
651
+ import json
652
+
653
+ class ConfigurableClient(SimpleGameClient):
654
+ def __init__(self, config_file="client_config.json"):
655
+ # Load configuration
656
+ try:
657
+ with open(config_file, 'r') as f:
658
+ config = json.load(f)
659
+ except FileNotFoundError:
660
+ config = self.default_config()
661
+
662
+ super().__init__(config.get('server_url', 'http://localhost:7860/gradio_api/mcp/sse'))
663
+ self.config = config
664
+
665
+ def default_config(self):
666
+ return {
667
+ "server_url": "http://localhost:7860/gradio_api/mcp/sse",
668
+ "player_name": "DefaultPlayer",
669
+ "auto_reconnect": True,
670
+ "move_delay": 0.5
671
+ }
672
+ ```
673
+
674
+ ---
675
+
676
+ ## Quick Start Script
677
+
678
+ Save this as `quick_client.py` for immediate testing:
679
+
680
+ ```python
681
+ #!/usr/bin/env python3
682
+ """
683
+ Quick Game Client - Connect and play immediately
684
+ Usage: python quick_client.py [player_name]
685
+ """
686
+
687
+ import asyncio
688
+ import sys
689
+ from simple_game_client import SimpleGameClient
690
+
691
+ async def quick_play(player_name="TestPlayer"):
692
+ client = SimpleGameClient()
693
+
694
+ print(f"🎮 Connecting as {player_name}...")
695
+
696
+ # Connect and register
697
+ if not await client.connect():
698
+ print("❌ Failed to connect!")
699
+ return
700
+
701
+ if not await client.register_player(player_name):
702
+ print("❌ Failed to register!")
703
+ return
704
+
705
+ print("✅ Connected and registered successfully!")
706
+
707
+ # Basic game session
708
+ await client.send_chat(f"Hello! {player_name} has joined the game!")
709
+
710
+ # Move around a bit
711
+ moves = ["north", "east", "south", "west"]
712
+ for move in moves:
713
+ print(f"🚶 Moving {move}...")
714
+ await client.move_player(move)
715
+ await asyncio.sleep(1)
716
+
717
+ # Get final state
718
+ state = await client.get_game_state()
719
+ print(f"📊 Final state: {state}")
720
+
721
+ await client.disconnect()
722
+ print("👋 Session ended!")
723
+
724
+ if __name__ == "__main__":
725
+ player_name = sys.argv[1] if len(sys.argv) > 1 else "TestPlayer"
726
+ asyncio.run(quick_play(player_name))
727
+ ```
728
+
729
+ Run with:
730
+ ```bash
731
+ python quick_client.py MyPlayerName
732
+ ```
733
+
734
+ ---
735
+
736
+ ## Next Steps
737
+
738
+ 1. **Customize the client** for your specific needs
739
+ 2. **Add game-specific features** like inventory management
740
+ 3. **Implement advanced AI** for automated gameplay
741
+ 4. **Create GUI interfaces** using tkinter, PyQt, or web frameworks
742
+ 5. **Build monitoring tools** for game statistics
743
+
744
+ This guide provides the foundation for any client application that needs to interact with the MMORPG game server!
USER_DOCUMENTATION.md ADDED
@@ -0,0 +1,907 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🎮 MMORPG with MCP Integration - User Documentation
2
+
3
+ ## 📋 Table of Contents
4
+
5
+ 1. [Overview](#overview)
6
+ 2. [Quick Start Guide](#quick-start-guide)
7
+ 3. [Core Features](#core-features)
8
+ 4. [User Interface Guide](#user-interface-guide)
9
+ 5. [Gameplay Mechanics](#gameplay-mechanics)
10
+ 6. [MCP Integration](#mcp-integration)
11
+ 7. [Developer & AI Agent Usage](#developer--ai-agent-usage)
12
+ 8. [NPC Addon System](#npc-addon-system)
13
+ 9. [Advanced Features](#advanced-features)
14
+ 10. [Troubleshooting](#troubleshooting)
15
+ 11. [API Reference](#api-reference)
16
+
17
+ ---
18
+
19
+ ## 📖 Overview
20
+
21
+ The **MMORPG with MCP Integration** is a real-time multiplayer online role-playing game that serves as both an entertaining game and a powerful platform for testing and developing Model Context Protocol (MCP) integrations. It combines traditional MMORPG elements with cutting-edge AI agent capabilities.
22
+
23
+ ### 🎯 What Makes This Special
24
+
25
+ - **🌍 Real-time Multiplayer**: Multiple human players can join simultaneously
26
+ - **🤖 AI Agent Support**: AI agents can connect and play alongside humans
27
+ - **🔌 MCP Integration**: Full Model Context Protocol support for extensibility
28
+ - **🧩 Addon System**: Extensible NPC addon architecture
29
+ - **⌨️ Keyboard Controls**: WASD and arrow key support for fluid gameplay
30
+ - **🔥 Secure Messaging**: Read2Burn self-destructing message system
31
+ - **🌤️ Live Services**: Real weather integration via MCP
32
+ - **🔧 Developer Platform**: Complete API for building custom integrations
33
+
34
+ ### 🚀 Use Cases
35
+
36
+ **For Gamers:**
37
+ - Experience a unique MMORPG with AI-powered NPCs
38
+ - Interact with real-time weather and other live services
39
+ - Send secure self-destructing messages to other players
40
+
41
+ **For Developers:**
42
+ - Test MCP server integrations in a real-world environment
43
+ - Develop custom NPC addons with rich functionality
44
+ - Build AI agents that can play and interact in the game
45
+ - Prototype multiplayer game mechanics
46
+
47
+ **For AI Researchers:**
48
+ - Study human-AI interaction in gaming environments
49
+ - Test AI agent behaviors in social multiplayer settings
50
+ - Develop and evaluate conversational AI systems
51
+
52
+ **For MCP Server Developers:**
53
+ - Test server implementations with real users
54
+ - Demonstrate MCP capabilities in an engaging context
55
+ - Debug and optimize server performance
56
+
57
+ ---
58
+
59
+ ## 🚀 Quick Start Guide
60
+
61
+ ### 1. Starting the Game
62
+
63
+ ```bash
64
+ # Navigate to the project directory
65
+ cd c:\Users\Chris4K\Projekte\projecthub\projects\MMOP_second_try
66
+
67
+ # Install dependencies (first time only)
68
+ pip install gradio mcp aiohttp asyncio
69
+
70
+ # Start the game server
71
+ python app.py
72
+ ```
73
+
74
+ The game will start and display:
75
+ ```
76
+ 🎮 Starting COMPLETE MMORPG with ALL FEATURES...
77
+ 🌐 Human players: Access via web interface
78
+ 🤖 AI agents: Connect via MCP API endpoints
79
+ ⌨️ Keyboard: Use WASD or Arrow Keys for movement
80
+ 🔌 MCP Tools available for AI integration
81
+ 🔥 Read2Burn mailbox for secure messaging
82
+ 🎯 All features working with deadlock fix!
83
+ * Running on local URL: http://127.0.0.1:7868
84
+ 🔨 MCP server (using SSE) running at: http://127.0.0.1:7868/gradio_api/mcp/sse
85
+ ```
86
+
87
+ ### 2. Joining as a Human Player
88
+
89
+ 1. Open your web browser and go to `http://127.0.0.1:7868`
90
+ 2. Navigate to the **🌍 Game World** tab
91
+ 3. Enter your desired player name in the "Player Name" field
92
+ 4. Click **"🎮 Join Game"**
93
+ 5. You'll see your character (🧝‍♂️) appear on the game map
94
+
95
+ ### 3. Basic Movement
96
+
97
+ **Web Interface:**
98
+ - Use the directional buttons: ↑ ↓ ← →
99
+
100
+ **Keyboard Controls:**
101
+ - **WASD Keys**: W (up), A (left), S (down), D (right)
102
+ - **Arrow Keys**: ↑ ↓ ← →
103
+ - **Space**: Action key (context-sensitive)
104
+
105
+ ### 4. First Interactions
106
+
107
+ **Chat with Others:**
108
+ 1. Go to the **💬 Chat** tab
109
+ 2. Type a message and press Enter
110
+ 3. All players will see your message
111
+
112
+ **Talk to NPCs:**
113
+ 1. Move close to an NPC on the map
114
+ 2. Go to the **🎯 Private Messages** tab
115
+ 3. Select the NPC from the dropdown
116
+ 4. Send them a message
117
+
118
+ ---
119
+
120
+ ## 🎮 Core Features
121
+
122
+ ### 🌍 Real-time Game World
123
+
124
+ **Map System:**
125
+ - **Size**: 500x400 pixel game world
126
+ - **Visual Style**: Fantasy forest theme with grid overlay
127
+ - **Real-time Updates**: All player movements update immediately
128
+ - **Collision Detection**: Prevents players from occupying same space
129
+
130
+ **Player Characters:**
131
+ - **Humans**: Represented by 🧝‍♂️ (elf character)
132
+ - **AI Agents**: Represented by 🤖 (robot character)
133
+ - **Name Tags**: Player names displayed above characters
134
+ - **Level Display**: Shows current player level
135
+
136
+ ### 👥 Multiplayer System
137
+
138
+ **Player Management:**
139
+ - **Concurrent Players**: Supports multiple simultaneous players
140
+ - **Unique Sessions**: Each player gets a unique session ID
141
+ - **Real-time Sync**: All actions sync across all connected clients
142
+ - **Player Stats**: Level, HP, gold, experience tracking
143
+
144
+ **Player Actions:**
145
+ - **Join/Leave**: Seamless game entry/exit
146
+ - **Movement**: 4-directional movement with smooth transitions
147
+ - **Chat**: Public chat visible to all players
148
+ - **Private Messaging**: Direct communication with NPCs
149
+
150
+ ### 🎯 NPC System
151
+
152
+ **Built-in NPCs:**
153
+ - **🧙‍♂️ Village Elder**: Wise questgiver and general information
154
+ - **⚔️ Warrior Trainer**: Combat training and warrior wisdom
155
+ - **🏪 Merchant**: Trade goods and equipment
156
+ - **🌟 Mystic Oracle**: Magical insights and prophecies
157
+ - **🚶‍♂️ Roaming Rick**: Wandering NPC with location updates
158
+ - **📮 Read2Burn Mailbox**: Secure message handling
159
+ - **🌤️ Weather Oracle**: Real-time weather information
160
+
161
+ **NPC Features:**
162
+ - **Personality System**: Each NPC has unique response patterns
163
+ - **Proximity Detection**: NPCs respond when players approach
164
+ - **Smart Responses**: Context-aware conversation handling
165
+ - **Addon Support**: Extensible with custom functionality
166
+
167
+ ### 💬 Communication Systems
168
+
169
+ **Public Chat:**
170
+ - **Real-time Messaging**: Instant message delivery
171
+ - **Player Identification**: Messages show sender name and type
172
+ - **Message History**: Scrollable chat history
173
+ - **Command Support**: Special commands like `/help`
174
+
175
+ **Private Messaging:**
176
+ - **NPC Communication**: Direct messages to any NPC
177
+ - **Secure Channels**: Private conversation channels
178
+ - **Response Handling**: NPCs can provide detailed responses
179
+ - **Message Logging**: Optional message history tracking
180
+
181
+ ---
182
+
183
+ ## 🖥️ User Interface Guide
184
+
185
+ ### 🌍 Game World Tab
186
+
187
+ **Main Game View:**
188
+ - **Game Map**: Central 500x400 pixel game world
189
+ - **Player Controls**: Movement buttons (↑ ↓ ← →)
190
+ - **Join/Leave**: Game participation controls
191
+ - **Auto-refresh**: Real-time world updates
192
+
193
+ **Player Information Panel:**
194
+ ```
195
+ 🟢 Connected | Player: YourName | Level: 1
196
+ HP: 100/100 | Gold: 50 | XP: 0/100
197
+ Position: (150, 200) | Session: abc12345...
198
+ Last Update: 14:30:15
199
+ ```
200
+
201
+ **Control Layout:**
202
+ ```
203
+ Player Name: [_____________]
204
+ [🎮 Join Game] [👋 Leave Game]
205
+
206
+ [↑]
207
+ [←] [⚔️] [→]
208
+ [↓]
209
+ ```
210
+
211
+ ### 💬 Chat Tab
212
+
213
+ **Interface Elements:**
214
+ - **Message Input**: Text field for typing messages
215
+ - **Send Button**: Submit messages to all players
216
+ - **Chat History**: Scrollable message display
217
+ - **Auto-scroll**: Automatically shows latest messages
218
+
219
+ **Message Format:**
220
+ ```
221
+ [14:30] 👤 PlayerName: Hello everyone!
222
+ [14:31] 🤖 AIAgent: Greetings, fellow adventurers!
223
+ [14:32] 🌍 System: New player joined the world
224
+ ```
225
+
226
+ ### 🎯 Private Messages Tab
227
+
228
+ **NPC Selection:**
229
+ - **Dropdown Menu**: List of all available NPCs
230
+ - **NPC Status**: Shows which NPCs are online/available
231
+ - **Message Input**: Text field for private messages
232
+ - **Response Display**: Shows NPC replies
233
+
234
+ **Example Interaction:**
235
+ ```
236
+ To: 🌤️ Weather Oracle
237
+ Message: What's the weather in Berlin?
238
+
239
+ 🌤️ Weather Oracle: 🌍 The spirits speak of Berlin, Germany:
240
+ ☀️ Clear skies with 18°C
241
+ 💨 Light winds from the northwest
242
+ 🌡️ Feels like 19°C
243
+ Perfect weather for outdoor adventures!
244
+ ```
245
+
246
+ ### 📊 Player Stats Tab
247
+
248
+ **Detailed Statistics:**
249
+ - **Basic Info**: Name, level, type (human/AI)
250
+ - **Combat Stats**: HP, maximum HP, combat level
251
+ - **Resources**: Gold pieces, experience points
252
+ - **Location**: Current X,Y coordinates
253
+ - **Session Info**: Connection time, session ID
254
+
255
+ ### 🔥 Read2Burn Mailbox Tab
256
+
257
+ **Secure Messaging Interface:**
258
+ - **Create Message**: Compose self-destructing messages
259
+ - **Message Management**: View your active messages
260
+ - **Read Messages**: Access messages sent to you
261
+ - **Security Settings**: Configure burn timers and read limits
262
+
263
+ **Usage Example:**
264
+ ```
265
+ Create Message:
266
+ Content: [Secret meeting at coordinates 200,150]
267
+ [📮 Create Secure Message]
268
+
269
+ Result:
270
+ ✅ Message Created Successfully!
271
+ 📝 Message ID: AbC123Xyz789
272
+ 🔗 Share this ID with the recipient
273
+ ⏰ Expires in 24 hours
274
+ 🔥 Burns after 1 read
275
+ ```
276
+
277
+ ### 🌤️ Weather Oracle Tab
278
+
279
+ **Weather Service Interface:**
280
+ - **Location Input**: City, Country format
281
+ - **Get Weather Button**: Fetch current conditions
282
+ - **Weather Display**: Formatted weather information
283
+ - **Examples**: Pre-filled location examples
284
+
285
+ **Weather Response Format:**
286
+ ```
287
+ 🌍 Weather for Berlin, Germany:
288
+ 🌡️ Temperature: 18°C (feels like 19°C)
289
+ ☀️ Conditions: Clear skies
290
+ 💨 Wind: 12 km/h northwest
291
+ 💧 Humidity: 65%
292
+ 👁️ Visibility: 10 km
293
+ ```
294
+
295
+ ---
296
+
297
+ ## 🎮 Gameplay Mechanics
298
+
299
+ ### 🚶‍♂️ Movement System
300
+
301
+ **Movement Controls:**
302
+ - **Web Buttons**: Click directional arrows
303
+ - **Keyboard**: WASD or arrow keys
304
+ - **Movement Speed**: Fixed speed with smooth transitions
305
+ - **Boundary Checking**: Prevents movement outside game world
306
+
307
+ **Movement Feedback:**
308
+ - **Visual**: Character position updates immediately
309
+ - **Audio**: Optional movement sound effects
310
+ - **Chat**: Movement can trigger proximity events
311
+ - **Stats**: Position coordinates update in real-time
312
+
313
+ ### 📈 Character Progression
314
+
315
+ **Experience System:**
316
+ - **Starting Level**: All players begin at level 1
317
+ - **Experience Gain**: Earn XP through various activities
318
+ - **Level Up**: Automatic progression when XP threshold reached
319
+ - **Benefits**: Higher levels unlock new features
320
+
321
+ **Resource Management:**
322
+ - **Health Points (HP)**: Combat and survival metric
323
+ - **Gold**: In-game currency for transactions
324
+ - **Experience (XP)**: Progression tracking
325
+ - **Inventory**: Items and equipment (future feature)
326
+
327
+ ### 🎯 Interaction System
328
+
329
+ **NPC Interaction:**
330
+ - **Proximity-based**: NPCs respond when players are nearby
331
+ - **Message-based**: Send direct messages to any NPC
332
+ - **Context-aware**: NPCs provide relevant responses
333
+ - **Addon-powered**: Enhanced NPCs with special abilities
334
+
335
+ **Player Interaction:**
336
+ - **Public Chat**: Communicate with all players
337
+ - **Private Messages**: Future feature for player-to-player
338
+ - **Collaborative**: Work together on quests and challenges
339
+ - **Social**: Build friendships and alliances
340
+
341
+ ### 🌍 World Events
342
+
343
+ **Dynamic Events:**
344
+ - **Player Join/Leave**: Announced to all players
345
+ - **NPC Activities**: Roaming NPCs change positions
346
+ - **Weather Updates**: Real-time weather changes
347
+ - **System Messages**: Important game announcements
348
+
349
+ **Event Examples:**
350
+ ```
351
+ 🌍 PlayerName has joined the adventure!
352
+ 🚶‍♂️ Roaming Rick moved to the village square
353
+ 🌤️ Weather updated for current location
354
+ ⚔️ A new quest has become available
355
+ ```
356
+
357
+ ---
358
+
359
+ ## 🔌 MCP Integration
360
+
361
+ ### 🤖 AI Agent Connection
362
+
363
+ **MCP Endpoint:**
364
+ - **URL**: `http://127.0.0.1:7868/gradio_api/mcp/sse`
365
+ - **Protocol**: Server-Sent Events (SSE)
366
+ - **Format**: JSON messages following MCP specification
367
+
368
+ **Available MCP Tools:**
369
+ 1. **join_game**: Register as a player
370
+ 2. **leave_game**: Leave the game world
371
+ 3. **move_up_handler/move_down_handler/move_left_handler/move_right_handler**: Movement controls
372
+ 4. **handle_chat**: Send public chat messages
373
+ 5. **handle_private_message**: Send private messages to NPCs
374
+ 6. **register_test_ai_agent**: Register AI agent
375
+ 7. **execute_ai_action**: Execute AI agent actions
376
+ 8. **handle_mailbox_command**: Interact with Read2Burn system
377
+ 9. **handle_weather_request**: Get weather information
378
+ 10. **install_addon**: Add new NPC addons
379
+
380
+ ### 📡 MCP Tool Usage Examples
381
+
382
+ **Joining the Game:**
383
+ ```json
384
+ {
385
+ "tool": "join_game",
386
+ "parameters": {
387
+ "name": "MyAIAgent"
388
+ }
389
+ }
390
+ ```
391
+
392
+ **Moving Around:**
393
+ ```json
394
+ {
395
+ "tool": "move_up_handler",
396
+ "parameters": {}
397
+ }
398
+ ```
399
+
400
+ **Sending Chat:**
401
+ ```json
402
+ {
403
+ "tool": "handle_chat",
404
+ "parameters": {
405
+ "message": "Hello from AI agent!"
406
+ }
407
+ }
408
+ ```
409
+
410
+ **Talking to NPCs:**
411
+ ```json
412
+ {
413
+ "tool": "handle_private_message",
414
+ "parameters": {
415
+ "message": "What quests are available?"
416
+ }
417
+ }
418
+ ```
419
+
420
+ ### 🌐 MCP Server Development
421
+
422
+ **Creating Custom MCP Servers:**
423
+ 1. Implement MCP protocol specification
424
+ 2. Expose game-relevant tools
425
+ 3. Handle authentication (if required)
426
+ 4. Provide tool descriptions and schemas
427
+ 5. Test with the game platform
428
+
429
+ **Integration Process:**
430
+ 1. Start your MCP server
431
+ 2. Use the **install_addon** tool
432
+ 3. Provide server endpoint URL
433
+ 4. Configure addon settings
434
+ 5. Test functionality in-game
435
+
436
+ ---
437
+
438
+ ## 👨‍💻 Developer & AI Agent Usage
439
+
440
+ ### 🔧 Platform Capabilities
441
+
442
+ **MCP Testing Platform:**
443
+ - **Real-world Environment**: Test MCP servers with actual users
444
+ - **Interactive Feedback**: See how humans interact with your tools
445
+ - **Performance Monitoring**: Track server response times and errors
446
+ - **Integration Validation**: Verify MCP protocol compliance
447
+
448
+ **Development Benefits:**
449
+ - **Rapid Prototyping**: Quickly test new MCP server features
450
+ - **User Experience Testing**: Observe how users interact with your tools
451
+ - **Community Feedback**: Get input from real users
452
+ - **Documentation**: Generate examples from actual usage
453
+
454
+ ### 🤖 AI Agent Development
455
+
456
+ **Agent Capabilities:**
457
+ ```python
458
+ # Example AI agent implementation
459
+ class GameAIAgent:
460
+ def __init__(self, name):
461
+ self.name = name
462
+ self.mcp_client = MCPClient("http://127.0.0.1:7868/gradio_api/mcp/sse")
463
+
464
+ async def join_and_explore(self):
465
+ # Join the game
466
+ await self.mcp_client.call_tool("join_game", {"name": self.name})
467
+
468
+ # Explore the world
469
+ directions = ["move_up_handler", "move_right_handler",
470
+ "move_down_handler", "move_left_handler"]
471
+
472
+ for direction in directions:
473
+ await self.mcp_client.call_tool(direction, {})
474
+ await asyncio.sleep(1)
475
+
476
+ # Interact with NPCs
477
+ await self.mcp_client.call_tool("handle_private_message", {
478
+ "message": "Hello, I'm an AI explorer!"
479
+ })
480
+ ```
481
+
482
+ **Agent Use Cases:**
483
+ - **Game Testing**: Automated gameplay testing
484
+ - **NPC Interaction**: Test conversational AI systems
485
+ - **Multiplayer Simulation**: Simulate player populations
486
+ - **Data Collection**: Gather interaction data for research
487
+
488
+ ### 🧩 Custom NPC Development
489
+
490
+ **Addon Architecture:**
491
+ ```python
492
+ class CustomNPCAddon(NPCAddon):
493
+ @property
494
+ def addon_id(self) -> str:
495
+ return "my_custom_npc"
496
+
497
+ @property
498
+ def addon_name(self) -> str:
499
+ return "My Custom NPC"
500
+
501
+ def get_interface(self) -> gr.Component:
502
+ # Create Gradio interface
503
+ pass
504
+
505
+ def handle_command(self, player_id: str, command: str) -> str:
506
+ # Process player commands
507
+ return "Custom NPC response"
508
+ ```
509
+
510
+ **Development Steps:**
511
+ 1. Inherit from `NPCAddon` base class
512
+ 2. Implement required methods
513
+ 3. Create Gradio interface components
514
+ 4. Handle player commands and interactions
515
+ 5. Register addon with game world
516
+
517
+ ### 📊 Research Applications
518
+
519
+ **Human-AI Interaction Studies:**
520
+ - **Mixed Populations**: Study humans and AI agents together
521
+ - **Conversation Analysis**: Analyze chat patterns and interactions
522
+ - **Behavior Modeling**: Model player behavior patterns
523
+ - **Social Dynamics**: Understand multiplayer social structures
524
+
525
+ **MCP Protocol Research:**
526
+ - **Performance Analysis**: Measure protocol efficiency
527
+ - **Compatibility Testing**: Test different MCP implementations
528
+ - **Feature Evaluation**: Evaluate new MCP features
529
+ - **Integration Patterns**: Develop best practices
530
+
531
+ ---
532
+
533
+ ## 🧩 NPC Addon System
534
+
535
+ ### 📮 Read2Burn Mailbox
536
+
537
+ **Features:**
538
+ - **Self-destructing Messages**: Messages burn after reading
539
+ - **Expiration Timer**: 24-hour automatic expiration
540
+ - **Read Limits**: Configurable read count before burning
541
+ - **Access Logging**: Track message creation and access
542
+ - **Secure IDs**: Unique, hard-to-guess message identifiers
543
+
544
+ **Commands:**
545
+ - **create [message]**: Create new secure message
546
+ - **read [message_id]**: Read and burn message
547
+ - **list**: Show your active messages
548
+
549
+ **Usage Example:**
550
+ ```
551
+ Player: create Secret meeting location
552
+ Mailbox: ✅ Message Created! ID: AbC123Xyz789
553
+
554
+ Player: read AbC123Xyz789
555
+ Mailbox: 📖 Message Content: Secret meeting location
556
+ 🔥 This message has been BURNED and deleted forever!
557
+ ```
558
+
559
+ ### 🌤️ Weather Oracle
560
+
561
+ **Features:**
562
+ - **Real-time Weather**: Live weather data via MCP
563
+ - **Global Coverage**: Weather for any city worldwide
564
+ - **Detailed Information**: Temperature, conditions, wind, humidity
565
+ - **Format Flexibility**: Supports "City, Country" format
566
+ - **Error Handling**: Graceful handling of invalid locations
567
+
568
+ **Usage Examples:**
569
+ ```
570
+ Player: Berlin, Germany
571
+ Oracle: 🌍 Weather for Berlin, Germany:
572
+ 🌡️ 18°C, clear skies
573
+ 💨 Light winds, 65% humidity
574
+
575
+ Player: Tokyo
576
+ Oracle: 🌍 Weather for Tokyo, Japan:
577
+ 🌡️ 22°C, partly cloudy
578
+ 💨 Moderate winds, 70% humidity
579
+ ```
580
+
581
+ ### 🏪 Generic NPCs
582
+
583
+ **Built-in NPCs:**
584
+ - **Village Elder**: Quests and lore
585
+ - **Warrior Trainer**: Combat training
586
+ - **Merchant**: Trading and equipment
587
+ - **Mystic Oracle**: Magical services
588
+ - **Roaming Rick**: Dynamic location updates
589
+
590
+ **Response System:**
591
+ - **Personality-based**: Each NPC has unique personality
592
+ - **Context-aware**: Responses based on player context
593
+ - **Randomized**: Multiple response options for variety
594
+ - **Extensible**: Easy to add new NPCs and responses
595
+
596
+ ---
597
+
598
+ ## 🔬 Advanced Features
599
+
600
+ ### 🎯 Auto-refresh System
601
+
602
+ **Real-time Updates:**
603
+ - **World State**: Continuous map updates
604
+ - **Player Positions**: Live position tracking
605
+ - **Chat Messages**: Instant message delivery
606
+ - **NPC Status**: Dynamic NPC state updates
607
+
608
+ **Performance Optimization:**
609
+ - **Selective Updates**: Only update changed elements
610
+ - **Throttling**: Prevent excessive update frequency
611
+ - **Error Recovery**: Graceful handling of update failures
612
+ - **Resource Management**: Efficient memory usage
613
+
614
+ ### 🔐 Session Management
615
+
616
+ **Player Sessions:**
617
+ - **Unique IDs**: Each player gets unique session identifier
618
+ - **State Persistence**: Maintain player state across interactions
619
+ - **Cleanup**: Automatic cleanup of disconnected players
620
+ - **Security**: Prevent session hijacking and spoofing
621
+
622
+ **Multi-client Support:**
623
+ - **Concurrent Players**: Multiple players simultaneously
624
+ - **Session Isolation**: Each player's state remains separate
625
+ - **Resource Sharing**: Efficient sharing of game world state
626
+ - **Conflict Resolution**: Handle simultaneous action conflicts
627
+
628
+ ### 📡 MCP Server Integration
629
+
630
+ **Server Discovery:**
631
+ - **Endpoint Management**: Track multiple MCP server endpoints
632
+ - **Health Checking**: Monitor server availability
633
+ - **Load Balancing**: Distribute requests across servers
634
+ - **Failover**: Handle server outages gracefully
635
+
636
+ **Protocol Support:**
637
+ - **SSE Integration**: Server-Sent Events for real-time communication
638
+ - **Tool Discovery**: Automatic detection of available tools
639
+ - **Schema Validation**: Ensure tool parameters match schemas
640
+ - **Error Handling**: Robust error recovery and reporting
641
+
642
+ ### 🎨 UI Customization
643
+
644
+ **Theme System:**
645
+ - **Color Schemes**: Multiple visual themes
646
+ - **Layout Options**: Customizable interface layouts
647
+ - **Accessibility**: Support for screen readers and keyboard navigation
648
+ - **Responsive Design**: Works on different screen sizes
649
+
650
+ **Interface Components:**
651
+ - **Gradio Integration**: Rich web interface components
652
+ - **Custom Styling**: CSS customization options
653
+ - **Interactive Elements**: Buttons, inputs, displays
654
+ - **Real-time Updates**: Live data binding and updates
655
+
656
+ ---
657
+
658
+ ## 🔧 Troubleshooting
659
+
660
+ ### 🚨 Common Issues
661
+
662
+ **Connection Problems:**
663
+ ```
664
+ Issue: "Cannot connect to game server"
665
+ Solution:
666
+ 1. Check if server is running (python app.py)
667
+ 2. Verify port 7868 is available
668
+ 3. Check firewall settings
669
+ 4. Try restarting the server
670
+ ```
671
+
672
+ **Keyboard Controls Not Working:**
673
+ ```
674
+ Issue: "WASD keys don't move character"
675
+ Solution:
676
+ 1. Ensure JavaScript is enabled
677
+ 2. Click on the game interface to focus
678
+ 3. Check browser console for errors
679
+ 4. Try refreshing the page
680
+ ```
681
+
682
+ **NPC Not Responding:**
683
+ ```
684
+ Issue: "NPC doesn't reply to messages"
685
+ Solution:
686
+ 1. Check NPC addon is loaded
687
+ 2. Verify message format
688
+ 3. Look for error messages in console
689
+ 4. Try different NPC
690
+ ```
691
+
692
+ **MCP Connection Failures:**
693
+ ```
694
+ Issue: "AI agent cannot connect via MCP"
695
+ Solution:
696
+ 1. Verify MCP endpoint URL
697
+ 2. Check MCP server is running
698
+ 3. Validate tool parameters
699
+ 4. Review MCP protocol compliance
700
+ ```
701
+
702
+ ### 🐛 Debug Information
703
+
704
+ **Enable Debug Mode:**
705
+ ```python
706
+ # Add to app.py for detailed logging
707
+ import logging
708
+ logging.basicConfig(level=logging.DEBUG)
709
+ ```
710
+
711
+ **Check Logs:**
712
+ - **Console Output**: Server startup and error messages
713
+ - **Browser Console**: JavaScript errors and warnings
714
+ - **MCP Logs**: Tool call results and errors
715
+ - **Game Events**: Player actions and world updates
716
+
717
+ **Performance Monitoring:**
718
+ - **Player Count**: Monitor concurrent player limits
719
+ - **Memory Usage**: Track RAM consumption
720
+ - **Response Times**: Measure tool execution times
721
+ - **Error Rates**: Monitor failure frequencies
722
+
723
+ ---
724
+
725
+ ## 📚 API Reference
726
+
727
+ ### 🎮 Core Game APIs
728
+
729
+ **Player Management:**
730
+ ```python
731
+ # Join game
732
+ game_world.add_player(player: Player) -> bool
733
+
734
+ # Leave game
735
+ game_world.remove_player(player_id: str) -> bool
736
+
737
+ # Move player
738
+ game_world.move_player(player_id: str, direction: str) -> bool
739
+ ```
740
+
741
+ **Chat System:**
742
+ ```python
743
+ # Public chat
744
+ game_world.add_chat_message(sender: str, message: str,
745
+ message_type: str = "public")
746
+
747
+ # Private message
748
+ game_world.send_private_message(sender_id: str, target_id: str,
749
+ message: str) -> tuple[bool, str]
750
+ ```
751
+
752
+ **World State:**
753
+ ```python
754
+ # Get world HTML
755
+ create_game_world_html() -> str
756
+
757
+ # Player stats
758
+ get_player_stats_display(player_id: str) -> Dict
759
+
760
+ # Player session
761
+ get_player_id_from_session(session_hash: str) -> Optional[str]
762
+ ```
763
+
764
+ ### 🔌 MCP Tool Reference
765
+
766
+ **Game Control Tools:**
767
+ - `join_game(name: str)`: Join as player
768
+ - `leave_game()`: Leave the game
769
+ - `move_up_handler()`: Move up
770
+ - `move_down_handler()`: Move down
771
+ - `move_left_handler()`: Move left
772
+ - `move_right_handler()`: Move right
773
+
774
+ **Communication Tools:**
775
+ - `handle_chat(message: str)`: Send public chat
776
+ - `handle_private_message(message: str)`: Send private message
777
+
778
+ **AI Agent Tools:**
779
+ - `register_test_ai_agent(ai_name: str)`: Register AI agent
780
+ - `execute_ai_action(action: str, message: str)`: Execute AI action
781
+
782
+ **Addon Tools:**
783
+ - `handle_mailbox_command(command: str)`: Read2Burn mailbox
784
+ - `handle_weather_request(location: str)`: Weather information
785
+ - `install_addon(addon_type: str, addon_url: str, addon_name: str)`: Install addon
786
+
787
+ ### 🧩 Addon Development APIs
788
+
789
+ **Base Classes:**
790
+ ```python
791
+ class NPCAddon(ABC):
792
+ @property
793
+ @abstractmethod
794
+ def addon_id(self) -> str: ...
795
+
796
+ @property
797
+ @abstractmethod
798
+ def addon_name(self) -> str: ...
799
+
800
+ @abstractmethod
801
+ def get_interface(self) -> gr.Component: ...
802
+
803
+ @abstractmethod
804
+ def handle_command(self, player_id: str, command: str) -> str: ...
805
+ ```
806
+
807
+ **Registration:**
808
+ ```python
809
+ # Register addon
810
+ game_world.addon_npcs[addon_id] = addon_instance
811
+
812
+ # Access addon
813
+ addon = game_world.addon_npcs.get(addon_id)
814
+ ```
815
+
816
+ ---
817
+
818
+ ## 🎯 Best Practices
819
+
820
+ ### 👥 For Players
821
+
822
+ **Effective Communication:**
823
+ - Use clear, descriptive messages
824
+ - Be patient with AI agents learning
825
+ - Report bugs and issues promptly
826
+ - Share interesting discoveries with community
827
+
828
+ **Game Etiquette:**
829
+ - Respect other players
830
+ - Don't spam chat or commands
831
+ - Help new players learn the game
832
+ - Provide feedback to developers
833
+
834
+ ### 🤖 For AI Agents
835
+
836
+ **Efficient Integration:**
837
+ - Cache frequently used data
838
+ - Handle errors gracefully
839
+ - Respect rate limits
840
+ - Log interactions for debugging
841
+
842
+ **Social Behavior:**
843
+ - Introduce yourself when joining
844
+ - Respond appropriately to other players
845
+ - Follow game rules and conventions
846
+ - Contribute positively to the community
847
+
848
+ ### 👨‍💻 For Developers
849
+
850
+ **MCP Server Development:**
851
+ - Follow MCP protocol specifications exactly
852
+ - Provide detailed tool descriptions
853
+ - Handle edge cases and errors
854
+ - Test thoroughly before deployment
855
+
856
+ **Addon Development:**
857
+ - Keep interfaces simple and intuitive
858
+ - Provide helpful error messages
859
+ - Document your addon's capabilities
860
+ - Share code examples with community
861
+
862
+ ---
863
+
864
+ ## 📞 Support & Community
865
+
866
+ ### 🆘 Getting Help
867
+
868
+ **Documentation:**
869
+ - This user guide for comprehensive information
870
+ - NPC Addon Development Guide for addon creation
871
+ - Simple Game Client Guide for client development
872
+ - Sample MCP Server for server examples
873
+
874
+ **Community Resources:**
875
+ - GitHub Issues for bug reports and feature requests
876
+ - Discussion forums for general questions
877
+ - Developer Discord for real-time chat
878
+ - Wiki for community-contributed content
879
+
880
+ **Contact Information:**
881
+ - **Technical Issues**: Create GitHub issue with detailed description
882
+ - **Feature Requests**: Use GitHub discussions
883
+ - **Security Issues**: Contact maintainers directly
884
+ - **General Questions**: Use community forums
885
+
886
+ ### 🤝 Contributing
887
+
888
+ **Ways to Contribute:**
889
+ - **Bug Reports**: Help identify and fix issues
890
+ - **Feature Development**: Implement new capabilities
891
+ - **Documentation**: Improve guides and tutorials
892
+ - **Testing**: Quality assurance and validation
893
+ - **Community Support**: Help other users and developers
894
+
895
+ **Development Process:**
896
+ 1. Fork the repository
897
+ 2. Create feature branch
898
+ 3. Implement changes with tests
899
+ 4. Submit pull request
900
+ 5. Participate in code review
901
+
902
+ ---
903
+
904
+ *This documentation is continuously updated. Last updated: June 6, 2025*
905
+ *For the latest version, visit: https://github.com/your-repo/MMOP*
906
+
907
+ **🎮 Happy Gaming and Developing! 🚀**
app.py CHANGED
The diff for this file is too large to render. See raw diff
 
app_original_backup.py ADDED
The diff for this file is too large to render. See raw diff
 
plugins/__pycache__/enhanced_chat_plugin.cpython-313.pyc ADDED
Binary file (15.6 kB). View file
 
plugins/__pycache__/enhanced_chat_plugin_final.cpython-313.pyc ADDED
Binary file (29.7 kB). View file
 
plugins/__pycache__/enhanced_chat_plugin_fixed.cpython-313.pyc ADDED
Binary file (13.2 kB). View file
 
plugins/__pycache__/plugin_registry.cpython-313.pyc ADDED
Binary file (1.18 kB). View file
 
plugins/__pycache__/sample_plugin.cpython-313.pyc ADDED
Binary file (2.97 kB). View file
 
plugins/__pycache__/trading_system_plugin.cpython-313.pyc ADDED
Binary file (15.3 kB). View file
 
plugins/__pycache__/weather_plugin.cpython-313.pyc ADDED
Binary file (9.12 kB). View file
 
plugins/enhanced_chat_plugin.py ADDED
@@ -0,0 +1,449 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Enhanced Chat Plugin
2
+ import asyncio
3
+ from src.interfaces.plugin_interfaces import IChatPlugin, PluginMetadata, PluginType
4
+ from typing import Dict, List, Any
5
+
6
+ class EnhancedChatPlugin(IChatPlugin):
7
+ def __init__(self):
8
+ self._metadata = PluginMetadata(
9
+ id="enhanced_chat",
10
+ name="Enhanced Chat System",
11
+ version="1.2.0",
12
+ author="MMORPG Dev Team",
13
+ description="Adds emotes, chat channels, and advanced chat commands",
14
+ plugin_type=PluginType.SERVICE,
15
+ dependencies=[],
16
+ config={
17
+ "enable_emotes": True,
18
+ "enable_channels": True,
19
+ "max_message_length": 500,
20
+ "chat_cooldown": 1
21
+ }
22
+ )
23
+ self._enabled = False
24
+ self._context = None
25
+
26
+ # Chat history storage
27
+ self.chat_history = []
28
+ self.max_history = 100
29
+
30
+ # Player ignore lists
31
+ self.ignore_lists = {}
32
+
33
+ # Custom channels
34
+ self.channels = {
35
+ 'global': {'description': 'Global chat channel', 'members': set()},
36
+ 'trade': {'description': 'Trading channel', 'members': set()},
37
+ 'help': {'description': 'Help and questions channel', 'members': set()}
38
+ }
39
+
40
+ # Player channel memberships
41
+ self.player_channels = {}
42
+
43
+ @property
44
+ def metadata(self) -> PluginMetadata:
45
+ return self._metadata
46
+
47
+ def initialize(self, context: Dict[str, Any]) -> bool:
48
+ """Initialize the enhanced chat plugin."""
49
+ try:
50
+ self._context = context
51
+ self._config = self._metadata.config
52
+ self._enabled = True
53
+ print(f"💬 Enhanced Chat Plugin initialized")
54
+ return True
55
+ except Exception as e:
56
+ print(f"💬 Failed to initialize Enhanced Chat Plugin: {e}")
57
+ return False
58
+
59
+ def shutdown(self) -> bool:
60
+ """Shutdown the plugin and cleanup resources."""
61
+ self._enabled = False
62
+ print("💬 Enhanced Chat Plugin shut down")
63
+ return True
64
+
65
+ def get_status(self) -> Dict[str, Any]:
66
+ """Get current plugin status."""
67
+ return {
68
+ "status": "active" if self._enabled else "inactive",
69
+ "message": "Enhanced chat system with marketplace commands"
70
+ }
71
+
72
+ def process_message(self, player_id: str, message: str, channel: str = "global") -> Dict[str, Any]:
73
+ """Process and enhance chat messages."""
74
+ if message.startswith('/'):
75
+ return self._process_command(player_id, message)
76
+ else:
77
+ return self._broadcast_message(player_id, message)
78
+
79
+ def get_available_commands(self) -> List[Dict[str, str]]:
80
+ """Get list of available chat commands."""
81
+ return [
82
+ {"command": "/marketplace", "description": "View marketplace listings"},
83
+ {"command": "/trade create <item> <price>", "description": "Create trade listing"},
84
+ {"command": "/trade accept <id>", "description": "Accept trade offer"},
85
+ {"command": "/auction create <item> <start_price>", "description": "Create auction"},
86
+ {"command": "/tell <player> <message>", "description": "Send private message"},
87
+ {"command": "/who", "description": "List online players"},
88
+ {"command": "/help", "description": "Show available commands"},
89
+ {"command": "/shout <message>", "description": "Shout to all players"},
90
+ {"command": "/emote <action>", "description": "Perform an emote"},
91
+ {"command": "/me <action>", "description": "Perform an emote (alias)"},
92
+ {"command": "/time", "description": "Show current time"},
93
+ {"command": "/channels", "description": "List available channels"}
94
+ ]
95
+
96
+ def get_chat_channels(self) -> Dict[str, Dict[str, str]]:
97
+ """Get available chat channels."""
98
+ return {
99
+ "global": {"name": "Global", "description": "Main chat channel"},
100
+ "trade": {"name": "Trade", "description": "Trading discussions"},
101
+ "help": {"name": "Help", "description": "Help and questions"}
102
+ }
103
+
104
+ def _process_command(self, player_id: str, message: str) -> Dict[str, Any]:
105
+ """Process chat commands"""
106
+ parts = message.split(' ', 1)
107
+ command = parts[0].lower()
108
+ args = parts[1] if len(parts) > 1 else ""
109
+
110
+ # Command handlers
111
+ command_handlers = {
112
+ '/marketplace': self._marketplace_command,
113
+ '/trade': self._trade_command,
114
+ '/auction': self._auction_command,
115
+ '/tell': self._tell_command,
116
+ '/whisper': self._tell_command, # Alias
117
+ '/who': self._who_command,
118
+ '/help': self._help_command,
119
+ '/commands': self._help_command, # Alias
120
+ '/shout': self._shout_command,
121
+ '/emote': self._emote_command,
122
+ '/me': self._emote_command, # Alias
123
+ '/time': self._time_command,
124
+ '/channels': self._channels_command
125
+ }
126
+
127
+ if command in command_handlers:
128
+ try:
129
+ return command_handlers[command](player_id, args)
130
+ except Exception as e:
131
+ return {
132
+ "success": False,
133
+ "message": f"Error executing command: {str(e)}",
134
+ "target": player_id
135
+ }
136
+ else:
137
+ return {
138
+ "success": False,
139
+ "message": f"Unknown command: {command}. Type /help for available commands.",
140
+ "target": player_id
141
+ }
142
+
143
+ def _broadcast_message(self, player_id: str, message: str) -> Dict[str, Any]:
144
+ """Broadcast a message to all players"""
145
+ player_name = self._get_player_name(player_id)
146
+ formatted_message = f"{player_name}: {message}"
147
+
148
+ # Add to history
149
+ self._add_to_history(formatted_message)
150
+
151
+ return {
152
+ "success": True,
153
+ "message": formatted_message,
154
+ "type": "broadcast",
155
+ "sender": player_id
156
+ }
157
+
158
+ def _add_to_history(self, message: str):
159
+ """Add message to chat history"""
160
+ import time
161
+ self.chat_history.append({
162
+ 'timestamp': time.time(),
163
+ 'message': message
164
+ })
165
+
166
+ # Keep only the last max_history messages
167
+ if len(self.chat_history) > self.max_history:
168
+ self.chat_history.pop(0)
169
+
170
+ def _marketplace_command(self, player_id: str, args: str) -> Dict[str, Any]:
171
+ """Handle marketplace command"""
172
+ marketplace_text = """
173
+ 🏪 **MARKETPLACE** 🏪
174
+ ═══════════════════════
175
+
176
+ 📦 **AVAILABLE ITEMS:**
177
+ • Iron Sword - 150 gold (Seller: Trader_Bob)
178
+ • Health Potion - 25 gold (Seller: Alchemist_Ann)
179
+ • Magic Ring - 300 gold (Seller: Wizard_Will)
180
+ • Steel Shield - 200 gold (Seller: Smith_Sam)
181
+
182
+ 💰 **RECENT TRADES:**
183
+ • Leather Boots sold for 75 gold
184
+ • Fire Staff sold for 450 gold
185
+ • Healing Herbs sold for 15 gold
186
+
187
+ 📝 Use `/trade create <item> <price>` to list an item
188
+ 🤝 Use `/trade accept <seller>` to buy an item
189
+ 🔨 Use `/auction create <item> <starting_price>` for auctions
190
+
191
+ Type `/help` for more trading commands!
192
+ """
193
+
194
+ return {
195
+ "success": True,
196
+ "message": marketplace_text.strip(),
197
+ "target": player_id,
198
+ "type": "info"
199
+ }
200
+
201
+ def _trade_command(self, player_id: str, args: str) -> Dict[str, Any]:
202
+ """Handle trade command"""
203
+ if not args:
204
+ return {
205
+ "success": False,
206
+ "message": "Usage: /trade <action> [parameters]\nActions: create, accept, list, cancel",
207
+ "target": player_id
208
+ }
209
+
210
+ parts = args.split(' ', 1)
211
+ action = parts[0].lower()
212
+
213
+ if action == 'create':
214
+ if len(parts) < 2:
215
+ return {
216
+ "success": False,
217
+ "message": "Usage: /trade create <item> <price>",
218
+ "target": player_id
219
+ }
220
+
221
+ # Parse item and price
222
+ create_args = parts[1].split(' ')
223
+ if len(create_args) < 2:
224
+ return {
225
+ "success": False,
226
+ "message": "Usage: /trade create <item> <price>",
227
+ "target": player_id
228
+ }
229
+
230
+ price = create_args[-1]
231
+ item = ' '.join(create_args[:-1])
232
+
233
+ return {
234
+ "success": True,
235
+ "message": f"✅ Trade listing created: {item} for {price} gold",
236
+ "target": player_id,
237
+ "type": "trade_create",
238
+ "data": {"item": item, "price": price, "seller": player_id}
239
+ }
240
+
241
+ elif action == 'accept':
242
+ if len(parts) < 2:
243
+ return {
244
+ "success": False,
245
+ "message": "Usage: /trade accept <seller_name>",
246
+ "target": player_id
247
+ }
248
+
249
+ seller_name = parts[1]
250
+ return {
251
+ "success": True,
252
+ "message": f"🤝 Attempting to trade with {seller_name}...",
253
+ "target": player_id,
254
+ "type": "trade_accept"
255
+ }
256
+
257
+ elif action == 'list':
258
+ return {
259
+ "success": True,
260
+ "message": "📋 Your active trade listings:\n• No active listings",
261
+ "target": player_id
262
+ }
263
+
264
+ else:
265
+ return {
266
+ "success": False,
267
+ "message": "Unknown trade action. Use 'create', 'accept', 'list', or 'cancel'.",
268
+ "target": player_id
269
+ }
270
+
271
+ def _auction_command(self, player_id: str, args: str) -> Dict[str, Any]:
272
+ """Handle auction command"""
273
+ if not args:
274
+ return {
275
+ "success": False,
276
+ "message": "Usage: /auction <action> [parameters]\nActions: create, bid, list",
277
+ "target": player_id
278
+ }
279
+
280
+ parts = args.split(' ', 1)
281
+ action = parts[0].lower()
282
+
283
+ if action == 'create':
284
+ if len(parts) < 2:
285
+ return {
286
+ "success": False,
287
+ "message": "Usage: /auction create <item> <starting_price>",
288
+ "target": player_id
289
+ }
290
+
291
+ # Parse item and starting price
292
+ create_args = parts[1].split(' ')
293
+ if len(create_args) < 2:
294
+ return {
295
+ "success": False,
296
+ "message": "Usage: /auction create <item> <starting_price>",
297
+ "target": player_id
298
+ }
299
+
300
+ starting_price = create_args[-1]
301
+ item = ' '.join(create_args[:-1])
302
+
303
+ return {
304
+ "success": True,
305
+ "message": f"🔨 Auction created: {item} starting at {starting_price} gold",
306
+ "target": player_id,
307
+ "type": "auction_create"
308
+ }
309
+
310
+ else:
311
+ return {
312
+ "success": False,
313
+ "message": "Unknown auction action. Use 'create' to start an auction.",
314
+ "target": player_id
315
+ }
316
+
317
+ def _tell_command(self, player_id: str, args: str) -> Dict[str, Any]:
318
+ """Send a private message to another player"""
319
+ if not args:
320
+ return {
321
+ "success": False,
322
+ "message": "Usage: /tell <player> <message>",
323
+ "target": player_id
324
+ }
325
+
326
+ parts = args.split(' ', 1)
327
+ if len(parts) < 2:
328
+ return {
329
+ "success": False,
330
+ "message": "Usage: /tell <player> <message>",
331
+ "target": player_id
332
+ }
333
+
334
+ target_name, message = parts
335
+ sender_name = self._get_player_name(player_id)
336
+
337
+ return {
338
+ "success": True,
339
+ "message": f"[TELL to {target_name}]: {message}",
340
+ "target": player_id,
341
+ "type": "tell",
342
+ "data": {
343
+ "target_name": target_name,
344
+ "sender_name": sender_name,
345
+ "message": message
346
+ }
347
+ }
348
+
349
+ def _who_command(self, player_id: str, args: str) -> Dict[str, Any]:
350
+ """Show list of online players"""
351
+ # In a real implementation, this would get the actual list of connected players
352
+ return {
353
+ "success": True,
354
+ "message": "👥 Online players (3): Player1, Player2, Player3",
355
+ "target": player_id,
356
+ "type": "info"
357
+ }
358
+
359
+ def _help_command(self, player_id: str, args: str) -> Dict[str, Any]:
360
+ """Show available commands"""
361
+ commands = self.get_available_commands()
362
+ help_text = "📋 **AVAILABLE COMMANDS:**\n"
363
+ help_text += "═══════════════════════\n\n"
364
+
365
+ for cmd in commands:
366
+ help_text += f"• {cmd['command']} - {cmd['description']}\n"
367
+
368
+ return {
369
+ "success": True,
370
+ "message": help_text,
371
+ "target": player_id,
372
+ "type": "info"
373
+ }
374
+
375
+ def _shout_command(self, player_id: str, args: str) -> Dict[str, Any]:
376
+ """Send a message to all players with emphasis"""
377
+ if not args:
378
+ return {
379
+ "success": False,
380
+ "message": "Usage: /shout <message>",
381
+ "target": player_id
382
+ }
383
+
384
+ player_name = self._get_player_name(player_id)
385
+ formatted_message = f"📢 {player_name} shouts: {args}"
386
+
387
+ self._add_to_history(formatted_message)
388
+
389
+ return {
390
+ "success": True,
391
+ "message": formatted_message,
392
+ "type": "shout",
393
+ "sender": player_id
394
+ }
395
+
396
+ def _emote_command(self, player_id: str, args: str) -> Dict[str, Any]:
397
+ """Send an emote message"""
398
+ if not args:
399
+ return {
400
+ "success": False,
401
+ "message": "Usage: /emote <action> or /me <action>",
402
+ "target": player_id
403
+ }
404
+
405
+ player_name = self._get_player_name(player_id)
406
+ formatted_message = f"✨ {player_name} {args}"
407
+
408
+ self._add_to_history(formatted_message)
409
+
410
+ return {
411
+ "success": True,
412
+ "message": formatted_message,
413
+ "type": "emote",
414
+ "sender": player_id
415
+ }
416
+
417
+ def _time_command(self, player_id: str, args: str) -> Dict[str, Any]:
418
+ """Show current game time"""
419
+ import datetime
420
+ current_time = datetime.datetime.now().strftime("%H:%M:%S")
421
+
422
+ return {
423
+ "success": True,
424
+ "message": f"🕐 Current time: {current_time}",
425
+ "target": player_id,
426
+ "type": "info"
427
+ }
428
+
429
+ def _channels_command(self, player_id: str, args: str) -> Dict[str, Any]:
430
+ """Show available chat channels"""
431
+ channels = self.get_chat_channels()
432
+ channels_text = "📺 **CHAT CHANNELS:**\n"
433
+ channels_text += "═══════════════════\n\n"
434
+
435
+ for channel_id, channel_info in channels.items():
436
+ channels_text += f"• #{channel_id} - {channel_info['description']}\n"
437
+
438
+ return {
439
+ "success": True,
440
+ "message": channels_text,
441
+ "target": player_id,
442
+ "type": "info"
443
+ }
444
+
445
+ # Helper methods
446
+ def _get_player_name(self, player_id: str) -> str:
447
+ """Get player name from player ID"""
448
+ # In a real implementation, this would look up the actual player name
449
+ return f"Player{player_id[-3:]}" if len(player_id) > 3 else f"Player{player_id}"
plugins/enhanced_chat_plugin.py.backup ADDED
@@ -0,0 +1,586 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ from src.interfaces.plugin_interfaces import IChatPlugin, PluginMetadata, PluginType
3
+ from typing import Dict, List, Any
4
+
5
+ class EnhancedChatPlugin(IChatPlugin):
6
+ def __init__(self): self._metadata = PluginMetadata(
7
+ id="enhanced_chat",
8
+ name="Enhanced Chat System",
9
+ version="1.2.0",
10
+ author="MMORPG Dev Team",
11
+ description="Adds emotes, chat channels, and advanced chat commands",
12
+ plugin_type=PluginType.SERVICE,
13
+ dependencies=[],
14
+ config={
15
+ "enable_emotes": True,
16
+ "enable_channels": True,
17
+ "max_message_length": 500,
18
+ "chat_cooldown": 1
19
+ }
20
+ )
21
+ self._enabled = False
22
+ self.commands = {
23
+ '/tell': self.tell_command,
24
+ '/whisper': self.whisper_command,
25
+ '/shout': self.shout_command,
26
+ '/emote': self.emote_command,
27
+ '/me': self.emote_command, # Alias for /emote
28
+ '/who': self.who_command,
29
+ '/time': self.time_command,
30
+ '/help': self.help_command,
31
+ '/commands': self.help_command, # Alias for /help
32
+ '/clear': self.clear_command,
33
+ '/ignore': self.ignore_command,
34
+ '/unignore': self.unignore_command,
35
+ '/history': self.history_command,
36
+ '/channels': self.channels_command,
37
+ '/join': self.join_channel_command,
38
+ '/leave': self.leave_channel_command,
39
+ '/channel': self.channel_command,
40
+ '/c': self.channel_command, # Alias for /channel
41
+ '/mail': self.mail_command,
42
+ '/read': self.read_mail_command,
43
+ '/mailbox': self.mailbox_command,
44
+ '/marketplace': self.marketplace_command,
45
+ '/trade': self.trade_command,
46
+ '/auction': self.auction_command,
47
+ }
48
+
49
+ # Chat history storage
50
+ self.chat_history = []
51
+ self.max_history = 100
52
+
53
+ # Player ignore lists
54
+ self.ignore_lists = {}
55
+
56
+ # Custom channels
57
+ self.channels = {
58
+ 'global': {'description': 'Global chat channel', 'members': set()},
59
+ 'trade': {'description': 'Trading channel', 'members': set()},
60
+ 'help': {'description': 'Help and questions channel', 'members': set()}
61
+ }
62
+
63
+ # Player channel memberships
64
+ self.player_channels = {}
65
+
66
+ @property
67
+ def metadata(self) -> PluginMetadata:
68
+ return self._metadata
69
+
70
+ def initialize(self, context: Dict[str, Any]) -> bool:
71
+ """Initialize the enhanced chat plugin."""
72
+ try:
73
+ self._config = self._metadata.config
74
+ self._enabled = True
75
+ print(f"💬 Enhanced Chat Plugin initialized")
76
+ return True
77
+ except Exception as e:
78
+ print(f"💬 Failed to initialize Enhanced Chat Plugin: {e}")
79
+ return False
80
+
81
+ def shutdown(self) -> bool:
82
+ """Shutdown the plugin and cleanup resources."""
83
+ self._enabled = False
84
+ print("💬 Enhanced Chat Plugin shut down")
85
+ return True
86
+
87
+ def get_status(self) -> Dict[str, Any]:
88
+ """Get current plugin status."""
89
+ return {
90
+ "status": "active" if self._enabled else "inactive",
91
+ "message": "Enhanced chat system with marketplace commands"
92
+ }
93
+
94
+ def process_message(self, player_id: str, message: str, channel: str = "global") -> Dict[str, Any]:
95
+ """Process and enhance chat messages."""
96
+ if message.startswith('/'):
97
+ return self.process_command(player_id, message)
98
+ else:
99
+ return self.broadcast_message(player_id, message)
100
+
101
+ def get_available_commands(self) -> List[Dict[str, str]]:
102
+ """Get list of available chat commands."""
103
+ return [
104
+ {"command": "/marketplace", "description": "View marketplace listings"},
105
+ {"command": "/trade create <item> <price>", "description": "Create trade listing"},
106
+ {"command": "/trade accept <id>", "description": "Accept trade offer"},
107
+ {"command": "/auction create <item> <start_price>", "description": "Create auction"},
108
+ {"command": "/tell <player> <message>", "description": "Send private message"},
109
+ {"command": "/who", "description": "List online players"},
110
+ {"command": "/help", "description": "Show available commands"}
111
+ ]
112
+
113
+ def get_chat_channels(self) -> Dict[str, Dict[str, str]]:
114
+ """Get available chat channels."""
115
+ return {
116
+ "global": {"name": "Global", "description": "Main chat channel"},
117
+ "trade": {"name": "Trade", "description": "Trading discussions"},
118
+ "help": {"name": "Help", "description": "Help and questions"}
119
+ }
120
+
121
+ async def on_player_message(self, player_id, message):
122
+ """Handle player chat messages"""
123
+ if message.startswith('/'):
124
+ await self.process_command(player_id, message)
125
+ else:
126
+ await self.broadcast_message(player_id, message)
127
+
128
+ async def process_command(self, player_id, message):
129
+ """Process chat commands"""
130
+ parts = message.split(' ', 1)
131
+ command = parts[0].lower()
132
+ args = parts[1] if len(parts) > 1 else ""
133
+
134
+ if command in self.commands:
135
+ try:
136
+ await self.commands[command](player_id, args)
137
+ except Exception as e:
138
+ await self.send_to_player(player_id, f"Error executing command: {str(e)}")
139
+ else:
140
+ await self.send_to_player(player_id, f"Unknown command: {command}. Type /help for available commands.")
141
+
142
+ async def broadcast_message(self, player_id, message):
143
+ """Broadcast a message to all players"""
144
+ player_name = await self.get_player_name(player_id)
145
+ formatted_message = f"{player_name}: {message}"
146
+
147
+ # Add to history
148
+ self.add_to_history(formatted_message)
149
+
150
+ # Send to all connected players
151
+ for pid in self.game_manager.get_connected_players():
152
+ if pid not in self.ignore_lists.get(player_id, set()):
153
+ await self.send_to_player(pid, formatted_message)
154
+
155
+ def add_to_history(self, message):
156
+ """Add message to chat history"""
157
+ self.chat_history.append({
158
+ 'timestamp': asyncio.get_event_loop().time(),
159
+ 'message': message
160
+ })
161
+
162
+ # Keep only the last max_history messages
163
+ if len(self.chat_history) > self.max_history:
164
+ self.chat_history.pop(0)
165
+
166
+ async def tell_command(self, player_id, args):
167
+ """Send a private message to another player"""
168
+ if not args:
169
+ await self.send_to_player(player_id, "Usage: /tell <player> <message>")
170
+ return
171
+
172
+ parts = args.split(' ', 1)
173
+ if len(parts) < 2:
174
+ await self.send_to_player(player_id, "Usage: /tell <player> <message>")
175
+ return
176
+
177
+ target_name, message = parts
178
+ target_id = await self.find_player_by_name(target_name)
179
+
180
+ if not target_id:
181
+ await self.send_to_player(player_id, f"Player '{target_name}' not found.")
182
+ return
183
+
184
+ if target_id in self.ignore_lists.get(player_id, set()):
185
+ await self.send_to_player(player_id, f"You are ignoring {target_name}.")
186
+ return
187
+
188
+ sender_name = await self.get_player_name(player_id)
189
+ await self.send_to_player(target_id, f"[TELL from {sender_name}]: {message}")
190
+ await self.send_to_player(player_id, f"[TELL to {target_name}]: {message}")
191
+
192
+ async def whisper_command(self, player_id, args):
193
+ """Alias for tell command"""
194
+ await self.tell_command(player_id, args)
195
+
196
+ async def shout_command(self, player_id, args):
197
+ """Send a message to all players in a wider area"""
198
+ if not args:
199
+ await self.send_to_player(player_id, "Usage: /shout <message>")
200
+ return
201
+
202
+ player_name = await self.get_player_name(player_id)
203
+ formatted_message = f"{player_name} shouts: {args}"
204
+
205
+ # Add to history
206
+ self.add_to_history(formatted_message)
207
+
208
+ # Send to all players (could be modified to only nearby players)
209
+ for pid in self.game_manager.get_connected_players():
210
+ if pid not in self.ignore_lists.get(player_id, set()):
211
+ await self.send_to_player(pid, formatted_message)
212
+
213
+ async def emote_command(self, player_id, args):
214
+ """Send an emote message"""
215
+ if not args:
216
+ await self.send_to_player(player_id, "Usage: /emote <action> or /me <action>")
217
+ return
218
+
219
+ player_name = await self.get_player_name(player_id)
220
+ formatted_message = f"* {player_name} {args}"
221
+
222
+ # Add to history
223
+ self.add_to_history(formatted_message)
224
+
225
+ # Send to all players
226
+ for pid in self.game_manager.get_connected_players():
227
+ if pid not in self.ignore_lists.get(player_id, set()):
228
+ await self.send_to_player(pid, formatted_message)
229
+
230
+ async def who_command(self, player_id, args):
231
+ """Show list of online players"""
232
+ connected_players = self.game_manager.get_connected_players()
233
+ player_names = []
234
+
235
+ for pid in connected_players:
236
+ name = await self.get_player_name(pid)
237
+ player_names.append(name)
238
+
239
+ if player_names:
240
+ message = f"Online players ({len(player_names)}): {', '.join(player_names)}"
241
+ else:
242
+ message = "No players currently online."
243
+
244
+ await self.send_to_player(player_id, message)
245
+
246
+ async def time_command(self, player_id, args):
247
+ """Show current game time"""
248
+ import datetime
249
+ current_time = datetime.datetime.now().strftime("%H:%M:%S")
250
+ await self.send_to_player(player_id, f"Current time: {current_time}")
251
+
252
+ async def help_command(self, player_id, args):
253
+ """Show available commands"""
254
+ commands = [
255
+ "/tell <player> <msg> - Send private message",
256
+ "/whisper <player> <msg> - Same as /tell",
257
+ "/shout <message> - Shout to all players",
258
+ "/emote <action> - Perform an action",
259
+ "/me <action> - Same as /emote",
260
+ "/who - Show online players",
261
+ "/time - Show current time",
262
+ "/help - Show this help",
263
+ "/clear - Clear your chat",
264
+ "/ignore <player> - Ignore a player",
265
+ "/unignore <player> - Stop ignoring a player",
266
+ "/history - Show recent chat history",
267
+ "/channels - Show available channels",
268
+ "/join <channel> - Join a channel",
269
+ "/leave <channel> - Leave a channel",
270
+ "/channel <channel> <msg> - Send message to channel",
271
+ "/c <channel> <msg> - Same as /channel",
272
+ "/mail <player> <msg> - Send mail to player",
273
+ "/read - Read your mail",
274
+ "/mailbox - Check mailbox status",
275
+ "/marketplace - View marketplace listings",
276
+ "/trade create <item> <price> - Create trade offer",
277
+ "/trade accept <id> - Accept trade offer",
278
+ "/auction create <item> <start_price> - Create auction"
279
+ ]
280
+
281
+ await self.send_to_player(player_id, "Available commands:")
282
+ for cmd in commands:
283
+ await self.send_to_player(player_id, f" {cmd}")
284
+
285
+ async def clear_command(self, player_id, args):
286
+ """Clear player's chat"""
287
+ # This would typically clear the client-side chat
288
+ await self.send_to_player(player_id, "\n" * 20)
289
+ await self.send_to_player(player_id, "Chat cleared.")
290
+
291
+ async def ignore_command(self, player_id, args):
292
+ """Add a player to ignore list"""
293
+ if not args:
294
+ await self.send_to_player(player_id, "Usage: /ignore <player>")
295
+ return
296
+
297
+ target_id = await self.find_player_by_name(args.strip())
298
+ if not target_id:
299
+ await self.send_to_player(player_id, f"Player '{args}' not found.")
300
+ return
301
+
302
+ if target_id == player_id:
303
+ await self.send_to_player(player_id, "You cannot ignore yourself.")
304
+ return
305
+
306
+ if player_id not in self.ignore_lists:
307
+ self.ignore_lists[player_id] = set()
308
+
309
+ self.ignore_lists[player_id].add(target_id)
310
+ await self.send_to_player(player_id, f"You are now ignoring {args}.")
311
+
312
+ async def unignore_command(self, player_id, args):
313
+ """Remove a player from ignore list"""
314
+ if not args:
315
+ await self.send_to_player(player_id, "Usage: /unignore <player>")
316
+ return
317
+
318
+ target_id = await self.find_player_by_name(args.strip())
319
+ if not target_id:
320
+ await self.send_to_player(player_id, f"Player '{args}' not found.")
321
+ return
322
+
323
+ if player_id in self.ignore_lists and target_id in self.ignore_lists[player_id]:
324
+ self.ignore_lists[player_id].remove(target_id)
325
+ await self.send_to_player(player_id, f"You are no longer ignoring {args}.")
326
+ else:
327
+ await self.send_to_player(player_id, f"You are not ignoring {args}.")
328
+
329
+ async def history_command(self, player_id, args):
330
+ """Show recent chat history"""
331
+ if not self.chat_history:
332
+ await self.send_to_player(player_id, "No chat history available.")
333
+ return
334
+
335
+ await self.send_to_player(player_id, "Recent chat history:")
336
+ for entry in self.chat_history[-10:]: # Show last 10 messages
337
+ await self.send_to_player(player_id, entry['message'])
338
+
339
+ async def channels_command(self, player_id, args):
340
+ """Show available channels"""
341
+ await self.send_to_player(player_id, "Available channels:")
342
+ for channel_name, channel_info in self.channels.items():
343
+ member_count = len(channel_info['members'])
344
+ await self.send_to_player(player_id, f" {channel_name}: {channel_info['description']} ({member_count} members)")
345
+
346
+ async def join_channel_command(self, player_id, args):
347
+ """Join a chat channel"""
348
+ if not args:
349
+ await self.send_to_player(player_id, "Usage: /join <channel>")
350
+ return
351
+
352
+ channel_name = args.strip().lower()
353
+ if channel_name not in self.channels:
354
+ await self.send_to_player(player_id, f"Channel '{channel_name}' does not exist.")
355
+ return
356
+
357
+ if player_id not in self.player_channels:
358
+ self.player_channels[player_id] = set()
359
+
360
+ self.player_channels[player_id].add(channel_name)
361
+ self.channels[channel_name]['members'].add(player_id)
362
+
363
+ player_name = await self.get_player_name(player_id)
364
+ await self.send_to_player(player_id, f"You have joined the '{channel_name}' channel.")
365
+
366
+ # Notify other channel members
367
+ for member_id in self.channels[channel_name]['members']:
368
+ if member_id != player_id:
369
+ await self.send_to_player(member_id, f"{player_name} has joined the '{channel_name}' channel.")
370
+
371
+ async def leave_channel_command(self, player_id, args):
372
+ """Leave a chat channel"""
373
+ if not args:
374
+ await self.send_to_player(player_id, "Usage: /leave <channel>")
375
+ return
376
+
377
+ channel_name = args.strip().lower()
378
+ if channel_name not in self.channels:
379
+ await self.send_to_player(player_id, f"Channel '{channel_name}' does not exist.")
380
+ return
381
+
382
+ if player_id not in self.player_channels or channel_name not in self.player_channels[player_id]:
383
+ await self.send_to_player(player_id, f"You are not in the '{channel_name}' channel.")
384
+ return
385
+
386
+ self.player_channels[player_id].remove(channel_name)
387
+ self.channels[channel_name]['members'].discard(player_id)
388
+
389
+ player_name = await self.get_player_name(player_id)
390
+ await self.send_to_player(player_id, f"You have left the '{channel_name}' channel.")
391
+
392
+ # Notify other channel members
393
+ for member_id in self.channels[channel_name]['members']:
394
+ await self.send_to_player(member_id, f"{player_name} has left the '{channel_name}' channel.")
395
+
396
+ async def channel_command(self, player_id, args):
397
+ """Send a message to a specific channel"""
398
+ if not args:
399
+ await self.send_to_player(player_id, "Usage: /channel <channel> <message>")
400
+ return
401
+
402
+ parts = args.split(' ', 1)
403
+ if len(parts) < 2:
404
+ await self.send_to_player(player_id, "Usage: /channel <channel> <message>")
405
+ return
406
+
407
+ channel_name, message = parts
408
+ channel_name = channel_name.lower()
409
+
410
+ if channel_name not in self.channels:
411
+ await self.send_to_player(player_id, f"Channel '{channel_name}' does not exist.")
412
+ return
413
+
414
+ if player_id not in self.player_channels or channel_name not in self.player_channels[player_id]:
415
+ await self.send_to_player(player_id, f"You are not in the '{channel_name}' channel. Use /join {channel_name} first.")
416
+ return
417
+
418
+ player_name = await self.get_player_name(player_id)
419
+ formatted_message = f"[{channel_name.upper()}] {player_name}: {message}"
420
+
421
+ # Send to all channel members
422
+ for member_id in self.channels[channel_name]['members']:
423
+ if member_id not in self.ignore_lists.get(player_id, set()):
424
+ await self.send_to_player(member_id, formatted_message)
425
+
426
+ async def mail_command(self, player_id, args):
427
+ """Send mail to a player using the read2burn system"""
428
+ if not args:
429
+ await self.send_to_player(player_id, "Usage: /mail <player> <message>")
430
+ return
431
+
432
+ parts = args.split(' ', 1)
433
+ if len(parts) < 2:
434
+ await self.send_to_player(player_id, "Usage: /mail <player> <message>")
435
+ return
436
+
437
+ target_name, message = parts
438
+ target_id = await self.find_player_by_name(target_name)
439
+
440
+ if not target_id:
441
+ await self.send_to_player(player_id, f"Player '{target_name}' not found.")
442
+ return
443
+
444
+ # Use the read2burn addon to send mail
445
+ try:
446
+ read2burn_addon = self.game_manager.get_addon('read2burn')
447
+ if read2burn_addon:
448
+ sender_name = await self.get_player_name(player_id)
449
+ await read2burn_addon.send_message(target_id, f"Mail from {sender_name}: {message}")
450
+ await self.send_to_player(player_id, f"Mail sent to {target_name}.")
451
+ else:
452
+ await self.send_to_player(player_id, "Mail system is not available.")
453
+ except Exception as e:
454
+ await self.send_to_player(player_id, f"Failed to send mail: {str(e)}")
455
+
456
+ async def read_mail_command(self, player_id, args):
457
+ """Read mail using the read2burn system"""
458
+ try:
459
+ read2burn_addon = self.game_manager.get_addon('read2burn')
460
+ if read2burn_addon:
461
+ messages = await read2burn_addon.get_messages(player_id)
462
+ if messages:
463
+ await self.send_to_player(player_id, f"You have {len(messages)} unread message(s):")
464
+ for i, msg in enumerate(messages):
465
+ await self.send_to_player(player_id, f"{i+1}. {msg}")
466
+ # Messages are automatically deleted after reading (read2burn)
467
+ await read2burn_addon.clear_messages(player_id)
468
+ else:
469
+ await self.send_to_player(player_id, "You have no unread mail.")
470
+ else:
471
+ await self.send_to_player(player_id, "Mail system is not available.")
472
+ except Exception as e:
473
+ await self.send_to_player(player_id, f"Failed to read mail: {str(e)}")
474
+
475
+ async def mailbox_command(self, player_id, args):
476
+ """Check mailbox status"""
477
+ try:
478
+ read2burn_addon = self.game_manager.get_addon('read2burn')
479
+ if read2burn_addon:
480
+ message_count = await read2burn_addon.get_message_count(player_id)
481
+ await self.send_to_player(player_id, f"You have {message_count} unread message(s) in your mailbox.")
482
+ else:
483
+ await self.send_to_player(player_id, "Mail system is not available.")
484
+ except Exception as e:
485
+ await self.send_to_player(player_id, f"Failed to check mailbox: {str(e)}")
486
+
487
+ async def marketplace_command(self, player_id, args):
488
+ """Show marketplace listings"""
489
+ # Demo marketplace listings - in a real implementation, this would query the trading system
490
+ demo_listings = [
491
+ "1. Iron Sword - 100 gold (seller: Alice)",
492
+ "2. Magic Potion - 50 gold (seller: Bob)",
493
+ "3. Dragon Scale - 500 gold (seller: Charlie)",
494
+ "4. Enchanted Ring - 250 gold (seller: Diana)"
495
+ ]
496
+
497
+ await self.send_to_player(player_id, "=== MARKETPLACE ===")
498
+ await self.send_to_player(player_id, "Current listings:")
499
+ for listing in demo_listings:
500
+ await self.send_to_player(player_id, f" {listing}")
501
+ await self.send_to_player(player_id, "Use '/trade create <item> <price>' to list an item")
502
+ await self.send_to_player(player_id, "Use '/trade accept <id>' to purchase an item")
503
+
504
+ async def trade_command(self, player_id, args):
505
+ """Handle trading commands"""
506
+ if not args:
507
+ await self.send_to_player(player_id, "Usage: /trade <create|accept> [arguments]")
508
+ return
509
+
510
+ parts = args.split(' ', 1)
511
+ action = parts[0].lower()
512
+
513
+ if action == 'create':
514
+ if len(parts) < 2:
515
+ await self.send_to_player(player_id, "Usage: /trade create <item> <price>")
516
+ return
517
+
518
+ # Parse item and price
519
+ create_args = parts[1].split(' ')
520
+ if len(create_args) < 2:
521
+ await self.send_to_player(player_id, "Usage: /trade create <item> <price>")
522
+ return
523
+
524
+ price = create_args[-1]
525
+ item = ' '.join(create_args[:-1])
526
+
527
+ await self.send_to_player(player_id, f"Created trade listing: {item} for {price} gold")
528
+ # In a real implementation, this would interact with the trading system plugin
529
+
530
+ elif action == 'accept':
531
+ if len(parts) < 2:
532
+ await self.send_to_player(player_id, "Usage: /trade accept <listing_id>")
533
+ return
534
+
535
+ listing_id = parts[1].strip()
536
+ await self.send_to_player(player_id, f"Attempting to purchase listing {listing_id}...")
537
+ # In a real implementation, this would process the trade
538
+
539
+ else:
540
+ await self.send_to_player(player_id, "Unknown trade action. Use 'create' or 'accept'.")
541
+
542
+ async def auction_command(self, player_id, args):
543
+ """Handle auction commands"""
544
+ if not args:
545
+ await self.send_to_player(player_id, "Usage: /auction create <item> <starting_price>")
546
+ return
547
+
548
+ parts = args.split(' ', 1)
549
+ action = parts[0].lower()
550
+
551
+ if action == 'create':
552
+ if len(parts) < 2:
553
+ await self.send_to_player(player_id, "Usage: /auction create <item> <starting_price>")
554
+ return
555
+
556
+ # Parse item and starting price
557
+ create_args = parts[1].split(' ')
558
+ if len(create_args) < 2:
559
+ await self.send_to_player(player_id, "Usage: /auction create <item> <starting_price>")
560
+ return
561
+
562
+ starting_price = create_args[-1]
563
+ item = ' '.join(create_args[:-1])
564
+
565
+ await self.send_to_player(player_id, f"Created auction: {item} starting at {starting_price} gold")
566
+ # In a real implementation, this would interact with the auction system
567
+
568
+ else:
569
+ await self.send_to_player(player_id, "Unknown auction action. Use 'create' to start an auction.")
570
+
571
+ # Helper methods
572
+ async def get_player_name(self, player_id):
573
+ """Get player name from player ID"""
574
+ # This should be implemented to get the actual player name
575
+ return f"Player{player_id}"
576
+
577
+ async def find_player_by_name(self, name):
578
+ """Find player ID by name"""
579
+ # This should be implemented to find players by name
580
+ # For now, return None if not found
581
+ return None
582
+
583
+ async def send_to_player(self, player_id, message):
584
+ """Send a message to a specific player"""
585
+ # This should be implemented to send messages to players
586
+ pass
plugins/enhanced_chat_plugin_fixed.py ADDED
@@ -0,0 +1,264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Enhanced Chat Plugin for MMORPG
3
+ Adds advanced chat features like emotes, channels, and chat commands
4
+ """
5
+
6
+ from src.interfaces.plugin_interfaces import IChatPlugin, PluginMetadata, PluginType
7
+ from typing import Dict, List, Any, Optional
8
+ import re
9
+ import time
10
+
11
+ class EnhancedChatPlugin(IChatPlugin):
12
+ """Plugin that enhances the chat system with emotes, channels, and commands."""
13
+
14
+ def __init__(self):
15
+ self._metadata = PluginMetadata(
16
+ id="enhanced_chat",
17
+ name="Enhanced Chat System",
18
+ version="1.2.0",
19
+ author="MMORPG Dev Team",
20
+ description="Adds emotes, chat channels, and advanced chat commands",
21
+ plugin_type=PluginType.SERVICE,
22
+ dependencies=[],
23
+ config={
24
+ "enable_emotes": True,
25
+ "enable_channels": True,
26
+ "max_message_length": 500,
27
+ "chat_cooldown": 1
28
+ }
29
+ )
30
+ self._enabled = False
31
+ self._chat_channels = {
32
+ "global": {"name": "Global", "color": "#ffffff"},
33
+ "trade": {"name": "Trade", "color": "#ffff00"},
34
+ "help": {"name": "Help", "color": "#00ff00"},
35
+ "guild": {"name": "Guild", "color": "#ff00ff"}
36
+ }
37
+ self._emotes = {
38
+ ":smile:": "😊", ":laugh:": "😂", ":wink:": "😉", ":heart:": "❤️",
39
+ ":thumbsup:": "👍", ":thumbsdown:": "👎", ":fire:": "🔥", ":star:": "⭐",
40
+ ":wave:": "👋", ":clap:": "👏", ":cry:": "😢", ":angry:": "😠",
41
+ ":surprised:": "😲", ":cool:": "😎", ":thinking:": "🤔", ":sleeping:": "😴"
42
+ }
43
+ self._player_cooldowns = {}
44
+
45
+ @property
46
+ def metadata(self) -> PluginMetadata:
47
+ return self._metadata
48
+
49
+ def initialize(self, context: Dict[str, Any]) -> bool:
50
+ """Initialize the enhanced chat plugin."""
51
+ try:
52
+ self._config = self._metadata.config
53
+ self._enabled = True
54
+ print(f"💬 Enhanced Chat Plugin initialized with {len(self._emotes)} emotes and {len(self._chat_channels)} channels")
55
+ return True
56
+ except Exception as e:
57
+ print(f"💬 Failed to initialize Enhanced Chat Plugin: {e}")
58
+ return False
59
+
60
+ def cleanup(self) -> None:
61
+ """Clean up chat plugin resources."""
62
+ self._enabled = False
63
+ self._player_cooldowns.clear()
64
+ print("💬 Enhanced Chat Plugin cleaned up")
65
+
66
+ def is_enabled(self) -> bool:
67
+ return self._enabled
68
+
69
+ def process_message(self, player_id: str, message: str, channel: str = "global") -> Dict[str, Any]:
70
+ """Process and enhance chat messages."""
71
+ if not self._enabled:
72
+ return {"original": message, "processed": message, "valid": True}
73
+
74
+ result = {
75
+ "original": message,
76
+ "processed": message,
77
+ "valid": True,
78
+ "channel": channel,
79
+ "error": None
80
+ }
81
+
82
+ # Check cooldown
83
+ if not self._check_cooldown(player_id):
84
+ result["valid"] = False
85
+ result["error"] = "Please wait before sending another message"
86
+ return result
87
+
88
+ # Check message length
89
+ max_length = self._config.get("max_message_length", 500)
90
+ if len(message) > max_length:
91
+ result["valid"] = False
92
+ result["error"] = f"Message too long (max {max_length} characters)"
93
+ return result
94
+
95
+ # Process chat commands
96
+ if message.startswith("/"):
97
+ return self._process_chat_command(player_id, message, channel)
98
+
99
+ # Process emotes
100
+ if self._config.get("enable_emotes", True):
101
+ result["processed"] = self._process_emotes(message)
102
+
103
+ # Validate and set channel
104
+ if self._config.get("enable_channels", True):
105
+ if channel not in self._chat_channels:
106
+ channel = "global"
107
+ result["channel"] = channel
108
+ result["channel_info"] = self._chat_channels[channel]
109
+
110
+ # Update cooldown
111
+ self._player_cooldowns[player_id] = time.time()
112
+
113
+ return result
114
+
115
+ def get_available_commands(self) -> List[Dict[str, str]]:
116
+ """Get list of available chat commands."""
117
+ if not self._enabled:
118
+ return []
119
+
120
+ commands = [
121
+ {"command": "/emotes", "description": "Show available emotes"},
122
+ {"command": "/channels", "description": "List available chat channels"},
123
+ {"command": "/join <channel>", "description": "Join a chat channel"},
124
+ {"command": "/who", "description": "List players in current channel"},
125
+ {"command": "/time", "description": "Show current server time"},
126
+ {"command": "/marketplace", "description": "View marketplace listings"},
127
+ {"command": "/trade create <player> <items>", "description": "Create a trade offer"},
128
+ {"command": "/trade accept <trade_id>", "description": "Accept a trade"},
129
+ {"command": "/auction create <item> <price>", "description": "Create an auction"},
130
+ {"command": "/help", "description": "Show this help message"}
131
+ ]
132
+
133
+ return commands
134
+
135
+ def get_chat_channels(self) -> Dict[str, Dict[str, str]]:
136
+ """Get available chat channels."""
137
+ if not self._enabled or not self._config.get("enable_channels", True):
138
+ return {"global": {"name": "Global", "color": "#ffffff"}}
139
+
140
+ return self._chat_channels.copy()
141
+
142
+ def get_emotes_list(self) -> Dict[str, str]:
143
+ """Get available emotes."""
144
+ if not self._enabled or not self._config.get("enable_emotes", True):
145
+ return {}
146
+
147
+ return self._emotes.copy()
148
+
149
+ def _check_cooldown(self, player_id: str) -> bool:
150
+ """Check if player is within chat cooldown."""
151
+ cooldown = self._config.get("chat_cooldown", 1)
152
+ if cooldown <= 0:
153
+ return True
154
+
155
+ last_message = self._player_cooldowns.get(player_id, 0)
156
+ return time.time() - last_message >= cooldown
157
+
158
+ def _process_emotes(self, message: str) -> str:
159
+ """Replace emote codes with emojis."""
160
+ processed = message
161
+ for emote_code, emoji in self._emotes.items():
162
+ processed = processed.replace(emote_code, emoji)
163
+ return processed
164
+
165
+ def _process_chat_command(self, player_id: str, message: str, channel: str) -> Dict[str, Any]:
166
+ """Process chat commands."""
167
+ parts = message.split()
168
+ command = parts[0].lower()
169
+
170
+ result = {
171
+ "original": message,
172
+ "processed": "",
173
+ "valid": True,
174
+ "channel": channel,
175
+ "is_command": True,
176
+ "command_response": ""
177
+ }
178
+
179
+ if command == "/emotes":
180
+ emote_list = ", ".join(self._emotes.keys())
181
+ result["command_response"] = f"Available emotes: {emote_list}"
182
+
183
+ elif command == "/channels":
184
+ channel_list = ", ".join([f"{k} ({v['name']})" for k, v in self._chat_channels.items()])
185
+ result["command_response"] = f"Available channels: {channel_list}"
186
+
187
+ elif command == "/join":
188
+ if len(parts) > 1:
189
+ target_channel = parts[1].lower()
190
+ if target_channel in self._chat_channels:
191
+ result["channel"] = target_channel
192
+ result["command_response"] = f"Joined channel: {self._chat_channels[target_channel]['name']}"
193
+ else:
194
+ result["command_response"] = f"Channel '{target_channel}' not found"
195
+ else:
196
+ result["command_response"] = "Usage: /join <channel>"
197
+
198
+ elif command == "/who":
199
+ result["command_response"] = "Player list functionality requires game integration"
200
+
201
+ elif command == "/time":
202
+ current_time = time.strftime("%H:%M:%S", time.localtime())
203
+ result["command_response"] = f"Server time: {current_time}"
204
+
205
+ elif command == "/marketplace":
206
+ result["command_response"] = "🏪 **Marketplace**\n\nDemo listings:\n• Iron Sword - 100 gold (Seller: PlayerX)\n• Health Potion - 25 gold (Seller: PlayerY)\n• Magic Staff - 350 gold (Seller: PlayerZ)\n\nUse `/trade create <player> <items>` to start trading!"
207
+
208
+ elif command == "/trade":
209
+ if len(parts) > 1:
210
+ action = parts[1].lower()
211
+ if action == "create":
212
+ result["command_response"] = "🤝 Trade creation system coming soon! For now, use direct player interaction."
213
+ elif action == "accept":
214
+ result["command_response"] = "✅ Trade acceptance system coming soon!"
215
+ else:
216
+ result["command_response"] = "Usage: /trade create <player> <items> or /trade accept <trade_id>"
217
+ else:
218
+ result["command_response"] = "Usage: /trade <create|accept> [parameters]"
219
+
220
+ elif command == "/auction":
221
+ if len(parts) > 1:
222
+ action = parts[1].lower()
223
+ if action == "create":
224
+ result["command_response"] = "🔨 Auction creation system coming soon!"
225
+ else:
226
+ result["command_response"] = "Usage: /auction create <item> <starting_price> <duration>"
227
+ else:
228
+ result["command_response"] = "Usage: /auction create <item> <starting_price> <duration>"
229
+
230
+ elif command == "/help":
231
+ commands = self.get_available_commands()
232
+ help_text = "Available commands:\n" + "\n".join([f"{cmd['command']} - {cmd['description']}" for cmd in commands])
233
+ result["command_response"] = help_text
234
+
235
+ else:
236
+ result["valid"] = False
237
+ result["command_response"] = f"Unknown command: {command}. Type /help for available commands."
238
+
239
+ return result
240
+
241
+ def get_status(self) -> Dict[str, Any]:
242
+ """Get the current status of the enhanced chat plugin."""
243
+ return {
244
+ "enabled": self._enabled,
245
+ "total_emotes": len(self._emotes),
246
+ "total_channels": len(self._chat_channels),
247
+ "active_cooldowns": len(self._player_cooldowns)
248
+ }
249
+
250
+ def shutdown(self) -> bool:
251
+ """Shutdown the enhanced chat plugin."""
252
+ try:
253
+ self._enabled = False
254
+ self._player_cooldowns.clear()
255
+ print("💬 Enhanced Chat Plugin shutdown")
256
+ return True
257
+ except Exception as e:
258
+ print(f"💬 Error shutting down Enhanced Chat Plugin: {e}")
259
+ return False
260
+
261
+ # Plugin entry point
262
+ def create_plugin():
263
+ """Factory function to create the plugin instance."""
264
+ return EnhancedChatPlugin()
plugins/plugin_registry.conf ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Plugin Registry - Sample Configuration
3
+ This file demonstrates how plugins can be configured and loaded
4
+ """
5
+
6
+ # Plugin configuration registry
7
+ PLUGIN_REGISTRY = {
8
+ "weather_plugin": {
9
+ "enabled": True,
10
+ "file": "weather_plugin.py",
11
+ "config": {
12
+ "weather_change_interval": 180, # Change weather every 3 minutes
13
+ "enable_seasons": True,
14
+ "weather_effects_enabled": True
15
+ },
16
+ "auto_load": True,
17
+ "priority": 1
18
+ },
19
+
20
+ "enhanced_chat_plugin": {
21
+ "enabled": True,
22
+ "file": "enhanced_chat_plugin.py",
23
+ "config": {
24
+ "enable_emotes": True,
25
+ "enable_channels": True,
26
+ "max_message_length": 300,
27
+ "chat_cooldown": 2 # 2 second cooldown between messages
28
+ },
29
+ "auto_load": True,
30
+ "priority": 2
31
+ },
32
+
33
+ "trading_system_plugin": {
34
+ "enabled": True,
35
+ "file": "trading_system_plugin.py",
36
+ "config": {
37
+ "enable_direct_trading": True,
38
+ "enable_marketplace": True,
39
+ "trade_tax_rate": 0.03, # 3% tax on marketplace trades
40
+ "max_trade_distance": 150
41
+ },
42
+ "auto_load": True,
43
+ "priority": 3,
44
+ "dependencies": ["enhanced_chat_plugin"]
45
+ }
46
+ }
47
+
48
+ # Plugin load order (based on dependencies and priority)
49
+ LOAD_ORDER = [
50
+ "enhanced_chat_plugin", # No dependencies
51
+ "weather_plugin", # No dependencies
52
+ "trading_system_plugin" # Depends on enhanced_chat
53
+ ]
54
+
55
+ # Default plugin directory
56
+ PLUGIN_DIRECTORY = "plugins"
57
+
58
+ # Plugin file extension
59
+ PLUGIN_EXTENSION = ".py"
60
+
61
+ # Plugin factory function name
62
+ PLUGIN_FACTORY_FUNCTION = "create_plugin"
plugins/sample_plugin.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Sample plugin demonstrating the plugin system.
3
+ """
4
+
5
+ from src.interfaces.plugin_interfaces import IPlugin, PluginMetadata, PluginType
6
+ from typing import Dict, Any
7
+
8
+
9
+ class SamplePlugin(IPlugin):
10
+ """Sample plugin implementation."""
11
+
12
+ @property
13
+ def metadata(self) -> PluginMetadata:
14
+ return PluginMetadata(
15
+ id="sample_plugin",
16
+ name="Sample Plugin",
17
+ version="1.0.0",
18
+ description="A sample plugin demonstrating the plugin system",
19
+ author="MMORPG System",
20
+ plugin_type=PluginType.EVENT,
21
+ dependencies=[],
22
+ config={}
23
+ )
24
+
25
+ def initialize(self, context: Dict[str, Any]) -> bool:
26
+ """Initialize the plugin."""
27
+ self.context = context
28
+ print("[SamplePlugin] Plugin initialized!")
29
+ return True
30
+
31
+ def shutdown(self) -> bool:
32
+ """Shutdown the plugin."""
33
+ print("[SamplePlugin] Plugin shutdown!")
34
+ return True
35
+
36
+ def get_status(self) -> Dict[str, Any]:
37
+ """Get plugin status."""
38
+ return {
39
+ "status": "active",
40
+ "message": "Sample plugin is running normally"
41
+ }
42
+
43
+ def on_player_join(self, player_id: str) -> None:
44
+ """Called when a player joins."""
45
+ print(f"[SamplePlugin] Player {player_id} joined!")
46
+
47
+ def on_player_leave(self, player_id: str) -> None:
48
+ """Called when a player leaves."""
49
+ print(f"[SamplePlugin] Player {player_id} left!")
50
+
51
+ def on_chat_message(self, sender_id: str, message: str, message_type: str) -> None:
52
+ """Called when a chat message is sent."""
53
+ if "hello plugin" in message.lower():
54
+ print(f"[SamplePlugin] Detected greeting from {sender_id}")
plugins/trading_system_plugin.py ADDED
@@ -0,0 +1,378 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Trading System Plugin for MMORPG
3
+ Adds player-to-player trading and marketplace functionality
4
+ """
5
+
6
+ from src.interfaces.plugin_interfaces import IEconomyPlugin, PluginMetadata, PluginType
7
+ from typing import Dict, List, Any, Optional
8
+ import time
9
+ import uuid
10
+
11
+ class TradingSystemPlugin(IEconomyPlugin):
12
+ """Plugin that adds comprehensive trading and marketplace features."""
13
+
14
+ def __init__(self):
15
+ self._metadata = PluginMetadata(
16
+ id="trading_system",
17
+ name="Trading System",
18
+ version="1.1.0",
19
+ author="MMORPG Dev Team",
20
+ description="Adds player trading, marketplace, and auction features",
21
+ plugin_type=PluginType.SERVICE,
22
+ dependencies=["enhanced_chat"], # Depends on chat for trade messages
23
+ config={
24
+ "enable_direct_trading": True,
25
+ "enable_marketplace": True,
26
+ "trade_tax_rate": 0.05,
27
+ "max_trade_distance": 100
28
+ }
29
+ )
30
+ self._enabled = False
31
+ self._active_trades = {} # Trade sessions
32
+ self._marketplace_listings = {} # Marketplace items
33
+ self._trade_history = []
34
+ # Sample items for trading
35
+ self._tradeable_items = {
36
+ "health_potion": {"name": "Health Potion", "base_value": 10, "stackable": True},
37
+ "mana_potion": {"name": "Mana Potion", "base_value": 15, "stackable": True},
38
+ "iron_sword": {"name": "Iron Sword", "base_value": 50, "stackable": False},
39
+ "wooden_shield": {"name": "Wooden Shield", "base_value": 30, "stackable": False},
40
+ "magic_scroll": {"name": "Magic Scroll", "base_value": 25, "stackable": True},
41
+ "gold_coin": {"name": "Gold Coin", "base_value": 1, "stackable": True} }
42
+
43
+ @property
44
+ def metadata(self) -> PluginMetadata:
45
+ return self._metadata
46
+
47
+ def initialize(self, context: Dict[str, Any]) -> bool:
48
+ """Initialize the trading system plugin."""
49
+ try:
50
+ self._config = self._metadata.config
51
+ self._enabled = True
52
+ print(f"🏪 Trading System Plugin initialized with {len(self._tradeable_items)} tradeable items")
53
+ return True
54
+ except Exception as e:
55
+ print(f"🏪 Failed to initialize Trading System Plugin: {e}")
56
+ return False
57
+
58
+ def cleanup(self) -> None:
59
+ """Clean up trading system resources."""
60
+ # Cancel all active trades
61
+ for trade_id in list(self._active_trades.keys()):
62
+ self.cancel_trade(trade_id)
63
+
64
+ self._enabled = False
65
+ print("💰 Trading System Plugin cleaned up")
66
+
67
+ def is_enabled(self) -> bool:
68
+ return self._enabled
69
+
70
+ def initiate_trade(self, player1_id: str, player2_id: str, player1_pos: tuple, player2_pos: tuple) -> Dict[str, Any]:
71
+ """Initiate a trade between two players."""
72
+ if not self._enabled or not self._config.get("enable_direct_trading", True):
73
+ return {"success": False, "error": "Trading is disabled"}
74
+
75
+ # Check distance
76
+ max_distance = self._config.get("max_trade_distance", 100)
77
+ distance = ((player1_pos[0] - player2_pos[0]) ** 2 + (player1_pos[1] - player2_pos[1]) ** 2) ** 0.5
78
+
79
+ if distance > max_distance:
80
+ return {"success": False, "error": "Players are too far apart to trade"}
81
+
82
+ # Create trade session
83
+ trade_id = str(uuid.uuid4())
84
+ trade_session = {
85
+ "id": trade_id,
86
+ "player1": player1_id,
87
+ "player2": player2_id,
88
+ "player1_items": {},
89
+ "player2_items": {},
90
+ "player1_gold": 0,
91
+ "player2_gold": 0,
92
+ "player1_accepted": False,
93
+ "player2_accepted": False,
94
+ "status": "active",
95
+ "created_at": time.time()
96
+ }
97
+
98
+ self._active_trades[trade_id] = trade_session
99
+
100
+ return {
101
+ "success": True,
102
+ "trade_id": trade_id,
103
+ "message": f"Trade initiated between players {player1_id} and {player2_id}"
104
+ }
105
+
106
+ def add_item_to_trade(self, trade_id: str, player_id: str, item_id: str, quantity: int = 1) -> Dict[str, Any]:
107
+ """Add an item to a trade."""
108
+ if trade_id not in self._active_trades:
109
+ return {"success": False, "error": "Trade not found"}
110
+
111
+ trade = self._active_trades[trade_id]
112
+
113
+ if trade["status"] != "active":
114
+ return {"success": False, "error": "Trade is not active"}
115
+
116
+ # Determine which player
117
+ if player_id == trade["player1"]:
118
+ items_dict = trade["player1_items"]
119
+ elif player_id == trade["player2"]:
120
+ items_dict = trade["player2_items"]
121
+ else:
122
+ return {"success": False, "error": "Player not part of this trade"}
123
+
124
+ # Add item
125
+ if item_id not in items_dict:
126
+ items_dict[item_id] = 0
127
+ items_dict[item_id] += quantity
128
+
129
+ # Reset acceptance status
130
+ trade["player1_accepted"] = False
131
+ trade["player2_accepted"] = False
132
+
133
+ return {
134
+ "success": True,
135
+ "message": f"Added {quantity}x {self._tradeable_items.get(item_id, {}).get('name', item_id)} to trade"
136
+ }
137
+
138
+ def add_gold_to_trade(self, trade_id: str, player_id: str, amount: int) -> Dict[str, Any]:
139
+ """Add gold to a trade."""
140
+ if trade_id not in self._active_trades:
141
+ return {"success": False, "error": "Trade not found"}
142
+
143
+ trade = self._active_trades[trade_id]
144
+
145
+ if trade["status"] != "active":
146
+ return {"success": False, "error": "Trade is not active"}
147
+
148
+ # Determine which player and update gold
149
+ if player_id == trade["player1"]:
150
+ trade["player1_gold"] += amount
151
+ elif player_id == trade["player2"]:
152
+ trade["player2_gold"] += amount
153
+ else:
154
+ return {"success": False, "error": "Player not part of this trade"}
155
+
156
+ # Reset acceptance status
157
+ trade["player1_accepted"] = False
158
+ trade["player2_accepted"] = False
159
+
160
+ return {"success": True, "message": f"Added {amount} gold to trade"}
161
+
162
+ def accept_trade(self, trade_id: str, player_id: str) -> Dict[str, Any]:
163
+ """Accept a trade."""
164
+ if trade_id not in self._active_trades:
165
+ return {"success": False, "error": "Trade not found"}
166
+
167
+ trade = self._active_trades[trade_id]
168
+
169
+ if trade["status"] != "active":
170
+ return {"success": False, "error": "Trade is not active"}
171
+
172
+ # Mark player as accepted
173
+ if player_id == trade["player1"]:
174
+ trade["player1_accepted"] = True
175
+ elif player_id == trade["player2"]:
176
+ trade["player2_accepted"] = True
177
+ else:
178
+ return {"success": False, "error": "Player not part of this trade"}
179
+
180
+ # Check if both players accepted
181
+ if trade["player1_accepted"] and trade["player2_accepted"]:
182
+ return self._complete_trade(trade_id)
183
+
184
+ return {"success": True, "message": "Trade accepted, waiting for other player"}
185
+
186
+ def cancel_trade(self, trade_id: str) -> Dict[str, Any]:
187
+ """Cancel a trade."""
188
+ if trade_id not in self._active_trades:
189
+ return {"success": False, "error": "Trade not found"}
190
+
191
+ trade = self._active_trades[trade_id]
192
+ trade["status"] = "cancelled"
193
+
194
+ del self._active_trades[trade_id]
195
+
196
+ return {"success": True, "message": "Trade cancelled"}
197
+
198
+ def get_trade_status(self, trade_id: str) -> Dict[str, Any]:
199
+ """Get the status of a trade."""
200
+ if trade_id not in self._active_trades:
201
+ return {"success": False, "error": "Trade not found"}
202
+
203
+ trade = self._active_trades[trade_id]
204
+
205
+ return {
206
+ "success": True,
207
+ "trade": {
208
+ "id": trade["id"],
209
+ "status": trade["status"],
210
+ "player1": trade["player1"],
211
+ "player2": trade["player2"],
212
+ "player1_items": trade["player1_items"],
213
+ "player2_items": trade["player2_items"],
214
+ "player1_gold": trade["player1_gold"],
215
+ "player2_gold": trade["player2_gold"],
216
+ "player1_accepted": trade["player1_accepted"],
217
+ "player2_accepted": trade["player2_accepted"]
218
+ }
219
+ }
220
+
221
+ def create_marketplace_listing(self, player_id: str, item_id: str, quantity: int, price: int) -> Dict[str, Any]:
222
+ """Create a marketplace listing."""
223
+ if not self._enabled or not self._config.get("enable_marketplace", True):
224
+ return {"success": False, "error": "Marketplace is disabled"}
225
+
226
+ listing_id = str(uuid.uuid4())
227
+ listing = {
228
+ "id": listing_id,
229
+ "seller_id": player_id,
230
+ "item_id": item_id,
231
+ "quantity": quantity,
232
+ "price": price,
233
+ "created_at": time.time(),
234
+ "status": "active"
235
+ }
236
+
237
+ self._marketplace_listings[listing_id] = listing
238
+
239
+ item_name = self._tradeable_items.get(item_id, {}).get("name", item_id)
240
+
241
+ return {
242
+ "success": True,
243
+ "listing_id": listing_id,
244
+ "message": f"Listed {quantity}x {item_name} for {price} gold"
245
+ }
246
+
247
+ def get_marketplace_listings(self, item_filter: Optional[str] = None) -> List[Dict[str, Any]]:
248
+ """Get marketplace listings."""
249
+ if not self._enabled or not self._config.get("enable_marketplace", True):
250
+ return []
251
+
252
+ listings = []
253
+ for listing in self._marketplace_listings.values():
254
+ if listing["status"] != "active":
255
+ continue
256
+
257
+ if item_filter and listing["item_id"] != item_filter:
258
+ continue
259
+
260
+ item_info = self._tradeable_items.get(listing["item_id"], {})
261
+ listing_info = {
262
+ "id": listing["id"],
263
+ "seller_id": listing["seller_id"],
264
+ "item_id": listing["item_id"],
265
+ "item_name": item_info.get("name", listing["item_id"]),
266
+ "quantity": listing["quantity"],
267
+ "price": listing["price"],
268
+ "price_per_unit": listing["price"] / listing["quantity"],
269
+ "created_at": listing["created_at"]
270
+ }
271
+ listings.append(listing_info)
272
+
273
+ # Sort by price per unit
274
+ listings.sort(key=lambda x: x["price_per_unit"])
275
+
276
+ return listings
277
+
278
+ def purchase_from_marketplace(self, buyer_id: str, listing_id: str) -> Dict[str, Any]:
279
+ """Purchase an item from the marketplace."""
280
+ if listing_id not in self._marketplace_listings:
281
+ return {"success": False, "error": "Listing not found"}
282
+
283
+ listing = self._marketplace_listings[listing_id]
284
+
285
+ if listing["status"] != "active":
286
+ return {"success": False, "error": "Listing is no longer active"}
287
+
288
+ if listing["seller_id"] == buyer_id:
289
+ return {"success": False, "error": "Cannot buy your own listing"}
290
+
291
+ # Calculate tax
292
+ tax_rate = self._config.get("trade_tax_rate", 0.05)
293
+ tax_amount = int(listing["price"] * tax_rate)
294
+ seller_receives = listing["price"] - tax_amount
295
+
296
+ # Mark listing as sold
297
+ listing["status"] = "sold"
298
+ listing["buyer_id"] = buyer_id
299
+ listing["sold_at"] = time.time()
300
+
301
+ # Add to trade history
302
+ trade_record = {
303
+ "type": "marketplace",
304
+ "seller_id": listing["seller_id"],
305
+ "buyer_id": buyer_id,
306
+ "item_id": listing["item_id"],
307
+ "quantity": listing["quantity"],
308
+ "price": listing["price"],
309
+ "tax_amount": tax_amount,
310
+ "timestamp": time.time()
311
+ }
312
+ self._trade_history.append(trade_record)
313
+
314
+ item_name = self._tradeable_items.get(listing["item_id"], {}).get("name", listing["item_id"])
315
+
316
+ return {
317
+ "success": True,
318
+ "message": f"Purchased {listing['quantity']}x {item_name} for {listing['price']} gold",
319
+ "seller_receives": seller_receives,
320
+ "tax_paid": tax_amount
321
+ }
322
+
323
+ def get_tradeable_items(self) -> Dict[str, Dict[str, Any]]:
324
+ """Get list of tradeable items."""
325
+ return self._tradeable_items.copy()
326
+
327
+ def _complete_trade(self, trade_id: str) -> Dict[str, Any]:
328
+ """Complete a trade between two players."""
329
+ trade = self._active_trades[trade_id]
330
+
331
+ # Mark trade as completed
332
+ trade["status"] = "completed"
333
+ trade["completed_at"] = time.time()
334
+ # Add to trade history
335
+ trade_record = {
336
+ "type": "direct_trade",
337
+ "player1": trade["player1"],
338
+ "player2": trade["player2"],
339
+ "player1_items": trade["player1_items"],
340
+ "player2_items": trade["player2_items"],
341
+ "player1_gold": trade["player1_gold"],
342
+ "player2_gold": trade["player2_gold"],
343
+ "timestamp": time.time()
344
+ }
345
+ self._trade_history.append(trade_record)
346
+
347
+ # Remove from active trades
348
+ del self._active_trades[trade_id]
349
+
350
+ return {
351
+ "success": True,
352
+ "message": "Trade completed successfully!",
353
+ "trade_summary": trade_record
354
+ }
355
+
356
+ def get_status(self) -> Dict[str, Any]:
357
+ """Get trading system status."""
358
+ return {
359
+ "enabled": self._enabled,
360
+ "active_trades": len(self._active_trades),
361
+ "marketplace_listings": len(self._marketplace_listings),
362
+ "total_items": len(self._tradeable_items),
363
+ "trade_history_count": len(self._trade_history)
364
+ }
365
+
366
+ def shutdown(self) -> bool:
367
+ """Shutdown the trading system."""
368
+ try:
369
+ self.cleanup()
370
+ return True
371
+ except Exception as e:
372
+ print(f"🏪 Error shutting down Trading System Plugin: {e}")
373
+ return False
374
+
375
+ # Plugin entry point
376
+ def create_plugin():
377
+ """Factory function to create the plugin instance."""
378
+ return TradingSystemPlugin()
plugins/weather_plugin.py ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Sample Weather Plugin for MMORPG
3
+ Adds weather effects and seasonal changes to the game world
4
+ """
5
+
6
+ from src.interfaces.plugin_interfaces import IWeatherPlugin, PluginMetadata, PluginType
7
+ from typing import Dict, List, Any
8
+ import random
9
+ import time
10
+
11
+ class WeatherPlugin(IWeatherPlugin):
12
+ """Plugin that adds dynamic weather system to the game."""
13
+
14
+ def __init__(self):
15
+ self._metadata = PluginMetadata(
16
+ id="weather_system",
17
+ name="Weather System",
18
+ version="1.0.0",
19
+ author="MMORPG Dev Team",
20
+ description="Adds dynamic weather effects and seasonal changes",
21
+ plugin_type=PluginType.SERVICE,
22
+ dependencies=[],
23
+ config={
24
+ "weather_change_interval": 300,
25
+ "enable_seasons": True,
26
+ "weather_effects_enabled": True
27
+ }
28
+ )
29
+ self._enabled = False
30
+ self._weather_state = {
31
+ "current_weather": "sunny",
32
+ "temperature": 20,
33
+ "season": "spring",
34
+ "last_change": time.time()
35
+ }
36
+ self._weather_types = [
37
+ "sunny", "cloudy", "rainy", "stormy", "foggy", "snowy"
38
+ ]
39
+
40
+ @property
41
+ def metadata(self) -> PluginMetadata:
42
+ return self._metadata
43
+
44
+ def initialize(self, context: Dict[str, Any]) -> bool:
45
+ """Initialize the weather plugin."""
46
+ try:
47
+ self._config = self._metadata.config
48
+ self._enabled = True
49
+ print("Weather Plugin initialized successfully")
50
+ self._update_weather()
51
+ return True
52
+ except Exception as e:
53
+ print(f"Failed to initialize Weather Plugin: {e}")
54
+ return False
55
+
56
+ def shutdown(self) -> bool:
57
+ """Shutdown the weather plugin."""
58
+ try:
59
+ self._enabled = False
60
+ print("Weather Plugin shut down")
61
+ return True
62
+ except Exception as e:
63
+ print(f"Error shutting down Weather Plugin: {e}")
64
+ return False
65
+
66
+ def get_status(self) -> Dict[str, Any]:
67
+ """Get weather plugin status."""
68
+ return {
69
+ "enabled": self._enabled,
70
+ "current_weather": self._weather_state["current_weather"],
71
+ "temperature": self._weather_state["temperature"],
72
+ "season": self._weather_state["season"],
73
+ "effects_enabled": self._config.get("weather_effects_enabled", True) if hasattr(self, '_config') else True
74
+ }
75
+
76
+ def get_weather_info(self) -> Dict[str, Any]:
77
+ """Get current weather information."""
78
+ if not self._enabled:
79
+ return {}
80
+
81
+ return {
82
+ "weather": self._weather_state["current_weather"],
83
+ "temperature": self._weather_state["temperature"],
84
+ "season": self._weather_state["season"],
85
+ "description": self._get_weather_description()
86
+ }
87
+
88
+ def update_weather(self) -> Dict[str, Any]:
89
+ """Update weather conditions."""
90
+ if not self._enabled:
91
+ return {}
92
+
93
+ current_time = time.time()
94
+ interval = self._config.get("weather_change_interval", 300)
95
+
96
+ if current_time - self._weather_state["last_change"] > interval:
97
+ self._update_weather()
98
+ self._weather_state["last_change"] = current_time
99
+
100
+ return self.get_weather_info()
101
+
102
+ def apply_weather_effects(self, player_data: Dict[str, Any]) -> Dict[str, Any]:
103
+ """Apply weather effects to player."""
104
+ if not self._enabled or not self._config.get("weather_effects_enabled", True):
105
+ return player_data
106
+
107
+ effects = player_data.copy()
108
+ weather_modifier = self._get_weather_modifier()
109
+
110
+ if "stats" not in effects:
111
+ effects["stats"] = {}
112
+
113
+ effects["stats"]["weather_modifier"] = weather_modifier
114
+ effects["weather_description"] = self._get_weather_description()
115
+
116
+ return effects
117
+
118
+ def _update_weather(self):
119
+ """Internal method to update weather conditions."""
120
+ self._weather_state["current_weather"] = random.choice(self._weather_types)
121
+
122
+ base_temp = self._get_seasonal_base_temperature()
123
+ weather_modifier = self._get_weather_temperature_modifier()
124
+ self._weather_state["temperature"] = base_temp + weather_modifier + random.randint(-5, 5)
125
+
126
+ if self._config.get("enable_seasons", True):
127
+ seasons = ["spring", "summer", "autumn", "winter"]
128
+ if random.random() < 0.01:
129
+ current_season_idx = seasons.index(self._weather_state["season"])
130
+ self._weather_state["season"] = seasons[(current_season_idx + 1) % len(seasons)]
131
+
132
+ def _get_seasonal_base_temperature(self) -> int:
133
+ """Get base temperature for current season."""
134
+ season_temps = {
135
+ "spring": 15,
136
+ "summer": 25,
137
+ "autumn": 10,
138
+ "winter": 0
139
+ }
140
+ return season_temps.get(self._weather_state["season"], 15)
141
+
142
+ def _get_weather_temperature_modifier(self) -> int:
143
+ """Get temperature modifier based on weather."""
144
+ weather_mods = {
145
+ "sunny": 5,
146
+ "cloudy": 0,
147
+ "rainy": -3,
148
+ "stormy": -5,
149
+ "foggy": -2,
150
+ "snowy": -10
151
+ }
152
+ return weather_mods.get(self._weather_state["current_weather"], 0)
153
+
154
+ def _get_weather_description(self) -> str:
155
+ """Get descriptive text for current weather."""
156
+ weather = self._weather_state["current_weather"]
157
+ temp = self._weather_state["temperature"]
158
+ season = self._weather_state["season"]
159
+
160
+ descriptions = {
161
+ "sunny": f"Bright and sunny {season} day ({temp} degrees C)",
162
+ "cloudy": f"Overcast {season} sky ({temp} degrees C)",
163
+ "rainy": f"Light rain falling in {season} ({temp} degrees C)",
164
+ "stormy": f"Thunderstorm brewing this {season} ({temp} degrees C)",
165
+ "foggy": f"Thick fog blankets the {season} landscape ({temp} degrees C)",
166
+ "snowy": f"Snow falling gently in {season} ({temp} degrees C)"
167
+ }
168
+ return descriptions.get(weather, f"Unknown weather in {season} ({temp} degrees C)")
169
+
170
+ def _get_weather_modifier(self) -> Dict[str, float]:
171
+ """Get weather-based stat modifiers."""
172
+ weather = self._weather_state["current_weather"]
173
+
174
+ modifiers = {
175
+ "sunny": {"movement_speed": 1.1, "visibility": 1.0, "morale": 1.1},
176
+ "cloudy": {"movement_speed": 1.0, "visibility": 0.95, "morale": 0.95},
177
+ "rainy": {"movement_speed": 0.8, "visibility": 0.9, "morale": 0.9},
178
+ "stormy": {"movement_speed": 0.6, "visibility": 0.7, "morale": 0.8},
179
+ "foggy": {"movement_speed": 0.9, "visibility": 0.5, "morale": 0.85},
180
+ "snowy": {"movement_speed": 0.7, "visibility": 0.8, "morale": 0.9}
181
+ }
182
+
183
+ return modifiers.get(weather, {"movement_speed": 1.0, "visibility": 1.0, "morale": 1.0})
184
+
185
+ def create_plugin():
186
+ """Factory function to create the plugin instance."""
187
+ return WeatherPlugin()
pytest.ini ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [tool:pytest]
2
+ markers =
3
+ stress: marks tests as stress tests (deselect with '-m "not stress"')
4
+ error_handling: marks tests as error handling tests
5
+ integration: marks tests as integration tests
6
+ browser: marks tests as browser tests (may require selenium)
7
+ slow: marks tests as slow running
8
+ e2e: marks tests as end-to-end tests
9
+ timeout: marks tests that need timeout protection
10
+
11
+ testpaths = tests
12
+ python_files = test_*.py
13
+ python_classes = Test*
14
+ python_functions = test_*
15
+
16
+ # Default timeout for tests
17
+ timeout = 60
18
+
19
+ # Ignore deprecation warnings from external libraries
20
+ filterwarnings =
21
+ ignore::DeprecationWarning:websockets.*
22
+ ignore::pytest.PytestCollectionWarning
23
+ ignore::pytest.PytestUnknownMarkWarning
24
+
25
+ # Set asyncio default loop scope
26
+ addopts =
27
+ --timeout=60
28
+ --timeout-method=thread
29
+ -v
30
+ --tb=short
quick_test.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Quick verification that the world events auto-refresh fix is working
4
+ """
5
+
6
+ import sys
7
+ import os
8
+ sys.path.append(os.path.dirname(os.path.abspath(__file__)))
9
+
10
+ def test_world_events_fix():
11
+ """Quick test of the world events fix"""
12
+ try:
13
+ from src.facades.game_facade import GameFacade
14
+ from src.ui.interface_manager import InterfaceManager
15
+
16
+ print("✅ Import successful")
17
+
18
+ # Test GameFacade has get_world_events method
19
+ facade = GameFacade()
20
+ if hasattr(facade, 'get_world_events'):
21
+ print("✅ GameFacade.get_world_events() method exists")
22
+
23
+ events = facade.get_world_events()
24
+ print(f"✅ Method returns {len(events)} events")
25
+ else:
26
+ print("❌ GameFacade.get_world_events() method missing")
27
+ return False
28
+
29
+ return True
30
+
31
+ except Exception as e:
32
+ print(f"❌ Error: {e}")
33
+ return False
34
+
35
+ if __name__ == "__main__":
36
+ print("🔧 QUICK WORLD EVENTS FIX VERIFICATION")
37
+ print("=" * 50)
38
+
39
+ if test_world_events_fix():
40
+ print("\n🎉 World events auto-refresh fix is working!")
41
+ print("📋 Summary of what was fixed:")
42
+ print("✅ Added 'world_events' as 8th output in timer configuration")
43
+ print("✅ Fixed mismatch between timer outputs (7) and method returns (8)")
44
+ print("✅ World events panel will now update automatically")
45
+ print("\n🌐 Server should be running at: http://localhost:7866")
46
+ else:
47
+ print("\n❌ There are still issues with the fix")
requirements.txt ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MMOP Second Try - Requirements File
2
+ # MMORPG with Gradio UI, MCP Integration, and Plugin System
3
+
4
+ # Core Web Framework & UI
5
+ gradio>=4.0.0
6
+ gradio[mcp]
7
+
8
+ # Data Processing & Math
9
+ numpy>=1.20.0
10
+ pandas>=1.3.0
11
+
12
+ # Development & Testing
13
+ pytest>=7.0.0
14
+ pytest-asyncio>=0.21.0
15
+
16
+ # Optional Dependencies (uncomment as needed)
17
+
18
+ # For enhanced logging and debugging
19
+ # colorlog>=6.0.0
20
+
21
+ # For configuration management
22
+ # pyyaml>=6.0
23
+ # python-dotenv>=0.19.0
24
+
25
+ # For HTTP/API support (if implementing REST APIs)
26
+ # fastapi>=0.68.0
27
+ # uvicorn>=0.15.0
28
+
29
+ # For advanced text processing
30
+ # regex>=2021.8.3
31
+
32
+ # Development Tools (for code quality)
33
+ # black>=22.0.0
34
+ # isort>=5.10.0
35
+ # flake8>=4.0.0
36
+ # mypy>=0.910
37
+
38
+ # Note: The following are included in Python 3.13 standard library:
39
+ # - dataclasses (built-in since Python 3.7)
40
+ # - typing (built-in)
41
+ # - asyncio (built-in)
42
+ # - threading (built-in)
43
+ # - uuid (built-in)
44
+ # - json (built-in)
45
+ # - pathlib (built-in)
46
+ # - os (built-in)
47
+ # - time (built-in)
48
+ # - random (built-in)
49
+ # - math (built-in)
50
+ # - hashlib (built-in)
51
+ # - importlib (built-in)
52
+ # - inspect (built-in)
53
+ # - sqlite3 (built-in)
setup.bat ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @echo off
2
+ REM MMOP Second Try - Windows Setup Script
3
+ REM This script sets up the MMORPG environment on Windows
4
+
5
+ echo.
6
+ echo ========================================
7
+ echo MMOP Second Try - Setup Script
8
+ echo ========================================
9
+ echo.
10
+
11
+ REM Check if Python is available
12
+ python --version >nul 2>&1
13
+ if errorlevel 1 (
14
+ echo ERROR: Python is not installed or not in PATH
15
+ echo Please install Python 3.8 or higher from python.org
16
+ pause
17
+ exit /b 1
18
+ )
19
+
20
+ echo Python found. Starting setup...
21
+ echo.
22
+
23
+ REM Run the Python setup script
24
+ python setup.py
25
+
26
+ echo.
27
+ echo ========================================
28
+ echo Setup completed!
29
+ echo.
30
+ echo To run the MMORPG:
31
+ echo python app.py
32
+ echo.
33
+ echo Then open your browser to the displayed URL
34
+ echo ========================================
35
+ echo.
36
+ pause
setup.ps1 ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MMOP Second Try - PowerShell Setup Script
2
+ # This script sets up the MMORPG environment using PowerShell
3
+
4
+ Write-Host ""
5
+ Write-Host "========================================" -ForegroundColor Cyan
6
+ Write-Host " MMOP Second Try - Setup Script" -ForegroundColor Cyan
7
+ Write-Host "========================================" -ForegroundColor Cyan
8
+ Write-Host ""
9
+
10
+ # Check if Python is available
11
+ try {
12
+ $pythonVersion = python --version 2>&1
13
+ Write-Host "✅ $pythonVersion found" -ForegroundColor Green
14
+ } catch {
15
+ Write-Host "❌ ERROR: Python is not installed or not in PATH" -ForegroundColor Red
16
+ Write-Host "Please install Python 3.8 or higher from python.org" -ForegroundColor Yellow
17
+ Read-Host "Press Enter to exit"
18
+ exit 1
19
+ }
20
+
21
+ Write-Host "🔧 Starting setup..." -ForegroundColor Yellow
22
+ Write-Host ""
23
+
24
+ # Run the Python setup script
25
+ try {
26
+ python setup.py
27
+ Write-Host ""
28
+ Write-Host "========================================" -ForegroundColor Green
29
+ Write-Host "🎉 Setup completed successfully!" -ForegroundColor Green
30
+ Write-Host ""
31
+ Write-Host "To run the MMORPG:" -ForegroundColor Cyan
32
+ Write-Host " python app.py" -ForegroundColor White
33
+ Write-Host ""
34
+ Write-Host "Then open your browser to the displayed URL" -ForegroundColor Cyan
35
+ Write-Host "========================================" -ForegroundColor Green
36
+ } catch {
37
+ Write-Host "❌ Setup failed. Please check the error messages above." -ForegroundColor Red
38
+ }
39
+
40
+ Write-Host ""
41
+ Read-Host "Press Enter to continue"
setup.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Setup script for MMOP Second Try
4
+ Installs dependencies and verifies the installation
5
+ """
6
+
7
+ import subprocess
8
+ import sys
9
+ import os
10
+ from pathlib import Path
11
+
12
+ def run_command(command, description):
13
+ """Run a command and handle errors"""
14
+ print(f"\n{'='*50}")
15
+ print(f"🔧 {description}")
16
+ print(f"{'='*50}")
17
+
18
+ try:
19
+ result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True)
20
+ print(f"✅ {description} completed successfully")
21
+ if result.stdout:
22
+ print(f"Output: {result.stdout}")
23
+ return True
24
+ except subprocess.CalledProcessError as e:
25
+ print(f"❌ {description} failed")
26
+ print(f"Error: {e.stderr}")
27
+ return False
28
+
29
+ def check_python_version():
30
+ """Check if Python version is suitable"""
31
+ print(f"🐍 Python version: {sys.version}")
32
+ if sys.version_info < (3, 8):
33
+ print("❌ Python 3.8 or higher is required")
34
+ return False
35
+ print("✅ Python version is compatible")
36
+ return True
37
+
38
+ def install_dependencies():
39
+ """Install dependencies from requirements.txt"""
40
+ requirements_file = Path("requirements.txt")
41
+ if not requirements_file.exists():
42
+ print("❌ requirements.txt not found")
43
+ return False
44
+
45
+ return run_command(
46
+ f"{sys.executable} -m pip install -r requirements.txt",
47
+ "Installing dependencies"
48
+ )
49
+
50
+ def verify_installation():
51
+ """Verify that key components can be imported"""
52
+ print(f"\n{'='*50}")
53
+ print("🔍 Verifying installation")
54
+ print(f"{'='*50}")
55
+
56
+ tests = [
57
+ ("import gradio", "Gradio UI framework"),
58
+ ("import numpy", "NumPy"),
59
+ ("import pandas", "Pandas"),
60
+ ("from src.facades.game_facade import GameFacade", "Game Facade"),
61
+ ("from src.mcp.mcp_tools import GradioMCPTools", "MCP Tools"),
62
+ ("from app import MMORPGApplication", "Main Application"),
63
+ ]
64
+
65
+ all_passed = True
66
+ for test_import, description in tests:
67
+ try:
68
+ exec(test_import)
69
+ print(f"✅ {description}")
70
+ except ImportError as e:
71
+ print(f"❌ {description}: {e}")
72
+ all_passed = False
73
+ except Exception as e:
74
+ print(f"⚠️ {description}: {e}")
75
+
76
+ return all_passed
77
+
78
+ def main():
79
+ """Main setup function"""
80
+ print("🎮 MMOP Second Try - Setup Script")
81
+ print("=" * 50)
82
+
83
+ # Check Python version
84
+ if not check_python_version():
85
+ sys.exit(1)
86
+
87
+ # Install dependencies
88
+ if not install_dependencies():
89
+ print("\n❌ Dependency installation failed")
90
+ sys.exit(1)
91
+
92
+ # Verify installation
93
+ if not verify_installation():
94
+ print("\n⚠️ Some components failed verification, but you can still try running the application")
95
+ else:
96
+ print("\n🎉 Setup completed successfully!")
97
+
98
+ print(f"\n{'='*50}")
99
+ print("🚀 Next steps:")
100
+ print("1. Run the application: python app.py")
101
+ print("2. Open your browser to the displayed URL")
102
+ print("3. Start playing your MMORPG!")
103
+ print(f"{'='*50}")
104
+
105
+ if __name__ == "__main__":
106
+ main()
src/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ MMORPG Application - Clean Architecture Implementation
3
+ Main package initialization
4
+ """
5
+
6
+ # Package version
7
+ __version__ = "2.0.0"
8
+
9
+ # Import main components for easy access
10
+ from .core.game_engine import GameEngine
11
+ from .facades.game_facade import GameFacade
src/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (420 Bytes). View file
 
src/addons/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ """
2
+ Add-on system package for MMOP game.
3
+ """
4
+
5
+ __version__ = "1.0.0"
src/addons/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (267 Bytes). View file
 
src/addons/__pycache__/example_npc_addon.cpython-313.pyc ADDED
Binary file (11.5 kB). View file
 
src/addons/__pycache__/read2burn_addon.cpython-313.pyc ADDED
Binary file (11 kB). View file
 
src/addons/__pycache__/weather_oracle_addon.cpython-313.pyc ADDED
Binary file (18.7 kB). View file
 
src/addons/example_npc_addon.py ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Example NPC Addon - Template for creating new NPCs with custom functionality
3
+ """
4
+
5
+ import gradio as gr
6
+ from typing import Dict, Any
7
+ from src.interfaces.npc_addon import NPCAddon
8
+
9
+
10
+ class ExampleNPCAddon(NPCAddon):
11
+ """Example NPC addon demonstrating the complete NPC creation process."""
12
+
13
+ def __init__(self):
14
+ super().__init__()
15
+ # Initialize any state or data your NPC needs
16
+ self.npc_inventory = {
17
+ "health_potion": {"name": "Health Potion", "price": 25, "stock": 10},
18
+ "magic_scroll": {"name": "Magic Scroll", "price": 50, "stock": 5},
19
+ "steel_sword": {"name": "Steel Sword", "price": 100, "stock": 3}
20
+ }
21
+ self.greeting_messages = [
22
+ "Welcome to my shop, adventurer!",
23
+ "Looking for some fine wares?",
24
+ "I have the best deals in town!",
25
+ "What can I help you with today?"
26
+ ]
27
+
28
+ @property
29
+ def addon_id(self) -> str:
30
+ """Unique identifier for this addon - must match NPC ID in world."""
31
+ return "example_merchant"
32
+
33
+ @property
34
+ def addon_name(self) -> str:
35
+ """Display name shown in the interface."""
36
+ return "Example Merchant"
37
+
38
+ def get_interface(self) -> gr.Component:
39
+ """Create the Gradio interface for this NPC."""
40
+ with gr.Column() as interface:
41
+ gr.Markdown("""
42
+ ## 🏪 Example Merchant
43
+
44
+ *Welcome to my humble shop! I offer fine wares for brave adventurers.*
45
+
46
+ ### Available Services:
47
+ - **Shop**: Browse and purchase items
48
+ - **Appraisal**: Get item value estimates
49
+ - **Information**: Learn about the local area
50
+
51
+ *Walk near me in the game world to unlock full functionality!*
52
+ """)
53
+
54
+ with gr.Tabs():
55
+ # Shop Tab
56
+ with gr.Tab("🛒 Shop"):
57
+ gr.Markdown("### Available Items")
58
+
59
+ with gr.Row():
60
+ item_select = gr.Dropdown(
61
+ label="Select Item",
62
+ choices=list(self.npc_inventory.keys()),
63
+ value=None
64
+ )
65
+ quantity = gr.Number(
66
+ label="Quantity",
67
+ value=1,
68
+ minimum=1,
69
+ maximum=10
70
+ )
71
+
72
+ with gr.Row():
73
+ buy_btn = gr.Button("💰 Purchase", variant="primary")
74
+ refresh_btn = gr.Button("🔄 Refresh Stock", variant="secondary")
75
+
76
+ purchase_result = gr.Textbox(
77
+ label="Transaction Result",
78
+ lines=3,
79
+ interactive=False
80
+ )
81
+ # Shop display
82
+ shop_display = gr.JSON(
83
+ label="Current Inventory",
84
+ value=self.npc_inventory
85
+ )
86
+
87
+ def handle_purchase(item_key, qty):
88
+ if not item_key:
89
+ return "❌ Please select an item first!"
90
+
91
+ # Get current player (simplified for example)
92
+ from ..core.game_engine import GameEngine
93
+ game_engine = GameEngine()
94
+ game_world = game_engine.get_world()
95
+ current_players = list(game_world.players.keys())
96
+ if not current_players:
97
+ return "❌ You must be in the game to make purchases!"
98
+
99
+ player_id = max(current_players, key=lambda pid: game_world.players[pid].last_active)
100
+ return self.purchase_item(player_id, item_key, int(qty))
101
+
102
+ def refresh_shop():
103
+ return self.npc_inventory
104
+
105
+ # Wire up the interface
106
+ buy_btn.click(handle_purchase, [item_select, quantity], purchase_result)
107
+ refresh_btn.click(refresh_shop, outputs=shop_display)
108
+
109
+ # Information Tab
110
+ with gr.Tab("ℹ️ Information"):
111
+ gr.Markdown("""
112
+ ### 📍 Local Area Information
113
+
114
+ - **Location**: Village Market Square
115
+ - **Trading Hours**: Always open for brave adventurers
116
+ - **Specialties**: Weapons, potions, and magical items
117
+ - **Payment**: Gold coins accepted
118
+
119
+ ### 🗺️ Nearby Locations
120
+ - **Village Elder**: North of here, near the fountain
121
+ - **Training Grounds**: East side of village
122
+ - **Mystic Oracle**: In the tower to the south
123
+ """)
124
+
125
+ info_btn = gr.Button("📋 Get Quest Information")
126
+ quest_info = gr.Textbox(
127
+ label="Available Quests",
128
+ lines=5,
129
+ interactive=False
130
+ )
131
+
132
+ def get_quest_info():
133
+ return """
134
+ 🗡️ **Available Quests:**
135
+
136
+ 1. **Goblin Problem** (Level 1-3)
137
+ - Clear goblins from the eastern caves
138
+ - Reward: 100 gold + basic equipment
139
+
140
+ 2. **Herb Collection** (Level 1-2)
141
+ - Gather 10 healing herbs from the forest
142
+ - Reward: 50 gold + health potion
143
+
144
+ 3. **Lost Merchant** (Level 3-5)
145
+ - Find the missing merchant on the trade route
146
+ - Reward: 200 gold + rare item
147
+ """
148
+
149
+ info_btn.click(get_quest_info, outputs=quest_info)
150
+
151
+ return interface
152
+
153
+ def handle_command(self, player_id: str, command: str) -> str:
154
+ """Handle commands sent via private messages to this NPC."""
155
+ command_lower = command.lower().strip()
156
+ # Get player info safely
157
+ from ..core.game_engine import GameEngine
158
+ game_engine = GameEngine()
159
+ game_world = game_engine.get_world()
160
+ player = game_world.players.get(player_id)
161
+ if not player:
162
+ return "❌ Player not found!"
163
+
164
+ player_name = player.name
165
+
166
+ # Command parsing
167
+ if any(word in command_lower for word in ['hello', 'hi', 'greeting', 'hey']):
168
+ import random
169
+ return f"🏪 {random.choice(self.greeting_messages)} {player_name}!"
170
+
171
+ elif any(word in command_lower for word in ['shop', 'buy', 'purchase', 'item']):
172
+ return self._get_shop_summary()
173
+
174
+ elif any(word in command_lower for word in ['quest', 'mission', 'task']):
175
+ return "📜 I have information about local quests! Visit my Information tab for details."
176
+
177
+ elif any(word in command_lower for word in ['help', 'commands']):
178
+ return """
179
+ 🏪 **Available Commands:**
180
+ - **hello/hi**: Friendly greeting
181
+ - **shop/buy**: View available items
182
+ - **quest**: Learn about available quests
183
+ - **help**: Show this help message
184
+
185
+ *Visit the NPC Add-ons tab for my full interface!*
186
+ """
187
+
188
+ else:
189
+ return f"🤔 Interesting words, {player_name}! Try 'help' to see what I can do, or visit my shop interface in the NPC Add-ons tab!"
190
+
191
+ def purchase_item(self, player_id: str, item_key: str, quantity: int) -> str:
192
+ """Handle item purchase logic."""
193
+ if item_key not in self.npc_inventory:
194
+ return "❌ Item not found in inventory!"
195
+
196
+ item = self.npc_inventory[item_key]
197
+ total_cost = item["price"] * quantity
198
+
199
+ if item["stock"] < quantity:
200
+ return f"❌ Not enough stock! Only {item['stock']} available."
201
+
202
+ # Here you would check player's gold and deduct it
203
+ # For this example, we'll simulate the transaction
204
+
205
+ # Update inventory
206
+ self.npc_inventory[item_key]["stock"] -= quantity
207
+
208
+ return f"""
209
+ ✅ **Purchase Successful!**
210
+
211
+ **Item**: {item['name']} x{quantity}
212
+ **Cost**: {total_cost} gold
213
+ **Remaining Stock**: {self.npc_inventory[item_key]['stock']}
214
+
215
+ *Thank you for your business, adventurer!*
216
+ """
217
+
218
+ def _get_shop_summary(self) -> str:
219
+ """Get a summary of available shop items."""
220
+ summary = "🏪 **Shop Inventory:**\n\n"
221
+ for key, item in self.npc_inventory.items():
222
+ if item["stock"] > 0:
223
+ summary += f"• **{item['name']}** - {item['price']} gold (Stock: {item['stock']})\n"
224
+
225
+ summary += "\n*Visit my shop interface in the NPC Add-ons tab to purchase items!*"
226
+ return summary
227
+
228
+
229
+ # Global instance for auto-registration
230
+ example_merchant_addon = ExampleNPCAddon()
src/addons/fortune_teller_addon.py ADDED
@@ -0,0 +1,240 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Fortune Teller Add-on - Self-contained addon demonstrating the new auto-registration system.
3
+ """
4
+
5
+ import random
6
+ import time
7
+ from typing import Dict, Any, Optional
8
+ import gradio as gr
9
+ from ..interfaces.npc_addon import NPCAddon
10
+
11
+
12
+ class FortuneTellerAddon(NPCAddon):
13
+ """Self-contained Fortune Teller NPC that auto-registers and positions itself."""
14
+
15
+ def __init__(self):
16
+ super().__init__() # This triggers auto-registration
17
+ self.fortunes = [
18
+ "A great adventure awaits you beyond the eastern mountains!",
19
+ "Beware of strangers bearing gifts in the next full moon.",
20
+ "Your courage will be tested, but victory will be yours.",
21
+ "Gold will come to you through an unexpected friendship.",
22
+ "The stars suggest a powerful ally will join your quest.",
23
+ "A hidden treasure lies where the old oak meets the stream.",
24
+ "Your wisdom will grow through helping others in need.",
25
+ "Trust your instincts - they will guide you true.",
26
+ "A challenge approaches, but it will make you stronger.",
27
+ "The path you seek becomes clear under starlight."
28
+ ]
29
+ self.player_fortunes: Dict[str, Dict] = {} # Track player fortune history
30
+
31
+ @property
32
+ def addon_id(self) -> str:
33
+ """Unique identifier for this add-on"""
34
+ return "fortune_teller"
35
+
36
+ @property
37
+ def addon_name(self) -> str:
38
+ """Display name for this add-on"""
39
+ return "🔮 Mystic Fortune Teller"
40
+
41
+ @property
42
+ def npc_config(self) -> Dict[str, Any]:
43
+ """NPC configuration for auto-placement in world"""
44
+ return {
45
+ 'id': 'fortune_teller',
46
+ 'name': 'Mystic Zelda',
47
+ 'x': 125, 'y': 75,
48
+ 'char': '🔮',
49
+ 'type': 'addon',
50
+ 'description': 'Wise fortune teller who can glimpse into your future'
51
+ }
52
+
53
+ @property
54
+ def ui_tab_name(self) -> str:
55
+ """UI tab name for this addon"""
56
+ return "🔮 Fortune Teller"
57
+
58
+ def get_interface(self) -> gr.Component:
59
+ """Create the Gradio interface for fortune telling"""
60
+ with gr.Column() as interface:
61
+ gr.Markdown("""
62
+ ## 🔮 Mystic Fortune Teller
63
+
64
+ *Peer into the mists of time and discover your destiny...*
65
+
66
+ **Services Available:**
67
+ - 🌟 **Daily Fortune** - Receive guidance for your journey
68
+ - 📜 **Fortune History** - View your past predictions
69
+ - 🎲 **Lucky Numbers** - Get your lucky numbers for today
70
+
71
+ *Walk near me in the game world for personalized readings!*
72
+ """)
73
+
74
+ with gr.Row():
75
+ fortune_btn = gr.Button("🔮 Get My Fortune", variant="primary", scale=2)
76
+ history_btn = gr.Button("📜 View History", variant="secondary", scale=1)
77
+ lucky_btn = gr.Button("🎲 Lucky Numbers", variant="secondary", scale=1)
78
+
79
+ fortune_output = gr.Textbox(
80
+ label="🌟 Your Fortune",
81
+ lines=6,
82
+ interactive=False,
83
+ placeholder="Click 'Get My Fortune' to reveal your destiny..."
84
+ )
85
+
86
+ def get_player_fortune():
87
+ """Get fortune for the active player"""
88
+ # In a real implementation, you'd get the actual player ID
89
+ # For demo purposes, we'll use a mock player
90
+ mock_player_id = "demo_player"
91
+ mock_player_name = "Brave Adventurer"
92
+ return self.get_daily_fortune(mock_player_id, mock_player_name)
93
+
94
+ def get_player_history():
95
+ """Get fortune history for the active player"""
96
+ mock_player_id = "demo_player"
97
+ return self.get_fortune_history(mock_player_id)
98
+
99
+ def get_lucky_numbers():
100
+ """Generate lucky numbers for the active player"""
101
+ mock_player_id = "demo_player"
102
+ return self.generate_lucky_numbers(mock_player_id)
103
+
104
+ # Wire up the interface
105
+ fortune_btn.click(get_player_fortune, outputs=[fortune_output])
106
+ history_btn.click(get_player_history, outputs=[fortune_output])
107
+ lucky_btn.click(get_lucky_numbers, outputs=[fortune_output])
108
+
109
+ return interface
110
+
111
+ def handle_command(self, player_id: str, command: str) -> str:
112
+ """Handle Fortune Teller commands via private messages"""
113
+ parts = command.strip().split(' ', 1)
114
+ cmd = parts[0].lower()
115
+
116
+ if cmd == "fortune":
117
+ # Get player name from game world
118
+ try:
119
+ from ..core.world import game_world
120
+ player_name = game_world.players.get(player_id, {}).get('name', 'Unknown Traveler')
121
+ except:
122
+ player_name = "Unknown Traveler"
123
+
124
+ return self.get_daily_fortune(player_id, player_name)
125
+
126
+ elif cmd == "history":
127
+ return self.get_fortune_history(player_id)
128
+
129
+ elif cmd == "lucky":
130
+ return self.generate_lucky_numbers(player_id)
131
+
132
+ elif cmd == "help":
133
+ return """🔮 **Fortune Teller Commands:**
134
+
135
+ **fortune** - Get your daily fortune reading
136
+ **history** - View your fortune history
137
+ **lucky** - Generate lucky numbers for today
138
+ **help** - Show this help
139
+
140
+ 🌟 **Example:** Send me "fortune" to receive mystical guidance!"""
141
+
142
+ else:
143
+ # If no specific command, treat as fortune request
144
+ try:
145
+ from ..core.world import game_world
146
+ player_name = game_world.players.get(player_id, {}).get('name', 'Unknown Traveler')
147
+ except:
148
+ player_name = "Unknown Traveler"
149
+
150
+ return self.get_daily_fortune(player_id, player_name)
151
+
152
+ def get_daily_fortune(self, player_id: str, player_name: str) -> str:
153
+ """Get or generate daily fortune for a player"""
154
+ today = time.strftime("%Y-%m-%d")
155
+
156
+ # Check if player already has a fortune for today
157
+ if player_id in self.player_fortunes:
158
+ player_data = self.player_fortunes[player_id]
159
+ if player_data.get('last_fortune_date') == today:
160
+ return (f"**{player_name}**, I have already revealed your destiny for today:\n\n"
161
+ f"✨ *{player_data['last_fortune']}*\n\n"
162
+ f"Return tomorrow for new guidance...")
163
+
164
+ # Generate new fortune
165
+ fortune = random.choice(self.fortunes)
166
+
167
+ # Store fortune
168
+ if player_id not in self.player_fortunes:
169
+ self.player_fortunes[player_id] = {'history': []}
170
+
171
+ self.player_fortunes[player_id].update({
172
+ 'last_fortune': fortune,
173
+ 'last_fortune_date': today
174
+ })
175
+
176
+ self.player_fortunes[player_id]['history'].append({
177
+ 'date': today,
178
+ 'fortune': fortune,
179
+ 'timestamp': time.time()
180
+ })
181
+
182
+ # Keep only last 10 fortunes
183
+ if len(self.player_fortunes[player_id]['history']) > 10:
184
+ self.player_fortunes[player_id]['history'] = self.player_fortunes[player_id]['history'][-10:]
185
+
186
+ return (f"🔮 **Greetings, {player_name}!**\n\n"
187
+ f"The crystal ball swirls with mystical energy...\n\n"
188
+ f"✨ *{fortune}*\n\n"
189
+ f"💫 This fortune is yours until the dawn of a new day.")
190
+
191
+ def get_fortune_history(self, player_id: str) -> str:
192
+ """Get fortune history for a player"""
193
+ if player_id not in self.player_fortunes or not self.player_fortunes[player_id].get('history'):
194
+ return "📜 **Fortune History**\n\nYou have no fortune history yet. Request your first fortune to begin building your mystical record!"
195
+
196
+ history = self.player_fortunes[player_id]['history']
197
+ result = "📜 **Your Fortune History**\n\n"
198
+
199
+ for entry in reversed(history[-5:]): # Show last 5 fortunes
200
+ result += f"**{entry['date']}:** {entry['fortune']}\n\n"
201
+
202
+ if len(history) > 5:
203
+ result += f"*...and {len(history) - 5} more in your mystical archives*"
204
+
205
+ return result
206
+
207
+ def generate_lucky_numbers(self, player_id: str) -> str:
208
+ """Generate lucky numbers for a player"""
209
+ # Use player ID as seed for consistent daily numbers
210
+ today = time.strftime("%Y-%m-%d")
211
+ seed = hash(f"{player_id}-{today}") % 2**31
212
+ random.seed(seed)
213
+
214
+ # Generate different types of lucky numbers
215
+ lottery_numbers = sorted([random.randint(1, 49) for _ in range(6)])
216
+ magic_number = random.randint(1, 100)
217
+ lucky_color_index = random.randint(0, 6)
218
+ lucky_colors = ["Red", "Blue", "Green", "Gold", "Silver", "Purple", "Orange"]
219
+ lucky_color = lucky_colors[lucky_color_index]
220
+
221
+ # Reset random seed
222
+ random.seed()
223
+
224
+ return (f"🎲 **Lucky Numbers for Today**\n\n"
225
+ f"🎰 **Lottery Numbers:** {', '.join(map(str, lottery_numbers))}\n"
226
+ f"✨ **Magic Number:** {magic_number}\n"
227
+ f"🌈 **Lucky Color:** {lucky_color}\n\n"
228
+ f"💫 *May fortune smile upon you this day!*")
229
+
230
+ def on_startup(self):
231
+ """Called when the addon is loaded during game startup"""
232
+ print(f"[{self.addon_id}] Fortune Teller Mystic Zelda has awakened and is ready to divine your future!")
233
+
234
+ def on_shutdown(self):
235
+ """Called when the addon is unloaded during game shutdown"""
236
+ print(f"[{self.addon_id}] The crystal ball dims as Mystic Zelda rests...")
237
+
238
+
239
+ # Auto-instantiate the addon for registration
240
+ fortune_teller_addon = FortuneTellerAddon()
src/addons/magic_shop_addon.py ADDED
@@ -0,0 +1,509 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Magic Shop Add-on - Self-contained NPC with position, registration, and UI.
3
+
4
+ This demonstrates the weather addon pattern where everything is contained in one file:
5
+ - NPC configuration with position
6
+ - Command handling
7
+ - UI interface
8
+ - Auto-registration through global instance
9
+
10
+ No manual edits to other files required!
11
+ """
12
+
13
+ import time
14
+ import gradio as gr
15
+ from typing import Dict, Any, List, Optional
16
+ from ..interfaces.npc_addon import NPCAddon
17
+
18
+
19
+ class MagicShopAddon(NPCAddon):
20
+ """Self-contained magic shop NPC that handles all aspects in one file."""
21
+
22
+ def __init__(self):
23
+ super().__init__() # This auto-registers the addon!
24
+
25
+ # Shop inventory
26
+ self.spell_inventory = {
27
+ "healing_spell": {
28
+ "name": "🌟 Basic Healing Spell",
29
+ "price": 100,
30
+ "stock": 8,
31
+ "description": "Restores 50 health points",
32
+ "level_required": 1
33
+ },
34
+ "fireball_spell": {
35
+ "name": "🔥 Fireball Spell",
36
+ "price": 250,
37
+ "stock": 5,
38
+ "description": "Deals fire damage to enemies",
39
+ "level_required": 3
40
+ },
41
+ "teleport_scroll": {
42
+ "name": "✨ Teleportation Scroll",
43
+ "price": 500,
44
+ "stock": 3,
45
+ "description": "Instantly travel to marked locations",
46
+ "level_required": 5
47
+ },
48
+ "mana_potion": {
49
+ "name": "🧪 Greater Mana Potion",
50
+ "price": 75,
51
+ "stock": 12,
52
+ "description": "Restores 100 mana points",
53
+ "level_required": 1
54
+ }
55
+ }
56
+
57
+ # Shop stats
58
+ self.total_sales = 0
59
+ self.customers_served = []
60
+ self.last_restock = time.strftime("%Y-%m-%d %H:%M:%S")
61
+
62
+ @property
63
+ def addon_id(self) -> str:
64
+ """Unique identifier for this add-on"""
65
+ return "magic_shop"
66
+
67
+ @property
68
+ def addon_name(self) -> str:
69
+ """Display name for this add-on"""
70
+ return "🪄 Enchanted Magic Shop"
71
+
72
+ @property
73
+ def npc_config(self) -> Dict[str, Any]:
74
+ """NPC configuration for auto-placement in world"""
75
+ return {
76
+ 'id': 'magic_shop_keeper',
77
+ 'name': '🧙‍♀️ Mystical Mage Elara',
78
+ 'x': 300, 'y': 150, # Position in the game world
79
+ 'char': '🧙‍♀️',
80
+ 'type': 'magic_shop',
81
+ 'description': 'Wise mage selling powerful spells and magical items'
82
+ }
83
+
84
+ @property
85
+ def ui_tab_name(self) -> str:
86
+ """UI tab name for this addon"""
87
+ return "🪄 Magic Shop"
88
+
89
+ def get_interface(self) -> gr.Component:
90
+ """Create the Gradio interface for the magic shop"""
91
+ with gr.Column() as interface:
92
+ # Header with shop info
93
+ gr.Markdown(f"""
94
+ ## 🪄 {self.addon_name}
95
+
96
+ *Greetings, brave adventurer! I am Elara, keeper of ancient magics.*
97
+
98
+ **📍 Shop Location:** ({self.npc_config['x']}, {self.npc_config['y']})
99
+ **👥 Customers Served:** {len(self.customers_served)}
100
+ **💰 Total Sales:** {self.total_sales} gold
101
+ **📦 Last Restock:** {self.last_restock}
102
+
103
+ ---
104
+ """)
105
+
106
+ with gr.Tabs():
107
+ # Shop inventory tab
108
+ with gr.Tab("🛒 Spell Catalog"):
109
+ gr.Markdown("### ✨ Available Magical Items")
110
+
111
+ # Create inventory display
112
+ inventory_data = []
113
+ for item_id, item in self.spell_inventory.items():
114
+ inventory_data.append({
115
+ "Item": item["name"],
116
+ "Price": f"{item['price']} gold",
117
+ "Stock": item["stock"],
118
+ "Level Req.": item["level_required"],
119
+ "Description": item["description"]
120
+ })
121
+
122
+ inventory_display = gr.DataFrame(
123
+ value=inventory_data,
124
+ label="📜 Spell Inventory",
125
+ interactive=False
126
+ )
127
+
128
+ # Purchase interface
129
+ with gr.Row():
130
+ item_dropdown = gr.Dropdown(
131
+ label="🎯 Select Spell/Item",
132
+ choices=[(item["name"], key) for key, item in self.spell_inventory.items()],
133
+ value=None
134
+ )
135
+ quantity_input = gr.Number(
136
+ label="📊 Quantity",
137
+ value=1,
138
+ minimum=1,
139
+ maximum=10
140
+ )
141
+
142
+ purchase_btn = gr.Button("💰 Purchase", variant="primary", size="lg")
143
+
144
+ purchase_result = gr.Textbox(
145
+ label="🧾 Transaction Result",
146
+ lines=4,
147
+ interactive=False
148
+ )
149
+
150
+ def handle_purchase(item_key, qty):
151
+ if not item_key:
152
+ return "❌ Please select an item first!"
153
+
154
+ if item_key not in self.spell_inventory:
155
+ return "❌ Item not found in inventory!"
156
+
157
+ item = self.spell_inventory[item_key]
158
+
159
+ if qty > item["stock"]:
160
+ return f"❌ Sorry! Only {item['stock']} {item['name']} in stock."
161
+
162
+ total_cost = item["price"] * qty
163
+
164
+ # Simulate purchase (in real game, check player gold)
165
+ self.spell_inventory[item_key]["stock"] -= qty
166
+ self.total_sales += total_cost
167
+
168
+ return f"""✅ **Purchase Successful!**
169
+
170
+ **Item:** {item['name']}
171
+ **Quantity:** {qty}
172
+ **Unit Price:** {item['price']} gold
173
+ **Total Cost:** {total_cost} gold
174
+
175
+ **Remaining Stock:** {self.spell_inventory[item_key]['stock']}
176
+
177
+ *Thank you for your patronage! May these enchantments serve you well!*"""
178
+
179
+ purchase_btn.click(
180
+ handle_purchase,
181
+ inputs=[item_dropdown, quantity_input],
182
+ outputs=[purchase_result]
183
+ )
184
+
185
+ # Shop information tab
186
+ with gr.Tab("ℹ️ Shop Info"):
187
+ gr.Markdown("""
188
+ ### 🏪 About Elara's Magic Shop
189
+
190
+ Welcome to the most enchanted emporium in the realm! I've been practicing
191
+ the magical arts for over 200 years, and my shop contains only the finest
192
+ spells and potions.
193
+
194
+ #### 📋 Services Offered:
195
+ - 🔮 **Spell Sales** - Powerful magic for every adventurer
196
+ - 🧪 **Potion Brewing** - Restore health and mana
197
+ - 📜 **Scroll Creation** - Portable magic for travel
198
+ - 🎓 **Magic Consultation** - Learn about spell mechanics
199
+
200
+ #### 🕐 Shop Hours:
201
+ - **Open:** Always available for brave souls
202
+ - **Restocking:** Weekly shipments from the Mage Tower
203
+ - **Special Orders:** Available for rare enchantments
204
+
205
+ #### 💡 Pro Tips:
206
+ - Higher level spells require more experience
207
+ - Potions stack in your inventory
208
+ - Scrolls are one-time use items
209
+ - Ask about bulk discounts for guilds!
210
+ """)
211
+
212
+ # Current shop stats
213
+ shop_stats = gr.JSON(
214
+ label="📊 Shop Statistics",
215
+ value={
216
+ "total_items_in_stock": sum(item["stock"] for item in self.spell_inventory.values()),
217
+ "most_expensive_item": max(self.spell_inventory.values(), key=lambda x: x["price"])["name"],
218
+ "cheapest_item": min(self.spell_inventory.values(), key=lambda x: x["price"])["name"],
219
+ "customers_today": len(self.customers_served),
220
+ "shop_established": "Year 1247 of the Third Age"
221
+ }
222
+ )
223
+
224
+ # Commands help tab
225
+ with gr.Tab("💬 Commands"):
226
+ gr.Markdown("""
227
+ ### 🎯 Private Message Commands
228
+
229
+ Send these commands via private messages to Mystical Mage Elara:
230
+
231
+ #### 🛒 Shopping Commands:
232
+ - `shop` - View available items and prices
233
+ - `buy <item_name> [quantity]` - Purchase magical items
234
+ - `stock` - Check current inventory levels
235
+
236
+ #### ℹ️ Information Commands:
237
+ - `info` - Get shop information and location
238
+ - `hours` - Check shop hours and services
239
+ - `help` - Show all available commands
240
+
241
+ #### 📊 Status Commands:
242
+ - `stats` - View shop statistics
243
+ - `history` - Check your purchase history
244
+
245
+ #### 🎯 Example Usage:
246
+ ```
247
+ shop # See all items
248
+ buy healing_spell 2 # Buy 2 healing spells
249
+ info # Get shop details
250
+ stats # View shop statistics
251
+ ```
252
+
253
+ *Walk near my shop in the game world to unlock the full experience!*
254
+ """)
255
+
256
+ return interface
257
+
258
+ def handle_command(self, player_id: str, command: str) -> str:
259
+ """Handle magic shop commands via private messages"""
260
+ if player_id not in self.customers_served:
261
+ self.customers_served.append(player_id)
262
+
263
+ parts = command.strip().split()
264
+ if not parts:
265
+ return self._show_help()
266
+
267
+ cmd = parts[0].lower()
268
+
269
+ if cmd == "shop":
270
+ return self._show_shop_catalog()
271
+
272
+ elif cmd == "buy":
273
+ if len(parts) < 2:
274
+ return "❓ Usage: `buy <item_name> [quantity]`\nExample: `buy healing_spell 2`"
275
+
276
+ item_name = parts[1].lower()
277
+ quantity = 1
278
+ if len(parts) > 2:
279
+ try:
280
+ quantity = int(parts[2])
281
+ if quantity < 1:
282
+ return "❌ Quantity must be at least 1!"
283
+ except ValueError:
284
+ return "❌ Invalid quantity! Please use a number."
285
+
286
+ return self._handle_purchase(player_id, item_name, quantity)
287
+
288
+ elif cmd == "stock":
289
+ return self._show_stock_levels()
290
+
291
+ elif cmd == "info":
292
+ return self._show_shop_info()
293
+
294
+ elif cmd == "hours":
295
+ return self._show_shop_hours()
296
+
297
+ elif cmd == "stats":
298
+ return self._show_shop_stats()
299
+
300
+ elif cmd == "history":
301
+ return self._show_customer_history(player_id)
302
+
303
+ elif cmd == "help":
304
+ return self._show_help()
305
+
306
+ else:
307
+ return f"❓ Unknown command: `{cmd}`\n\nTry `help` to see available commands."
308
+
309
+ def _show_shop_catalog(self) -> str:
310
+ """Display the complete shop catalog"""
311
+ catalog = "🪄 **Elara's Magic Shop - Spell Catalog**\n\n"
312
+
313
+ for item_id, item in self.spell_inventory.items():
314
+ stock_status = "✅ In Stock" if item["stock"] > 0 else "❌ Sold Out"
315
+ catalog += f"**{item['name']}**\n"
316
+ catalog += f"• Price: {item['price']} gold\n"
317
+ catalog += f"• Stock: {item['stock']} ({stock_status})\n"
318
+ catalog += f"• Level Required: {item['level_required']}\n"
319
+ catalog += f"• {item['description']}\n\n"
320
+
321
+ catalog += f"💰 Total Sales Today: {self.total_sales} gold\n"
322
+ catalog += f"👥 Customers Served: {len(self.customers_served)}\n\n"
323
+ catalog += "*To purchase: `buy <item_name> [quantity]`*"
324
+
325
+ return catalog
326
+
327
+ def _handle_purchase(self, player_id: str, item_name: str, quantity: int) -> str:
328
+ """Handle item purchase logic"""
329
+ # Find item by name or ID
330
+ item_key = None
331
+ for key, item in self.spell_inventory.items():
332
+ if key == item_name or item["name"].lower().replace(" ", "_") == item_name:
333
+ item_key = key
334
+ break
335
+
336
+ if not item_key:
337
+ available_items = ", ".join(self.spell_inventory.keys())
338
+ return f"❌ Item '{item_name}' not found!\n\nAvailable items: {available_items}\n\nTry: `shop` to see the full catalog"
339
+
340
+ item = self.spell_inventory[item_key]
341
+
342
+ if item["stock"] < quantity:
343
+ return f"❌ Sorry! Only {item['stock']} {item['name']} available.\n\nTry a smaller quantity or check back after restock."
344
+
345
+ total_cost = item["price"] * quantity
346
+
347
+ # Simulate purchase (in real game, check/deduct player gold)
348
+ self.spell_inventory[item_key]["stock"] -= quantity
349
+ self.total_sales += total_cost
350
+
351
+ return f"""✅ **Purchase Successful!**
352
+
353
+ 🎯 **Item:** {item['name']}
354
+ 📊 **Quantity:** {quantity}
355
+ 💰 **Total Cost:** {total_cost} gold
356
+ 📦 **Remaining Stock:** {self.spell_inventory[item_key]['stock']}
357
+
358
+ *Your magical items have been added to your inventory!*
359
+ *May they serve you well on your adventures!*
360
+
361
+ Use `shop` to browse more items or `stats` to see shop statistics."""
362
+
363
+ def _show_stock_levels(self) -> str:
364
+ """Show current stock levels"""
365
+ stock_report = "📦 **Current Stock Levels**\n\n"
366
+
367
+ for item in self.spell_inventory.values():
368
+ status_emoji = "🟢" if item["stock"] > 5 else "🟡" if item["stock"] > 0 else "🔴"
369
+ stock_report += f"{status_emoji} **{item['name']}**: {item['stock']} available\n"
370
+
371
+ stock_report += f"\n📅 **Last Restock:** {self.last_restock}"
372
+ stock_report += "\n💡 *Low stock items will be restocked weekly*"
373
+
374
+ return stock_report
375
+
376
+ def _show_shop_info(self) -> str:
377
+ """Show detailed shop information"""
378
+ return f"""🏪 **Elara's Magic Shop Information**
379
+
380
+ 🧙‍♀️ **Shopkeeper:** Mystical Mage Elara
381
+ 📍 **Location:** ({self.npc_config['x']}, {self.npc_config['y']})
382
+ 🏛️ **Shop Type:** Magical Items & Spell Emporium
383
+
384
+ **About the Shop:**
385
+ I've been practicing magic for over 200 years and offer only the finest
386
+ enchantments, potions, and scrolls. My shop serves adventurers of all
387
+ levels, from novice spell-casters to master wizards.
388
+
389
+ **Specialties:**
390
+ • 🌟 Healing and restoration magic
391
+ • 🔥 Offensive spells and scrolls
392
+ • 🧪 Mana and health potions
393
+ • ✨ Utility and travel magic
394
+
395
+ **Shop Guarantee:**
396
+ All items come with Elara's personal enchantment guarantee!
397
+ If a spell fails due to magical defect, return for full refund.
398
+
399
+ *Walk near my shop to access the full interface and advanced features!*"""
400
+
401
+ def _show_shop_hours(self) -> str:
402
+ """Show shop hours and services"""
403
+ return """🕐 **Shop Hours & Services**
404
+
405
+ ⏰ **Operating Hours:**
406
+ • Always open for adventurers in need
407
+ • Emergency magical supplies available 24/7
408
+ • Personal consultations by appointment
409
+
410
+ 📦 **Restocking Schedule:**
411
+ • Weekly shipments from the Mage Tower
412
+ • Special orders processed every 3 days
413
+ • Rare items available on request
414
+
415
+ 🎓 **Additional Services:**
416
+ • Spell identification and appraisal
417
+ • Magic item consultation
418
+ • Bulk orders for guilds (discount rates)
419
+ • Custom enchantment requests
420
+
421
+ 💡 **Current Status:** 🟢 Open and fully stocked!"""
422
+
423
+ def _show_shop_stats(self) -> str:
424
+ """Show shop statistics"""
425
+ total_items = sum(item["stock"] for item in self.spell_inventory.values())
426
+ most_expensive = max(self.spell_inventory.values(), key=lambda x: x["price"])
427
+ cheapest = min(self.spell_inventory.values(), key=lambda x: x["price"])
428
+
429
+ return f"""📊 **Shop Statistics**
430
+
431
+ 💰 **Sales Data:**
432
+ • Total Sales: {self.total_sales} gold
433
+ • Customers Served: {len(self.customers_served)}
434
+ • Average Sale: {self.total_sales // max(len(self.customers_served), 1)} gold
435
+
436
+ 📦 **Inventory:**
437
+ • Total Items in Stock: {total_items}
438
+ • Most Expensive: {most_expensive['name']} ({most_expensive['price']} gold)
439
+ • Most Affordable: {cheapest['name']} ({cheapest['price']} gold)
440
+
441
+ 🏆 **Shop Achievements:**
442
+ • Established: Year 1247 of the Third Age
443
+ • Reputation: ⭐⭐⭐⭐⭐ (5/5 stars)
444
+ • Mage Guild Certified: ✅ Premium Vendor
445
+
446
+ *Thank you for supporting local magical businesses!*"""
447
+
448
+ def _show_customer_history(self, player_id: str) -> str:
449
+ """Show customer interaction history"""
450
+ return f"""👤 **Customer History for {player_id}**
451
+
452
+ 📋 **Account Status:** Valued Customer
453
+ 🎯 **First Visit:** Today (Welcome!)
454
+ 💰 **Total Purchases:** Check your inventory for items
455
+ 🏅 **Customer Level:** Novice Adventurer
456
+
457
+ **Recommended Items:**
458
+ • For beginners: Healing Spell + Mana Potion
459
+ • For adventurers: Fireball Spell
460
+ • For experts: Teleportation Scroll
461
+
462
+ 💡 **Next Purchase Suggestion:**
463
+ Based on your level, I recommend starting with healing magic!
464
+
465
+ *Continue shopping to unlock loyalty rewards and discounts!*"""
466
+
467
+ def _show_help(self) -> str:
468
+ """Show all available commands"""
469
+ return """🪄 **Elara's Magic Shop - Command Help**
470
+
471
+ **🛒 Shopping Commands:**
472
+ • `shop` - Browse the complete spell catalog
473
+ • `buy <item> [qty]` - Purchase magical items
474
+ • `stock` - Check current inventory levels
475
+
476
+ **ℹ️ Information Commands:**
477
+ • `info` - Shop location and details
478
+ • `hours` - Operating hours and services
479
+ • `stats` - View shop statistics
480
+ • `history` - Your customer history
481
+
482
+ **💡 Examples:**
483
+ • `shop` - See all available spells
484
+ • `buy healing_spell 2` - Buy 2 healing spells
485
+ • `buy mana_potion` - Buy 1 mana potion
486
+ • `stock` - Check what's available
487
+
488
+ **🎯 Item Names:**
489
+ • `healing_spell` - Basic healing magic
490
+ • `fireball_spell` - Fire damage spell
491
+ • `teleport_scroll` - Travel magic
492
+ • `mana_potion` - Mana restoration
493
+
494
+ *Visit my shop interface in the NPC Add-ons tab for the full experience!*
495
+ *Walk near my location at ({self.npc_config['x']}, {self.npc_config['y']}) in the game world!*"""
496
+
497
+ def on_startup(self):
498
+ """Called when the addon is loaded during game startup"""
499
+ print(f"[{self.addon_id}] Elara's Magic Shop is now open for business!")
500
+ print(f"[{self.addon_id}] Located at ({self.npc_config['x']}, {self.npc_config['y']}) with {len(self.spell_inventory)} magical items")
501
+
502
+ def on_shutdown(self):
503
+ """Called when the addon is unloaded during game shutdown"""
504
+ print(f"[{self.addon_id}] Elara's Magic Shop is closing... Total sales: {self.total_sales} gold")
505
+
506
+
507
+ # Global instance - this triggers auto-registration!
508
+ # Just like weather_oracle_service at the bottom of weather_oracle_addon.py
509
+ magic_shop_addon = MagicShopAddon()
src/addons/read2burn_addon.py ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Read2Burn Mailbox Add-on - Self-destructing secure messaging system.
3
+ """
4
+
5
+ import time
6
+ import random
7
+ import string
8
+ from typing import Dict, List, Tuple
9
+ from dataclasses import dataclass
10
+ from abc import ABC, abstractmethod
11
+ import gradio as gr
12
+
13
+ from ..interfaces.npc_addon import NPCAddon
14
+
15
+
16
+ @dataclass
17
+ class Read2BurnMessage:
18
+ """Data class for Read2Burn messages."""
19
+ id: str
20
+ creator_id: str
21
+ content: str
22
+ created_at: float
23
+ expires_at: float
24
+ reads_left: int
25
+ burned: bool = False
26
+
27
+
28
+ class IRead2BurnService(ABC):
29
+ """Interface for Read2Burn service operations."""
30
+
31
+ @abstractmethod
32
+ def create_message(self, creator_id: str, content: str) -> str:
33
+ """Create a new self-destructing message."""
34
+ pass
35
+
36
+ @abstractmethod
37
+ def read_message(self, reader_id: str, message_id: str) -> str:
38
+ """Read and potentially burn a message."""
39
+ pass
40
+
41
+ @abstractmethod
42
+ def list_player_messages(self, player_id: str) -> str:
43
+ """List messages created by a player."""
44
+ pass
45
+
46
+
47
+ class Read2BurnService(IRead2BurnService, NPCAddon):
48
+ """Service for managing Read2Burn secure messaging."""
49
+
50
+ def __init__(self):
51
+ super().__init__()
52
+ self.messages: Dict[str, Read2BurnMessage] = {}
53
+ self.access_log: List[Dict] = []
54
+
55
+ @property
56
+ def addon_id(self) -> str:
57
+ """Unique identifier for this add-on"""
58
+ return "read2burn_mailbox"
59
+
60
+ @property
61
+ def addon_name(self) -> str:
62
+ """Display name for this add-on"""
63
+ return "Read2Burn Secure Mailbox"
64
+
65
+ def get_interface(self) -> gr.Component:
66
+ """Return Gradio interface for this add-on"""
67
+ # This will be implemented later for the UI
68
+ return gr.Markdown("📧 **Read2Burn Secure Mailbox**\n\nUse private messages to the read2burn NPC to manage secure messages.")
69
+
70
+ def generate_message_id(self) -> str:
71
+ """Generate a unique message ID."""
72
+ return ''.join(random.choices(string.ascii_uppercase + string.digits, k=8))
73
+
74
+ def create_message(self, creator_id: str, content: str) -> str:
75
+ """Create a new self-destructing message."""
76
+ message_id = self.generate_message_id()
77
+
78
+ message = Read2BurnMessage(
79
+ id=message_id,
80
+ creator_id=creator_id,
81
+ content=content, # In production, encrypt this
82
+ created_at=time.time(),
83
+ expires_at=time.time() + (24 * 3600), # 24 hours
84
+ reads_left=1,
85
+ burned=False
86
+ )
87
+
88
+ self.messages[message_id] = message
89
+
90
+ self.access_log.append({
91
+ 'action': 'create',
92
+ 'message_id': message_id,
93
+ 'player_id': creator_id,
94
+ 'timestamp': time.time()
95
+ })
96
+
97
+ return f"✅ **Message Created Successfully!**\n\n📝 **Message ID:** `{message_id}`\n🔗 Share this ID with the recipient\n⏰ Expires in 24 hours\n🔥 Burns after 1 read"
98
+
99
+ def read_message(self, reader_id: str, message_id: str) -> str:
100
+ """Read and burn a message."""
101
+ if message_id not in self.messages:
102
+ return "❌ Message not found or already burned"
103
+
104
+ message = self.messages[message_id]
105
+
106
+ # Check expiry
107
+ if time.time() > message.expires_at:
108
+ del self.messages[message_id]
109
+ return "❌ Message expired and has been burned"
110
+
111
+ # Check if already burned
112
+ if message.burned or message.reads_left <= 0:
113
+ del self.messages[message_id]
114
+ return "❌ Message has already been burned"
115
+
116
+ # Read the message
117
+ content = message.content
118
+ message.reads_left -= 1
119
+
120
+ self.access_log.append({
121
+ 'action': 'read',
122
+ 'message_id': message_id,
123
+ 'player_id': reader_id,
124
+ 'timestamp': time.time()
125
+ })
126
+
127
+ # Burn the message after reading
128
+ if message.reads_left <= 0:
129
+ message.burned = True
130
+ del self.messages[message_id]
131
+ return f"🔥 **Message Self-Destructed After Reading**\n\n📖 **Content:** {content}\n\n💨 This message has been permanently destroyed."
132
+
133
+ return f"📖 **Message Content:** {content}\n\n⚠️ Reads remaining: {message.reads_left}"
134
+
135
+ def list_player_messages(self, player_id: str) -> str:
136
+ """List messages created by a player."""
137
+ player_messages = [msg for msg in self.messages.values() if msg.creator_id == player_id]
138
+
139
+ if not player_messages:
140
+ return "📪 No messages found. Create one with: `create Your message here`"
141
+
142
+ result = "📋 **Your Created Messages:**\n\n"
143
+ for msg in player_messages:
144
+ status = "🔥 Burned" if msg.burned else f"✅ Active ({msg.reads_left} reads left)"
145
+ created_time = time.strftime("%Y-%m-%d %H:%M", time.localtime(msg.created_at))
146
+ expires_time = time.strftime("%Y-%m-%d %H:%M", time.localtime(msg.expires_at))
147
+
148
+ result += f"**ID:** `{msg.id}`\n"
149
+ result += f"**Status:** {status}\n"
150
+ result += f"**Created:** {created_time}\n"
151
+ result += f"**Expires:** {expires_time}\n"
152
+ result += f"**Preview:** {msg.content[:50]}{'...' if len(msg.content) > 50 else ''}\n\n"
153
+
154
+ return result
155
+
156
+ def handle_command(self, player_id: str, command: str) -> str:
157
+ """Handle Read2Burn mailbox commands."""
158
+ parts = command.strip().split(' ', 1)
159
+ cmd = parts[0].lower()
160
+
161
+ if cmd == "create" and len(parts) > 1:
162
+ return self.create_message(player_id, parts[1])
163
+ elif cmd == "read" and len(parts) > 1:
164
+ return self.read_message(player_id, parts[1])
165
+ elif cmd == "list":
166
+ return self.list_player_messages(player_id)
167
+ elif cmd == "help":
168
+ return """📚 **Read2Burn Mailbox Commands:**
169
+
170
+ **create** `Your secret message here` - Create new message
171
+ **read** `MESSAGE_ID` - Read message (destroys it!)
172
+ **list** - Show your created messages
173
+ **help** - Show this help
174
+
175
+ 🔥 **Features:**
176
+ • Messages self-destruct after reading
177
+ • 24-hour automatic expiration
178
+ • Secure delivery system
179
+ • Anonymous messaging support"""
180
+ else:
181
+ return "❓ Invalid command. Try: `create <message>`, `read <id>`, `list`, or `help`"
182
+
183
+ def get_player_message_history(self, player_id: str) -> List[List[str]]:
184
+ """Get message history for display in a dataframe."""
185
+ player_messages = [msg for msg in self.messages.values() if msg.creator_id == player_id]
186
+
187
+ history = []
188
+ for msg in player_messages:
189
+ status = "🔥 Burned" if msg.burned else "✅ Active"
190
+ created_time = time.strftime("%H:%M", time.localtime(msg.created_at))
191
+ reads_left = str(msg.reads_left) if not msg.burned else "0"
192
+
193
+ history.append([msg.id, created_time, status, reads_left])
194
+
195
+ return history
196
+
197
+ def cleanup_expired_messages(self):
198
+ """Clean up expired messages."""
199
+ current_time = time.time()
200
+ expired_ids = [
201
+ msg_id for msg_id, msg in self.messages.items()
202
+ if current_time > msg.expires_at
203
+ ]
204
+
205
+ for msg_id in expired_ids:
206
+ del self.messages[msg_id]
207
+
208
+ if expired_ids:
209
+ print(f"[Read2Burn] Cleaned up {len(expired_ids)} expired messages")
210
+
211
+
212
+ # Global Read2Burn service instance
213
+ read2burn_service = Read2BurnService()
src/addons/self_contained_example_addon.py ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Self-Contained Example Addon - Demonstrates the simplified addon system.
3
+
4
+ This addon automatically registers itself and requires no manual edits to system files.
5
+ """
6
+
7
+ import time
8
+ import gradio as gr
9
+ from typing import Dict, Any, Optional
10
+
11
+ from ..interfaces.npc_addon import NPCAddon
12
+
13
+
14
+ class SelfContainedExampleAddon(NPCAddon):
15
+ """Example self-contained addon that demonstrates auto-registration."""
16
+
17
+ def __init__(self):
18
+ super().__init__()
19
+ self.interaction_count = 0
20
+ self.last_interaction = None
21
+
22
+ @property
23
+ def addon_id(self) -> str:
24
+ """Unique identifier for this add-on"""
25
+ return "self_contained_example"
26
+
27
+ @property
28
+ def addon_name(self) -> str:
29
+ """Display name for this add-on"""
30
+ return "🎯 Self-Contained Example"
31
+
32
+ @property
33
+ def npc_config(self) -> Dict[str, Any]:
34
+ """NPC configuration for auto-placement in world"""
35
+ return {
36
+ 'id': 'self_contained_npc',
37
+ 'name': '🎯 Example Bot',
38
+ 'x': 400, 'y': 350,
39
+ 'char': '🎯',
40
+ 'type': 'example',
41
+ 'description': 'Self-contained addon demonstration NPC'
42
+ }
43
+
44
+ @property
45
+ def ui_tab_name(self) -> str:
46
+ """UI tab name for this addon"""
47
+ return "🎯 Example Addon"
48
+
49
+ def get_interface(self) -> gr.Component:
50
+ """Return Gradio interface for this add-on"""
51
+ with gr.Column():
52
+ gr.Markdown("""
53
+ ## 🎯 Self-Contained Example Addon
54
+
55
+ This demonstrates the new simplified addon system where:
56
+ - ✅ **Auto-Registration**: No manual file edits needed
57
+ - ✅ **Self-Contained**: All config in one file
58
+ - ✅ **Auto-Positioning**: NPC placed automatically
59
+ - ✅ **UI Integration**: Tab created automatically
60
+
61
+ ### Features:
62
+ - Counter for interactions
63
+ - Timestamp of last interaction
64
+ - Example command processing
65
+ - Automatic UI generation
66
+ """)
67
+
68
+ # Status display
69
+ status_display = gr.JSON(
70
+ label="📊 Addon Status",
71
+ value={
72
+ "addon_id": self.addon_id,
73
+ "addon_name": self.addon_name,
74
+ "npc_position": f"({self.npc_config['x']}, {self.npc_config['y']})",
75
+ "interaction_count": self.interaction_count,
76
+ "last_interaction": self.last_interaction or "Never",
77
+ "status": "🟢 Active"
78
+ }
79
+ )
80
+
81
+ # Test interaction
82
+ with gr.Row():
83
+ test_input = gr.Textbox(
84
+ label="🧪 Test Command",
85
+ placeholder="Type 'status', 'count', 'reset', or 'help'",
86
+ scale=3
87
+ )
88
+ test_btn = gr.Button("Send Command", variant="primary", scale=1)
89
+
90
+ test_result = gr.Textbox(
91
+ label="📤 Response",
92
+ lines=3,
93
+ interactive=False
94
+ )
95
+
96
+ def handle_test_command(command: str):
97
+ """Handle test commands from the UI"""
98
+ result = self.handle_command("ui_user", command)
99
+
100
+ # Update status display
101
+ updated_status = {
102
+ "addon_id": self.addon_id,
103
+ "addon_name": self.addon_name,
104
+ "npc_position": f"({self.npc_config['x']}, {self.npc_config['y']})",
105
+ "interaction_count": self.interaction_count,
106
+ "last_interaction": self.last_interaction or "Never",
107
+ "status": "🟢 Active"
108
+ }
109
+
110
+ return result, updated_status
111
+
112
+ test_btn.click(
113
+ handle_test_command,
114
+ inputs=[test_input],
115
+ outputs=[test_result, status_display]
116
+ )
117
+
118
+ test_input.submit(
119
+ handle_test_command,
120
+ inputs=[test_input],
121
+ outputs=[test_result, status_display]
122
+ )
123
+
124
+ gr.Markdown("""
125
+ ### 🔧 Development Notes:
126
+
127
+ **File Location:** `src/addons/self_contained_example_addon.py`
128
+
129
+ **No Manual Edits Required:**
130
+ - ❌ No editing `world.py`
131
+ - ❌ No editing `game_engine.py`
132
+ - ❌ No editing `huggingface_ui.py`
133
+
134
+ **Auto-Registration:**
135
+ 1. Inherits from `NPCAddon`
136
+ 2. Calls `super().__init__()` to register
137
+ 3. Implements required properties and methods
138
+ 4. Game engine auto-discovers on startup
139
+
140
+ **Result:** Complete addon with NPC, UI, and commands!
141
+ """)
142
+
143
+ return gr.Column() # Return a container component
144
+
145
+ def handle_command(self, player_id: str, command: str) -> str:
146
+ """Handle player commands via private messages"""
147
+ self.interaction_count += 1
148
+ self.last_interaction = time.strftime("%Y-%m-%d %H:%M:%S")
149
+
150
+ cmd = command.strip().lower()
151
+
152
+ if cmd == "help":
153
+ return """🎯 **Example Addon Commands:**
154
+
155
+ **status** - Show addon status
156
+ **count** - Show interaction count
157
+ **reset** - Reset interaction counter
158
+ **info** - Show addon information
159
+ **help** - Show this help message
160
+
161
+ *This is a demonstration of the self-contained addon system!*"""
162
+
163
+ elif cmd == "status":
164
+ return f"""🎯 **Example Addon Status:**
165
+
166
+ 📊 **Interaction Count:** {self.interaction_count}
167
+ 🕒 **Last Interaction:** {self.last_interaction}
168
+ 📍 **NPC Position:** ({self.npc_config['x']}, {self.npc_config['y']})
169
+ 🎮 **Player:** {player_id}
170
+ ✅ **Status:** Active and responding"""
171
+
172
+ elif cmd == "count":
173
+ return f"🔢 **Interaction Count:** {self.interaction_count}"
174
+
175
+ elif cmd == "reset":
176
+ old_count = self.interaction_count
177
+ self.interaction_count = 1 # Set to 1 because this command counts as an interaction
178
+ return f"🔄 **Counter Reset!** Previous count: {old_count}, New count: {self.interaction_count}"
179
+
180
+ elif cmd == "info":
181
+ return f"""🎯 **Self-Contained Example Addon**
182
+
183
+ **ID:** `{self.addon_id}`
184
+ **Name:** {self.addon_name}
185
+ **Type:** Demonstration addon
186
+ **Features:** Auto-registration, UI integration, command processing
187
+
188
+ This addon demonstrates the new simplified system where all configuration,
189
+ registration, and positioning is contained within the addon file itself!"""
190
+
191
+ else:
192
+ return f"""❓ **Unknown command:** `{command}`
193
+
194
+ Try one of these commands:
195
+ • `help` - Show available commands
196
+ • `status` - Show current status
197
+ • `count` - Show interaction count
198
+ • `info` - Show addon information
199
+
200
+ 💡 *Tip: This addon auto-registered itself without any manual file edits!*"""
201
+
202
+ def on_startup(self):
203
+ """Called when the addon is loaded during game startup"""
204
+ print(f"[{self.addon_id}] Self-contained example addon started successfully!")
205
+ print(f"[{self.addon_id}] NPC positioned at ({self.npc_config['x']}, {self.npc_config['y']})")
206
+ print(f"[{self.addon_id}] UI tab '{self.ui_tab_name}' will be created automatically")
207
+
208
+ def on_shutdown(self):
209
+ """Called when the addon is unloaded during game shutdown"""
210
+ print(f"[{self.addon_id}] Self-contained example addon shutting down...")
211
+ print(f"[{self.addon_id}] Total interactions during session: {self.interaction_count}")
212
+
213
+
214
+ # Auto-instantiate the addon (this triggers registration)
215
+ self_contained_example = SelfContainedExampleAddon()
src/addons/simple_trader_addon.py ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Simple Trader NPC Add-on - Self-contained NPC with automatic registration.
3
+ """
4
+
5
+ import gradio as gr
6
+ from typing import Dict, List
7
+ from ..interfaces.npc_addon import NPCAddon
8
+
9
+
10
+ class SimpleTraderAddon(NPCAddon):
11
+ """Self-contained trader NPC that handles its own registration and positioning."""
12
+
13
+ def __init__(self):
14
+ super().__init__()
15
+ self.inventory = {
16
+ "Health Potion": {"price": 50, "stock": 10, "description": "Restores 100 HP"},
17
+ "Magic Scroll": {"price": 150, "stock": 5, "description": "Cast magic spells"},
18
+ "Iron Sword": {"price": 300, "stock": 3, "description": "Sharp iron weapon"},
19
+ "Shield": {"price": 200, "stock": 4, "description": "Protective gear"}
20
+ }
21
+ self.sales_history = []
22
+
23
+ # ===== ADDON INTERFACE =====
24
+ @property
25
+ def addon_id(self) -> str:
26
+ return "simple_trader"
27
+
28
+ @property
29
+ def addon_name(self) -> str:
30
+ return "🛒 Simple Trader"
31
+
32
+ def get_interface(self) -> gr.Component:
33
+ """Create the Gradio interface for this addon."""
34
+ with gr.Column() as interface:
35
+ gr.Markdown("### 🛒 Simple Trader Shop")
36
+ gr.Markdown("Browse items and check prices. Use private messages to trade!")
37
+
38
+ # Display inventory
39
+ inventory_data = []
40
+ for item, info in self.inventory.items():
41
+ inventory_data.append([item, f"{info['price']} gold", info['stock'], info['description']])
42
+
43
+ gr.Dataframe(
44
+ headers=["Item", "Price", "Stock", "Description"],
45
+ value=inventory_data,
46
+ interactive=False
47
+ )
48
+
49
+ gr.Markdown("**Commands:** Send private message to Simple Trader")
50
+ gr.Markdown("• `buy <item>` - Purchase an item")
51
+ gr.Markdown("• `inventory` - See available items")
52
+ gr.Markdown("• `help` - Show all commands")
53
+
54
+ return interface
55
+
56
+ def handle_command(self, player_id: str, command: str) -> str:
57
+ """Handle commands sent to this NPC."""
58
+ parts = command.strip().split(' ', 1)
59
+ cmd = parts[0].lower()
60
+
61
+ if cmd == "buy" and len(parts) > 1:
62
+ return self._handle_buy(player_id, parts[1])
63
+ elif cmd == "inventory":
64
+ return self._show_inventory()
65
+ elif cmd == "help":
66
+ return self._show_help()
67
+ else:
68
+ return "🛒 I don't understand that command. Try 'help' to see what I can do!"
69
+
70
+ # ===== AUTO-REGISTRATION METHODS =====
71
+
72
+ @classmethod
73
+ def get_npc_config(cls) -> Dict:
74
+ """Return the NPC configuration for automatic world registration."""
75
+ return {
76
+ 'id': 'simple_trader',
77
+ 'name': '🛒 Simple Trader',
78
+ 'x': 450, # Position on map
79
+ 'y': 150,
80
+ 'char': '🛒', # Character emoji
81
+ 'type': 'addon',
82
+ 'personality': 'trader',
83
+ 'description': 'A friendly trader selling useful items'
84
+ }
85
+
86
+ @classmethod
87
+ def auto_register(cls, game_engine):
88
+ """Automatically register this NPC with the game engine."""
89
+ try:
90
+ # Create addon instance
91
+ addon_instance = cls()
92
+
93
+ # Get NPC config
94
+ npc_config = cls.get_npc_config()
95
+
96
+ # Register NPC in world
97
+ npc_service = game_engine.get_npc_service()
98
+ npc_service.register_npc(npc_config['id'], npc_config)
99
+
100
+ # Register addon for command handling
101
+ if not hasattr(game_engine.get_world(), 'addon_npcs'):
102
+ game_engine.get_world().addon_npcs = {}
103
+ game_engine.get_world().addon_npcs[npc_config['id']] = addon_instance
104
+
105
+ print(f"[SimpleTrader] Auto-registered NPC '{npc_config['name']}' at position ({npc_config['x']}, {npc_config['y']})")
106
+ return True
107
+
108
+ except Exception as e:
109
+ print(f"[SimpleTrader] Auto-registration failed: {e}")
110
+ return False
111
+
112
+ # ===== PRIVATE METHODS =====
113
+
114
+ def _handle_buy(self, player_id: str, item_name: str) -> str:
115
+ """Handle buy command."""
116
+ # Find item (case-insensitive)
117
+ item_key = None
118
+ for key in self.inventory.keys():
119
+ if key.lower() == item_name.lower():
120
+ item_key = key
121
+ break
122
+
123
+ if not item_key:
124
+ return f"❌ I don't have '{item_name}' in stock. Try 'inventory' to see what's available."
125
+
126
+ item = self.inventory[item_key]
127
+
128
+ if item['stock'] <= 0:
129
+ return f"❌ Sorry, {item_key} is out of stock!"
130
+
131
+ # Simulate purchase (in real game, you'd check player's gold, etc.)
132
+ item['stock'] -= 1
133
+ self.sales_history.append({
134
+ 'player': player_id,
135
+ 'item': item_key,
136
+ 'price': item['price']
137
+ })
138
+
139
+ return f"✅ **Purchase Successful!**\n\n🛒 **Item:** {item_key}\n💰 **Price:** {item['price']} gold\n📦 **Stock Remaining:** {item['stock']}\n\nThank you for your business!"
140
+
141
+ def _show_inventory(self) -> str:
142
+ """Show current inventory."""
143
+ if not self.inventory:
144
+ return "📦 My shop is currently empty. Come back later!"
145
+
146
+ result = "🛒 **Simple Trader Shop Inventory:**\n\n"
147
+ for item, info in self.inventory.items():
148
+ stock_status = "✅ In Stock" if info['stock'] > 0 else "❌ Out of Stock"
149
+ result += f"**{item}**\n"
150
+ result += f"💰 Price: {info['price']} gold\n"
151
+ result += f"📦 Stock: {info['stock']}\n"
152
+ result += f"📝 {info['description']}\n"
153
+ result += f"Status: {stock_status}\n\n"
154
+
155
+ result += "💬 Send me 'buy <item name>' to purchase!"
156
+ return result
157
+
158
+ def _show_help(self) -> str:
159
+ """Show help information."""
160
+ return """🛒 **Simple Trader Commands:**
161
+
162
+ **buy** `<item name>` - Purchase an item
163
+ **inventory** - See all available items
164
+ **help** - Show this help
165
+
166
+ 📋 **Example Commands:**
167
+ • buy Health Potion
168
+ • buy magic scroll
169
+ • inventory
170
+
171
+ 💰 **How to Trade:**
172
+ 1. Check my inventory to see items and prices
173
+ 2. Send me a buy command with the item name
174
+ 3. Complete your purchase!
175
+
176
+ 🏪 I'm here to help you gear up for your adventures!"""
177
+
178
+
179
+ # Global instance for easy access
180
+ simple_trader_addon = SimpleTraderAddon()
181
+
182
+
183
+ # Auto-registration function that can be called from game engine
184
+ def register_simple_trader(game_engine):
185
+ """Convenience function to register this addon."""
186
+ return SimpleTraderAddon.auto_register(game_engine)
src/addons/weather_oracle_addon.py ADDED
@@ -0,0 +1,380 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Weather Oracle Add-on - MCP-powered weather information system.
3
+ """
4
+
5
+ import time
6
+ import json
7
+ import random
8
+ import string
9
+ import asyncio
10
+ from typing import Dict, List, Optional
11
+ import gradio as gr
12
+ from abc import ABC, abstractmethod
13
+ from mcp import ClientSession
14
+ from mcp.client.sse import sse_client
15
+ from contextlib import AsyncExitStack
16
+
17
+ from ..interfaces.npc_addon import NPCAddon
18
+
19
+
20
+ class IWeatherService(ABC):
21
+ """Interface for Weather service operations."""
22
+
23
+ @abstractmethod
24
+ def get_weather(self, location: str) -> str:
25
+ """Get weather information for a location."""
26
+ pass
27
+
28
+ @abstractmethod
29
+ def connect_to_mcp(self) -> str:
30
+ """Connect to MCP weather server."""
31
+ pass
32
+
33
+
34
+ class WeatherOracleService(IWeatherService, NPCAddon):
35
+ """Service for managing Weather Oracle MCP integration."""
36
+
37
+ def __init__(self):
38
+ super().__init__()
39
+ self.connected = False
40
+ self.last_connection_attempt = 0
41
+ self.connection_cooldown = 30 # 30 seconds between connection attempts
42
+ self.server_url = "https://chris4k-weather.hf.space/gradio_api/mcp/sse"
43
+ self.session = None
44
+ self.tools = []
45
+ self.exit_stack = None
46
+ # Set up event loop for async operations
47
+ try:
48
+ self.loop = asyncio.get_event_loop()
49
+ except RuntimeError:
50
+ self.loop = asyncio.new_event_loop()
51
+
52
+ @property
53
+ def addon_id(self) -> str:
54
+ """Unique identifier for this add-on"""
55
+ return "weather_oracle"
56
+
57
+ @property
58
+ def addon_name(self) -> str:
59
+ """Display name for this add-on"""
60
+ return "Weather Oracle (MCP)"
61
+
62
+ @property
63
+ def npc_config(self) -> Dict:
64
+ """NPC configuration for auto-placement in world"""
65
+ return {
66
+ 'id': 'weather_oracle',
67
+ 'name': 'Weather Oracle (MCP)',
68
+ 'x': 150, 'y': 300,
69
+ 'char': '🌤️',
70
+ 'type': 'mcp',
71
+ 'description': 'MCP-powered weather information service'
72
+ }
73
+
74
+ @property
75
+ def ui_tab_name(self) -> str:
76
+ """UI tab name for this addon"""
77
+ return "Weather Oracle"
78
+
79
+ def get_interface(self) -> gr.Component:
80
+ """Return Gradio interface for this add-on"""
81
+ with gr.Column() as interface:
82
+ gr.Markdown("""
83
+ ## 🌤️ Weather Oracle (MCP)
84
+
85
+ *I commune with the weather spirits through the mystical MCP protocol to bring you weather wisdom from across the realms!*
86
+
87
+ **Ask me about weather in any city:**
88
+ - Current conditions and temperature
89
+ - Real-time weather data from global sources
90
+ - Powered by Model Context Protocol (MCP)
91
+
92
+ *Format: "City, Country" (e.g., "Berlin, Germany")*
93
+ """)
94
+
95
+ # Connection status
96
+ connection_status = gr.HTML(
97
+ value=f"<div style='color: {'green' if self.connected else 'red'};'>{'🟢 Connected to weather spirits' if self.connected else '🔴 Disconnected from weather realm'}</div>"
98
+ )
99
+
100
+ with gr.Row():
101
+ location_input = gr.Textbox(
102
+ label="Location",
103
+ placeholder="e.g., Berlin, Germany",
104
+ scale=3
105
+ )
106
+ get_weather_btn = gr.Button("🌡️ Consult Weather Spirits", variant="primary", scale=1)
107
+
108
+ weather_output = gr.Textbox(
109
+ label="🌤️ Weather Wisdom",
110
+ lines=8,
111
+ interactive=False,
112
+ placeholder="Enter a location and I will consult the weather spirits..."
113
+ )
114
+
115
+ # Example locations
116
+ with gr.Row():
117
+ gr.Examples(
118
+ examples=[
119
+ ["Berlin, Germany"],
120
+ ["Tokyo, Japan"],
121
+ ["New York, USA"],
122
+ ["London, UK"],
123
+ ["Sydney, Australia"],
124
+ ["Paris, France"],
125
+ ["Moscow, Russia"]
126
+ ],
127
+ inputs=[location_input],
128
+ label="🌍 Try These Locations"
129
+ )
130
+
131
+ # Connection controls
132
+ with gr.Row():
133
+ connect_btn = gr.Button("🔗 Connect to MCP", variant="secondary")
134
+ status_btn = gr.Button("📊 Check Status", variant="secondary")
135
+
136
+ def handle_weather_request(location: str):
137
+ if not location.strip():
138
+ return "❓ Please enter a location to get weather information."
139
+ return self.get_weather(location)
140
+
141
+ def handle_connect():
142
+ result = self.connect_to_mcp()
143
+ # Update connection status
144
+ new_status = f"<div style='color: {'green' if self.connected else 'red'};'>{'🟢 Connected to weather spirits' if self.connected else '🔴 Disconnected from weather realm'}</div>"
145
+ return result, new_status
146
+
147
+ def handle_status():
148
+ status = "🟢 Connected" if self.connected else "🔴 Disconnected"
149
+ return f"🌤️ **Weather Oracle Status**\n\nConnection: {status}\nLast update: {time.strftime('%H:%M')}"
150
+
151
+ # Wire up events
152
+ get_weather_btn.click(
153
+ handle_weather_request,
154
+ inputs=[location_input],
155
+ outputs=[weather_output]
156
+ )
157
+
158
+ location_input.submit(
159
+ handle_weather_request,
160
+ inputs=[location_input],
161
+ outputs=[weather_output]
162
+ )
163
+
164
+ connect_btn.click(
165
+ handle_connect,
166
+ outputs=[weather_output, connection_status]
167
+ )
168
+
169
+ status_btn.click(
170
+ handle_status,
171
+ outputs=[weather_output]
172
+ )
173
+
174
+ return interface
175
+
176
+ def connect_to_mcp(self) -> str:
177
+ """Connect to MCP weather server."""
178
+ current_time = time.time()
179
+ if current_time - self.last_connection_attempt < self.connection_cooldown:
180
+ return "⏳ Please wait before retrying connection..."
181
+
182
+ self.last_connection_attempt = current_time
183
+
184
+ try:
185
+ return self.loop.run_until_complete(self._connect())
186
+ except Exception as e:
187
+ self.connected = False
188
+ return f"❌ Connection failed: {str(e)}"
189
+
190
+ async def _connect(self) -> str:
191
+ """Async connect to MCP server using SSE."""
192
+ try:
193
+ # Clean up previous connection
194
+ if self.exit_stack:
195
+ await self.exit_stack.aclose()
196
+
197
+ self.exit_stack = AsyncExitStack()
198
+
199
+ # Connect to SSE MCP server
200
+ sse_transport = await self.exit_stack.enter_async_context(
201
+ sse_client(self.server_url)
202
+ )
203
+ read_stream, write_callable = sse_transport
204
+
205
+ self.session = await self.exit_stack.enter_async_context(
206
+ ClientSession(read_stream, write_callable)
207
+ )
208
+ await self.session.initialize()
209
+
210
+ # Get available tools
211
+ response = await self.session.list_tools()
212
+ self.tools = response.tools
213
+
214
+ self.connected = True
215
+ tool_names = [tool.name for tool in self.tools]
216
+ return f"✅ Connected to weather MCP server!\nAvailable tools: {', '.join(tool_names)}"
217
+
218
+ except Exception as e:
219
+ self.connected = False
220
+ return f"❌ Connection failed: {str(e)}"
221
+
222
+ def get_weather(self, location: str) -> str:
223
+ """Get weather for a location using actual MCP server"""
224
+ if not self.connected:
225
+ # Try to auto-connect
226
+ connect_result = self.connect_to_mcp()
227
+ if not self.connected:
228
+ return f"❌ Failed to connect to weather server. {connect_result}"
229
+
230
+ if not location.strip():
231
+ return "❌ Please enter a location (e.g., 'Berlin, Germany')"
232
+
233
+ try:
234
+ return self.loop.run_until_complete(self._get_weather(location))
235
+ except Exception as e:
236
+ return f"❌ Error getting weather: {str(e)}"
237
+
238
+ async def _get_weather(self, location: str) -> str:
239
+ """Async get weather using MCP."""
240
+ try:
241
+ # Parse location
242
+ if ',' in location:
243
+ city, country = [part.strip() for part in location.split(',', 1)]
244
+ else:
245
+ city = location.strip()
246
+ country = ""
247
+
248
+ # Find the weather tool
249
+ weather_tool = next((tool for tool in self.tools if 'weather' in tool.name.lower()), None)
250
+ if not weather_tool:
251
+ return "❌ Weather tool not found on server"
252
+
253
+ # Call the tool
254
+ params = {"city": city, "country": country}
255
+ result = await self.session.call_tool(weather_tool.name, params)
256
+
257
+ # Extract content properly
258
+ content_text = ""
259
+ if hasattr(result, 'content') and result.content:
260
+ if isinstance(result.content, list):
261
+ for content_item in result.content:
262
+ if hasattr(content_item, 'text'):
263
+ content_text += content_item.text
264
+ elif hasattr(content_item, 'content'):
265
+ content_text += str(content_item.content)
266
+ else:
267
+ content_text += str(content_item)
268
+ elif hasattr(result.content, 'text'):
269
+ content_text = result.content.text
270
+ else:
271
+ content_text = str(result.content)
272
+
273
+ if not content_text:
274
+ return "❌ No content received from server"
275
+
276
+ try:
277
+ # Try to parse as JSON
278
+ parsed = json.loads(content_text)
279
+ if isinstance(parsed, dict):
280
+ if 'error' in parsed:
281
+ return f"❌ Error: {parsed['error']}"
282
+
283
+ # Format weather data nicely
284
+ if 'current_weather' in parsed:
285
+ weather = parsed['current_weather']
286
+ formatted = f"🌍 **{parsed.get('location', location)}**\n\n"
287
+ formatted += f"🌡️ Temperature: {weather.get('temperature_celsius', 'N/A')}°C\n"
288
+ formatted += f"🌤️ Conditions: {weather.get('weather_description', 'N/A')}\n"
289
+ formatted += f"💨 Wind: {weather.get('wind_speed_kmh', 'N/A')} km/h\n"
290
+ formatted += f"💧 Humidity: {weather.get('humidity_percent', 'N/A')}%\n"
291
+ formatted += f"\n⏰ Last updated: {time.strftime('%H:%M')}\n⚡ **Powered by MCP**"
292
+ return formatted
293
+ elif 'temperature (°C)' in parsed:
294
+ # Handle the original format from your server
295
+ formatted = f"🌍 **{parsed.get('location', location)}**\n\n"
296
+ formatted += f"🌡️ Temperature: {parsed.get('temperature (°C)', 'N/A')}°C\n"
297
+ formatted += f"🌤️ Weather Code: {parsed.get('weather_code', 'N/A')}\n"
298
+ formatted += f"🕐 Timezone: {parsed.get('timezone', 'N/A')}\n"
299
+ formatted += f"🕒 Local Time: {parsed.get('local_time', 'N/A')}\n"
300
+ formatted += f"\n⏰ Last updated: {time.strftime('%H:%M')}\n⚡ **Powered by MCP**"
301
+ return formatted
302
+ else:
303
+ return f"🌍 **Weather for {location}**\n\n✅ Weather data:\n```json\n{json.dumps(parsed, indent=2)}\n```\n\n⚡ **Powered by MCP**"
304
+
305
+ except json.JSONDecodeError:
306
+ # If not JSON, return as text
307
+ return f"🌍 **Weather for {location}**\n\n✅ Weather data:\n```\n{content_text}\n```\n\n⚡ **Powered by MCP**"
308
+
309
+ return f"🌍 **Weather for {location}**\n\n✅ Raw result:\n{content_text}\n\n⚡ **Powered by MCP**"
310
+
311
+ except Exception as e:
312
+ return f"❌ Failed to get weather: {str(e)}"
313
+
314
+ def handle_command(self, player_id: str, command: str) -> str:
315
+ """Handle Weather Oracle commands."""
316
+ parts = command.strip().split(' ', 1)
317
+ cmd = parts[0].lower()
318
+
319
+ if cmd == "weather" and len(parts) > 1:
320
+ return self.get_weather(parts[1])
321
+ elif cmd == "connect":
322
+ return self.connect_to_mcp()
323
+ elif cmd == "status":
324
+ status = "🟢 Connected" if self.connected else "🔴 Disconnected"
325
+ return f"🌤️ **Weather Oracle Status**\n\nConnection: {status}\nLast update: {time.strftime('%H:%M')}"
326
+ elif cmd == "help":
327
+ return """🌤️ **Weather Oracle Commands:**
328
+
329
+ **weather** `location` - Get weather (e.g., 'weather Berlin, Germany')
330
+ **connect** - Connect to MCP weather server
331
+ **status** - Check connection status
332
+ **help** - Show this help
333
+
334
+ 🌍 **Example Commands:**
335
+ • weather London, UK
336
+ • weather Tokyo
337
+ • weather New York, USA
338
+
339
+ ⚡ **Powered by MCP (Model Context Protocol)**"""
340
+ else:
341
+ return "❓ Invalid command. Try: `weather <location>`, `connect`, `status`, or `help`"
342
+
343
+
344
+ # Global Weather Oracle service instance
345
+ weather_oracle_service = WeatherOracleService()
346
+
347
+
348
+ def auto_register(game_engine):
349
+ """Auto-register the Weather Oracle addon with the game engine.
350
+
351
+ This function makes the addon self-contained by handling its own registration.
352
+ """
353
+ try:
354
+ # Create the weather oracle NPC definition
355
+ weather_oracle_npc = {
356
+ 'id': 'weather_oracle_auto',
357
+ 'name': '🌤️ Weather Oracle (Auto)',
358
+ 'x': 300, 'y': 150,
359
+ 'char': '🌤️',
360
+ 'type': 'mcp',
361
+ 'personality': 'weather_oracle',
362
+ 'description': 'Self-contained MCP-powered weather information service'
363
+ }
364
+
365
+ # Register the NPC with the NPC service
366
+ npc_service = game_engine.get_npc_service()
367
+ npc_service.register_npc('weather_oracle_auto', weather_oracle_npc)
368
+
369
+ # Register the addon for handling private message commands
370
+ world = game_engine.get_world()
371
+ if not hasattr(world, 'addon_npcs'):
372
+ world.addon_npcs = {}
373
+ world.addon_npcs['weather_oracle_auto'] = weather_oracle_service
374
+
375
+ print("[WeatherOracleAddon] Auto-registered successfully as self-contained addon")
376
+ return True
377
+
378
+ except Exception as e:
379
+ print(f"[WeatherOracleAddon] Error during auto-registration: {e}")
380
+ return False
src/core/__pycache__/game_engine.cpython-313.pyc ADDED
Binary file (12.4 kB). View file
 
src/core/__pycache__/player.cpython-313.pyc ADDED
Binary file (4.66 kB). View file