shuffle players implementado correctamente
This commit is contained in:
@@ -68,6 +68,7 @@ class ColyseusService {
|
|||||||
} else {
|
} else {
|
||||||
// No client-side persistence; user must confirm name once per session
|
// No client-side persistence; user must confirm name once per session
|
||||||
}
|
}
|
||||||
|
// Resume request is now triggered by Lobby.vue after listeners are attached
|
||||||
});
|
});
|
||||||
|
|
||||||
room.onMessage("nameUpdated", (data) => {
|
room.onMessage("nameUpdated", (data) => {
|
||||||
@@ -186,6 +187,37 @@ class ColyseusService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async joinShuffledGameRoom(roomId: string, role: string, playerName: string, playerColor: string): Promise<Room> {
|
||||||
|
try {
|
||||||
|
const uuid = this.getUuidFromPath();
|
||||||
|
console.log(`[Colyseus] Joining shuffled room ${roomId} as ${role} with UUID ${uuid}`);
|
||||||
|
|
||||||
|
// Update local values
|
||||||
|
this.playerName.value = playerName;
|
||||||
|
this.playerColor.value = playerColor;
|
||||||
|
|
||||||
|
const gameRoom = await this.client.joinById(roomId, {
|
||||||
|
playerName,
|
||||||
|
playerColor,
|
||||||
|
uuid,
|
||||||
|
isShuffleJoin: true,
|
||||||
|
role
|
||||||
|
});
|
||||||
|
|
||||||
|
try { this.sessionId.value = (gameRoom as any).sessionId || this.sessionId.value; } catch {}
|
||||||
|
this.gameRoom.value = gameRoom;
|
||||||
|
this.currentRoom = gameRoom;
|
||||||
|
|
||||||
|
// Register reconnection token on server for this UUID
|
||||||
|
try { (gameRoom as any).send("registerReconnection", (gameRoom as any).reconnectionToken || ""); } catch {}
|
||||||
|
|
||||||
|
return gameRoom;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to join shuffled game room:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sendClick(): void {
|
sendClick(): void {
|
||||||
if (this.gameRoom.value) {
|
if (this.gameRoom.value) {
|
||||||
this.gameRoom.value.send("click");
|
this.gameRoom.value.send("click");
|
||||||
|
|||||||
@@ -137,19 +137,72 @@ onMounted(async () => {
|
|||||||
// Listen for server-initiated resume to existing game (fallback joinById)
|
// Listen for server-initiated resume to existing game (fallback joinById)
|
||||||
room.onMessage("resumeGame", async (data: any) => {
|
room.onMessage("resumeGame", async (data: any) => {
|
||||||
if (guardResume()) return;
|
if (guardResume()) return;
|
||||||
|
const tryJoin = async (attempt = 1): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const gameRoom = await colyseusService.joinGameRoom(data.roomId);
|
const gameRoom = await colyseusService.joinGameRoom(data.roomId);
|
||||||
// Leave lobby before navigating
|
|
||||||
if (colyseusService.lobbyRoom.value) {
|
if (colyseusService.lobbyRoom.value) {
|
||||||
colyseusService.lobbyRoom.value.leave();
|
colyseusService.lobbyRoom.value.leave();
|
||||||
colyseusService.lobbyRoom.value = null;
|
colyseusService.lobbyRoom.value = null;
|
||||||
}
|
}
|
||||||
await router.push(`/${routeUuid.value}/demo`);
|
await router.push(`/${routeUuid.value}/demo`);
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error('Auto-join failed:', error);
|
const msg = String(error?.message || error);
|
||||||
|
if (attempt < 3 && (msg.includes('locked') || msg.includes('full'))) {
|
||||||
|
setTimeout(() => tryJoin(attempt + 1), 300);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
console.error('Auto-join failed:', error);
|
||||||
|
// allow future resume if this one failed entirely
|
||||||
|
resumed = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await tryJoin(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Listen for shuffle redirect with complete player information
|
||||||
|
room.onMessage("shuffleRedirect", async (data: any) => {
|
||||||
|
if (guardResume()) return;
|
||||||
|
console.log('[Lobby] Received shuffle redirect:', data);
|
||||||
|
|
||||||
|
// Update player info before joining
|
||||||
|
if (data.playerName) {
|
||||||
|
colyseusService.playerName.value = data.playerName;
|
||||||
|
}
|
||||||
|
if (data.playerColor) {
|
||||||
|
colyseusService.playerColor.value = data.playerColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tryJoin = async (attempt = 1): Promise<void> => {
|
||||||
|
try {
|
||||||
|
// Join with shuffle flag to bypass normal restrictions
|
||||||
|
const gameRoom = await colyseusService.joinShuffledGameRoom(
|
||||||
|
data.roomId,
|
||||||
|
data.role,
|
||||||
|
data.playerName,
|
||||||
|
data.playerColor
|
||||||
|
);
|
||||||
|
|
||||||
|
if (colyseusService.lobbyRoom.value) {
|
||||||
|
colyseusService.lobbyRoom.value.leave();
|
||||||
|
colyseusService.lobbyRoom.value = null;
|
||||||
|
}
|
||||||
|
await router.push(`/${routeUuid.value}/demo`);
|
||||||
|
} catch (error: any) {
|
||||||
|
const msg = String(error?.message || error);
|
||||||
|
if (attempt < 3) {
|
||||||
|
setTimeout(() => tryJoin(attempt + 1), 500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.error('Shuffle join failed:', error);
|
||||||
|
resumed = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await tryJoin(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// After listeners are attached, ask server if we should resume (shuffle/currentRoom)
|
||||||
|
try { room.send("resumeMe"); } catch {}
|
||||||
|
|
||||||
// Keep color input synced with server-updated color
|
// Keep color input synced with server-updated color
|
||||||
watch(() => colyseusService.playerColor.value, (c) => {
|
watch(() => colyseusService.playerColor.value, (c) => {
|
||||||
if (c && c !== colorInput.value) colorInput.value = c;
|
if (c && c !== colorInput.value) colorInput.value = c;
|
||||||
|
|||||||
@@ -6,6 +6,26 @@ import { broadcastDashboardUpdate } from "../adminApi";
|
|||||||
|
|
||||||
export class GameRoom extends Room<GameState> {
|
export class GameRoom extends Room<GameState> {
|
||||||
maxClients = 2;
|
maxClients = 2;
|
||||||
|
|
||||||
|
getAvailableData() {
|
||||||
|
// If waiting for shuffled players, report as available regardless of current client count
|
||||||
|
if (this.isWaitingForShuffledPlayers) {
|
||||||
|
return {
|
||||||
|
clients: 0,
|
||||||
|
maxClients: this.maxClients,
|
||||||
|
metadata: this.metadata
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return super.getAvailableData();
|
||||||
|
}
|
||||||
|
|
||||||
|
hasReachedMaxClients() {
|
||||||
|
// If waiting for shuffled players, allow up to 2 new clients regardless of current count
|
||||||
|
if (this.isWaitingForShuffledPlayers) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return super.hasReachedMaxClients();
|
||||||
|
}
|
||||||
private gameInterval?: NodeJS.Timeout;
|
private gameInterval?: NodeJS.Timeout;
|
||||||
private recentSystemMessage: { text: string; kind: string; timestamp: number } | null = null;
|
private recentSystemMessage: { text: string; kind: string; timestamp: number } | null = null;
|
||||||
|
|
||||||
@@ -242,7 +262,7 @@ export class GameRoom extends Room<GameState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onJoin(client: Client, options: any) {
|
onJoin(client: Client, options: any) {
|
||||||
console.log(`[GameRoom] ${client.sessionId} joined room ${this.roomId} with name: ${options.playerName}`);
|
console.log(`[GameRoom] ${client.sessionId} joined room ${this.roomId} with name: ${options.playerName}, isShuffleJoin: ${options.isShuffleJoin}, isWaitingForShuffledPlayers: ${this.isWaitingForShuffledPlayers}, playersCount: ${this.state.players.size}`);
|
||||||
const uuid = options?.uuid as string | undefined;
|
const uuid = options?.uuid as string | undefined;
|
||||||
|
|
||||||
// UUID-based reconnection: if game already started or room is full, allow join if UUID matches a participant
|
// UUID-based reconnection: if game already started or room is full, allow join if UUID matches a participant
|
||||||
@@ -283,12 +303,13 @@ export class GameRoom extends Room<GameState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Special handling for shuffled players
|
// Special handling for shuffled players
|
||||||
if (this.isWaitingForShuffledPlayers && options.uuid) {
|
if ((this.isWaitingForShuffledPlayers || options.isShuffleJoin) && options.uuid) {
|
||||||
return this.handleShuffledPlayerJoin(client, options);
|
return this.handleShuffledPlayerJoin(client, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent new joins if game already started or two players are registered
|
// Prevent new joins if game already started or two players are registered
|
||||||
if (this.state.gameStatus !== GameStatus.WAITING || this.state.players.size >= 2) {
|
// BUT allow if waiting for shuffled players (they should go through shuffle handler above)
|
||||||
|
if ((this.state.gameStatus !== GameStatus.WAITING || this.state.players.size >= 2) && !this.isWaitingForShuffledPlayers) {
|
||||||
try { client.leave(1000); } catch {}
|
try { client.leave(1000); } catch {}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -685,10 +706,18 @@ export class GameRoom extends Room<GameState> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Clear all players from state completely
|
||||||
|
this.state.players.clear();
|
||||||
|
this.state.p1Id = "";
|
||||||
|
this.state.p2Id = "";
|
||||||
|
this.sessionToUuid.clear();
|
||||||
|
|
||||||
// Reset room state but keep variant
|
// Reset room state but keep variant
|
||||||
const currentVariant = this.state.currentVariant;
|
const currentVariant = this.state.currentVariant;
|
||||||
this.state.restartGame();
|
this.state.restartGame();
|
||||||
this.state.currentVariant = currentVariant;
|
this.state.currentVariant = currentVariant;
|
||||||
|
// Ensure room is accepting new joins after shuffle
|
||||||
|
try { (this as any).unlock?.(); } catch {}
|
||||||
|
|
||||||
// Prepare for new players
|
// Prepare for new players
|
||||||
this.isWaitingForShuffledPlayers = true;
|
this.isWaitingForShuffledPlayers = true;
|
||||||
@@ -704,26 +733,55 @@ export class GameRoom extends Room<GameState> {
|
|||||||
this.expectedShuffledPlayers = assignments;
|
this.expectedShuffledPlayers = assignments;
|
||||||
this.isWaitingForShuffledPlayers = true;
|
this.isWaitingForShuffledPlayers = true;
|
||||||
|
|
||||||
// Update metadata
|
// Update metadata to reflect that room is waiting for shuffled players
|
||||||
this.setMetadata({
|
this.setMetadata({
|
||||||
gameStatus: 'waiting',
|
gameStatus: 'waiting',
|
||||||
currentRound: 1,
|
currentRound: 1,
|
||||||
currentVariant: this.state.currentVariant
|
currentVariant: this.state.currentVariant,
|
||||||
|
playersCount: 0, // Force reset player count
|
||||||
|
maxClients: 2
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Make sure the room is unlocked and can accept new connections
|
||||||
|
try {
|
||||||
|
(this as any).unlock?.();
|
||||||
|
// Force Colyseus to recalculate available slots
|
||||||
|
(this as any).maxClients = 2;
|
||||||
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleShuffledPlayerJoin(client: Client, options: any) {
|
private handleShuffledPlayerJoin(client: Client, options: any) {
|
||||||
const uuid = options.uuid;
|
const uuid = options.uuid;
|
||||||
console.log(`[GameRoom] Shuffled player ${uuid} trying to join room ${this.roomId}`);
|
console.log(`[GameRoom] Shuffled player ${uuid} trying to join room ${this.roomId} with options:`, options);
|
||||||
|
console.log(`[GameRoom] Room state - isWaitingForShuffledPlayers: ${this.isWaitingForShuffledPlayers}, playersCount: ${this.state.players.size}, expectedPlayers:`, this.expectedShuffledPlayers);
|
||||||
|
|
||||||
// Check if this player is expected in this room
|
// Check if this player is expected in this room
|
||||||
let expectedRole: 'P1' | 'P2' | null = null;
|
let expectedRole: 'P1' | 'P2' | null = options.role || null;
|
||||||
|
|
||||||
|
// If role not provided, determine from expected players
|
||||||
|
if (!expectedRole) {
|
||||||
if (this.expectedShuffledPlayers.p1?.uuid === uuid) {
|
if (this.expectedShuffledPlayers.p1?.uuid === uuid) {
|
||||||
expectedRole = 'P1';
|
expectedRole = 'P1';
|
||||||
} else if (this.expectedShuffledPlayers.p2?.uuid === uuid) {
|
} else if (this.expectedShuffledPlayers.p2?.uuid === uuid) {
|
||||||
expectedRole = 'P2';
|
expectedRole = 'P2';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!expectedRole) {
|
||||||
|
// Fallback: consult NameManager assignment if not set yet in room
|
||||||
|
try {
|
||||||
|
const { NameManager } = require("../utils/nameManager");
|
||||||
|
const assign = NameManager.getInstance().getPlayerRoomAssignment(uuid);
|
||||||
|
if (assign && assign.roomId === this.roomId) {
|
||||||
|
expectedRole = assign.role;
|
||||||
|
if (assign.role === 'P1' && !this.expectedShuffledPlayers.p1) {
|
||||||
|
this.expectedShuffledPlayers.p1 = { uuid, name: options.playerName || 'player', color: options.playerColor };
|
||||||
|
} else if (assign.role === 'P2' && !this.expectedShuffledPlayers.p2) {
|
||||||
|
this.expectedShuffledPlayers.p2 = { uuid, name: options.playerName || 'player', color: options.playerColor };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
if (!expectedRole) {
|
if (!expectedRole) {
|
||||||
console.log(`[GameRoom] Player ${uuid} not expected in room ${this.roomId}, rejecting`);
|
console.log(`[GameRoom] Player ${uuid} not expected in room ${this.roomId}, rejecting`);
|
||||||
@@ -731,15 +789,19 @@ export class GameRoom extends Room<GameState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get player info from expected data
|
// Get player info from expected data or use provided options as fallback
|
||||||
const expectedPlayerData = expectedRole === 'P1' ? this.expectedShuffledPlayers.p1 : this.expectedShuffledPlayers.p2;
|
const expectedPlayerData = expectedRole === 'P1' ? this.expectedShuffledPlayers.p1 : this.expectedShuffledPlayers.p2;
|
||||||
|
const playerName = expectedPlayerData?.name || options.playerName || 'player';
|
||||||
|
const playerColor = expectedPlayerData?.color || options.playerColor || "#667eea";
|
||||||
|
|
||||||
console.log(`[GameRoom] Adding shuffled player ${uuid} as ${expectedRole} in room ${this.roomId}`);
|
console.log(`[GameRoom] Adding shuffled player ${uuid} as ${expectedRole} in room ${this.roomId}`);
|
||||||
|
|
||||||
// Add player with the expected role
|
// Add player with the expected role
|
||||||
const player = this.state.addPlayer(client.sessionId, expectedPlayerData.name);
|
const player = this.state.addPlayer(client.sessionId, playerName);
|
||||||
player.role = expectedRole;
|
player.role = expectedRole;
|
||||||
player.color = expectedPlayerData.color || "#667eea";
|
player.color = playerColor;
|
||||||
|
(player as any).uuid = uuid;
|
||||||
|
this.sessionToUuid.set(client.sessionId, uuid);
|
||||||
|
|
||||||
// Set role IDs
|
// Set role IDs
|
||||||
if (expectedRole === 'P1') {
|
if (expectedRole === 'P1') {
|
||||||
@@ -750,12 +812,16 @@ export class GameRoom extends Room<GameState> {
|
|||||||
|
|
||||||
client.send("playerInfo", {
|
client.send("playerInfo", {
|
||||||
sessionId: client.sessionId,
|
sessionId: client.sessionId,
|
||||||
name: expectedPlayerData.name,
|
name: playerName,
|
||||||
roomId: this.roomId
|
roomId: this.roomId
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove from NameManager assignment once joined
|
// Update mappings in NameManager
|
||||||
|
try {
|
||||||
|
const { NameManager } = require("../utils/nameManager");
|
||||||
|
NameManager.getInstance().setCurrentRoom(uuid, this.roomId);
|
||||||
NameManager.getInstance().removePlayerRoomAssignment(uuid);
|
NameManager.getInstance().removePlayerRoomAssignment(uuid);
|
||||||
|
} catch {}
|
||||||
|
|
||||||
// Check if room is complete
|
// Check if room is complete
|
||||||
if (this.state.players.size === 2) {
|
if (this.state.players.size === 2) {
|
||||||
|
|||||||
@@ -44,6 +44,52 @@ export class LobbyRoom extends Room<LobbyState> {
|
|||||||
this.handleJoinRoom(client, roomId);
|
this.handleJoinRoom(client, roomId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Client asks server to suggest a resume target (after listeners are ready)
|
||||||
|
this.onMessage("resumeMe", async (client) => {
|
||||||
|
const uuid = this.sessionToUuid.get(client.sessionId);
|
||||||
|
if (!uuid) return;
|
||||||
|
|
||||||
|
// If shuffle in progress and an assignment exists, prefer it
|
||||||
|
if (NameManager.getInstance().isShuffleInProgress()) {
|
||||||
|
const assignment = NameManager.getInstance().getPlayerRoomAssignment(uuid);
|
||||||
|
if (assignment) {
|
||||||
|
try {
|
||||||
|
const delay = assignment.role === 'P1' ? 150 : 750; // stagger joins to avoid full race
|
||||||
|
const playerName = NameManager.getInstance().getPlayerName(uuid) || "";
|
||||||
|
const playerColor = NameManager.getInstance().getPlayerColor(uuid) || "#667eea";
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
try {
|
||||||
|
client.send("shuffleRedirect", {
|
||||||
|
roomId: assignment.roomId,
|
||||||
|
role: assignment.role,
|
||||||
|
playerName,
|
||||||
|
playerColor,
|
||||||
|
isShuffleJoin: true
|
||||||
|
});
|
||||||
|
} catch {}
|
||||||
|
}, delay);
|
||||||
|
} catch {}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, try current room mapping
|
||||||
|
try {
|
||||||
|
const currentRoomId = NameManager.getInstance().getCurrentRoom(uuid);
|
||||||
|
if (currentRoomId) {
|
||||||
|
const rooms = await matchMaker.query({ roomId: currentRoomId });
|
||||||
|
const room = rooms[0];
|
||||||
|
const status = room?.metadata?.gameStatus || "waiting";
|
||||||
|
if (room && status !== "finished") {
|
||||||
|
const token = NameManager.getInstance().getReconnectToken(uuid);
|
||||||
|
if (token) { client.send("resumeReconnection", { token }); }
|
||||||
|
else { client.send("resumeGame", { roomId: currentRoomId }); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
});
|
||||||
|
|
||||||
this.updateInterval = setInterval(() => {
|
this.updateInterval = setInterval(() => {
|
||||||
this.updateAvailableRooms();
|
this.updateAvailableRooms();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
@@ -88,11 +134,7 @@ export class LobbyRoom extends Room<LobbyState> {
|
|||||||
name: existingName || "",
|
name: existingName || "",
|
||||||
color: this.state.players.get(client.sessionId)?.color || "#667eea"
|
color: this.state.players.get(client.sessionId)?.color || "#667eea"
|
||||||
});
|
});
|
||||||
|
// Do not push immediate redirect here; client will send "resumeMe" after handlers are ready
|
||||||
// Then immediately redirect to assigned room
|
|
||||||
setTimeout(() => {
|
|
||||||
this.handleJoinRoom(client, assignment.roomId);
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -237,7 +279,7 @@ export class LobbyRoom extends Room<LobbyState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleJoinRoom(client: Client, roomId: string) {
|
private async handleJoinRoom(client: Client, roomId: string, opts?: { force?: boolean }) {
|
||||||
const player = this.state.players.get(client.sessionId);
|
const player = this.state.players.get(client.sessionId);
|
||||||
if (!player || player.inGame) return;
|
if (!player || player.inGame) return;
|
||||||
if (!player.name || !player.name.trim()) {
|
if (!player.name || !player.name.trim()) {
|
||||||
@@ -246,13 +288,14 @@ export class LobbyRoom extends Room<LobbyState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (!opts?.force) {
|
||||||
// Verify the room exists and is available
|
// Verify the room exists and is available
|
||||||
const rooms = await matchMaker.query({ roomId });
|
const rooms = await matchMaker.query({ roomId });
|
||||||
|
|
||||||
const status = rooms[0]?.metadata?.gameStatus || "waiting";
|
const status = rooms[0]?.metadata?.gameStatus || "waiting";
|
||||||
if (rooms.length === 0 || rooms[0].clients >= 2 || status !== "waiting") {
|
if (rooms.length === 0 || rooms[0].clients >= 2 || status !== "waiting") {
|
||||||
throw new Error("Room not available");
|
throw new Error("Room not available");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.state.setPlayerInGame(client.sessionId, true);
|
this.state.setPlayerInGame(client.sessionId, true);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user