Files
FunConnect/server/src/room.ts

188 lines
4.4 KiB
TypeScript
Raw Normal View History

import { v4 as uuidv4 } from 'uuid';
import net from 'net';
import logger from './logger';
export interface Player {
id: string;
name: string;
socket: net.Socket;
joinedAt: Date;
}
export interface RoomInfo {
id: string;
name: string;
hostId: string;
hostName: string;
hostPort: number;
gameVersion: string;
gameEdition: 'java' | 'bedrock';
maxPlayers: number;
currentPlayers: number;
nodeId: string;
createdAt: Date;
password?: string;
}
export class Room {
public id: string;
public name: string;
public hostId: string;
public hostName: string;
public hostSocket: net.Socket | null = null;
public hostPort: number;
public gameVersion: string;
public gameEdition: 'java' | 'bedrock';
public maxPlayers: number;
public password?: string;
public nodeId: string;
public createdAt: Date;
public players: Map<string, Player> = new Map();
private playerSockets: Map<string, net.Socket> = new Map();
constructor(options: {
name: string;
hostName: string;
hostPort: number;
gameVersion: string;
gameEdition: 'java' | 'bedrock';
maxPlayers: number;
nodeId: string;
password?: string;
}) {
this.id = uuidv4().substring(0, 8).toUpperCase();
this.name = options.name;
this.hostId = uuidv4();
this.hostName = options.hostName;
this.hostPort = options.hostPort;
this.gameVersion = options.gameVersion;
this.gameEdition = options.gameEdition;
this.maxPlayers = options.maxPlayers;
this.nodeId = options.nodeId;
this.password = options.password;
this.createdAt = new Date();
}
get currentPlayers(): number {
return this.players.size;
}
get isFull(): boolean {
return this.currentPlayers >= this.maxPlayers;
}
addPlayer(name: string, socket: net.Socket): Player {
const player: Player = {
id: uuidv4(),
name,
socket,
joinedAt: new Date(),
};
this.players.set(player.id, player);
this.playerSockets.set(player.id, socket);
logger.info(`Player ${name} joined room ${this.name} (${this.id})`);
return player;
}
removePlayer(playerId: string): void {
const player = this.players.get(playerId);
if (player) {
logger.info(`Player ${player.name} left room ${this.name} (${this.id})`);
this.players.delete(playerId);
this.playerSockets.delete(playerId);
}
}
toInfo(): RoomInfo {
return {
id: this.id,
name: this.name,
hostId: this.hostId,
hostName: this.hostName,
hostPort: this.hostPort,
gameVersion: this.gameVersion,
gameEdition: this.gameEdition,
maxPlayers: this.maxPlayers,
currentPlayers: this.currentPlayers,
nodeId: this.nodeId,
createdAt: this.createdAt,
password: this.password ? '***' : undefined,
};
}
destroy(): void {
for (const [, player] of this.players) {
try {
player.socket.destroy();
} catch (e) { /* ignore */ }
}
if (this.hostSocket) {
try {
this.hostSocket.destroy();
} catch (e) { /* ignore */ }
}
this.players.clear();
this.playerSockets.clear();
logger.info(`Room ${this.name} (${this.id}) destroyed`);
}
}
export class RoomManager {
private rooms: Map<string, Room> = new Map();
private maxRooms: number;
constructor(maxRooms: number) {
this.maxRooms = maxRooms;
}
createRoom(options: {
name: string;
hostName: string;
hostPort: number;
gameVersion: string;
gameEdition: 'java' | 'bedrock';
maxPlayers: number;
nodeId: string;
password?: string;
}): Room | null {
if (this.rooms.size >= this.maxRooms) {
logger.warn('Max rooms reached, cannot create new room');
return null;
}
const room = new Room(options);
this.rooms.set(room.id, room);
logger.info(`Room created: ${room.name} (${room.id}) on node ${room.nodeId}`);
return room;
}
getRoom(roomId: string): Room | undefined {
return this.rooms.get(roomId);
}
deleteRoom(roomId: string): boolean {
const room = this.rooms.get(roomId);
if (room) {
room.destroy();
this.rooms.delete(roomId);
return true;
}
return false;
}
listRooms(): RoomInfo[] {
return Array.from(this.rooms.values()).map(r => r.toInfo());
}
getRoomCount(): number {
return this.rooms.size;
}
getTotalPlayers(): number {
let total = 0;
for (const [, room] of this.rooms) {
total += room.currentPlayers;
}
return total;
}
}