Compare commits
	
		
			No commits in common. "13f1308669f7180a29c8c562349cfef3f0512d4c" and "8ee20835fed84897cecfc05f6f185785aa7215b5" have entirely different histories.
		
	
	
		
			13f1308669
			...
			8ee20835fe
		
	
		
| @ -1,216 +1,127 @@ | ||||
| // /public/js/auth.js
 | ||||
| 
 | ||||
| // Эта функция будет вызвана из main.js и получит необходимые зависимости
 | ||||
| // /public/js/auth.js (для приложения bc.js)
 | ||||
| export function initAuth(dependencies) { | ||||
|     console.log('[Auth.js] initAuth called. Dependencies received:', !!dependencies); // <--- ДОБАВЛЕНО
 | ||||
|     console.log('[Auth.js] initAuth called for BattleClub.'); | ||||
| 
 | ||||
|     const { socket, clientState, ui } = dependencies; | ||||
|     const { loginForm, registerForm, logoutButton } = ui.elements; | ||||
|     let APP_BASE_PATH = ''; | ||||
| 
 | ||||
|     console.log('[Auth.js DOM Check] loginForm in initAuth:', loginForm);     // <--- ДОБАВЛЕНО
 | ||||
|     console.log('[Auth.js DOM Check] registerForm in initAuth:', registerForm); // <--- ДОБАВЛЕНО
 | ||||
|     console.log('[Auth.js DOM Check] logoutButton in initAuth:', logoutButton); // <--- ДОБАВЛЕНО
 | ||||
|     if (window.location.pathname.startsWith('/battleclub')) { | ||||
|         APP_BASE_PATH = '/battleclub'; | ||||
|     } | ||||
|     const getApiUrl = (path) => `${window.location.origin}${APP_BASE_PATH}${path}`; | ||||
|     console.log('[Auth.js] API URLs will be relative to:', window.location.origin); // <--- ДОБАВЛЕНО
 | ||||
| 
 | ||||
|     const APP_BASE_PATH = '/battleclub'; // Определяем базовый путь для этого приложения
 | ||||
|     const JWT_TOKEN_KEY = 'jwtToken'; | ||||
| 
 | ||||
|     // Функция для формирования полного URL API с учетом базового пути
 | ||||
|     const getApiUrl = (apiPath) => `${window.location.origin}${APP_BASE_PATH}${apiPath}`; | ||||
| 
 | ||||
|     async function handleAuthResponse(response, formType) { | ||||
|         console.log(`[Auth.js handleAuthResponse] Handling response for form: ${formType}. Response status: ${response.status}`); // <--- ДОБАВЛЕНО
 | ||||
|         console.log(`[Auth.js handleAuthResponse] Form: ${formType}, Status: ${response.status}`); | ||||
|         const regButton = registerForm ? registerForm.querySelector('button') : null; | ||||
|         const loginButton = loginForm ? loginForm.querySelector('button') : null; | ||||
| 
 | ||||
|         try { | ||||
|             const data = await response.json(); | ||||
|             console.log(`[Auth.js handleAuthResponse] Parsed data for ${formType}:`, data); // <--- ДОБАВЛЕНО
 | ||||
|             console.log(`[Auth.js handleAuthResponse] Parsed data:`, data); | ||||
| 
 | ||||
|             if (response.ok && data.success && data.token) { | ||||
|                 console.log(`[Auth.js handleAuthResponse] ${formType} successful. Token received.`); // <--- ДОБАВЛЕНО
 | ||||
|                 localStorage.setItem(JWT_TOKEN_KEY, data.token); | ||||
| 
 | ||||
|                 clientState.isLoggedIn = true; | ||||
|                 clientState.loggedInUsername = data.username; | ||||
|                 clientState.myUserId = data.userId; | ||||
|                 console.log('[Auth.js handleAuthResponse] Client state updated:', JSON.parse(JSON.stringify(clientState))); // <--- ДОБАВЛЕНО
 | ||||
| 
 | ||||
|                 console.log('[Auth.js handleAuthResponse] Auth successful. Client state updated:', JSON.parse(JSON.stringify(clientState))); | ||||
|                  | ||||
|                 ui.setAuthMessage(''); | ||||
|                 ui.showGameSelectionScreen(data.username); | ||||
| 
 | ||||
|                 console.log('[Auth.js handleAuthResponse] Disconnecting and reconnecting socket with new token.'); // <--- ДОБАВЛЕНО
 | ||||
|                 console.log('[Auth.js handleAuthResponse] Reconnecting socket with new token.'); | ||||
|                 if (socket.connected) { | ||||
|                     socket.disconnect(); | ||||
|                 } | ||||
|                 // Socket.IO клиент (в main.js) уже должен быть настроен с правильным path: '/battleclub/socket.io'
 | ||||
|                 socket.auth = { token: data.token }; | ||||
|                 socket.connect(); | ||||
| 
 | ||||
|             } else { | ||||
|                 console.warn(`[Auth.js handleAuthResponse] ${formType} failed or token missing. Message: ${data.message}`); // <--- ДОБАВЛЕНО
 | ||||
|                 clientState.isLoggedIn = false; | ||||
|                 clientState.loggedInUsername = ''; | ||||
|                 clientState.myUserId = null; | ||||
|                 console.warn(`[Auth.js handleAuthResponse] Auth failed or token missing. Message: ${data.message}`); | ||||
|                 clientState.isLoggedIn = false; clientState.loggedInUsername = ''; clientState.myUserId = null; | ||||
|                 localStorage.removeItem(JWT_TOKEN_KEY); | ||||
|                 ui.setAuthMessage(data.message || 'Ошибка сервера.', true); | ||||
|             } | ||||
|         } catch (error) { | ||||
|             console.error(`[Auth.js handleAuthResponse] Error processing ${formType} response JSON or other:`, error); // <--- ДОБАВЛЕНО
 | ||||
|             clientState.isLoggedIn = false; | ||||
|             clientState.loggedInUsername = ''; | ||||
|             clientState.myUserId = null; | ||||
|             console.error(`[Auth.js handleAuthResponse] Error processing response:`, error); | ||||
|             clientState.isLoggedIn = false; clientState.loggedInUsername = ''; clientState.myUserId = null; | ||||
|             localStorage.removeItem(JWT_TOKEN_KEY); | ||||
|             ui.setAuthMessage('Произошла ошибка сети или ответа сервера. Попробуйте снова.', true); | ||||
|             ui.setAuthMessage('Произошла ошибка сети или ответа сервера.', true); | ||||
|         } finally { | ||||
|             console.log(`[Auth.js handleAuthResponse] Re-enabling buttons for ${formType}.`); // <--- ДОБАВЛЕНО
 | ||||
|             if (regButton) regButton.disabled = false; | ||||
|             if (loginButton) loginButton.disabled = false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     // --- Обработчики событий DOM ---
 | ||||
|     if (registerForm) { | ||||
|         console.log('[Auth.js] Attaching submit listener to registerForm.'); // <--- ДОБАВЛЕНО
 | ||||
|         registerForm.addEventListener('submit', async (e) => { | ||||
|             e.preventDefault(); | ||||
|             console.log('[Auth.js] Register form submitted.'); // <--- ДОБАВЛЕНО
 | ||||
| 
 | ||||
|             const usernameInput = document.getElementById('register-username'); | ||||
|             const passwordInput = document.getElementById('register-password'); | ||||
| 
 | ||||
|             if (!usernameInput || !passwordInput) { | ||||
|                 console.error('[Auth.js] Register form username or password input not found!'); // <--- ДОБАВЛЕНО
 | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             const username = usernameInput.value; | ||||
|             const password = passwordInput.value; | ||||
|             console.log(`[Auth.js] Attempting to register with username: "${username}", password length: ${password.length}`); // <--- ДОБАВЛЕНО
 | ||||
| 
 | ||||
|             const regButton = registerForm.querySelector('button'); | ||||
|             const loginButton = loginForm ? loginForm.querySelector('button') : null; | ||||
|             if (regButton) regButton.disabled = true; | ||||
|             if (loginButton) loginButton.disabled = true; // Блокируем обе кнопки на время запроса
 | ||||
| 
 | ||||
|             const username = document.getElementById('register-username').value; | ||||
|             const password = document.getElementById('register-password').value; | ||||
|             console.log(`[Auth.js] Registering: "${username}"`); | ||||
|             if (registerForm.querySelector('button')) registerForm.querySelector('button').disabled = true; | ||||
|             if (loginForm && loginForm.querySelector('button')) loginForm.querySelector('button').disabled = true; | ||||
|             ui.setAuthMessage('Регистрация...'); | ||||
|             const apiUrl = getApiUrl('/auth/register'); | ||||
|             console.log('[Auth.js] Sending register request to:', apiUrl); // <--- ДОБАВЛЕНО
 | ||||
| 
 | ||||
|             try { | ||||
|                 const response = await fetch(apiUrl, { | ||||
|                 const response = await fetch(getApiUrl('/auth/register'), { // Используем getApiUrl
 | ||||
|                     method: 'POST', | ||||
|                     headers: { 'Content-Type': 'application/json' }, | ||||
|                     body: JSON.stringify({ username, password }), | ||||
|                 }); | ||||
|                 console.log('[Auth.js] Received response from register request.'); // <--- ДОБАВЛЕНО
 | ||||
|                 await handleAuthResponse(response, 'register'); | ||||
|                 if (response.ok && clientState.isLoggedIn && registerForm) { | ||||
|                     console.log('[Auth.js] Registration successful, resetting register form.'); // <--- ДОБАВЛЕНО
 | ||||
|                     registerForm.reset(); | ||||
|                 } | ||||
|                 if (response.ok && clientState.isLoggedIn) registerForm.reset(); | ||||
|             } catch (error) { | ||||
|                 console.error('[Auth.js] Network error during registration fetch:', error); // <--- ДОБАВЛЕНО
 | ||||
|                 ui.setAuthMessage('Ошибка сети при регистрации. Пожалуйста, проверьте ваше подключение.', true); | ||||
|                 if (regButton) regButton.disabled = false; | ||||
|                 if (loginButton) loginButton.disabled = false; | ||||
|                 console.error('[Auth.js] Network error during registration:', error); | ||||
|                 ui.setAuthMessage('Ошибка сети при регистрации.', true); | ||||
|                 if (registerForm.querySelector('button')) registerForm.querySelector('button').disabled = false; | ||||
|                 if (loginForm && loginForm.querySelector('button')) loginForm.querySelector('button').disabled = false; | ||||
|             } | ||||
|         }); | ||||
|     } else { | ||||
|         console.warn('[Auth.js] registerForm element not found, listener not attached.'); // <--- ДОБАВЛЕНО
 | ||||
|     } | ||||
| 
 | ||||
|     if (loginForm) { | ||||
|         console.log('[Auth.js] Attaching submit listener to loginForm.'); // <--- ДОБАВЛЕНО
 | ||||
|         loginForm.addEventListener('submit', async (e) => { | ||||
|             e.preventDefault(); | ||||
|             console.log('[Auth.js] Login form submitted.'); // <--- ДОБАВЛЕНО
 | ||||
| 
 | ||||
|             const usernameInput = document.getElementById('login-username'); | ||||
|             const passwordInput = document.getElementById('login-password'); | ||||
| 
 | ||||
|             if (!usernameInput || !passwordInput) { | ||||
|                 console.error('[Auth.js] Login form username or password input not found!'); // <--- ДОБАВЛЕНО
 | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             const username = usernameInput.value; | ||||
|             const password = passwordInput.value; | ||||
|             console.log(`[Auth.js] Attempting to login with username: "${username}", password length: ${password.length}`); // <--- ДОБАВЛЕНО
 | ||||
| 
 | ||||
|             const loginButton = loginForm.querySelector('button'); | ||||
|             const regButton = registerForm ? registerForm.querySelector('button') : null; | ||||
|             if (loginButton) loginButton.disabled = true; | ||||
|             if (regButton) regButton.disabled = true; | ||||
| 
 | ||||
|             const username = document.getElementById('login-username').value; | ||||
|             const password = document.getElementById('login-password').value; | ||||
|             console.log(`[Auth.js] Logging in: "${username}"`); | ||||
|             if (loginForm.querySelector('button')) loginForm.querySelector('button').disabled = true; | ||||
|             if (registerForm && registerForm.querySelector('button')) registerForm.querySelector('button').disabled = true; | ||||
|             ui.setAuthMessage('Вход...'); | ||||
|             const apiUrl = getApiUrl('/auth/login'); | ||||
|             console.log('[Auth.js] Sending login request to:', apiUrl); // <--- ДОБАВЛЕНО
 | ||||
| 
 | ||||
|             try { | ||||
|                 const response = await fetch(apiUrl, { | ||||
|                 const response = await fetch(getApiUrl('/auth/login'), { // Используем getApiUrl
 | ||||
|                     method: 'POST', | ||||
|                     headers: { 'Content-Type': 'application/json' }, | ||||
|                     body: JSON.stringify({ username, password }), | ||||
|                 }); | ||||
|                 console.log('[Auth.js] Received response from login request.'); // <--- ДОБАВЛЕНО
 | ||||
|                 await handleAuthResponse(response, 'login'); | ||||
|                 // Форма логина обычно не сбрасывается или перенаправляется немедленно,
 | ||||
|                 // это делает showGameSelectionScreen
 | ||||
|             } catch (error) { | ||||
|                 console.error('[Auth.js] Network error during login fetch:', error); // <--- ДОБАВЛЕНО
 | ||||
|                 ui.setAuthMessage('Ошибка сети при входе. Пожалуйста, проверьте ваше подключение.', true); | ||||
|                 if (loginButton) loginButton.disabled = false; | ||||
|                 if (regButton) regButton.disabled = false; | ||||
|                 console.error('[Auth.js] Network error during login:', error); | ||||
|                 ui.setAuthMessage('Ошибка сети при входе.', true); | ||||
|                 if (loginForm.querySelector('button')) loginForm.querySelector('button').disabled = false; | ||||
|                 if (registerForm && registerForm.querySelector('button')) registerForm.querySelector('button').disabled = false; | ||||
|             } | ||||
|         }); | ||||
|     } else { | ||||
|         console.warn('[Auth.js] loginForm element not found, listener not attached.'); // <--- ДОБАВЛЕНО
 | ||||
|     } | ||||
| 
 | ||||
|     if (logoutButton) { | ||||
|         console.log('[Auth.js] Attaching click listener to logoutButton.'); // <--- ДОБАВЛЕНО
 | ||||
|         logoutButton.addEventListener('click', () => { | ||||
|             console.log('[Auth.js] Logout button clicked.'); // <--- ДОБАВЛЕНО
 | ||||
|             // ... (ваш существующий код для logout, он не зависит от API URL) ...
 | ||||
|             console.log('[Auth.js] Logout button clicked.'); | ||||
|             logoutButton.disabled = true; | ||||
| 
 | ||||
|             if (clientState.isLoggedIn && clientState.isInGame && clientState.currentGameId) { | ||||
|                 if (clientState.currentGameState && | ||||
|                     clientState.currentGameState.gameMode === 'pvp' && | ||||
|                     !clientState.currentGameState.isGameOver) { | ||||
|                     console.log('[Auth.js] Player is in an active PvP game. Emitting playerSurrender.'); | ||||
|                     socket.emit('playerSurrender'); | ||||
|             if (clientState.isLoggedIn && clientState.isInGame && clientState.currentGameId /* ... доп. проверки ... */) { | ||||
|                 // ... логика playerSurrender / leaveAiGame ...
 | ||||
|             } | ||||
|                 else if (clientState.currentGameState && | ||||
|                     clientState.currentGameState.gameMode === 'ai' && | ||||
|                     !clientState.currentGameState.isGameOver) { | ||||
|                     console.log('[Auth.js] Player is in an active AI game. Emitting leaveAiGame.'); | ||||
|                     socket.emit('leaveAiGame'); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             console.log('[Auth.js] Removing JWT token from localStorage.'); // <--- ДОБАВЛЕНО
 | ||||
|             localStorage.removeItem(JWT_TOKEN_KEY); | ||||
| 
 | ||||
|             clientState.isLoggedIn = false; | ||||
|             clientState.loggedInUsername = ''; | ||||
|             clientState.myUserId = null; | ||||
|             console.log('[Auth.js] Client state reset for logout.'); // <--- ДОБАВЛЕНО
 | ||||
| 
 | ||||
|             clientState.isLoggedIn = false; clientState.loggedInUsername = ''; clientState.myUserId = null; | ||||
|             ui.showAuthScreen(); | ||||
|             ui.setAuthMessage("Вы успешно вышли из системы."); | ||||
| 
 | ||||
|             console.log('[Auth.js] Disconnecting and reconnecting socket after logout.'); // <--- ДОБАВЛЕНО
 | ||||
|             if (socket.connected) { | ||||
|                 socket.disconnect(); | ||||
|             } | ||||
|             if (socket.connected) socket.disconnect(); | ||||
|             socket.auth = { token: null }; | ||||
|             socket.connect(); // Это вызовет 'connect' в main.js, который затем вызовет showAuthScreen
 | ||||
|             socket.connect(); | ||||
|         }); | ||||
|     } else { | ||||
|         console.warn('[Auth.js] logoutButton element not found, listener not attached.'); // <--- ДОБАВЛЕНО
 | ||||
|     } | ||||
|     console.log('[Auth.js] initAuth finished.'); // <--- ДОБАВЛЕНО
 | ||||
|     console.log('[Auth.js] initAuth for BattleClub finished.'); | ||||
| } | ||||
| @ -218,7 +218,7 @@ export function initGameplay(dependencies) { | ||||
|         handleGameDataReceived(data, 'gameStarted'); | ||||
|     }); | ||||
| 
 | ||||
|     socket.on('gameState', (data) => { // Это событие было добавлено для поддержки reconnect из старого GameInstance
 | ||||
|     socket.on('gameState', (data) => { | ||||
|         handleGameDataReceived(data, 'gameState (reconnect)'); | ||||
|     }); | ||||
| 
 | ||||
| @ -246,10 +246,10 @@ export function initGameplay(dependencies) { | ||||
|                     } | ||||
| 
 | ||||
|                     console.log(`[CLIENT ${username}] gameStateUpdate - Clearing game status message as game is active.`); | ||||
|                     ui.setGameStatusMessage(""); // Очищаем статус, если игра активна
 | ||||
|                     ui.setGameStatusMessage(""); | ||||
| 
 | ||||
|                 } else if (clientState.currentGameState && clientState.currentGameState.isGameOver) { | ||||
|                     disableGameControls(); // Отключаем управление, если игра закончилась этим обновлением
 | ||||
|                     disableGameControls(); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
| @ -270,35 +270,34 @@ export function initGameplay(dependencies) { | ||||
| 
 | ||||
|     socket.on('gameOver', (data) => { | ||||
|         if (!clientState.isLoggedIn || !clientState.currentGameId || !window.GAME_CONFIG) { | ||||
|             // Если нет ID игры, но залогинен, возможно, стоит запросить состояние
 | ||||
|             if (!clientState.currentGameId && clientState.isLoggedIn) socket.emit('requestGameState'); | ||||
|             else if (!clientState.isLoggedIn) ui.showAuthScreen(); // Если не залогинен, показать экран входа
 | ||||
|             else if (!clientState.isLoggedIn) ui.showAuthScreen(); | ||||
|             return; | ||||
|         } | ||||
|         const username = clientState.loggedInUsername || 'N/A'; | ||||
|         console.log(`[CLIENT ${username}] Event: gameOver.`); | ||||
| 
 | ||||
|         const playerWon = data.winnerId === clientState.myPlayerId; | ||||
|         clientState.currentGameState = data.finalGameState; // Обновляем состояние последним полученным
 | ||||
|         clientState.isInGame = false; // Игра точно закончена
 | ||||
|         clientState.currentGameState = data.finalGameState; | ||||
|         clientState.isInGame = false; | ||||
| 
 | ||||
|         ui.updateGlobalWindowVariablesForUI(); // Обновляем глобальные переменные для ui.js
 | ||||
|         ui.updateGlobalWindowVariablesForUI(); | ||||
| 
 | ||||
|         if (window.gameUI?.updateUI) requestAnimationFrame(() => window.gameUI.updateUI()); // Обновляем UI один раз
 | ||||
|         if (window.gameUI?.updateUI) requestAnimationFrame(() => window.gameUI.updateUI()); | ||||
|         if (window.gameUI?.addToLog && data.log) { | ||||
|             data.log.forEach(log => window.gameUI.addToLog(log.message, log.type)); | ||||
|         } | ||||
|         if (window.gameUI?.showGameOver) { | ||||
|             const oppKey = clientState.opponentBaseStatsServer?.characterKey; // Используем сохраненные данные оппонента
 | ||||
|             const oppKey = clientState.opponentBaseStatsServer?.characterKey; | ||||
|             window.gameUI.showGameOver(playerWon, data.reason, oppKey, data); | ||||
|         } | ||||
|         if (returnToMenuButton) returnToMenuButton.disabled = false; | ||||
|         // `ui.setGameStatusMessage` будет установлено специфичным сообщением о результате игры
 | ||||
|         // ui.setGameStatusMessage("Игра окончена. " + (playerWon ? "Вы победили!" : "Вы проиграли."));
 | ||||
|         if (window.gameUI?.updateTurnTimerDisplay) { | ||||
|             window.gameUI.updateTurnTimerDisplay(null, false, clientState.currentGameState?.gameMode); // Сбрасываем таймер
 | ||||
|             window.gameUI.updateTurnTimerDisplay(null, false, clientState.currentGameState?.gameMode); | ||||
|         } | ||||
|         disableGameControls(); // Отключаем управление игрой
 | ||||
|         disableGameControls(); | ||||
|     }); | ||||
| 
 | ||||
|     socket.on('opponentDisconnected', (data) => { | ||||
| @ -313,28 +312,24 @@ export function initGameplay(dependencies) { | ||||
|         // }
 | ||||
| 
 | ||||
|         if (clientState.currentGameState && !clientState.currentGameState.isGameOver) { | ||||
|             ui.setGameStatusMessage(`Противник (${name}) отключился. Ожидание...`, true); // Показываем сообщение ожидания
 | ||||
|             disableGameControls(); // Отключаем управление на время ожидания
 | ||||
|             ui.setGameStatusMessage(`Противник (${name}) отключился. Ожидание...`, true); | ||||
|             disableGameControls(); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     socket.on('turnTimerUpdate', (data) => { | ||||
|         // Проверяем, в игре ли мы и есть ли gameState, прежде чем обновлять таймер
 | ||||
|         if (!clientState.isInGame || !clientState.currentGameState || !window.GAME_CONFIG) { | ||||
|             // Если не в игре, но gameState есть (например, игра завершена, но экран еще не обновился),
 | ||||
|             // то таймер нужно сбросить/скрыть.
 | ||||
|             if (window.gameUI?.updateTurnTimerDisplay && clientState.currentGameState && !clientState.currentGameState.isGameOver) { | ||||
|                 window.gameUI.updateTurnTimerDisplay(null, false, clientState.currentGameState.gameMode); | ||||
|             } | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // Если игра завершена, таймер не должен обновляться или должен быть сброшен
 | ||||
|         if (clientState.currentGameState.isGameOver) { | ||||
|             if (window.gameUI?.updateTurnTimerDisplay) { | ||||
|                 window.gameUI.updateTurnTimerDisplay(null, false, clientState.currentGameState.gameMode); | ||||
|             } | ||||
|             disableGameControls(); // Убедимся, что управление отключено
 | ||||
|             disableGameControls(); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
| @ -349,25 +344,17 @@ export function initGameplay(dependencies) { | ||||
| 
 | ||||
|             window.gameUI.updateTurnTimerDisplay(data.remainingTime, isMyActualTurn, clientState.currentGameState.gameMode); | ||||
| 
 | ||||
|             // Включаем/отключаем управление в зависимости от хода
 | ||||
|             if (isMyActualTurn) { | ||||
|                 enableGameControls(); | ||||
|             } else { | ||||
|                 disableGameControls(); | ||||
|             } | ||||
| 
 | ||||
|             // Если таймер активен и игра не закончена, общее сообщение "Ожидание" должно быть снято
 | ||||
|             // (если оно не специфично для дисконнекта оппонента)
 | ||||
|             if (!clientState.currentGameState.isGameOver) { | ||||
|                 // Проверяем, не показывается ли уже сообщение о дисконнекте оппонента
 | ||||
|                 const statusMsgElement = document.getElementById('game-status-message'); | ||||
|                 const currentStatusText = statusMsgElement ? statusMsgElement.textContent : ""; | ||||
|                 if (!currentStatusText.toLowerCase().includes("отключился")) { | ||||
|                 console.log(`[CLIENT ${username}] turnTimerUpdate - Clearing game status message as timer is active.`); | ||||
|                 ui.setGameStatusMessage(""); | ||||
|             } | ||||
|         } | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     // Начальная деактивация
 | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| // /server/game/GameManager.js
 | ||||
| const { v4: uuidv4 } = require('uuid'); | ||||
| const GameInstance = require('./instance/GameInstance'); | ||||
| const GameInstance = require('./instance/GameInstance'); // Путь к GameInstance с геттерами
 | ||||
| const dataUtils = require('../data/dataUtils'); | ||||
| const GAME_CONFIG = require('../core/config'); | ||||
| 
 | ||||
| @ -13,66 +13,46 @@ class GameManager { | ||||
|         console.log("[GameManager] Initialized."); | ||||
|     } | ||||
| 
 | ||||
|     // Модифицированная функция: теперь она просто удаляет ожидающую игру пользователя, если находит.
 | ||||
|     // excludeGameId здесь больше не так критичен, так как логика вызова изменится.
 | ||||
|     _cleanupPreviousPendingGameForUser(identifier, reasonSuffix = 'unknown_cleanup_reason') { | ||||
|     _removePreviousPendingGames(currentSocketId, identifier, excludeGameId = null) { | ||||
|         console.log(`[GameManager._removePreviousPendingGames] User: ${identifier}, Socket: ${currentSocketId}, Exclude: ${excludeGameId}`); | ||||
|         const oldPendingGameId = this.userIdentifierToGameId[identifier]; | ||||
|         if (oldPendingGameId && this.games[oldPendingGameId]) { | ||||
|             const gameToRemove = this.games[oldPendingGameId]; | ||||
|             if (gameToRemove.mode === 'pvp' && | ||||
|                 gameToRemove.playerCount === 1 && // Убеждаемся, что это именно ожидающая игра с одним игроком
 | ||||
|                 gameToRemove.ownerIdentifier === identifier && // И этот игрок - владелец
 | ||||
|                 this.pendingPvPGames.includes(oldPendingGameId)) { | ||||
|                 console.log(`[GameManager._cleanupPreviousPendingGameForUser] User ${identifier} performing new action. Removing previous pending PvP game: ${oldPendingGameId}. Reason: ${reasonSuffix}`); | ||||
|                 this._cleanupGame(oldPendingGameId, `owner_action_removed_pending_game_${reasonSuffix}`); | ||||
|                 // _cleanupGame должен удалить запись из userIdentifierToGameId
 | ||||
|                 return true; // Успешно очистили
 | ||||
|             } | ||||
|         } | ||||
|         return false; // Нечего было очищать или условия не совпали
 | ||||
|     } | ||||
| 
 | ||||
|         if (oldPendingGameId && oldPendingGameId !== excludeGameId && this.games[oldPendingGameId]) { | ||||
|             const gameToRemove = this.games[oldPendingGameId]; | ||||
|             // Используем gameToRemove.playerCount (через геттер)
 | ||||
|             if (gameToRemove.mode === 'pvp' && | ||||
|                 gameToRemove.playerCount === 1 && | ||||
|                 gameToRemove.ownerIdentifier === identifier && | ||||
|                 this.pendingPvPGames.includes(oldPendingGameId)) { | ||||
|                 console.log(`[GameManager._removePreviousPendingGames] User ${identifier} creating/joining new. Removing previous pending PvP game: ${oldPendingGameId}`); | ||||
|                 this._cleanupGame(oldPendingGameId, 'owner_action_removed_pending_game'); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     createGame(socket, mode = 'ai', chosenCharacterKey = null, identifier) { | ||||
|         console.log(`[GameManager.createGame] User: ${identifier} (Socket: ${socket.id}), Mode: ${mode}, Char: ${chosenCharacterKey || 'Default'}`); | ||||
| 
 | ||||
|         // 1. Проверить, не находится ли пользователь уже в ЗАВЕРШЕННОЙ, но не очищенной игре.
 | ||||
|         const existingGameId = this.userIdentifierToGameId[identifier]; | ||||
|         if (existingGameId && this.games[existingGameId]) { | ||||
|             const existingGame = this.games[existingGameId]; | ||||
|             if (existingGame.gameState && existingGame.gameState.isGameOver) { | ||||
|                 console.warn(`[GameManager.createGame] User ${identifier} was in a finished game ${existingGameId}. Cleaning it up.`); | ||||
|                 this._cleanupGame(existingGameId, `stale_finished_on_create_${identifier}`); | ||||
|                 // existingGameId в userIdentifierToGameId[identifier] должен был удалиться
 | ||||
|             } else if (existingGame.mode === mode && // Если это та же самая игра, к которой он пытается "пересоздать"
 | ||||
|                 ((mode === 'ai' && existingGame.ownerIdentifier === identifier) || | ||||
|                     (mode === 'pvp' && existingGame.ownerIdentifier === identifier && existingGame.playerCount === 1) )) | ||||
|             { | ||||
|                 console.warn(`[GameManager.createGame] User ${identifier} trying to recreate an existing identical game ${existingGameId}. Sending current state.`); | ||||
|                 socket.emit('gameError', { message: mode === 'pvp' ? 'Вы уже создали PvP игру и ожидаете оппонента.' : 'Вы уже в игре с AI.' }); | ||||
|                 this.handleRequestGameState(socket, identifier); | ||||
|                 return; | ||||
|             } else if (existingGame.mode !== mode || existingGame.ownerIdentifier !== identifier) { | ||||
|                 // Если он в другой активной игре (не своей ожидающей)
 | ||||
|                 socket.emit('gameError', { message: 'Вы уже находитесь в другой активной игре.' }); | ||||
|                 this.handleRequestGameState(socket, identifier); | ||||
|                 return; | ||||
|             } | ||||
|             // Если это его собственная ожидающая PvP игра, мы ее удалим ниже.
 | ||||
|         } | ||||
|             // Используем existingGame.playerCount (через геттер)
 | ||||
|             console.warn(`[GameManager.createGame] User ${identifier} already in game ${existingGameId}. Mode: ${existingGame.mode}, Players: ${existingGame.playerCount}, Owner: ${existingGame.ownerIdentifier}, GameOver: ${existingGame.gameState?.isGameOver}`); | ||||
| 
 | ||||
|         // 2. Удалить предыдущую ОЖИДАЮЩУЮ PvP игру этого пользователя, если он создает новую любую игру.
 | ||||
|         this._cleanupPreviousPendingGameForUser(identifier, `creating_new_game_mode_${mode}`); | ||||
| 
 | ||||
|         // 3. Если после очистки пользователь все еще привязан к какой-то *другой* активной игре (не той, что только что очистили)
 | ||||
|         //    Это может случиться, если _cleanupPreviousPendingGameForUser не нашла ожидающую, но он в другой игре.
 | ||||
|         const stillExistingGameId = this.userIdentifierToGameId[identifier]; | ||||
|         if (stillExistingGameId && this.games[stillExistingGameId] && !this.games[stillExistingGameId].gameState?.isGameOver) { | ||||
|             if (existingGame.gameState && !existingGame.gameState.isGameOver) { | ||||
|                 // Используем existingGame.playerCount (через геттер)
 | ||||
|                 if (existingGame.mode === 'pvp' && existingGame.playerCount === 1 && existingGame.ownerIdentifier === identifier) { | ||||
|                     socket.emit('gameError', { message: 'Вы уже создали PvP игру и ожидаете оппонента.' }); | ||||
|                 } else { | ||||
|                     socket.emit('gameError', { message: 'Вы уже находитесь в активной игре.' }); | ||||
|                 } | ||||
|                 this.handleRequestGameState(socket, identifier); | ||||
|                 return; | ||||
|             } else { | ||||
|                 this._cleanupGame(existingGameId, `stale_finished_on_create_${identifier}`); | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
|         this._removePreviousPendingGames(socket.id, identifier); | ||||
| 
 | ||||
|         const gameId = uuidv4(); | ||||
|         console.log(`[GameManager.createGame] New GameID: ${gameId}`); | ||||
| @ -83,13 +63,14 @@ class GameManager { | ||||
| 
 | ||||
|         if (game.addPlayer(socket, charKeyForPlayer, identifier)) { | ||||
|             this.userIdentifierToGameId[identifier] = gameId; | ||||
|             // Получаем роль и актуальный ключ из GameInstance через геттер game.players
 | ||||
|             const playerInfo = Object.values(game.players).find(p => p.identifier === identifier); | ||||
|             const assignedPlayerId = playerInfo?.id; | ||||
|             const actualCharacterKey = playerInfo?.chosenCharacterKey; | ||||
| 
 | ||||
|             if (!assignedPlayerId || !actualCharacterKey) { | ||||
|                 console.error(`[GameManager.createGame] CRITICAL: Failed to get player role/charKey after addPlayer for ${identifier} in game ${gameId}. Cleaning up.`); | ||||
|                 this._cleanupGame(gameId, 'player_info_missing_after_add_on_create'); | ||||
|                 this._cleanupGame(gameId, 'player_info_missing_after_add'); | ||||
|                 socket.emit('gameError', { message: 'Ошибка сервера при создании роли в игре.' }); | ||||
|                 return; | ||||
|             } | ||||
| @ -109,98 +90,69 @@ class GameManager { | ||||
|                     this._cleanupGame(gameId, 'init_fail_ai_create_gm'); | ||||
|                 } | ||||
|             } else if (mode === 'pvp') { | ||||
|                 if (game.initializeGame()) { // Для PvP инициализируем даже с одним игроком
 | ||||
|                 game.initializeGame(); | ||||
|                 if (!this.pendingPvPGames.includes(gameId)) { | ||||
|                     this.pendingPvPGames.push(gameId); | ||||
|                 } | ||||
|                 socket.emit('waitingForOpponent'); | ||||
|                 this.broadcastAvailablePvPGames(); | ||||
|                 } else { | ||||
|                     this._cleanupGame(gameId, 'init_fail_pvp_create_gm_single_player'); | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             console.error(`[GameManager.createGame] game.addPlayer failed for ${identifier} in ${gameId}. Cleaning up.`); | ||||
|             this._cleanupGame(gameId, 'player_add_failed_in_instance_gm_on_create'); | ||||
|             console.error(`[GameManager.createGame] game.addPlayer (instance method) failed for ${identifier} in ${gameId}. Cleaning up.`); | ||||
|             this._cleanupGame(gameId, 'player_add_failed_in_instance_gm'); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     joinGame(socket, gameIdToJoin, identifier, chosenCharacterKey = null) { | ||||
|         console.log(`[GameManager.joinGame] User: ${identifier} (Socket: ${socket.id}) attempts to join ${gameIdToJoin} with char ${chosenCharacterKey || 'Default'}`); | ||||
|         const gameToJoin = this.games[gameIdToJoin]; | ||||
|         const game = this.games[gameIdToJoin]; | ||||
| 
 | ||||
|         if (!gameToJoin) { socket.emit('gameError', { message: 'Игра с таким ID не найдена.' }); return; } | ||||
|         if (gameToJoin.gameState?.isGameOver) { socket.emit('gameError', { message: 'Эта игра уже завершена.' }); this._cleanupGame(gameIdToJoin, `attempt_join_finished_game_${identifier}`); return; } | ||||
|         if (gameToJoin.mode !== 'pvp') { socket.emit('gameError', { message: 'К этой игре нельзя присоединиться (не PvP).' }); return; } | ||||
|         if (!game) { socket.emit('gameError', { message: 'Игра с таким ID не найдена.' }); return; } | ||||
|         if (game.gameState?.isGameOver) { socket.emit('gameError', { message: 'Эта игра уже завершена.' }); this._cleanupGame(gameIdToJoin, `attempt_join_finished_${identifier}`); return; } | ||||
|         if (game.mode !== 'pvp') { socket.emit('gameError', { message: 'К этой игре нельзя присоединиться (не PvP).' }); return; } | ||||
| 
 | ||||
|         const playerInfoInTargetGame = Object.values(gameToJoin.players).find(p => p.identifier === identifier); | ||||
|         if (gameToJoin.playerCount >= 2 && !playerInfoInTargetGame?.isTemporarilyDisconnected) { | ||||
|         // Используем геттер game.players
 | ||||
|         const playerInfoInGame = Object.values(game.players).find(p => p.identifier === identifier); | ||||
|         // Используем game.playerCount (через геттер)
 | ||||
|         if (game.playerCount >= 2 && !playerInfoInGame?.isTemporarilyDisconnected) { | ||||
|             socket.emit('gameError', { message: 'Эта PvP игра уже заполнена.' }); return; | ||||
|         } | ||||
|         // Нельзя присоединиться к своей же игре, если ты ее владелец и не отключен временно
 | ||||
|         if (gameToJoin.ownerIdentifier === identifier && !playerInfoInTargetGame?.isTemporarilyDisconnected) { | ||||
|             // Это может быть ситуация, когда он уже в этой игре (например, обновил страницу и пытается "присоединиться" к своей же)
 | ||||
|             // handleRequestGameState должен корректно обработать реконнект
 | ||||
|             console.warn(`[GameManager.joinGame] User ${identifier} trying to join their own game ${gameIdToJoin} as a new player. Treating as reconnect request.`); | ||||
|             this.handleRequestGameState(socket, identifier); | ||||
|             return; | ||||
|         if (game.ownerIdentifier === identifier && !playerInfoInGame?.isTemporarilyDisconnected) { | ||||
|             socket.emit('gameError', { message: 'Вы не можете присоединиться к своей же ожидающей игре как новый игрок.' }); this.handleRequestGameState(socket, identifier); return; | ||||
|         } | ||||
| 
 | ||||
|         // 1. Проверить, не находится ли пользователь уже в ЗАВЕРШЕННОЙ, но не очищенной игре.
 | ||||
|         const currentActiveGameId = this.userIdentifierToGameId[identifier]; | ||||
|         if (currentActiveGameId && this.games[currentActiveGameId] && this.games[currentActiveGameId].gameState?.isGameOver) { | ||||
|             console.warn(`[GameManager.joinGame] User ${identifier} was in a finished game ${currentActiveGameId} while trying to join ${gameIdToJoin}. Cleaning old one.`); | ||||
|             this._cleanupGame(currentActiveGameId, `stale_finished_on_join_attempt_${identifier}`); | ||||
|         const existingGameIdOfUser = this.userIdentifierToGameId[identifier]; | ||||
|         if (existingGameIdOfUser && existingGameIdOfUser !== gameIdToJoin) { | ||||
|             const otherGame = this.games[existingGameIdOfUser]; | ||||
|             if (otherGame && !otherGame.gameState?.isGameOver) { socket.emit('gameError', { message: 'Вы уже в другой активной игре.' }); this.handleRequestGameState(socket, identifier); return; } | ||||
|             else if (otherGame?.gameState?.isGameOver) this._cleanupGame(existingGameIdOfUser, `stale_finished_on_join_${identifier}`); | ||||
|         } | ||||
| 
 | ||||
|         // 2. Если пользователь УЖЕ ПРИВЯЗАН к какой-то ДРУГОЙ АКТИВНОЙ игре (не той, к которой пытается присоединиться),
 | ||||
|         //    и это НЕ его собственная ожидающая PvP игра, то отказать.
 | ||||
|         //    Если это ЕГО ОЖИДАЮЩАЯ PvP игра, то ее нужно удалить.
 | ||||
|         if (currentActiveGameId && currentActiveGameId !== gameIdToJoin && this.games[currentActiveGameId] && !this.games[currentActiveGameId].gameState?.isGameOver) { | ||||
|             const usersCurrentGame = this.games[currentActiveGameId]; | ||||
|             if (usersCurrentGame.mode === 'pvp' && | ||||
|                 usersCurrentGame.playerCount === 1 && | ||||
|                 usersCurrentGame.ownerIdentifier === identifier && | ||||
|                 this.pendingPvPGames.includes(currentActiveGameId)) { | ||||
|                 console.log(`[GameManager.joinGame] User ${identifier} is owner of pending game ${currentActiveGameId}, but wants to join ${gameIdToJoin}. Cleaning up old game.`); | ||||
|                 this._cleanupPreviousPendingGameForUser(identifier, `joining_another_game_${gameIdToJoin}`); | ||||
|             } else { | ||||
|                 // Пользователь в другой активной игре (не своей ожидающей)
 | ||||
|                 socket.emit('gameError', { message: 'Вы уже находитесь в другой активной игре.' }); | ||||
|                 this.handleRequestGameState(socket, identifier); // Попытаться вернуть в ту игру
 | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         this._removePreviousPendingGames(socket.id, identifier, gameIdToJoin); | ||||
| 
 | ||||
|         const charKeyForJoin = chosenCharacterKey || 'elena'; | ||||
|         if (gameToJoin.addPlayer(socket, charKeyForJoin, identifier)) { | ||||
|         if (game.addPlayer(socket, charKeyForJoin, identifier)) { | ||||
|             this.userIdentifierToGameId[identifier] = gameIdToJoin; | ||||
|             const joinedPlayerInfo = Object.values(gameToJoin.players).find(p => p.identifier === identifier); | ||||
|             // Используем геттер game.players
 | ||||
|             const joinedPlayerInfo = Object.values(game.players).find(p => p.identifier === identifier); | ||||
| 
 | ||||
|             if (!joinedPlayerInfo || !joinedPlayerInfo.id || !joinedPlayerInfo.chosenCharacterKey) { | ||||
|                 console.error(`[GameManager.joinGame] CRITICAL: Failed to get player role/charKey after addPlayer for ${identifier} joining ${gameIdToJoin}.`); | ||||
|                 // Здесь важно НЕ удалять gameToJoin, так как в ней мог быть первый игрок.
 | ||||
|                 // addPlayer должен был вернуть false и не изменить playerCount игры, если что-то пошло не так критично.
 | ||||
|                 // Если addPlayer вернул true, но инфо нет, это проблема в addPlayer или GameInstance.
 | ||||
|                 socket.emit('gameError', { message: 'Ошибка сервера при назначении роли в игре.' }); | ||||
|                 // Возможно, стоит удалить игрока из userIdentifierToGameId, если он не смог корректно добавиться
 | ||||
|                 if (this.userIdentifierToGameId[identifier] === gameIdToJoin) delete this.userIdentifierToGameId[identifier]; | ||||
|                 return; | ||||
|             } | ||||
|             console.log(`[GameManager.joinGame] Player ${identifier} added/reconnected to ${gameIdToJoin} as ${joinedPlayerInfo.id}.`); | ||||
|             socket.emit('gameCreated', { // Используем gameCreated для консистентности, т.к. клиент ожидает это для установки ID игры
 | ||||
|             socket.emit('gameCreated', { | ||||
|                 gameId: gameIdToJoin, | ||||
|                 mode: gameToJoin.mode, | ||||
|                 mode: game.mode, | ||||
|                 yourPlayerId: joinedPlayerInfo.id, | ||||
|                 chosenCharacterKey: joinedPlayerInfo.chosenCharacterKey | ||||
|             }); | ||||
| 
 | ||||
|             if (gameToJoin.playerCount === 2) { | ||||
|             // Используем game.playerCount (через геттер)
 | ||||
|             if (game.playerCount === 2) { | ||||
|                 console.log(`[GameManager.joinGame] Game ${gameIdToJoin} is now full. Initializing and starting.`); | ||||
|                 // Убедимся, что игра еще раз инициализируется с обоими игроками, если нужно
 | ||||
|                 if (gameToJoin.initializeGame()) { | ||||
|                     gameToJoin.startGame(); | ||||
|                 if (game.initializeGame()) { | ||||
|                     game.startGame(); | ||||
|                 } else { | ||||
|                     this._cleanupGame(gameIdToJoin, 'full_init_fail_pvp_join_gm'); return; | ||||
|                 } | ||||
| @ -208,59 +160,40 @@ class GameManager { | ||||
|                 if (idx > -1) this.pendingPvPGames.splice(idx, 1); | ||||
|                 this.broadcastAvailablePvPGames(); | ||||
|             } | ||||
|         } else { | ||||
|             // addPlayer вернул false, GameInstance должен был отправить причину через gameError
 | ||||
|             console.warn(`[GameManager.joinGame] gameToJoin.addPlayer returned false for user ${identifier} in game ${gameIdToJoin}.`); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     findAndJoinRandomPvPGame(socket, chosenCharacterKeyForCreation = 'elena', identifier) { | ||||
|         console.log(`[GameManager.findRandomPvPGame] User: ${identifier} (Socket: ${socket.id}), CharForCreation: ${chosenCharacterKeyForCreation}`); | ||||
| 
 | ||||
|         // 1. Проверить, не находится ли пользователь уже в ЗАВЕРШЕННОЙ, но не очищенной игре.
 | ||||
|         const existingGameId = this.userIdentifierToGameId[identifier]; | ||||
|         if (existingGameId && this.games[existingGameId]) { | ||||
|             const existingGame = this.games[existingGameId]; | ||||
|             if (existingGame.gameState && existingGame.gameState.isGameOver) { | ||||
|                 this._cleanupGame(existingGameId, `stale_finished_on_find_random_${identifier}`); | ||||
|             } else { | ||||
|                 // Если он уже в активной игре (своей ожидающей или другой)
 | ||||
|             if (!existingGame.gameState?.isGameOver) { | ||||
|                 socket.emit('gameError', { message: 'Вы уже в активной или ожидающей игре.' }); | ||||
|                 this.handleRequestGameState(socket, identifier); return; | ||||
|             } else { | ||||
|                 this._cleanupGame(existingGameId, `stale_finished_on_find_random_${identifier}`); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // 2. Удалить предыдущую ОЖИДАЮЩУЮ PvP игру этого пользователя, если он ищет новую.
 | ||||
|         this._cleanupPreviousPendingGameForUser(identifier, `finding_random_game`); | ||||
| 
 | ||||
|         // 3. Если после очистки пользователь все еще привязан к какой-то *другой* активной игре
 | ||||
|         const stillExistingGameId = this.userIdentifierToGameId[identifier]; | ||||
|         if (stillExistingGameId && this.games[stillExistingGameId] && !this.games[stillExistingGameId].gameState?.isGameOver) { | ||||
|             socket.emit('gameError', { message: 'Вы уже находитесь в активной игре.' }); | ||||
|             this.handleRequestGameState(socket, identifier); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this._removePreviousPendingGames(socket.id, identifier); | ||||
| 
 | ||||
|         let gameIdToJoin = null; | ||||
|         // Итерируем копию массива, чтобы избежать проблем при удалении элементов из pendingPvPGames внутри цикла
 | ||||
|         for (const id of [...this.pendingPvPGames]) { | ||||
|             const pendingGame = this.games[id]; | ||||
|             // Используем pendingGame.playerCount (через геттер)
 | ||||
|             if (pendingGame && pendingGame.mode === 'pvp' && | ||||
|                 pendingGame.playerCount === 1 && | ||||
|                 pendingGame.ownerIdentifier !== identifier && // Не присоединяться к своей же игре через "случайный поиск"
 | ||||
|                 pendingGame.ownerIdentifier !== identifier && | ||||
|                 !pendingGame.gameState?.isGameOver) { | ||||
|                 gameIdToJoin = id; break; | ||||
|             } else if (!pendingGame || pendingGame.gameState?.isGameOver) { // Очистка "мертвых" ожидающих игр
 | ||||
|                 // Это может случиться, если игра была удалена, но ID остался в pendingPvPGames
 | ||||
|                 console.warn(`[GameManager.findRandomPvPGame] Found stale/finished pending game ${id}. Cleaning up.`); | ||||
|             } else if (pendingGame?.gameState?.isGameOver) { | ||||
|                 this._cleanupGame(id, `stale_finished_pending_on_find_random`); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (gameIdToJoin) { | ||||
|             console.log(`[GameManager.findRandomPvPGame] Found pending game ${gameIdToJoin} for ${identifier}. Joining...`); | ||||
|             const randomJoinCharKey = ['elena', 'almagest', 'balard'][Math.floor(Math.random() * 3)]; // TODO: Сделать выбор персонажа более умным
 | ||||
|             const randomJoinCharKey = ['elena', 'almagest', 'balard'][Math.floor(Math.random() * 3)]; | ||||
|             this.joinGame(socket, gameIdToJoin, identifier, randomJoinCharKey); | ||||
|         } else { | ||||
|             console.log(`[GameManager.findRandomPvPGame] No suitable pending game. Creating new PvP game for ${identifier}.`); | ||||
| @ -270,23 +203,20 @@ class GameManager { | ||||
| 
 | ||||
|     handlePlayerAction(identifier, actionData) { | ||||
|         const gameId = this.userIdentifierToGameId[identifier]; | ||||
|         // console.log(`[GameManager.handlePlayerAction] User: ${identifier}, Action: ${actionData?.actionType}, GameID: ${gameId}`);
 | ||||
|         console.log(`[GameManager.handlePlayerAction] User: ${identifier}, Action: ${actionData?.actionType}, GameID: ${gameId}`); | ||||
|         const game = this.games[gameId]; | ||||
|         if (game) { | ||||
|             if (game.gameState?.isGameOver) { | ||||
|                 // Используем геттер game.players
 | ||||
|                 const playerSocket = Object.values(game.players).find(p => p.identifier === identifier)?.socket; | ||||
|                 if (playerSocket) { | ||||
|                     console.warn(`[GameManager.handlePlayerAction] Action from ${identifier} for game ${gameId}, but game is over. Requesting state.`); | ||||
|                     this.handleRequestGameState(playerSocket, identifier); | ||||
|                 } | ||||
|                 if (playerSocket) this.handleRequestGameState(playerSocket, identifier); | ||||
|                 return; | ||||
|             } | ||||
|             game.processPlayerAction(identifier, actionData); | ||||
|         } else { | ||||
|             console.warn(`[GameManager.handlePlayerAction] No game found for user ${identifier} (mapped to game ${gameId}). Clearing map entry.`); | ||||
|             delete this.userIdentifierToGameId[identifier]; | ||||
|             const clientSocket = this._findClientSocketByIdentifier(identifier); | ||||
|             if (clientSocket) clientSocket.emit('gameNotFound', { message: 'Ваша игровая сессия не найдена при совершении действия.' }); | ||||
|             if (clientSocket) clientSocket.emit('gameNotFound', { message: 'Ваша игровая сессия не найдена при действии.' }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -297,13 +227,12 @@ class GameManager { | ||||
|         if (game) { | ||||
|             if (game.gameState?.isGameOver) { | ||||
|                 console.warn(`[GameManager.handlePlayerSurrender] User ${identifier} in game ${gameId} surrender, but game ALREADY OVER.`); | ||||
|                 // Не удаляем из userIdentifierToGameId здесь, _cleanupGame сделает это, если игра действительно должна быть удалена.
 | ||||
|                 if (this.userIdentifierToGameId[identifier] === gameId) delete this.userIdentifierToGameId[identifier]; | ||||
|                 return; | ||||
|             } | ||||
|             if (typeof game.playerDidSurrender === 'function') game.playerDidSurrender(identifier); | ||||
|             else { console.error(`[GameManager.handlePlayerSurrender] CRITICAL: GameInstance ${gameId} missing playerDidSurrender!`); this._cleanupGame(gameId, "surrender_missing_method_gm"); } | ||||
|             else { console.error(`[GameManager.handlePlayerSurrender] CRITICAL: GameInstance ${gameId} missing playerDidSurrender!`); this._cleanupGame(gameId, "surrender_missing_method"); } | ||||
|         } else { | ||||
|             console.warn(`[GameManager.handlePlayerSurrender] No game found for user ${identifier}. Clearing map entry.`); | ||||
|             if (this.userIdentifierToGameId[identifier]) delete this.userIdentifierToGameId[identifier]; | ||||
|         } | ||||
|     } | ||||
| @ -322,13 +251,12 @@ class GameManager { | ||||
|                     game.playerExplicitlyLeftAiGame(identifier); | ||||
|                 } else { | ||||
|                     console.error(`[GameManager.handleLeaveAiGame] CRITICAL: GameInstance ${gameId} missing playerExplicitlyLeftAiGame! Cleaning up directly.`); | ||||
|                     this._cleanupGame(gameId, "leave_ai_missing_method_gm"); | ||||
|                     this._cleanupGame(gameId, "leave_ai_missing_method"); | ||||
|                 } | ||||
|             } else { | ||||
|                 console.warn(`[GameManager.handleLeaveAiGame] User ${identifier} sent leaveAiGame, but game ${gameId} is not AI mode (${game.mode}).`); | ||||
|             } | ||||
|         } else { | ||||
|             console.warn(`[GameManager.handleLeaveAiGame] No game found for user ${identifier}. Clearing map entry.`); | ||||
|             if (this.userIdentifierToGameId[identifier]) delete this.userIdentifierToGameId[identifier]; | ||||
|         } | ||||
|     } | ||||
| @ -348,11 +276,10 @@ class GameManager { | ||||
| 
 | ||||
|         if (game) { | ||||
|             if (game.gameState?.isGameOver) { | ||||
|                 console.log(`[GameManager.handleDisconnect] Game ${gameIdFromMap} for user ${identifier} (socket ${socketId}) ALREADY OVER. Game will be cleaned up by its own logic or next relevant action.`); | ||||
|                 // Не удаляем из userIdentifierToGameId здесь, пусть это сделает _cleanupGame по завершению игры.
 | ||||
|                 console.log(`[GameManager.handleDisconnect] Game ${gameIdFromMap} for user ${identifier} (socket ${socketId}) ALREADY OVER.`); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             // Используем геттер game.players
 | ||||
|             const playerInfoInGame = Object.values(game.players).find(p => p.identifier === identifier); | ||||
| 
 | ||||
|             if (playerInfoInGame && playerInfoInGame.socket?.id === socketId && !playerInfoInGame.isTemporarilyDisconnected) { | ||||
| @ -364,21 +291,15 @@ class GameManager { | ||||
|                     this._cleanupGame(gameIdFromMap, "missing_reconnect_logic_on_disconnect_gm"); | ||||
|                 } | ||||
|             } else if (playerInfoInGame && playerInfoInGame.socket?.id !== socketId) { | ||||
|                 console.log(`[GameManager.handleDisconnect] Disconnected socket ${socketId} is STALE for user ${identifier}. Active socket in game: ${playerInfoInGame.socket?.id}. No action taken by GM.`); | ||||
|                 console.log(`[GameManager.handleDisconnect] Disconnected socket ${socketId} is STALE for user ${identifier}. Active socket in game: ${playerInfoInGame.socket?.id}.`); | ||||
|             } else if (playerInfoInGame && playerInfoInGame.isTemporarilyDisconnected) { | ||||
|                 console.log(`[GameManager.handleDisconnect] User ${identifier} (socket ${socketId}) disconnected while ALREADY temp disconnected. Reconnect timer in GameInstance handles final cleanup.`); | ||||
|                 console.log(`[GameManager.handleDisconnect] User ${identifier} (socket ${socketId}) disconnected while already temp disconnected. Reconnect timer handles final cleanup.`); | ||||
|             } else if (!playerInfoInGame) { | ||||
|                 // Это странная ситуация: пользователь привязан к игре, но его нет в списке игроков этой игры.
 | ||||
|                 // Это может случиться, если игра была очищена, но userIdentifierToGameId не обновился.
 | ||||
|                 console.warn(`[GameManager.handleDisconnect] User ${identifier} mapped to game ${gameIdFromMap}, but not found in game.players. This might indicate a stale userIdentifierToGameId entry. Clearing map for this user.`); | ||||
|                 if (this.userIdentifierToGameId[identifier] === gameIdFromMap) { | ||||
|                     delete this.userIdentifierToGameId[identifier]; | ||||
|                 } | ||||
|                 console.warn(`[GameManager.handleDisconnect] User ${identifier} mapped to game ${gameIdFromMap}, but not found in game's player list. Clearing map.`); | ||||
|                 if (this.userIdentifierToGameId[identifier] === gameIdFromMap) delete this.userIdentifierToGameId[identifier]; | ||||
|             } | ||||
|         } else { | ||||
|             // Если игра не найдена, но пользователь был к ней привязан, значит, карта устарела.
 | ||||
|             if (this.userIdentifierToGameId[identifier]) { | ||||
|                 console.warn(`[GameManager.handleDisconnect] No game instance found for gameId ${gameIdFromMap} (user ${identifier}). Clearing stale map entry.`); | ||||
|                 delete this.userIdentifierToGameId[identifier]; | ||||
|             } | ||||
|         } | ||||
| @ -389,61 +310,41 @@ class GameManager { | ||||
|         const game = this.games[gameId]; | ||||
| 
 | ||||
|         if (!game) { | ||||
|             // Если игры уже нет в this.games, но она есть в pendingPvPGames или userIdentifierToGameId,
 | ||||
|             // нужно все равно почистить эти структуры.
 | ||||
|             console.warn(`[GameManager._cleanupGame] Game instance for ${gameId} not found in this.games. Cleaning up associated records.`); | ||||
|             const pendingIdx = this.pendingPvPGames.indexOf(gameId); | ||||
|             if (pendingIdx > -1) { | ||||
|                 this.pendingPvPGames.splice(pendingIdx, 1); | ||||
|                 console.log(`[GameManager._cleanupGame] Removed ${gameId} from pendingPvPGames.`); | ||||
|             if (pendingIdx > -1) { this.pendingPvPGames.splice(pendingIdx, 1); this.broadcastAvailablePvPGames(); } | ||||
|             for (const idKey in this.userIdentifierToGameId) { if (this.userIdentifierToGameId[idKey] === gameId) delete this.userIdentifierToGameId[idKey]; } | ||||
|             return false; | ||||
|         } | ||||
|             for (const idKey in this.userIdentifierToGameId) { | ||||
|                 if (this.userIdentifierToGameId[idKey] === gameId) { | ||||
|                     delete this.userIdentifierToGameId[idKey]; | ||||
|                     console.log(`[GameManager._cleanupGame] Removed mapping for user ${idKey} to game ${gameId}.`); | ||||
|                 } | ||||
|             } | ||||
|             this.broadcastAvailablePvPGames(); // Обновляем список, так как ожидающая игра могла быть удалена
 | ||||
|             return false; // Игры не было для основной очистки
 | ||||
|         } | ||||
| 
 | ||||
|         // Используем game.playerCount (через геттер)
 | ||||
|         console.log(`[GameManager._cleanupGame] Cleaning up game ${game.id}. Owner: ${game.ownerIdentifier}. Reason: ${reason}. Players in game: ${game.playerCount}`); | ||||
|         if (typeof game.turnTimer?.clear === 'function') game.turnTimer.clear(); | ||||
|         if (typeof game.clearAllReconnectTimers === 'function') game.clearAllReconnectTimers(); | ||||
| 
 | ||||
|         // Убедимся, что игра помечена как завершенная, если она еще не была
 | ||||
|         if (game.gameState && !game.gameState.isGameOver) { | ||||
|             console.log(`[GameManager._cleanupGame] Marking game ${game.id} as game over.`); | ||||
|             game.gameState.isGameOver = true; | ||||
|             // Можно также отправить финальное событие gameOver, если это не было сделано ранее
 | ||||
|             // game.io.to(game.id).emit('gameOver', { /* ... данные ... */ });
 | ||||
|         } | ||||
| 
 | ||||
|         // Очищаем всех игроков этой игры из глобальной карты userIdentifierToGameId
 | ||||
|         let playersCleanedFromMap = 0; | ||||
|         // Используем геттер game.players
 | ||||
|         Object.values(game.players).forEach(pInfo => { | ||||
|             if (pInfo?.identifier && this.userIdentifierToGameId[pInfo.identifier] === gameId) { | ||||
|                 delete this.userIdentifierToGameId[pInfo.identifier]; | ||||
|                 console.log(`[GameManager._cleanupGame] Cleared userIdentifierToGameId for player ${pInfo.identifier}.`); | ||||
|                 playersCleanedFromMap++; | ||||
|             } | ||||
|         }); | ||||
|         // Дополнительная проверка для ownerIdentifier, если он не был в game.players
 | ||||
|         if (game.ownerIdentifier && this.userIdentifierToGameId[game.ownerIdentifier] === gameId) { | ||||
|             if (!Object.values(game.players).some(p => p.identifier === game.ownerIdentifier)) { | ||||
|         // Используем геттер game.players
 | ||||
|         if (game.ownerIdentifier && this.userIdentifierToGameId[game.ownerIdentifier] === gameId && | ||||
|             !Object.values(game.players).some(p=>p.identifier === game.ownerIdentifier)) { | ||||
|             delete this.userIdentifierToGameId[game.ownerIdentifier]; | ||||
|                 console.log(`[GameManager._cleanupGame] Cleared userIdentifierToGameId for owner ${game.ownerIdentifier} (was not in players list).`); | ||||
|             playersCleanedFromMap++; | ||||
|         } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         const pendingIdx = this.pendingPvPGames.indexOf(gameId); | ||||
|         if (pendingIdx > -1) { | ||||
|             this.pendingPvPGames.splice(pendingIdx, 1); | ||||
|             console.log(`[GameManager._cleanupGame] Removed ${gameId} from pendingPvPGames.`); | ||||
|         } | ||||
|         if (pendingIdx > -1) this.pendingPvPGames.splice(pendingIdx, 1); | ||||
| 
 | ||||
|         delete this.games[gameId]; | ||||
|         console.log(`[GameManager._cleanupGame] Game ${gameId} instance deleted. Games left: ${Object.keys(this.games).length}. Pending: ${this.pendingPvPGames.length}. User map size: ${Object.keys(this.userIdentifierToGameId).length}`); | ||||
|         this.broadcastAvailablePvPGames(); // Обновляем список, т.к. ожидающая игра могла быть удалена
 | ||||
|         console.log(`[GameManager._cleanupGame] Game ${game.id} instance deleted. Games left: ${Object.keys(this.games).length}. Pending: ${this.pendingPvPGames.length}. User map size: ${Object.keys(this.userIdentifierToGameId).length}`); | ||||
|         this.broadcastAvailablePvPGames(); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
| @ -451,33 +352,26 @@ class GameManager { | ||||
|         return this.pendingPvPGames | ||||
|             .map(gameId => { | ||||
|                 const game = this.games[gameId]; | ||||
|                 // Убедимся, что игра существует и действительно ожидает
 | ||||
|                 // Используем game.playerCount (через геттер)
 | ||||
|                 if (game && game.mode === 'pvp' && game.playerCount === 1 && (!game.gameState || !game.gameState.isGameOver)) { | ||||
|                     // Используем геттер game.players
 | ||||
|                     const p1Entry = Object.values(game.players).find(p => p.id === GAME_CONFIG.PLAYER_ID && !p.isTemporarilyDisconnected); | ||||
|                     let p1Username = 'Игрок'; | ||||
|                     let p1CharName = 'Неизвестный'; | ||||
|                     const ownerId = game.ownerIdentifier; | ||||
| 
 | ||||
|                     if (p1Entry && p1Entry.socket?.userData) { // Приоритет данным из сокета, если есть
 | ||||
|                     if (p1Entry && p1Entry.socket?.userData) { | ||||
|                         p1Username = p1Entry.socket.userData.username || `User#${String(p1Entry.identifier).substring(0,4)}`; | ||||
|                         const charData = dataUtils.getCharacterBaseStats(p1Entry.chosenCharacterKey); | ||||
|                         p1CharName = charData?.name || p1Entry.chosenCharacterKey || 'Не выбран'; | ||||
|                     } else if (ownerId){ // Фоллбэк на данные по ownerId
 | ||||
|                         const ownerSocket = this._findClientSocketByIdentifier(ownerId); // Попытаться найти сокет владельца
 | ||||
|                     } else if (ownerId){ | ||||
|                         const ownerSocket = this._findClientSocketByIdentifier(ownerId); | ||||
|                         p1Username = ownerSocket?.userData?.username || `Owner#${String(ownerId).substring(0,4)}`; | ||||
|                         const ownerCharKey = game.playerCharacterKey; | ||||
|                         const charData = ownerCharKey ? dataUtils.getCharacterBaseStats(ownerCharKey) : null; | ||||
|                         p1CharName = charData?.name || ownerCharKey || 'Не выбран'; | ||||
|                     } else if (p1Entry) { // Если есть p1Entry, но нет userData (маловероятно для создателя)
 | ||||
|                         p1Username = `User#${String(p1Entry.identifier).substring(0,4)}`; | ||||
|                         const charData = dataUtils.getCharacterBaseStats(p1Entry.chosenCharacterKey); | ||||
|                         p1CharName = charData?.name || p1Entry.chosenCharacterKey || 'Не выбран'; | ||||
|                     } | ||||
|                     return { id: gameId, status: `Ожидает (${p1Username} за ${p1CharName})`, ownerIdentifier: ownerId }; | ||||
|                 } else if (game && (game.playerCount !== 1 || game.gameState?.isGameOver)) { | ||||
|                     // Если игра есть в pending, но не соответствует условиям, ее нужно оттуда удалить
 | ||||
|                     console.warn(`[GameManager.getAvailablePvPGamesListForClient] Game ${gameId} is in pendingPvPGames but is not a valid pending game (players: ${game.playerCount}, over: ${game.gameState?.isGameOver}). Removing.`); | ||||
|                     this._cleanupGame(gameId, 'invalid_pending_game_in_list'); // Это вызовет broadcastAvailablePvPGames снова, поэтому мы делаем map на копии.
 | ||||
|                 } | ||||
|                 return null; | ||||
|             }) | ||||
| @ -495,67 +389,47 @@ class GameManager { | ||||
|         const game = gameIdFromMap ? this.games[gameIdFromMap] : null; | ||||
| 
 | ||||
|         if (game) { | ||||
|             // Используем геттер game.players
 | ||||
|             const playerInfoInGame = Object.values(game.players).find(p => p.identifier === identifier); | ||||
|             // console.log(`[GameManager.handleRequestGameState] Game ${gameIdFromMap} found. PlayerInfo in game.players: ${playerInfoInGame ? `Role: ${playerInfoInGame.id}, TempDisco: ${playerInfoInGame.isTemporarilyDisconnected}` : 'Not found in game.players'}`);
 | ||||
|             console.log(`[GameManager.handleRequestGameState] Game ${gameIdFromMap} found. PlayerInfo: ${playerInfoInGame ? `Role: ${playerInfoInGame.id}, TempDisco: ${playerInfoInGame.isTemporarilyDisconnected}` : 'Not found in game.players'}`); | ||||
| 
 | ||||
|             if (playerInfoInGame) { // Игрок действительно является частью этой игры
 | ||||
|             if (playerInfoInGame) { | ||||
|                 if (game.gameState?.isGameOver) { | ||||
|                     socket.emit('gameNotFound', { message: 'Ваша предыдущая игра уже завершена.' }); | ||||
|                     // _cleanupGame должна быть вызвана, когда игра фактически завершается,
 | ||||
|                     // здесь мы не должны удалять из userIdentifierToGameId, если игра еще есть в this.games.
 | ||||
|                     // Если игра уже очищена, то game будет null.
 | ||||
|                     if(this.userIdentifierToGameId[identifier] === gameIdFromMap) delete this.userIdentifierToGameId[identifier]; | ||||
|                     return; | ||||
|                 } | ||||
|                 if (typeof game.handlePlayerReconnected === 'function') { | ||||
|                     const reconnected = game.handlePlayerReconnected(playerInfoInGame.id, socket); | ||||
|                     // reconnected может вернуть false, если реконнект не удался по внутренней причине GameInstance
 | ||||
|                     if (!reconnected) { | ||||
|                         console.warn(`[GameManager.handleRequestGameState] game.handlePlayerReconnected for ${identifier} in ${game.id} returned false.`); | ||||
|                         // GameInstance должен был отправить ошибку клиенту, если нужно.
 | ||||
|                         // Можно рассмотреть _handleGameRecoveryError, если это критично.
 | ||||
|                     } | ||||
|                     // ... (обработка результата reconnected, если нужно)
 | ||||
|                 } else { | ||||
|                     console.error(`[GameManager.handleRequestGameState] CRITICAL: GameInstance ${game.id} missing handlePlayerReconnected!`); | ||||
|                     this._handleGameRecoveryError(socket, game.id, identifier, 'gi_missing_reconnect_method_gm_on_request'); | ||||
|                     this._handleGameRecoveryError(socket, game.id, identifier, 'gi_missing_reconnect_method_gm'); | ||||
|                 } | ||||
|             } else { | ||||
|                 // Пользователь привязан к этой игре в userIdentifierToGameId, но его нет в списке игроков game.players
 | ||||
|                 // Это может означать, что он был удален из игры (например, таймаут реконнекта), но userIdentifierToGameId не очистился.
 | ||||
|                 console.warn(`[GameManager.handleRequestGameState] User ${identifier} mapped to game ${gameIdFromMap}, but NOT FOUND in game.players. Cleaning map & sending gameNotFound.`); | ||||
|                 this._handleGameRecoveryError(socket, gameIdFromMap, identifier, 'player_not_in_gi_players_but_mapped_on_request'); | ||||
|                 this._handleGameRecoveryError(socket, gameIdFromMap, identifier, 'player_not_in_gi_players_reconnect_gm'); | ||||
|             } | ||||
|         } else { | ||||
|             // Игра не найдена в this.games, но могла быть в userIdentifierToGameId
 | ||||
|             socket.emit('gameNotFound', { message: 'Активная игровая сессия не найдена.' }); | ||||
|             if (this.userIdentifierToGameId[identifier]) { // Если привязка была, удаляем ее
 | ||||
|                 console.warn(`[GameManager.handleRequestGameState] No game instance found for gameId ${gameIdFromMap} (user ${identifier}). Clearing stale map entry.`); | ||||
|                 delete this.userIdentifierToGameId[identifier]; | ||||
|             } | ||||
|             if (this.userIdentifierToGameId[identifier]) delete this.userIdentifierToGameId[identifier]; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     _handleGameRecoveryError(socket, gameId, identifier, reasonCode) { | ||||
|         console.error(`[GameManager._handleGameRecoveryError] Error recovering game (ID: ${gameId || 'N/A'}) for user ${identifier}. Reason: ${reasonCode}.`); | ||||
|         socket.emit('gameError', { message: 'Ошибка сервера при восстановлении состояния игры. Попробуйте войти снова.' }); | ||||
| 
 | ||||
|         // Очищаем игру, если она существует и вызвала ошибку
 | ||||
|         socket.emit('gameError', { message: 'Ошибка сервера при восстановлении состояния игры.' }); | ||||
|         if (gameId && this.games[gameId]) { | ||||
|             this._cleanupGame(gameId, `recovery_error_gm_${reasonCode}_for_${identifier}`); | ||||
|             this._cleanupGame(gameId, `recovery_error_${reasonCode}_for_${identifier}`); | ||||
|         } else if (this.userIdentifierToGameId[identifier]) { | ||||
|             // Если игра уже удалена, но пользователь все еще к ней привязан в карте
 | ||||
|             const problematicGameIdForUser = this.userIdentifierToGameId[identifier]; | ||||
|             if (this.games[problematicGameIdForUser]) { // Если она все же есть (маловероятно, если gameId был null)
 | ||||
|                 this._cleanupGame(problematicGameIdForUser, `recovery_error_stale_map_gm_${identifier}_reason_${reasonCode}`); | ||||
|             } else { // Если ее нет, просто чистим карту
 | ||||
|             const problematicGameId = this.userIdentifierToGameId[identifier]; | ||||
|             if (this.games[problematicGameId]) { | ||||
|                 this._cleanupGame(problematicGameId, `recovery_error_stale_map_${identifier}_reason_${reasonCode}`); | ||||
|             } else { | ||||
|                 delete this.userIdentifierToGameId[identifier]; | ||||
|             } | ||||
|         } | ||||
|         // Если после _cleanupGame пользователь все еще привязан (маловероятно, но для гарантии)
 | ||||
|         if (this.userIdentifierToGameId[identifier]) { | ||||
|             delete this.userIdentifierToGameId[identifier]; | ||||
|         } | ||||
|         // Отправляем gameNotFound, чтобы клиент перешел на экран логина или выбора игры
 | ||||
|         socket.emit('gameNotFound', { message: 'Ваша игровая сессия была завершена из-за ошибки. Пожалуйста, войдите снова.' }); | ||||
|         if (this.userIdentifierToGameId[identifier]) delete this.userIdentifierToGameId[identifier]; | ||||
|         socket.emit('gameNotFound', { message: 'Ваша игровая сессия была завершена из-за ошибки.' }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user