bc/public/js/main.js

381 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

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

// /public/js/main.js
import { initAuth } from './auth.js';
import { initGameSetup } from './gameSetup.js';
import { initGameplay } from './gameplay.js';
// ui.js загружен глобально и ожидает window.* переменных
function parseJwtPayload(token) {
try {
if (typeof token !== 'string') { return null; }
const parts = token.split('.');
if (parts.length !== 3) { return null; }
const base64Url = parts[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return JSON.parse(jsonPayload);
} catch (e) {
console.error("[parseJwtPayload] Error parsing JWT payload:", e);
return null;
}
}
document.addEventListener('DOMContentLoaded', () => {
// SERVER_URL и API_BASE_URL убраны. Socket.IO подключится к источнику загрузки страницы.
// auth.js будет использовать относительные пути для API запросов.
const initialToken = localStorage.getItem('jwtToken');
let clientState = {
isLoggedIn: false,
loggedInUsername: '',
myUserId: null,
isInGame: false,
currentGameId: null,
currentGameState: null,
myPlayerId: null,
myCharacterKey: null,
opponentCharacterKey: null,
playerBaseStatsServer: null,
opponentBaseStatsServer: null,
playerAbilitiesServer: null,
opponentAbilitiesServer: null,
};
if (initialToken) {
const decodedToken = parseJwtPayload(initialToken);
if (decodedToken && decodedToken.userId && decodedToken.username) {
const nowInSeconds = Math.floor(Date.now() / 1000);
if (decodedToken.exp && decodedToken.exp > nowInSeconds) {
console.log("[Client Init] Token found, pre-populating clientState.");
clientState.isLoggedIn = true;
clientState.myUserId = decodedToken.userId;
clientState.loggedInUsername = decodedToken.username;
} else {
console.warn("[Client Init] Token expired or invalid 'exp'. Clearing.");
localStorage.removeItem('jwtToken');
}
} else {
console.warn("[Client Init] Token invalid or missing data. Clearing.");
localStorage.removeItem('jwtToken');
}
}
const socket = io({ // SERVER_URL удален отсюда
autoConnect: false,
auth: { token: localStorage.getItem('jwtToken') }
});
// --- DOM Элементы ---
const authSection = document.getElementById('auth-section');
const loginForm = document.getElementById('login-form');
const registerForm = document.getElementById('register-form');
const authMessage = document.getElementById('auth-message');
const statusContainer = document.getElementById('status-container');
const userInfoDiv = document.getElementById('user-info');
const loggedInUsernameSpan = document.getElementById('logged-in-username');
const logoutButton = document.getElementById('logout-button');
const gameSetupDiv = document.getElementById('game-setup');
const createAIGameButton = document.getElementById('create-ai-game');
const createPvPGameButton = document.getElementById('create-pvp-game');
const joinPvPGameButton = document.getElementById('join-pvp-game');
const findRandomPvPGameButton = document.getElementById('find-random-pvp-game');
const gameIdInput = document.getElementById('game-id-input');
const availableGamesDiv = document.getElementById('available-games-list');
const gameStatusMessage = document.getElementById('game-status-message');
const pvpCharacterRadios = document.querySelectorAll('input[name="pvp-character"]');
const gameWrapper = document.querySelector('.game-wrapper');
const returnToMenuButton = document.getElementById('return-to-menu-button');
const turnTimerContainer = document.getElementById('turn-timer-container');
const turnTimerSpan = document.getElementById('turn-timer');
// --- Функции обновления UI и состояния ---
function updateGlobalWindowVariablesForUI() {
window.gameState = clientState.currentGameState;
window.gameData = {
playerBaseStats: clientState.playerBaseStatsServer,
opponentBaseStats: clientState.opponentBaseStatsServer,
playerAbilities: clientState.playerAbilitiesServer,
opponentAbilities: clientState.opponentAbilitiesServer
};
window.myPlayerId = clientState.myPlayerId;
}
function resetGameVariables() {
console.log("[Main:resetGameVariables] Resetting game variables. State BEFORE:", JSON.parse(JSON.stringify(clientState)));
clientState.currentGameId = null;
clientState.currentGameState = null;
clientState.myPlayerId = null;
clientState.myCharacterKey = null;
clientState.opponentCharacterKey = null;
clientState.playerBaseStatsServer = null;
clientState.opponentBaseStatsServer = null;
clientState.playerAbilitiesServer = null;
clientState.opponentAbilitiesServer = null;
updateGlobalWindowVariablesForUI();
console.log("[Main:resetGameVariables] Game variables reset. State AFTER:", JSON.parse(JSON.stringify(clientState)));
}
function explicitlyHideGameOverModal() {
console.log("[Main:explicitlyHideGameOverModal] Attempting to hide Game Over modal.");
if (window.gameUI?.uiElements?.gameOver?.screen && window.GAME_CONFIG) {
const gameOverScreenElement = window.gameUI.uiElements.gameOver.screen;
const modalContentElement = window.gameUI.uiElements.gameOver.modalContent;
const messageElement = window.gameUI.uiElements.gameOver.message;
const hiddenClass = window.GAME_CONFIG.CSS_CLASS_HIDDEN || 'hidden';
if (gameOverScreenElement && !gameOverScreenElement.classList.contains(hiddenClass)) {
gameOverScreenElement.classList.add(hiddenClass);
gameOverScreenElement.style.opacity = '0';
if (modalContentElement) {
modalContentElement.style.transform = 'scale(0.8) translateY(30px)';
modalContentElement.style.opacity = '0';
}
console.log("[Main:explicitlyHideGameOverModal] Game Over screen explicitly hidden.");
} else if (gameOverScreenElement) {
console.log("[Main:explicitlyHideGameOverModal] Game Over screen was already hidden or not found.");
}
if (messageElement) messageElement.textContent = '';
} else {
console.warn("[Main:explicitlyHideGameOverModal] Cannot hide Game Over modal: gameUI or GAME_CONFIG not available.");
}
}
function showAuthScreen() {
console.log("[Main:showAuthScreen] Showing Auth Screen. Resetting game state.");
authSection.style.display = 'block';
userInfoDiv.style.display = 'none';
gameSetupDiv.style.display = 'none';
gameWrapper.style.display = 'none';
explicitlyHideGameOverModal();
statusContainer.style.display = 'block';
clientState.isInGame = false;
resetGameVariables();
if (turnTimerContainer) turnTimerContainer.style.display = 'none';
if (turnTimerSpan) turnTimerSpan.textContent = '--';
if(registerForm) registerForm.querySelector('button').disabled = false;
if(loginForm) loginForm.querySelector('button').disabled = false;
if(logoutButton) logoutButton.disabled = true;
}
function showGameSelectionScreen(username) {
console.log(`[Main:showGameSelectionScreen] Showing Game Selection Screen for ${username}. Resetting game state.`);
authSection.style.display = 'none';
userInfoDiv.style.display = 'block';
if(loggedInUsernameSpan) loggedInUsernameSpan.textContent = username;
if(logoutButton) logoutButton.disabled = false;
gameSetupDiv.style.display = 'block';
gameWrapper.style.display = 'none';
explicitlyHideGameOverModal();
setGameStatusMessage("Выберите режим игры или присоединитесь к существующей.");
statusContainer.style.display = 'block';
if (socket.connected) {
socket.emit('requestPvPGameList');
} else {
console.warn("[Main:showGameSelectionScreen] Socket not connected, cannot request PvP game list yet.");
}
if (availableGamesDiv) availableGamesDiv.innerHTML = '<h3>Доступные PvP игры:</h3><p>Загрузка...</p>';
if (gameIdInput) gameIdInput.value = '';
const elenaRadio = document.getElementById('char-elena');
if (elenaRadio) elenaRadio.checked = true;
clientState.isInGame = false;
resetGameVariables();
if (turnTimerContainer) turnTimerContainer.style.display = 'none';
if (turnTimerSpan) turnTimerSpan.textContent = '--';
enableSetupButtons();
if (window.gameUI?.uiElements?.gameOver?.returnToMenuButton) {
window.gameUI.uiElements.gameOver.returnToMenuButton.disabled = false;
}
}
function showGameScreen() {
console.log("[Main:showGameScreen] Showing Game Screen.");
authSection.style.display = 'none';
userInfoDiv.style.display = 'block';
if(logoutButton) logoutButton.disabled = false;
gameSetupDiv.style.display = 'none';
gameWrapper.style.display = 'flex';
setGameStatusMessage("");
statusContainer.style.display = 'none';
clientState.isInGame = true;
updateGlobalWindowVariablesForUI();
if (turnTimerContainer) turnTimerContainer.style.display = 'block';
if (turnTimerSpan) turnTimerSpan.textContent = '--';
}
function setAuthMessage(message, isError = false) {
if (authMessage) {
authMessage.textContent = message;
authMessage.className = isError ? 'error' : 'success';
authMessage.style.display = message ? 'block' : 'none';
}
if (message && gameStatusMessage && gameStatusMessage.style.display !== 'none') gameStatusMessage.style.display = 'none';
}
function setGameStatusMessage(message, isError = false) {
if (gameStatusMessage) {
gameStatusMessage.textContent = message;
gameStatusMessage.style.display = message ? 'block' : 'none';
gameStatusMessage.style.color = isError ? 'var(--damage-color, red)' : 'var(--turn-color, yellow)';
if (statusContainer) statusContainer.style.display = message ? 'block' : 'none';
}
if (message && authMessage && authMessage.style.display !== 'none') authMessage.style.display = 'none';
}
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;
}
// --- Сборка зависимостей для модулей ---
const dependencies = {
socket,
clientState,
ui: {
showAuthScreen,
showGameSelectionScreen,
showGameScreen,
setAuthMessage,
setGameStatusMessage,
resetGameVariables,
updateGlobalWindowVariablesForUI,
disableSetupButtons,
enableSetupButtons,
elements: {
loginForm, registerForm, logoutButton,
createAIGameButton, createPvPGameButton, joinPvPGameButton,
findRandomPvPGameButton, gameIdInput, availableGamesDiv,
pvpCharacterRadios, returnToMenuButton,
}
},
// API_BASE_URL больше не передается, т.к. auth.js будет использовать относительные пути.
// Если auth.js все еще требует явного API_BASE_URL для случая, когда он не может
// сам определить window.location.origin, можно было бы передать:
// API_BASE_URL: window.location.origin
};
initAuth(dependencies);
initGameSetup(dependencies);
initGameplay(dependencies);
// --- Обработчики событий Socket.IO ---
socket.on('connect', () => {
const currentToken = socket.auth.token || localStorage.getItem('jwtToken');
console.log('[Main:SocketConnect] Socket connected:', socket.id, 'Auth token sent:', !!currentToken);
if (clientState.isLoggedIn && clientState.myUserId) {
console.log(`[Main:SocketConnect] Client state indicates logged in as ${clientState.loggedInUsername}. Requesting game state.`);
if (authSection.style.display === 'block' || gameSetupDiv.style.display === 'block') {
setGameStatusMessage("Восстановление игровой сессии...");
}
socket.emit('requestGameState');
} else {
console.log('[Main:SocketConnect] Client state indicates NOT logged in. Showing auth screen.');
showAuthScreen();
setAuthMessage("Пожалуйста, войдите или зарегистрируйтесь.");
}
});
socket.on('connect_error', (err) => {
console.error('[Main:SocketConnectError] Socket connection error:', err.message, err.data ? err.data : '');
const errorMessageLower = err.message ? err.message.toLowerCase() : "";
const isAuthError = errorMessageLower.includes('auth') || errorMessageLower.includes('token') ||
errorMessageLower.includes('unauthorized') || err.message === 'invalid token' ||
err.message === 'no token' || (err.data && typeof err.data === 'string' && err.data.toLowerCase().includes('auth'));
if (isAuthError) {
console.warn('[Main:SocketConnectError] Authentication error. Clearing token, resetting state, showing auth screen.');
localStorage.removeItem('jwtToken');
clientState.isLoggedIn = false;
clientState.loggedInUsername = '';
clientState.myUserId = null;
if (socket.auth) socket.auth.token = null;
showAuthScreen();
setAuthMessage("Ошибка аутентификации. Пожалуйста, войдите снова.", true);
} else {
// Общая ошибка подключения
let currentScreenMessageFunc = setAuthMessage;
if (clientState.isLoggedIn && clientState.isInGame) {
currentScreenMessageFunc = setGameStatusMessage;
} else if (clientState.isLoggedIn) {
currentScreenMessageFunc = setGameStatusMessage; // Или setAuthMessage, если statusContainer не виден
}
currentScreenMessageFunc(`Ошибка подключения: ${err.message}. Попытка переподключения...`, true);
if (authSection.style.display !== 'block' && !clientState.isLoggedIn) {
showAuthScreen(); // Если не на экране логина и не залогинен, показываем его
}
}
if (turnTimerSpan) turnTimerSpan.textContent = 'Ошибка';
});
socket.on('disconnect', (reason) => {
console.warn('[Main:SocketDisconnect] Disconnected from server:', reason);
let messageFunc = setAuthMessage;
if (clientState.isInGame) {
messageFunc = setGameStatusMessage;
} else if (clientState.isLoggedIn && gameSetupDiv.style.display === 'block') {
messageFunc = setGameStatusMessage;
}
messageFunc(`Потеряно соединение: ${reason}. Попытка переподключения...`, true);
if (turnTimerSpan) turnTimerSpan.textContent = 'Откл.';
});
socket.on('gameError', (data) => {
console.error('[Main:SocketGameError] Received gameError from server:', data.message);
if (clientState.isInGame && window.gameUI?.addToLog) {
window.gameUI.addToLog(`❌ Ошибка сервера: ${data.message}`, 'system');
} else if (clientState.isLoggedIn) {
setGameStatusMessage(`❌ Ошибка: ${data.message}`, true);
enableSetupButtons();
} else {
setAuthMessage(`❌ Ошибка: ${data.message}`, true);
if(registerForm) registerForm.querySelector('button').disabled = false;
if(loginForm) loginForm.querySelector('button').disabled = false;
}
});
socket.on('gameNotFound', (data) => {
console.log('[Main:SocketGameNotFound] Game not found/ended after request:', data?.message);
clientState.isInGame = false;
resetGameVariables();
explicitlyHideGameOverModal();
if (turnTimerContainer) turnTimerContainer.style.display = 'none';
if (turnTimerSpan) turnTimerSpan.textContent = '--';
if (clientState.isLoggedIn && clientState.myUserId) {
showGameSelectionScreen(clientState.loggedInUsername);
setGameStatusMessage(data?.message || "Активная игровая сессия не найдена. Выберите новую игру.");
} else {
showAuthScreen();
setAuthMessage(data?.message || "Пожалуйста, войдите.");
}
});
// --- Инициализация UI ---
authSection.style.display = 'none';
gameSetupDiv.style.display = 'none';
gameWrapper.style.display = 'none';
userInfoDiv.style.display = 'none';
statusContainer.style.display = 'block';
if (clientState.isLoggedIn) {
setGameStatusMessage("Подключение и восстановление сессии...");
} else {
setAuthMessage("Подключение к серверу...");
}
socket.connect(); // Начинаем подключение к серверу
});