bc/public/js/gameplay.js

375 lines
21 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

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

// /public/js/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 = '<p class="placeholder-text">Ошибка загрузки способностей.</p>';
return;
}
abilitiesGrid.innerHTML = '';
const config = window.GAME_CONFIG;
const abilitiesToDisplay = clientState.playerAbilitiesServer;
const baseStatsForResource = clientState.playerBaseStatsServer;
if (!abilitiesToDisplay || abilitiesToDisplay.length === 0 || !baseStatsForResource) {
abilitiesGrid.innerHTML = '<p class="placeholder-text">Нет доступных способностей.</p>';
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) => { // Это событие было добавлено для поддержки reconnect из старого GameInstance
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) {
// Если нет ID игры, но залогинен, возможно, стоит запросить состояние
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(); // Обновляем глобальные переменные для ui.js
if (window.gameUI?.updateUI) requestAnimationFrame(() => window.gameUI.updateUI()); // Обновляем UI один раз
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) => {
// Проверяем, в игре ли мы и есть ли gameState, прежде чем обновлять таймер
if (!clientState.isInGame || !clientState.currentGameState || !window.GAME_CONFIG) {
// Если не в игре, но gameState есть (например, игра завершена, но экран еще не обновился),
// то таймер нужно сбросить/скрыть.
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) {
// Проверяем, не показывается ли уже сообщение о дисконнекте оппонента
const statusMsgElement = document.getElementById('game-status-message');
const currentStatusText = statusMsgElement ? statusMsgElement.textContent : "";
if (!currentStatusText.toLowerCase().includes("отключился")) {
console.log(`[CLIENT ${username}] turnTimerUpdate - Clearing game status message as timer is active.`);
ui.setGameStatusMessage("");
}
}
}
});
// Начальная деактивация
disableGameControls();
}