revert d5d5497dd67530dd3f08a80f8226d71e16c33f9d
revert Загрузить файлы в «public/js»
This commit is contained in:
parent
33a454193b
commit
ba183c472f
@ -15,7 +15,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
let opponentAbilitiesServer = null;
|
let opponentAbilitiesServer = null;
|
||||||
let isLoggedIn = false;
|
let isLoggedIn = false;
|
||||||
let loggedInUsername = '';
|
let loggedInUsername = '';
|
||||||
let isInGame = false; // ФЛАГ СОСТОЯНИЯ ИГРЫ
|
|
||||||
|
|
||||||
// --- DOM Элементы ---
|
// --- DOM Элементы ---
|
||||||
// Аутентификация
|
// Аутентификация
|
||||||
@ -23,7 +22,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const registerForm = document.getElementById('register-form');
|
const registerForm = document.getElementById('register-form');
|
||||||
const loginForm = document.getElementById('login-form');
|
const loginForm = document.getElementById('login-form');
|
||||||
const authMessage = document.getElementById('auth-message');
|
const authMessage = document.getElementById('auth-message');
|
||||||
const statusContainer = document.getElementById('status-container'); // Добавим ссылку на контейнер
|
|
||||||
const userInfoDiv = document.getElementById('user-info');
|
const userInfoDiv = document.getElementById('user-info');
|
||||||
const loggedInUsernameSpan = document.getElementById('logged-in-username');
|
const loggedInUsernameSpan = document.getElementById('logged-in-username');
|
||||||
const logoutButton = document.getElementById('logout-button');
|
const logoutButton = document.getElementById('logout-button');
|
||||||
@ -32,7 +30,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const gameSetupDiv = document.getElementById('game-setup');
|
const gameSetupDiv = document.getElementById('game-setup');
|
||||||
const createAIGameButton = document.getElementById('create-ai-game');
|
const createAIGameButton = document.getElementById('create-ai-game');
|
||||||
const createPvPGameButton = document.getElementById('create-pvp-game');
|
const createPvPGameButton = document.getElementById('create-pvp-game');
|
||||||
const joinPvPGameButton = document.getElementById('join-pvP-game');
|
const joinPvPGameButton = document.getElementById('join-pvp-game');
|
||||||
const findRandomPvPGameButton = document.getElementById('find-random-pvp-game');
|
const findRandomPvPGameButton = document.getElementById('find-random-pvp-game');
|
||||||
const gameIdInput = document.getElementById('game-id-input');
|
const gameIdInput = document.getElementById('game-id-input');
|
||||||
const availableGamesDiv = document.getElementById('available-games-list');
|
const availableGamesDiv = document.getElementById('available-games-list');
|
||||||
@ -44,8 +42,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const attackButton = document.getElementById('button-attack');
|
const attackButton = document.getElementById('button-attack');
|
||||||
const returnToMenuButton = document.getElementById('return-to-menu-button');
|
const returnToMenuButton = document.getElementById('return-to-menu-button');
|
||||||
const gameOverScreen = document.getElementById('game-over-screen');
|
const gameOverScreen = document.getElementById('game-over-screen');
|
||||||
const abilitiesGrid = document.getElementById('abilities-grid');
|
|
||||||
|
|
||||||
|
|
||||||
console.log('Client.js DOMContentLoaded. Initializing elements...');
|
console.log('Client.js DOMContentLoaded. Initializing elements...');
|
||||||
|
|
||||||
@ -58,12 +54,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (gameSetupDiv) gameSetupDiv.style.display = 'none';
|
if (gameSetupDiv) gameSetupDiv.style.display = 'none';
|
||||||
if (gameWrapper) gameWrapper.style.display = 'none';
|
if (gameWrapper) gameWrapper.style.display = 'none';
|
||||||
hideGameOverModal();
|
hideGameOverModal();
|
||||||
// setGameStatusMessage("Войдите или зарегистрируйтесь для начала игры."); // Это сообщение перенесено в setAuthMessage/начальный статус
|
setGameStatusMessage("Войдите или зарегистрируйтесь для начала игры.");
|
||||||
setAuthMessage("Ожидание подключения к серверу..."); // Начальный статус
|
setAuthMessage("");
|
||||||
if (statusContainer) statusContainer.style.display = 'block'; // Убедимся, что статус виден
|
|
||||||
isInGame = false;
|
|
||||||
disableGameControls();
|
|
||||||
resetGameVariables(); // Сбрасываем переменные игры при выходе на экран логина
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function showGameSelectionScreen(username) {
|
function showGameSelectionScreen(username) {
|
||||||
@ -77,15 +69,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (gameWrapper) gameWrapper.style.display = 'none';
|
if (gameWrapper) gameWrapper.style.display = 'none';
|
||||||
hideGameOverModal();
|
hideGameOverModal();
|
||||||
setGameStatusMessage("Выберите режим игры или присоединитесь к существующей.");
|
setGameStatusMessage("Выберите режим игры или присоединитесь к существующей.");
|
||||||
if (statusContainer) statusContainer.style.display = 'block'; // Убедимся, что статус виден
|
|
||||||
socket.emit('requestPvPGameList');
|
socket.emit('requestPvPGameList');
|
||||||
updateAvailableGamesList([]);
|
updateAvailableGamesList([]);
|
||||||
if (gameIdInput) gameIdInput.value = '';
|
if (gameIdInput) gameIdInput.value = '';
|
||||||
const elenaRadio = document.getElementById('char-elena');
|
const elenaRadio = document.getElementById('char-elena');
|
||||||
if (elenaRadio) elenaRadio.checked = true;
|
if (elenaRadio) elenaRadio.checked = true;
|
||||||
isInGame = false;
|
|
||||||
disableGameControls();
|
|
||||||
resetGameVariables(); // Сбрасываем переменные игры при выходе на экран выбора игры
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function showGameScreen() {
|
function showGameScreen() {
|
||||||
@ -95,48 +83,30 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (userInfoDiv) userInfoDiv.style.display = 'block';
|
if (userInfoDiv) userInfoDiv.style.display = 'block';
|
||||||
if (gameSetupDiv) gameSetupDiv.style.display = 'none';
|
if (gameSetupDiv) gameSetupDiv.style.display = 'none';
|
||||||
if (gameWrapper) gameWrapper.style.display = 'flex';
|
if (gameWrapper) gameWrapper.style.display = 'flex';
|
||||||
setGameStatusMessage(""); // Очищаем статус игры, т.к. теперь есть индикатор хода
|
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() {
|
function hideGameOverModal() {
|
||||||
const hiddenClass = (window.GAME_CONFIG && window.GAME_CONFIG.CSS_CLASS_HIDDEN) ? window.GAME_CONFIG.CSS_CLASS_HIDDEN : 'hidden';
|
const hiddenClass = (window.GAME_CONFIG && window.GAME_CONFIG.CSS_CLASS_HIDDEN) ? window.GAME_CONFIG.CSS_CLASS_HIDDEN : 'hidden';
|
||||||
if (gameOverScreen && !gameOverScreen.classList.contains(hiddenClass)) {
|
if (gameOverScreen && !gameOverScreen.classList.contains(hiddenClass)) {
|
||||||
console.log('[Client.js DEBUG] Hiding GameOver Modal.');
|
console.log('[Client.js DEBUG] Hiding GameOver Modal.');
|
||||||
gameOverScreen.classList.add(hiddenClass);
|
gameOverScreen.classList.add(hiddenClass);
|
||||||
if (window.gameUI?.uiElements?.gameOver?.modalContent) {
|
if (window.gameUI && gameUI.uiElements && gameUI.uiElements.gameOver && gameUI.uiElements.gameOver.modalContent) {
|
||||||
window.gameUI.uiElements.gameOver.modalContent.style.transform = 'scale(0.8) translateY(30px)';
|
gameUI.uiElements.gameOver.modalContent.style.transform = 'scale(0.8) translateY(30px)';
|
||||||
window.gameUI.uiElements.gameOver.modalContent.style.opacity = '0';
|
gameUI.uiElements.gameOver.modalContent.style.opacity = '0';
|
||||||
}
|
}
|
||||||
if (window.gameUI?.uiElements?.opponent?.panel) {
|
if (window.gameUI && window.gameUI.uiElements && window.gameUI.uiElements.opponent && window.gameUI.uiElements.opponent.panel) {
|
||||||
const opponentPanel = window.gameUI.uiElements.opponent.panel;
|
const opponentPanel = window.gameUI.uiElements.opponent.panel;
|
||||||
if (opponentPanel.classList.contains('dissolving')) {
|
if (opponentPanel.classList.contains('dissolving')) {
|
||||||
console.log('[Client.js DEBUG] Removing .dissolving from opponent panel during hideGameOverModal.');
|
console.log('[Client.js DEBUG] Removing .dissolving from opponent panel during hideGameOverModal.');
|
||||||
opponentPanel.classList.remove('dissolving');
|
opponentPanel.classList.remove('dissolving');
|
||||||
|
const originalTransition = opponentPanel.style.transition;
|
||||||
|
opponentPanel.style.transition = 'none';
|
||||||
opponentPanel.style.opacity = '1';
|
opponentPanel.style.opacity = '1';
|
||||||
opponentPanel.style.transform = 'scale(1) translateY(0)';
|
opponentPanel.style.transform = 'scale(1) translateY(0)';
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
opponentPanel.style.transition = originalTransition || '';
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -148,10 +118,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
authMessage.className = isError ? 'error' : 'success';
|
authMessage.className = isError ? 'error' : 'success';
|
||||||
authMessage.style.display = message ? 'block' : 'none';
|
authMessage.style.display = message ? 'block' : 'none';
|
||||||
}
|
}
|
||||||
// Скрываем gameStatusMessage, если показываем authMessage
|
|
||||||
if (message && gameStatusMessage) {
|
|
||||||
gameStatusMessage.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setGameStatusMessage(message, isError = false) {
|
function setGameStatusMessage(message, isError = false) {
|
||||||
@ -159,11 +125,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
gameStatusMessage.textContent = message;
|
gameStatusMessage.textContent = message;
|
||||||
gameStatusMessage.style.display = message ? 'block' : 'none';
|
gameStatusMessage.style.display = message ? 'block' : 'none';
|
||||||
gameStatusMessage.style.color = isError ? 'var(--damage-color, red)' : 'var(--turn-color, yellow)';
|
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';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,22 +140,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
return selectedKey;
|
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) {
|
if (registerForm) {
|
||||||
@ -203,9 +148,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const usernameInput = document.getElementById('register-username');
|
const usernameInput = document.getElementById('register-username');
|
||||||
const passwordInput = document.getElementById('register-password');
|
const passwordInput = document.getElementById('register-password');
|
||||||
if (usernameInput && passwordInput) {
|
if (usernameInput && passwordInput) {
|
||||||
// Отключаем кнопки на время регистрации
|
|
||||||
registerForm.querySelector('button').disabled = true;
|
|
||||||
loginForm.querySelector('button').disabled = true;
|
|
||||||
socket.emit('register', { username: usernameInput.value, password: passwordInput.value });
|
socket.emit('register', { username: usernameInput.value, password: passwordInput.value });
|
||||||
} else {
|
} else {
|
||||||
setAuthMessage("Ошибка: поля ввода не найдены.", true);
|
setAuthMessage("Ошибка: поля ввода не найдены.", true);
|
||||||
@ -219,9 +161,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const usernameInput = document.getElementById('login-username');
|
const usernameInput = document.getElementById('login-username');
|
||||||
const passwordInput = document.getElementById('login-password');
|
const passwordInput = document.getElementById('login-password');
|
||||||
if (usernameInput && passwordInput) {
|
if (usernameInput && passwordInput) {
|
||||||
// Отключаем кнопки на время логина
|
|
||||||
registerForm.querySelector('button').disabled = true;
|
|
||||||
loginForm.querySelector('button').disabled = true;
|
|
||||||
socket.emit('login', { username: usernameInput.value, password: passwordInput.value });
|
socket.emit('login', { username: usernameInput.value, password: passwordInput.value });
|
||||||
} else {
|
} else {
|
||||||
setAuthMessage("Ошибка: поля ввода не найдены.", true);
|
setAuthMessage("Ошибка: поля ввода не найдены.", true);
|
||||||
@ -231,18 +170,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
if (logoutButton) {
|
if (logoutButton) {
|
||||||
logoutButton.addEventListener('click', () => {
|
logoutButton.addEventListener('click', () => {
|
||||||
// Отключаем кнопку выхода
|
|
||||||
logoutButton.disabled = true;
|
|
||||||
socket.emit('logout');
|
socket.emit('logout');
|
||||||
// Сброс состояния и UI происходит по событию logoutResponse или gameNotFound/gameEnded после logout
|
isLoggedIn = false;
|
||||||
// Пока просто сбрасываем флаги и показываем Auth, т.к. сервер не присылает специальный logoutResponse
|
loggedInUsername = '';
|
||||||
isLoggedIn = false; loggedInUsername = '';
|
currentGameId = null; currentGameState = null; myPlayerId = null;
|
||||||
resetGameVariables();
|
myCharacterKey = null; opponentCharacterKey = null;
|
||||||
isInGame = false;
|
playerBaseStatsServer = null; opponentBaseStatsServer = null;
|
||||||
disableGameControls();
|
playerAbilitiesServer = null; opponentAbilitiesServer = null;
|
||||||
|
window.gameState = null; window.gameData = null; window.myPlayerId = null;
|
||||||
showAuthScreen();
|
showAuthScreen();
|
||||||
setGameStatusMessage("Вы вышли из системы."); // Используем gameStatusMessage для уведомления
|
setGameStatusMessage("Вы вышли из системы.");
|
||||||
logoutButton.disabled = false; // Включаем кнопку после обработки (хотя она будет скрыта)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,8 +188,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
setGameStatusMessage("Пожалуйста, войдите, чтобы начать игру.", true); return;
|
setGameStatusMessage("Пожалуйста, войдите, чтобы начать игру.", true); return;
|
||||||
}
|
}
|
||||||
// Отключаем кнопки настройки игры
|
|
||||||
disableSetupButtons();
|
|
||||||
socket.emit('createGame', { mode: 'ai', characterKey: 'elena' });
|
socket.emit('createGame', { mode: 'ai', characterKey: 'elena' });
|
||||||
setGameStatusMessage("Создание игры против AI...");
|
setGameStatusMessage("Создание игры против AI...");
|
||||||
});
|
});
|
||||||
@ -262,8 +197,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
setGameStatusMessage("Пожалуйста, войдите, чтобы начать игру.", true); return;
|
setGameStatusMessage("Пожалуйста, войдите, чтобы начать игру.", true); return;
|
||||||
}
|
}
|
||||||
// Отключаем кнопки настройки игры
|
|
||||||
disableSetupButtons();
|
|
||||||
const selectedCharacter = getSelectedCharacterKey();
|
const selectedCharacter = getSelectedCharacterKey();
|
||||||
socket.emit('createGame', { mode: 'pvp', characterKey: selectedCharacter });
|
socket.emit('createGame', { mode: 'pvp', characterKey: selectedCharacter });
|
||||||
setGameStatusMessage(`Создание PvP игры за ${selectedCharacter === 'elena' ? 'Елену' : 'Альмагест'}...`);
|
setGameStatusMessage(`Создание PvP игры за ${selectedCharacter === 'elena' ? 'Елену' : 'Альмагест'}...`);
|
||||||
@ -276,8 +209,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
const gameIdToJoin = gameIdInput.value.trim();
|
const gameIdToJoin = gameIdInput.value.trim();
|
||||||
if (gameIdToJoin) {
|
if (gameIdToJoin) {
|
||||||
// Отключаем кнопки настройки игры
|
|
||||||
disableSetupButtons();
|
|
||||||
socket.emit('joinGame', { gameId: gameIdToJoin });
|
socket.emit('joinGame', { gameId: gameIdToJoin });
|
||||||
setGameStatusMessage(`Присоединение к игре ${gameIdToJoin}...`);
|
setGameStatusMessage(`Присоединение к игре ${gameIdToJoin}...`);
|
||||||
} else {
|
} else {
|
||||||
@ -290,42 +221,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
setGameStatusMessage("Пожалуйста, войдите, чтобы найти игру.", true); return;
|
setGameStatusMessage("Пожалуйста, войдите, чтобы найти игру.", true); return;
|
||||||
}
|
}
|
||||||
// Отключаем кнопки настройки игры
|
|
||||||
disableSetupButtons();
|
|
||||||
const selectedCharacter = getSelectedCharacterKey();
|
const selectedCharacter = getSelectedCharacterKey();
|
||||||
socket.emit('findRandomGame', { characterKey: selectedCharacter });
|
socket.emit('findRandomGame', { characterKey: selectedCharacter });
|
||||||
setGameStatusMessage(`Поиск случайной PvP игры (предпочтение: ${selectedCharacter === 'elena' ? 'Елена' : 'Альмагест'})...`);
|
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) {
|
if (attackButton) {
|
||||||
attackButton.addEventListener('click', () => {
|
attackButton.addEventListener('click', () => {
|
||||||
// Проверяем isInGame и другие флаги перед отправкой действия
|
if (currentGameId && currentGameState && !currentGameState.isGameOver && isLoggedIn) {
|
||||||
if (isLoggedIn && isInGame && currentGameId && currentGameState && !currentGameState.isGameOver) {
|
|
||||||
socket.emit('playerAction', { actionType: 'attack' });
|
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();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -333,72 +238,75 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
function handleAbilityButtonClick(event) {
|
function handleAbilityButtonClick(event) {
|
||||||
const button = event.currentTarget;
|
const button = event.currentTarget;
|
||||||
const abilityId = button.dataset.abilityId;
|
const abilityId = button.dataset.abilityId;
|
||||||
// Проверяем isInGame и другие флаги перед отправкой действия
|
if (currentGameId && abilityId && currentGameState && !currentGameState.isGameOver && isLoggedIn) {
|
||||||
if (isLoggedIn && isInGame && currentGameId && abilityId && currentGameState && !currentGameState.isGameOver) {
|
|
||||||
socket.emit('playerAction', { actionType: 'ability', abilityId: abilityId });
|
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) {
|
if (returnToMenuButton) {
|
||||||
returnToMenuButton.addEventListener('click', () => {
|
returnToMenuButton.addEventListener('click', () => {
|
||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
showAuthScreen(); // Если каким-то образом кнопка активна без логина
|
showAuthScreen();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Отключаем кнопку возврата в меню
|
|
||||||
returnToMenuButton.disabled = true;
|
|
||||||
|
|
||||||
console.log('[Client] Return to menu button clicked. Resetting game state and showing selection screen.');
|
console.log('[Client] Return to menu button clicked.');
|
||||||
// Сбрасываем все переменные состояния игры и глобальные ссылки
|
currentGameId = null;
|
||||||
resetGameVariables();
|
currentGameState = null;
|
||||||
isInGame = false;
|
myPlayerId = null;
|
||||||
disableGameControls(); // Убедимся, что игровые кнопки отключены
|
myCharacterKey = null;
|
||||||
hideGameOverModal(); // Убедимся, что модалка скрыта
|
opponentCharacterKey = null;
|
||||||
|
playerBaseStatsServer = null;
|
||||||
|
opponentBaseStatsServer = null;
|
||||||
|
playerAbilitiesServer = null;
|
||||||
|
opponentAbilitiesServer = null;
|
||||||
|
|
||||||
showGameSelectionScreen(loggedInUsername); // Возвращаемся на экран выбора игры
|
window.gameState = null;
|
||||||
// Кнопки настройки игры будут включены в showGameSelectionScreen / updateAvailableGamesList
|
window.gameData = null;
|
||||||
|
window.myPlayerId = null;
|
||||||
|
|
||||||
|
showGameSelectionScreen(loggedInUsername);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeAbilityButtons() {
|
function initializeAbilityButtons() {
|
||||||
|
const abilitiesGrid = document.getElementById('abilities-grid');
|
||||||
if (!abilitiesGrid || !window.gameUI || !window.GAME_CONFIG) {
|
if (!abilitiesGrid || !window.gameUI || !window.GAME_CONFIG) {
|
||||||
if(abilitiesGrid) abilitiesGrid.innerHTML = '<p class="placeholder-text">Ошибка загрузки способностей.</p>';
|
if(abilitiesGrid) abilitiesGrid.innerHTML = '<p class="placeholder-text">Ошибка загрузки способностей.</p>';
|
||||||
console.error('[Client.js] initializeAbilityButtons failed: abilitiesGrid, gameUI, or GAME_CONFIG not found.');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
abilitiesGrid.innerHTML = '';
|
abilitiesGrid.innerHTML = '';
|
||||||
const config = window.GAME_CONFIG;
|
const config = window.GAME_CONFIG;
|
||||||
|
|
||||||
const abilitiesToDisplay = playerAbilitiesServer;
|
const abilitiesToDisplay = playerAbilitiesServer; // Используем данные, сохраненные при gameStarted
|
||||||
const baseStatsForResource = playerBaseStatsServer;
|
const baseStatsForResource = playerBaseStatsServer; // Используем данные, сохраненные при gameStarted
|
||||||
|
|
||||||
if (!abilitiesToDisplay || abilitiesToDisplay.length === 0 || !baseStatsForResource) {
|
if (!abilitiesToDisplay || abilitiesToDisplay.length === 0 || !baseStatsForResource) {
|
||||||
abilitiesGrid.innerHTML = '<p class="placeholder-text">Нет доступных способностей.</p>';
|
abilitiesGrid.innerHTML = '<p class="placeholder-text">Нет доступных способностей.</p>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const resourceName = baseStatsForResource.resourceName || "Ресурс";
|
const resourceName = baseStatsForResource.resourceName || "Ресурс";
|
||||||
const abilityButtonClass = config.CSS_CLASS_ABILITY_BUTTON || 'ability-button';
|
|
||||||
|
|
||||||
abilitiesToDisplay.forEach(ability => {
|
abilitiesToDisplay.forEach(ability => {
|
||||||
const button = document.createElement('button');
|
const button = document.createElement('button');
|
||||||
button.id = `ability-btn-${ability.id}`;
|
button.id = `ability-btn-${ability.id}`;
|
||||||
button.classList.add(abilityButtonClass);
|
button.classList.add(config.CSS_CLASS_ABILITY_BUTTON || 'ability-button');
|
||||||
button.dataset.abilityId = ability.id;
|
button.dataset.abilityId = ability.id;
|
||||||
|
|
||||||
let descriptionText = ability.description;
|
let descriptionText = ability.description;
|
||||||
|
if (typeof ability.descriptionFunction === 'function') {
|
||||||
let cooldown = ability.cooldown;
|
const targetStatsForDesc = opponentBaseStatsServer; // Используем данные, сохраненные при gameStarted
|
||||||
let cooldownText = "";
|
descriptionText = ability.descriptionFunction(config, targetStatsForDesc);
|
||||||
if (typeof cooldown === 'number' && cooldown > 0) {
|
|
||||||
cooldownText = ` (КД: ${cooldown} х.)`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let title = `${ability.name} (${ability.cost} ${resourceName})${cooldownText} - ${descriptionText || 'Нет описания'}`;
|
let title = `${ability.name} (${ability.cost} ${resourceName}) - ${descriptionText}`;
|
||||||
|
let cooldown = ability.cooldown;
|
||||||
|
if (ability.internalCooldownFromConfig && config[ability.internalCooldownFromConfig]) {
|
||||||
|
cooldown = config[ability.internalCooldownFromConfig];
|
||||||
|
} else if (ability.internalCooldownValue) {
|
||||||
|
cooldown = ability.internalCooldownValue;
|
||||||
|
}
|
||||||
|
if (cooldown) title += ` (КД: ${cooldown} х.)`;
|
||||||
button.setAttribute('title', title);
|
button.setAttribute('title', title);
|
||||||
|
|
||||||
const nameSpan = document.createElement('span');
|
const nameSpan = document.createElement('span');
|
||||||
@ -406,24 +314,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
button.appendChild(nameSpan);
|
button.appendChild(nameSpan);
|
||||||
|
|
||||||
const descSpan = document.createElement('span');
|
const descSpan = document.createElement('span');
|
||||||
descSpan.classList.add('ability-desc');
|
descSpan.classList.add('ability-desc'); descSpan.textContent = `(${ability.cost} ${resourceName})`;
|
||||||
descSpan.textContent = `(${ability.cost} ${resourceName})`;
|
|
||||||
|
|
||||||
button.appendChild(descSpan);
|
button.appendChild(descSpan);
|
||||||
|
|
||||||
const cdDisplay = document.createElement('span');
|
const cdDisplay = document.createElement('span');
|
||||||
cdDisplay.classList.add('ability-cooldown-display');
|
cdDisplay.classList.add('ability-cooldown-display'); cdDisplay.style.display = 'none';
|
||||||
cdDisplay.style.display = 'none';
|
|
||||||
button.appendChild(cdDisplay);
|
button.appendChild(cdDisplay);
|
||||||
|
|
||||||
button.addEventListener('click', handleAbilityButtonClick);
|
button.addEventListener('click', handleAbilityButtonClick);
|
||||||
|
|
||||||
abilitiesGrid.appendChild(button);
|
abilitiesGrid.appendChild(button);
|
||||||
});
|
});
|
||||||
const placeholder = abilitiesGrid.querySelector('.placeholder-text');
|
|
||||||
if (placeholder) placeholder.remove();
|
|
||||||
|
|
||||||
// Кнопки инициализированы, updateUI будет управлять их disabled состоянием
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateAvailableGamesList(games) {
|
function updateAvailableGamesList(games) {
|
||||||
@ -442,8 +342,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
setGameStatusMessage("Пожалуйста, войдите, чтобы присоединиться к игре.", true); return;
|
setGameStatusMessage("Пожалуйста, войдите, чтобы присоединиться к игре.", true); return;
|
||||||
}
|
}
|
||||||
// Отключаем кнопки настройки игры перед присоединением
|
|
||||||
disableSetupButtons();
|
|
||||||
socket.emit('joinGame', { gameId: e.target.dataset.gameId });
|
socket.emit('joinGame', { gameId: e.target.dataset.gameId });
|
||||||
});
|
});
|
||||||
li.appendChild(joinBtn);
|
li.appendChild(joinBtn);
|
||||||
@ -451,348 +349,229 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
availableGamesDiv.appendChild(ul);
|
availableGamesDiv.appendChild(ul);
|
||||||
// Включаем кнопки JOIN в списке
|
|
||||||
availableGamesDiv.querySelectorAll('button').forEach(btn => btn.disabled = false);
|
|
||||||
} else {
|
} else {
|
||||||
availableGamesDiv.innerHTML += '<p>Нет доступных игр. Создайте свою!</p>';
|
availableGamesDiv.innerHTML += '<p>Нет доступных игр. Создайте свою!</p>';
|
||||||
}
|
}
|
||||||
enableSetupButtons(); // Включаем основные кнопки создания игры после обновления списка
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Обработчики событий Socket.IO ---
|
// --- Обработчики событий Socket.IO ---
|
||||||
socket.on('connect', () => {
|
socket.on('connect', () => {
|
||||||
console.log('[Client] Socket connected to server! Socket ID:', socket.id);
|
console.log('[Client] Socket connected to server! Socket ID:', socket.id);
|
||||||
// При подключении, если залогинен, запросить состояние игры.
|
if (!isLoggedIn) {
|
||||||
// Это нужно ТОЛЬКО для восстановления игры, если клиент был в игре и переподключился.
|
|
||||||
if (isLoggedIn) {
|
|
||||||
console.log(`[Client] Reconnected as ${loggedInUsername}. Requesting state.`);
|
|
||||||
socket.emit('requestGameState');
|
|
||||||
} else {
|
|
||||||
// Если не залогинен, показываем экран аутентификации
|
|
||||||
showAuthScreen();
|
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 {
|
} else {
|
||||||
isLoggedIn = false;
|
console.log(`[Client] Reconnected as ${loggedInUsername}. Requesting state or showing game selection.`);
|
||||||
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);
|
showGameSelectionScreen(loggedInUsername);
|
||||||
// Сообщение: "Игровая сессия не найдена" может быть показано, но как статус, не ошибка.
|
|
||||||
// Можно сделать его менее тревожным или вовсе не показывать.
|
|
||||||
// setGameStatusMessage(data?.message || "Активная игровая сессия не найдена.", false); // Информационный статус
|
|
||||||
setGameStatusMessage("Выберите режим игры или присоединитесь к существующей."); // Сбрасываем на стандартное сообщение
|
|
||||||
enableSetupButtons(); // Включаем кнопки настройки игры
|
|
||||||
} else {
|
|
||||||
// Если не залогинен и получил gameNotFound (что странно), сбрасываем и показываем логин
|
|
||||||
showAuthScreen();
|
|
||||||
setAuthMessage(data?.message || "Пожалуйста, войдите, чтобы начать новую игру.", false);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
socket.on('disconnect', (reason) => {
|
socket.on('disconnect', (reason) => {
|
||||||
console.log('[Client] Disconnected from server:', reason);
|
console.log('[Client] Disconnected from server:', reason);
|
||||||
setGameStatusMessage(`Отключено от сервера: ${reason}. Пожалуйста, обновите страницу.`, true);
|
setGameStatusMessage(`Отключено от сервера: ${reason}. Попробуйте обновить страницу.`, true);
|
||||||
|
hideGameOverModal();
|
||||||
|
});
|
||||||
|
|
||||||
// Отключаем игровые кнопки, чтобы предотвратить отправку действий
|
socket.on('registerResponse', (data) => {
|
||||||
disableGameControls();
|
setAuthMessage(data.message, !data.success);
|
||||||
|
if (data.success && registerForm) registerForm.reset();
|
||||||
|
});
|
||||||
|
|
||||||
// НЕ сбрасываем игровые переменные немедленно.
|
socket.on('loginResponse', (data) => {
|
||||||
// Если мы были в игре (isInGame=true), возможно, сервер пришлет gameOver или gameNotFound позже.
|
setAuthMessage(data.message, !data.success);
|
||||||
// Если game over придет, его обработчик покажет модалку и включит кнопку "В меню".
|
if (data.success) {
|
||||||
// Если gameNotFound придет, его обработчик сбросит переменные и переключит UI.
|
isLoggedIn = true; loggedInUsername = data.username;
|
||||||
// Если ничего не придет, страница может зависнуть.
|
setAuthMessage(""); showGameSelectionScreen(data.username);
|
||||||
// В продакшене тут может быть таймер на принудительный сброс и возврат в меню.
|
} else {
|
||||||
|
isLoggedIn = false; loggedInUsername = '';
|
||||||
// Если мы не были в игре (например, на экране выбора игры), просто показываем статус.
|
|
||||||
if (!isInGame) {
|
|
||||||
// Остаемся на текущем экране (выбора игры или логина) и показываем статус дисконнекта
|
|
||||||
// UI уже настроен showGameSelectionScreen или showAuthScreen
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Обработка gameStarted - без изменений
|
socket.on('gameCreated', (data) => {
|
||||||
socket.on('gameStarted', (data) => {
|
if (!isLoggedIn) return;
|
||||||
if (!isLoggedIn) {
|
currentGameId = data.gameId;
|
||||||
console.warn('[Client] Ignoring gameStarted: Not logged in.');
|
myPlayerId = data.yourPlayerId; // Запоминаем наш технический ID слота
|
||||||
return;
|
console.log(`[Client] Game created/joined: ${currentGameId}, Mode: ${data.mode}, You (${loggedInUsername}) are in slot: ${myPlayerId}`);
|
||||||
|
if (data.mode === 'pvp') {
|
||||||
|
if (gameIdInput) gameIdInput.value = currentGameId;
|
||||||
|
setGameStatusMessage(`PvP игра ${currentGameId.substring(0,8)}... создана. ID для друга: ${currentGameId}. Ожидание второго игрока...`);
|
||||||
|
} else {
|
||||||
|
setGameStatusMessage(`Игра против AI ${currentGameId.substring(0,8)}... создана. Ожидание начала...`);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('gameStarted', (data) => {
|
||||||
|
if (!isLoggedIn) return;
|
||||||
console.log('[Client] Event "gameStarted" received:', data);
|
console.log('[Client] Event "gameStarted" received:', data);
|
||||||
|
|
||||||
if (window.gameUI?.uiElements?.opponent?.panel) {
|
if (window.gameUI && window.gameUI.uiElements && window.gameUI.uiElements.opponent && window.gameUI.uiElements.opponent.panel) {
|
||||||
const opponentPanel = window.gameUI.uiElements.opponent.panel;
|
const opponentPanel = window.gameUI.uiElements.opponent.panel;
|
||||||
if (opponentPanel.classList.contains('dissolving')) {
|
opponentPanel.classList.remove('dissolving');
|
||||||
console.log('[Client.js DEBUG] Removing .dissolving from opponent panel before new game start.');
|
const originalTransition = opponentPanel.style.transition;
|
||||||
opponentPanel.classList.remove('dissolving');
|
opponentPanel.style.transition = 'none';
|
||||||
opponentPanel.style.opacity = '1';
|
opponentPanel.style.opacity = '1';
|
||||||
opponentPanel.style.transform = 'scale(1) translateY(0)';
|
opponentPanel.style.transform = 'scale(1) translateY(0)';
|
||||||
}
|
requestAnimationFrame(() => {
|
||||||
|
opponentPanel.style.transition = originalTransition || '';
|
||||||
|
});
|
||||||
|
console.log('[Client RESTART FIX Improved] Opponent panel styles explicitly reset for new game.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Убедимся, что игровые переменные обновлены (на случай, если игра началась сразу после логина без requestGameState)
|
|
||||||
currentGameId = data.gameId;
|
currentGameId = data.gameId;
|
||||||
myPlayerId = data.yourPlayerId;
|
myPlayerId = data.yourPlayerId; // Сервер присылает ID слота, который занимает ЭТОТ клиент
|
||||||
currentGameState = data.initialGameState;
|
currentGameState = data.initialGameState;
|
||||||
|
|
||||||
|
// Сервер присылает playerBaseStats и opponentBaseStats ОТНОСИТЕЛЬНО этого клиента
|
||||||
|
// То есть, data.playerBaseStats - это статы персонажа, которым управляет этот клиент
|
||||||
|
// data.opponentBaseStats - это статы персонажа-оппонента для этого клиента
|
||||||
playerBaseStatsServer = data.playerBaseStats;
|
playerBaseStatsServer = data.playerBaseStats;
|
||||||
opponentBaseStatsServer = data.opponentBaseStats;
|
opponentBaseStatsServer = data.opponentBaseStats;
|
||||||
playerAbilitiesServer = data.playerAbilities;
|
playerAbilitiesServer = data.playerAbilities;
|
||||||
opponentAbilitiesServer = data.opponentAbilities;
|
opponentAbilitiesServer = data.opponentAbilities;
|
||||||
myCharacterKey = playerBaseStatsServer?.characterKey;
|
|
||||||
opponentCharacterKey = opponentBaseStatsServer?.characterKey;
|
myCharacterKey = playerBaseStatsServer?.characterKey; // Ключ персонажа этого клиента
|
||||||
|
opponentCharacterKey = opponentBaseStatsServer?.characterKey; // Ключ персонажа оппонента этого клиента
|
||||||
|
|
||||||
|
console.log(`[Client gameStarted] My Slot ID (technical): ${myPlayerId}`);
|
||||||
|
console.log(`[Client gameStarted] My Character: ${myCharacterKey} (Name: ${playerBaseStatsServer?.name})`);
|
||||||
|
console.log(`[Client gameStarted] Opponent Character: ${opponentCharacterKey} (Name: ${opponentBaseStatsServer?.name})`);
|
||||||
|
|
||||||
|
|
||||||
if (data.clientConfig) {
|
if (data.clientConfig) {
|
||||||
window.GAME_CONFIG = { ...data.clientConfig };
|
window.GAME_CONFIG = { ...data.clientConfig };
|
||||||
console.log('[Client.js gameStarted] Received clientConfig from server.');
|
|
||||||
} else if (!window.GAME_CONFIG) {
|
} else if (!window.GAME_CONFIG) {
|
||||||
window.GAME_CONFIG = { PLAYER_ID: 'player', OPPONENT_ID: 'opponent', CSS_CLASS_HIDDEN: 'hidden' };
|
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.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Глобальные переменные для ui.js
|
||||||
window.gameState = currentGameState;
|
window.gameState = currentGameState;
|
||||||
window.gameData = {
|
window.gameData = { // Эти данные используются в ui.js для отображения панелей
|
||||||
playerBaseStats: playerBaseStatsServer,
|
playerBaseStats: playerBaseStatsServer, // Статы "моего" персонажа
|
||||||
opponentBaseStats: opponentBaseStatsServer,
|
opponentBaseStats: opponentBaseStatsServer, // Статы "моего оппонента"
|
||||||
playerAbilities: playerAbilitiesServer,
|
playerAbilities: playerAbilitiesServer, // Способности "моего" персонажа
|
||||||
opponentAbilities: opponentAbilitiesServer
|
opponentAbilities: opponentAbilitiesServer // Способности "моего оппонента"
|
||||||
};
|
};
|
||||||
window.myPlayerId = myPlayerId;
|
window.myPlayerId = myPlayerId; // Технический ID слота этого клиента
|
||||||
|
|
||||||
showGameScreen(); // Показываем игровой экран (ставит isInGame = true)
|
showGameScreen();
|
||||||
initializeAbilityButtons(); // Инициализируем кнопки способностей
|
initializeAbilityButtons();
|
||||||
|
|
||||||
if (window.gameUI?.uiElements?.log?.list) {
|
if (window.gameUI && gameUI.uiElements?.log?.list) {
|
||||||
window.gameUI.uiElements.log.list.innerHTML = '';
|
gameUI.uiElements.log.list.innerHTML = '';
|
||||||
}
|
}
|
||||||
if (window.gameUI && typeof window.gameUI.addToLog === 'function' && data.log) {
|
if (window.gameUI && typeof gameUI.addToLog === 'function' && data.log) {
|
||||||
data.log.forEach(logEntry => window.gameUI.addToLog(logEntry.message, logEntry.type));
|
data.log.forEach(logEntry => gameUI.addToLog(logEntry.message, logEntry.type));
|
||||||
}
|
}
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (window.gameUI && typeof window.gameUI.updateUI === 'function') {
|
if (window.gameUI && typeof gameUI.updateUI === 'function') {
|
||||||
console.log('[Client] Calling gameUI.updateUI() after gameStarted.');
|
console.log('[Client] Calling gameUI.updateUI() after style reset and rAF in gameStarted.');
|
||||||
window.gameUI.updateUI();
|
gameUI.updateUI();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
hideGameOverModal();
|
hideGameOverModal();
|
||||||
setGameStatusMessage(""); // Скрываем статус сообщение, если видим игровой экран
|
if (returnToMenuButton) {
|
||||||
|
returnToMenuButton.disabled = true;
|
||||||
|
}
|
||||||
|
setGameStatusMessage("");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Обработка gameStateUpdate - без изменений (проверяет isLoggedIn и isInGame)
|
|
||||||
socket.on('gameStateUpdate', (data) => {
|
socket.on('gameStateUpdate', (data) => {
|
||||||
if (!isLoggedIn || !isInGame || !currentGameId || !window.GAME_CONFIG) {
|
if (!isLoggedIn || !currentGameId) return;
|
||||||
console.warn('[Client] Ignoring gameStateUpdate: Not logged in or not in game context.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
currentGameState = data.gameState;
|
currentGameState = data.gameState;
|
||||||
window.gameState = currentGameState;
|
window.gameState = currentGameState; // ui.js использует это для обновления
|
||||||
|
|
||||||
if (window.gameUI && typeof window.gameUI.updateUI === 'function') {
|
if (window.gameUI && typeof gameUI.updateUI === 'function') {
|
||||||
window.gameUI.updateUI();
|
gameUI.updateUI();
|
||||||
}
|
}
|
||||||
if (window.gameUI && typeof window.gameUI.addToLog === 'function' && data.log) {
|
if (window.gameUI && typeof gameUI.addToLog === 'function' && data.log) {
|
||||||
data.log.forEach(logEntry => window.gameUI.addToLog(logEntry.message, logEntry.type));
|
data.log.forEach(logEntry => gameUI.addToLog(logEntry.message, logEntry.type));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Обработка logUpdate - без изменений (проверяет isLoggedIn и isInGame)
|
|
||||||
socket.on('logUpdate', (data) => {
|
socket.on('logUpdate', (data) => {
|
||||||
if (!isLoggedIn || !isInGame || !currentGameId || !window.GAME_CONFIG) {
|
if (!isLoggedIn || !currentGameId) return;
|
||||||
console.warn('[Client] Ignoring logUpdate: Not logged in or not in game context.');
|
if (window.gameUI && typeof gameUI.addToLog === 'function' && data.log) {
|
||||||
return;
|
data.log.forEach(logEntry => gameUI.addToLog(logEntry.message, logEntry.type));
|
||||||
}
|
|
||||||
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) => {
|
socket.on('gameOver', (data) => {
|
||||||
if (!isLoggedIn || !currentGameId || !window.GAME_CONFIG) {
|
if (!isLoggedIn || !currentGameId) return;
|
||||||
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}`);
|
console.log(`[Client gameOver] Received. My technical slot ID (myPlayerId): ${myPlayerId}, Winner's slot ID from server (data.winnerId): ${data.winnerId}`);
|
||||||
const playerWon = data.winnerId === myPlayerId;
|
const playerWon = data.winnerId === myPlayerId; // Определяем, выиграл ли ЭТОТ клиент
|
||||||
console.log(`[Client gameOver] Calculated playerWon for this client: ${playerWon}`);
|
console.log(`[Client gameOver] Calculated playerWon for this client: ${playerWon}`);
|
||||||
|
|
||||||
currentGameState = data.finalGameState;
|
currentGameState = data.finalGameState;
|
||||||
window.gameState = currentGameState;
|
window.gameState = currentGameState;
|
||||||
|
|
||||||
console.log('[Client gameOver] Final GameState:', currentGameState);
|
// Логи для отладки имен, которые будут использоваться в ui.js
|
||||||
|
|
||||||
if (window.gameData) {
|
if (window.gameData) {
|
||||||
console.log(`[Client gameOver] For ui.js, myName: ${window.gameData.playerBaseStats?.name}, opponentName: ${window.gameData.opponentBaseStats?.name}`);
|
console.log(`[Client gameOver] For ui.js, myName will be: ${window.gameData.playerBaseStats?.name}, opponentName will be: ${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') {
|
if (window.gameUI && typeof gameUI.updateUI === 'function') gameUI.updateUI();
|
||||||
|
if (window.gameUI && typeof gameUI.addToLog === 'function' && data.log) {
|
||||||
|
data.log.forEach(logEntry => gameUI.addToLog(logEntry.message, logEntry.type));
|
||||||
|
}
|
||||||
|
if (window.gameUI && typeof gameUI.showGameOver === 'function') {
|
||||||
|
// opponentCharacterKeyFromClient передается, чтобы ui.js знал, какой персонаж был оппонентом
|
||||||
|
// и мог применить, например, анимацию .dissolving к правильному типу оппонента (Балард/Альмагест)
|
||||||
const opponentKeyForModal = window.gameData?.opponentBaseStats?.characterKey;
|
const opponentKeyForModal = window.gameData?.opponentBaseStats?.characterKey;
|
||||||
window.gameUI.showGameOver(playerWon, data.reason, opponentKeyForModal, data);
|
gameUI.showGameOver(playerWon, data.reason, opponentKeyForModal);
|
||||||
}
|
|
||||||
|
|
||||||
if (returnToMenuButton) {
|
if (returnToMenuButton) {
|
||||||
returnToMenuButton.disabled = false; // Включаем кнопку "В меню" в модалке
|
returnToMenuButton.disabled = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setGameStatusMessage("Игра окончена. " + (playerWon ? "Вы победили!" : "Вы проиграли."));
|
setGameStatusMessage("Игра окончена. " + (playerWon ? "Вы победили!" : "Вы проиграли."));
|
||||||
|
|
||||||
// isInGame остается true, пока не нажмут "В меню"
|
|
||||||
// disableGameControls() уже вызвано через updateUI из-за isGameOver
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Обработка waitingForOpponent - без изменений
|
|
||||||
socket.on('waitingForOpponent', () => {
|
socket.on('waitingForOpponent', () => {
|
||||||
if (!isLoggedIn) return;
|
if (!isLoggedIn) return;
|
||||||
setGameStatusMessage("Ожидание присоединения оппонента...");
|
setGameStatusMessage("Ожидание присоединения оппонента...");
|
||||||
disableGameControls(); // Отключаем кнопки, пока ждем
|
|
||||||
// Включаем кнопки настройки игры после попытки создания/присоединения к ожидающей игре
|
|
||||||
// чтобы игрок мог отменить или попробовать другое
|
|
||||||
enableSetupButtons();
|
|
||||||
// Однако, если игрок создал игру, кнопки "Создать" должны быть отключены,
|
|
||||||
// а если он искал и создал, то тоже.
|
|
||||||
// Возможно, лучше отключать кнопки создания/поиска, оставляя только "Присоединиться" по ID или отмену.
|
|
||||||
// Для простоты пока включаем все, кроме кнопок боя.
|
|
||||||
// disableSetupButtons(); // Лучше оставить их отключенными до gameStarted или gameNotFound
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Обработка opponentDisconnected - без изменений (проверяет isLoggedIn и isInGame)
|
|
||||||
socket.on('opponentDisconnected', (data) => {
|
socket.on('opponentDisconnected', (data) => {
|
||||||
if (!isLoggedIn || !isInGame || !currentGameId || !window.GAME_CONFIG) {
|
if (!isLoggedIn || !currentGameId) return;
|
||||||
console.warn('[Client] Ignoring opponentDisconnected: Not logged in or not in game context.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const systemLogType = (window.GAME_CONFIG?.LOG_TYPE_SYSTEM) || 'system';
|
const systemLogType = (window.GAME_CONFIG?.LOG_TYPE_SYSTEM) || 'system';
|
||||||
const disconnectedCharacterName = data.disconnectedCharacterName || 'Противник';
|
if (window.gameUI && typeof gameUI.addToLog === 'function') {
|
||||||
const disconnectedCharacterKey = data.disconnectedCharacterKey || 'unknown';
|
gameUI.addToLog(`Противник (${data.disconnectedCharacterName || 'Игрок'}) отключился.`, systemLogType);
|
||||||
|
|
||||||
if (window.gameUI && typeof window.gameUI.addToLog === 'function') {
|
|
||||||
window.gameUI.addToLog(`🔌 Противник (${disconnectedCharacterName}) отключился.`, systemLogType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentGameState && !currentGameState.isGameOver) {
|
if (currentGameState && !currentGameState.isGameOver) {
|
||||||
setGameStatusMessage(`Противник (${disconnectedCharacterName}) отключился. Ожидание завершения игры сервером...`, true);
|
setGameStatusMessage("Противник отключился. Игра может быть завершена сервером.", true);
|
||||||
disableGameControls(); // Отключаем кнопки немедленно
|
// Сервер должен прислать 'gameOver', если игра действительно завершается
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Обработка gameError - без изменений
|
|
||||||
socket.on('gameError', (data) => {
|
socket.on('gameError', (data) => {
|
||||||
console.error('[Client] Server error:', data.message);
|
console.error('[Client] Server error:', data.message);
|
||||||
const systemLogType = (window.GAME_CONFIG?.LOG_TYPE_SYSTEM) || 'system';
|
const systemLogType = (window.GAME_CONFIG?.LOG_TYPE_SYSTEM) || 'system';
|
||||||
|
if (isLoggedIn && currentGameId && currentGameState && !currentGameState.isGameOver && window.gameUI && typeof gameUI.addToLog === 'function') {
|
||||||
// Если в игре, добавляем в лог и отключаем кнопки
|
gameUI.addToLog(`Ошибка: ${data.message}`, systemLogType);
|
||||||
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
socket.on('availablePvPGamesList', (games) => {
|
socket.on('availablePvPGamesList', (games) => {
|
||||||
if (!isLoggedIn) return;
|
if (!isLoggedIn) return;
|
||||||
updateAvailableGamesList(games); // updateAvailableGamesList включает кнопки Join и основные кнопки создания
|
updateAvailableGamesList(games);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('noPendingGamesFound', (data) => {
|
socket.on('noPendingGamesFound', (data) => {
|
||||||
if (!isLoggedIn) return;
|
if (!isLoggedIn) return;
|
||||||
// Это информационное сообщение, когда игрок искал игру и создал новую
|
|
||||||
// currentGameId и myPlayerId должны быть установлены событием 'gameCreated'
|
|
||||||
setGameStatusMessage(data.message || "Свободных игр не найдено. Создана новая для вас, ожидайте оппонента.");
|
setGameStatusMessage(data.message || "Свободных игр не найдено. Создана новая для вас, ожидайте оппонента.");
|
||||||
updateAvailableGamesList([]); // Очищаем список, т.к. мы теперь в ожидающей игре
|
updateAvailableGamesList([]);
|
||||||
isInGame = false; // Пока ждем, не в активной игре
|
if (data.gameId) {
|
||||||
disableGameControls(); // Кнопки боя отключены
|
currentGameId = data.gameId;
|
||||||
// Кнопки настройки игры должны оставаться отключенными, пока ждем игрока
|
myPlayerId = data.yourPlayerId; // Запоминаем наш технический ID слота
|
||||||
disableSetupButtons();
|
if (gameIdInput) gameIdInput.value = currentGameId;
|
||||||
|
console.log(`[Client] New game ${currentGameId} created after no pending games found. My slot: ${myPlayerId}`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- Начальное состояние UI ---
|
||||||
// --- Изначальное состояние UI при загрузке страницы ---
|
|
||||||
// При загрузке страницы всегда начинаем с Auth.
|
|
||||||
showAuthScreen();
|
showAuthScreen();
|
||||||
});
|
});
|
507
public/js/ui.js
507
public/js/ui.js
@ -56,7 +56,7 @@
|
|||||||
const li = document.createElement('li');
|
const li = document.createElement('li');
|
||||||
li.textContent = message;
|
li.textContent = message;
|
||||||
const config = window.GAME_CONFIG || {}; // Получаем конфиг из глобальной области
|
const config = window.GAME_CONFIG || {}; // Получаем конфиг из глобальной области
|
||||||
// Формируем класс для лога на основе типа (используем константы из конфига или фоллбэк)
|
// Формируем класс для лога на основе типа
|
||||||
const logTypeClass = config[`LOG_TYPE_${type.toUpperCase()}`] ? `log-${config[`LOG_TYPE_${type.toUpperCase()}`]}` : `log-${type}`;
|
const logTypeClass = config[`LOG_TYPE_${type.toUpperCase()}`] ? `log-${config[`LOG_TYPE_${type.toUpperCase()}`]}` : `log-${type}`;
|
||||||
li.className = logTypeClass;
|
li.className = logTypeClass;
|
||||||
logListElement.appendChild(li);
|
logListElement.appendChild(li);
|
||||||
@ -68,13 +68,12 @@
|
|||||||
const elements = uiElements[panelRole]; // 'player' или 'opponent'
|
const elements = uiElements[panelRole]; // 'player' или 'opponent'
|
||||||
const config = window.GAME_CONFIG || {};
|
const config = window.GAME_CONFIG || {};
|
||||||
|
|
||||||
// Базовая проверка наличия необходимых элементов и данных
|
|
||||||
if (!elements || !elements.hpFill || !elements.hpText || !elements.resourceFill || !elements.resourceText || !elements.status || !fighterState || !fighterBaseStats) {
|
if (!elements || !elements.hpFill || !elements.hpText || !elements.resourceFill || !elements.resourceText || !elements.status || !fighterState || !fighterBaseStats) {
|
||||||
// console.warn(`updateFighterPanelUI: Отсутствуют элементы UI, состояние бойца или базовые статы для панели ${panelRole}.`);
|
// console.warn(`updateFighterPanelUI: Отсутствуют элементы UI, состояние бойца или базовые статы для панели ${panelRole}.`);
|
||||||
// Если панель должна быть видима, но нет данных, можно ее скрыть или показать плейсхолдер
|
// Если панель должна быть видима, но нет данных, можно ее скрыть или показать плейсхолдер
|
||||||
if (elements && elements.panel && elements.panel.style.display !== 'none') {
|
if (elements && elements.panel && elements.panel.style.display !== 'none' && (!fighterState || !fighterBaseStats)) {
|
||||||
// console.warn(`updateFighterPanelUI: Нет данных для видимой панели ${panelRole}.`);
|
// console.warn(`updateFighterPanelUI: Нет данных для видимой панели ${panelRole}.`);
|
||||||
// elements.panel.style.opacity = '0.5'; // Пример: сделать полупрозрачной, если нет данных
|
// elements.panel.style.opacity = '0.3'; // Пример: сделать полупрозрачной
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -87,34 +86,23 @@
|
|||||||
// Обновление имени и иконки персонажа
|
// Обновление имени и иконки персонажа
|
||||||
if (elements.name) {
|
if (elements.name) {
|
||||||
let iconClass = 'fa-question'; // Иконка по умолчанию
|
let iconClass = 'fa-question'; // Иконка по умолчанию
|
||||||
// let accentColor = 'var(--text-muted)'; // Цвет по умолчанию - теперь берется из CSS через классы иконок
|
let accentColor = 'var(--text-muted)'; // Цвет по умолчанию
|
||||||
const characterKey = fighterBaseStats.characterKey;
|
const characterKey = fighterBaseStats.characterKey;
|
||||||
|
|
||||||
// Определяем класс иконки в зависимости от персонажа
|
if (characterKey === 'elena') { iconClass = 'fa-hat-wizard icon-player'; accentColor = 'var(--accent-player)'; }
|
||||||
if (characterKey === 'elena') { iconClass = 'fa-hat-wizard icon-player'; } // icon-player имеет цвет через CSS
|
else if (characterKey === 'almagest') { iconClass = 'fa-staff-aesculapius icon-almagest'; accentColor = 'var(--accent-almagest)'; }
|
||||||
else if (characterKey === 'almagest') { iconClass = 'fa-staff-aesculapius icon-almagest'; } // icon-almagest имеет цвет через CSS
|
else if (characterKey === 'balard') { iconClass = 'fa-khanda icon-opponent'; accentColor = 'var(--accent-opponent)'; }
|
||||||
else if (characterKey === 'balard') { iconClass = 'fa-khanda icon-opponent'; } // icon-opponent имеет цвет через CSS
|
else { /* console.warn(`updateFighterPanelUI: Неизвестный characterKey "${characterKey}" для иконки/цвета имени.`); */ }
|
||||||
else { /* console.warn(`updateFighterPanelUI: Неизвестный characterKey "${characterKey}" для иконки имени.`); */ }
|
|
||||||
|
|
||||||
// Обновляем innerHTML имени, включая иконку и текст. Добавляем "(Вы)" для управляемого персонажа.
|
|
||||||
let nameHtml = `<i class="fas ${iconClass}"></i> ${fighterBaseStats.name || 'Неизвестно'}`;
|
let nameHtml = `<i class="fas ${iconClass}"></i> ${fighterBaseStats.name || 'Неизвестно'}`;
|
||||||
if (isControlledByThisClient) nameHtml += " (Вы)";
|
if (isControlledByThisClient) nameHtml += " (Вы)";
|
||||||
elements.name.innerHTML = nameHtml;
|
elements.name.innerHTML = nameHtml;
|
||||||
// Цвет имени теперь задается CSS через классы icon-player/opponent/almagest, примененные к самой иконке
|
elements.name.style.color = accentColor;
|
||||||
// elements.name.style.color = accentColor; // Эту строку можно удалить, если цвет задан через CSS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновление аватара
|
// Обновление аватара
|
||||||
if (elements.avatar && fighterBaseStats.avatarPath) {
|
if (elements.avatar && fighterBaseStats.avatarPath) elements.avatar.src = fighterBaseStats.avatarPath;
|
||||||
elements.avatar.src = fighterBaseStats.avatarPath;
|
else if (elements.avatar) elements.avatar.src = 'images/default_avatar.png'; // Запасной аватар
|
||||||
// Обновляем рамку аватара в зависимости от персонажа
|
|
||||||
elements.avatar.classList.remove('avatar-elena', 'avatar-almagest', 'avatar-balard'); // Убираем старые классы
|
|
||||||
elements.avatar.classList.add(`avatar-${fighterBaseStats.characterKey}`); // Добавляем класс для текущего персонажа
|
|
||||||
} else if (elements.avatar) {
|
|
||||||
elements.avatar.src = 'images/default_avatar.png'; // Запасной аватар
|
|
||||||
elements.avatar.classList.remove('avatar-elena', 'avatar-almagest', 'avatar-balard'); // Убираем старые классы
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Обновление полос здоровья и ресурса
|
// Обновление полос здоровья и ресурса
|
||||||
const maxHp = Math.max(1, fighterBaseStats.maxHp); // Избегаем деления на ноль
|
const maxHp = Math.max(1, fighterBaseStats.maxHp); // Избегаем деления на ноль
|
||||||
@ -124,10 +112,8 @@
|
|||||||
|
|
||||||
elements.hpFill.style.width = `${(currentHp / maxHp) * 100}%`;
|
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.resourceFill.style.width = `${(currentRes / maxRes) * 100}%`;
|
||||||
elements.resourceText.textContent = `${currentRes} / ${fighterBaseStats.maxResource}`; // <-- ИСПРАВЛЕНО
|
elements.resourceText.textContent = `${Math.round(currentRes)} / ${fighterBaseStats.maxResource}`;
|
||||||
|
|
||||||
|
|
||||||
// Обновление типа ресурса и иконки (mana/stamina/dark-energy)
|
// Обновление типа ресурса и иконки (mana/stamina/dark-energy)
|
||||||
const resourceBarContainerToUpdate = (panelRole === 'player') ? uiElements.playerResourceBarContainer : uiElements.opponentResourceBarContainer;
|
const resourceBarContainerToUpdate = (panelRole === 'player') ? uiElements.playerResourceBarContainer : uiElements.opponentResourceBarContainer;
|
||||||
@ -135,39 +121,30 @@
|
|||||||
|
|
||||||
if (resourceBarContainerToUpdate && resourceIconElementToUpdate) {
|
if (resourceBarContainerToUpdate && resourceIconElementToUpdate) {
|
||||||
resourceBarContainerToUpdate.classList.remove('mana', 'stamina', 'dark-energy'); // Сначала удаляем все классы ресурсов
|
resourceBarContainerToUpdate.classList.remove('mana', 'stamina', 'dark-energy'); // Сначала удаляем все классы ресурсов
|
||||||
let resourceClass = 'mana'; let iconClass = 'fa-flask'; // Значения по умолчанию (для Маны)
|
let resourceClass = 'mana'; let iconClass = 'fa-flask'; // Значения по умолчанию (для Елены)
|
||||||
if (fighterBaseStats.resourceName === 'Ярость') { resourceClass = 'stamina'; iconClass = 'fa-fire-alt'; }
|
if (fighterBaseStats.resourceName === 'Ярость') { resourceClass = 'stamina'; iconClass = 'fa-fire-alt'; }
|
||||||
else if (fighterBaseStats.resourceName === 'Темная Энергия') { resourceClass = 'dark-energy'; iconClass = 'fa-skull'; } // Или другую иконку для темной энергии
|
else if (fighterBaseStats.resourceName === 'Темная Энергия') { resourceClass = 'dark-energy'; iconClass = 'fa-skull'; }
|
||||||
resourceBarContainerToUpdate.classList.add(resourceClass);
|
resourceBarContainerToUpdate.classList.add(resourceClass);
|
||||||
resourceIconElementToUpdate.className = `fas ${iconClass}`; // Обновляем класс иконки
|
resourceIconElementToUpdate.className = `fas ${iconClass}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновление статуса (Готов/Защищается)
|
// Обновление статуса (Готов/Защищается)
|
||||||
const statusText = fighterState.isBlocking ? (config.STATUS_BLOCKING || 'Защищается') : (config.STATUS_READY || 'Готов(а)');
|
const statusText = fighterState.isBlocking ? (config.STATUS_BLOCKING || 'Защищается') : (config.STATUS_READY || 'Готов(а)');
|
||||||
elements.status.textContent = statusText;
|
elements.status.textContent = statusText;
|
||||||
elements.status.classList.toggle(config.CSS_CLASS_BLOCKING || 'blocking', fighterState.isBlocking); // Применяем класс для стилизации статуса "Защищается"
|
elements.status.classList.toggle(config.CSS_CLASS_BLOCKING || 'blocking', fighterState.isBlocking);
|
||||||
|
|
||||||
|
// Обновление подсветки и рамки панели
|
||||||
// Обновление подсветки и рамки панели (в зависимости от персонажа)
|
|
||||||
if (elements.panel) {
|
if (elements.panel) {
|
||||||
let borderColorVar = 'var(--panel-border)'; // Цвет по умолчанию
|
let glowColorVar = '--panel-glow-opponent';
|
||||||
// Снимаем все старые классы для рамки
|
let borderColorVar = 'var(--accent-opponent)'; // По умолчанию для оппонента
|
||||||
elements.panel.classList.remove('panel-elena', 'panel-almagest', 'panel-balard');
|
if (fighterBaseStats.characterKey === 'elena') { glowColorVar = '--panel-glow-player'; borderColorVar = 'var(--accent-player)'; }
|
||||||
|
else if (fighterBaseStats.characterKey === 'almagest') { glowColorVar = 'var(--panel-glow-opponent)'; borderColorVar = 'var(--accent-almagest)'; } // Для Альмагест используется ее цвет рамки
|
||||||
|
else if (fighterBaseStats.characterKey === 'balard') { glowColorVar = 'var(--panel-glow-opponent)'; borderColorVar = 'var(--accent-opponent)'; }
|
||||||
|
else { borderColorVar = 'var(--panel-border)'; } // Фоллбэк
|
||||||
|
|
||||||
// Применяем класс для рамки в зависимости от персонажа
|
|
||||||
if (fighterBaseStats.characterKey === 'elena') { elements.panel.classList.add('panel-elena'); borderColorVar = 'var(--accent-player)'; } // Цвет рамки через CSS переменную
|
|
||||||
else if (fighterBaseStats.characterKey === 'almagest') { elements.panel.classList.add('panel-almagest'); borderColorVar = 'var(--accent-almagest)'; } // Цвет рамки через CSS переменную
|
|
||||||
else if (fighterBaseStats.characterKey === 'balard') { elements.panel.classList.add('panel-balard'); borderColorVar = 'var(--accent-opponent)'; } // Цвет рамки через CSS переменную
|
|
||||||
|
|
||||||
// Обновляем тень (свечение). Цвет свечения тоже может быть переменной.
|
|
||||||
let glowColorVar = 'rgba(0, 0, 0, 0.4)'; // Базовая тень
|
|
||||||
if (fighterBaseStats.characterKey === 'elena') glowColorVar = 'var(--panel-glow-player)';
|
|
||||||
else if (fighterBaseStats.characterKey === 'almagest' || fighterBaseStats.characterKey === 'balard') glowColorVar = 'var(--panel-glow-opponent)'; // Используем одну тень для всех оппонентов (Балард/Альмагест)
|
|
||||||
|
|
||||||
// Устанавливаем рамку и тень
|
|
||||||
elements.panel.style.borderColor = borderColorVar;
|
elements.panel.style.borderColor = borderColorVar;
|
||||||
// Используем переменную для свечения. Базовая тень inset оставлена как есть.
|
// Убедимся, что переменная glowColorVar существует, иначе тень может не примениться или вызвать ошибку
|
||||||
elements.panel.style.boxShadow = `0 0 15px ${glowColorVar}, inset 0 0 10px rgba(0, 0, 0, 0.3)`;
|
elements.panel.style.boxShadow = glowColorVar ? `0 0 15px ${glowColorVar}, inset 0 0 10px rgba(0, 0, 0, 0.3)` : `0 0 15px rgba(0,0,0,0.4), inset 0 0 10px rgba(0,0,0,0.3)`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,115 +152,47 @@
|
|||||||
const config = window.GAME_CONFIG || {};
|
const config = window.GAME_CONFIG || {};
|
||||||
if (!effectsArray || effectsArray.length === 0) return 'Нет';
|
if (!effectsArray || effectsArray.length === 0) return 'Нет';
|
||||||
|
|
||||||
// Сортируем эффекты: сначала положительные (buff, block, heal), затем отрицательные (debuff, disable)
|
return effectsArray.map(eff => {
|
||||||
// иконка для стана/безмолвия, иконка для ослабления, иконка для усиления
|
let effectClasses = config.CSS_CLASS_EFFECT || 'effect';
|
||||||
const sortedEffects = [...effectsArray].sort((a, b) => {
|
|
||||||
const typeOrder = {
|
|
||||||
[config.ACTION_TYPE_BUFF]: 1,
|
|
||||||
grantsBlock: 2,
|
|
||||||
[config.ACTION_TYPE_HEAL]: 3, // HoT эффекты
|
|
||||||
[config.ACTION_TYPE_DEBUFF]: 4, // DoT, ресурсные дебаффы
|
|
||||||
[config.ACTION_TYPE_DISABLE]: 5 // Silence, Stun
|
|
||||||
};
|
|
||||||
// Определяем порядок для эффекта A
|
|
||||||
let orderA = typeOrder[a.type];
|
|
||||||
if (a.grantsBlock) orderA = typeOrder.grantsBlock;
|
|
||||||
if (a.isFullSilence || a.id.startsWith('playerSilencedOn_')) orderA = typeOrder[config.ACTION_TYPE_DISABLE];
|
|
||||||
|
|
||||||
// Определяем порядок для эффекта B
|
|
||||||
let orderB = typeOrder[b.type];
|
|
||||||
if (b.grantsBlock) orderB = typeOrder.grantsBlock;
|
|
||||||
if (b.isFullSilence || b.id.startsWith('playerSilencedOn_')) orderB = typeOrder[config.ACTION_TYPE_DISABLE];
|
|
||||||
|
|
||||||
return (orderA || 99) - (orderB || 99); // Сортируем по порядку, неизвестные типы в конец
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
return sortedEffects.map(eff => {
|
|
||||||
let effectClasses = config.CSS_CLASS_EFFECT || 'effect'; // Базовый класс для всех эффектов
|
|
||||||
// Формируем заголовок тултипа
|
|
||||||
const title = `${eff.name}${eff.description ? ` - ${eff.description}` : ''} (Осталось: ${eff.turnsLeft} х.)`;
|
const title = `${eff.name}${eff.description ? ` - ${eff.description}` : ''} (Осталось: ${eff.turnsLeft} х.)`;
|
||||||
// Текст, отображаемый на самой плашке эффекта
|
|
||||||
const displayText = `${eff.name} (${eff.turnsLeft} х.)`;
|
const displayText = `${eff.name} (${eff.turnsLeft} х.)`;
|
||||||
|
|
||||||
// Добавляем специфичные классы для стилизации по типу эффекта
|
if (eff.isFullSilence || eff.id.startsWith('playerSilencedOn_') || (eff.type === config.ACTION_TYPE_DISABLE && !eff.grantsBlock) ) {
|
||||||
if (eff.isFullSilence || eff.id.startsWith('playerSilencedOn_') || (eff.type === config.ACTION_TYPE_DISABLE)) { // Эффекты полного безмолвия или специфичного заглушения
|
effectClasses += ' effect-stun'; // Стан/безмолвие
|
||||||
effectClasses += ' effect-stun'; // Класс для стана/безмолвия
|
} else if (eff.grantsBlock) {
|
||||||
} else if (eff.grantsBlock) { // Эффекты, дающие блок
|
effectClasses += ' effect-block'; // Эффект блока
|
||||||
effectClasses += ' effect-block'; // Класс для эффектов блока
|
} else if (eff.type === config.ACTION_TYPE_DEBUFF || (eff.power && eff.power < 0 && eff.type !== config.ACTION_TYPE_HEAL )) {
|
||||||
} else if (eff.type === config.ACTION_TYPE_DEBUFF) { // Явные дебаффы (например, сжигание ресурса, DoT)
|
effectClasses += ' effect-debuff'; // Ослабления, DoT
|
||||||
effectClasses += ' effect-debuff'; // Класс для ослаблений
|
} else { // ACTION_TYPE_BUFF или положительные эффекты (например, HoT)
|
||||||
} else if (eff.type === config.ACTION_TYPE_BUFF || eff.type === config.ACTION_TYPE_HEAL) { // Явные баффы или эффекты HoT
|
effectClasses += ' effect-buff';
|
||||||
effectClasses += ' effect-buff'; // Класс для усилений
|
|
||||||
} else {
|
|
||||||
// console.warn(`generateEffectsHTML: Неизвестный тип эффекта для стилизации: ${eff.type} (ID: ${eff.id})`);
|
|
||||||
effectClasses += ' effect-info'; // Класс по умолчанию или информационный
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return `<span class="${effectClasses}" title="${title}">${displayText}</span>`;
|
return `<span class="${effectClasses}" title="${title}">${displayText}</span>`;
|
||||||
}).join(' '); // Объединяем все HTML-строки эффектов в одну
|
}).join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateEffectsUI(currentGameState) {
|
function updateEffectsUI(currentGameState) {
|
||||||
if (!currentGameState || !window.GAME_CONFIG) { return; }
|
if (!currentGameState || !window.GAME_CONFIG) { return; }
|
||||||
const mySlotId = window.myPlayerId; // Технический ID слота этого клиента
|
const mySlotId = window.myPlayerId; // Технический ID слота этого клиента
|
||||||
const config = window.GAME_CONFIG;
|
|
||||||
if (!mySlotId) { return; }
|
if (!mySlotId) { return; }
|
||||||
|
|
||||||
const opponentSlotId = mySlotId === config.PLAYER_ID ? config.OPPONENT_ID : config.PLAYER_ID;
|
const opponentSlotId = mySlotId === window.GAME_CONFIG.PLAYER_ID ? window.GAME_CONFIG.OPPONENT_ID : window.GAME_CONFIG.PLAYER_ID;
|
||||||
|
|
||||||
const myState = currentGameState[mySlotId]; // Состояние персонажа этого клиента
|
const myState = currentGameState[mySlotId]; // Состояние персонажа этого клиента
|
||||||
if (uiElements.player && uiElements.player.buffsList && uiElements.player.debuffsList && myState && myState.activeEffects) {
|
if (uiElements.player && uiElements.player.buffsList && uiElements.player.debuffsList && myState && myState.activeEffects) {
|
||||||
// Разделяем эффекты на баффы и дебаффы для отображения
|
uiElements.player.buffsList.innerHTML = generateEffectsHTML(myState.activeEffects.filter(e => e.type === window.GAME_CONFIG.ACTION_TYPE_BUFF || e.grantsBlock || (e.type === window.GAME_CONFIG.ACTION_TYPE_HEAL && e.turnsLeft > 0) ));
|
||||||
// Критерии разделения могут быть специфичны для игры:
|
uiElements.player.debuffsList.innerHTML = generateEffectsHTML(myState.activeEffects.filter(e => e.type !== window.GAME_CONFIG.ACTION_TYPE_BUFF && !e.grantsBlock && !(e.type === window.GAME_CONFIG.ACTION_TYPE_HEAL && e.turnsLeft > 0) ));
|
||||||
// Баффы: тип BUFF, эффекты дающие блок (grantsBlock), эффекты лечения (HoT)
|
|
||||||
// Дебаффы: тип DEBUFF, тип DISABLE (кроме grantsBlock), эффекты урона (DoT)
|
|
||||||
// Включаем isFullSilence и playerSilencedOn_X в "дебаффы" для отображения (можно сделать отдельную категорию)
|
|
||||||
const myBuffs = myState.activeEffects.filter(e =>
|
|
||||||
e.type === config.ACTION_TYPE_BUFF || e.grantsBlock || (e.type === config.ACTION_TYPE_HEAL) // HoT как бафф
|
|
||||||
);
|
|
||||||
const myDebuffs = myState.activeEffects.filter(e =>
|
|
||||||
e.type === config.ACTION_TYPE_DEBUFF ||
|
|
||||||
e.type === config.ACTION_TYPE_DISABLE // Disable как дебафф
|
|
||||||
// || (e.type === config.ACTION_TYPE_DAMAGE) // DoT как дебафф, если есть
|
|
||||||
);
|
|
||||||
// Специально добавляем полные безмолвия и заглушения абилок в дебаффы, даже если их тип не DEBUFF/DISABLE
|
|
||||||
myDebuffs.push(...myState.activeEffects.filter(e => e.isFullSilence || e.id.startsWith('playerSilencedOn_') && !myDebuffs.some(d => d.id === e.id))); // Избегаем дублирования
|
|
||||||
|
|
||||||
uiElements.player.buffsList.innerHTML = generateEffectsHTML(myBuffs);
|
|
||||||
uiElements.player.debuffsList.innerHTML = generateEffectsHTML(myDebuffs);
|
|
||||||
} else if (uiElements.player && uiElements.player.buffsList && uiElements.player.debuffsList) {
|
|
||||||
// Если нет активных эффектов или состояния, очищаем списки
|
|
||||||
uiElements.player.buffsList.innerHTML = 'Нет';
|
|
||||||
uiElements.player.debuffsList.innerHTML = 'Нет';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const opponentState = currentGameState[opponentSlotId]; // Состояние оппонента этого клиента
|
const opponentState = currentGameState[opponentSlotId]; // Состояние оппонента этого клиента
|
||||||
if (uiElements.opponent && uiElements.opponent.buffsList && uiElements.opponent.debuffsList && opponentState && opponentState.activeEffects) {
|
if (uiElements.opponent && uiElements.opponent.buffsList && uiElements.opponent.debuffsList && opponentState && opponentState.activeEffects) {
|
||||||
const opponentBuffs = opponentState.activeEffects.filter(e =>
|
uiElements.opponent.buffsList.innerHTML = generateEffectsHTML(opponentState.activeEffects.filter(e => e.type === window.GAME_CONFIG.ACTION_TYPE_BUFF || e.grantsBlock || (e.type === window.GAME_CONFIG.ACTION_TYPE_HEAL && e.turnsLeft > 0) ));
|
||||||
e.type === config.ACTION_TYPE_BUFF || e.grantsBlock || (e.type === config.ACTION_TYPE_HEAL) // HoT как бафф
|
uiElements.opponent.debuffsList.innerHTML = generateEffectsHTML(opponentState.activeEffects.filter(e => e.type !== window.GAME_CONFIG.ACTION_TYPE_BUFF && !e.grantsBlock && !(e.type === window.GAME_CONFIG.ACTION_TYPE_HEAL && e.turnsLeft > 0) ));
|
||||||
);
|
|
||||||
const opponentDebuffs = opponentState.activeEffects.filter(e =>
|
|
||||||
e.type === config.ACTION_TYPE_DEBUFF ||
|
|
||||||
e.type === config.ACTION_TYPE_DISABLE // Disable как дебафф
|
|
||||||
// || (e.type === config.ACTION_TYPE_DAMAGE) // DoT как дебафф, если есть
|
|
||||||
);
|
|
||||||
// Специально добавляем полные безмолвия и заглушения абилок в дебаффы оппонента, даже если их тип не DEBUFF/DISABLE
|
|
||||||
opponentDebuffs.push(...opponentState.activeEffects.filter(e => e.isFullSilence || e.id.startsWith('playerSilencedOn_') && !opponentDebuffs.some(d => d.id === e.id))); // Избегаем дублирования
|
|
||||||
|
|
||||||
uiElements.opponent.buffsList.innerHTML = generateEffectsHTML(opponentBuffs);
|
|
||||||
uiElements.opponent.debuffsList.innerHTML = generateEffectsHTML(opponentDebuffs);
|
|
||||||
} else if (uiElements.opponent && uiElements.opponent.buffsList && uiElements.opponent.debuffsList) {
|
|
||||||
// Если нет активных эффектов или состояния оппонента, очищаем списки
|
|
||||||
uiElements.opponent.buffsList.innerHTML = 'Нет';
|
|
||||||
uiElements.opponent.debuffsList.innerHTML = 'Нет';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateUI() {
|
function updateUI() {
|
||||||
const currentGameState = window.gameState; // Глобальное состояние игры
|
const currentGameState = window.gameState; // Глобальное состояние игры
|
||||||
const gameDataGlobal = window.gameData; // Глобальные данные ( статы, абилки ) для этого клиента
|
const gameDataGlobal = window.gameData; // Глобальные данные (статы, абилки) для этого клиента
|
||||||
const configGlobal = window.GAME_CONFIG; // Глобальный конфиг
|
const configGlobal = window.GAME_CONFIG; // Глобальный конфиг
|
||||||
const myActualPlayerId = window.myPlayerId; // Технический ID слота этого клиента
|
const myActualPlayerId = window.myPlayerId; // Технический ID слота этого клиента
|
||||||
|
|
||||||
@ -298,88 +207,77 @@
|
|||||||
|
|
||||||
// Определяем, чей сейчас ход по ID слота
|
// Определяем, чей сейчас ход по ID слота
|
||||||
const actorSlotWhoseTurnItIs = currentGameState.isPlayerTurn ? configGlobal.PLAYER_ID : configGlobal.OPPONENT_ID;
|
const actorSlotWhoseTurnItIs = currentGameState.isPlayerTurn ? configGlobal.PLAYER_ID : configGlobal.OPPONENT_ID;
|
||||||
// Определяем ID слота оппонента для этого клиента ( необходимо для определения, чьи панели обновлять как "мои" и "противника")
|
// Определяем ID слота оппонента для этого клиента
|
||||||
const opponentActualSlotId = myActualPlayerId === configGlobal.PLAYER_ID ? configGlobal.OPPONENT_ID : configGlobal.PLAYER_ID;
|
const opponentActualSlotId = myActualPlayerId === configGlobal.PLAYER_ID ? configGlobal.OPPONENT_ID : configGlobal.PLAYER_ID;
|
||||||
|
|
||||||
// Обновление панели "моего" персонажа ( которым управляет этот клиент)
|
// Обновление панели "моего" персонажа
|
||||||
const myStateInGameState = currentGameState[myActualPlayerId];
|
if (gameDataGlobal.playerBaseStats && currentGameState[myActualPlayerId]) {
|
||||||
const myBaseStatsForUI = gameDataGlobal.playerBaseStats; // playerBaseStats в gameData - это всегда статы персонажа этого клиента
|
if (uiElements.player.panel && uiElements.player.panel.style.opacity !== '1') uiElements.player.panel.style.opacity = '1';
|
||||||
if (myStateInGameState && myBaseStatsForUI) {
|
updateFighterPanelUI('player', currentGameState[myActualPlayerId], gameDataGlobal.playerBaseStats, true);
|
||||||
if (uiElements.player.panel) uiElements.player.panel.style.opacity = '1'; // Делаем панель полностью видимой, если есть данные
|
|
||||||
updateFighterPanelUI('player', myStateInGameState, myBaseStatsForUI, true);
|
|
||||||
} else {
|
} else {
|
||||||
if (uiElements.player.panel) uiElements.player.panel.style.opacity = '0.5'; // Затемняем, если нет данных
|
if (uiElements.player.panel) uiElements.player.panel.style.opacity = '0.5';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновление панели "моего оппонента" ( персонажа в слоте противника для этого клиента)
|
// Обновление панели "моего оппонента"
|
||||||
const opponentStateInGameState = currentGameState[opponentActualSlotId];
|
if (gameDataGlobal.opponentBaseStats && currentGameState[opponentActualSlotId]) {
|
||||||
const opponentBaseStatsForUI = gameDataGlobal.opponentBaseStats; // opponentBaseStats в gameData - это всегда статы оппонента этого клиента
|
if (uiElements.opponent.panel && uiElements.opponent.panel.style.opacity !== '1' && currentGameState.isGameOver === false ) {
|
||||||
if (opponentStateInGameState && opponentBaseStatsForUI) {
|
// Если панель оппонента была "затемнена" и игра не окончена, восстанавливаем видимость
|
||||||
// Если игра не окончена, а панель оппонента "тает" или не полностью видна, восстанавливаем это
|
uiElements.opponent.panel.style.opacity = '1';
|
||||||
if (uiElements.opponent.panel && (uiElements.opponent.panel.style.opacity !== '1' || uiElements.opponent.panel.classList.contains('dissolving')) && currentGameState.isGameOver === false ) {
|
}
|
||||||
console.log("[UI UPDATE DEBUG] Opponent panel not fully visible/dissolving but game not over. Restoring opacity/transform.");
|
// Перед обновлением, если игра НЕ окончена, а панель "тает", отменяем это
|
||||||
|
if (uiElements.opponent.panel && uiElements.opponent.panel.classList.contains('dissolving') && currentGameState.isGameOver === false) {
|
||||||
|
console.warn("[UI UPDATE DEBUG] Opponent panel has .dissolving but game is NOT over. Forcing visible.");
|
||||||
const panel = uiElements.opponent.panel;
|
const panel = uiElements.opponent.panel;
|
||||||
panel.classList.remove('dissolving');
|
panel.classList.remove('dissolving');
|
||||||
// Force reflow before applying instant style change
|
const originalTransition = panel.style.transition; panel.style.transition = 'none';
|
||||||
panel.offsetHeight; // Trigger reflow
|
panel.style.opacity = '1'; panel.style.transform = 'scale(1) translateY(0)';
|
||||||
panel.style.opacity = '1';
|
requestAnimationFrame(() => { panel.style.transition = originalTransition || ''; });
|
||||||
panel.style.transform = 'scale(1) translateY(0)';
|
|
||||||
} else if (uiElements.opponent.panel) {
|
|
||||||
uiElements.opponent.panel.style.opacity = '1'; // Убеждаемся, что видна, если есть данные
|
|
||||||
}
|
}
|
||||||
updateFighterPanelUI('opponent', opponentStateInGameState, opponentBaseStatsForUI, false);
|
updateFighterPanelUI('opponent', currentGameState[opponentActualSlotId], gameDataGlobal.opponentBaseStats, false);
|
||||||
} else {
|
} else {
|
||||||
// Нет данных оппонента ( например, PvP игра ожидает игрока). Затемняем панель.
|
if (uiElements.opponent.panel) uiElements.opponent.panel.style.opacity = '0.5';
|
||||||
if (uiElements.opponent.panel) {
|
|
||||||
uiElements.opponent.panel.style.opacity = '0.5';
|
|
||||||
// Очищаем панель, если данных нет
|
|
||||||
if(uiElements.opponent.name) uiElements.opponent.name.innerHTML = '<i class="fas fa-question icon-opponent"></i> Ожидание игрока...';
|
|
||||||
if(uiElements.opponent.hpText) uiElements.opponent.hpText.textContent = 'N/A';
|
|
||||||
if(uiElements.opponent.resourceText) uiElements.opponent.resourceText.textContent = 'N/A';
|
|
||||||
if(uiElements.opponent.status) uiElements.opponent.status.textContent = 'Не готов';
|
|
||||||
if(uiElements.opponent.buffsList) uiElements.opponent.buffsList.innerHTML = 'Нет';
|
|
||||||
if(uiElements.opponent.debuffsList) uiElements.opponent.debuffsList.innerHTML = 'Нет';
|
|
||||||
if(uiElements.opponent.avatar) uiElements.opponent.avatar.src = 'images/default_avatar.png';
|
|
||||||
if(uiElements.opponentResourceTypeIcon) uiElements.opponentResourceTypeIcon.className = 'fas fa-question';
|
|
||||||
if(uiElements.opponentResourceBarContainer) uiElements.opponentResourceBarContainer.classList.remove('mana', 'stamina', 'dark-energy');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
updateEffectsUI(currentGameState);
|
updateEffectsUI(currentGameState);
|
||||||
|
|
||||||
// Обновление заголовка игры ( Имя1 vs Имя2)
|
// Обновление заголовка игры (Имя1 vs Имя2)
|
||||||
if (uiElements.gameHeaderTitle && gameDataGlobal.playerBaseStats && gameDataGlobal.opponentBaseStats) {
|
if (uiElements.gameHeaderTitle && gameDataGlobal.playerBaseStats && gameDataGlobal.opponentBaseStats) {
|
||||||
const myName = gameDataGlobal.playerBaseStats.name; // Имя моего персонажа
|
const myName = gameDataGlobal.playerBaseStats.name; // Имя моего персонажа
|
||||||
const opponentName = gameDataGlobal.opponentBaseStats.name; // Имя моего оппонента
|
const opponentName = gameDataGlobal.opponentBaseStats.name; // Имя моего оппонента
|
||||||
const myKey = gameDataGlobal.playerBaseStats.characterKey;
|
const myKey = gameDataGlobal.playerBaseStats.characterKey;
|
||||||
const opponentKey = gameDataGlobal.opponentBaseStats.characterKey;
|
const opponentKey = gameDataGlobal.opponentBaseStats.characterKey;
|
||||||
|
|
||||||
let myClass = 'title-player'; // Базовый класс
|
let myClass = 'title-player';
|
||||||
if (myKey === 'elena') myClass = 'title-enchantress';
|
if (myKey === 'elena') myClass = 'title-enchantress'; else if (myKey === 'almagest') myClass = 'title-sorceress';
|
||||||
else if (myKey === 'almagest') myClass = 'title-sorceress';
|
|
||||||
|
|
||||||
let opponentClass = 'title-opponent'; // Базовый класс
|
let opponentClass = 'title-opponent';
|
||||||
if (opponentKey === 'elena') opponentClass = 'title-enchantress';
|
if (opponentKey === 'elena') opponentClass = 'title-enchantress'; else if (opponentKey === 'almagest') opponentClass = 'title-sorceress'; else if (opponentKey === 'balard') opponentClass = 'title-knight';
|
||||||
else if (opponentKey === 'almagest') opponentClass = 'title-sorceress';
|
|
||||||
else if (opponentKey === 'balard') opponentClass = 'title-knight';
|
|
||||||
|
|
||||||
// Используем имена персонажей, которые видит этот клиент
|
|
||||||
uiElements.gameHeaderTitle.innerHTML = `<span class="${myClass}">${myName}</span> <span class="separator"><i class="fas fa-fist-raised"></i></span> <span class="${opponentClass}">${opponentName}</span>`;
|
uiElements.gameHeaderTitle.innerHTML = `<span class="${myClass}">${myName}</span> <span class="separator"><i class="fas fa-fist-raised"></i></span> <span class="${opponentClass}">${opponentName}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Обновление индикатора хода
|
||||||
|
if (uiElements.controls.turnIndicator) {
|
||||||
|
// Имя того, чей ход, берем из gameState по ID слота
|
||||||
|
const currentTurnActorState = currentGameState[actorSlotWhoseTurnItIs]; // 'player' или 'opponent'
|
||||||
|
const currentTurnName = currentTurnActorState?.name || 'Неизвестно';
|
||||||
|
uiElements.controls.turnIndicator.textContent = `Ход: ${currentTurnName}`;
|
||||||
|
|
||||||
|
const currentTurnCharacterKey = currentTurnActorState?.characterKey;
|
||||||
|
let turnColor = 'var(--turn-color)';
|
||||||
|
if (currentTurnCharacterKey === 'elena') turnColor = 'var(--accent-player)';
|
||||||
|
else if (currentTurnCharacterKey === 'almagest') turnColor = 'var(--accent-almagest)';
|
||||||
|
else if (currentTurnCharacterKey === 'balard') turnColor = 'var(--accent-opponent)';
|
||||||
|
uiElements.controls.turnIndicator.style.color = turnColor;
|
||||||
|
}
|
||||||
|
|
||||||
// Управление активностью кнопок
|
// Управление активностью кнопок
|
||||||
// Этот клиент может действовать только если его технический слот ('player' или 'opponent')
|
const canThisClientAct = actorSlotWhoseTurnItIs === myActualPlayerId; // Может ли ЭТОТ клиент ходить
|
||||||
// соответствует слоту, чей сейчас ход ( actorSlotWhoseTurnItIs).
|
|
||||||
const canThisClientAct = actorSlotWhoseTurnItIs === myActualPlayerId;
|
|
||||||
const isGameActive = !currentGameState.isGameOver;
|
const isGameActive = !currentGameState.isGameOver;
|
||||||
|
|
||||||
// Кнопка атаки
|
// Кнопка атаки
|
||||||
if (uiElements.controls.buttonAttack) {
|
if (uiElements.controls.buttonAttack) {
|
||||||
// Кнопка атаки активна, если это ход этого клиента и игра активна
|
|
||||||
uiElements.controls.buttonAttack.disabled = !(canThisClientAct && isGameActive);
|
uiElements.controls.buttonAttack.disabled = !(canThisClientAct && isGameActive);
|
||||||
|
|
||||||
// Управление классом для подсветки бафнутой атаки
|
|
||||||
const myCharKey = gameDataGlobal.playerBaseStats?.characterKey;
|
const myCharKey = gameDataGlobal.playerBaseStats?.characterKey;
|
||||||
const myStateForAttackBuff = currentGameState[myActualPlayerId]; // Состояние моего персонажа
|
const myStateForAttackBuff = currentGameState[myActualPlayerId]; // Состояние моего персонажа
|
||||||
let attackBuffId = null;
|
let attackBuffId = null;
|
||||||
@ -387,9 +285,7 @@
|
|||||||
else if (myCharKey === 'almagest') attackBuffId = configGlobal.ABILITY_ID_ALMAGEST_BUFF_ATTACK;
|
else if (myCharKey === 'almagest') attackBuffId = configGlobal.ABILITY_ID_ALMAGEST_BUFF_ATTACK;
|
||||||
|
|
||||||
if (attackBuffId && myStateForAttackBuff && myStateForAttackBuff.activeEffects) {
|
if (attackBuffId && myStateForAttackBuff && myStateForAttackBuff.activeEffects) {
|
||||||
// Проверяем, есть ли активный бафф атаки И он не только что наложен в этом ходу
|
const isAttackBuffReady = myStateForAttackBuff.activeEffects.some(eff => eff.id === attackBuffId && !eff.justCast);
|
||||||
const isAttackBuffReady = myStateForAttackBuff.activeEffects.some(eff => eff.id === attackBuffId && !eff.justCast && eff.turnsLeft > 0);
|
|
||||||
// Подсветка активна, если бафф готов И это ход этого клиента И игра активна
|
|
||||||
uiElements.controls.buttonAttack.classList.toggle(configGlobal.CSS_CLASS_ATTACK_BUFFED || 'attack-buffed', isAttackBuffReady && canThisClientAct && isGameActive);
|
uiElements.controls.buttonAttack.classList.toggle(configGlobal.CSS_CLASS_ATTACK_BUFFED || 'attack-buffed', isAttackBuffReady && canThisClientAct && isGameActive);
|
||||||
} else {
|
} else {
|
||||||
uiElements.controls.buttonAttack.classList.remove(configGlobal.CSS_CLASS_ATTACK_BUFFED || 'attack-buffed');
|
uiElements.controls.buttonAttack.classList.remove(configGlobal.CSS_CLASS_ATTACK_BUFFED || 'attack-buffed');
|
||||||
@ -399,74 +295,39 @@
|
|||||||
|
|
||||||
// Кнопки способностей
|
// Кнопки способностей
|
||||||
const actingPlayerState = currentGameState[myActualPlayerId]; // Состояние моего персонажа
|
const actingPlayerState = currentGameState[myActualPlayerId]; // Состояние моего персонажа
|
||||||
const actingPlayerAbilities = gameDataGlobal.playerAbilities; // Способности моего персонажа (с точки зрения клиента)
|
const actingPlayerAbilities = gameDataGlobal.playerAbilities; // Способности моего персонажа
|
||||||
const actingPlayerResourceName = gameDataGlobal.playerBaseStats?.resourceName; // Имя ресурса моего персонажа
|
const actingPlayerResourceName = gameDataGlobal.playerBaseStats?.resourceName;
|
||||||
|
|
||||||
uiElements.controls.abilitiesGrid?.querySelectorAll(`.${configGlobal.CSS_CLASS_ABILITY_BUTTON || 'ability-button'}`).forEach(button => {
|
uiElements.controls.abilitiesGrid?.querySelectorAll(`.${configGlobal.CSS_CLASS_ABILITY_BUTTON || 'ability-button'}`).forEach(button => {
|
||||||
if (!(button instanceof HTMLButtonElement) || !actingPlayerState || !actingPlayerAbilities || !actingPlayerResourceName || !isGameActive) {
|
if (!(button instanceof HTMLButtonElement) || !actingPlayerState || !actingPlayerAbilities || !actingPlayerResourceName) {
|
||||||
// Если нет необходимых данных или игра неактивна, дизейблим все кнопки способностей
|
|
||||||
if(button instanceof HTMLButtonElement) button.disabled = true;
|
if(button instanceof HTMLButtonElement) button.disabled = true;
|
||||||
button.classList.remove(configGlobal.CSS_CLASS_NOT_ENOUGH_RESOURCE||'not-enough-resource', configGlobal.CSS_CLASS_BUFF_IS_ACTIVE||'buff-is-active', configGlobal.CSS_CLASS_ABILITY_SILENCED||'is-silenced', configGlobal.CSS_CLASS_ABILITY_ON_COOLDOWN||'is-on-cooldown');
|
|
||||||
const cooldownDisplay = button.querySelector('.ability-cooldown-display');
|
|
||||||
if (cooldownDisplay) cooldownDisplay.style.display = 'none';
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const abilityId = button.dataset.abilityId;
|
const abilityId = button.dataset.abilityId;
|
||||||
const ability = actingPlayerAbilities.find(ab => ab.id === abilityId);
|
const ability = actingPlayerAbilities.find(ab => ab.id === abilityId);
|
||||||
if (!ability) {
|
if (!ability) { button.disabled = true; return; }
|
||||||
button.disabled = true; // Если способность не найдена в данных (ошибка)
|
|
||||||
button.classList.remove(configGlobal.CSS_CLASS_NOT_ENOUGH_RESOURCE||'not-enough-resource', configGlobal.CSS_CLASS_BUFF_IS_ACTIVE||'buff-is-active', configGlobal.CSS_CLASS_ABILITY_SILENCED||'is-silenced', configGlobal.CSS_CLASS_ABILITY_ON_COOLDOWN||'is-on-cooldown');
|
|
||||||
const cooldownDisplay = button.querySelector('.ability-cooldown-display');
|
|
||||||
if (cooldownDisplay) cooldownDisplay.style.display = 'none';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверяем условия доступности способности
|
|
||||||
const hasEnoughResource = actingPlayerState.currentResource >= ability.cost;
|
const hasEnoughResource = actingPlayerState.currentResource >= ability.cost;
|
||||||
// Бафф уже активен (для баффов, которые не стакаются)
|
|
||||||
const isBuffAlreadyActive = ability.type === configGlobal.ACTION_TYPE_BUFF && actingPlayerState.activeEffects?.some(eff => eff.id === ability.id);
|
const isBuffAlreadyActive = ability.type === configGlobal.ACTION_TYPE_BUFF && actingPlayerState.activeEffects?.some(eff => eff.id === ability.id);
|
||||||
// На общем кулдауне
|
|
||||||
const isOnCooldown = (actingPlayerState.abilityCooldowns?.[ability.id] || 0) > 0;
|
const isOnCooldown = (actingPlayerState.abilityCooldowns?.[ability.id] || 0) > 0;
|
||||||
// Под полным безмолвием
|
|
||||||
const isGenerallySilenced = actingPlayerState.activeEffects?.some(eff => eff.isFullSilence && eff.turnsLeft > 0);
|
const isGenerallySilenced = actingPlayerState.activeEffects?.some(eff => eff.isFullSilence && eff.turnsLeft > 0);
|
||||||
// Под специфическим заглушением этой способности
|
|
||||||
const specificSilenceEffect = actingPlayerState.disabledAbilities?.find(dis => dis.abilityId === abilityId && dis.turnsLeft > 0);
|
const specificSilenceEffect = actingPlayerState.disabledAbilities?.find(dis => dis.abilityId === abilityId && dis.turnsLeft > 0);
|
||||||
const isSpecificallySilenced = !!specificSilenceEffect;
|
const isSpecificallySilenced = !!specificSilenceEffect;
|
||||||
const isSilenced = isGenerallySilenced || isSpecificallySilenced; // Считается заглушенным, если под полным или специфическим безмолвием
|
const isSilenced = isGenerallySilenced || isSpecificallySilenced;
|
||||||
// Определяем длительность безмолвия для отображения (берем из специфического, если есть, иначе из полного)
|
const silenceTurnsLeft = isGenerallySilenced
|
||||||
const silenceTurnsLeft = isSpecificallySilenced
|
? (actingPlayerState.activeEffects.find(eff => eff.isFullSilence)?.turnsLeft || 0)
|
||||||
? (specificSilenceEffect?.turnsLeft || 0)
|
: (specificSilenceEffect?.turnsLeft || 0);
|
||||||
: (isGenerallySilenced ? (actingPlayerState.activeEffects.find(eff => eff.isFullSilence)?.turnsLeft || 0) : 0);
|
|
||||||
|
|
||||||
|
let isDisabledByDebuffOnTarget = false;
|
||||||
// Нельзя кастовать дебафф на цель, если он уже на ней (для определенных дебаффов)
|
const opponentStateForDebuffCheck = currentGameState[opponentActualSlotId]; // Состояние моего оппонента
|
||||||
// Нужна проверка состояния ОППОНЕНТА этого клиента (т.е. цели)
|
if (opponentStateForDebuffCheck && opponentStateForDebuffCheck.activeEffects &&
|
||||||
const opponentStateForDebuffCheck = currentGameState[opponentActualSlotId];
|
(ability.id === configGlobal.ABILITY_ID_SEAL_OF_WEAKNESS || ability.id === configGlobal.ABILITY_ID_ALMAGEST_DEBUFF)) {
|
||||||
let isDebuffAlreadyOnTarget = false;
|
const effectIdForDebuff = 'effect_' + ability.id;
|
||||||
const isTargetedDebuffAbility = ability.id === configGlobal.ABILITY_ID_SEAL_OF_WEAKNESS || ability.id === configGlobal.ABILITY_ID_ALMAGEST_DEBUFF;
|
isDisabledByDebuffOnTarget = opponentStateForDebuffCheck.activeEffects.some(e => e.id === effectIdForDebuff);
|
||||||
if (isTargetedDebuffAbility && opponentStateForDebuffCheck && opponentStateForDebuffCheck.activeEffects) {
|
|
||||||
const effectIdForDebuff = 'effect_' + ability.id; // Ищем эффект с префиксом effect_ на цели
|
|
||||||
isDebuffAlreadyOnTarget = opponentStateForDebuffCheck.activeEffects.some(e => e.id === effectIdForDebuff);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.disabled = !(canThisClientAct && isGameActive) || !hasEnoughResource || isBuffAlreadyActive || isSilenced || isOnCooldown || isDisabledByDebuffOnTarget;
|
||||||
// Кнопка активна, если:
|
|
||||||
// - Это ход этого клиента
|
|
||||||
// - Игра активна
|
|
||||||
// - Достаточно ресурса
|
|
||||||
// - Бафф не активен (если это бафф)
|
|
||||||
// - Не на кулдауне
|
|
||||||
// - Не под безмолвием (полным или специфическим)
|
|
||||||
// - Дебафф не активен на цели (если это такой дебафф)
|
|
||||||
button.disabled = !(canThisClientAct && isGameActive) ||
|
|
||||||
!hasEnoughResource ||
|
|
||||||
isBuffAlreadyActive ||
|
|
||||||
isSilenced ||
|
|
||||||
isOnCooldown ||
|
|
||||||
isDebuffAlreadyOnTarget;
|
|
||||||
|
|
||||||
|
|
||||||
// Управление классами для стилизации кнопки
|
|
||||||
button.classList.remove(configGlobal.CSS_CLASS_NOT_ENOUGH_RESOURCE||'not-enough-resource', configGlobal.CSS_CLASS_BUFF_IS_ACTIVE||'buff-is-active', configGlobal.CSS_CLASS_ABILITY_SILENCED||'is-silenced', configGlobal.CSS_CLASS_ABILITY_ON_COOLDOWN||'is-on-cooldown');
|
button.classList.remove(configGlobal.CSS_CLASS_NOT_ENOUGH_RESOURCE||'not-enough-resource', configGlobal.CSS_CLASS_BUFF_IS_ACTIVE||'buff-is-active', configGlobal.CSS_CLASS_ABILITY_SILENCED||'is-silenced', configGlobal.CSS_CLASS_ABILITY_ON_COOLDOWN||'is-on-cooldown');
|
||||||
const cooldownDisplay = button.querySelector('.ability-cooldown-display');
|
const cooldownDisplay = button.querySelector('.ability-cooldown-display');
|
||||||
|
|
||||||
@ -475,71 +336,39 @@
|
|||||||
if (cooldownDisplay) { cooldownDisplay.textContent = `КД: ${actingPlayerState.abilityCooldowns[ability.id]}`; cooldownDisplay.style.display = 'block'; }
|
if (cooldownDisplay) { cooldownDisplay.textContent = `КД: ${actingPlayerState.abilityCooldowns[ability.id]}`; cooldownDisplay.style.display = 'block'; }
|
||||||
} else if (isSilenced) {
|
} else if (isSilenced) {
|
||||||
button.classList.add(configGlobal.CSS_CLASS_ABILITY_SILENCED||'is-silenced');
|
button.classList.add(configGlobal.CSS_CLASS_ABILITY_SILENCED||'is-silenced');
|
||||||
if (cooldownDisplay) {
|
if (cooldownDisplay) { cooldownDisplay.textContent = `Безм: ${silenceTurnsLeft}`; cooldownDisplay.style.display = 'block'; }
|
||||||
const icon = isGenerallySilenced ? '🔕' : '🔇'; // Иконка для полного/частичного безмолвия
|
|
||||||
cooldownDisplay.textContent = `${icon} ${silenceTurnsLeft}`;
|
|
||||||
cooldownDisplay.style.display = 'block';
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (cooldownDisplay) cooldownDisplay.style.display = 'none'; // Скрываем, если нет ни КД, ни безмолвия
|
if (cooldownDisplay) cooldownDisplay.style.display = 'none';
|
||||||
// Добавляем классы, если действие возможно, но есть ограничения (недостаточно ресурса, бафф активен, дебафф на цели)
|
button.classList.toggle(configGlobal.CSS_CLASS_NOT_ENOUGH_RESOURCE||'not-enough-resource', !hasEnoughResource && !isBuffAlreadyActive && !isDisabledByDebuffOnTarget);
|
||||||
// Эти классы используются для визуальной обратной связи, когда кнопка *не* задизейблена по КД или безмолвию.
|
button.classList.toggle(configGlobal.CSS_CLASS_BUFF_IS_ACTIVE||'buff-is-active', isBuffAlreadyActive && !isDisabledByDebuffOnTarget);
|
||||||
// Если кнопка disabled из-за !hasEnoughResource, классы not-enough-resource и buff-is-active все равно могут быть добавлены.
|
|
||||||
button.classList.toggle(configGlobal.CSS_CLASS_NOT_ENOUGH_RESOURCE||'not-enough-resource', !hasEnoughResource && !isBuffAlreadyActive && !isDebuffAlreadyOnTarget);
|
|
||||||
button.classList.toggle(configGlobal.CSS_CLASS_BUFF_IS_ACTIVE||'buff-is-active', isBuffAlreadyActive && !isDebuffAlreadyOnTarget);
|
|
||||||
// Если дебафф уже на цели, но кнопка не задизейблена по другим причинам, можно добавить отдельный класс
|
|
||||||
// button.classList.toggle('debuff-on-target', isDebuffAlreadyOnTarget && !button.disabled);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновление title (всплывающей подсказки) - показываем полную информацию
|
// Обновление title (всплывающей подсказки)
|
||||||
let titleText = `${ability.name} (${ability.cost} ${actingPlayerResourceName})`;
|
let titleText = `${ability.name} (${ability.cost} ${actingPlayerResourceName})`;
|
||||||
let descriptionTextFull = ability.description; // Используем описание, пришедшее с сервера
|
let descriptionText = ability.description;
|
||||||
|
if (typeof ability.descriptionFunction === 'function') {
|
||||||
if (descriptionTextFull) titleText += ` - ${descriptionTextFull}`;
|
descriptionText = ability.descriptionFunction(configGlobal, gameDataGlobal.opponentBaseStats); // Для описания используем статы оппонента этого клиента
|
||||||
|
|
||||||
let abilityBaseCooldown = ability.cooldown; // Исходный КД из данных
|
|
||||||
// Учитываем внутренние КД Баларда, если это способность Баларда (хотя игроку AI способности не отображаются, но для полноты)
|
|
||||||
// if (actingPlayerState.characterKey === 'balard') {
|
|
||||||
// if (ability.id === configGlobal.ABILITY_ID_BALARD_SILENCE) abilityBaseCooldown = configGlobal.BALARD_SILENCE_INTERNAL_COOLDOWN;
|
|
||||||
// else if (ability.id === configGlobal.ABILITY_ID_BALARD_MANA_DRAIN && typeof ability.internalCooldownValue === 'number') abilityBaseCooldown = ability.internalCooldownValue;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (typeof abilityBaseCooldown === 'number' && abilityBaseCooldown > 0) {
|
|
||||||
titleText += ` (КД: ${abilityBaseCooldown} х.)`;
|
|
||||||
}
|
}
|
||||||
|
if (descriptionText) titleText += ` - ${descriptionText}`;
|
||||||
|
let abilityBaseCooldown = ability.cooldown;
|
||||||
|
if (ability.internalCooldownFromConfig && configGlobal[ability.internalCooldownFromConfig]) abilityBaseCooldown = configGlobal[ability.internalCooldownFromConfig];
|
||||||
|
else if (ability.internalCooldownValue) abilityBaseCooldown = ability.internalCooldownValue;
|
||||||
|
if (abilityBaseCooldown) titleText += ` (КД: ${abilityBaseCooldown} х.)`;
|
||||||
|
|
||||||
// Добавляем информацию о текущем состоянии (КД, безмолвие, активный бафф/debuff) в тултип, если применимо
|
if (isOnCooldown) titleText = `${ability.name} - На перезарядке! Осталось: ${actingPlayerState.abilityCooldowns[ability.id]} х.`;
|
||||||
if (isOnCooldown) {
|
else if (isSilenced) titleText = `Безмолвие! Осталось: ${silenceTurnsLeft} х.`;
|
||||||
titleText += ` | На перезарядке! Осталось: ${actingPlayerState.abilityCooldowns[ability.id]} х.`;
|
else if (isBuffAlreadyActive) {
|
||||||
}
|
|
||||||
if (isSilenced) {
|
|
||||||
titleText += ` | Под безмолвием! Осталось: ${silenceTurnsLeft} х.`;
|
|
||||||
}
|
|
||||||
if (isBuffAlreadyActive) {
|
|
||||||
const activeEffect = actingPlayerState.activeEffects?.find(eff => eff.id === abilityId);
|
const activeEffect = actingPlayerState.activeEffects?.find(eff => eff.id === abilityId);
|
||||||
titleText += ` | Эффект уже активен${activeEffect ? ` (${activeEffect.turnsLeft} х.)` : ''}.`;
|
titleText = `Эффект "${ability.name}" уже активен${activeEffect ? ` (${activeEffect.turnsLeft} х.)` : ''}`;
|
||||||
|
} else if (isDisabledByDebuffOnTarget && opponentStateForDebuffCheck) {
|
||||||
|
const activeDebuff = opponentStateForDebuffCheck.activeEffects?.find(e => e.id === 'effect_' + ability.id);
|
||||||
|
titleText = `Эффект "${ability.name}" уже наложен на ${opponentStateForDebuffCheck.name}${activeDebuff ? ` (${activeDebuff.turnsLeft} х.)` : ''}`;
|
||||||
}
|
}
|
||||||
if (isDebuffAlreadyOnTarget && opponentStateForDebuffCheck) {
|
|
||||||
const activeDebuff = opponentStateForDebuffCheck.activeEffects?.find(e => e.id === 'effect_' + abilityId);
|
|
||||||
titleText += ` | Эффект уже наложен на ${opponentBaseStatsForUI?.name || 'противника'}${activeDebuff ? ` (${activeDebuff.turnsLeft} х.)` : ''}.`;
|
|
||||||
}
|
|
||||||
if (!hasEnoughResource) {
|
|
||||||
titleText += ` | Недостаточно ${actingPlayerResourceName} (${actingPlayerState.currentResource}/${ability.cost})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
button.setAttribute('title', titleText);
|
button.setAttribute('title', titleText);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function showGameOver(playerWon, reason = "", opponentCharacterKeyFromClient = null) {
|
||||||
* Показывает модальное окно конца игры.
|
|
||||||
* @param {boolean} playerWon - Флаг, выиграл ли игрок, управляющий этим клиентом.
|
|
||||||
* @param {string} [reason=""] - Причина завершения игры.
|
|
||||||
* @param {string|null} opponentCharacterKeyFromClient - Ключ персонажа оппонента с т.з. клиента.
|
|
||||||
* @param {object} [data=null] - Полный объект данных из события gameOver (включает disconnectedCharacterName и т.д.)
|
|
||||||
*/
|
|
||||||
function showGameOver(playerWon, reason = "", opponentCharacterKeyFromClient = null, data = null) { // ИСПРАВЛЕНО: Добавлен аргумент data
|
|
||||||
const config = window.GAME_CONFIG || {};
|
const config = window.GAME_CONFIG || {};
|
||||||
// Используем gameData, сохраненное в client.js, так как оно отражает перспективу этого клиента
|
// Используем gameData, сохраненное в client.js, так как оно отражает перспективу этого клиента
|
||||||
const clientSpecificGameData = window.gameData;
|
const clientSpecificGameData = window.gameData;
|
||||||
@ -551,7 +380,6 @@
|
|||||||
console.log(`[UI.JS DEBUG] Opponent Character Key (from client via param): ${opponentCharacterKeyFromClient}`);
|
console.log(`[UI.JS DEBUG] Opponent Character Key (from client via param): ${opponentCharacterKeyFromClient}`);
|
||||||
console.log(`[UI.JS DEBUG] My Character Name (from window.gameData): ${clientSpecificGameData?.playerBaseStats?.name}`);
|
console.log(`[UI.JS DEBUG] My Character Name (from window.gameData): ${clientSpecificGameData?.playerBaseStats?.name}`);
|
||||||
console.log(`[UI.JS DEBUG] Opponent Character Name (from window.gameData): ${clientSpecificGameData?.opponentBaseStats?.name}`);
|
console.log(`[UI.JS DEBUG] Opponent Character Name (from window.gameData): ${clientSpecificGameData?.opponentBaseStats?.name}`);
|
||||||
console.log(`[UI.JS DEBUG] Full game over data received:`, data); // Добавьте этот лог
|
|
||||||
|
|
||||||
|
|
||||||
if (!gameOverScreenElement) {
|
if (!gameOverScreenElement) {
|
||||||
@ -568,16 +396,7 @@
|
|||||||
let winText = `Победа! ${myNameForResult} празднует!`;
|
let winText = `Победа! ${myNameForResult} празднует!`;
|
||||||
let loseText = `Поражение! ${opponentNameForResult} оказался(лась) сильнее!`;
|
let loseText = `Поражение! ${opponentNameForResult} оказался(лась) сильнее!`;
|
||||||
if (reason === 'opponent_disconnected') {
|
if (reason === 'opponent_disconnected') {
|
||||||
// Определяем, кто отключился, по данным из события gameOver
|
winText = `${opponentNameForResult} покинул(а) игру. Победа присуждается вам!`;
|
||||||
let disconnectedName = "Противник";
|
|
||||||
if (data && data.disconnectedCharacterName) {
|
|
||||||
disconnectedName = data.disconnectedCharacterName;
|
|
||||||
} else {
|
|
||||||
// Фоллбэк на имя оппонента с точки зрения клиента
|
|
||||||
disconnectedName = opponentNameForResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
winText = `${disconnectedName} покинул(а) игру. Победа присуждается вам!`;
|
|
||||||
// Если оппонент отключился, а мы проиграли (технически такое возможно, если сервер так решит)
|
// Если оппонент отключился, а мы проиграли (технически такое возможно, если сервер так решит)
|
||||||
// То текст поражения можно оставить стандартным или специфичным.
|
// То текст поражения можно оставить стандартным или специфичным.
|
||||||
// Пока оставим стандартный, если playerWon = false и reason = opponent_disconnected.
|
// Пока оставим стандартный, если playerWon = false и reason = opponent_disconnected.
|
||||||
@ -588,78 +407,54 @@
|
|||||||
|
|
||||||
const opponentPanelElement = uiElements.opponent.panel;
|
const opponentPanelElement = uiElements.opponent.panel;
|
||||||
if (opponentPanelElement) {
|
if (opponentPanelElement) {
|
||||||
// Сначала убеждаемся, что анимация растворения снята, если она была активна от предыдущей попытки
|
|
||||||
// console.log(`[UI.JS DEBUG] Opponent panel classList before potential dissolve: ${opponentPanelElement.className}`);
|
|
||||||
opponentPanelElement.classList.remove('dissolving');
|
opponentPanelElement.classList.remove('dissolving');
|
||||||
opponentPanelElement.offsetHeight; // Trigger reflow to reset state instantly
|
console.log(`[UI.JS DEBUG] Opponent panel classList after initial remove .dissolving: ${opponentPanelElement.className}`);
|
||||||
|
|
||||||
// Используем opponentCharacterKeyFromClient, так как это ключ реального персонажа оппонента с т.з. клиента
|
// Используем opponentCharacterKeyFromClient, так как это ключ реального персонажа оппонента
|
||||||
const keyForDissolveEffect = opponentCharacterKeyFromClient;
|
const keyForDissolveEffect = opponentCharacterKeyFromClient;
|
||||||
|
|
||||||
// Применяем анимацию растворения только если игра окончена, игрок выиграл, и это не дисконнект,
|
|
||||||
// и противник был Балардом или Альмагест (у которых есть эта анимация).
|
|
||||||
if (currentActualGameState && currentActualGameState.isGameOver === true && playerWon && reason !== 'opponent_disconnected') {
|
if (currentActualGameState && currentActualGameState.isGameOver === true && playerWon && reason !== 'opponent_disconnected') {
|
||||||
if (keyForDissolveEffect === 'balard' || keyForDissolveEffect === 'almagest') {
|
if (keyForDissolveEffect === 'balard' || keyForDissolveEffect === 'almagest') {
|
||||||
console.log(`[UI.JS DEBUG] ADDING .dissolving to opponent panel. Conditions met.`);
|
console.log(`[UI.JS DEBUG] ADDING .dissolving to opponent panel. Conditions: isGameOver=${currentActualGameState.isGameOver}, playerWon=${playerWon}, opponentKeyForEffect=${keyForDissolveEffect}`);
|
||||||
opponentPanelElement.classList.add('dissolving');
|
opponentPanelElement.classList.add('dissolving');
|
||||||
// Убеждаемся, что панель станет полностью прозрачной и сместится после анимации
|
|
||||||
opponentPanelElement.style.opacity = '0';
|
|
||||||
// opponentPanelElement.style.transform = 'scale(0.9) translateY(20px)'; // Трансформация уже в CSS анимации
|
|
||||||
} else {
|
} else {
|
||||||
console.log(`[UI.JS DEBUG] NOT adding .dissolving (opponent key mismatch for dissolve effect: ${keyForDissolveEffect} or reason: ${reason}).`);
|
console.log(`[UI.JS DEBUG] NOT adding .dissolving (opponent key mismatch for dissolve effect). Key for effect: ${keyForDissolveEffect}`);
|
||||||
// Если анимация не применяется, убеждаемся, что панель видна
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`[UI.JS DEBUG] NOT adding .dissolving. Conditions NOT met: isGameOver=${currentActualGameState?.isGameOver}, playerWon=${playerWon}, reason=${reason}`);
|
||||||
|
if (!currentActualGameState || currentActualGameState.isGameOver === false) {
|
||||||
|
console.log(`[UI.JS DEBUG] Ensuring opponent panel is visible because game is not 'isGameOver=true' or gameState missing.`);
|
||||||
|
const originalTransition = opponentPanelElement.style.transition;
|
||||||
|
opponentPanelElement.style.transition = 'none';
|
||||||
opponentPanelElement.style.opacity = '1';
|
opponentPanelElement.style.opacity = '1';
|
||||||
opponentPanelElement.style.transform = 'scale(1) translateY(0)';
|
opponentPanelElement.style.transform = 'scale(1) translateY(0)';
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
opponentPanelElement.style.transition = originalTransition || '';
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
console.log(`[UI.JS DEBUG] NOT adding .dissolving. Conditions NOT met: isGameOver=${currentActualGameState?.isGameOver}, playerWon=${playerWon}, reason=${reason}.`);
|
|
||||||
// Если игра не окончена или игрок проиграл/оппонент отключился, убеждаемся, что панель видна
|
|
||||||
opponentPanelElement.style.opacity = '1';
|
|
||||||
opponentPanelElement.style.transform = 'scale(1) translateY(0)';
|
|
||||||
}
|
}
|
||||||
// console.log(`[UI.JS DEBUG] Opponent panel classList FINAL in showGameOver (before timeout): ${opponentPanelElement.className}`);
|
console.log(`[UI.JS DEBUG] Opponent panel classList FINAL in showGameOver: ${opponentPanelElement.className}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Показываем модальное окно конца игры с небольшой задержкой
|
setTimeout(() => {
|
||||||
// ИСПРАВЛЕНО: Передаем аргументы в колбэк, чтобы не полагаться на глобальный gameState
|
if (window.gameState && window.gameState.isGameOver === true) { // Перепроверяем перед показом
|
||||||
setTimeout((finalState, won, gameOverReason, opponentKeyForModalParam, eventData) => { // Названия аргументов, чтобы избежать путаницы
|
console.log(`[UI.JS DEBUG] Showing gameOverScreen modal (isGameOver is true).`);
|
||||||
console.log("[UI.JS DEBUG] Timeout callback fired.");
|
gameOverScreenElement.classList.remove(config.CSS_CLASS_HIDDEN || 'hidden');
|
||||||
console.log("[UI.JS DEBUG] State in timeout:", finalState);
|
|
||||||
console.log("[UI.JS DEBUG] isGameOver in state:", finalState?.isGameOver);
|
|
||||||
|
|
||||||
// Перепроверяем состояние перед показом на случай быстрых обновлений
|
|
||||||
if (finalState && finalState.isGameOver === true) { // Используем переданный state
|
|
||||||
console.log(`[UI.JS DEBUG] Condition (finalState && finalState.isGameOver === true) IS TRUE. Attempting to show modal.`);
|
|
||||||
// Убеждаемся, что modal не имеет display: none перед запуском transition opacity
|
|
||||||
// display: none полностью убирает элемент из потока и не позволяет анимировать opacity
|
|
||||||
// Переводим display в flex, если он был hidden (display: none !important в CSS)
|
|
||||||
if (gameOverScreenElement.classList.contains(config.CSS_CLASS_HIDDEN || 'hidden')) {
|
|
||||||
gameOverScreenElement.classList.remove(config.CSS_CLASS_HIDDEN || 'hidden');
|
|
||||||
}
|
|
||||||
// Убеждаемся, что opacity 0 для начала анимации
|
|
||||||
gameOverScreenElement.style.opacity = '0';
|
|
||||||
// Убеждаемся, что display корректен
|
|
||||||
gameOverScreenElement.style.display = 'flex'; // Или какой там display в CSS для .modal
|
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
// Применяем opacity 1 после display flex для анимации
|
gameOverScreenElement.style.opacity = '0';
|
||||||
gameOverScreenElement.style.opacity = '1';
|
setTimeout(() => {
|
||||||
// Запускаем анимацию контента модального окна (scale/translate)
|
gameOverScreenElement.style.opacity = '1';
|
||||||
if (uiElements.gameOver.modalContent) {
|
if (uiElements.gameOver.modalContent) {
|
||||||
uiElements.gameOver.modalContent.style.transform = 'scale(1) translateY(0)';
|
uiElements.gameOver.modalContent.style.transform = 'scale(1) translateY(0)';
|
||||||
uiElements.gameOver.modalContent.style.opacity = '1';
|
uiElements.gameOver.modalContent.style.opacity = '1';
|
||||||
}
|
}
|
||||||
|
}, config.MODAL_TRANSITION_DELAY || 10);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log(`[UI.JS DEBUG] Condition (finalState && finalState.isGameOver === true) IS FALSE. Modal will NOT be shown.`);
|
console.log(`[UI.JS DEBUG] NOT showing gameOverScreen modal (isGameOver is now false or gameState missing).`);
|
||||||
// Убеждаемся, что модалка скрыта, если условия больше не выполняются
|
|
||||||
gameOverScreenElement.classList.add(config.CSS_CLASS_HIDDEN || 'hidden');
|
|
||||||
gameOverScreenElement.style.opacity = '0'; // Убеждаемся, что opacity сброшен
|
|
||||||
}
|
}
|
||||||
}, config.DELAY_BEFORE_VICTORY_MODAL || 1500, currentActualGameState, playerWon, reason, opponentCharacterKeyFromClient, data); // ИСПРАВЛЕНО: Передаем аргументы
|
}, config.DELAY_BEFORE_VICTORY_MODAL || 1500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Экспортируем функции UI для использования в client.js
|
|
||||||
window.gameUI = { uiElements, addToLog, updateUI, showGameOver };
|
window.gameUI = { uiElements, addToLog, updateUI, showGameOver };
|
||||||
})();
|
})();
|
Loading…
x
Reference in New Issue
Block a user