|
|
|
"""
|
|
Simple MMORPG Game Client
|
|
========================
|
|
|
|
A basic client implementation for connecting to the MMORPG game server via MCP.
|
|
This client can join the game, move around, chat, and interact with NPCs.
|
|
|
|
Requirements:
|
|
pip install mcp aiohttp asyncio
|
|
|
|
Usage:
|
|
python simple_game_client.py
|
|
|
|
Features:
|
|
- Connect to game server via MCP
|
|
- Register as a player
|
|
- Move around the game world
|
|
- Send chat messages
|
|
- Interact with NPCs
|
|
- Get game state information
|
|
- Interactive console interface
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
import uuid
|
|
import sys
|
|
from typing import Dict, List, Optional
|
|
|
|
try:
|
|
from mcp import ClientSession
|
|
from mcp.client.sse import sse_client
|
|
from contextlib import AsyncExitStack
|
|
except ImportError:
|
|
print("❌ Missing dependencies. Install with: pip install mcp aiohttp")
|
|
sys.exit(1)
|
|
|
|
|
|
class SimpleGameClient:
|
|
"""Simple client for connecting to the MMORPG game server"""
|
|
|
|
def __init__(self, server_url="http://127.0.0.1:7868/gradio_api/mcp/sse"):
|
|
self.server_url = server_url
|
|
self.session = None
|
|
self.connected = False
|
|
self.tools = []
|
|
self.exit_stack = None
|
|
self.client_id = f"client_{uuid.uuid4().hex[:8]}"
|
|
self.agent_id = None
|
|
self.player_name = None
|
|
|
|
print(f"🎮 Game Client initialized with ID: {self.client_id}")
|
|
|
|
async def connect(self):
|
|
"""Connect to the game server"""
|
|
print(f"🔌 Connecting to {self.server_url}...")
|
|
|
|
try:
|
|
|
|
if self.exit_stack:
|
|
await self.exit_stack.aclose()
|
|
|
|
self.exit_stack = AsyncExitStack()
|
|
|
|
|
|
transport = await self.exit_stack.enter_async_context(
|
|
sse_client(self.server_url)
|
|
)
|
|
read_stream, write_callable = transport
|
|
|
|
|
|
self.session = await self.exit_stack.enter_async_context(
|
|
ClientSession(read_stream, write_callable)
|
|
)
|
|
|
|
|
|
await self.session.initialize()
|
|
|
|
|
|
response = await self.session.list_tools()
|
|
self.tools = response.tools
|
|
|
|
self.connected = True
|
|
tool_names = [tool.name for tool in self.tools]
|
|
print(f"✅ Connected successfully!")
|
|
print(f"📋 Available commands: {', '.join(tool_names)}")
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"❌ Connection failed: {e}")
|
|
self.connected = False
|
|
return False
|
|
|
|
async def register_player(self, player_name: str):
|
|
"""Register as a new player in the game"""
|
|
if not self.connected:
|
|
print("❌ Not connected to server")
|
|
return False
|
|
|
|
print(f"👤 Registering player: {player_name}")
|
|
|
|
try:
|
|
|
|
register_tool = self._find_tool(['register', 'agent'])
|
|
if not register_tool:
|
|
print("❌ Register tool not available on server")
|
|
return False
|
|
|
|
|
|
result = await self.session.call_tool(
|
|
register_tool.name,
|
|
{"name": player_name, "client_id": self.client_id}
|
|
)
|
|
|
|
|
|
content = self._extract_content(result)
|
|
print(f"📝 Registration response: {content}")
|
|
|
|
if "registered" in content.lower() or "success" in content.lower():
|
|
self.player_name = player_name
|
|
|
|
self._parse_agent_id(content)
|
|
print(f"✅ Successfully registered as: {player_name}")
|
|
return True
|
|
else:
|
|
print(f"❌ Registration failed: {content}")
|
|
return False
|
|
|
|
except Exception as e:
|
|
print(f"❌ Registration error: {e}")
|
|
return False
|
|
|
|
async def move_player(self, direction: str):
|
|
"""Move the player in the specified direction"""
|
|
if not self._check_ready():
|
|
return False
|
|
|
|
direction = direction.lower()
|
|
valid_directions = ['north', 'south', 'east', 'west', 'up', 'down']
|
|
|
|
if direction not in valid_directions:
|
|
print(f"❌ Invalid direction. Use: {', '.join(valid_directions)}")
|
|
return False
|
|
|
|
try:
|
|
|
|
move_tool = self._find_tool(['move', 'agent'])
|
|
if not move_tool:
|
|
print("❌ Move tool not available")
|
|
return False
|
|
|
|
|
|
result = await self.session.call_tool(
|
|
move_tool.name,
|
|
{"client_id": self.client_id, "direction": direction}
|
|
)
|
|
|
|
content = self._extract_content(result)
|
|
print(f"🚶 Move {direction}: {content}")
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"❌ Move error: {e}")
|
|
return False
|
|
|
|
async def send_chat(self, message: str):
|
|
"""Send a chat message to other players"""
|
|
if not self._check_ready():
|
|
return False
|
|
|
|
if not message.strip():
|
|
print("❌ Cannot send empty message")
|
|
return False
|
|
|
|
try:
|
|
|
|
chat_tool = self._find_tool(['chat', 'send'])
|
|
if not chat_tool:
|
|
print("❌ Chat tool not available")
|
|
return False
|
|
|
|
|
|
result = await self.session.call_tool(
|
|
chat_tool.name,
|
|
{"client_id": self.client_id, "message": message}
|
|
)
|
|
|
|
content = self._extract_content(result)
|
|
print(f"💬 Chat sent: {content}")
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"❌ Chat error: {e}")
|
|
return False
|
|
|
|
async def get_game_state(self):
|
|
"""Get current game state information"""
|
|
if not self.connected:
|
|
print("❌ Not connected to server")
|
|
return None
|
|
|
|
try:
|
|
|
|
state_tool = self._find_tool(['state', 'game'])
|
|
if not state_tool:
|
|
print("❌ Game state tool not available")
|
|
return None
|
|
|
|
|
|
result = await self.session.call_tool(state_tool.name, {})
|
|
content = self._extract_content(result)
|
|
|
|
try:
|
|
|
|
game_state = json.loads(content)
|
|
return game_state
|
|
except json.JSONDecodeError:
|
|
|
|
return {"info": content}
|
|
|
|
except Exception as e:
|
|
print(f"❌ Game state error: {e}")
|
|
return None
|
|
|
|
async def interact_with_npc(self, npc_id: str, message: str):
|
|
"""Interact with an NPC"""
|
|
if not self._check_ready():
|
|
return False
|
|
|
|
if not npc_id.strip() or not message.strip():
|
|
print("❌ NPC ID and message are required")
|
|
return False
|
|
|
|
try:
|
|
|
|
interact_tool = self._find_tool(['interact', 'npc'])
|
|
if not interact_tool:
|
|
print("❌ NPC interaction tool not available")
|
|
return False
|
|
|
|
|
|
result = await self.session.call_tool(
|
|
interact_tool.name,
|
|
{"client_id": self.client_id, "npc_id": npc_id, "message": message}
|
|
)
|
|
|
|
content = self._extract_content(result)
|
|
print(f"🤖 {npc_id}: {content}")
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"❌ NPC interaction error: {e}")
|
|
return False
|
|
|
|
async def list_tools(self):
|
|
"""List all available tools on the server"""
|
|
if not self.connected:
|
|
print("❌ Not connected to server")
|
|
return
|
|
|
|
print("🛠️ Available Tools:")
|
|
for i, tool in enumerate(self.tools, 1):
|
|
print(f" {i}. {tool.name}")
|
|
if hasattr(tool, 'description') and tool.description:
|
|
print(f" Description: {tool.description}")
|
|
|
|
def _find_tool(self, keywords: List[str]):
|
|
"""Find a tool by keywords"""
|
|
for keyword in keywords:
|
|
tool = next((t for t in self.tools if keyword in t.name.lower()), None)
|
|
if tool:
|
|
return tool
|
|
return None
|
|
|
|
def _extract_content(self, result):
|
|
"""Extract content from MCP result"""
|
|
try:
|
|
if hasattr(result, 'content') and result.content:
|
|
if isinstance(result.content, list):
|
|
content_parts = []
|
|
for item in result.content:
|
|
if hasattr(item, 'text'):
|
|
content_parts.append(item.text)
|
|
else:
|
|
content_parts.append(str(item))
|
|
return ''.join(content_parts)
|
|
elif hasattr(result.content, 'text'):
|
|
return result.content.text
|
|
else:
|
|
return str(result.content)
|
|
return str(result)
|
|
except Exception as e:
|
|
return f"Error extracting content: {e}"
|
|
|
|
def _parse_agent_id(self, content: str):
|
|
"""Try to extract agent ID from response"""
|
|
lines = content.split('\n')
|
|
for line in lines:
|
|
if any(keyword in line.lower() for keyword in ['agent_id', 'id:', 'player id']):
|
|
parts = line.split(':')
|
|
if len(parts) > 1:
|
|
self.agent_id = parts[1].strip()
|
|
print(f"🆔 Agent ID: {self.agent_id}")
|
|
break
|
|
|
|
def _check_ready(self):
|
|
"""Check if client is ready for game operations"""
|
|
if not self.connected:
|
|
print("❌ Not connected to server")
|
|
return False
|
|
if not self.player_name:
|
|
print("❌ Not registered as a player")
|
|
return False
|
|
return True
|
|
|
|
async def disconnect(self):
|
|
"""Disconnect from the server"""
|
|
if self.exit_stack:
|
|
await self.exit_stack.aclose()
|
|
self.connected = False
|
|
self.player_name = None
|
|
self.agent_id = None
|
|
print("🔌 Disconnected from server")
|
|
|
|
|
|
class InteractiveGameClient:
|
|
"""Interactive console interface for the game client"""
|
|
|
|
def __init__(self):
|
|
self.client = SimpleGameClient()
|
|
self.running = True
|
|
|
|
async def start(self):
|
|
"""Start the interactive client"""
|
|
print("🎮 MMORPG Simple Game Client")
|
|
print("=" * 40)
|
|
self.show_help()
|
|
|
|
while self.running:
|
|
try:
|
|
command = input("\n> ").strip()
|
|
if command:
|
|
await self.process_command(command)
|
|
except KeyboardInterrupt:
|
|
print("\n\n👋 Goodbye!")
|
|
break
|
|
except EOFError:
|
|
break
|
|
|
|
await self.client.disconnect()
|
|
|
|
async def process_command(self, command: str):
|
|
"""Process a user command"""
|
|
parts = command.split()
|
|
if not parts:
|
|
return
|
|
|
|
cmd = parts[0].lower()
|
|
args = parts[1:]
|
|
|
|
if cmd == "help":
|
|
self.show_help()
|
|
|
|
elif cmd == "connect":
|
|
await self.client.connect()
|
|
|
|
elif cmd == "register":
|
|
if args:
|
|
name = " ".join(args)
|
|
await self.client.register_player(name)
|
|
else:
|
|
print("❌ Usage: register <player_name>")
|
|
|
|
elif cmd == "move":
|
|
if args:
|
|
direction = args[0]
|
|
await self.client.move_player(direction)
|
|
else:
|
|
print("❌ Usage: move <direction>")
|
|
print(" Directions: north, south, east, west")
|
|
|
|
elif cmd == "chat":
|
|
if args:
|
|
message = " ".join(args)
|
|
await self.client.send_chat(message)
|
|
else:
|
|
print("❌ Usage: chat <message>")
|
|
|
|
elif cmd == "state":
|
|
state = await self.client.get_game_state()
|
|
if state:
|
|
print("📊 Game State:")
|
|
print(json.dumps(state, indent=2))
|
|
|
|
elif cmd == "npc":
|
|
if len(args) >= 2:
|
|
npc_id = args[0]
|
|
message = " ".join(args[1:])
|
|
await self.client.interact_with_npc(npc_id, message)
|
|
else:
|
|
print("❌ Usage: npc <npc_id> <message>")
|
|
print(" Example: npc weather_oracle What's the weather in Berlin?")
|
|
|
|
elif cmd == "tools":
|
|
await self.client.list_tools()
|
|
|
|
elif cmd in ["quit", "exit", "q"]:
|
|
self.running = False
|
|
|
|
else:
|
|
print(f"❓ Unknown command: {cmd}")
|
|
print(" Type 'help' for available commands")
|
|
|
|
def show_help(self):
|
|
"""Show available commands"""
|
|
print("\n📋 Available Commands:")
|
|
print(" help - Show this help")
|
|
print(" connect - Connect to game server")
|
|
print(" register <name> - Register as a player")
|
|
print(" move <direction> - Move player (north/south/east/west)")
|
|
print(" chat <message> - Send chat message")
|
|
print(" npc <id> <message> - Talk to an NPC")
|
|
print(" state - Get game state")
|
|
print(" tools - List available server tools")
|
|
print(" quit - Exit the client")
|
|
print("\n💡 Example session:")
|
|
print(" > connect")
|
|
print(" > register MyPlayer")
|
|
print(" > move north")
|
|
print(" > chat Hello everyone!")
|
|
print(" > npc weather_oracle What's the weather?")
|
|
|
|
|
|
async def quick_demo():
|
|
"""Quick demo showing basic functionality"""
|
|
print("🚀 Running Quick Demo...")
|
|
|
|
client = SimpleGameClient()
|
|
|
|
|
|
if not await client.connect():
|
|
print("❌ Demo failed - could not connect")
|
|
return
|
|
|
|
|
|
if not await client.register_player("DemoPlayer"):
|
|
print("❌ Demo failed - could not register")
|
|
return
|
|
|
|
|
|
await client.send_chat("Hello! Demo player here!")
|
|
|
|
|
|
moves = ["north", "east", "south", "west"]
|
|
for direction in moves:
|
|
await client.move_player(direction)
|
|
await asyncio.sleep(1)
|
|
|
|
|
|
state = await client.get_game_state()
|
|
if state:
|
|
print(f"📊 Current game state: {json.dumps(state, indent=2)}")
|
|
|
|
|
|
await client.interact_with_npc("weather_oracle", "Hello there!")
|
|
|
|
await client.disconnect()
|
|
print("✅ Demo completed!")
|
|
|
|
|
|
def main():
|
|
"""Main entry point"""
|
|
if len(sys.argv) > 1 and sys.argv[1] == "demo":
|
|
|
|
asyncio.run(quick_demo())
|
|
else:
|
|
|
|
client = InteractiveGameClient()
|
|
asyncio.run(client.start())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|