bc/public/js/client.js

798 lines
45 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// /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 = '<p class="placeholder-text">Ошибка загрузки способностей.</p>';
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 = '<p class="placeholder-text">Нет доступных способностей.</p>';
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 = '<h3>Доступные PvP игры:</h3>';
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 += '<p>Нет доступных игр. Создайте свою!</p>';
}
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();
});