- Fix room.state.players undefined error on component mount - Wait for initial state sync before accessing room data - Move message handlers from service to Game component - Fix player count display in waiting screen (was showing 0/2) - Prevent lobby component from clearing game room on unmount - Separate leaveLobby() and leaveGame() methods for proper cleanup - Ensure player names persist when moving from lobby to game - Add proper error handling for state initialization
169 lines
4.8 KiB
TypeScript
169 lines
4.8 KiB
TypeScript
import { Room, Client, matchMaker } from "colyseus";
|
|
import { LobbyState, AvailableRoom } from "./schemas/LobbyState";
|
|
import { NameManager } from "../utils/nameManager";
|
|
|
|
export class LobbyRoom extends Room<LobbyState> {
|
|
private updateInterval?: NodeJS.Timeout;
|
|
|
|
onCreate(options: any) {
|
|
this.setState(new LobbyState());
|
|
this.setPrivate(false);
|
|
|
|
this.onMessage("setName", (client, playerName: string) => {
|
|
this.handleSetName(client, playerName);
|
|
});
|
|
|
|
this.onMessage("quickPlay", (client) => {
|
|
this.handleQuickPlay(client);
|
|
});
|
|
|
|
this.onMessage("joinRoom", (client, roomId: string) => {
|
|
this.handleJoinRoom(client, roomId);
|
|
});
|
|
|
|
this.updateInterval = setInterval(() => {
|
|
this.updateAvailableRooms();
|
|
}, 2000);
|
|
}
|
|
|
|
onJoin(client: Client, options: any) {
|
|
console.log(`[LobbyRoom] ${client.sessionId} joined lobby`);
|
|
|
|
const defaultName = `guest`;
|
|
const uniqueName = NameManager.getInstance().generateUniquePlayerName(defaultName, client.sessionId);
|
|
|
|
this.state.addPlayer(client.sessionId, uniqueName);
|
|
|
|
client.send("welcome", {
|
|
sessionId: client.sessionId,
|
|
assignedName: uniqueName
|
|
});
|
|
|
|
this.updateAvailableRooms();
|
|
}
|
|
|
|
onLeave(client: Client, consented: boolean) {
|
|
console.log(`[LobbyRoom] ${client.sessionId} left lobby`);
|
|
|
|
const player = this.state.players.get(client.sessionId);
|
|
if (player) {
|
|
NameManager.getInstance().releasePlayerName(client.sessionId);
|
|
}
|
|
|
|
this.state.removePlayer(client.sessionId);
|
|
}
|
|
|
|
onDispose() {
|
|
console.log("[LobbyRoom] Disposing lobby room");
|
|
|
|
if (this.updateInterval) {
|
|
clearInterval(this.updateInterval);
|
|
}
|
|
|
|
this.state.players.forEach(player => {
|
|
NameManager.getInstance().releasePlayerName(player.sessionId);
|
|
});
|
|
}
|
|
|
|
private handleSetName(client: Client, playerName: string) {
|
|
const currentPlayer = this.state.players.get(client.sessionId);
|
|
if (!currentPlayer) return;
|
|
|
|
NameManager.getInstance().releasePlayerName(client.sessionId);
|
|
|
|
const uniqueName = NameManager.getInstance().generateUniquePlayerName(playerName, client.sessionId);
|
|
|
|
currentPlayer.name = uniqueName;
|
|
|
|
client.send("nameUpdated", {
|
|
name: uniqueName
|
|
});
|
|
}
|
|
|
|
private async handleQuickPlay(client: Client) {
|
|
const player = this.state.players.get(client.sessionId);
|
|
if (!player || player.inGame) return;
|
|
|
|
try {
|
|
// First try to find an available room
|
|
const rooms = await matchMaker.query({ name: "game", locked: false });
|
|
let targetRoomId: string;
|
|
|
|
// Find a room with less than 2 players
|
|
const availableRoom = rooms.find(room => room.clients < 2);
|
|
|
|
if (availableRoom) {
|
|
targetRoomId = availableRoom.roomId;
|
|
} else {
|
|
// If no room available, create a new one
|
|
const newRoom = await matchMaker.createRoom("game", {});
|
|
targetRoomId = newRoom.roomId;
|
|
}
|
|
|
|
this.state.setPlayerInGame(client.sessionId, true);
|
|
|
|
// Send the roomId to the client
|
|
client.send("gameJoined", {
|
|
roomId: targetRoomId
|
|
});
|
|
|
|
// Don't auto-leave, let the client handle it
|
|
// The client will leave the lobby after successfully joining the game room
|
|
|
|
} catch (error) {
|
|
console.error("[LobbyRoom] Error in quick play:", error);
|
|
client.send("error", {
|
|
message: "Could not find or create a game room"
|
|
});
|
|
}
|
|
}
|
|
|
|
private async handleJoinRoom(client: Client, roomId: string) {
|
|
const player = this.state.players.get(client.sessionId);
|
|
if (!player || player.inGame) return;
|
|
|
|
try {
|
|
// Verify the room exists and is available
|
|
const rooms = await matchMaker.query({ roomId });
|
|
|
|
if (rooms.length === 0 || rooms[0].clients >= 2) {
|
|
throw new Error("Room not available");
|
|
}
|
|
|
|
this.state.setPlayerInGame(client.sessionId, true);
|
|
|
|
// Send the roomId to the client
|
|
client.send("gameJoined", {
|
|
roomId: roomId
|
|
});
|
|
|
|
// Don't auto-leave, let the client handle it
|
|
// The client will leave the lobby after successfully joining the game room
|
|
|
|
} catch (error) {
|
|
console.error("[LobbyRoom] Error joining room:", error);
|
|
client.send("error", {
|
|
message: "Could not join the selected room"
|
|
});
|
|
}
|
|
}
|
|
|
|
private async updateAvailableRooms() {
|
|
try {
|
|
const rooms = await matchMaker.query({ name: "game" });
|
|
|
|
const availableRooms = rooms
|
|
.filter(room => !room.locked && room.clients < 2)
|
|
.map(room => new AvailableRoom(
|
|
room.roomId,
|
|
room.clients,
|
|
room.metadata?.gameStatus || "waiting"
|
|
));
|
|
|
|
this.state.updateAvailableRooms(availableRooms);
|
|
|
|
} catch (error) {
|
|
console.error("[LobbyRoom] Error updating available rooms:", error);
|
|
}
|
|
}
|
|
} |