// /public/js/client.js document.addEventListener('DOMContentLoaded', () => { const socket = io({ path: '/battleclub/socket.io' }) // --- Состояние клиента --- let currentGameState = null; let myPlayerId = null; // Технический ID слота, который занимает ЭТОТ клиент ('player' или 'opponent') let myCharacterKey = null; let opponentCharacterKey = null; let currentGameId = null; let playerBaseStatsServer = null; // Статы персонажа, которым УПРАВЛЯЕТ этот клиент (приходят от сервера как data.playerBaseStats) let opponentBaseStatsServer = null; // Статы персонажа-оппонента этого клиента (приходят от сервера как data.opponentBaseStats) let playerAbilitiesServer = null; let opponentAbilitiesServer = null; let isLoggedIn = false; let loggedInUsername = ''; let isInGame = false; // ФЛАГ СОСТОЯНИЯ ИГРЫ // --- DOM Элементы --- // Аутентификация const authSection = document.getElementById('auth-section'); const registerForm = document.getElementById('register-form'); const loginForm = document.getElementById('login-form'); const authMessage = document.getElementById('auth-message'); const statusContainer = document.getElementById('status-container'); // Добавим ссылку на контейнер const userInfoDiv = document.getElementById('user-info'); const loggedInUsernameSpan = document.getElementById('logged-in-username'); const logoutButton = document.getElementById('logout-button'); // Настройка игры const gameSetupDiv = document.getElementById('game-setup'); const createAIGameButton = document.getElementById('create-ai-game'); const createPvPGameButton = document.getElementById('create-pvp-game'); const joinPvPGameButton = document.getElementById('join-pvP-game'); const findRandomPvPGameButton = document.getElementById('find-random-pvp-game'); const gameIdInput = document.getElementById('game-id-input'); const availableGamesDiv = document.getElementById('available-games-list'); const gameStatusMessage = document.getElementById('game-status-message'); const pvpCharacterRadios = document.querySelectorAll('input[name="pvp-character"]'); // Игровая Арена const gameWrapper = document.querySelector('.game-wrapper'); const attackButton = document.getElementById('button-attack'); const returnToMenuButton = document.getElementById('return-to-menu-button'); const gameOverScreen = document.getElementById('game-over-screen'); const abilitiesGrid = document.getElementById('abilities-grid'); console.log('Client.js DOMContentLoaded. Initializing elements...'); // --- Функции управления UI --- function showAuthScreen() { console.log('[UI] Showing Auth Screen'); if (authSection) authSection.style.display = 'block'; if (userInfoDiv) userInfoDiv.style.display = 'none'; if (gameSetupDiv) gameSetupDiv.style.display = 'none'; if (gameWrapper) gameWrapper.style.display = 'none'; hideGameOverModal(); // setGameStatusMessage("Войдите или зарегистрируйтесь для начала игры."); // Это сообщение перенесено в setAuthMessage/начальный статус setAuthMessage("Ожидание подключения к серверу..."); // Начальный статус if (statusContainer) statusContainer.style.display = 'block'; // Убедимся, что статус виден isInGame = false; disableGameControls(); resetGameVariables(); // Сбрасываем переменные игры при выходе на экран логина } function showGameSelectionScreen(username) { console.log('[UI] Showing Game Selection Screen for:', username); if (authSection) authSection.style.display = 'none'; if (userInfoDiv) { userInfoDiv.style.display = 'block'; if(loggedInUsernameSpan) loggedInUsernameSpan.textContent = username; } if (gameSetupDiv) gameSetupDiv.style.display = 'block'; if (gameWrapper) gameWrapper.style.display = 'none'; hideGameOverModal(); setGameStatusMessage("Выберите режим игры или присоединитесь к существующей."); if (statusContainer) statusContainer.style.display = 'block'; // Убедимся, что статус виден socket.emit('requestPvPGameList'); updateAvailableGamesList([]); if (gameIdInput) gameIdInput.value = ''; const elenaRadio = document.getElementById('char-elena'); if (elenaRadio) elenaRadio.checked = true; isInGame = false; disableGameControls(); resetGameVariables(); // Сбрасываем переменные игры при выходе на экран выбора игры } function showGameScreen() { console.log('[UI] Showing Game Screen'); hideGameOverModal(); if (authSection) authSection.style.display = 'none'; if (userInfoDiv) userInfoDiv.style.display = 'block'; if (gameSetupDiv) gameSetupDiv.style.display = 'none'; if (gameWrapper) gameWrapper.style.display = 'flex'; setGameStatusMessage(""); // Очищаем статус игры, т.к. теперь есть индикатор хода if (statusContainer) statusContainer.style.display = 'none'; // Скрываем статус контейнер в игре isInGame = true; disableGameControls(); // Отключаем кнопки изначально, updateUI их включит при ходе } // <--- НОВАЯ ФУНКЦИЯ ДЛЯ СБРОСА ИГРОВЫХ ПЕРЕМЕННЫХ --- function resetGameVariables() { currentGameId = null; currentGameState = null; myPlayerId = null; myCharacterKey = null; opponentCharacterKey = null; playerBaseStatsServer = null; opponentBaseStatsServer = null; playerAbilitiesServer = null; opponentAbilitiesServer = null; window.gameState = null; window.gameData = null; window.myPlayerId = null; // window.GAME_CONFIG = null; // Не сбрасываем, т.к. содержит общие вещи } // --- КОНЕЦ НОВОЙ ФУНКЦИИ --- function hideGameOverModal() { const hiddenClass = (window.GAME_CONFIG && window.GAME_CONFIG.CSS_CLASS_HIDDEN) ? window.GAME_CONFIG.CSS_CLASS_HIDDEN : 'hidden'; if (gameOverScreen && !gameOverScreen.classList.contains(hiddenClass)) { console.log('[Client.js DEBUG] Hiding GameOver Modal.'); gameOverScreen.classList.add(hiddenClass); if (window.gameUI?.uiElements?.gameOver?.modalContent) { window.gameUI.uiElements.gameOver.modalContent.style.transform = 'scale(0.8) translateY(30px)'; window.gameUI.uiElements.gameOver.modalContent.style.opacity = '0'; } if (window.gameUI?.uiElements?.opponent?.panel) { const opponentPanel = window.gameUI.uiElements.opponent.panel; if (opponentPanel.classList.contains('dissolving')) { console.log('[Client.js DEBUG] Removing .dissolving from opponent panel during hideGameOverModal.'); opponentPanel.classList.remove('dissolving'); opponentPanel.style.opacity = '1'; opponentPanel.style.transform = 'scale(1) translateY(0)'; } } } } function setAuthMessage(message, isError = false) { if (authMessage) { authMessage.textContent = message; authMessage.className = isError ? 'error' : 'success'; authMessage.style.display = message ? 'block' : 'none'; } // Скрываем gameStatusMessage, если показываем authMessage if (message && gameStatusMessage) { gameStatusMessage.style.display = 'none'; } } function setGameStatusMessage(message, isError = false) { if (gameStatusMessage) { gameStatusMessage.textContent = message; gameStatusMessage.style.display = message ? 'block' : 'none'; gameStatusMessage.style.color = isError ? 'var(--damage-color, red)' : 'var(--turn-color, yellow)'; if (statusContainer) statusContainer.style.display = message ? 'block' : 'none'; // Показываем контейнер статуса } // Скрываем authMessage, если показываем gameStatusMessage if (message && authMessage) { authMessage.style.display = 'none'; } } function getSelectedCharacterKey() { let selectedKey = 'elena'; if (pvpCharacterRadios) { pvpCharacterRadios.forEach(radio => { if (radio.checked) { selectedKey = radio.value; } }); } return selectedKey; } function enableGameControls(enableAttack = true, enableAbilities = true) { if (attackButton) attackButton.disabled = !enableAttack; if (abilitiesGrid) { const abilityButtonClass = window.GAME_CONFIG?.CSS_CLASS_ABILITY_BUTTON || 'ability-button'; abilitiesGrid.querySelectorAll(`.${abilityButtonClass}`).forEach(button => { button.disabled = !enableAbilities; }); } if (window.gameUI?.uiElements?.controls?.buttonBlock) window.gameUI.uiElements.controls.buttonBlock.disabled = true; } function disableGameControls() { enableGameControls(false, false); } // --- Инициализация кнопок и обработчиков --- if (registerForm) { registerForm.addEventListener('submit', (e) => { e.preventDefault(); const usernameInput = document.getElementById('register-username'); const passwordInput = document.getElementById('register-password'); if (usernameInput && passwordInput) { // Отключаем кнопки на время регистрации registerForm.querySelector('button').disabled = true; loginForm.querySelector('button').disabled = true; socket.emit('register', { username: usernameInput.value, password: passwordInput.value }); } else { setAuthMessage("Ошибка: поля ввода не найдены.", true); } }); } if (loginForm) { loginForm.addEventListener('submit', (e) => { e.preventDefault(); const usernameInput = document.getElementById('login-username'); const passwordInput = document.getElementById('login-password'); if (usernameInput && passwordInput) { // Отключаем кнопки на время логина registerForm.querySelector('button').disabled = true; loginForm.querySelector('button').disabled = true; socket.emit('login', { username: usernameInput.value, password: passwordInput.value }); } else { setAuthMessage("Ошибка: поля ввода не найдены.", true); } }); } if (logoutButton) { logoutButton.addEventListener('click', () => { // Отключаем кнопку выхода logoutButton.disabled = true; socket.emit('logout'); // Сброс состояния и UI происходит по событию logoutResponse или gameNotFound/gameEnded после logout // Пока просто сбрасываем флаги и показываем Auth, т.к. сервер не присылает специальный logoutResponse isLoggedIn = false; loggedInUsername = ''; resetGameVariables(); isInGame = false; disableGameControls(); showAuthScreen(); setGameStatusMessage("Вы вышли из системы."); // Используем gameStatusMessage для уведомления logoutButton.disabled = false; // Включаем кнопку после обработки (хотя она будет скрыта) }); } if (createAIGameButton) { createAIGameButton.addEventListener('click', () => { if (!isLoggedIn) { setGameStatusMessage("Пожалуйста, войдите, чтобы начать игру.", true); return; } // Отключаем кнопки настройки игры disableSetupButtons(); socket.emit('createGame', { mode: 'ai', characterKey: 'elena' }); setGameStatusMessage("Создание игры против AI..."); }); } if (createPvPGameButton) { createPvPGameButton.addEventListener('click', () => { if (!isLoggedIn) { setGameStatusMessage("Пожалуйста, войдите, чтобы начать игру.", true); return; } // Отключаем кнопки настройки игры disableSetupButtons(); const selectedCharacter = getSelectedCharacterKey(); socket.emit('createGame', { mode: 'pvp', characterKey: selectedCharacter }); setGameStatusMessage(`Создание PvP игры за ${selectedCharacter === 'elena' ? 'Елену' : 'Альмагест'}...`); }); } if (joinPvPGameButton && gameIdInput) { joinPvPGameButton.addEventListener('click', () => { if (!isLoggedIn) { setGameStatusMessage("Пожалуйста, войдите, чтобы присоединиться к игре.", true); return; } const gameIdToJoin = gameIdInput.value.trim(); if (gameIdToJoin) { // Отключаем кнопки настройки игры disableSetupButtons(); socket.emit('joinGame', { gameId: gameIdToJoin }); setGameStatusMessage(`Присоединение к игре ${gameIdToJoin}...`); } else { setGameStatusMessage("Пожалуйста, введите ID игры для присоединения.", true); } }); } if (findRandomPvPGameButton) { findRandomPvPGameButton.addEventListener('click', () => { if (!isLoggedIn) { setGameStatusMessage("Пожалуйста, войдите, чтобы найти игру.", true); return; } // Отключаем кнопки настройки игры disableSetupButtons(); const selectedCharacter = getSelectedCharacterKey(); socket.emit('findRandomGame', { characterKey: selectedCharacter }); setGameStatusMessage(`Поиск случайной PvP игры (предпочтение: ${selectedCharacter === 'elena' ? 'Елена' : 'Альмагест'})...`); }); } // Функция для отключения кнопок на экране настройки игры function disableSetupButtons() { if(createAIGameButton) createAIGameButton.disabled = true; if(createPvPGameButton) createPvPGameButton.disabled = true; if(joinPvPGameButton) joinPvPGameButton.disabled = true; if(findRandomPvPGameButton) findRandomPvPGameButton.disabled = true; if(availableGamesDiv) availableGamesDiv.querySelectorAll('button').forEach(btn => btn.disabled = true); } // Функция для включения кнопок на экране настройки игры (вызывается после получения списка игр или сброса состояния) function enableSetupButtons() { if(createAIGameButton) createAIGameButton.disabled = false; if(createPvPGameButton) createPvPGameButton.disabled = false; if(joinPvPGameButton) joinPvPGameButton.disabled = false; if(findRandomPvPGameButton) findRandomPvPGameButton.disabled = false; // Кнопки Join в списке игр включаются при обновлении списка (updateAvailableGamesList) } if (attackButton) { attackButton.addEventListener('click', () => { // Проверяем isInGame и другие флаги перед отправкой действия if (isLoggedIn && isInGame && currentGameId && currentGameState && !currentGameState.isGameOver) { socket.emit('playerAction', { actionType: 'attack' }); } else { console.warn('[Client] Попытка действия (атака) вне допустимого состояния игры. isLogged:', isLoggedIn, 'isInGame:', isInGame); disableGameControls(); // Гарантируем, что кнопки будут отключены // Если мы залогинены, но не в игре (isInGame=false), возможно, стоит вернуться в меню выбора игры if (isLoggedIn && !isInGame) showGameSelectionScreen(loggedInUsername); else if (!isLoggedIn) showAuthScreen(); } }); } function handleAbilityButtonClick(event) { const button = event.currentTarget; const abilityId = button.dataset.abilityId; // Проверяем isInGame и другие флаги перед отправкой действия if (isLoggedIn && isInGame && currentGameId && abilityId && currentGameState && !currentGameState.isGameOver) { socket.emit('playerAction', { actionType: 'ability', abilityId: abilityId }); } else { console.warn('[Client] Попытка действия (способность) вне допустимого состояния игры. isLogged:', isLoggedIn, 'isInGame:', isInGame); disableGameControls(); // Гарантируем, что кнопки будут отключены if (isLoggedIn && !isInGame) showGameSelectionScreen(loggedInUsername); else if (!isLoggedIn) showAuthScreen(); } } if (returnToMenuButton) { returnToMenuButton.addEventListener('click', () => { if (!isLoggedIn) { showAuthScreen(); // Если каким-то образом кнопка активна без логина return; } // Отключаем кнопку возврата в меню returnToMenuButton.disabled = true; console.log('[Client] Return to menu button clicked. Resetting game state and showing selection screen.'); // Сбрасываем все переменные состояния игры и глобальные ссылки resetGameVariables(); isInGame = false; disableGameControls(); // Убедимся, что игровые кнопки отключены hideGameOverModal(); // Убедимся, что модалка скрыта showGameSelectionScreen(loggedInUsername); // Возвращаемся на экран выбора игры // Кнопки настройки игры будут включены в showGameSelectionScreen / updateAvailableGamesList }); } function initializeAbilityButtons() { if (!abilitiesGrid || !window.gameUI || !window.GAME_CONFIG) { if(abilitiesGrid) abilitiesGrid.innerHTML = '

Ошибка загрузки способностей.

'; console.error('[Client.js] initializeAbilityButtons failed: abilitiesGrid, gameUI, or GAME_CONFIG not found.'); return; } abilitiesGrid.innerHTML = ''; const config = window.GAME_CONFIG; const abilitiesToDisplay = playerAbilitiesServer; const baseStatsForResource = playerBaseStatsServer; if (!abilitiesToDisplay || abilitiesToDisplay.length === 0 || !baseStatsForResource) { abilitiesGrid.innerHTML = '

Нет доступных способностей.

'; return; } const resourceName = baseStatsForResource.resourceName || "Ресурс"; const abilityButtonClass = config.CSS_CLASS_ABILITY_BUTTON || 'ability-button'; abilitiesToDisplay.forEach(ability => { const button = document.createElement('button'); button.id = `ability-btn-${ability.id}`; button.classList.add(abilityButtonClass); button.dataset.abilityId = ability.id; let descriptionText = ability.description; let cooldown = ability.cooldown; let cooldownText = ""; if (typeof cooldown === 'number' && cooldown > 0) { cooldownText = ` (КД: ${cooldown} х.)`; } let title = `${ability.name} (${ability.cost} ${resourceName})${cooldownText} - ${descriptionText || 'Нет описания'}`; button.setAttribute('title', title); const nameSpan = document.createElement('span'); nameSpan.classList.add('ability-name'); nameSpan.textContent = ability.name; button.appendChild(nameSpan); const descSpan = document.createElement('span'); descSpan.classList.add('ability-desc'); descSpan.textContent = `(${ability.cost} ${resourceName})`; button.appendChild(descSpan); const cdDisplay = document.createElement('span'); cdDisplay.classList.add('ability-cooldown-display'); cdDisplay.style.display = 'none'; button.appendChild(cdDisplay); button.addEventListener('click', handleAbilityButtonClick); abilitiesGrid.appendChild(button); }); const placeholder = abilitiesGrid.querySelector('.placeholder-text'); if (placeholder) placeholder.remove(); // Кнопки инициализированы, updateUI будет управлять их disabled состоянием } function updateAvailableGamesList(games) { if (!availableGamesDiv) return; availableGamesDiv.innerHTML = '

Доступные PvP игры:

'; if (games && games.length > 0) { const ul = document.createElement('ul'); games.forEach(game => { if (game && game.id) { const li = document.createElement('li'); li.textContent = `ID: ${game.id.substring(0, 8)}... - ${game.status || 'Ожидает игрока'}`; const joinBtn = document.createElement('button'); joinBtn.textContent = 'Присоединиться'; joinBtn.dataset.gameId = game.id; joinBtn.addEventListener('click', (e) => { if (!isLoggedIn) { setGameStatusMessage("Пожалуйста, войдите, чтобы присоединиться к игре.", true); return; } // Отключаем кнопки настройки игры перед присоединением disableSetupButtons(); socket.emit('joinGame', { gameId: e.target.dataset.gameId }); }); li.appendChild(joinBtn); ul.appendChild(li); } }); availableGamesDiv.appendChild(ul); // Включаем кнопки JOIN в списке availableGamesDiv.querySelectorAll('button').forEach(btn => btn.disabled = false); } else { availableGamesDiv.innerHTML += '

Нет доступных игр. Создайте свою!

'; } enableSetupButtons(); // Включаем основные кнопки создания игры после обновления списка } // --- Обработчики событий Socket.IO --- socket.on('connect', () => { console.log('[Client] Socket connected to server! Socket ID:', socket.id); // При подключении, если залогинен, запросить состояние игры. // Это нужно ТОЛЬКО для восстановления игры, если клиент был в игре и переподключился. if (isLoggedIn) { console.log(`[Client] Reconnected as ${loggedInUsername}. Requesting state.`); socket.emit('requestGameState'); } else { // Если не залогинен, показываем экран аутентификации showAuthScreen(); } }); // Обработка registerResponse - теперь включает включение кнопок форм socket.on('registerResponse', (data) => { setAuthMessage(data.message, !data.success); if (data.success && registerForm) registerForm.reset(); // Включаем кнопки форм обратно if(registerForm) registerForm.querySelector('button').disabled = false; if(loginForm) loginForm.querySelector('button').disabled = false; }); // Обработка loginResponse - Ключевое изменение здесь socket.on('loginResponse', (data) => { setAuthMessage(data.message, !data.success); if (data.success) { isLoggedIn = true; loggedInUsername = data.username; setAuthMessage(""); // Очищаем сообщение аутентификации // --- ИЗМЕНЕНИЕ: СРАЗУ ПОКАЗЫВАЕМ ЭКРАН ВЫБОРА ИГРЫ --- // Не ждем gameNotFound или gameState. Сразу переходим. showGameSelectionScreen(data.username); // enableSetupButtons() вызывается внутри showGameSelectionScreen / updateAvailableGamesList // --- КОНЕЦ ИЗМЕНЕНИЯ --- } else { isLoggedIn = false; loggedInUsername = ''; // Включаем кнопки форм обратно при ошибке логина if(registerForm) registerForm.querySelector('button').disabled = false; if(loginForm) loginForm.querySelector('button').disabled = false; } }); // gameNotFound теперь обрабатывается иначе для залогиненных vs не залогиненных socket.on('gameNotFound', (data) => { console.log('[Client] Game not found response:', data?.message); // Сбрасываем игровые переменные, если они были установлены (например, после дисконнекта в игре) resetGameVariables(); isInGame = false; disableGameControls(); // Убеждаемся, что игровые кнопки отключены hideGameOverModal(); // Убеждаемся, что модалка скрыта if (isLoggedIn) { // Если залогинен, и игра не найдена, это НОРМАЛЬНОЕ состояние, если он не был в игре. // Просто показываем экран выбора игры. Сообщение может быть информационным, а не ошибкой. showGameSelectionScreen(loggedInUsername); // Сообщение: "Игровая сессия не найдена" может быть показано, но как статус, не ошибка. // Можно сделать его менее тревожным или вовсе не показывать. // setGameStatusMessage(data?.message || "Активная игровая сессия не найдена.", false); // Информационный статус setGameStatusMessage("Выберите режим игры или присоединитесь к существующей."); // Сбрасываем на стандартное сообщение enableSetupButtons(); // Включаем кнопки настройки игры } else { // Если не залогинен и получил gameNotFound (что странно), сбрасываем и показываем логин showAuthScreen(); setAuthMessage(data?.message || "Пожалуйста, войдите, чтобы начать новую игру.", false); } }); socket.on('disconnect', (reason) => { console.log('[Client] Disconnected from server:', reason); setGameStatusMessage(`Отключено от сервера: ${reason}. Пожалуйста, обновите страницу.`, true); // Отключаем игровые кнопки, чтобы предотвратить отправку действий disableGameControls(); // НЕ сбрасываем игровые переменные немедленно. // Если мы были в игре (isInGame=true), возможно, сервер пришлет gameOver или gameNotFound позже. // Если game over придет, его обработчик покажет модалку и включит кнопку "В меню". // Если gameNotFound придет, его обработчик сбросит переменные и переключит UI. // Если ничего не придет, страница может зависнуть. // В продакшене тут может быть таймер на принудительный сброс и возврат в меню. // Если мы не были в игре (например, на экране выбора игры), просто показываем статус. if (!isInGame) { // Остаемся на текущем экране (выбора игры или логина) и показываем статус дисконнекта // UI уже настроен showGameSelectionScreen или showAuthScreen } }); // Обработка gameStarted - без изменений socket.on('gameStarted', (data) => { if (!isLoggedIn) { console.warn('[Client] Ignoring gameStarted: Not logged in.'); return; } console.log('[Client] Event "gameStarted" received:', data); if (window.gameUI?.uiElements?.opponent?.panel) { const opponentPanel = window.gameUI.uiElements.opponent.panel; if (opponentPanel.classList.contains('dissolving')) { console.log('[Client.js DEBUG] Removing .dissolving from opponent panel before new game start.'); opponentPanel.classList.remove('dissolving'); opponentPanel.style.opacity = '1'; opponentPanel.style.transform = 'scale(1) translateY(0)'; } } // Убедимся, что игровые переменные обновлены (на случай, если игра началась сразу после логина без requestGameState) currentGameId = data.gameId; myPlayerId = data.yourPlayerId; currentGameState = data.initialGameState; playerBaseStatsServer = data.playerBaseStats; opponentBaseStatsServer = data.opponentBaseStats; playerAbilitiesServer = data.playerAbilities; opponentAbilitiesServer = data.opponentAbilities; myCharacterKey = playerBaseStatsServer?.characterKey; opponentCharacterKey = opponentBaseStatsServer?.characterKey; if (data.clientConfig) { window.GAME_CONFIG = { ...data.clientConfig }; console.log('[Client.js gameStarted] Received clientConfig from server.'); } else if (!window.GAME_CONFIG) { window.GAME_CONFIG = { PLAYER_ID: 'player', OPPONENT_ID: 'opponent', CSS_CLASS_HIDDEN: 'hidden' }; console.warn('[Client.js gameStarted] No clientConfig received from server. Using fallback.'); } window.gameState = currentGameState; window.gameData = { playerBaseStats: playerBaseStatsServer, opponentBaseStats: opponentBaseStatsServer, playerAbilities: playerAbilitiesServer, opponentAbilities: opponentAbilitiesServer }; window.myPlayerId = myPlayerId; showGameScreen(); // Показываем игровой экран (ставит isInGame = true) initializeAbilityButtons(); // Инициализируем кнопки способностей if (window.gameUI?.uiElements?.log?.list) { window.gameUI.uiElements.log.list.innerHTML = ''; } if (window.gameUI && typeof window.gameUI.addToLog === 'function' && data.log) { data.log.forEach(logEntry => window.gameUI.addToLog(logEntry.message, logEntry.type)); } requestAnimationFrame(() => { if (window.gameUI && typeof window.gameUI.updateUI === 'function') { console.log('[Client] Calling gameUI.updateUI() after gameStarted.'); window.gameUI.updateUI(); } }); hideGameOverModal(); setGameStatusMessage(""); // Скрываем статус сообщение, если видим игровой экран }); // Обработка gameStateUpdate - без изменений (проверяет isLoggedIn и isInGame) socket.on('gameStateUpdate', (data) => { if (!isLoggedIn || !isInGame || !currentGameId || !window.GAME_CONFIG) { console.warn('[Client] Ignoring gameStateUpdate: Not logged in or not in game context.'); return; } currentGameState = data.gameState; window.gameState = currentGameState; if (window.gameUI && typeof window.gameUI.updateUI === 'function') { window.gameUI.updateUI(); } if (window.gameUI && typeof window.gameUI.addToLog === 'function' && data.log) { data.log.forEach(logEntry => window.gameUI.addToLog(logEntry.message, logEntry.type)); } }); // Обработка logUpdate - без изменений (проверяет isLoggedIn и isInGame) socket.on('logUpdate', (data) => { if (!isLoggedIn || !isInGame || !currentGameId || !window.GAME_CONFIG) { console.warn('[Client] Ignoring logUpdate: Not logged in or not in game context.'); return; } if (window.gameUI && typeof window.gameUI.addToLog === 'function' && data.log) { data.log.forEach(logEntry => window.gameUI.addToLog(logEntry.message, logEntry.type)); } }); // Обработка gameOver - без изменений (сбрасывает gameState в конце для UI, но переменные игры не сбрасывает сразу) socket.on('gameOver', (data) => { if (!isLoggedIn || !currentGameId || !window.GAME_CONFIG) { console.warn('[Client] Ignoring gameOver: Not logged in or currentGameId is null/stale.'); // Если игра окончена, но состояние клиента было некорректным, попробуем сбросить его if (!currentGameId && isLoggedIn) socket.emit('requestGameState'); // Попробуем запросить состояние else if (!isLoggedIn) showAuthScreen(); return; } console.log(`[Client gameOver] Received for game ${currentGameId}. My technical slot ID (myPlayerId): ${myPlayerId}, Winner's slot ID from server (data.winnerId): ${data.winnerId}`); const playerWon = data.winnerId === myPlayerId; console.log(`[Client gameOver] Calculated playerWon for this client: ${playerWon}`); currentGameState = data.finalGameState; window.gameState = currentGameState; console.log('[Client gameOver] Final GameState:', currentGameState); if (window.gameData) { console.log(`[Client gameOver] For ui.js, myName: ${window.gameData.playerBaseStats?.name}, opponentName: ${window.gameData.opponentBaseStats?.name}`); } if (window.gameUI && typeof window.gameUI.updateUI === 'function') { window.gameUI.updateUI(); } if (window.gameUI && typeof window.gameUI.addToLog === 'function' && data.log) { data.log.forEach(logEntry => window.gameUI.addToLog(logEntry.message, logEntry.type)); } if (window.gameUI && typeof window.gameUI.showGameOver === 'function') { const opponentKeyForModal = window.gameData?.opponentBaseStats?.characterKey; window.gameUI.showGameOver(playerWon, data.reason, opponentKeyForModal, data); } if (returnToMenuButton) { returnToMenuButton.disabled = false; // Включаем кнопку "В меню" в модалке } setGameStatusMessage("Игра окончена. " + (playerWon ? "Вы победили!" : "Вы проиграли.")); // isInGame остается true, пока не нажмут "В меню" // disableGameControls() уже вызвано через updateUI из-за isGameOver }); // Обработка waitingForOpponent - без изменений socket.on('waitingForOpponent', () => { if (!isLoggedIn) return; setGameStatusMessage("Ожидание присоединения оппонента..."); disableGameControls(); // Отключаем кнопки, пока ждем // Включаем кнопки настройки игры после попытки создания/присоединения к ожидающей игре // чтобы игрок мог отменить или попробовать другое enableSetupButtons(); // Однако, если игрок создал игру, кнопки "Создать" должны быть отключены, // а если он искал и создал, то тоже. // Возможно, лучше отключать кнопки создания/поиска, оставляя только "Присоединиться" по ID или отмену. // Для простоты пока включаем все, кроме кнопок боя. // disableSetupButtons(); // Лучше оставить их отключенными до gameStarted или gameNotFound }); // Обработка opponentDisconnected - без изменений (проверяет isLoggedIn и isInGame) socket.on('opponentDisconnected', (data) => { if (!isLoggedIn || !isInGame || !currentGameId || !window.GAME_CONFIG) { console.warn('[Client] Ignoring opponentDisconnected: Not logged in or not in game context.'); return; } const systemLogType = (window.GAME_CONFIG?.LOG_TYPE_SYSTEM) || 'system'; const disconnectedCharacterName = data.disconnectedCharacterName || 'Противник'; const disconnectedCharacterKey = data.disconnectedCharacterKey || 'unknown'; if (window.gameUI && typeof window.gameUI.addToLog === 'function') { window.gameUI.addToLog(`🔌 Противник (${disconnectedCharacterName}) отключился.`, systemLogType); } if (currentGameState && !currentGameState.isGameOver) { setGameStatusMessage(`Противник (${disconnectedCharacterName}) отключился. Ожидание завершения игры сервером...`, true); disableGameControls(); // Отключаем кнопки немедленно } }); // Обработка gameError - без изменений socket.on('gameError', (data) => { console.error('[Client] Server error:', data.message); const systemLogType = (window.GAME_CONFIG?.LOG_TYPE_SYSTEM) || 'system'; // Если в игре, добавляем в лог и отключаем кнопки if (isLoggedIn && isInGame && currentGameId && currentGameState && !currentGameState.isGameOver && window.gameUI && typeof window.gameUI.addToLog === 'function') { window.gameUI.addToLog(`❌ Ошибка игры: ${data.message}`, systemLogType); disableGameControls(); // Отключаем кнопки при ошибке setGameStatusMessage(`Ошибка в игре: ${data.message}.`, true); // Возможно, тут нужно вернуть игрока в меню после небольшой задержки? // setTimeout(() => { // if (isLoggedIn && isInGame) { // Проверяем, что все еще в игре после задержки // alert("Произошла ошибка. Вы будете возвращены в меню выбора игры."); // Сообщение пользователю // // Симулируем нажатие кнопки "В меню" // if (returnToMenuButton && !returnToMenuButton.disabled) { // returnToMenuButton.click(); // } else { // // Если кнопка "В меню" отключена или не найдена, сбрасываем вручную // resetGameVariables(); isInGame = false; showGameSelectionScreen(loggedInUsername); // } // } // }, 3000); // Задержка перед возвратом } else { // Ошибка вне контекста игры setGameStatusMessage(`❌ Ошибка игры: ${data.message}`, true); // Сбрасываем состояние, если ошибка пришла не в игре resetGameVariables(); isInGame = false; disableGameControls(); if(isLoggedIn && loggedInUsername) { showGameSelectionScreen(loggedInUsername); // Возвращаемся на экран выбора игры } else { showAuthScreen(); // Возвращаемся на экран логина } } // Включаем кнопки форм/настройки игры после обработки ошибки if (!isLoggedIn) { // Если на экране логина if(registerForm) registerForm.querySelector('button').disabled = false; if(loginForm) loginForm.querySelector('button').disabled = false; } else if (!isInGame) { // Если на экране выбора игры enableSetupButtons(); } }); socket.on('availablePvPGamesList', (games) => { if (!isLoggedIn) return; updateAvailableGamesList(games); // updateAvailableGamesList включает кнопки Join и основные кнопки создания }); socket.on('noPendingGamesFound', (data) => { if (!isLoggedIn) return; // Это информационное сообщение, когда игрок искал игру и создал новую // currentGameId и myPlayerId должны быть установлены событием 'gameCreated' setGameStatusMessage(data.message || "Свободных игр не найдено. Создана новая для вас, ожидайте оппонента."); updateAvailableGamesList([]); // Очищаем список, т.к. мы теперь в ожидающей игре isInGame = false; // Пока ждем, не в активной игре disableGameControls(); // Кнопки боя отключены // Кнопки настройки игры должны оставаться отключенными, пока ждем игрока disableSetupButtons(); }); // --- Изначальное состояние UI при загрузке страницы --- // При загрузке страницы всегда начинаем с Auth. showAuthScreen(); });