2025-12-28 12:23:55 +08:00
|
|
|
/**
|
|
|
|
|
* @zh 房间管理器
|
|
|
|
|
* @en Room manager
|
|
|
|
|
*/
|
|
|
|
|
|
2026-01-01 18:39:00 +08:00
|
|
|
import { Room, type RoomOptions } from './Room.js';
|
|
|
|
|
import type { Player } from './Player.js';
|
|
|
|
|
import { createLogger } from '../logger.js';
|
|
|
|
|
|
|
|
|
|
const logger = createLogger('Room');
|
2025-12-28 12:23:55 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @zh 房间类型
|
|
|
|
|
* @en Room class type
|
|
|
|
|
*/
|
|
|
|
|
export type RoomClass<T extends Room = Room> = new () => T
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @zh 房间定义
|
|
|
|
|
* @en Room definition
|
|
|
|
|
*/
|
|
|
|
|
interface RoomDefinition {
|
|
|
|
|
roomClass: RoomClass
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @zh 房间管理器
|
|
|
|
|
* @en Room manager
|
|
|
|
|
*/
|
|
|
|
|
export class RoomManager {
|
2026-01-01 18:39:00 +08:00
|
|
|
private _definitions: Map<string, RoomDefinition> = new Map();
|
|
|
|
|
private _rooms: Map<string, Room> = new Map();
|
|
|
|
|
private _playerToRoom: Map<string, string> = new Map();
|
|
|
|
|
private _nextRoomId = 1;
|
2025-12-28 12:23:55 +08:00
|
|
|
|
2026-01-01 18:39:00 +08:00
|
|
|
private _sendFn: (conn: any, type: string, data: unknown) => void;
|
2025-12-28 12:23:55 +08:00
|
|
|
|
|
|
|
|
constructor(sendFn: (conn: any, type: string, data: unknown) => void) {
|
2026-01-01 18:39:00 +08:00
|
|
|
this._sendFn = sendFn;
|
2025-12-28 12:23:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @zh 注册房间类型
|
|
|
|
|
* @en Define room type
|
|
|
|
|
*/
|
|
|
|
|
define<T extends Room>(name: string, roomClass: RoomClass<T>): void {
|
2026-01-01 18:39:00 +08:00
|
|
|
this._definitions.set(name, { roomClass });
|
2025-12-28 12:23:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @zh 创建房间
|
|
|
|
|
* @en Create room
|
|
|
|
|
*/
|
|
|
|
|
async create(name: string, options?: RoomOptions): Promise<Room | null> {
|
2026-01-01 18:39:00 +08:00
|
|
|
const def = this._definitions.get(name);
|
2025-12-28 12:23:55 +08:00
|
|
|
if (!def) {
|
2026-01-01 18:39:00 +08:00
|
|
|
logger.warn(`Room type not found: ${name}`);
|
|
|
|
|
return null;
|
2025-12-28 12:23:55 +08:00
|
|
|
}
|
|
|
|
|
|
2026-01-01 18:39:00 +08:00
|
|
|
const roomId = this._generateRoomId();
|
|
|
|
|
const room = new def.roomClass();
|
2025-12-28 12:23:55 +08:00
|
|
|
|
|
|
|
|
room._init({
|
|
|
|
|
id: roomId,
|
|
|
|
|
sendFn: this._sendFn,
|
|
|
|
|
broadcastFn: (type, data) => {
|
|
|
|
|
for (const player of room.players) {
|
2026-01-01 18:39:00 +08:00
|
|
|
player.send(type, data);
|
2025-12-28 12:23:55 +08:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
disposeFn: () => {
|
2026-01-01 18:39:00 +08:00
|
|
|
this._rooms.delete(roomId);
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-12-28 12:23:55 +08:00
|
|
|
|
2026-01-01 18:39:00 +08:00
|
|
|
this._rooms.set(roomId, room);
|
|
|
|
|
await room._create(options);
|
2025-12-28 12:23:55 +08:00
|
|
|
|
2026-01-01 18:39:00 +08:00
|
|
|
logger.info(`Created: ${name} (${roomId})`);
|
|
|
|
|
return room;
|
2025-12-28 12:23:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @zh 加入或创建房间
|
|
|
|
|
* @en Join or create room
|
|
|
|
|
*/
|
|
|
|
|
async joinOrCreate(
|
|
|
|
|
name: string,
|
|
|
|
|
playerId: string,
|
|
|
|
|
conn: any,
|
|
|
|
|
options?: RoomOptions
|
|
|
|
|
): Promise<{ room: Room; player: Player } | null> {
|
|
|
|
|
// 查找可加入的房间
|
2026-01-01 18:39:00 +08:00
|
|
|
let room = this._findAvailableRoom(name);
|
2025-12-28 12:23:55 +08:00
|
|
|
|
|
|
|
|
// 没有则创建
|
|
|
|
|
if (!room) {
|
2026-01-01 18:39:00 +08:00
|
|
|
room = await this.create(name, options);
|
|
|
|
|
if (!room) return null;
|
2025-12-28 12:23:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 加入房间
|
2026-01-01 18:39:00 +08:00
|
|
|
const player = await room._addPlayer(playerId, conn);
|
|
|
|
|
if (!player) return null;
|
2025-12-28 12:23:55 +08:00
|
|
|
|
2026-01-01 18:39:00 +08:00
|
|
|
this._playerToRoom.set(playerId, room.id);
|
2025-12-28 12:23:55 +08:00
|
|
|
|
2026-01-01 18:39:00 +08:00
|
|
|
logger.info(`Player ${playerId} joined ${room.id}`);
|
|
|
|
|
return { room, player };
|
2025-12-28 12:23:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @zh 加入指定房间
|
|
|
|
|
* @en Join specific room
|
|
|
|
|
*/
|
|
|
|
|
async joinById(
|
|
|
|
|
roomId: string,
|
|
|
|
|
playerId: string,
|
|
|
|
|
conn: any
|
|
|
|
|
): Promise<{ room: Room; player: Player } | null> {
|
2026-01-01 18:39:00 +08:00
|
|
|
const room = this._rooms.get(roomId);
|
|
|
|
|
if (!room) return null;
|
2025-12-28 12:23:55 +08:00
|
|
|
|
2026-01-01 18:39:00 +08:00
|
|
|
const player = await room._addPlayer(playerId, conn);
|
|
|
|
|
if (!player) return null;
|
2025-12-28 12:23:55 +08:00
|
|
|
|
2026-01-01 18:39:00 +08:00
|
|
|
this._playerToRoom.set(playerId, room.id);
|
2025-12-28 12:23:55 +08:00
|
|
|
|
2026-01-01 18:39:00 +08:00
|
|
|
logger.info(`Player ${playerId} joined ${room.id}`);
|
|
|
|
|
return { room, player };
|
2025-12-28 12:23:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @zh 玩家离开
|
|
|
|
|
* @en Player leave
|
|
|
|
|
*/
|
|
|
|
|
async leave(playerId: string, reason?: string): Promise<void> {
|
2026-01-01 18:39:00 +08:00
|
|
|
const roomId = this._playerToRoom.get(playerId);
|
|
|
|
|
if (!roomId) return;
|
2025-12-28 12:23:55 +08:00
|
|
|
|
2026-01-01 18:39:00 +08:00
|
|
|
const room = this._rooms.get(roomId);
|
2025-12-28 12:23:55 +08:00
|
|
|
if (room) {
|
2026-01-01 18:39:00 +08:00
|
|
|
await room._removePlayer(playerId, reason);
|
2025-12-28 12:23:55 +08:00
|
|
|
}
|
|
|
|
|
|
2026-01-01 18:39:00 +08:00
|
|
|
this._playerToRoom.delete(playerId);
|
|
|
|
|
logger.info(`Player ${playerId} left ${roomId}`);
|
2025-12-28 12:23:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @zh 处理消息
|
|
|
|
|
* @en Handle message
|
|
|
|
|
*/
|
|
|
|
|
handleMessage(playerId: string, type: string, data: unknown): void {
|
2026-01-01 18:39:00 +08:00
|
|
|
const roomId = this._playerToRoom.get(playerId);
|
|
|
|
|
if (!roomId) return;
|
2025-12-28 12:23:55 +08:00
|
|
|
|
2026-01-01 18:39:00 +08:00
|
|
|
const room = this._rooms.get(roomId);
|
2025-12-28 12:23:55 +08:00
|
|
|
if (room) {
|
2026-01-01 18:39:00 +08:00
|
|
|
room._handleMessage(type, data, playerId);
|
2025-12-28 12:23:55 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @zh 获取房间
|
|
|
|
|
* @en Get room
|
|
|
|
|
*/
|
|
|
|
|
getRoom(roomId: string): Room | undefined {
|
2026-01-01 18:39:00 +08:00
|
|
|
return this._rooms.get(roomId);
|
2025-12-28 12:23:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @zh 获取玩家所在房间
|
|
|
|
|
* @en Get player's room
|
|
|
|
|
*/
|
|
|
|
|
getPlayerRoom(playerId: string): Room | undefined {
|
2026-01-01 18:39:00 +08:00
|
|
|
const roomId = this._playerToRoom.get(playerId);
|
|
|
|
|
return roomId ? this._rooms.get(roomId) : undefined;
|
2025-12-28 12:23:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @zh 获取所有房间
|
|
|
|
|
* @en Get all rooms
|
|
|
|
|
*/
|
|
|
|
|
getRooms(): ReadonlyArray<Room> {
|
2026-01-01 18:39:00 +08:00
|
|
|
return Array.from(this._rooms.values());
|
2025-12-28 12:23:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @zh 获取指定类型的所有房间
|
|
|
|
|
* @en Get all rooms of a type
|
|
|
|
|
*/
|
|
|
|
|
getRoomsByType(name: string): Room[] {
|
2026-01-01 18:39:00 +08:00
|
|
|
const def = this._definitions.get(name);
|
|
|
|
|
if (!def) return [];
|
2025-12-28 12:23:55 +08:00
|
|
|
|
|
|
|
|
return Array.from(this._rooms.values()).filter(
|
2026-01-01 18:39:00 +08:00
|
|
|
(room) => room instanceof def.roomClass
|
|
|
|
|
);
|
2025-12-28 12:23:55 +08:00
|
|
|
}
|
|
|
|
|
|
2025-12-29 15:02:13 +08:00
|
|
|
private _findAvailableRoom(name: string): Room | null {
|
2026-01-01 18:39:00 +08:00
|
|
|
const def = this._definitions.get(name);
|
|
|
|
|
if (!def) return null;
|
2025-12-28 12:23:55 +08:00
|
|
|
|
|
|
|
|
for (const room of this._rooms.values()) {
|
|
|
|
|
if (
|
|
|
|
|
room instanceof def.roomClass &&
|
|
|
|
|
!room.isFull &&
|
|
|
|
|
!room.isLocked &&
|
|
|
|
|
!room.isDisposed
|
|
|
|
|
) {
|
2026-01-01 18:39:00 +08:00
|
|
|
return room;
|
2025-12-28 12:23:55 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-01 18:39:00 +08:00
|
|
|
return null;
|
2025-12-28 12:23:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _generateRoomId(): string {
|
2026-01-01 18:39:00 +08:00
|
|
|
return `room_${this._nextRoomId++}`;
|
2025-12-28 12:23:55 +08:00
|
|
|
}
|
|
|
|
|
}
|