Files
g0v0-server/app/signalr/hub/spectator_multiplayer_integration.py
2025-08-22 13:52:28 +08:00

252 lines
11 KiB
Python

"""
SpectatorHub增强补丁
与MultiplayerHub集成以支持多人游戏观战
"""
from __future__ import annotations
import asyncio
import json
from datetime import datetime, UTC
from typing import Dict, Optional
from collections import defaultdict
from app.dependencies.database import get_redis
from app.log import logger
class SpectatorMultiplayerIntegration:
"""SpectatorHub的多人游戏集成扩展"""
def __init__(self, spectator_hub_instance):
self.hub = spectator_hub_instance
self.redis = None
self.multiplayer_subscribers = defaultdict(set) # room_id -> set of connection_ids
self.leaderboard_cache = {} # room_id -> leaderboard data
async def initialize(self):
"""初始化多人游戏集成"""
self.redis = get_redis()
# 启动Redis订阅任务
asyncio.create_task(self._subscribe_to_multiplayer_events())
asyncio.create_task(self._subscribe_to_leaderboard_updates())
asyncio.create_task(self._subscribe_to_spectator_sync())
async def _subscribe_to_multiplayer_events(self):
"""订阅多人游戏事件"""
try:
pubsub = self.redis.pubsub()
await pubsub.psubscribe("multiplayer_spectator:*")
async for message in pubsub.listen():
if message['type'] == 'pmessage':
try:
data = json.loads(message['data'])
await self._handle_multiplayer_event(message['channel'], data)
except Exception as e:
logger.error(f"Error processing multiplayer event: {e}")
except Exception as e:
logger.error(f"Error in multiplayer events subscription: {e}")
async def _subscribe_to_leaderboard_updates(self):
"""订阅排行榜更新"""
try:
pubsub = self.redis.pubsub()
await pubsub.psubscribe("leaderboard_update:*")
async for message in pubsub.listen():
if message['type'] == 'pmessage':
try:
data = json.loads(message['data'])
user_id = message['channel'].split(':')[-1]
await self._send_leaderboard_to_user(int(user_id), data['leaderboard'])
except Exception as e:
logger.error(f"Error processing leaderboard update: {e}")
except Exception as e:
logger.error(f"Error in leaderboard updates subscription: {e}")
async def _subscribe_to_spectator_sync(self):
"""订阅观战同步事件"""
try:
pubsub = self.redis.pubsub()
await pubsub.psubscribe("spectator_sync:*")
async for message in pubsub.listen():
if message['type'] == 'pmessage':
try:
data = json.loads(message['data'])
room_id = message['channel'].split(':')[-1]
await self._handle_spectator_sync(int(room_id), data)
except Exception as e:
logger.error(f"Error processing spectator sync: {e}")
except Exception as e:
logger.error(f"Error in spectator sync subscription: {e}")
async def _handle_multiplayer_event(self, channel: str, data: Dict):
"""处理多人游戏事件"""
room_id = data.get('room_id')
event_type = data.get('event_type')
event_data = data.get('data', {})
if not room_id or not event_type:
return
if event_type == "gameplay_started":
await self._handle_gameplay_started(room_id, event_data)
elif event_type == "gameplay_ended":
await self._handle_gameplay_ended(room_id, event_data)
elif event_type == "user_state_changed":
await self._handle_user_state_changed(room_id, event_data)
async def _handle_gameplay_started(self, room_id: int, game_data: Dict):
"""处理游戏开始事件"""
logger.info(f"[SpectatorHub] Multiplayer game started in room {room_id}")
# 通知所有观战该房间的用户
if room_id in self.multiplayer_subscribers:
for connection_id in self.multiplayer_subscribers[room_id]:
await self._send_to_connection(connection_id, "MultiplayerGameStarted", game_data)
async def _handle_gameplay_ended(self, room_id: int, results_data: Dict):
"""处理游戏结束事件"""
logger.info(f"[SpectatorHub] Multiplayer game ended in room {room_id}")
# 发送最终结果给观战者
if room_id in self.multiplayer_subscribers:
for connection_id in self.multiplayer_subscribers[room_id]:
await self._send_to_connection(connection_id, "MultiplayerGameEnded", results_data)
async def _handle_user_state_changed(self, room_id: int, state_data: Dict):
"""处理用户状态变化"""
user_id = state_data.get('user_id')
new_state = state_data.get('new_state')
if room_id in self.multiplayer_subscribers:
for connection_id in self.multiplayer_subscribers[room_id]:
await self._send_to_connection(connection_id, "MultiplayerUserStateChanged", {
'user_id': user_id,
'state': new_state
})
async def _handle_spectator_sync(self, room_id: int, sync_data: Dict):
"""处理观战同步请求"""
target_user = sync_data.get('target_user')
snapshot = sync_data.get('snapshot')
if target_user and snapshot:
# 找到目标用户的连接并发送快照
client = self.hub.get_client_by_id(str(target_user))
if client:
await self._send_to_connection(client.connection_id, "MultiplayerSnapshot", snapshot)
async def _send_leaderboard_to_user(self, user_id: int, leaderboard: list):
"""发送排行榜给指定用户"""
client = self.hub.get_client_by_id(str(user_id))
if client:
await self._send_to_connection(client.connection_id, "MultiplayerLeaderboard", leaderboard)
async def _send_to_connection(self, connection_id: str, method: str, data):
"""发送数据到指定连接"""
try:
await self.hub.broadcast_call(connection_id, method, data)
except Exception as e:
logger.error(f"Error sending {method} to connection {connection_id}: {e}")
async def subscribe_to_multiplayer_room(self, connection_id: str, room_id: int):
"""订阅多人游戏房间的观战"""
self.multiplayer_subscribers[room_id].add(connection_id)
# 通知MultiplayerHub有新的观战者
await self.redis.publish(f"multiplayer_spectator:room:{room_id}", json.dumps({
'event_type': 'spectator_joined',
'data': {'user_id': self._get_user_id_from_connection(connection_id)},
'room_id': room_id,
'timestamp': datetime.now(UTC).isoformat()
}))
logger.info(f"[SpectatorHub] Connection {connection_id} subscribed to multiplayer room {room_id}")
async def unsubscribe_from_multiplayer_room(self, connection_id: str, room_id: int):
"""取消订阅多人游戏房间"""
if room_id in self.multiplayer_subscribers:
self.multiplayer_subscribers[room_id].discard(connection_id)
if not self.multiplayer_subscribers[room_id]:
del self.multiplayer_subscribers[room_id]
logger.info(f"[SpectatorHub] Connection {connection_id} unsubscribed from multiplayer room {room_id}")
async def request_multiplayer_leaderboard(self, connection_id: str, room_id: int):
"""请求多人游戏排行榜"""
user_id = self._get_user_id_from_connection(connection_id)
if user_id:
await self.redis.publish(f"multiplayer_spectator:room:{room_id}", json.dumps({
'event_type': 'request_leaderboard',
'data': {'user_id': user_id},
'room_id': room_id,
'timestamp': datetime.now(UTC).isoformat()
}))
def _get_user_id_from_connection(self, connection_id: str) -> Optional[int]:
"""从连接ID获取用户ID"""
# 这需要根据实际的SpectatorHub实现来调整
for client in self.hub.clients.values():
if client.connection_id == connection_id:
return client.user_id
return None
# 在SpectatorHub类中添加的方法
async def init_multiplayer_integration(self):
"""初始化多人游戏集成"""
if not hasattr(self, 'multiplayer_integration'):
self.multiplayer_integration = SpectatorMultiplayerIntegration(self)
await self.multiplayer_integration.initialize()
async def WatchMultiplayerRoom(self, client, room_id: int):
"""开始观战多人游戏房间"""
try:
if not hasattr(self, 'multiplayer_integration'):
await self.init_multiplayer_integration()
await self.multiplayer_integration.subscribe_to_multiplayer_room(
client.connection_id, room_id
)
# 请求当前状态同步
await self.multiplayer_integration.request_multiplayer_leaderboard(
client.connection_id, room_id
)
return {"success": True, "message": f"Now watching multiplayer room {room_id}"}
except Exception as e:
logger.error(f"Error starting multiplayer room watch: {e}")
raise InvokeException(f"Failed to watch multiplayer room: {e}")
async def StopWatchingMultiplayerRoom(self, client, room_id: int):
"""停止观战多人游戏房间"""
try:
if hasattr(self, 'multiplayer_integration'):
await self.multiplayer_integration.unsubscribe_from_multiplayer_room(
client.connection_id, room_id
)
return {"success": True, "message": f"Stopped watching multiplayer room {room_id}"}
except Exception as e:
logger.error(f"Error stopping multiplayer room watch: {e}")
raise InvokeException(f"Failed to stop watching multiplayer room: {e}")
async def RequestMultiplayerLeaderboard(self, client, room_id: int):
"""请求多人游戏实时排行榜"""
try:
if not hasattr(self, 'multiplayer_integration'):
await self.init_multiplayer_integration()
await self.multiplayer_integration.request_multiplayer_leaderboard(
client.connection_id, room_id
)
return {"success": True, "message": "Leaderboard request sent"}
except Exception as e:
logger.error(f"Error requesting multiplayer leaderboard: {e}")
raise InvokeException(f"Failed to request leaderboard: {e}")