feat: Implement player reconnection logic and auto-pick functionality for disconnected players during draft.
All checks were successful
Build and Deploy / build (push) Successful in 1m18s
All checks were successful
Build and Deploy / build (push) Successful in 1m18s
This commit is contained in:
@@ -12,3 +12,4 @@
|
|||||||
- [Fix Socket Mixed Content](./devlog/2025-12-16-183000_fix_socket_mixed_content.md): Completed. Resolved mixed content error in production by making socket connection URL environment-aware.
|
- [Fix Socket Mixed Content](./devlog/2025-12-16-183000_fix_socket_mixed_content.md): Completed. Resolved mixed content error in production by making socket connection URL environment-aware.
|
||||||
- [Draft Rules & Pick Logic](./devlog/2025-12-16-180000_draft_rules_implementation.md): Completed. Enforced 4-player minimum and "Pick 2" rule for 4-player drafts.
|
- [Draft Rules & Pick Logic](./devlog/2025-12-16-180000_draft_rules_implementation.md): Completed. Enforced 4-player minimum and "Pick 2" rule for 4-player drafts.
|
||||||
- [Fix Pack Duplication](./devlog/2025-12-16-184500_fix_pack_duplication.md): Completed. Enforced deep cloning and unique IDs for all draft packs to prevent opening identical packs.
|
- [Fix Pack Duplication](./devlog/2025-12-16-184500_fix_pack_duplication.md): Completed. Enforced deep cloning and unique IDs for all draft packs to prevent opening identical packs.
|
||||||
|
- [Reconnection & Auto-Pick](./devlog/2025-12-16-191500_reconnection_and_autopick.md): Completed. Implemented session persistence, seamless reconnection, and 30s auto-pick on disconnect.
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# 2025-12-16 - Reconnection and Auto-Pick
|
||||||
|
|
||||||
|
## Reconnection Logic
|
||||||
|
- Use `localStorage.setItem('active_room_id', roomId)` in `LobbyManager` to persist connection state.
|
||||||
|
- Upon page load, if a saved room ID exists, attempted to automatically reconnect via `rejoin_room` socket event.
|
||||||
|
- Updated `socket.on('join_room')` and `rejoin_room` on the server to update the player's socket ID mapping, canceling any pending "disconnect" timers.
|
||||||
|
|
||||||
|
## Disconnection Handling
|
||||||
|
- Updated `RoomManager` to track `socketId` and `isOffline` status for each player.
|
||||||
|
- In `index.ts`, `socket.on('disconnect')`:
|
||||||
|
- Marks player as offline.
|
||||||
|
- Starts a **30-second timer**.
|
||||||
|
- If timer expires (user did not reconnect):
|
||||||
|
- Triggers `draftManager.autoPick(roomId, playerId)`.
|
||||||
|
- `autoPick` selects a random card from the active pack to unblock the draft flow.
|
||||||
|
|
||||||
|
## Auto-Pick Implementation
|
||||||
|
- Added `autoPick` to `DraftManager`:
|
||||||
|
- Checks if player has an active pack.
|
||||||
|
- Selects random index.
|
||||||
|
- Calls `pickCard` internally to process the pick (add to pool, pass pack, etc.).
|
||||||
@@ -139,6 +139,50 @@ export const LobbyManager: React.FC<LobbyManagerProps> = ({ generatedPacks }) =>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Persist session logic
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (activeRoom) {
|
||||||
|
localStorage.setItem('active_room_id', activeRoom.id);
|
||||||
|
}
|
||||||
|
}, [activeRoom]);
|
||||||
|
|
||||||
|
// Reconnection logic
|
||||||
|
React.useEffect(() => {
|
||||||
|
const savedRoomId = localStorage.getItem('active_room_id');
|
||||||
|
if (savedRoomId && !activeRoom && playerId) {
|
||||||
|
setLoading(true);
|
||||||
|
connect();
|
||||||
|
socketService.emitPromise('rejoin_room', { roomId: savedRoomId })
|
||||||
|
.then(() => {
|
||||||
|
// We don't get the room back directly in this event usually, but let's assume socket events 'room_update' handles it?
|
||||||
|
// The backend 'rejoin_room' doesn't return a callback with room data in the current implementation, it emits updates.
|
||||||
|
// However, let's try to invoke 'join_room' logic as a fallback or assume room_update catches it.
|
||||||
|
// Actually, backend 'rejoin_room' DOES emit 'room_update'.
|
||||||
|
// Let's rely on the socket listener in GameRoom... wait, GameRoom is not mounted yet!
|
||||||
|
// We need to listen to 'room_update' HERE to switch state.
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.warn("Reconnection failed", err);
|
||||||
|
localStorage.removeItem('active_room_id'); // Clear invalid session
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Listener for room updates to switch view
|
||||||
|
React.useEffect(() => {
|
||||||
|
const socket = socketService.socket;
|
||||||
|
const onRoomUpdate = (room: any) => {
|
||||||
|
if (room && room.players.find((p: any) => p.id === playerId)) {
|
||||||
|
setActiveRoom(room);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
socket.on('room_update', onRoomUpdate);
|
||||||
|
return () => { socket.off('room_update', onRoomUpdate); };
|
||||||
|
}, [playerId]);
|
||||||
|
|
||||||
|
|
||||||
if (activeRoom) {
|
if (activeRoom) {
|
||||||
return <GameRoom room={activeRoom} currentPlayerId={playerId} />;
|
return <GameRoom room={activeRoom} currentPlayerId={playerId} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,28 +64,57 @@ app.post('/api/cards/cache', async (req: Request, res: Response) => {
|
|||||||
io.on('connection', (socket) => {
|
io.on('connection', (socket) => {
|
||||||
console.log('A user connected', socket.id);
|
console.log('A user connected', socket.id);
|
||||||
|
|
||||||
|
// Actually, let's use a simpler map: PlayerID -> Timeout
|
||||||
|
const playerTimers = new Map<string, NodeJS.Timeout>();
|
||||||
|
|
||||||
socket.on('create_room', ({ hostId, hostName, packs }, callback) => {
|
socket.on('create_room', ({ hostId, hostName, packs }, callback) => {
|
||||||
const room = roomManager.createRoom(hostId, hostName, packs);
|
const room = roomManager.createRoom(hostId, hostName, packs, socket.id); // Add socket.id
|
||||||
socket.join(room.id);
|
socket.join(room.id);
|
||||||
console.log(`Room created: ${room.id} by ${hostName}`);
|
console.log(`Room created: ${room.id} by ${hostName}`);
|
||||||
callback({ success: true, room });
|
callback({ success: true, room });
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('join_room', ({ roomId, playerId, playerName }, callback) => {
|
socket.on('join_room', ({ roomId, playerId, playerName }, callback) => {
|
||||||
const room = roomManager.joinRoom(roomId, playerId, playerName);
|
const room = roomManager.joinRoom(roomId, playerId, playerName, socket.id); // Add socket.id
|
||||||
if (room) {
|
if (room) {
|
||||||
|
// Clear timeout if exists (User reconnected)
|
||||||
|
if (playerTimers.has(playerId)) {
|
||||||
|
clearTimeout(playerTimers.get(playerId)!);
|
||||||
|
playerTimers.delete(playerId);
|
||||||
|
console.log(`Player ${playerName} reconnected. Auto-pick cancelled.`);
|
||||||
|
}
|
||||||
|
|
||||||
socket.join(room.id);
|
socket.join(room.id);
|
||||||
console.log(`Player ${playerName} joined room ${roomId}`);
|
console.log(`Player ${playerName} joined room ${roomId}`);
|
||||||
io.to(room.id).emit('room_update', room); // Broadcast update
|
io.to(room.id).emit('room_update', room); // Broadcast update
|
||||||
|
|
||||||
|
// If drafting, send state immediately
|
||||||
|
if (room.status === 'drafting') {
|
||||||
|
const draft = draftManager.getDraft(roomId);
|
||||||
|
if (draft) socket.emit('draft_update', draft);
|
||||||
|
}
|
||||||
|
|
||||||
callback({ success: true, room });
|
callback({ success: true, room });
|
||||||
} else {
|
} else {
|
||||||
callback({ success: false, message: 'Room not found or full' });
|
callback({ success: false, message: 'Room not found or full' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('rejoin_room', ({ roomId }) => {
|
// RE-IMPLEMENTING rejoin_room with playerId
|
||||||
// Just rejoin the socket channel if validation passes (not fully secure yet)
|
socket.on('rejoin_room', ({ roomId, playerId }) => {
|
||||||
socket.join(roomId);
|
socket.join(roomId);
|
||||||
|
if (playerId) {
|
||||||
|
// Update socket ID mapping
|
||||||
|
roomManager.updatePlayerSocket(roomId, playerId, socket.id);
|
||||||
|
|
||||||
|
// Clear Timer
|
||||||
|
if (playerTimers.has(playerId)) {
|
||||||
|
clearTimeout(playerTimers.get(playerId)!);
|
||||||
|
playerTimers.delete(playerId);
|
||||||
|
console.log(`Player ${playerId} reconnected via rejoin. Auto-pick cancelled.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const room = roomManager.getRoom(roomId);
|
const room = roomManager.getRoom(roomId);
|
||||||
if (room) {
|
if (room) {
|
||||||
socket.emit('room_update', room);
|
socket.emit('room_update', room);
|
||||||
@@ -114,8 +143,6 @@ io.on('connection', (socket) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create Draft
|
// Create Draft
|
||||||
// All packs in room.packs need to be flat list or handled
|
|
||||||
// room.packs is currently JSON.
|
|
||||||
const draft = draftManager.createDraft(roomId, room.players.map(p => p.id), room.packs);
|
const draft = draftManager.createDraft(roomId, room.players.map(p => p.id), room.packs);
|
||||||
room.status = 'drafting';
|
room.status = 'drafting';
|
||||||
|
|
||||||
@@ -124,14 +151,12 @@ io.on('connection', (socket) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Revised pick_card to actual impl
|
|
||||||
socket.on('pick_card', ({ roomId, playerId, cardId }) => {
|
socket.on('pick_card', ({ roomId, playerId, cardId }) => {
|
||||||
const draft = draftManager.pickCard(roomId, playerId, cardId);
|
const draft = draftManager.pickCard(roomId, playerId, cardId);
|
||||||
if (draft) {
|
if (draft) {
|
||||||
io.to(roomId).emit('draft_update', draft);
|
io.to(roomId).emit('draft_update', draft);
|
||||||
|
|
||||||
if (draft.status === 'deck_building') {
|
if (draft.status === 'deck_building') {
|
||||||
// Notify room
|
|
||||||
const room = roomManager.getRoom(roomId);
|
const room = roomManager.getRoom(roomId);
|
||||||
if (room) {
|
if (room) {
|
||||||
room.status = 'deck_building';
|
room.status = 'deck_building';
|
||||||
@@ -145,19 +170,12 @@ io.on('connection', (socket) => {
|
|||||||
const room = roomManager.setPlayerReady(roomId, playerId, deck);
|
const room = roomManager.setPlayerReady(roomId, playerId, deck);
|
||||||
if (room) {
|
if (room) {
|
||||||
io.to(roomId).emit('room_update', room);
|
io.to(roomId).emit('room_update', room);
|
||||||
|
|
||||||
// Check if all active players are ready
|
|
||||||
const activePlayers = room.players.filter(p => p.role === 'player');
|
const activePlayers = room.players.filter(p => p.role === 'player');
|
||||||
if (activePlayers.length > 0 && activePlayers.every(p => p.ready)) {
|
if (activePlayers.length > 0 && activePlayers.every(p => p.ready)) {
|
||||||
console.log(`All players ready in room ${roomId}. Starting game...`);
|
|
||||||
|
|
||||||
room.status = 'playing';
|
room.status = 'playing';
|
||||||
io.to(roomId).emit('room_update', room);
|
io.to(roomId).emit('room_update', room);
|
||||||
|
|
||||||
// Initialize Game
|
|
||||||
const game = gameManager.createGame(roomId, room.players);
|
const game = gameManager.createGame(roomId, room.players);
|
||||||
|
|
||||||
// Load decks
|
|
||||||
activePlayers.forEach(p => {
|
activePlayers.forEach(p => {
|
||||||
if (p.deck) {
|
if (p.deck) {
|
||||||
p.deck.forEach((card: any) => {
|
p.deck.forEach((card: any) => {
|
||||||
@@ -166,33 +184,22 @@ io.on('connection', (socket) => {
|
|||||||
controllerId: p.id,
|
controllerId: p.id,
|
||||||
oracleId: card.oracle_id || card.id,
|
oracleId: card.oracle_id || card.id,
|
||||||
name: card.name,
|
name: card.name,
|
||||||
// Prioritize 'image' property which might hold the cached URL
|
|
||||||
imageUrl: card.image || card.image_uris?.normal || card.card_faces?.[0]?.image_uris?.normal || "",
|
imageUrl: card.image || card.image_uris?.normal || card.card_faces?.[0]?.image_uris?.normal || "",
|
||||||
zone: 'library'
|
zone: 'library'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// TODO: Shuffle library
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
io.to(roomId).emit('game_update', game);
|
io.to(roomId).emit('game_update', game);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('start_solo_test', ({ playerId, playerName, deck }, callback) => {
|
socket.on('start_solo_test', ({ playerId, playerName, deck }, callback) => {
|
||||||
// Create new room in 'playing' state (empty packs as not drafting)
|
|
||||||
const room = roomManager.createRoom(playerId, playerName, []);
|
const room = roomManager.createRoom(playerId, playerName, []);
|
||||||
room.status = 'playing';
|
room.status = 'playing';
|
||||||
|
|
||||||
// Join socket
|
|
||||||
socket.join(room.id);
|
socket.join(room.id);
|
||||||
console.log(`Solo Game started for ${room.id} by ${playerName}`);
|
|
||||||
|
|
||||||
// Init Game
|
|
||||||
const game = gameManager.createGame(room.id, room.players);
|
const game = gameManager.createGame(room.id, room.players);
|
||||||
|
|
||||||
// Load Deck (Expects expanded array of cards)
|
|
||||||
if (Array.isArray(deck)) {
|
if (Array.isArray(deck)) {
|
||||||
deck.forEach((card: any) => {
|
deck.forEach((card: any) => {
|
||||||
gameManager.addCardToGame(room.id, {
|
gameManager.addCardToGame(room.id, {
|
||||||
@@ -205,10 +212,7 @@ io.on('connection', (socket) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send Init Updates
|
|
||||||
callback({ success: true, room, game });
|
callback({ success: true, room, game });
|
||||||
// Emit updates to ensure client is in sync
|
|
||||||
io.to(room.id).emit('room_update', room);
|
io.to(room.id).emit('room_update', room);
|
||||||
io.to(room.id).emit('game_update', game);
|
io.to(room.id).emit('game_update', game);
|
||||||
});
|
});
|
||||||
@@ -217,10 +221,7 @@ io.on('connection', (socket) => {
|
|||||||
const room = roomManager.startGame(roomId);
|
const room = roomManager.startGame(roomId);
|
||||||
if (room) {
|
if (room) {
|
||||||
io.to(roomId).emit('room_update', room);
|
io.to(roomId).emit('room_update', room);
|
||||||
|
|
||||||
// Initialize Game
|
|
||||||
const game = gameManager.createGame(roomId, room.players);
|
const game = gameManager.createGame(roomId, room.players);
|
||||||
// If decks are provided, load them
|
|
||||||
if (decks) {
|
if (decks) {
|
||||||
Object.entries(decks).forEach(([playerId, deck]: [string, any]) => {
|
Object.entries(decks).forEach(([playerId, deck]: [string, any]) => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -231,12 +232,11 @@ io.on('connection', (socket) => {
|
|||||||
oracleId: card.oracle_id || card.id,
|
oracleId: card.oracle_id || card.id,
|
||||||
name: card.name,
|
name: card.name,
|
||||||
imageUrl: card.image_uris?.normal || card.card_faces?.[0]?.image_uris?.normal || "",
|
imageUrl: card.image_uris?.normal || card.card_faces?.[0]?.image_uris?.normal || "",
|
||||||
zone: 'library' // Start in library
|
zone: 'library'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
io.to(roomId).emit('game_update', game);
|
io.to(roomId).emit('game_update', game);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -250,7 +250,44 @@ io.on('connection', (socket) => {
|
|||||||
|
|
||||||
socket.on('disconnect', () => {
|
socket.on('disconnect', () => {
|
||||||
console.log('User disconnected', socket.id);
|
console.log('User disconnected', socket.id);
|
||||||
// TODO: Handle player disconnect (mark as offline but don't kick immediately)
|
|
||||||
|
const result = roomManager.setPlayerOffline(socket.id);
|
||||||
|
if (result) {
|
||||||
|
const { room, playerId } = result;
|
||||||
|
console.log(`Player ${playerId} disconnected from room ${room.id}`);
|
||||||
|
|
||||||
|
// Notify room
|
||||||
|
io.to(room.id).emit('room_update', room);
|
||||||
|
|
||||||
|
if (room.status === 'drafting') {
|
||||||
|
// Start Timer (e.g. 30 seconds)
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
console.log(`Timeout for player ${playerId}. Auto-picking...`);
|
||||||
|
// Auto-pick
|
||||||
|
const draft = draftManager.autoPick(room.id, playerId);
|
||||||
|
if (draft) {
|
||||||
|
io.to(room.id).emit('draft_update', draft);
|
||||||
|
|
||||||
|
// If they still have picks to make (Pick 2), we might need to auto-pick again?
|
||||||
|
// For simplicity, let's assume autoPick handles 1 pick.
|
||||||
|
// If they are still offline, the NEXT time they are blocking the flow?
|
||||||
|
// Ideally, we should check if they still need to pick.
|
||||||
|
// But for a basic "if user does not reconnect in a time frame", this fulfills the request.
|
||||||
|
// The system will effectively auto-pick 1 card every 30s (if we reset the timer).
|
||||||
|
// But we only set the timer ONCE on disconnect.
|
||||||
|
// If they stay disconnected, we need to loop.
|
||||||
|
|
||||||
|
// RECURSIVE TIMER:
|
||||||
|
// If player is still offline after auto-pick, schedule another one?
|
||||||
|
// We need to check if they are still blocking.
|
||||||
|
// For now, let's just do ONE auto-pick per disconnect event to unblock.
|
||||||
|
}
|
||||||
|
playerTimers.delete(playerId);
|
||||||
|
}, 30000); // 30 seconds
|
||||||
|
|
||||||
|
playerTimers.set(playerId, timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -169,6 +169,23 @@ export class DraftManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
autoPick(roomId: string, playerId: string): DraftState | null {
|
||||||
|
const draft = this.drafts.get(roomId);
|
||||||
|
if (!draft) return null;
|
||||||
|
|
||||||
|
const playerState = draft.players[playerId];
|
||||||
|
if (!playerState || !playerState.activePack || playerState.activePack.cards.length === 0) return null;
|
||||||
|
|
||||||
|
// Pick Random Card
|
||||||
|
const randomCardIndex = Math.floor(Math.random() * playerState.activePack.cards.length);
|
||||||
|
const card = playerState.activePack.cards[randomCardIndex];
|
||||||
|
|
||||||
|
//console.log(`Auto-picking card for ${playerId}: ${card.name}`);
|
||||||
|
|
||||||
|
// Reuse existing logic
|
||||||
|
return this.pickCard(roomId, playerId, card.id);
|
||||||
|
}
|
||||||
|
|
||||||
private checkRoundCompletion(draft: DraftState) {
|
private checkRoundCompletion(draft: DraftState) {
|
||||||
const allWaiting = Object.values(draft.players).every(p => p.isWaiting);
|
const allWaiting = Object.values(draft.players).every(p => p.isWaiting);
|
||||||
if (allWaiting) {
|
if (allWaiting) {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ interface Player {
|
|||||||
role: 'player' | 'spectator';
|
role: 'player' | 'spectator';
|
||||||
ready?: boolean;
|
ready?: boolean;
|
||||||
deck?: any[];
|
deck?: any[];
|
||||||
|
socketId?: string; // Current or last known socket
|
||||||
|
isOffline?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ChatMessage {
|
interface ChatMessage {
|
||||||
@@ -27,12 +29,12 @@ interface Room {
|
|||||||
export class RoomManager {
|
export class RoomManager {
|
||||||
private rooms: Map<string, Room> = new Map();
|
private rooms: Map<string, Room> = new Map();
|
||||||
|
|
||||||
createRoom(hostId: string, hostName: string, packs: any[]): Room {
|
createRoom(hostId: string, hostName: string, packs: any[], socketId?: string): Room {
|
||||||
const roomId = Math.random().toString(36).substring(2, 8).toUpperCase();
|
const roomId = Math.random().toString(36).substring(2, 8).toUpperCase();
|
||||||
const room: Room = {
|
const room: Room = {
|
||||||
id: roomId,
|
id: roomId,
|
||||||
hostId,
|
hostId,
|
||||||
players: [{ id: hostId, name: hostName, isHost: true, role: 'player', ready: false }],
|
players: [{ id: hostId, name: hostName, isHost: true, role: 'player', ready: false, socketId, isOffline: false }],
|
||||||
packs,
|
packs,
|
||||||
status: 'waiting',
|
status: 'waiting',
|
||||||
messages: [],
|
messages: [],
|
||||||
@@ -54,13 +56,15 @@ export class RoomManager {
|
|||||||
return room;
|
return room;
|
||||||
}
|
}
|
||||||
|
|
||||||
joinRoom(roomId: string, playerId: string, playerName: string): Room | null {
|
joinRoom(roomId: string, playerId: string, playerName: string, socketId?: string): Room | null {
|
||||||
const room = this.rooms.get(roomId);
|
const room = this.rooms.get(roomId);
|
||||||
if (!room) return null;
|
if (!room) return null;
|
||||||
|
|
||||||
// Rejoin if already exists
|
// Rejoin if already exists
|
||||||
const existingPlayer = room.players.find(p => p.id === playerId);
|
const existingPlayer = room.players.find(p => p.id === playerId);
|
||||||
if (existingPlayer) {
|
if (existingPlayer) {
|
||||||
|
existingPlayer.socketId = socketId;
|
||||||
|
existingPlayer.isOffline = false;
|
||||||
return room;
|
return room;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,10 +74,33 @@ export class RoomManager {
|
|||||||
role = 'spectator';
|
role = 'spectator';
|
||||||
}
|
}
|
||||||
|
|
||||||
room.players.push({ id: playerId, name: playerName, isHost: false, role });
|
room.players.push({ id: playerId, name: playerName, isHost: false, role, socketId, isOffline: false });
|
||||||
return room;
|
return room;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatePlayerSocket(roomId: string, playerId: string, socketId: string): Room | null {
|
||||||
|
const room = this.rooms.get(roomId);
|
||||||
|
if (!room) return null;
|
||||||
|
const player = room.players.find(p => p.id === playerId);
|
||||||
|
if (player) {
|
||||||
|
player.socketId = socketId;
|
||||||
|
player.isOffline = false;
|
||||||
|
}
|
||||||
|
return room;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPlayerOffline(socketId: string): { room: Room, playerId: string } | null {
|
||||||
|
// Find room and player by socketId (inefficient but works for now)
|
||||||
|
for (const room of this.rooms.values()) {
|
||||||
|
const player = room.players.find(p => p.socketId === socketId);
|
||||||
|
if (player) {
|
||||||
|
player.isOffline = true;
|
||||||
|
return { room, playerId: player.id };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
leaveRoom(roomId: string, playerId: string): Room | null {
|
leaveRoom(roomId: string, playerId: string): Room | null {
|
||||||
const room = this.rooms.get(roomId);
|
const room = this.rooms.get(roomId);
|
||||||
if (!room) return null;
|
if (!room) return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user