feat: Implement server-side draft timer with AFK auto-pick and global draft loop, updating client-side timer to reflect server state.

This commit is contained in:
2025-12-16 22:10:20 +01:00
parent 33a5fcd501
commit a1cba11d68
5 changed files with 161 additions and 71 deletions

View File

@@ -60,58 +60,59 @@ app.post('/api/cards/cache', async (req: Request, res: Response) => {
}
});
// Global Draft Timer Loop
setInterval(() => {
const updates = draftManager.checkTimers();
updates.forEach(({ roomId, draft }) => {
io.to(roomId).emit('draft_update', draft);
// Check for forced game start (Deck Building Timeout)
if (draft.status === 'complete') {
const room = roomManager.getRoom(roomId);
// Only trigger if room exists and not already playing
if (room && room.status !== 'playing') {
console.log(`Deck building timeout for Room ${roomId}. Forcing start.`);
// Force ready for unready players
const activePlayers = room.players.filter(p => p.role === 'player');
activePlayers.forEach(p => {
if (!p.ready) {
const pool = draft.players[p.id]?.pool || [];
roomManager.setPlayerReady(roomId, p.id, pool);
}
});
// Start Game Logic
room.status = 'playing';
io.to(roomId).emit('room_update', room);
const game = gameManager.createGame(roomId, room.players);
activePlayers.forEach(p => {
if (p.deck) {
p.deck.forEach((card: any) => {
gameManager.addCardToGame(roomId, {
ownerId: p.id,
controllerId: p.id,
oracleId: card.oracle_id || card.id,
name: card.name,
imageUrl: card.image || card.image_uris?.normal || card.card_faces?.[0]?.image_uris?.normal || "",
zone: 'library'
});
});
}
});
io.to(roomId).emit('game_update', game);
}
}
});
}, 1000);
// Socket.IO logic
io.on('connection', (socket) => {
console.log('A user connected', socket.id);
// Timer management
const playerTimers = new Map<string, NodeJS.Timeout>();
const startAutoPickTimer = (roomId: string, playerId: string) => {
// Clear existing if any (debounce)
if (playerTimers.has(playerId)) {
clearTimeout(playerTimers.get(playerId)!);
}
const timer = setTimeout(() => {
console.log(`Timeout for player ${playerId}. Auto-picking...`);
const draft = draftManager.autoPick(roomId, playerId);
if (draft) {
io.to(roomId).emit('draft_update', draft);
// We only pick once. If they stay offline, the next pick depends on the next turn cycle.
// If we wanted continuous auto-pick, we'd need to check if it's still their turn and recurse.
// For now, this unblocks the current step.
}
playerTimers.delete(playerId);
}, 30000); // 30s
playerTimers.set(playerId, timer);
};
const stopAutoPickTimer = (playerId: string) => {
if (playerTimers.has(playerId)) {
clearTimeout(playerTimers.get(playerId)!);
playerTimers.delete(playerId);
}
};
const stopAllRoomTimers = (roomId: string) => {
const room = roomManager.getRoom(roomId);
if (room) {
room.players.forEach(p => stopAutoPickTimer(p.id));
}
};
const resumeRoomTimers = (roomId: string) => {
const room = roomManager.getRoom(roomId);
if (room && room.status === 'drafting') {
room.players.forEach(p => {
if (p.isOffline && p.role === 'player') {
startAutoPickTimer(roomId, p.id);
}
});
}
};
// Timer management removed (Global loop handled)
socket.on('create_room', ({ hostId, hostName, packs }, callback) => {
const room = roomManager.createRoom(hostId, hostName, packs, socket.id); // Add socket.id
@@ -124,8 +125,8 @@ io.on('connection', (socket) => {
const room = roomManager.joinRoom(roomId, playerId, playerName, socket.id); // Add socket.id
if (room) {
// Clear timeout if exists (User reconnected)
stopAutoPickTimer(playerId);
console.log(`Player ${playerName} reconnected. Auto-pick cancelled.`);
// stopAutoPickTimer(playerId); // Global timer handles this now
console.log(`Player ${playerName} reconnected.`);
socket.join(room.id);
console.log(`Player ${playerName} joined room ${roomId}`);
@@ -134,7 +135,7 @@ io.on('connection', (socket) => {
// Check if Host Reconnected -> Resume Game
if (room.hostId === playerId) {
console.log(`Host ${playerName} reconnected. Resuming draft timers.`);
resumeRoomTimers(roomId);
draftManager.setPaused(roomId, false);
}
// If drafting, send state immediately and include in callback
@@ -160,7 +161,7 @@ io.on('connection', (socket) => {
if (room) {
// Clear Timer
stopAutoPickTimer(playerId);
// stopAutoPickTimer(playerId);
console.log(`Player ${playerId} reconnected via rejoin.`);
// Notify others (isOffline false)
@@ -169,7 +170,7 @@ io.on('connection', (socket) => {
// Check if Host Reconnected -> Resume Game
if (room.hostId === playerId) {
console.log(`Host ${playerId} reconnected. Resuming draft timers.`);
resumeRoomTimers(roomId);
draftManager.setPaused(roomId, false);
}
// Prepare Draft State if exists
@@ -394,10 +395,9 @@ io.on('connection', (socket) => {
if (hostOffline) {
console.log("Host is offline. Pausing game (stopping all timers).");
stopAllRoomTimers(room.id);
draftManager.setPaused(room.id, true);
} else {
// Host is online, but THIS player disconnected. Start timer for them.
startAutoPickTimer(room.id, playerId);
// Host is online, but THIS player disconnected. Timer continues automatically.
}
}
}