188 lines
4.4 KiB
TypeScript
188 lines
4.4 KiB
TypeScript
|
|
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;
|
||
|
|
}
|
||
|
|
}
|