传输层实现(客户端/服务端,链接管理和心跳机制,重连机制)
消息序列化(json序列化,消息压缩,消息ID和时间戳) 网络服务器核心(networkserver/基础room/链接状态同步) 网络客户端核心(networkclient/消息队列)
This commit is contained in:
507
packages/network-server/src/rooms/Room.ts
Normal file
507
packages/network-server/src/rooms/Room.ts
Normal file
@@ -0,0 +1,507 @@
|
||||
/**
|
||||
* 房间基础实现
|
||||
* 提供房间的基本功能,包括玩家管理和房间状态管理
|
||||
*/
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { RoomState, IRoomInfo, INetworkMessage, EventEmitter } from '@esengine/network-shared';
|
||||
import { ClientSession } from '../core/ConnectionManager';
|
||||
|
||||
/**
|
||||
* 房间配置
|
||||
*/
|
||||
export interface RoomConfig {
|
||||
id: string;
|
||||
name: string;
|
||||
maxPlayers: number;
|
||||
isPublic: boolean;
|
||||
password?: string;
|
||||
metadata?: Record<string, any>;
|
||||
autoDestroy: boolean; // 是否在空房间时自动销毁
|
||||
customData?: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 玩家信息
|
||||
*/
|
||||
export interface PlayerInfo {
|
||||
sessionId: string;
|
||||
name: string;
|
||||
isHost: boolean;
|
||||
joinTime: number;
|
||||
customData?: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 房间事件接口
|
||||
*/
|
||||
export interface RoomEvents {
|
||||
playerJoined: (player: PlayerInfo) => void;
|
||||
playerLeft: (player: PlayerInfo, reason?: string) => void;
|
||||
hostChanged: (oldHost: PlayerInfo, newHost: PlayerInfo) => void;
|
||||
stateChanged: (oldState: RoomState, newState: RoomState) => void;
|
||||
messageReceived: (message: INetworkMessage, fromPlayer: PlayerInfo) => void;
|
||||
roomDestroyed: (reason: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 房间统计信息
|
||||
*/
|
||||
export interface RoomStats {
|
||||
id: string;
|
||||
playerCount: number;
|
||||
maxPlayers: number;
|
||||
createTime: number;
|
||||
totalPlayersJoined: number;
|
||||
messagesSent: number;
|
||||
messagesReceived: number;
|
||||
state: RoomState;
|
||||
}
|
||||
|
||||
/**
|
||||
* 房间类
|
||||
*/
|
||||
export class Room extends EventEmitter {
|
||||
private logger = createLogger('Room');
|
||||
private config: RoomConfig;
|
||||
private state: RoomState = RoomState.Waiting;
|
||||
private players: Map<string, PlayerInfo> = new Map();
|
||||
private hostId?: string;
|
||||
private createTime: number = Date.now();
|
||||
private stats: RoomStats;
|
||||
|
||||
// 事件处理器
|
||||
private eventHandlers: Partial<RoomEvents> = {};
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
constructor(config: RoomConfig) {
|
||||
super();
|
||||
this.config = { ...config };
|
||||
|
||||
this.stats = {
|
||||
id: config.id,
|
||||
playerCount: 0,
|
||||
maxPlayers: config.maxPlayers,
|
||||
createTime: this.createTime,
|
||||
totalPlayersJoined: 0,
|
||||
messagesSent: 0,
|
||||
messagesReceived: 0,
|
||||
state: this.state
|
||||
};
|
||||
|
||||
this.logger.info(`房间已创建: ${config.id} (${config.name})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 玩家加入房间
|
||||
*/
|
||||
addPlayer(session: ClientSession, playerName?: string, password?: string): boolean {
|
||||
// 检查房间是否已满
|
||||
if (this.players.size >= this.config.maxPlayers) {
|
||||
this.logger.warn(`房间已满,拒绝玩家加入: ${session.id}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查玩家是否已在房间中
|
||||
if (this.players.has(session.id)) {
|
||||
this.logger.warn(`玩家已在房间中: ${session.id}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查房间密码
|
||||
if (this.config.password && password !== this.config.password) {
|
||||
this.logger.warn(`密码错误,拒绝玩家加入: ${session.id}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查房间状态
|
||||
if (this.state === RoomState.Finished) {
|
||||
this.logger.warn(`房间已结束,拒绝玩家加入: ${session.id}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建玩家信息
|
||||
const player: PlayerInfo = {
|
||||
sessionId: session.id,
|
||||
name: playerName || `Player_${session.id.substr(-6)}`,
|
||||
isHost: this.players.size === 0, // 第一个加入的玩家成为房主
|
||||
joinTime: Date.now(),
|
||||
customData: {}
|
||||
};
|
||||
|
||||
// 添加玩家到房间
|
||||
this.players.set(session.id, player);
|
||||
this.stats.playerCount = this.players.size;
|
||||
this.stats.totalPlayersJoined++;
|
||||
|
||||
// 设置房主
|
||||
if (player.isHost) {
|
||||
this.hostId = session.id;
|
||||
}
|
||||
|
||||
this.logger.info(`玩家加入房间: ${player.name} (${session.id}) -> 房间 ${this.config.id}`);
|
||||
|
||||
// 触发事件
|
||||
this.eventHandlers.playerJoined?.(player);
|
||||
this.emit('playerJoined', player);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 玩家离开房间
|
||||
*/
|
||||
removePlayer(sessionId: string, reason?: string): boolean {
|
||||
const player = this.players.get(sessionId);
|
||||
if (!player) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 从房间移除玩家
|
||||
this.players.delete(sessionId);
|
||||
this.stats.playerCount = this.players.size;
|
||||
|
||||
this.logger.info(`玩家离开房间: ${player.name} (${sessionId}) <- 房间 ${this.config.id}, 原因: ${reason || '未知'}`);
|
||||
|
||||
// 如果离开的是房主,需要转移房主权限
|
||||
if (player.isHost && this.players.size > 0) {
|
||||
this.transferHost();
|
||||
}
|
||||
|
||||
// 触发事件
|
||||
this.eventHandlers.playerLeft?.(player, reason);
|
||||
this.emit('playerLeft', player, reason);
|
||||
|
||||
// 检查是否需要自动销毁房间
|
||||
if (this.config.autoDestroy && this.players.size === 0) {
|
||||
this.destroy('房间为空');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取玩家信息
|
||||
*/
|
||||
getPlayer(sessionId: string): PlayerInfo | undefined {
|
||||
return this.players.get(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有玩家
|
||||
*/
|
||||
getAllPlayers(): PlayerInfo[] {
|
||||
return Array.from(this.players.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取房主
|
||||
*/
|
||||
getHost(): PlayerInfo | undefined {
|
||||
return this.hostId ? this.players.get(this.hostId) : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转移房主权限
|
||||
*/
|
||||
transferHost(newHostId?: string): boolean {
|
||||
if (this.players.size === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const oldHost = this.getHost();
|
||||
let newHost: PlayerInfo | undefined;
|
||||
|
||||
if (newHostId) {
|
||||
newHost = this.players.get(newHostId);
|
||||
if (!newHost) {
|
||||
this.logger.warn(`指定的新房主不存在: ${newHostId}`);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// 自动选择第一个玩家作为新房主
|
||||
newHost = Array.from(this.players.values())[0];
|
||||
}
|
||||
|
||||
// 更新房主信息
|
||||
if (oldHost) {
|
||||
oldHost.isHost = false;
|
||||
}
|
||||
newHost.isHost = true;
|
||||
this.hostId = newHost.sessionId;
|
||||
|
||||
this.logger.info(`房主权限转移: ${oldHost?.name || 'unknown'} -> ${newHost.name}`);
|
||||
|
||||
// 触发事件
|
||||
if (oldHost) {
|
||||
this.eventHandlers.hostChanged?.(oldHost, newHost);
|
||||
this.emit('hostChanged', oldHost, newHost);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置房间状态
|
||||
*/
|
||||
setState(newState: RoomState): void {
|
||||
if (this.state === newState) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldState = this.state;
|
||||
this.state = newState;
|
||||
this.stats.state = newState;
|
||||
|
||||
this.logger.info(`房间状态变化: ${oldState} -> ${newState}`);
|
||||
|
||||
// 触发事件
|
||||
this.eventHandlers.stateChanged?.(oldState, newState);
|
||||
this.emit('stateChanged', oldState, newState);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理房间消息
|
||||
*/
|
||||
handleMessage(message: INetworkMessage, fromSessionId: string): void {
|
||||
const player = this.players.get(fromSessionId);
|
||||
if (!player) {
|
||||
this.logger.warn(`收到非房间成员的消息: ${fromSessionId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.stats.messagesReceived++;
|
||||
|
||||
// 触发事件
|
||||
this.eventHandlers.messageReceived?.(message, player);
|
||||
this.emit('messageReceived', message, player);
|
||||
}
|
||||
|
||||
/**
|
||||
* 广播消息到房间内所有玩家
|
||||
*/
|
||||
broadcast(message: INetworkMessage, exclude?: string[], onSend?: (sessionId: string) => void): void {
|
||||
const excludeSet = new Set(exclude || []);
|
||||
let sentCount = 0;
|
||||
|
||||
for (const player of this.players.values()) {
|
||||
if (!excludeSet.has(player.sessionId)) {
|
||||
if (onSend) {
|
||||
onSend(player.sessionId);
|
||||
sentCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.stats.messagesSent += sentCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查玩家是否在房间中
|
||||
*/
|
||||
hasPlayer(sessionId: string): boolean {
|
||||
return this.players.has(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查房间是否已满
|
||||
*/
|
||||
isFull(): boolean {
|
||||
return this.players.size >= this.config.maxPlayers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查房间是否为空
|
||||
*/
|
||||
isEmpty(): boolean {
|
||||
return this.players.size === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取房间信息
|
||||
*/
|
||||
getRoomInfo(): IRoomInfo {
|
||||
return {
|
||||
id: this.config.id,
|
||||
name: this.config.name,
|
||||
playerCount: this.players.size,
|
||||
maxPlayers: this.config.maxPlayers,
|
||||
state: this.state,
|
||||
metadata: this.config.metadata
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取房间配置
|
||||
*/
|
||||
getConfig(): RoomConfig {
|
||||
return { ...this.config };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取房间统计信息
|
||||
*/
|
||||
getStats(): RoomStats {
|
||||
return {
|
||||
...this.stats,
|
||||
playerCount: this.players.size
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新房间配置
|
||||
*/
|
||||
updateConfig(updates: Partial<RoomConfig>): void {
|
||||
Object.assign(this.config, updates);
|
||||
this.logger.info(`房间配置已更新: ${this.config.id}`, updates);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置玩家自定义数据
|
||||
*/
|
||||
setPlayerData(sessionId: string, data: Record<string, any>): boolean {
|
||||
const player = this.players.get(sessionId);
|
||||
if (!player) {
|
||||
return false;
|
||||
}
|
||||
|
||||
player.customData = { ...player.customData, ...data };
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取房间运行时间
|
||||
*/
|
||||
getUptime(): number {
|
||||
return Date.now() - this.createTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证密码
|
||||
*/
|
||||
validatePassword(password?: string): boolean {
|
||||
if (!this.config.password) {
|
||||
return true; // 无密码房间
|
||||
}
|
||||
return password === this.config.password;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置事件处理器
|
||||
*/
|
||||
override on<K extends keyof RoomEvents>(event: K, handler: RoomEvents[K]): this {
|
||||
this.eventHandlers[event] = handler;
|
||||
return super.on(event, handler as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件处理器
|
||||
*/
|
||||
override off<K extends keyof RoomEvents>(event: K): this {
|
||||
delete this.eventHandlers[event];
|
||||
return super.off(event, this.eventHandlers[event] as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁房间
|
||||
*/
|
||||
destroy(reason: string = '房间关闭'): void {
|
||||
this.logger.info(`房间销毁: ${this.config.id}, 原因: ${reason}`);
|
||||
|
||||
// 清理所有玩家
|
||||
const playersToRemove = Array.from(this.players.keys());
|
||||
for (const sessionId of playersToRemove) {
|
||||
this.removePlayer(sessionId, reason);
|
||||
}
|
||||
|
||||
// 触发销毁事件
|
||||
this.eventHandlers.roomDestroyed?.(reason);
|
||||
this.emit('roomDestroyed', reason);
|
||||
|
||||
// 清理资源
|
||||
this.players.clear();
|
||||
this.removeAllListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取房间详细状态
|
||||
*/
|
||||
getDetailedStatus() {
|
||||
return {
|
||||
config: this.getConfig(),
|
||||
info: this.getRoomInfo(),
|
||||
stats: this.getStats(),
|
||||
players: this.getAllPlayers(),
|
||||
host: this.getHost(),
|
||||
uptime: this.getUptime(),
|
||||
isEmpty: this.isEmpty(),
|
||||
isFull: this.isFull()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 踢出玩家
|
||||
*/
|
||||
kickPlayer(sessionId: string, reason: string = '被踢出房间'): boolean {
|
||||
if (!this.hasPlayer(sessionId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.removePlayer(sessionId, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停房间
|
||||
*/
|
||||
pause(): void {
|
||||
if (this.state === RoomState.Playing) {
|
||||
this.setState(RoomState.Paused);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复房间
|
||||
*/
|
||||
resume(): void {
|
||||
if (this.state === RoomState.Paused) {
|
||||
this.setState(RoomState.Playing);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始游戏
|
||||
*/
|
||||
startGame(): boolean {
|
||||
if (this.state !== RoomState.Waiting) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.players.size === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.setState(RoomState.Playing);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束游戏
|
||||
*/
|
||||
endGame(): boolean {
|
||||
if (this.state !== RoomState.Playing && this.state !== RoomState.Paused) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.setState(RoomState.Finished);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置房间到等待状态
|
||||
*/
|
||||
reset(): void {
|
||||
this.setState(RoomState.Waiting);
|
||||
// 可以根据需要重置其他状态
|
||||
}
|
||||
}
|
||||
621
packages/network-server/src/rooms/RoomManager.ts
Normal file
621
packages/network-server/src/rooms/RoomManager.ts
Normal file
@@ -0,0 +1,621 @@
|
||||
/**
|
||||
* 房间管理器
|
||||
* 负责房间的创建、销毁和管理
|
||||
*/
|
||||
import { createLogger, ITimer, Core } from '@esengine/ecs-framework';
|
||||
import { Room, RoomConfig, PlayerInfo, RoomEvents } from './Room';
|
||||
import { ClientSession } from '../core/ConnectionManager';
|
||||
import { RoomState, IRoomInfo, EventEmitter } from '@esengine/network-shared';
|
||||
|
||||
/**
|
||||
* 房间管理器配置
|
||||
*/
|
||||
export interface RoomManagerConfig {
|
||||
maxRooms: number;
|
||||
defaultMaxPlayers: number;
|
||||
autoCleanupInterval: number; // 自动清理间隔(毫秒)
|
||||
roomIdLength: number;
|
||||
allowDuplicateNames: boolean;
|
||||
defaultAutoDestroy: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 房间查询选项
|
||||
*/
|
||||
export interface RoomQueryOptions {
|
||||
state?: RoomState;
|
||||
hasPassword?: boolean;
|
||||
minPlayers?: number;
|
||||
maxPlayers?: number;
|
||||
notFull?: boolean;
|
||||
publicOnly?: boolean;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 房间创建选项
|
||||
*/
|
||||
export interface CreateRoomOptions {
|
||||
id?: string;
|
||||
name: string;
|
||||
maxPlayers?: number;
|
||||
isPublic?: boolean;
|
||||
password?: string;
|
||||
metadata?: Record<string, any>;
|
||||
autoDestroy?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 房间管理器事件接口
|
||||
*/
|
||||
export interface RoomManagerEvents {
|
||||
roomCreated: (room: Room) => void;
|
||||
roomDestroyed: (room: Room, reason: string) => void;
|
||||
playerJoinedRoom: (room: Room, player: PlayerInfo) => void;
|
||||
playerLeftRoom: (room: Room, player: PlayerInfo, reason?: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 房间管理器统计
|
||||
*/
|
||||
export interface RoomManagerStats {
|
||||
totalRooms: number;
|
||||
activeRooms: number;
|
||||
totalPlayers: number;
|
||||
roomsByState: Record<RoomState, number>;
|
||||
roomsCreated: number;
|
||||
roomsDestroyed: number;
|
||||
playersJoined: number;
|
||||
playersLeft: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 房间管理器
|
||||
*/
|
||||
export class RoomManager extends EventEmitter {
|
||||
private logger = createLogger('RoomManager');
|
||||
private config: RoomManagerConfig;
|
||||
private rooms: Map<string, Room> = new Map();
|
||||
private playerRoomMap: Map<string, string> = new Map(); // sessionId -> roomId
|
||||
private stats: RoomManagerStats;
|
||||
private cleanupTimer?: ITimer;
|
||||
|
||||
// 事件处理器
|
||||
private eventHandlers: Partial<RoomManagerEvents> = {};
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
constructor(config: Partial<RoomManagerConfig> = {}) {
|
||||
super();
|
||||
|
||||
this.config = {
|
||||
maxRooms: 1000,
|
||||
defaultMaxPlayers: 8,
|
||||
autoCleanupInterval: 300000, // 5分钟
|
||||
roomIdLength: 8,
|
||||
allowDuplicateNames: true,
|
||||
defaultAutoDestroy: true,
|
||||
...config
|
||||
};
|
||||
|
||||
this.stats = {
|
||||
totalRooms: 0,
|
||||
activeRooms: 0,
|
||||
totalPlayers: 0,
|
||||
roomsByState: {
|
||||
[RoomState.Waiting]: 0,
|
||||
[RoomState.Playing]: 0,
|
||||
[RoomState.Paused]: 0,
|
||||
[RoomState.Finished]: 0
|
||||
},
|
||||
roomsCreated: 0,
|
||||
roomsDestroyed: 0,
|
||||
playersJoined: 0,
|
||||
playersLeft: 0
|
||||
};
|
||||
|
||||
this.startAutoCleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建房间
|
||||
*/
|
||||
createRoom(creatorSession: ClientSession, options: CreateRoomOptions): Room | null {
|
||||
// 检查房间数量限制
|
||||
if (this.rooms.size >= this.config.maxRooms) {
|
||||
this.logger.warn(`房间数量已达上限: ${this.config.maxRooms}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查玩家是否已在其他房间
|
||||
if (this.playerRoomMap.has(creatorSession.id)) {
|
||||
this.logger.warn(`玩家已在其他房间中: ${creatorSession.id}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查房间名称重复
|
||||
if (!this.config.allowDuplicateNames && this.isNameExists(options.name)) {
|
||||
this.logger.warn(`房间名称已存在: ${options.name}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 生成房间ID
|
||||
const roomId = options.id || this.generateRoomId();
|
||||
if (this.rooms.has(roomId)) {
|
||||
this.logger.warn(`房间ID已存在: ${roomId}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 创建房间配置
|
||||
const roomConfig: RoomConfig = {
|
||||
id: roomId,
|
||||
name: options.name,
|
||||
maxPlayers: options.maxPlayers || this.config.defaultMaxPlayers,
|
||||
isPublic: options.isPublic !== false, // 默认为公开
|
||||
password: options.password,
|
||||
metadata: options.metadata || {},
|
||||
autoDestroy: options.autoDestroy ?? this.config.defaultAutoDestroy
|
||||
};
|
||||
|
||||
try {
|
||||
// 创建房间实例
|
||||
const room = new Room(roomConfig);
|
||||
this.setupRoomEvents(room);
|
||||
|
||||
// 添加到房间列表
|
||||
this.rooms.set(roomId, room);
|
||||
|
||||
// 创建者自动加入房间
|
||||
const success = room.addPlayer(creatorSession, `Creator_${creatorSession.id.substr(-6)}`);
|
||||
if (!success) {
|
||||
// 加入失败,销毁房间
|
||||
this.destroyRoom(roomId, '创建者加入失败');
|
||||
return null;
|
||||
}
|
||||
|
||||
// 更新玩家房间映射
|
||||
this.playerRoomMap.set(creatorSession.id, roomId);
|
||||
|
||||
// 更新统计
|
||||
this.stats.roomsCreated++;
|
||||
this.updateStats();
|
||||
|
||||
this.logger.info(`房间创建成功: ${roomId} by ${creatorSession.id}`);
|
||||
|
||||
// 触发事件
|
||||
this.eventHandlers.roomCreated?.(room);
|
||||
this.emit('roomCreated', room);
|
||||
|
||||
return room;
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error(`创建房间失败: ${roomId}`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁房间
|
||||
*/
|
||||
destroyRoom(roomId: string, reason: string = '房间关闭'): boolean {
|
||||
const room = this.rooms.get(roomId);
|
||||
if (!room) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 移除所有玩家的房间映射
|
||||
for (const player of room.getAllPlayers()) {
|
||||
this.playerRoomMap.delete(player.sessionId);
|
||||
}
|
||||
|
||||
// 销毁房间
|
||||
room.destroy(reason);
|
||||
|
||||
// 从房间列表移除
|
||||
this.rooms.delete(roomId);
|
||||
|
||||
// 更新统计
|
||||
this.stats.roomsDestroyed++;
|
||||
this.updateStats();
|
||||
|
||||
this.logger.info(`房间已销毁: ${roomId}, 原因: ${reason}`);
|
||||
|
||||
// 触发事件
|
||||
this.eventHandlers.roomDestroyed?.(room, reason);
|
||||
this.emit('roomDestroyed', room, reason);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 玩家加入房间
|
||||
*/
|
||||
joinRoom(session: ClientSession, roomId: string, password?: string, playerName?: string): boolean {
|
||||
// 检查玩家是否已在其他房间
|
||||
if (this.playerRoomMap.has(session.id)) {
|
||||
this.logger.warn(`玩家已在其他房间中: ${session.id}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取房间
|
||||
const room = this.rooms.get(roomId);
|
||||
if (!room) {
|
||||
this.logger.warn(`房间不存在: ${roomId}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 尝试加入房间
|
||||
const success = room.addPlayer(session, playerName, password);
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 更新玩家房间映射
|
||||
this.playerRoomMap.set(session.id, roomId);
|
||||
|
||||
// 更新统计
|
||||
this.stats.playersJoined++;
|
||||
this.updateStats();
|
||||
|
||||
const player = room.getPlayer(session.id)!;
|
||||
this.eventHandlers.playerJoinedRoom?.(room, player);
|
||||
this.emit('playerJoinedRoom', room, player);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 玩家离开房间
|
||||
*/
|
||||
leaveRoom(sessionId: string, reason?: string): boolean {
|
||||
const roomId = this.playerRoomMap.get(sessionId);
|
||||
if (!roomId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const room = this.rooms.get(roomId);
|
||||
if (!room) {
|
||||
this.playerRoomMap.delete(sessionId);
|
||||
return false;
|
||||
}
|
||||
|
||||
const player = room.getPlayer(sessionId);
|
||||
if (!player) {
|
||||
this.playerRoomMap.delete(sessionId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 从房间移除玩家
|
||||
const success = room.removePlayer(sessionId, reason);
|
||||
if (success) {
|
||||
// 更新玩家房间映射
|
||||
this.playerRoomMap.delete(sessionId);
|
||||
|
||||
// 更新统计
|
||||
this.stats.playersLeft++;
|
||||
this.updateStats();
|
||||
|
||||
this.eventHandlers.playerLeftRoom?.(room, player, reason);
|
||||
this.emit('playerLeftRoom', room, player, reason);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取房间
|
||||
*/
|
||||
getRoom(roomId: string): Room | undefined {
|
||||
return this.rooms.get(roomId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取玩家所在房间
|
||||
*/
|
||||
getPlayerRoom(sessionId: string): Room | undefined {
|
||||
const roomId = this.playerRoomMap.get(sessionId);
|
||||
return roomId ? this.rooms.get(roomId) : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询房间列表
|
||||
*/
|
||||
queryRooms(options: RoomQueryOptions = {}): Room[] {
|
||||
let rooms = Array.from(this.rooms.values());
|
||||
|
||||
// 应用过滤条件
|
||||
if (options.state !== undefined) {
|
||||
rooms = rooms.filter(room => room.getRoomInfo().state === options.state);
|
||||
}
|
||||
|
||||
if (options.hasPassword !== undefined) {
|
||||
rooms = rooms.filter(room => {
|
||||
const config = room.getConfig();
|
||||
return options.hasPassword ? !!config.password : !config.password;
|
||||
});
|
||||
}
|
||||
|
||||
if (options.minPlayers !== undefined) {
|
||||
rooms = rooms.filter(room => room.getAllPlayers().length >= options.minPlayers!);
|
||||
}
|
||||
|
||||
if (options.maxPlayers !== undefined) {
|
||||
rooms = rooms.filter(room => room.getAllPlayers().length <= options.maxPlayers!);
|
||||
}
|
||||
|
||||
if (options.notFull) {
|
||||
rooms = rooms.filter(room => !room.isFull());
|
||||
}
|
||||
|
||||
if (options.publicOnly) {
|
||||
rooms = rooms.filter(room => room.getConfig().isPublic);
|
||||
}
|
||||
|
||||
// 分页
|
||||
if (options.offset) {
|
||||
rooms = rooms.slice(options.offset);
|
||||
}
|
||||
|
||||
if (options.limit) {
|
||||
rooms = rooms.slice(0, options.limit);
|
||||
}
|
||||
|
||||
return rooms;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取房间信息列表
|
||||
*/
|
||||
getRoomInfoList(options: RoomQueryOptions = {}): IRoomInfo[] {
|
||||
return this.queryRooms(options).map(room => room.getRoomInfo());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
getStats(): RoomManagerStats {
|
||||
this.updateStats();
|
||||
return { ...this.stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
resetStats(): void {
|
||||
this.stats = {
|
||||
totalRooms: this.rooms.size,
|
||||
activeRooms: this.rooms.size,
|
||||
totalPlayers: this.playerRoomMap.size,
|
||||
roomsByState: {
|
||||
[RoomState.Waiting]: 0,
|
||||
[RoomState.Playing]: 0,
|
||||
[RoomState.Paused]: 0,
|
||||
[RoomState.Finished]: 0
|
||||
},
|
||||
roomsCreated: 0,
|
||||
roomsDestroyed: 0,
|
||||
playersJoined: 0,
|
||||
playersLeft: 0
|
||||
};
|
||||
|
||||
this.updateStats();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
updateConfig(newConfig: Partial<RoomManagerConfig>): void {
|
||||
Object.assign(this.config, newConfig);
|
||||
this.logger.info('房间管理器配置已更新:', newConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置事件处理器
|
||||
*/
|
||||
override on<K extends keyof RoomManagerEvents>(event: K, handler: RoomManagerEvents[K]): this {
|
||||
this.eventHandlers[event] = handler;
|
||||
return super.on(event, handler as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件处理器
|
||||
*/
|
||||
override off<K extends keyof RoomManagerEvents>(event: K): this {
|
||||
delete this.eventHandlers[event];
|
||||
return super.off(event, this.eventHandlers[event] as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁管理器
|
||||
*/
|
||||
destroy(): void {
|
||||
// 停止自动清理
|
||||
if (this.cleanupTimer) {
|
||||
this.cleanupTimer.stop();
|
||||
this.cleanupTimer = undefined;
|
||||
}
|
||||
|
||||
// 销毁所有房间
|
||||
const roomIds = Array.from(this.rooms.keys());
|
||||
for (const roomId of roomIds) {
|
||||
this.destroyRoom(roomId, '管理器销毁');
|
||||
}
|
||||
|
||||
// 清理资源
|
||||
this.rooms.clear();
|
||||
this.playerRoomMap.clear();
|
||||
this.removeAllListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成房间ID
|
||||
*/
|
||||
private generateRoomId(): string {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
let result = '';
|
||||
|
||||
for (let i = 0; i < this.config.roomIdLength; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
|
||||
// 确保ID唯一
|
||||
if (this.rooms.has(result)) {
|
||||
return this.generateRoomId();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查房间名称是否存在
|
||||
*/
|
||||
private isNameExists(name: string): boolean {
|
||||
for (const room of this.rooms.values()) {
|
||||
if (room.getConfig().name === name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置房间事件监听
|
||||
*/
|
||||
private setupRoomEvents(room: Room): void {
|
||||
room.on('roomDestroyed', (reason) => {
|
||||
// 自动清理已销毁的房间
|
||||
this.rooms.delete(room.getConfig().id);
|
||||
this.updateStats();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新统计信息
|
||||
*/
|
||||
private updateStats(): void {
|
||||
this.stats.totalRooms = this.rooms.size;
|
||||
this.stats.activeRooms = this.rooms.size;
|
||||
this.stats.totalPlayers = this.playerRoomMap.size;
|
||||
|
||||
// 重置状态统计
|
||||
this.stats.roomsByState = {
|
||||
[RoomState.Waiting]: 0,
|
||||
[RoomState.Playing]: 0,
|
||||
[RoomState.Paused]: 0,
|
||||
[RoomState.Finished]: 0
|
||||
};
|
||||
|
||||
// 统计各状态房间数量
|
||||
for (const room of this.rooms.values()) {
|
||||
const state = room.getRoomInfo().state;
|
||||
this.stats.roomsByState[state]++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动自动清理
|
||||
*/
|
||||
private startAutoCleanup(): void {
|
||||
this.cleanupTimer = Core.schedule(this.config.autoCleanupInterval / 1000, true, this, () => {
|
||||
this.performAutoCleanup();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行自动清理
|
||||
*/
|
||||
private performAutoCleanup(): void {
|
||||
const now = Date.now();
|
||||
const roomsToDestroy: string[] = [];
|
||||
|
||||
for (const [roomId, room] of this.rooms) {
|
||||
const config = room.getConfig();
|
||||
const stats = room.getStats();
|
||||
|
||||
// 清理空房间(如果启用了自动销毁)
|
||||
if (config.autoDestroy && room.isEmpty()) {
|
||||
roomsToDestroy.push(roomId);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 清理长时间无活动的已结束房间
|
||||
if (stats.state === RoomState.Finished &&
|
||||
now - stats.createTime > 3600000) { // 1小时
|
||||
roomsToDestroy.push(roomId);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 执行清理
|
||||
for (const roomId of roomsToDestroy) {
|
||||
this.destroyRoom(roomId, '自动清理');
|
||||
}
|
||||
|
||||
if (roomsToDestroy.length > 0) {
|
||||
this.logger.info(`自动清理了 ${roomsToDestroy.length} 个房间`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取管理器状态摘要
|
||||
*/
|
||||
getStatusSummary() {
|
||||
const stats = this.getStats();
|
||||
const rooms = Array.from(this.rooms.values());
|
||||
|
||||
return {
|
||||
stats,
|
||||
roomCount: rooms.length,
|
||||
playerCount: this.playerRoomMap.size,
|
||||
publicRooms: rooms.filter(r => r.getConfig().isPublic).length,
|
||||
privateRooms: rooms.filter(r => !r.getConfig().isPublic).length,
|
||||
fullRooms: rooms.filter(r => r.isFull()).length,
|
||||
emptyRooms: rooms.filter(r => r.isEmpty()).length,
|
||||
averagePlayersPerRoom: rooms.length > 0 ?
|
||||
rooms.reduce((sum, r) => sum + r.getAllPlayers().length, 0) / rooms.length : 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 踢出玩家(从其所在房间)
|
||||
*/
|
||||
kickPlayer(sessionId: string, reason: string = '被管理员踢出'): boolean {
|
||||
const room = this.getPlayerRoom(sessionId);
|
||||
if (!room) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return room.kickPlayer(sessionId, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量销毁房间
|
||||
*/
|
||||
destroyRoomsBatch(roomIds: string[], reason: string = '批量清理'): number {
|
||||
let destroyedCount = 0;
|
||||
|
||||
for (const roomId of roomIds) {
|
||||
if (this.destroyRoom(roomId, reason)) {
|
||||
destroyedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return destroyedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查玩家是否在房间中
|
||||
*/
|
||||
isPlayerInRoom(sessionId: string): boolean {
|
||||
return this.playerRoomMap.has(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取玩家所在房间ID
|
||||
*/
|
||||
getPlayerRoomId(sessionId: string): string | undefined {
|
||||
return this.playerRoomMap.get(sessionId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user