Победа!
-
diff --git a/public/js/client.js b/public/js/client.js
index 3c42a43..3f729fd 100644
--- a/public/js/client.js
+++ b/public/js/client.js
@@ -7,17 +7,17 @@ document.addEventListener('DOMContentLoaded', () => {
// --- Состояние клиента ---
let currentGameState = null;
- let myPlayerId = null; // Технический ID слота, который занимает ЭТОТ клиент ('player' или 'opponent')
+ let myPlayerId = null;
let myCharacterKey = null;
let opponentCharacterKey = null;
let currentGameId = null;
- let playerBaseStatsServer = null; // Статы персонажа, которым УПРАВЛЯЕТ этот клиент (приходят от сервера как data.playerBaseStats)
- let opponentBaseStatsServer = null; // Статы персонажа-оппонента этого клиента (приходят от сервера как data.opponentBaseStats)
+ let playerBaseStatsServer = null;
+ let opponentBaseStatsServer = null;
let playerAbilitiesServer = null;
let opponentAbilitiesServer = null;
let isLoggedIn = false;
let loggedInUsername = '';
- let isInGame = false; // ФЛАГ СОСТОЯНИЯ ИГРЫ
+ let isInGame = false;
// --- DOM Элементы ---
// Аутентификация
@@ -25,7 +25,7 @@ document.addEventListener('DOMContentLoaded', () => {
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 statusContainer = document.getElementById('status-container');
const userInfoDiv = document.getElementById('user-info');
const loggedInUsernameSpan = document.getElementById('logged-in-username');
const logoutButton = document.getElementById('logout-button');
@@ -34,7 +34,7 @@ document.addEventListener('DOMContentLoaded', () => {
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 joinPvPGameButton = document.getElementById('join-pvP-game'); // Опечатка в ID, должно быть 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');
@@ -48,6 +48,10 @@ document.addEventListener('DOMContentLoaded', () => {
const gameOverScreen = document.getElementById('game-over-screen');
const abilitiesGrid = document.getElementById('abilities-grid');
+ // === ИЗМЕНЕНИЕ: DOM элемент для таймера ===
+ const turnTimerSpan = document.getElementById('turn-timer'); // Элемент для отображения времени
+ const turnTimerContainer = document.getElementById('turn-timer-container'); // Контейнер таймера для управления видимостью
+ // === КОНЕЦ ИЗМЕНЕНИЯ ===
console.log('Client.js DOMContentLoaded. Initializing elements...');
@@ -60,12 +64,15 @@ document.addEventListener('DOMContentLoaded', () => {
if (gameSetupDiv) gameSetupDiv.style.display = 'none';
if (gameWrapper) gameWrapper.style.display = 'none';
hideGameOverModal();
- // setGameStatusMessage("Войдите или зарегистрируйтесь для начала игры."); // Это сообщение перенесено в setAuthMessage/начальный статус
- setAuthMessage("Ожидание подключения к серверу..."); // Начальный статус
- if (statusContainer) statusContainer.style.display = 'block'; // Убедимся, что статус виден
+ setAuthMessage("Ожидание подключения к серверу...");
+ if (statusContainer) statusContainer.style.display = 'block';
isInGame = false;
disableGameControls();
- resetGameVariables(); // Сбрасываем переменные игры при выходе на экран логина
+ resetGameVariables();
+ // === ИЗМЕНЕНИЕ: Скрываем таймер при выходе на экран аутентификации ===
+ if (turnTimerContainer) turnTimerContainer.style.display = 'none';
+ if (turnTimerSpan) turnTimerSpan.textContent = '--';
+ // === КОНЕЦ ИЗМЕНЕНИЯ ===
}
function showGameSelectionScreen(username) {
@@ -73,13 +80,13 @@ document.addEventListener('DOMContentLoaded', () => {
if (authSection) authSection.style.display = 'none';
if (userInfoDiv) {
userInfoDiv.style.display = 'block';
- if(loggedInUsernameSpan) loggedInUsernameSpan.textContent = username;
+ 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'; // Убедимся, что статус виден
+ if (statusContainer) statusContainer.style.display = 'block';
socket.emit('requestPvPGameList');
updateAvailableGamesList([]);
if (gameIdInput) gameIdInput.value = '';
@@ -87,23 +94,30 @@ document.addEventListener('DOMContentLoaded', () => {
if (elenaRadio) elenaRadio.checked = true;
isInGame = false;
disableGameControls();
- resetGameVariables(); // Сбрасываем переменные игры при выходе на экран выбора игры
+ resetGameVariables();
+ // === ИЗМЕНЕНИЕ: Скрываем таймер при выходе на экран выбора игры ===
+ if (turnTimerContainer) turnTimerContainer.style.display = 'none';
+ if (turnTimerSpan) turnTimerSpan.textContent = '--';
+ // === КОНЕЦ ИЗМЕНЕНИЯ ===
}
function showGameScreen() {
console.log('[UI] Showing Game Screen');
hideGameOverModal();
if (authSection) authSection.style.display = 'none';
- if (userInfoDiv) userInfoDiv.style.display = 'block';
+ if (userInfoDiv) userInfoDiv.style.display = 'block'; // Оставляем видимым, чтобы видеть "Привет, username"
if (gameSetupDiv) gameSetupDiv.style.display = 'none';
if (gameWrapper) gameWrapper.style.display = 'flex';
- setGameStatusMessage(""); // Очищаем статус игры, т.к. теперь есть индикатор хода
- if (statusContainer) statusContainer.style.display = 'none'; // Скрываем статус контейнер в игре
+ setGameStatusMessage("");
+ if (statusContainer) statusContainer.style.display = 'none';
isInGame = true;
- disableGameControls(); // Отключаем кнопки изначально, updateUI их включит при ходе
+ disableGameControls();
+ // === ИЗМЕНЕНИЕ: Показываем контейнер таймера, когда игра начинается ===
+ if (turnTimerContainer) turnTimerContainer.style.display = 'block';
+ if (turnTimerSpan) turnTimerSpan.textContent = '--'; // Начальное значение
+ // === КОНЕЦ ИЗМЕНЕНИЯ ===
}
- // <--- НОВАЯ ФУНКЦИЯ ДЛЯ СБРОСА ИГРОВЫХ ПЕРЕМЕННЫХ ---
function resetGameVariables() {
currentGameId = null;
currentGameState = null;
@@ -114,14 +128,10 @@ document.addEventListener('DOMContentLoaded', () => {
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';
@@ -135,7 +145,6 @@ document.addEventListener('DOMContentLoaded', () => {
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)';
@@ -150,10 +159,7 @@ document.addEventListener('DOMContentLoaded', () => {
authMessage.className = isError ? 'error' : 'success';
authMessage.style.display = message ? 'block' : 'none';
}
- // Скрываем gameStatusMessage, если показываем authMessage
- if (message && gameStatusMessage) {
- gameStatusMessage.style.display = 'none';
- }
+ if (message && gameStatusMessage) gameStatusMessage.style.display = 'none';
}
function setGameStatusMessage(message, isError = false) {
@@ -161,22 +167,15 @@ document.addEventListener('DOMContentLoaded', () => {
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';
+ if (statusContainer) statusContainer.style.display = message ? 'block' : 'none';
}
+ if (message && authMessage) authMessage.style.display = 'none';
}
function getSelectedCharacterKey() {
let selectedKey = 'elena';
if (pvpCharacterRadios) {
- pvpCharacterRadios.forEach(radio => {
- if (radio.checked) {
- selectedKey = radio.value;
- }
- });
+ pvpCharacterRadios.forEach(radio => { if (radio.checked) selectedKey = radio.value; });
}
return selectedKey;
}
@@ -185,9 +184,7 @@ document.addEventListener('DOMContentLoaded', () => {
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;
- });
+ abilitiesGrid.querySelectorAll(`.${abilityButtonClass}`).forEach(button => { button.disabled = !enableAbilities; });
}
if (window.gameUI?.uiElements?.controls?.buttonBlock) window.gameUI.uiElements.controls.buttonBlock.disabled = true;
}
@@ -196,64 +193,45 @@ document.addEventListener('DOMContentLoaded', () => {
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;
+ if (loginForm) loginForm.querySelector('button').disabled = true;
socket.emit('register', { username: usernameInput.value, password: passwordInput.value });
- } else {
- setAuthMessage("Ошибка: поля ввода не найдены.", true);
- }
+ } 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;
+ if (registerForm) registerForm.querySelector('button').disabled = true;
loginForm.querySelector('button').disabled = true;
socket.emit('login', { username: usernameInput.value, password: passwordInput.value });
- } else {
- setAuthMessage("Ошибка: поля ввода не найдены.", true);
- }
+ } 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();
+ resetGameVariables(); isInGame = false; disableGameControls();
showAuthScreen();
- setGameStatusMessage("Вы вышли из системы."); // Используем gameStatusMessage для уведомления
- logoutButton.disabled = false; // Включаем кнопку после обработки (хотя она будет скрыта)
+ setGameStatusMessage("Вы вышли из системы.");
+ logoutButton.disabled = false;
});
}
-
if (createAIGameButton) {
createAIGameButton.addEventListener('click', () => {
- if (!isLoggedIn) {
- setGameStatusMessage("Пожалуйста, войдите, чтобы начать игру.", true); return;
- }
- // Отключаем кнопки настройки игры
+ if (!isLoggedIn) { setGameStatusMessage("Пожалуйста, войдите, чтобы начать игру.", true); return; }
disableSetupButtons();
socket.emit('createGame', { mode: 'ai', characterKey: 'elena' });
setGameStatusMessage("Создание игры против AI...");
@@ -261,121 +239,91 @@ document.addEventListener('DOMContentLoaded', () => {
}
if (createPvPGameButton) {
createPvPGameButton.addEventListener('click', () => {
- if (!isLoggedIn) {
- setGameStatusMessage("Пожалуйста, войдите, чтобы начать игру.", true); return;
- }
- // Отключаем кнопки настройки игры
+ 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;
- }
+ // Исправляем селектор для joinPvPGameButton, если ID в HTML был join-pvP-game
+ const actualJoinPvPGameButton = document.getElementById('join-pvp-game') || document.getElementById('join-pvP-game');
+ if (actualJoinPvPGameButton && gameIdInput) {
+ actualJoinPvPGameButton.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);
- }
+ } else { setGameStatusMessage("Пожалуйста, введите ID игры для присоединения.", true); }
});
}
if (findRandomPvPGameButton) {
findRandomPvPGameButton.addEventListener('click', () => {
- if (!isLoggedIn) {
- setGameStatusMessage("Пожалуйста, войдите, чтобы найти игру.", true); return;
- }
- // Отключаем кнопки настройки игры
+ 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);
+ if (createAIGameButton) createAIGameButton.disabled = true;
+ if (createPvPGameButton) createPvPGameButton.disabled = true;
+ if (actualJoinPvPGameButton) actualJoinPvPGameButton.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 (createAIGameButton) createAIGameButton.disabled = false;
+ if (createPvPGameButton) createPvPGameButton.disabled = false;
+ if (actualJoinPvPGameButton) actualJoinPvPGameButton.disabled = false;
+ if (findRandomPvPGameButton) findRandomPvPGameButton.disabled = false;
}
-
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), возможно, стоит вернуться в меню выбора игры
+ disableGameControls();
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(); // Гарантируем, что кнопки будут отключены
+ disableGameControls();
if (isLoggedIn && !isInGame) showGameSelectionScreen(loggedInUsername);
else if (!isLoggedIn) showAuthScreen();
}
}
-
if (returnToMenuButton) {
returnToMenuButton.addEventListener('click', () => {
- if (!isLoggedIn) {
- showAuthScreen(); // Если каким-то образом кнопка активна без логина
- return;
- }
- // Отключаем кнопку возврата в меню
+ 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
+ resetGameVariables(); isInGame = false; disableGameControls(); hideGameOverModal();
+ showGameSelectionScreen(loggedInUsername);
});
}
function initializeAbilityButtons() {
if (!abilitiesGrid || !window.gameUI || !window.GAME_CONFIG) {
- if(abilitiesGrid) abilitiesGrid.innerHTML = '
Ошибка загрузки способностей.
';
+ 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;
@@ -391,41 +339,18 @@ document.addEventListener('DOMContentLoaded', () => {
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 || 'Нет описания'}`;
+ let cooldownText = (typeof cooldown === 'number' && cooldown > 0) ? ` (КД: ${cooldown} х.)` : "";
+ let title = `${ability.name} (${ability.cost} ${resourceName})${cooldownText} - ${ability.description || 'Нет описания'}`;
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);
-
+ 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) {
@@ -438,208 +363,125 @@ document.addEventListener('DOMContentLoaded', () => {
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.textContent = 'Присоединиться'; joinBtn.dataset.gameId = game.id;
joinBtn.addEventListener('click', (e) => {
- if (!isLoggedIn) {
- setGameStatusMessage("Пожалуйста, войдите, чтобы присоединиться к игре.", true); return;
- }
- // Отключаем кнопки настройки игры перед присоединением
+ if (!isLoggedIn) { setGameStatusMessage("Пожалуйста, войдите, чтобы присоединиться к игре.", true); return; }
disableSetupButtons();
socket.emit('joinGame', { gameId: e.target.dataset.gameId });
});
- li.appendChild(joinBtn);
- ul.appendChild(li);
+ li.appendChild(joinBtn); ul.appendChild(li);
}
});
availableGamesDiv.appendChild(ul);
- // Включаем кнопки JOIN в списке
availableGamesDiv.querySelectorAll('button').forEach(btn => btn.disabled = false);
- } else {
- availableGamesDiv.innerHTML += '
Нет доступных игр. Создайте свою!
';
- }
- enableSetupButtons(); // Включаем основные кнопки создания игры после обновления списка
+ } 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();
- }
+ } 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;
+ 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. Сразу переходим.
+ isLoggedIn = true; loggedInUsername = data.username; setAuthMessage("");
showGameSelectionScreen(data.username);
- // enableSetupButtons() вызывается внутри showGameSelectionScreen / updateAvailableGamesList
- // --- КОНЕЦ ИЗМЕНЕНИЯ ---
-
} else {
- isLoggedIn = false;
- loggedInUsername = '';
- // Включаем кнопки форм обратно при ошибке логина
- if(registerForm) registerForm.querySelector('button').disabled = false;
- if(loginForm) loginForm.querySelector('button').disabled = false;
+ 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(); // Убеждаемся, что модалка скрыта
+ resetGameVariables(); isInGame = false; disableGameControls(); hideGameOverModal();
+ if (turnTimerContainer) turnTimerContainer.style.display = 'none'; // Скрываем таймер
if (isLoggedIn) {
- // Если залогинен, и игра не найдена, это НОРМАЛЬНОЕ состояние, если он не был в игре.
- // Просто показываем экран выбора игры. Сообщение может быть информационным, а не ошибкой.
showGameSelectionScreen(loggedInUsername);
- // Сообщение: "Игровая сессия не найдена" может быть показано, но как статус, не ошибка.
- // Можно сделать его менее тревожным или вовсе не показывать.
- // setGameStatusMessage(data?.message || "Активная игровая сессия не найдена.", false); // Информационный статус
- setGameStatusMessage("Выберите режим игры или присоединитесь к существующей."); // Сбрасываем на стандартное сообщение
- enableSetupButtons(); // Включаем кнопки настройки игры
+ 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
- }
+ // === ИЗМЕНЕНИЕ: При дисконнекте останавливаем таймер (если он виден) ===
+ if (turnTimerSpan) turnTimerSpan.textContent = 'Отключено';
+ // Не скрываем контейнер, чтобы было видно сообщение "Отключено"
+ // === КОНЕЦ ИЗМЕНЕНИЯ ===
});
- // Обработка gameStarted - без изменений
socket.on('gameStarted', (data) => {
- if (!isLoggedIn) {
- console.warn('[Client] Ignoring gameStarted: Not logged in.');
- return;
- }
+ 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)';
+ opponentPanel.style.opacity = '1'; opponentPanel.style.transform = 'scale(1) translateY(0)';
}
}
+ 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;
- // Убедимся, что игровые переменные обновлены (на случай, если игра началась сразу после логина без 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) {
+ if (data.clientConfig) window.GAME_CONFIG = { ...data.clientConfig };
+ 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.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 = '';
- }
+ showGameScreen(); 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(""); // Скрываем статус сообщение, если видим игровой экран
+ 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();
- }
+ 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.');
@@ -650,66 +492,46 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
- // Обработка 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'); // Попробуем запросить состояние
+ 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;
-
+ 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.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; // Включаем кнопку "В меню" в модалке
- }
+ if (returnToMenuButton) returnToMenuButton.disabled = false;
setGameStatusMessage("Игра окончена. " + (playerWon ? "Вы победили!" : "Вы проиграли."));
-
- // isInGame остается true, пока не нажмут "В меню"
- // disableGameControls() уже вызвано через updateUI из-за isGameOver
+ // === ИЗМЕНЕНИЕ: При gameOver скрываем таймер или показываем "Игра окончена" ===
+ if (turnTimerContainer) turnTimerContainer.style.display = 'block'; // Оставляем видимым
+ if (turnTimerSpan) turnTimerSpan.textContent = 'Конец';
+ // === КОНЕЦ ИЗМЕНЕНИЯ ===
});
- // Обработка waitingForOpponent - без изменений
socket.on('waitingForOpponent', () => {
if (!isLoggedIn) return;
setGameStatusMessage("Ожидание присоединения оппонента...");
- disableGameControls(); // Отключаем кнопки, пока ждем
- // Включаем кнопки настройки игры после попытки создания/присоединения к ожидающей игре
- // чтобы игрок мог отменить или попробовать другое
- enableSetupButtons();
- // Однако, если игрок создал игру, кнопки "Создать" должны быть отключены,
- // а если он искал и создал, то тоже.
- // Возможно, лучше отключать кнопки создания/поиска, оставляя только "Присоединиться" по ID или отмену.
- // Для простоты пока включаем все, кроме кнопок боя.
- // disableSetupButtons(); // Лучше оставить их отключенными до gameStarted или gameNotFound
+ disableGameControls();
+ enableSetupButtons(); // Можно оставить возможность отменить, если долго ждет
+ // === ИЗМЕНЕНИЕ: При ожидании оппонента таймер неактивен ===
+ if (turnTimerContainer) turnTimerContainer.style.display = 'none';
+ if (turnTimerSpan) turnTimerSpan.textContent = '--';
+ // === КОНЕЦ ИЗМЕНЕНИЯ ===
});
- // Обработка 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.');
@@ -717,84 +539,91 @@ document.addEventListener('DOMContentLoaded', () => {
}
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(); // Отключаем кнопки немедленно
+ 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();
+ setGameStatusMessage(`Ошибка в игре: ${data.message}.`, true);
+ } 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 и основные кнопки создания
+ updateAvailableGamesList(games);
});
socket.on('noPendingGamesFound', (data) => {
if (!isLoggedIn) return;
- // Это информационное сообщение, когда игрок искал игру и создал новую
- // currentGameId и myPlayerId должны быть установлены событием 'gameCreated'
setGameStatusMessage(data.message || "Свободных игр не найдено. Создана новая для вас, ожидайте оппонента.");
- updateAvailableGamesList([]); // Очищаем список, т.к. мы теперь в ожидающей игре
- isInGame = false; // Пока ждем, не в активной игре
- disableGameControls(); // Кнопки боя отключены
- // Кнопки настройки игры должны оставаться отключенными, пока ждем игрока
- disableSetupButtons();
+ updateAvailableGamesList([]);
+ isInGame = false; disableGameControls(); disableSetupButtons();
+ // === ИЗМЕНЕНИЕ: При ожидании оппонента (создана новая игра) таймер неактивен ===
+ if (turnTimerContainer) turnTimerContainer.style.display = 'none';
+ if (turnTimerSpan) turnTimerSpan.textContent = '--';
+ // === КОНЕЦ ИЗМЕНЕНИЯ ===
});
+ // === ИЗМЕНЕНИЕ: Обработчик события обновления таймера ===
+ socket.on('turnTimerUpdate', (data) => {
+ if (!isInGame || !currentGameState || currentGameState.isGameOver) {
+ // Если игра не активна, или уже завершена, или нет состояния, игнорируем обновление таймера
+ if (turnTimerContainer && !currentGameState?.isGameOver) turnTimerContainer.style.display = 'none'; // Скрываем, если не game over
+ if (turnTimerSpan && !currentGameState?.isGameOver) turnTimerSpan.textContent = '--';
+ return;
+ }
+
+ if (turnTimerSpan && turnTimerContainer) {
+ if (data.remainingTime === null || data.remainingTime === undefined) {
+ // Сервер сигнализирует, что таймер неактивен (например, ход AI)
+ turnTimerContainer.style.display = 'block'; // Контейнер может быть видимым
+ // Определяем, чей ход, чтобы показать соответствующее сообщение
+ const isMyActualTurn = myPlayerId && currentGameState.isPlayerTurn === (myPlayerId === GAME_CONFIG.PLAYER_ID);
+
+ if (!data.isPlayerTurn && currentGameState.gameMode === 'ai') { // Ход AI
+ turnTimerSpan.textContent = 'Ход ИИ';
+ turnTimerSpan.classList.remove('low-time');
+ } else if (!isMyActualTurn && currentGameState.gameMode === 'pvp' && !data.isPlayerTurn !== (myPlayerId === GAME_CONFIG.PLAYER_ID)) { // Ход оппонента в PvP
+ turnTimerSpan.textContent = 'Ход оппонента';
+ turnTimerSpan.classList.remove('low-time');
+ } else { // Ход текущего игрока, но сервер прислал null - странно, но покажем '--'
+ turnTimerSpan.textContent = '--';
+ turnTimerSpan.classList.remove('low-time');
+ }
+ } else {
+ turnTimerContainer.style.display = 'block'; // Убедимся, что контейнер виден
+ const seconds = Math.ceil(data.remainingTime / 1000);
+ turnTimerSpan.textContent = `0:${seconds < 10 ? '0' : ''}${seconds}`;
+
+ // Добавляем/удаляем класс для предупреждения, если времени мало
+ if (seconds <= 10) { // Например, 10 секунд - порог
+ turnTimerSpan.classList.add('low-time');
+ } else {
+ turnTimerSpan.classList.remove('low-time');
+ }
+ }
+ }
+ });
+ // === КОНЕЦ ИЗМЕНЕНИЯ ===
- // --- Изначальное состояние UI при загрузке страницы ---
- // При загрузке страницы всегда начинаем с Auth.
showAuthScreen();
});
\ No newline at end of file
diff --git a/public/js/ui.js b/public/js/ui.js
index ae35db4..a96d57e 100644
--- a/public/js/ui.js
+++ b/public/js/ui.js
@@ -5,7 +5,7 @@
(function() {
// --- DOM Элементы ---
const uiElements = {
- player: { // Панель для персонажа, которым управляет ЭТОТ клиент
+ player: {
panel: document.getElementById('player-panel'),
name: document.getElementById('player-name'),
avatar: document.getElementById('player-panel')?.querySelector('.player-avatar'),
@@ -14,10 +14,9 @@
status: document.getElementById('player-status'),
effectsContainer: document.getElementById('player-effects'),
buffsList: document.getElementById('player-effects')?.querySelector('.player-buffs'),
- // ИСПРАВЛЕНО: Селектор для списка дебаффов игрока
debuffsList: document.getElementById('player-effects')?.querySelector('.player-debuffs')
},
- opponent: { // Панель для персонажа-противника ЭТОГО клиента
+ opponent: {
panel: document.getElementById('opponent-panel'),
name: document.getElementById('opponent-name'),
avatar: document.getElementById('opponent-panel')?.querySelector('.opponent-avatar'),
@@ -25,16 +24,18 @@
resourceFill: document.getElementById('opponent-resource-fill'), resourceText: document.getElementById('opponent-resource-text'),
status: document.getElementById('opponent-status'),
effectsContainer: document.getElementById('opponent-effects'),
- // ИСПРАВЛЕНО: Селектор для списка баффов оппонента
buffsList: document.getElementById('opponent-effects')?.querySelector('.opponent-buffs'),
- // ИСПРАВЛЕНО: Селектор для списка дебаффов оппонента
debuffsList: document.getElementById('opponent-effects')?.querySelector('.opponent-debuffs')
},
controls: {
turnIndicator: document.getElementById('turn-indicator'),
buttonAttack: document.getElementById('button-attack'),
- buttonBlock: document.getElementById('button-block'), // Защита пока не активна
+ buttonBlock: document.getElementById('button-block'),
abilitiesGrid: document.getElementById('abilities-grid'),
+ // === ИЗМЕНЕНИЕ: Добавлены элементы таймера ===
+ turnTimerContainer: document.getElementById('turn-timer-container'),
+ turnTimerSpan: document.getElementById('turn-timer')
+ // === КОНЕЦ ИЗМЕНЕНИЯ ===
},
log: {
list: document.getElementById('log-list'),
@@ -58,26 +59,17 @@
const li = document.createElement('li');
li.textContent = message;
const config = window.GAME_CONFIG || {};
- // Формируем класс для лога на основе типа (используем константы из конфига или фоллбэк)
const logTypeClass = config[`LOG_TYPE_${type.toUpperCase()}`] ? `log-${config[`LOG_TYPE_${type.toUpperCase()}`]}` : `log-${type}`;
li.className = logTypeClass;
logListElement.appendChild(li);
- // Прокрутка лога вниз
requestAnimationFrame(() => { logListElement.scrollTop = logListElement.scrollHeight; });
}
function updateFighterPanelUI(panelRole, fighterState, fighterBaseStats, isControlledByThisClient) {
- const elements = uiElements[panelRole]; // 'player' или 'opponent'
+ const elements = uiElements[panelRole];
const config = window.GAME_CONFIG || {};
- // Базовая проверка наличия необходимых элементов и данных
if (!elements || !elements.hpFill || !elements.hpText || !elements.resourceFill || !elements.resourceText || !elements.status || !fighterState || !fighterBaseStats) {
- // Если панель должна быть видима, но нет данных, можно ее скрыть или показать плейсхолдер
- if (elements && elements.panel && elements.panel.style.display !== 'none') {
- // console.warn(`updateFighterPanelUI: Нет данных для видимой панели ${panelRole}.`);
- // elements.panel.style.opacity = '0.5'; // Пример: сделать полупрозрачной, если нет данных
- }
- // ВАЖНО: Очистить содержимое панели, если данных нет.
if (elements) {
if(elements.name) elements.name.innerHTML = (panelRole === 'player') ? '
Ожидание данных...' : '
Ожидание игрока...';
if(elements.hpText) elements.hpText.textContent = 'N/A';
@@ -90,33 +82,25 @@
if(panelRole === 'opponent' && uiElements.opponentResourceTypeIcon) uiElements.opponentResourceTypeIcon.className = 'fas fa-question';
if(panelRole === 'player' && uiElements.playerResourceBarContainer) uiElements.playerResourceBarContainer.classList.remove('mana', 'stamina', 'dark-energy');
if(panelRole === 'opponent' && uiElements.opponentResourceBarContainer) uiElements.opponentResourceBarContainer.classList.remove('mana', 'stamina', 'dark-energy');
- if(elements.panel) elements.panel.style.opacity = '0.5'; // Затемняем
+ if(elements.panel) elements.panel.style.opacity = '0.5';
}
return;
}
- if (elements.panel) elements.panel.style.opacity = '1'; // Делаем видимой, если данные есть
+ if (elements.panel) elements.panel.style.opacity = '1';
-
- // Обновление имени и иконки персонажа
if (elements.name) {
- let iconClass = 'fa-question'; // Иконка по умолчанию
+ let iconClass = 'fa-question';
const characterKey = fighterBaseStats.characterKey;
-
- // Определяем класс иконки в зависимости от персонажа
- if (characterKey === 'elena') { iconClass = 'fa-hat-wizard icon-player'; }
+ if (characterKey === 'elena') { iconClass = 'fa-hat-wizard icon-elena'; } // Используем специфичный класс для цвета
else if (characterKey === 'almagest') { iconClass = 'fa-staff-aesculapius icon-almagest'; }
- else if (characterKey === 'balard') { iconClass = 'fa-khanda icon-opponent'; }
- else { /* console.warn(`updateFighterPanelUI: Неизвестный characterKey "${characterKey}" для иконки имени.`); */ }
-
+ else if (characterKey === 'balard') { iconClass = 'fa-khanda icon-balard'; } // Для Баларда тоже специфичный
let nameHtml = `
${fighterBaseStats.name || 'Неизвестно'}`;
if (isControlledByThisClient) nameHtml += " (Вы)";
elements.name.innerHTML = nameHtml;
}
- // Обновление аватара
if (elements.avatar && fighterBaseStats.avatarPath) {
elements.avatar.src = fighterBaseStats.avatarPath;
- // Обновляем рамку аватара в зависимости от персонажа
elements.avatar.classList.remove('avatar-elena', 'avatar-almagest', 'avatar-balard');
elements.avatar.classList.add(`avatar-${fighterBaseStats.characterKey}`);
} else if (elements.avatar) {
@@ -124,243 +108,172 @@
elements.avatar.classList.remove('avatar-elena', 'avatar-almagest', 'avatar-balard');
}
-
- // Обновление полос здоровья и ресурса
const maxHp = Math.max(1, fighterBaseStats.maxHp);
const maxRes = Math.max(1, fighterBaseStats.maxResource);
const currentHp = Math.max(0, fighterState.currentHp);
const currentRes = Math.max(0, fighterState.currentResource);
-
elements.hpFill.style.width = `${(currentHp / maxHp) * 100}%`;
- elements.hpText.textContent = `${Math.round(currentHp)} / ${fighterBaseStats.maxHp}`; // Здоровье округляем
+ elements.hpText.textContent = `${Math.round(currentHp)} / ${fighterBaseStats.maxHp}`;
elements.resourceFill.style.width = `${(currentRes / maxRes) * 100}%`;
- elements.resourceText.textContent = `${currentRes} / ${fighterBaseStats.maxResource}`; // Ресурс не округляем
+ elements.resourceText.textContent = `${currentRes} / ${fighterBaseStats.maxResource}`;
-
- // Обновление типа ресурса и иконки (mana/stamina/dark-energy)
const resourceBarContainerToUpdate = (panelRole === 'player') ? uiElements.playerResourceBarContainer : uiElements.opponentResourceBarContainer;
const resourceIconElementToUpdate = (panelRole === 'player') ? uiElements.playerResourceTypeIcon : uiElements.opponentResourceTypeIcon;
-
if (resourceBarContainerToUpdate && resourceIconElementToUpdate) {
resourceBarContainerToUpdate.classList.remove('mana', 'stamina', 'dark-energy');
let resourceClass = 'mana'; let iconClass = 'fa-flask';
if (fighterBaseStats.resourceName === 'Ярость') { resourceClass = 'stamina'; iconClass = 'fa-fire-alt'; }
- else if (fighterBaseStats.resourceName === 'Темная Энергия') { resourceClass = 'dark-energy'; iconClass = 'fa-skull'; } // или fa-wand-magic-sparkles, fa-star-half-alt и т.д.
- else { console.warn(`updateFighterPanelUI: Unknown resource name "${fighterBaseStats.resourceName}" for icon/color.`); iconClass = 'fa-question-circle'; }
+ else if (fighterBaseStats.resourceName === 'Темная Энергия') { resourceClass = 'dark-energy'; iconClass = 'fa-skull'; }
resourceBarContainerToUpdate.classList.add(resourceClass);
resourceIconElementToUpdate.className = `fas ${iconClass}`;
}
- // Обновление статуса (Готов/Защищается)
const statusText = fighterState.isBlocking ? (config.STATUS_BLOCKING || 'Защищается') : (config.STATUS_READY || 'Готов(а)');
elements.status.textContent = statusText;
elements.status.classList.toggle(config.CSS_CLASS_BLOCKING || 'blocking', fighterState.isBlocking);
-
- // Обновление подсветки и рамки панели (в зависимости от персонажа)
if (elements.panel) {
let borderColorVar = 'var(--panel-border)';
elements.panel.classList.remove('panel-elena', 'panel-almagest', 'panel-balard');
-
if (fighterBaseStats.characterKey === 'elena') { elements.panel.classList.add('panel-elena'); borderColorVar = 'var(--accent-player)'; }
else if (fighterBaseStats.characterKey === 'almagest') { elements.panel.classList.add('panel-almagest'); borderColorVar = 'var(--accent-almagest)'; }
else if (fighterBaseStats.characterKey === 'balard') { elements.panel.classList.add('panel-balard'); borderColorVar = 'var(--accent-opponent)'; }
- else { console.warn(`updateFighterPanelUI: Unknown character key "${fighterBaseStats.characterKey}" for panel border color.`); }
-
-
- let glowColorVar = 'rgba(0, 0, 0, 0.4)'; // Базовая тень
+ let glowColorVar = 'rgba(0, 0, 0, 0.4)';
if (fighterBaseStats.characterKey === 'elena') glowColorVar = 'var(--panel-glow-player)';
- // В твоем CSS --panel-glow-opponent используется для обоих Баларда и Альмагест
- else if (fighterBaseStats.characterKey === 'almagest' || fighterBaseStats.characterKey === 'balard') glowColorVar = 'var(--panel-glow-opponent)';
-
+ else if (fighterBaseStats.characterKey === 'almagest') glowColorVar = 'var(--panel-glow-almagest)'; // Отдельный цвет для Альмагест
+ else if (fighterBaseStats.characterKey === 'balard') glowColorVar = 'var(--panel-glow-opponent)';
elements.panel.style.borderColor = borderColorVar;
elements.panel.style.boxShadow = `0 0 15px ${glowColorVar}, inset 0 0 10px rgba(0, 0, 0, 0.3)`;
}
}
- /**
- * Генерирует HTML для списка эффектов.
- * @param {Array