// /public/js/gameplay.js (Откаченная версия, совместимая с последним GameInstance.js) export function initGameplay(dependencies) { const { socket, clientState, ui } = dependencies; const { returnToMenuButton } = ui.elements; const attackButton = document.getElementById('button-attack'); const abilitiesGrid = document.getElementById('abilities-grid'); // --- Вспомогательные функции --- function enableGameControls(enableAttack = true, enableAbilities = true) { if (attackButton) attackButton.disabled = !enableAttack; if (abilitiesGrid) { 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?.updateUI) { requestAnimationFrame(() => window.gameUI.updateUI()); } } function disableGameControls() { enableGameControls(false, false); } function initializeAbilityButtons() { if (!abilitiesGrid || !window.gameUI || !window.GAME_CONFIG) { if (abilitiesGrid) abilitiesGrid.innerHTML = '

Ошибка загрузки способностей.

'; return; } abilitiesGrid.innerHTML = ''; const config = window.GAME_CONFIG; const abilitiesToDisplay = clientState.playerAbilitiesServer; const baseStatsForResource = clientState.playerBaseStatsServer; if (!abilitiesToDisplay || abilitiesToDisplay.length === 0 || !baseStatsForResource) { abilitiesGrid.innerHTML = '

Нет доступных способностей.

'; return; } const resourceName = baseStatsForResource.resourceName || "Ресурс"; const abilityButtonClass = config.CSS_CLASS_ABILITY_BUTTON || 'ability-button'; abilitiesToDisplay.forEach(ability => { const button = document.createElement('button'); button.id = `ability-btn-${ability.id}`; button.classList.add(abilityButtonClass); button.dataset.abilityId = ability.id; 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); button.addEventListener('click', handleAbilityButtonClick); abilitiesGrid.appendChild(button); }); const placeholder = abilitiesGrid.querySelector('.placeholder-text'); if (placeholder) placeholder.remove(); } function handleAbilityButtonClick(event) { const abilityId = event.currentTarget.dataset.abilityId; if (clientState.isLoggedIn && clientState.isInGame && clientState.currentGameId && abilityId && clientState.currentGameState && !clientState.currentGameState.isGameOver) { socket.emit('playerAction', { actionType: 'ability', abilityId: abilityId }); disableGameControls(); } else { console.warn("Cannot perform ability action, invalid state"); } } // --- Обработчики событий DOM --- if (attackButton) { attackButton.addEventListener('click', () => { if (clientState.isLoggedIn && clientState.isInGame && clientState.currentGameId && clientState.currentGameState && !clientState.currentGameState.isGameOver) { socket.emit('playerAction', { actionType: 'attack' }); disableGameControls(); } else { console.warn("Cannot perform attack action, invalid state."); } }); } if (returnToMenuButton) { returnToMenuButton.addEventListener('click', () => { if (!clientState.isLoggedIn) { ui.showAuthScreen(); return; } returnToMenuButton.disabled = true; clientState.isInGame = false; disableGameControls(); ui.showGameSelectionScreen(clientState.loggedInUsername); }); } // --- ОБЩИЙ ОБРАБОТЧИК ДЛЯ ЗАПУСКА/ВОССТАНОВЛЕНИЯ ИГРЫ --- function handleGameDataReceived(data, eventName = "unknown") { if (!clientState.isLoggedIn) return; 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))); clientState.currentGameId = data.gameId; clientState.myPlayerId = data.yourPlayerId; 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; 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 }; } else if (!window.GAME_CONFIG) { window.GAME_CONFIG = { PLAYER_ID: 'player', OPPONENT_ID: 'opponent', CSS_CLASS_HIDDEN: 'hidden' }; } ui.updateGlobalWindowVariablesForUI(); 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(); 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?.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?.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(); } } }); // Управление 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(); 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)); } }); 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)); } }); 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(); 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.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); } if (returnToMenuButton) returnToMenuButton.disabled = false; // `ui.setGameStatusMessage` будет установлено специфичным сообщением о результате игры // ui.setGameStatusMessage("Игра окончена. " + (playerWon ? "Вы победили!" : "Вы проиграли.")); if (window.gameUI?.updateTurnTimerDisplay) { window.gameUI.updateTurnTimerDisplay(null, false, clientState.currentGameState?.gameMode); } 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 || 'Противник'; // Сообщение об отключении оппонента должно приходить через 'logUpdate' от сервера // if (window.gameUI?.addToLog) { // window.gameUI.addToLog(`🔌 Противник (${name}) отключился.`, 'system'); // } if (clientState.currentGameState && !clientState.currentGameState.isGameOver) { ui.setGameStatusMessage(`Противник (${name}) отключился. Ожидание...`, true); disableGameControls(); } }); socket.on('turnTimerUpdate', (data) => { 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 (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(""); } } }); // Начальная деактивация disableGameControls(); }