diff --git a/package-lock.json b/package-lock.json
index b0e68f7..e81e8de 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,7 @@
"bcryptjs": "^3.0.2",
"dotenv": "^16.5.0",
"express": "^5.1.0",
+ "jsonwebtoken": "^9.0.2",
"mysql2": "^3.14.1",
"socket.io": "^4.8.1",
"uuid": "^11.1.0"
@@ -97,6 +98,12 @@
"node": ">=18"
}
},
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+ "license": "BSD-3-Clause"
+ },
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -247,6 +254,15 @@
"node": ">= 0.4"
}
},
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -619,6 +635,91 @@
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
"license": "MIT"
},
+ "node_modules/jsonwebtoken": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
+ "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "jws": "^3.2.2",
+ "lodash.includes": "^4.3.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isinteger": "^4.0.4",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.once": "^4.0.0",
+ "ms": "^2.1.1",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/jwa": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
+ "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal-constant-time": "^1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "license": "MIT",
+ "dependencies": {
+ "jwa": "^1.4.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/lodash.includes": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isinteger": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+ "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.once": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
+ "license": "MIT"
+ },
"node_modules/long": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
@@ -901,6 +1002,18 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
+ "node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/send": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
diff --git a/package.json b/package.json
index 1e86957..d102f2a 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,7 @@
"bcryptjs": "^3.0.2",
"dotenv": "^16.5.0",
"express": "^5.1.0",
+ "jsonwebtoken": "^9.0.2",
"mysql2": "^3.14.1",
"socket.io": "^4.8.1",
"uuid": "^11.1.0"
diff --git a/public/js/auth.js b/public/js/auth.js
index b4465c8..5be2017 100644
--- a/public/js/auth.js
+++ b/public/js/auth.js
@@ -5,9 +5,69 @@ export function initAuth(dependencies) {
const { socket, clientState, ui } = dependencies;
const { loginForm, registerForm, logoutButton } = ui.elements; // Получаем нужные DOM элементы
+ // URL вашего API сервера. Лучше вынести в конфигурацию или передавать.
+ // Для примера захардкодим, но в main.js можно будет это улучшить.
+ const API_BASE_URL = dependencies.API_BASE_URL || 'http://127.0.0.1:3200'; // Убедитесь, что это ваш URL
+
+ // Название ключа для хранения JWT в localStorage
+ const JWT_TOKEN_KEY = 'jwtToken';
+
+ async function handleAuthResponse(response, formType) {
+ const regButton = registerForm ? registerForm.querySelector('button') : null;
+ const loginButton = loginForm ? loginForm.querySelector('button') : null;
+
+ try {
+ const data = await response.json();
+
+ if (response.ok && data.success && data.token) {
+ // Успешная аутентификация/регистрация
+ localStorage.setItem(JWT_TOKEN_KEY, data.token); // Сохраняем токен
+
+ clientState.isLoggedIn = true;
+ clientState.loggedInUsername = data.username;
+ clientState.myUserId = data.userId;
+
+ ui.setAuthMessage(''); // Очищаем сообщение об аутентификации
+ ui.showGameSelectionScreen(data.username); // Показываем экран выбора игры
+
+ // Важно: переподключить сокет с новым токеном
+ if (socket.connected) {
+ socket.disconnect();
+ }
+ // Обновляем auth объект сокета перед подключением
+ // В main.js при создании сокета, он должен уже брать токен из localStorage
+ // Но если сокет уже существует, нужно обновить его auth данные
+ socket.auth = { token: data.token };
+ socket.connect(); // Это вызовет 'connect' и 'requestGameState' в main.js
+
+ } else {
+ // Ошибка аутентификации/регистрации
+ clientState.isLoggedIn = false;
+ clientState.loggedInUsername = '';
+ clientState.myUserId = null;
+ localStorage.removeItem(JWT_TOKEN_KEY); // Удаляем старый токен, если был
+ ui.setAuthMessage(data.message || 'Ошибка сервера.', true);
+ }
+ } catch (error) {
+ // Ошибка парсинга JSON или другая сетевая ошибка
+ console.error(`[Auth] Error processing ${formType} response:`, error);
+ clientState.isLoggedIn = false;
+ clientState.loggedInUsername = '';
+ clientState.myUserId = null;
+ localStorage.removeItem(JWT_TOKEN_KEY);
+ ui.setAuthMessage('Произошла ошибка сети. Попробуйте снова.', true);
+ } finally {
+ // Разблокируем кнопки в любом случае
+ if (regButton) regButton.disabled = false;
+ if (loginButton) loginButton.disabled = false;
+ if (logoutButton && clientState.isLoggedIn) logoutButton.disabled = false;
+ }
+ }
+
+
// --- Обработчики событий DOM ---
if (registerForm) {
- registerForm.addEventListener('submit', (e) => {
+ registerForm.addEventListener('submit', async (e) => {
e.preventDefault();
const usernameInput = document.getElementById('register-username');
const passwordInput = document.getElementById('register-password');
@@ -16,19 +76,32 @@ export function initAuth(dependencies) {
const username = usernameInput.value;
const password = passwordInput.value;
- // Блокируем кнопки на время запроса
const regButton = registerForm.querySelector('button');
const loginButton = loginForm ? loginForm.querySelector('button') : null;
if (regButton) regButton.disabled = true;
if (loginButton) loginButton.disabled = true;
ui.setAuthMessage('Регистрация...');
- socket.emit('register', { username, password });
+
+ try {
+ const response = await fetch(`${API_BASE_URL}/auth/register`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ username, password }),
+ });
+ await handleAuthResponse(response, 'register');
+ if (response.ok && registerForm) registerForm.reset(); // Очищаем форму при успехе
+ } catch (error) {
+ console.error('[Auth] Network error during registration:', error);
+ ui.setAuthMessage('Ошибка сети при регистрации. Пожалуйста, проверьте ваше подключение.', true);
+ if (regButton) regButton.disabled = false;
+ if (loginButton) loginButton.disabled = false;
+ }
});
}
if (loginForm) {
- loginForm.addEventListener('submit', (e) => {
+ loginForm.addEventListener('submit', async (e) => {
e.preventDefault();
const usernameInput = document.getElementById('login-username');
const passwordInput = document.getElementById('login-password');
@@ -37,92 +110,101 @@ export function initAuth(dependencies) {
const username = usernameInput.value;
const password = passwordInput.value;
- // Блокируем кнопки на время запроса
const loginButton = loginForm.querySelector('button');
const regButton = registerForm ? registerForm.querySelector('button') : null;
if (loginButton) loginButton.disabled = true;
if (regButton) regButton.disabled = true;
ui.setAuthMessage('Вход...');
- socket.emit('login', { username, password });
+
+ try {
+ const response = await fetch(`${API_BASE_URL}/auth/login`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ username, password }),
+ });
+ await handleAuthResponse(response, 'login');
+ } catch (error) {
+ console.error('[Auth] Network error during login:', error);
+ ui.setAuthMessage('Ошибка сети при входе. Пожалуйста, проверьте ваше подключение.', true);
+ if (loginButton) loginButton.disabled = false;
+ if (regButton) regButton.disabled = false;
+ }
});
}
if (logoutButton) {
logoutButton.addEventListener('click', () => {
logoutButton.disabled = true;
- socket.emit('logout');
- // Обновляем состояние клиента немедленно, не дожидаясь ответа сервера (опционально)
+ // --- НАЧАЛО ИЗМЕНЕНИЯ ---
+ // Если игрок в активной PvP игре, отправляем сигнал о сдаче
+ if (clientState.isLoggedIn &&
+ clientState.isInGame &&
+ clientState.currentGameId &&
+ clientState.currentGameState && // Убедимся, что gameState существует
+ clientState.currentGameState.gameMode === 'pvp' && // Проверяем режим игры
+ !clientState.currentGameState.isGameOver) { // Только если игра еще не закончена
+
+ console.log('[Auth] Player is in an active PvP game. Emitting playerSurrender.');
+ socket.emit('playerSurrender');
+ // Не ждем ответа от сервера здесь, так как logout - это безусловное действие на клиенте.
+ // Сервер обработает 'playerSurrender' и соответствующим образом завершит игру.
+ }
+ // --- КОНЕЦ ИЗМЕНЕНИЯ ---
+
+
+ // Серверный эндпоинт для логаута не обязателен для JWT,
+ // если нет необходимости аннулировать токен на сервере (что сложно с JWT).
+ // Основное действие - удаление токена на клиенте.
+ // socket.emit('logout'); // Можно оставить, если на сервере есть логика для этого (например, GameManager.handleDisconnect)
+
+ localStorage.removeItem(JWT_TOKEN_KEY); // Удаляем токен
+
clientState.isLoggedIn = false;
clientState.loggedInUsername = '';
clientState.myUserId = null;
// isInGame и другие игровые переменные сбросятся в ui.showAuthScreen()
- // disableGameControls() также будет вызван опосредованно через showAuthScreen -> resetGameVariables
+ // ui.disableGameControls() также будет вызван опосредованно
ui.showAuthScreen(); // Показываем экран логина
- ui.setGameStatusMessage("Вы вышли из системы."); // Используем gameStatusMessage для общего статуса после выхода
- // ui.setAuthMessage("Вы вышли из системы."); // или authMessage, если он виден
- // Кнопка разблокируется при следующем показе userInfoDiv или можно здесь
- // logoutButton.disabled = false; // но лучше, чтобы UI сам управлял этим при показе
+ ui.setGameStatusMessage("Вы вышли из системы."); // Можно заменить на ui.setAuthMessage, если хотим видеть сообщение на экране логина
+
+ // Переподключаем сокет без токена
+ if (socket.connected) {
+ socket.disconnect();
+ }
+ socket.auth = { token: null }; // Очищаем токен в auth объекте сокета
+ socket.connect(); // Сокет подключится как неаутентифицированный
+
+ // Кнопка logout будет активирована, когда пользователь снова войдет
+ // или если она видна только залогиненным пользователям, то исчезнет.
+ // (В showAuthScreen logoutButton.disabled устанавливается в true)
});
}
// --- Обработчики событий Socket.IO ---
- socket.on('registerResponse', (data) => {
- ui.setAuthMessage(data.message, !data.success);
- if (data.success && registerForm) {
- registerForm.reset(); // Очищаем форму при успехе
- }
- // Разблокируем кнопки
- if (registerForm) {
- const regButton = registerForm.querySelector('button');
- if (regButton) regButton.disabled = false;
- }
- if (loginForm) {
- const loginButton = loginForm.querySelector('button');
- if (loginButton) loginButton.disabled = false;
- }
- });
+ // Старые 'registerResponse' и 'loginResponse' больше не нужны,
+ // так как эти ответы приходят через HTTP.
- socket.on('loginResponse', (data) => {
- if (data.success) {
- clientState.isLoggedIn = true;
- clientState.loggedInUsername = data.username;
- clientState.myUserId = data.userId;
-
- ui.setAuthMessage(""); // Очищаем сообщение об аутентификации
- ui.showGameSelectionScreen(data.username); // Показываем экран выбора игры
- // Запрос gameState при успешном логине и реконнекте теперь обрабатывается в main.js
- // если пользователь уже был залогинен при 'connect'
- } else {
- clientState.isLoggedIn = false;
- clientState.loggedInUsername = '';
- clientState.myUserId = null;
- ui.setAuthMessage(data.message, true); // Показываем ошибку
- }
- // Разблокируем кнопки
- if (registerForm) {
- const regButton = registerForm.querySelector('button');
- if (regButton) regButton.disabled = false;
- }
- if (loginForm) {
- const loginButton = loginForm.querySelector('button');
- if (loginButton) loginButton.disabled = false;
- }
- // Убедимся, что кнопка logout активна, если пользователь успешно вошел
- if (logoutButton && clientState.isLoggedIn) {
- logoutButton.disabled = false;
- }
- });
-
- // Примечание: событие 'logout' от сервера обычно не требует специального обработчика здесь,
- // так как клиент сам инициирует выход и обновляет UI.
- // Если сервер принудительно разлогинивает, то такой обработчик может понадобиться.
+ // Можно добавить обработчик для принудительного разлогинивания от сервера, если такой будет
// socket.on('forceLogout', (data) => {
+ // console.log('[Auth] Forced logout by server:', data.message);
+ // localStorage.removeItem(JWT_TOKEN_KEY);
// clientState.isLoggedIn = false;
- // // ...
+ // clientState.loggedInUsername = '';
+ // clientState.myUserId = null;
// ui.showAuthScreen();
// ui.setAuthMessage(data.message || "Вы были разлогинены сервером.");
+ // if (socket.connected) socket.disconnect();
+ // socket.auth = { token: null };
+ // socket.connect();
// });
+
+ // При загрузке модуля auth.js, проверяем, нет ли уже токена в localStorage
+ // Эта логика лучше всего будет работать в main.js при инициализации сокета,
+ // но здесь можно было бы сделать предварительную проверку и обновление clientState,
+ // если бы это было необходимо до создания сокета.
+ // Однако, поскольку сокет создается в main.js и сразу использует токен из localStorage,
+ // отдельная логика здесь не так критична.
}
\ No newline at end of file
diff --git a/public/js/client.js b/public/js/client_del.js
similarity index 100%
rename from public/js/client.js
rename to public/js/client_del.js
diff --git a/public/js/gameplay.js b/public/js/gameplay.js
index 5048d0c..ccd2c3b 100644
--- a/public/js/gameplay.js
+++ b/public/js/gameplay.js
@@ -1,16 +1,9 @@
-// /public/js/gameplay.js
+// /public/js/gameplay.js (Откаченная версия, совместимая с последним GameInstance.js)
export function initGameplay(dependencies) {
const { socket, clientState, ui } = dependencies;
- // Элементы управления боем обычно находятся внутри gameWrapper и управляются через ui.js,
- // но нам могут понадобиться ссылки на кнопки для привязки событий, если они не привязаны в ui.js
- // или если ui.js не экспортирует их напрямую.
- // В данном случае, attackButton и abilitiesGrid есть в client.js, так что получим их.
- // ui.elements из main.js содержит returnToMenuButton
const { returnToMenuButton } = ui.elements;
- // Получаем ссылки на кнопки атаки и способностей напрямую, как было в client.js
- // или, если бы ui.js их экспортировал, можно было бы через window.gameUI.uiElements
const attackButton = document.getElementById('button-attack');
const abilitiesGrid = document.getElementById('abilities-grid');
@@ -18,28 +11,26 @@ export function initGameplay(dependencies) {
function enableGameControls(enableAttack = true, enableAbilities = true) {
if (attackButton) attackButton.disabled = !enableAttack;
if (abilitiesGrid) {
- // Предполагаем, что GAME_CONFIG доступен глобально или его нужно передать
const config = window.GAME_CONFIG || {};
const cls = config.CSS_CLASS_ABILITY_BUTTON || 'ability-button';
abilitiesGrid.querySelectorAll(`.${cls}`).forEach(b => { b.disabled = !enableAbilities; });
}
- // Если кнопка блока есть и управляется отсюда
- // if (window.gameUI?.uiElements?.controls?.buttonBlock) window.gameUI.uiElements.controls.buttonBlock.disabled = true;
+ if (window.gameUI?.updateUI) {
+ requestAnimationFrame(() => window.gameUI.updateUI());
+ }
}
function disableGameControls() {
enableGameControls(false, false);
}
- // Эта функция была в client.js, переносим сюда
function initializeAbilityButtons() {
if (!abilitiesGrid || !window.gameUI || !window.GAME_CONFIG) {
if (abilitiesGrid) abilitiesGrid.innerHTML = '
Ошибка загрузки способностей.
';
return;
}
- abilitiesGrid.innerHTML = ''; // Очищаем предыдущие кнопки
+ abilitiesGrid.innerHTML = '';
const config = window.GAME_CONFIG;
- // Используем данные из clientState, которые были обновлены из событий сервера
const abilitiesToDisplay = clientState.playerAbilitiesServer;
const baseStatsForResource = clientState.playerBaseStatsServer;
@@ -56,34 +47,18 @@ export function initGameplay(dependencies) {
button.id = `ability-btn-${ability.id}`;
button.classList.add(abilityButtonClass);
button.dataset.abilityId = ability.id;
-
- let cooldown = ability.cooldown; // Это базовый КД из данных персонажа
+ let cooldown = ability.cooldown;
let cooldownText = (typeof cooldown === 'number' && cooldown > 0) ? ` (КД: ${cooldown} х.)` : "";
let title = `${ability.name} (${ability.cost} ${resourceName})${cooldownText} - ${ability.description || 'Нет описания'}`;
button.setAttribute('title', title);
-
- const nameSpan = document.createElement('span');
- nameSpan.classList.add('ability-name');
- nameSpan.textContent = ability.name;
- button.appendChild(nameSpan);
-
- const descSpan = document.createElement('span');
- descSpan.classList.add('ability-desc');
- descSpan.textContent = `(${ability.cost} ${resourceName})`;
- button.appendChild(descSpan);
-
- const cdDisplay = document.createElement('span');
- cdDisplay.classList.add('ability-cooldown-display'); // Для отображения текущего КД
- cdDisplay.style.display = 'none'; // Скрыт по умолчанию
- button.appendChild(cdDisplay);
-
+ const nameSpan = document.createElement('span'); nameSpan.classList.add('ability-name'); nameSpan.textContent = ability.name; button.appendChild(nameSpan);
+ const descSpan = document.createElement('span'); descSpan.classList.add('ability-desc'); descSpan.textContent = `(${ability.cost} ${resourceName})`; button.appendChild(descSpan);
+ const cdDisplay = document.createElement('span'); cdDisplay.classList.add('ability-cooldown-display'); cdDisplay.style.display = 'none'; button.appendChild(cdDisplay);
button.addEventListener('click', handleAbilityButtonClick);
abilitiesGrid.appendChild(button);
});
-
const placeholder = abilitiesGrid.querySelector('.placeholder-text');
if (placeholder) placeholder.remove();
- // После инициализации кнопок, их состояние (disabled/enabled) будет управляться window.gameUI.updateUI()
}
function handleAbilityButtonClick(event) {
@@ -94,22 +69,13 @@ export function initGameplay(dependencies) {
abilityId &&
clientState.currentGameState &&
!clientState.currentGameState.isGameOver) {
- // Перед отправкой действия можно добавить быструю проверку на клиенте (например, хватает ли ресурса),
- // но основная валидация все равно на сервере.
socket.emit('playerAction', { actionType: 'ability', abilityId: abilityId });
- disableGameControls(); // Блокируем управление до ответа сервера или следующего хода
+ disableGameControls();
} else {
- console.warn("Cannot perform ability action, invalid state:", {
- isLoggedIn: clientState.isLoggedIn,
- isInGame: clientState.isInGame,
- gameId: clientState.currentGameId,
- abilityId,
- gameState: clientState.currentGameState
- });
+ console.warn("Cannot perform ability action, invalid state");
}
}
-
// --- Обработчики событий DOM ---
if (attackButton) {
attackButton.addEventListener('click', () => {
@@ -119,95 +85,50 @@ export function initGameplay(dependencies) {
clientState.currentGameState &&
!clientState.currentGameState.isGameOver) {
socket.emit('playerAction', { actionType: 'attack' });
- disableGameControls(); // Блокируем управление до ответа сервера или следующего хода
+ disableGameControls();
} else {
console.warn("Cannot perform attack action, invalid state.");
}
});
}
- if (returnToMenuButton) { // Кнопка из модалки GameOver
+ if (returnToMenuButton) {
returnToMenuButton.addEventListener('click', () => {
if (!clientState.isLoggedIn) {
- ui.showAuthScreen(); // Если как-то оказались здесь без логина
+ ui.showAuthScreen();
return;
}
- returnToMenuButton.disabled = true; // Блокируем на время перехода
- // ui.resetGameVariables(); // Вызывается в showGameSelectionScreen
- clientState.isInGame = false; // Устанавливаем, что мы больше не в игре
- disableGameControls(); // Деактивируем игровые контролы
- // window.gameUI.showGameOver(false, "", null, { finalGameState: { isGameOver: false } }); // Скрываем модалку (делается в showGameSelectionScreen)
-
- ui.showGameSelectionScreen(clientState.loggedInUsername); // Возвращаемся на экран выбора
- // Кнопка returnToMenuButton включится при следующем показе модалки GameOver (логика в ui.js или здесь при gameOver)
+ returnToMenuButton.disabled = true;
+ clientState.isInGame = false;
+ disableGameControls();
+ ui.showGameSelectionScreen(clientState.loggedInUsername);
});
}
- // --- Обработчики событий Socket.IO ---
- socket.on('gameStarted', (data) => {
- if (!clientState.isLoggedIn) return; // Игнорируем, если не залогинены
- console.log('[Gameplay] Game started:', data);
- // Обновляем состояние клиента
- clientState.currentGameId = data.gameId;
- clientState.myPlayerId = data.yourPlayerId;
- clientState.currentGameState = data.initialGameState;
- clientState.playerBaseStatsServer = data.playerBaseStats;
- clientState.opponentBaseStatsServer = data.opponentBaseStats;
- clientState.playerAbilitiesServer = data.playerAbilities;
- clientState.opponentAbilitiesServer = data.opponentAbilities;
- clientState.myCharacterKey = data.playerBaseStats?.characterKey;
- clientState.opponentCharacterKey = data.opponentBaseStats?.characterKey;
- clientState.isInGame = true;
-
- if (data.clientConfig) { // Если сервер прислал конфиг
- // Важно: GAME_CONFIG используется в ui.js и других местах
- window.GAME_CONFIG = { ...window.GAME_CONFIG, ...data.clientConfig };
- } else if (!window.GAME_CONFIG) { // Базовый конфиг, если не пришел с сервера и не определен
- window.GAME_CONFIG = { PLAYER_ID: 'player', OPPONENT_ID: 'opponent', CSS_CLASS_HIDDEN: 'hidden' /* ... другие важные ключи ... */ };
- }
-
- ui.updateGlobalWindowVariablesForUI(); // Обновляем глобальные переменные для ui.js
-
- ui.showGameScreen(); // Показываем игровой экран
- initializeAbilityButtons(); // Инициализируем кнопки способностей с новыми данными
-
- // Очистка лога перед началом новой игры
- if (window.gameUI?.uiElements?.log?.list) {
- window.gameUI.uiElements.log.list.innerHTML = '';
- }
- // Добавление начальных логов, если есть
- if (window.gameUI && typeof window.gameUI.addToLog === 'function' && data.log) {
- data.log.forEach(logEntry => window.gameUI.addToLog(logEntry.message, logEntry.type));
- }
-
- // Первичное обновление UI боевого экрана
- requestAnimationFrame(() => {
- if (window.gameUI && typeof window.gameUI.updateUI === 'function') {
- window.gameUI.updateUI();
- }
- });
- // ui.hideGameOverModal(); // Теперь делается в showGameScreen
- ui.setGameStatusMessage(""); // Очищаем общий статус
- // Таймер хода будет обновлен событием 'turnTimerUpdate'
- });
-
- // Используется для восстановления состояния уже идущей игры (например, при реконнекте)
- socket.on('gameState', (data) => {
+ // --- ОБЩИЙ ОБРАБОТЧИК ДЛЯ ЗАПУСКА/ВОССТАНОВЛЕНИЯ ИГРЫ ---
+ function handleGameDataReceived(data, eventName = "unknown") {
if (!clientState.isLoggedIn) return;
- console.log('[Gameplay] Received full gameState (e.g. on reconnect):', data);
+ const username = clientState.loggedInUsername || 'N/A'; // Для логов
+ console.log(`[CLIENT ${username}] ${eventName} received.`);
+ // if (data.log) console.log(`[CLIENT ${username}] ${eventName} log content:`, JSON.parse(JSON.stringify(data.log)));
+
- // Обновляем состояние клиента (похоже на gameStarted)
clientState.currentGameId = data.gameId;
clientState.myPlayerId = data.yourPlayerId;
- clientState.currentGameState = data.gameState; // Используем gameState вместо initialGameState
+ clientState.currentGameState = data.initialGameState || data.gameState;
clientState.playerBaseStatsServer = data.playerBaseStats;
clientState.opponentBaseStatsServer = data.opponentBaseStats;
clientState.playerAbilitiesServer = data.playerAbilities;
clientState.opponentAbilitiesServer = data.opponentAbilities;
clientState.myCharacterKey = data.playerBaseStats?.characterKey;
clientState.opponentCharacterKey = data.opponentBaseStats?.characterKey;
- clientState.isInGame = true; // Устанавливаем, что мы в игре
+
+ if (clientState.currentGameState && !clientState.currentGameState.isGameOver) {
+ clientState.isInGame = true;
+ } else if (clientState.currentGameState && clientState.currentGameState.isGameOver) {
+ clientState.isInGame = false;
+ }
if (data.clientConfig) {
window.GAME_CONFIG = { ...window.GAME_CONFIG, ...data.clientConfig };
@@ -216,46 +137,132 @@ export function initGameplay(dependencies) {
}
ui.updateGlobalWindowVariablesForUI();
- if (!clientState.isInGame || document.querySelector('.game-wrapper').style.display === 'none') {
- ui.showGameScreen(); // Показываем игровой экран, если еще не там
+ const gameWrapperElement = document.querySelector('.game-wrapper');
+ if (clientState.isInGame && clientState.currentGameState && !clientState.currentGameState.isGameOver) {
+ const isGameWrapperVisible = gameWrapperElement && (gameWrapperElement.style.display === 'flex' || getComputedStyle(gameWrapperElement).display === 'flex');
+ if (!isGameWrapperVisible) {
+ ui.showGameScreen();
+ }
}
- initializeAbilityButtons(); // Переинициализируем кнопки способностей
- // Лог при 'gameState' может быть уже накопленным, очищаем и добавляем новый
- if (window.gameUI?.uiElements?.log?.list && data.log) {
- window.gameUI.uiElements.log.list.innerHTML = '';
+ initializeAbilityButtons();
+
+ if (window.gameUI?.uiElements?.log?.list) {
+ // console.log(`[CLIENT ${username}] Log BEFORE clear in ${eventName}:`, window.gameUI.uiElements.log.list.innerHTML.substring(0,100));
+ window.gameUI.uiElements.log.list.innerHTML = ''; // Очищаем UI-лог перед добавлением новых
+ // console.log(`[CLIENT ${username}] Log AFTER clear in ${eventName}:`, window.gameUI.uiElements.log.list.innerHTML);
}
- if (window.gameUI && typeof window.gameUI.addToLog === 'function' && data.log) {
- data.log.forEach(logEntry => window.gameUI.addToLog(logEntry.message, logEntry.type));
+ if (window.gameUI?.addToLog && data.log) {
+ data.log.forEach(logEntry => {
+ // console.log(`[CLIENT ${username}] Adding to UI log from ${eventName}: "${logEntry.message}"`);
+ window.gameUI.addToLog(logEntry.message, logEntry.type);
+ });
}
requestAnimationFrame(() => {
- if (window.gameUI && typeof window.gameUI.updateUI === 'function') {
+ if (window.gameUI?.updateUI) {
window.gameUI.updateUI();
}
+ if (clientState.isInGame && clientState.currentGameState && !clientState.currentGameState.isGameOver && window.GAME_CONFIG) {
+ const config = window.GAME_CONFIG;
+ const isMyActualTurn = clientState.myPlayerId &&
+ ((clientState.currentGameState.isPlayerTurn && clientState.myPlayerId === config.PLAYER_ID) ||
+ (!clientState.currentGameState.isPlayerTurn && clientState.myPlayerId === config.OPPONENT_ID));
+ if (isMyActualTurn) {
+ enableGameControls();
+ } else {
+ disableGameControls();
+ }
+ }
});
- // ui.hideGameOverModal(); // Делается в showGameScreen
- // Таймер хода будет обновлен событием 'turnTimerUpdate'
+
+ // Управление gameStatusMessage
+ if (clientState.currentGameState && clientState.currentGameState.isGameOver) {
+ // gameOver имеет свой обработчик статуса (внутри socket.on('gameOver',...))
+ } else if (eventName === 'gameStarted' || eventName === 'gameState (reconnect)') {
+ // Это начало игры или восстановление сессии, статус должен быть чистым
+ console.log(`[CLIENT ${username}] ${eventName} - Clearing game status message because it's a fresh game/state load.`);
+ ui.setGameStatusMessage("");
+ } else {
+ // Для gameStateUpdate и других событий, не являющихся полной перезагрузкой,
+ // gameStatusMessage будет управляться в их обработчиках или через turnTimerUpdate.
+ // Если игра продолжается и не gameOver, общее сообщение "Ожидание" должно сниматься.
+ if (clientState.isInGame) {
+ ui.setGameStatusMessage("");
+ }
+ }
+ // Если игра пришла завершенной, то showGameOver должен быть вызван.
+ if (clientState.currentGameState && clientState.currentGameState.isGameOver) {
+ if (window.gameUI?.showGameOver && !document.getElementById('game-over-screen').classList.contains('hidden')) {
+ // Экран уже показан
+ } else if (window.gameUI?.showGameOver) {
+ let playerWon = false;
+ if (data.winnerId) {
+ playerWon = data.winnerId === clientState.myPlayerId;
+ } else if (clientState.currentGameState.player && clientState.currentGameState.opponent) {
+ if (clientState.currentGameState.player.currentHp > 0 && clientState.currentGameState.opponent.currentHp <=0) {
+ playerWon = clientState.myPlayerId === clientState.currentGameState.player.id;
+ } else if (clientState.currentGameState.opponent.currentHp > 0 && clientState.currentGameState.player.currentHp <=0) {
+ playerWon = clientState.myPlayerId === clientState.currentGameState.opponent.id;
+ }
+ }
+ window.gameUI.showGameOver(playerWon, data.reason || "Игра завершена", clientState.opponentCharacterKey, { finalGameState: clientState.currentGameState, ...data });
+ }
+ if (returnToMenuButton) returnToMenuButton.disabled = false;
+ }
+ }
+
+
+ // --- Обработчики событий Socket.IO ---
+ socket.on('gameStarted', (data) => {
+ handleGameDataReceived(data, 'gameStarted');
+ });
+
+ socket.on('gameState', (data) => {
+ handleGameDataReceived(data, 'gameState (reconnect)');
});
socket.on('gameStateUpdate', (data) => {
if (!clientState.isLoggedIn || !clientState.isInGame || !clientState.currentGameId || !window.GAME_CONFIG) return;
+ const username = clientState.loggedInUsername || 'N/A';
+ console.log(`[CLIENT ${username}] Event: gameStateUpdate.`);
clientState.currentGameState = data.gameState;
- ui.updateGlobalWindowVariablesForUI(); // Обновляем window.gameState для ui.js
+ ui.updateGlobalWindowVariablesForUI();
- if (window.gameUI?.updateUI) window.gameUI.updateUI();
+ if (window.gameUI?.updateUI) {
+ requestAnimationFrame(() => {
+ window.gameUI.updateUI();
+ if (clientState.isInGame && clientState.currentGameState && !clientState.currentGameState.isGameOver && window.GAME_CONFIG) {
+ const config = window.GAME_CONFIG;
+ const isMyActualTurn = clientState.myPlayerId &&
+ ((clientState.currentGameState.isPlayerTurn && clientState.myPlayerId === config.PLAYER_ID) ||
+ (!clientState.currentGameState.isPlayerTurn && clientState.myPlayerId === config.OPPONENT_ID));
+
+ if (isMyActualTurn) {
+ enableGameControls();
+ } else {
+ disableGameControls();
+ }
+
+ console.log(`[CLIENT ${username}] gameStateUpdate - Clearing game status message as game is active.`);
+ ui.setGameStatusMessage("");
+
+ } else if (clientState.currentGameState && clientState.currentGameState.isGameOver) {
+ disableGameControls();
+ }
+ });
+ }
- // Добавляем только новые логи, если они есть в этом частичном обновлении
if (window.gameUI?.addToLog && data.log) {
data.log.forEach(log => window.gameUI.addToLog(log.message, log.type));
}
- // Логика включения/выключения контролов на основе gameState.isPlayerTurn и myPlayerId
- // обычно делается внутри window.gameUI.updateUI()
});
socket.on('logUpdate', (data) => {
if (!clientState.isLoggedIn || !clientState.isInGame || !clientState.currentGameId || !window.GAME_CONFIG) return;
+ const username = clientState.loggedInUsername || 'N/A';
+ // console.log(`[CLIENT ${username}] Event: logUpdate. Logs:`, data.log);
if (window.gameUI?.addToLog && data.log) {
data.log.forEach(log => window.gameUI.addToLog(log.message, log.type));
}
@@ -263,77 +270,93 @@ export function initGameplay(dependencies) {
socket.on('gameOver', (data) => {
if (!clientState.isLoggedIn || !clientState.currentGameId || !window.GAME_CONFIG) {
- // Если мы не в игре, но залогинены, запросим состояние (вдруг это старое событие)
if (!clientState.currentGameId && clientState.isLoggedIn) socket.emit('requestGameState');
- else if (!clientState.isLoggedIn) ui.showAuthScreen(); // Если вообще не залогинены
+ else if (!clientState.isLoggedIn) ui.showAuthScreen();
return;
}
+ const username = clientState.loggedInUsername || 'N/A';
+ console.log(`[CLIENT ${username}] Event: gameOver.`);
const playerWon = data.winnerId === clientState.myPlayerId;
- clientState.currentGameState = data.finalGameState; // Обновляем состояние последним
- // clientState.isInGame = false; // НЕ СБРАСЫВАЕМ ЗДЕСЬ, чтобы UI показывал экран GameOver. Сбросится при выходе в меню.
+ clientState.currentGameState = data.finalGameState;
+ clientState.isInGame = false;
- ui.updateGlobalWindowVariablesForUI(); // Обновляем window.gameState для ui.js
-
- if (window.gameUI?.updateUI) window.gameUI.updateUI(); // Обновляем панели в последний раз
+ ui.updateGlobalWindowVariablesForUI();
+ if (window.gameUI?.updateUI) requestAnimationFrame(() => window.gameUI.updateUI());
if (window.gameUI?.addToLog && data.log) {
data.log.forEach(log => window.gameUI.addToLog(log.message, log.type));
}
-
if (window.gameUI?.showGameOver) {
const oppKey = clientState.opponentBaseStatsServer?.characterKey;
- window.gameUI.showGameOver(playerWon, data.reason, oppKey, data); // ui.js покажет модалку
+ window.gameUI.showGameOver(playerWon, data.reason, oppKey, data);
}
-
- if (returnToMenuButton) returnToMenuButton.disabled = false; // Активируем кнопку "Вернуться в меню"
-
- ui.setGameStatusMessage("Игра окончена. " + (playerWon ? "Вы победили!" : "Вы проиграли."));
-
- // Обновляем UI таймера, чтобы показать "Конец" или скрыть
+ if (returnToMenuButton) returnToMenuButton.disabled = false;
+ // `ui.setGameStatusMessage` будет установлено специфичным сообщением о результате игры
+ // ui.setGameStatusMessage("Игра окончена. " + (playerWon ? "Вы победили!" : "Вы проиграли."));
if (window.gameUI?.updateTurnTimerDisplay) {
window.gameUI.updateTurnTimerDisplay(null, false, clientState.currentGameState?.gameMode);
}
- // Контролы должны быть заблокированы, т.к. игра окончена (ui.js->updateUI это сделает)
+ disableGameControls();
});
socket.on('opponentDisconnected', (data) => {
if (!clientState.isLoggedIn || !clientState.isInGame || !clientState.currentGameId || !window.GAME_CONFIG) return;
-
+ const username = clientState.loggedInUsername || 'N/A';
+ console.log(`[CLIENT ${username}] Event: opponentDisconnected.`);
const name = data.disconnectedCharacterName || clientState.opponentBaseStatsServer?.name || 'Противник';
- if (window.gameUI?.addToLog) {
- window.gameUI.addToLog(`🔌 Противник (${name}) отключился.`, 'system');
- }
- // Если игра еще не окончена, сервер может дать время на переподключение или объявить победу
+ // Сообщение об отключении оппонента должно приходить через 'logUpdate' от сервера
+ // if (window.gameUI?.addToLog) {
+ // window.gameUI.addToLog(`🔌 Противник (${name}) отключился.`, 'system');
+ // }
+
if (clientState.currentGameState && !clientState.currentGameState.isGameOver) {
ui.setGameStatusMessage(`Противник (${name}) отключился. Ожидание...`, true);
- disableGameControls(); // Блокируем управление, пока сервер не решит исход
+ disableGameControls();
}
});
socket.on('turnTimerUpdate', (data) => {
- if (!clientState.isInGame || !clientState.currentGameState || clientState.currentGameState.isGameOver) {
- // Если игра окончена или не в игре, обновляем таймер соответственно
- if (window.gameUI?.updateTurnTimerDisplay && !clientState.currentGameState?.isGameOver) {
- window.gameUI.updateTurnTimerDisplay(null, false, clientState.currentGameState?.gameMode);
+ if (!clientState.isInGame || !clientState.currentGameState || !window.GAME_CONFIG) {
+ if (window.gameUI?.updateTurnTimerDisplay && clientState.currentGameState && !clientState.currentGameState.isGameOver) {
+ window.gameUI.updateTurnTimerDisplay(null, false, clientState.currentGameState.gameMode);
}
return;
}
- if (window.gameUI && typeof window.gameUI.updateTurnTimerDisplay === 'function') {
- const config = window.GAME_CONFIG || {};
- // Определяем, является ли текущий ход ходом этого клиента
- const isMyActualTurn = clientState.myPlayerId && clientState.currentGameState.isPlayerTurn === (clientState.myPlayerId === config.PLAYER_ID);
- window.gameUI.updateTurnTimerDisplay(data.remainingTime, isMyActualTurn, clientState.currentGameState.gameMode);
+ if (clientState.currentGameState.isGameOver) {
+ if (window.gameUI?.updateTurnTimerDisplay) {
+ window.gameUI.updateTurnTimerDisplay(null, false, clientState.currentGameState.gameMode);
+ }
+ disableGameControls();
+ return;
+ }
+
+ const username = clientState.loggedInUsername || 'N/A';
+ // console.log(`[CLIENT ${username}] Event: turnTimerUpdate.`);
+
+ if (window.gameUI && typeof window.gameUI.updateTurnTimerDisplay === 'function') {
+ const config = window.GAME_CONFIG;
+ const isMyActualTurn = clientState.myPlayerId && clientState.currentGameState &&
+ ((clientState.currentGameState.isPlayerTurn && clientState.myPlayerId === config.PLAYER_ID) ||
+ (!clientState.currentGameState.isPlayerTurn && clientState.myPlayerId === config.OPPONENT_ID));
+
+ window.gameUI.updateTurnTimerDisplay(data.remainingTime, isMyActualTurn, clientState.currentGameState.gameMode);
+
+ if (isMyActualTurn) {
+ enableGameControls();
+ } else {
+ disableGameControls();
+ }
+
+ if (!clientState.currentGameState.isGameOver) {
+ console.log(`[CLIENT ${username}] turnTimerUpdate - Clearing game status message as timer is active.`);
+ ui.setGameStatusMessage("");
+ }
}
- // Логика включения/выключения контролов на основе isMyActualTurn
- // обычно выполняется в window.gameUI.updateUI(), которая вызывается после gameStateUpdate.
- // Если turnTimerUpdate приходит отдельно и должен влиять на контролы, то нужно добавить:
- // if (isMyActualTurn) enableGameControls(); else disableGameControls();
- // Но это может конфликтовать с логикой в updateUI(). Обычно updateUI() - главный источник правды.
});
- // Начальная деактивация игровых контролов при загрузке модуля
+ // Начальная деактивация
disableGameControls();
}
\ No newline at end of file
diff --git a/public/js/main.js b/public/js/main.js
index a326102..ea0ea2b 100644
--- a/public/js/main.js
+++ b/public/js/main.js
@@ -3,56 +3,37 @@
import { initAuth } from './auth.js';
import { initGameSetup } from './gameSetup.js';
import { initGameplay } from './gameplay.js';
-// Предполагаем, что ui.js загружен перед этим скриптом (в HTML)
-// и создал глобальный объект window.gameUI
-// Также ui.js будет читать window.gameState, window.gameData, window.myPlayerId, window.GAME_CONFIG
+// ui.js загружен глобально
+
+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', () => {
- const socket = io({
- // Опции Socket.IO, если нужны
- });
+ const SERVER_URL = 'http://127.0.0.1:3200';
+ const API_BASE_URL = SERVER_URL;
+ const initialToken = localStorage.getItem('jwtToken');
- // --- DOM Элементы для общего UI-управления ---
- // (Эти элементы управляют общим потоком приложения, а не деталями боя)
- 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'); // Для auth.js
-
- 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"]'); // для gameSetup.js
-
- const gameWrapper = document.querySelector('.game-wrapper');
- // Элементы, связанные с gameOver, управляются через window.gameUI.showGameOver,
- // но кнопка "Вернуться в меню" может быть здесь для общего сброса.
- const returnToMenuButton = document.getElementById('return-to-menu-button');
- const turnTimerContainer = document.getElementById('turn-timer-container');
- const turnTimerSpan = document.getElementById('turn-timer');
-
-
- // --- Состояние клиента (глобальное для main и передаваемое в модули) ---
- // Это состояние будет модифицироваться из разных модулей
let clientState = {
isLoggedIn: false,
loggedInUsername: '',
myUserId: null,
isInGame: false,
- // Игровые переменные, которые ранее были глобальными в client.js
- // и от которых зависит ui.js
currentGameId: null,
- currentGameState: null,
+ currentGameState: null, // Будет объектом или null
myPlayerId: null,
myCharacterKey: null,
opponentCharacterKey: null,
@@ -62,10 +43,55 @@ document.addEventListener('DOMContentLoaded', () => {
opponentAbilitiesServer: null,
};
- // Обновляем глобальные переменные window, на которые рассчитывает ui.js
- // Это временная мера. В идеале, ui.js должен получать эти данные как аргументы функций.
+ 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') }
+ });
+
+ 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'); // Он же в ui.elements.gameOver.returnToMenuButton
+ const turnTimerContainer = document.getElementById('turn-timer-container');
+ const turnTimerSpan = document.getElementById('turn-timer');
+
function updateGlobalWindowVariablesForUI() {
- window.gameState = clientState.currentGameState;
+ // console.log("[Main] Updating global window variables. currentGameState:", clientState.currentGameState ? JSON.parse(JSON.stringify(clientState.currentGameState)) : null);
+ window.gameState = clientState.currentGameState; // Может быть null
window.gameData = {
playerBaseStats: clientState.playerBaseStatsServer,
opponentBaseStats: clientState.opponentBaseStatsServer,
@@ -73,78 +99,18 @@ document.addEventListener('DOMContentLoaded', () => {
opponentAbilities: clientState.opponentAbilitiesServer
};
window.myPlayerId = clientState.myPlayerId;
- // window.GAME_CONFIG остается как есть, если он глобальный и не меняется часто
- // Если GAME_CONFIG приходит от сервера, его тоже нужно обновлять здесь
- // if (clientState.serverConfig) window.GAME_CONFIG = { ...clientState.serverConfig };
- }
-
-
- // --- Функции управления UI (для переключения основных экранов и общих сообщений) ---
- function showAuthScreen() {
- authSection.style.display = 'block';
- userInfoDiv.style.display = 'none';
- gameSetupDiv.style.display = 'none';
- gameWrapper.style.display = 'none';
- if (window.gameUI?.showGameOver) { // Скрываем модалку GameOver, если была
- window.gameUI.showGameOver(false, "", null, { finalGameState: { isGameOver: false } });
- }
- setAuthMessage("Ожидание подключения к серверу...");
- statusContainer.style.display = 'block';
- clientState.isInGame = false;
- // disableGameControls(); // Вызов будет из gameplay.js
- resetGameVariables(); // Важно для сброса состояния
- updateGlobalWindowVariablesForUI(); // Обновляем глоб. переменные для ui.js
- if (turnTimerContainer) turnTimerContainer.style.display = 'none';
- if (turnTimerSpan) turnTimerSpan.textContent = '--';
- }
-
- function showGameSelectionScreen(username) {
- authSection.style.display = 'none';
- userInfoDiv.style.display = 'block';
- loggedInUsernameSpan.textContent = username;
- gameSetupDiv.style.display = 'block';
- gameWrapper.style.display = 'none';
- if (window.gameUI?.showGameOver) { // Скрываем модалку GameOver
- window.gameUI.showGameOver(false, "", null, { finalGameState: { isGameOver: false } });
- }
- setGameStatusMessage("Выберите режим игры или присоединитесь к существующей.");
- statusContainer.style.display = 'block';
- socket.emit('requestPvPGameList'); // Запрашиваем список игр
- if (availableGamesDiv) availableGamesDiv.innerHTML = '
Доступные PvP игры:
Загрузка...
'; // Очистка перед запросом
- if (gameIdInput) gameIdInput.value = '';
-
- const elenaRadio = document.getElementById('char-elena'); // Для сброса выбора персонажа
- if (elenaRadio) elenaRadio.checked = true;
-
- clientState.isInGame = false;
- // disableGameControls(); // Вызов будет из gameplay.js
- resetGameVariables();
- updateGlobalWindowVariablesForUI();
- if (turnTimerContainer) turnTimerContainer.style.display = 'none';
- if (turnTimerSpan) turnTimerSpan.textContent = '--';
- enableSetupButtons();
- }
-
- function showGameScreen() {
- if (window.gameUI?.showGameOver) { // Скрываем модалку GameOver
- window.gameUI.showGameOver(false, "", null, { finalGameState: { isGameOver: false } });
- }
- authSection.style.display = 'none';
- userInfoDiv.style.display = 'block'; // Оставляем инфо о пользователе
- gameSetupDiv.style.display = 'none';
- gameWrapper.style.display = 'flex';
- setGameStatusMessage(""); // Очищаем статус
- statusContainer.style.display = 'none'; // Скрываем общий статус контейнер
- clientState.isInGame = true;
- // disableGameControls(); // Начальная деактивация, gameplay.js включит при ходе
- updateGlobalWindowVariablesForUI(); // Убедимся, что ui.js имеет свежие данные
- if (turnTimerContainer) turnTimerContainer.style.display = 'block';
- if (turnTimerSpan) turnTimerSpan.textContent = '--';
+ // window.GAME_CONFIG устанавливается при gameStarted/gameState из gameplay.js
}
function resetGameVariables() {
+ console.log("[Main:resetGameVariables] Resetting game variables. State BEFORE:", JSON.parse(JSON.stringify(clientState)));
clientState.currentGameId = null;
+ // ВАЖНО: currentGameState должен быть сброшен в состояние "нет игры"
+ // Либо null, либо объект, который ui.js интерпретирует как "нет игры"
clientState.currentGameState = null;
+ // Можно также так, если ui.js лучше работает с объектом:
+ // clientState.currentGameState = { isGameOver: false, player: null, opponent: null, turnNumber: 0 };
+
clientState.myPlayerId = null;
clientState.myCharacterKey = null;
clientState.opponentCharacterKey = null;
@@ -152,17 +118,123 @@ document.addEventListener('DOMContentLoaded', () => {
clientState.opponentBaseStatsServer = null;
clientState.playerAbilitiesServer = null;
clientState.opponentAbilitiesServer = null;
- // Также обновляем глобальные переменные для ui.js
- updateGlobalWindowVariablesForUI();
+ // clientState.isInGame будет установлено в вызывающей функции (showAuthScreen/showGameSelectionScreen)
+
+ 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; // Кнопка Logout должна быть недоступна на экране логина
+ }
+
+ 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; // Logout доступен
+ 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 = '
Доступные PvP игры:
Загрузка...
';
+ 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();
+ // Убедимся, что кнопка "Вернуться в меню" на gameOver модалке (если она вдруг видима) активна,
+ // хотя сама модалка должна быть скрыта.
+ if (window.gameUI?.uiElements?.gameOver?.returnToMenuButton) {
+ window.gameUI.uiElements.gameOver.returnToMenuButton.disabled = false;
+ }
+ }
+
+ function showGameScreen() {
+ console.log("[Main:showGameScreen] Showing Game Screen.");
+ // Не нужно здесь вызывать explicitlyHideGameOverModal, так как если игра начинается,
+ // а модалка была видима, это ошибка логики где-то еще.
+ // GameStarted/GameState должно само приводить UI в порядок.
+ 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(); // Обновляем перед тем, как UI начнет рендерить игровой экран
+ 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'; // Скрываем другой статус
+ if (message && gameStatusMessage && gameStatusMessage.style.display !== 'none') gameStatusMessage.style.display = 'none';
}
function setGameStatusMessage(message, isError = false) {
@@ -172,10 +244,9 @@ document.addEventListener('DOMContentLoaded', () => {
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'; // Скрываем другой статус
+ if (message && authMessage && authMessage.style.display !== 'none') authMessage.style.display = 'none';
}
- // Функции для управления кнопками на экране выбора игры (могут быть вызваны из gameSetup)
function disableSetupButtons() {
if(createAIGameButton) createAIGameButton.disabled = true;
if(createPvPGameButton) createPvPGameButton.disabled = true;
@@ -188,109 +259,159 @@ document.addEventListener('DOMContentLoaded', () => {
if(createPvPGameButton) createPvPGameButton.disabled = false;
if(joinPvPGameButton) joinPvPGameButton.disabled = false;
if(findRandomPvPGameButton) findRandomPvPGameButton.disabled = false;
- // Кнопки в списке игр включаются в updateAvailableGamesList (в gameSetup.js)
+ // Кнопки в списке доступных игр управляются в gameSetup.js -> updateAvailableGamesList
}
- // --- Сборка зависимостей для передачи в модули ---
const dependencies = {
socket,
- clientState, // Объект состояния, который модули могут читать и изменять
- ui: { // Функции и элементы для управления общим UI и состоянием
+ clientState,
+ ui: {
showAuthScreen,
showGameSelectionScreen,
showGameScreen,
setAuthMessage,
setGameStatusMessage,
- resetGameVariables,
- updateGlobalWindowVariablesForUI, // Важно для ui.js
+ resetGameVariables, // Передаем, чтобы другие модули могли вызвать при необходимости (хотя лучше избегать)
+ updateGlobalWindowVariablesForUI,
disableSetupButtons,
enableSetupButtons,
- elements: { // Передаем элементы, нужные для специфической логики модулей
- // Для auth.js
- loginForm,
- registerForm,
- logoutButton,
- // Для gameSetup.js
- createAIGameButton,
- createPvPGameButton,
- joinPvPGameButton,
- findRandomPvPGameButton,
- gameIdInput,
- availableGamesDiv,
- pvpCharacterRadios,
- // Для gameplay.js (или для обработки gameover здесь)
- returnToMenuButton,
+ elements: {
+ loginForm, registerForm, logoutButton,
+ createAIGameButton, createPvPGameButton, joinPvPGameButton,
+ findRandomPvPGameButton, gameIdInput, availableGamesDiv,
+ pvpCharacterRadios, returnToMenuButton, // returnToMenuButton из gameplay.js, но здесь тоже может быть полезен
}
},
- // gameUI: window.gameUI // Можно передать, если модули должны напрямую вызывать gameUI.
- // Но пока gameplay.js будет использовать глобальный window.gameUI
+ API_BASE_URL: API_BASE_URL
};
- // Инициализация модулей
initAuth(dependencies);
initGameSetup(dependencies);
initGameplay(dependencies);
-
- // --- Обработчики событий Socket.IO (глобальные для приложения) ---
socket.on('connect', () => {
- console.log('[Client] Socket connected:', socket.id);
- setAuthMessage("Успешно подключено к серверу. Вход...");
+ 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("Восстановление игровой сессии...");
+ }
+ // Не очищаем здесь resetGameVariables, так как gameplay.js ожидает, что clientState может содержать
+ // предыдущие данные, которые он перезапишет при получении gameState или gameStarted.
+ // Если придет gameNotFound, то там уже будет reset.
socket.emit('requestGameState');
} else {
- // Показываем экран логина, если не залогинены
- showAuthScreen();
+ console.log('[Main:SocketConnect] Client state indicates NOT logged in. Showing auth screen.');
+ showAuthScreen(); // Убеждаемся, что все сброшено и показан экран логина
+ setAuthMessage("Пожалуйста, войдите или зарегистрируйтесь.");
}
});
- socket.on('disconnect', (reason) => {
- console.warn('[Client] Disconnected:', reason);
- setGameStatusMessage(`Отключено от сервера: ${reason}. Попытка переподключения...`, true);
- // Здесь можно добавить логику для UI, показывающую состояние "отключено"
- // disableGameControls(); // будет в gameplay
- if (turnTimerSpan) turnTimerSpan.textContent = 'Откл.';
- // Не сбрасываем isLoggedIn, чтобы при переподключении можно было восстановить сессию
+ 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(); // Это вызовет resetGameVariables и скроет модалку
+ setAuthMessage("Ошибка аутентификации. Пожалуйста, войдите снова.", true);
+ } else {
+ if (clientState.isLoggedIn && clientState.isInGame) {
+ setGameStatusMessage(`Ошибка подключения: ${err.message}. Попытка переподключения...`, true);
+ } else if (clientState.isLoggedIn) {
+ setGameStatusMessage(`Ошибка подключения к серверу: ${err.message}. Попытка переподключения...`, true);
+ } else {
+ setAuthMessage(`Ошибка подключения к серверу: ${err.message}. Попытка переподключения...`, true);
+ if (authSection.style.display !== 'block') {
+ showAuthScreen(); // Если не на экране логина, но ошибка не auth, все равно показываем его
+ }
+ }
+ }
+ if (turnTimerSpan) turnTimerSpan.textContent = 'Ошибка';
+ });
+
+ socket.on('disconnect', (reason) => {
+ console.warn('[Main:SocketDisconnect] Disconnected from server:', reason);
+ // Сообщения в зависимости от текущего состояния
+ if (clientState.isInGame) {
+ setGameStatusMessage(`Потеряно соединение: ${reason}. Попытка переподключения...`, true);
+ } else if (clientState.isLoggedIn) {
+ // Уже должен быть на экране выбора игры или восстановления, setGameStatusMessage там уместно
+ if (gameSetupDiv.style.display === 'block') {
+ setGameStatusMessage(`Потеряно соединение с сервером: ${reason}. Попытка переподключения...`, true);
+ } else {
+ // Если где-то между экранами, но залогинен
+ setAuthMessage(`Потеряно соединение: ${reason}. Попытка переподключения...`, true); // Используем authMessage для общего случая
+ }
+ } else {
+ setAuthMessage(`Потеряно соединение с сервером: ${reason}. Попытка переподключения...`, true);
+ }
+ if (turnTimerSpan) turnTimerSpan.textContent = 'Откл.';
+ // Не сбрасываем clientState.isLoggedIn здесь, чтобы socket.connect мог попытаться восстановить сессию
});
- // Общая обработка ошибок от сервера, если они не перехвачены в модулях
socket.on('gameError', (data) => {
- console.error('[Client] Received gameError:', data.message);
- // Показываем ошибку пользователю
+ 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 и здесь, если ошибка критическая для игры
+ } else if (clientState.isLoggedIn) { // На экране выбора игры
setGameStatusMessage(`❌ Ошибка: ${data.message}`, true);
- enableSetupButtons(); // Возвращаем активность кнопкам на экране выбора игры
- } else {
+ enableSetupButtons(); // Разблокировать кнопки, если ошибка при создании/присоединении
+ } else { // На экране логина
setAuthMessage(`❌ Ошибка: ${data.message}`, true);
if(registerForm) registerForm.querySelector('button').disabled = false;
if(loginForm) loginForm.querySelector('button').disabled = false;
}
});
- // Обработчик для gameNotFound, который может прийти при реконнекте, если игры нет
socket.on('gameNotFound', (data) => {
- console.log('[Client] Main: Game not found/ended:', data?.message);
- dependencies.ui.resetGameVariables(); // Сбрасываем игровые переменные
- clientState.isInGame = false;
- // disableGameControls(); // в gameplay
- if (window.gameUI?.showGameOver) window.gameUI.showGameOver(false, "", null, { finalGameState: { isGameOver: false } }); // Скрыть модалку
+ console.log('[Main:SocketGameNotFound] Game not found/ended after request:', data?.message);
+
+ // Важно: gameNotFound означает, что активной игры нет.
+ // Сбрасываем состояние и показываем экран выбора игры, если залогинены.
+ clientState.isInGame = false; // Явно выходим из игры
+ resetGameVariables(); // Полный сброс игровых переменных
+ explicitlyHideGameOverModal(); // Убеждаемся, что модалка скрыта
+
if (turnTimerContainer) turnTimerContainer.style.display = 'none';
if (turnTimerSpan) turnTimerSpan.textContent = '--';
- if (clientState.isLoggedIn) {
- showGameSelectionScreen(clientState.loggedInUsername);
- setGameStatusMessage(data?.message || "Активная игровая сессия не найдена.");
+ if (clientState.isLoggedIn && clientState.myUserId) {
+ showGameSelectionScreen(clientState.loggedInUsername); // Переходим на выбор игры (он вызовет resetGameVariables еще раз, но это не страшно)
+ setGameStatusMessage(data?.message || "Активная игровая сессия не найдена. Выберите новую игру.");
} else {
+ // Если по какой-то причине мы не залогинены (например, токен истек и connect_error сбросил isLoggedIn)
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';
- // --- Инициализация UI ---
- showAuthScreen(); // Показываем начальный экран аутентификации
+ if (clientState.isLoggedIn) {
+ setGameStatusMessage("Подключение и восстановление сессии..."); // Или setAuthMessage, если statusContainer не виден сразу
+ } else {
+ setAuthMessage("Подключение к серверу...");
+ }
+
+ socket.connect();
});
\ No newline at end of file
diff --git a/server/auth/authService.js b/server/auth/authService.js
index f0a6e79..99325c2 100644
--- a/server/auth/authService.js
+++ b/server/auth/authService.js
@@ -1,14 +1,15 @@
// /server/auth/authService.js
const bcrypt = require('bcryptjs'); // Для хеширования паролей
-const db = require('../core/db'); // Путь к вашему модулю для работы с базой данных (в папке core)
+const jwt = require('jsonwebtoken'); // <<< ДОБАВЛЕНО
+const db = require('../core/db'); // Путь к вашему модулю для работы с базой данных
const SALT_ROUNDS = 10; // Количество раундов для генерации соли bcrypt
/**
- * Регистрирует нового пользователя.
+ * Регистрирует нового пользователя и генерирует JWT.
* @param {string} username - Имя пользователя.
* @param {string} password - Пароль пользователя.
- * @returns {Promise