Обработка ситуаций рекконекта. Доработка 4. Клиент

This commit is contained in:
PsiMagistr 2025-05-29 15:35:11 +03:00
parent a8e383fb3c
commit 0a627deac2

View File

@ -2,19 +2,21 @@
export function initGameplay(dependencies) { export function initGameplay(dependencies) {
const { socket, clientState, ui } = dependencies; const { socket, clientState, ui } = dependencies;
const { returnToMenuButton } = ui.elements; const { returnToMenuButton } = ui.elements; // Предполагается, что это кнопка "Вернуться в меню" из модалки gameOver
const attackButton = document.getElementById('button-attack'); const attackButton = document.getElementById('button-attack');
const abilitiesGrid = document.getElementById('abilities-grid'); const abilitiesGrid = document.getElementById('abilities-grid');
// Инициализируем флаг в clientState, если он еще не существует (лучше делать в main.js) // Инициализируем флаг в clientState, если он еще не существует (лучше делать в main.js при объявлении clientState)
if (typeof clientState.isActionInProgress === 'undefined') { if (typeof clientState.isActionInProgress === 'undefined') {
clientState.isActionInProgress = false; clientState.isActionInProgress = false;
} }
// --- Вспомогательные функции --- // --- Вспомогательные функции ---
function enableGameControls(enableAttack = true, enableAbilities = true) { function enableGameControls(enableAttack = true, enableAbilities = true) {
// console.log(`[GP] enableGameControls called. enableAttack: ${enableAttack}, enableAbilities: ${enableAbilities}, isActionInProgress: ${clientState.isActionInProgress}`); const username = clientState.loggedInUsername || 'N/A';
// console.log(`[CLIENT ${username} GP] enableGameControls called. enableAttack: ${enableAttack}, enableAbilities: ${enableAbilities}, isActionInProgress: ${clientState.isActionInProgress}`);
if (clientState.isActionInProgress) { if (clientState.isActionInProgress) {
if (attackButton) attackButton.disabled = true; if (attackButton) attackButton.disabled = true;
if (abilitiesGrid) { if (abilitiesGrid) {
@ -22,8 +24,8 @@ export function initGameplay(dependencies) {
const cls = config.CSS_CLASS_ABILITY_BUTTON || 'ability-button'; const cls = config.CSS_CLASS_ABILITY_BUTTON || 'ability-button';
abilitiesGrid.querySelectorAll(`.${cls}`).forEach(b => { b.disabled = true; }); abilitiesGrid.querySelectorAll(`.${cls}`).forEach(b => { b.disabled = true; });
} }
// console.log(`[GP] Action in progress, controls remain disabled.`); // console.log(`[CLIENT ${username} GP] Action is in progress, controls remain disabled by enableGameControls.`);
if (window.gameUI?.updateUI) requestAnimationFrame(() => window.gameUI.updateUI()); // Не вызываем updateUI здесь, чтобы не было рекурсии, если updateUI вызвал enableGameControls
return; return;
} }
@ -33,37 +35,41 @@ export function initGameplay(dependencies) {
const cls = config.CSS_CLASS_ABILITY_BUTTON || 'ability-button'; const cls = config.CSS_CLASS_ABILITY_BUTTON || 'ability-button';
abilitiesGrid.querySelectorAll(`.${cls}`).forEach(b => { b.disabled = !enableAbilities; }); abilitiesGrid.querySelectorAll(`.${cls}`).forEach(b => { b.disabled = !enableAbilities; });
} }
// console.log(`[GP] Controls set. Attack disabled: ${attackButton ? attackButton.disabled : 'N/A'}`); // console.log(`[CLIENT ${username} GP] Controls set by enableGameControls. Attack disabled: ${attackButton ? attackButton.disabled : 'N/A'}`);
if (window.gameUI?.updateUI) { // Обновление UI для 반영反映 состояния disabled должно происходить в gameUI.updateUI()
requestAnimationFrame(() => window.gameUI.updateUI()); // Обновляем UI, чтобы 반영 반영反映 изменения в disabled // или здесь, если gameUI.updateUI() не покрывает это полностью.
if (window.gameUI?.updateUI && clientState.currentGameState && !clientState.currentGameState.isGameOver) { // Обновляем только если в активной игре
requestAnimationFrame(() => window.gameUI.updateUI());
} }
} }
function disableGameControls() { function disableGameControls() {
// console.log(`[GP] disableGameControls called.`); const username = clientState.loggedInUsername || 'N/A';
// console.log(`[CLIENT ${username} GP] disableGameControls called.`);
if (attackButton) attackButton.disabled = true; if (attackButton) attackButton.disabled = true;
if (abilitiesGrid) { if (abilitiesGrid) {
const config = window.GAME_CONFIG || {}; const config = window.GAME_CONFIG || {};
const cls = config.CSS_CLASS_ABILITY_BUTTON || 'ability-button'; const cls = config.CSS_CLASS_ABILITY_BUTTON || 'ability-button';
abilitiesGrid.querySelectorAll(`.${cls}`).forEach(b => { b.disabled = true; }); abilitiesGrid.querySelectorAll(`.${cls}`).forEach(b => { b.disabled = true; });
} }
if (window.gameUI?.updateUI) { // Обновление UI для 반영反映 состояния disabled
requestAnimationFrame(() => window.gameUI.updateUI()); // Обновляем UI, чтобы 반영 반영反映 изменения в disabled if (window.gameUI?.updateUI && clientState.currentGameState) { // Обновляем, если есть gameState (даже если gameOver)
requestAnimationFrame(() => window.gameUI.updateUI());
} }
} }
function initializeAbilityButtons() { function initializeAbilityButtons() {
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>';
return; return;
} }
abilitiesGrid.innerHTML = ''; abilitiesGrid.innerHTML = ''; // Очищаем предыдущие кнопки
const config = window.GAME_CONFIG; const config = window.GAME_CONFIG;
const abilitiesToDisplay = clientState.playerAbilitiesServer; const abilitiesToDisplay = clientState.playerAbilitiesServer;
const baseStatsForResource = clientState.playerBaseStatsServer; const baseStatsForResource = clientState.playerBaseStatsServer;
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;
} }
@ -74,25 +80,25 @@ export function initGameplay(dependencies) {
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(abilityButtonClass);
button.dataset.abilityId = ability.id; button.dataset.abilityId = ability.id; // Сохраняем ID способности
let cooldown = ability.cooldown; let cooldown = ability.cooldown;
let cooldownText = (typeof cooldown === 'number' && cooldown > 0) ? ` (КД: ${cooldown} х.)` : ""; let cooldownText = (typeof cooldown === 'number' && cooldown > 0) ? ` (КД: ${cooldown} х.)` : "";
let title = `${ability.name} (${ability.cost} ${resourceName})${cooldownText} - ${ability.description || 'Нет описания'}`; let title = `${ability.name} (${ability.cost} ${resourceName})${cooldownText} - ${ability.description || 'Нет описания'}`;
button.setAttribute('title', title); button.setAttribute('title', title); // Для всплывающей подсказки
const nameSpan = document.createElement('span'); nameSpan.classList.add('ability-name'); nameSpan.textContent = ability.name; button.appendChild(nameSpan); 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 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 cdDisplay = document.createElement('span'); cdDisplay.classList.add('ability-cooldown-display'); cdDisplay.style.display = 'none'; button.appendChild(cdDisplay); // Для отображения КД/безмолвия
button.addEventListener('click', handleAbilityButtonClick); button.addEventListener('click', handleAbilityButtonClick);
abilitiesGrid.appendChild(button); abilitiesGrid.appendChild(button);
}); });
const placeholder = abilitiesGrid.querySelector('.placeholder-text'); const placeholder = abilitiesGrid.querySelector('.placeholder-text');
if (placeholder) placeholder.remove(); if (placeholder) placeholder.remove(); // Удаляем плейсхолдер, если он был
} }
function handleAbilityButtonClick(event) { function handleAbilityButtonClick(event) {
const abilityId = event.currentTarget.dataset.abilityId; const abilityId = event.currentTarget.dataset.abilityId;
const username = clientState.loggedInUsername || 'N/A'; const username = clientState.loggedInUsername || 'N/A';
console.log(`[CLIENT ${username}] handleAbilityButtonClick. AbilityID: ${abilityId}, isActionInProgress: ${clientState.isActionInProgress}`); console.log(`[CLIENT ${username} GP] handleAbilityButtonClick. AbilityID: ${abilityId}, isActionInProgress: ${clientState.isActionInProgress}`);
if (clientState.isLoggedIn && if (clientState.isLoggedIn &&
clientState.isInGame && clientState.isInGame &&
@ -100,14 +106,14 @@ export function initGameplay(dependencies) {
abilityId && abilityId &&
clientState.currentGameState && clientState.currentGameState &&
!clientState.currentGameState.isGameOver && !clientState.currentGameState.isGameOver &&
!clientState.isActionInProgress) { // <--- ПРОВЕРКА ФЛАГА !clientState.isActionInProgress) {
console.log(`[CLIENT ${username}] Emitting playerAction (ability: ${abilityId}). Setting isActionInProgress = true.`); console.log(`[CLIENT ${username} GP] Emitting playerAction (ability: ${abilityId}). Setting isActionInProgress = true.`);
clientState.isActionInProgress = true; // <--- УСТАНОВКА ФЛАГА clientState.isActionInProgress = true;
disableGameControls(); // <--- БЛОКИРОВКА СРАЗУ disableGameControls(); // Блокируем все контролы немедленно
socket.emit('playerAction', { actionType: 'ability', abilityId: abilityId }); socket.emit('playerAction', { actionType: 'ability', abilityId: abilityId });
} else { } else {
console.warn(`[CLIENT ${username}] Cannot perform ability action. Conditions not met or action in progress. InGame: ${clientState.isInGame}, GameOver: ${clientState.currentGameState?.isGameOver}, ActionInProgress: ${clientState.isActionInProgress}`); console.warn(`[CLIENT ${username} GP] Cannot perform ability action. Conditions not met or action in progress. InGame: ${clientState.isInGame}, GameOver: ${clientState.currentGameState?.isGameOver}, ActionInProgress: ${clientState.isActionInProgress}, AbilityID: ${abilityId}`);
} }
} }
@ -115,36 +121,38 @@ export function initGameplay(dependencies) {
if (attackButton) { if (attackButton) {
attackButton.addEventListener('click', () => { attackButton.addEventListener('click', () => {
const username = clientState.loggedInUsername || 'N/A'; const username = clientState.loggedInUsername || 'N/A';
console.log(`[CLIENT ${username}] Attack button clicked. isActionInProgress: ${clientState.isActionInProgress}`); console.log(`[CLIENT ${username} GP] Attack button clicked. isActionInProgress: ${clientState.isActionInProgress}`);
if (clientState.isLoggedIn && if (clientState.isLoggedIn &&
clientState.isInGame && clientState.isInGame &&
clientState.currentGameId && clientState.currentGameId &&
clientState.currentGameState && clientState.currentGameState &&
!clientState.currentGameState.isGameOver && !clientState.currentGameState.isGameOver &&
!clientState.isActionInProgress) { // <--- ПРОВЕРКА ФЛАГА !clientState.isActionInProgress) {
console.log(`[CLIENT ${username}] Emitting playerAction (attack). Setting isActionInProgress = true.`); console.log(`[CLIENT ${username} GP] Emitting playerAction (attack). Setting isActionInProgress = true.`);
clientState.isActionInProgress = true; // <--- УСТАНОВКА ФЛАГА clientState.isActionInProgress = true;
disableGameControls(); // <--- БЛОКИРОВКА СРАЗУ disableGameControls(); // Блокируем все контролы немедленно
socket.emit('playerAction', { actionType: 'attack' }); socket.emit('playerAction', { actionType: 'attack' });
} else { } else {
console.warn(`[CLIENT ${username}] Cannot perform attack action. Conditions not met or action in progress. InGame: ${clientState.isInGame}, GameOver: ${clientState.currentGameState?.isGameOver}, ActionInProgress: ${clientState.isActionInProgress}`); console.warn(`[CLIENT ${username} GP] Cannot perform attack action. Conditions not met or action in progress. InGame: ${clientState.isInGame}, GameOver: ${clientState.currentGameState?.isGameOver}, ActionInProgress: ${clientState.isActionInProgress}`);
} }
}); });
} }
if (returnToMenuButton) { if (returnToMenuButton) {
returnToMenuButton.addEventListener('click', () => { returnToMenuButton.addEventListener('click', () => {
const username = clientState.loggedInUsername || 'N/A';
console.log(`[CLIENT ${username} GP] Return to menu button clicked.`);
if (!clientState.isLoggedIn) { if (!clientState.isLoggedIn) {
ui.showAuthScreen(); ui.showAuthScreen(); // Если не залогинен, на экран авторизации
return; return;
} }
returnToMenuButton.disabled = true; // Блокируем сразу, чтобы избежать двойных кликов returnToMenuButton.disabled = true;
clientState.isActionInProgress = false; // Сбрасываем на всякий случай, если покидаем игру clientState.isActionInProgress = false; // Сбрасываем флаг при выходе в меню
clientState.isInGame = false; clientState.isInGame = false;
disableGameControls(); disableGameControls(); // Отключаем игровые контролы
ui.showGameSelectionScreen(clientState.loggedInUsername); ui.showGameSelectionScreen(clientState.loggedInUsername); // Показываем экран выбора игры
}); });
} }
@ -152,16 +160,16 @@ export function initGameplay(dependencies) {
// --- ОБЩИЙ ОБРАБОТЧИК ДЛЯ ЗАПУСКА/ВОССТАНОВЛЕНИЯ ИГРЫ --- // --- ОБЩИЙ ОБРАБОТЧИК ДЛЯ ЗАПУСКА/ВОССТАНОВЛЕНИЯ ИГРЫ ---
function handleGameDataReceived(data, eventName = "unknown") { function handleGameDataReceived(data, eventName = "unknown") {
if (!clientState.isLoggedIn) { if (!clientState.isLoggedIn) {
console.warn(`[CLIENT] handleGameDataReceived (${eventName}) called, but client not logged in. Ignoring.`); console.warn(`[CLIENT GP] handleGameDataReceived (${eventName}) called, but client not logged in. Ignoring.`);
return; return;
} }
const username = clientState.loggedInUsername || 'N/A'; const username = clientState.loggedInUsername || 'N/A';
console.log(`[CLIENT ${username}] handleGameDataReceived from event: ${eventName}. GameID: ${data.gameId}, YourPlayerID: ${data.yourPlayerId}, GS.isPlayerTurn: ${data.initialGameState?.isPlayerTurn || data.gameState?.isPlayerTurn}`); console.log(`[CLIENT ${username} GP] handleGameDataReceived from event: ${eventName}. GameID: ${data.gameId}, YourPlayerID: ${data.yourPlayerId}, GS.isPlayerTurn: ${data.initialGameState?.isPlayerTurn || data.gameState?.isPlayerTurn}`);
clientState.isActionInProgress = false; // <--- СБРОС ФЛАГА при получении нового полного состояния clientState.isActionInProgress = false; // Сброс флага при получении нового полного состояния игры
clientState.currentGameId = data.gameId; clientState.currentGameId = data.gameId;
clientState.myPlayerId = data.yourPlayerId; clientState.myPlayerId = data.yourPlayerId; // Роль игрока (player/opponent)
clientState.currentGameState = data.initialGameState || data.gameState; clientState.currentGameState = data.initialGameState || data.gameState;
clientState.playerBaseStatsServer = data.playerBaseStats; clientState.playerBaseStatsServer = data.playerBaseStats;
clientState.opponentBaseStatsServer = data.opponentBaseStats; clientState.opponentBaseStatsServer = data.opponentBaseStats;
@ -173,28 +181,28 @@ export function initGameplay(dependencies) {
if (clientState.currentGameState && !clientState.currentGameState.isGameOver) { if (clientState.currentGameState && !clientState.currentGameState.isGameOver) {
clientState.isInGame = true; clientState.isInGame = true;
} else if (clientState.currentGameState && clientState.currentGameState.isGameOver) { } else if (clientState.currentGameState && clientState.currentGameState.isGameOver) {
clientState.isInGame = false; clientState.isInGame = false; // Если игра уже завершена, мы не в активной игре
} }
if (data.clientConfig) { if (data.clientConfig) {
window.GAME_CONFIG = { ...window.GAME_CONFIG, ...data.clientConfig }; window.GAME_CONFIG = { ...(window.GAME_CONFIG || {}), ...data.clientConfig };
} 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', LOG_TYPE_SYSTEM: 'system' };
} }
ui.updateGlobalWindowVariablesForUI(); ui.updateGlobalWindowVariablesForUI(); // Обновляем глобальные переменные для ui.js
const gameWrapperElement = document.querySelector('.game-wrapper'); const gameWrapperElement = document.querySelector('.game-wrapper');
if (clientState.isInGame && clientState.currentGameState && !clientState.currentGameState.isGameOver) { if (clientState.isInGame && clientState.currentGameState && !clientState.currentGameState.isGameOver) {
const isGameWrapperVisible = gameWrapperElement && (gameWrapperElement.style.display === 'flex' || getComputedStyle(gameWrapperElement).display === 'flex'); const isGameWrapperVisible = gameWrapperElement && (gameWrapperElement.style.display === 'flex' || getComputedStyle(gameWrapperElement).display === 'flex');
if (!isGameWrapperVisible) { if (!isGameWrapperVisible) {
ui.showGameScreen(); ui.showGameScreen(); // Показываем игровой экран, если он не был виден
} }
} }
initializeAbilityButtons(); initializeAbilityButtons(); // Инициализируем кнопки способностей на основе полученных данных
if (window.gameUI?.uiElements?.log?.list) { if (window.gameUI?.uiElements?.log?.list) {
window.gameUI.uiElements.log.list.innerHTML = ''; window.gameUI.uiElements.log.list.innerHTML = ''; // Очищаем UI-лог перед добавлением новых
} }
if (window.gameUI?.addToLog && data.log) { if (window.gameUI?.addToLog && data.log) {
data.log.forEach(logEntry => { data.log.forEach(logEntry => {
@ -202,9 +210,10 @@ export function initGameplay(dependencies) {
}); });
} }
// Запрос на обновление UI и контролов
requestAnimationFrame(() => { requestAnimationFrame(() => {
if (window.gameUI?.updateUI) { if (window.gameUI?.updateUI) {
window.gameUI.updateUI(); window.gameUI.updateUI(); // Обновляем весь UI игры (панели игроков, эффекты и т.д.)
} }
if (clientState.isInGame && clientState.currentGameState && !clientState.currentGameState.isGameOver && window.GAME_CONFIG) { if (clientState.isInGame && clientState.currentGameState && !clientState.currentGameState.isGameOver && window.GAME_CONFIG) {
const config = window.GAME_CONFIG; const config = window.GAME_CONFIG;
@ -212,49 +221,53 @@ export function initGameplay(dependencies) {
((clientState.currentGameState.isPlayerTurn && clientState.myPlayerId === config.PLAYER_ID) || ((clientState.currentGameState.isPlayerTurn && clientState.myPlayerId === config.PLAYER_ID) ||
(!clientState.currentGameState.isPlayerTurn && clientState.myPlayerId === config.OPPONENT_ID)); (!clientState.currentGameState.isPlayerTurn && clientState.myPlayerId === config.OPPONENT_ID));
console.log(`[CLIENT ${username}] handleGameDataReceived - Determining controls. isMyActualTurn: ${isMyActualTurn}`); console.log(`[CLIENT ${username} GP] handleGameDataReceived - Determining controls. isMyActualTurn: ${isMyActualTurn}`);
if (isMyActualTurn) { if (isMyActualTurn) {
enableGameControls(); enableGameControls();
} else { } else {
disableGameControls(); disableGameControls();
} }
} else if (clientState.currentGameState && clientState.currentGameState.isGameOver) { } else if (clientState.currentGameState && clientState.currentGameState.isGameOver) {
console.log(`[CLIENT ${username}] handleGameDataReceived - Game is over, disabling controls.`); console.log(`[CLIENT ${username} GP] handleGameDataReceived - Game is over, disabling controls.`);
disableGameControls(); disableGameControls();
} }
}); });
// Управление gameStatusMessage
if (clientState.currentGameState && clientState.currentGameState.isGameOver) { if (clientState.currentGameState && clientState.currentGameState.isGameOver) {
// Обработка gameOver уже есть в своем обработчике // gameOver имеет свой обработчик статуса
} else if (eventName === 'gameStarted' || eventName === 'gameState (reconnect)') { } else if (eventName === 'gameStarted' || eventName === 'gameState (reconnect)') {
console.log(`[CLIENT ${username}] ${eventName} - Clearing game status message because it's a fresh game/state load.`); console.log(`[CLIENT ${username} GP] ${eventName} - Clearing game status message for fresh game/state load.`);
ui.setGameStatusMessage(""); ui.setGameStatusMessage("");
} else { } else {
if (clientState.isInGame) { if (clientState.isInGame) {
// Если это просто gameStateUpdate, и игра активна, убедимся, что нет сообщения об ожидании
const statusMsgElement = document.getElementById('game-status-message'); const statusMsgElement = document.getElementById('game-status-message');
const currentStatusText = statusMsgElement ? statusMsgElement.textContent : ""; const currentStatusText = statusMsgElement ? statusMsgElement.textContent : "";
if (!currentStatusText.toLowerCase().includes("отключился")) { // Не стираем сообщение об отключении оппонента if (!currentStatusText.toLowerCase().includes("отключился")) {
ui.setGameStatusMessage(""); ui.setGameStatusMessage("");
} }
} }
} }
// Если игра пришла завершенной (даже в gameStarted), вызываем showGameOver
if (clientState.currentGameState && clientState.currentGameState.isGameOver) { if (clientState.currentGameState && clientState.currentGameState.isGameOver) {
if (window.gameUI?.showGameOver && !document.getElementById('game-over-screen').classList.contains(window.GAME_CONFIG?.CSS_CLASS_HIDDEN || 'hidden')) { // Проверяем, не показан ли уже экран gameOver
// Экран уже показан const gameOverScreen = document.getElementById('game-over-screen');
} else if (window.gameUI?.showGameOver) { const hiddenClass = window.GAME_CONFIG?.CSS_CLASS_HIDDEN || 'hidden';
let playerWon = false; if (gameOverScreen && gameOverScreen.classList.contains(hiddenClass)) {
if (data.winnerId) { let playerWon = data.winnerId === clientState.myPlayerId;
playerWon = data.winnerId === clientState.myPlayerId; // Дополнительная логика определения победителя, если winnerId не всегда приходит
} else if (clientState.currentGameState.player && clientState.currentGameState.opponent) { if (data.winnerId === null && data.reason && data.reason.startsWith('server_error')) {
// Дополнительная логика определения победителя, если winnerId нет (маловероятно при корректной работе сервера) // Ничья или ошибка сервера, никто не выиграл
if (clientState.currentGameState.player.currentHp > 0 && clientState.currentGameState.opponent.currentHp <=0) { } else if (!data.winnerId && clientState.currentGameState.player && clientState.currentGameState.opponent) {
playerWon = clientState.myPlayerId === clientState.currentGameState.player.id; if (clientState.currentGameState.player.currentHp <= 0 && clientState.currentGameState.opponent.currentHp <= 0) playerWon = false; // Ничья или оба проиграли
} else if (clientState.currentGameState.opponent.currentHp > 0 && clientState.currentGameState.player.currentHp <=0) { else if (clientState.currentGameState.player.currentHp > 0 && clientState.currentGameState.opponent.currentHp <= 0) playerWon = (clientState.myPlayerId === clientState.currentGameState.player.id);
playerWon = clientState.myPlayerId === clientState.currentGameState.opponent.id; else if (clientState.currentGameState.opponent.currentHp > 0 && clientState.currentGameState.player.currentHp <= 0) playerWon = (clientState.myPlayerId === clientState.currentGameState.opponent.id);
} }
console.log(`[CLIENT ${username} GP] Game received as 'Over' in ${eventName}. Calling showGameOver. PlayerWon: ${playerWon}`);
if (window.gameUI?.showGameOver) {
window.gameUI.showGameOver(playerWon, data.reason || "Игра завершена", clientState.opponentCharacterKey || data.loserCharacterKey, data);
} }
window.gameUI.showGameOver(playerWon, data.reason || "Игра завершена", clientState.opponentCharacterKey || data.loserCharacterKey, { finalGameState: clientState.currentGameState, ...data });
} }
if (returnToMenuButton) returnToMenuButton.disabled = false; if (returnToMenuButton) returnToMenuButton.disabled = false;
} }
@ -266,14 +279,14 @@ export function initGameplay(dependencies) {
handleGameDataReceived(data, 'gameStarted'); handleGameDataReceived(data, 'gameStarted');
}); });
socket.on('gameState', (data) => { socket.on('gameState', (data) => { // Для совместимости со старым событием реконнекта
handleGameDataReceived(data, 'gameState (reconnect)'); handleGameDataReceived(data, 'gameState (reconnect)');
}); });
socket.on('gameStateUpdate', (data) => { socket.on('gameStateUpdate', (data) => {
if (!clientState.isLoggedIn || !clientState.isInGame || !clientState.currentGameId || !window.GAME_CONFIG) return; if (!clientState.isLoggedIn || !clientState.isInGame || !clientState.currentGameId || !window.GAME_CONFIG) return;
const username = clientState.loggedInUsername || 'N/A'; const username = clientState.loggedInUsername || 'N/A';
console.log(`[CLIENT ${username}] Event: gameStateUpdate. GS.isPlayerTurn: ${data.gameState?.isPlayerTurn}`); console.log(`[CLIENT ${username} GP] Event: gameStateUpdate. GS.isPlayerTurn: ${data.gameState?.isPlayerTurn}. ActionInProgress before reset: ${clientState.isActionInProgress}`);
clientState.isActionInProgress = false; // <--- СБРОС ФЛАГА clientState.isActionInProgress = false; // <--- СБРОС ФЛАГА
clientState.currentGameState = data.gameState; clientState.currentGameState = data.gameState;
@ -281,14 +294,14 @@ export function initGameplay(dependencies) {
if (window.gameUI?.updateUI) { if (window.gameUI?.updateUI) {
requestAnimationFrame(() => { requestAnimationFrame(() => {
window.gameUI.updateUI(); window.gameUI.updateUI(); // Обновляем панели, эффекты и т.д.
if (clientState.isInGame && clientState.currentGameState && !clientState.currentGameState.isGameOver && window.GAME_CONFIG) { if (clientState.isInGame && clientState.currentGameState && !clientState.currentGameState.isGameOver && window.GAME_CONFIG) {
const config = window.GAME_CONFIG; const config = window.GAME_CONFIG;
const isMyActualTurn = clientState.myPlayerId && const isMyActualTurn = clientState.myPlayerId &&
((clientState.currentGameState.isPlayerTurn && clientState.myPlayerId === config.PLAYER_ID) || ((clientState.currentGameState.isPlayerTurn && clientState.myPlayerId === config.PLAYER_ID) ||
(!clientState.currentGameState.isPlayerTurn && clientState.myPlayerId === config.OPPONENT_ID)); (!clientState.currentGameState.isPlayerTurn && clientState.myPlayerId === config.OPPONENT_ID));
console.log(`[CLIENT ${username}] gameStateUpdate - Determining controls. isMyActualTurn: ${isMyActualTurn}`); console.log(`[CLIENT ${username} GP] gameStateUpdate - Determining controls. isMyActualTurn: ${isMyActualTurn}`);
if (isMyActualTurn) { if (isMyActualTurn) {
enableGameControls(); enableGameControls();
} else { } else {
@ -297,13 +310,13 @@ export function initGameplay(dependencies) {
const statusMsgElement = document.getElementById('game-status-message'); const statusMsgElement = document.getElementById('game-status-message');
const currentStatusText = statusMsgElement ? statusMsgElement.textContent : ""; const currentStatusText = statusMsgElement ? statusMsgElement.textContent : "";
if (!currentStatusText.toLowerCase().includes("отключился")) { if (!currentStatusText.toLowerCase().includes("отключился")) { // Не стираем сообщение о дисконнекте оппонента
ui.setGameStatusMessage(""); ui.setGameStatusMessage(""); // Очищаем общий статус, если игра активна
} }
} else if (clientState.currentGameState && clientState.currentGameState.isGameOver) { } else if (clientState.currentGameState && clientState.currentGameState.isGameOver) {
console.log(`[CLIENT ${username}] gameStateUpdate - Game is over, disabling controls.`); console.log(`[CLIENT ${username} GP] gameStateUpdate resulted in GameOver. Disabling controls.`);
disableGameControls(); disableGameControls(); // Отключаем управление, если игра закончилась этим обновлением
} }
}); });
} }
@ -315,8 +328,6 @@ export function initGameplay(dependencies) {
socket.on('logUpdate', (data) => { socket.on('logUpdate', (data) => {
if (!clientState.isLoggedIn || !clientState.isInGame || !clientState.currentGameId || !window.GAME_CONFIG) return; 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) { if (window.gameUI?.addToLog && data.log) {
data.log.forEach(log => window.gameUI.addToLog(log.message, log.type)); data.log.forEach(log => window.gameUI.addToLog(log.message, log.type));
} }
@ -329,7 +340,7 @@ export function initGameplay(dependencies) {
return; return;
} }
const username = clientState.loggedInUsername || 'N/A'; const username = clientState.loggedInUsername || 'N/A';
console.log(`[CLIENT ${username}] Event: gameOver. WinnerID: ${data.winnerId}, Reason: ${data.reason}`); console.log(`[CLIENT ${username} GP] Event: gameOver. WinnerID: ${data.winnerId}, Reason: ${data.reason}`);
clientState.isActionInProgress = false; // <--- СБРОС ФЛАГА clientState.isActionInProgress = false; // <--- СБРОС ФЛАГА
const playerWon = data.winnerId === clientState.myPlayerId; const playerWon = data.winnerId === clientState.myPlayerId;
@ -357,7 +368,7 @@ export function initGameplay(dependencies) {
socket.on('opponentDisconnected', (data) => { socket.on('opponentDisconnected', (data) => {
if (!clientState.isLoggedIn || !clientState.isInGame || !clientState.currentGameId || !window.GAME_CONFIG) return; if (!clientState.isLoggedIn || !clientState.isInGame || !clientState.currentGameId || !window.GAME_CONFIG) return;
const username = clientState.loggedInUsername || 'N/A'; const username = clientState.loggedInUsername || 'N/A';
console.log(`[CLIENT ${username}] Event: opponentDisconnected. PlayerID: ${data.disconnectedPlayerId}`); console.log(`[CLIENT ${username} GP] Event: opponentDisconnected. PlayerID: ${data.disconnectedPlayerId}`);
const name = data.disconnectedCharacterName || clientState.opponentBaseStatsServer?.name || 'Противник'; const name = data.disconnectedCharacterName || clientState.opponentBaseStatsServer?.name || 'Противник';
if (clientState.currentGameState && !clientState.currentGameState.isGameOver) { if (clientState.currentGameState && !clientState.currentGameState.isGameOver) {
@ -366,28 +377,29 @@ export function initGameplay(dependencies) {
} }
}); });
socket.on('playerReconnected', (data) => { // Обработчик события, что оппонент переподключился socket.on('playerReconnected', (data) => {
if (!clientState.isLoggedIn || !clientState.isInGame || !clientState.currentGameId || !window.GAME_CONFIG) return; if (!clientState.isLoggedIn || !clientState.isInGame || !clientState.currentGameId || !window.GAME_CONFIG) return;
const username = clientState.loggedInUsername || 'N/A'; const username = clientState.loggedInUsername || 'N/A';
console.log(`[CLIENT ${username}] Event: playerReconnected. PlayerID: ${data.reconnectedPlayerId}, Name: ${data.reconnectedPlayerName}`); console.log(`[CLIENT ${username} GP] Event: playerReconnected. PlayerID: ${data.reconnectedPlayerId}, Name: ${data.reconnectedPlayerName}`);
// const name = data.reconnectedPlayerName || clientState.opponentBaseStatsServer?.name || 'Противник';
if (clientState.currentGameState && !clientState.currentGameState.isGameOver) { if (clientState.currentGameState && !clientState.currentGameState.isGameOver) {
// Сообщение о переподключении оппонента обычно приходит через 'logUpdate'
// Но если нужно немедленно убрать статус "Ожидание...", можно сделать здесь:
const statusMsgElement = document.getElementById('game-status-message'); const statusMsgElement = document.getElementById('game-status-message');
const currentStatusText = statusMsgElement ? statusMsgElement.textContent : ""; const currentStatusText = statusMsgElement ? statusMsgElement.textContent : "";
if (currentStatusText.toLowerCase().includes("отключился")) { if (currentStatusText.toLowerCase().includes("отключился")) {
ui.setGameStatusMessage(""); // Очищаем сообщение об ожидании ui.setGameStatusMessage(""); // Очищаем сообщение об ожидании
} }
// Логика enable/disableGameControls будет вызвана следующим gameStateUpdate или turnTimerUpdate // После этого должен прийти gameStateUpdate, который правильно установит контролы
// или turnTimerUpdate. Можно не вызывать enable/disable здесь.
} }
}); });
socket.on('turnTimerUpdate', (data) => { socket.on('turnTimerUpdate', (data) => {
const username = clientState.loggedInUsername || 'N/A';
// console.log(`[CLIENT ${username} GP] Event: turnTimerUpdate RECEIVED. Data:`, JSON.stringify(data), `Current isInGame: ${clientState.isInGame}, GS exists: ${!!clientState.currentGameState}`);
if (!clientState.isInGame || !clientState.currentGameState || !window.GAME_CONFIG) { if (!clientState.isInGame || !clientState.currentGameState || !window.GAME_CONFIG) {
if (window.gameUI?.updateTurnTimerDisplay && clientState.currentGameState && !clientState.currentGameState.isGameOver) { if (window.gameUI?.updateTurnTimerDisplay && clientState.currentGameState && clientState.currentGameState.isGameOver === false) { // Если не в игре, но gameState еще не gameOver
window.gameUI.updateTurnTimerDisplay(null, false, clientState.currentGameState.gameMode); window.gameUI.updateTurnTimerDisplay(null, false, clientState.currentGameState.gameMode);
} }
return; return;
@ -397,48 +409,41 @@ export function initGameplay(dependencies) {
if (window.gameUI?.updateTurnTimerDisplay) { if (window.gameUI?.updateTurnTimerDisplay) {
window.gameUI.updateTurnTimerDisplay(null, false, clientState.currentGameState.gameMode); window.gameUI.updateTurnTimerDisplay(null, false, clientState.currentGameState.gameMode);
} }
// disableGameControls() уже должен быть вызван в gameOver return; // Не обновляем контролы, если игра окончена
return;
} }
// const username = clientState.loggedInUsername || 'N/A';
// console.log(`[CLIENT ${username}] Event: turnTimerUpdate. Remaining: ${data.remainingTime}, isPlayerTurnForTimer: ${data.isPlayerTurn}, isPaused: ${data.isPaused}`);
if (window.gameUI && typeof window.gameUI.updateTurnTimerDisplay === 'function') { if (window.gameUI && typeof window.gameUI.updateTurnTimerDisplay === 'function') {
const config = window.GAME_CONFIG; const config = window.GAME_CONFIG;
const isMyTurnForTimer = clientState.myPlayerId && clientState.currentGameState && // data.isPlayerTurn здесь - это isPlayerTurnForTimer от сервера (чей ход с точки зрения таймера)
((data.isPlayerTurn && clientState.myPlayerId === config.PLAYER_ID) || // Серверное data.isPlayerTurn здесь авторитетно для таймера const isMyTurnAccordingToTimer = clientState.myPlayerId &&
((data.isPlayerTurn && clientState.myPlayerId === config.PLAYER_ID) ||
(!data.isPlayerTurn && clientState.myPlayerId === config.OPPONENT_ID)); (!data.isPlayerTurn && clientState.myPlayerId === config.OPPONENT_ID));
window.gameUI.updateTurnTimerDisplay(data.remainingTime, isMyTurnForTimer, clientState.currentGameState.gameMode); window.gameUI.updateTurnTimerDisplay(data.remainingTime, isMyTurnAccordingToTimer, clientState.currentGameState.gameMode);
// Если игра НЕ на паузе (серверной или клиентской из-за дисконнекта оппонента) // Управление кнопками на основе информации из ТАЙМЕРА.
if (!data.isPaused) { // Это может быть полезно, если gameStateUpdate запаздывает.
// Управление кнопками должно быть на основе isPlayerTurn из gameState, а не из turnTimerUpdate // Но нужно быть осторожным, чтобы не конфликтовать с gameState.isPlayerTurn.
// gameStateUpdate обработает это. Здесь только если нужно немедленно реагировать на isPlayerTurn из таймера, // Если игра НЕ на паузе (по данным таймера), то можно обновить кнопки.
// но это может привести к конфликтам с gameState.isPlayerTurn. if (!data.isPaused) { // isPaused от сервера (isTimerLogicPaused || game.isGameEffectivelyPaused())
// Лучше положиться на gameStateUpdate. if (isMyTurnAccordingToTimer && !clientState.currentGameState.isGameOver) {
// Однако, если ТАЙМЕР НЕ ПРИОСТАНОВЛЕН и это МОЙ ХОД по таймеру, то кнопки должны быть активны.
// Это может быть полезно, если gameStateUpdate запаздывает.
if (isMyTurnForTimer && !clientState.currentGameState.isGameOver) { // Дополнительная проверка на GameOver
enableGameControls(); enableGameControls();
} else if (!isMyTurnForTimer && !clientState.currentGameState.isGameOver){ // Иначе, если не мой ход } else if (!isMyTurnAccordingToTimer && !clientState.currentGameState.isGameOver){
disableGameControls(); disableGameControls();
} }
const statusMsgElement = document.getElementById('game-status-message'); const statusMsgElement = document.getElementById('game-status-message');
const currentStatusText = statusMsgElement ? statusMsgElement.textContent : ""; const currentStatusText = statusMsgElement ? statusMsgElement.textContent : "";
if (!currentStatusText.toLowerCase().includes("отключился") && !clientState.currentGameState.isGameOver) { if (!currentStatusText.toLowerCase().includes("отключился") && !clientState.currentGameState.isGameOver) {
// console.log(`[CLIENT ${username}] turnTimerUpdate - Clearing game status message as timer is active and not paused.`);
ui.setGameStatusMessage(""); ui.setGameStatusMessage("");
} }
} else { // Если игра на паузе (по данным таймера) } else { // Если игра на паузе (по данным таймера)
// console.log(`[CLIENT ${username}] turnTimerUpdate - Game is paused, disabling controls.`); disableGameControls();
disableGameControls(); // Отключаем управление, если таймер говорит, что игра на паузе
} }
} }
}); });
// Начальная деактивация (на всякий случай, хотя showAuthScreen/showGameSelectionScreen должны это делать) // Начальная деактивация (на случай, если UI не скрыт изначально)
disableGameControls(); disableGameControls();
} }