339 lines
20 KiB
JavaScript
339 lines
20 KiB
JavaScript
// /public/js/gameplay.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');
|
||
|
||
// --- Вспомогательные функции ---
|
||
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;
|
||
}
|
||
|
||
function disableGameControls() {
|
||
enableGameControls(false, false);
|
||
}
|
||
|
||
// Эта функция была в client.js, переносим сюда
|
||
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;
|
||
// Используем данные из clientState, которые были обновлены из событий сервера
|
||
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();
|
||
// После инициализации кнопок, их состояние (disabled/enabled) будет управляться window.gameUI.updateUI()
|
||
}
|
||
|
||
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:", {
|
||
isLoggedIn: clientState.isLoggedIn,
|
||
isInGame: clientState.isInGame,
|
||
gameId: clientState.currentGameId,
|
||
abilityId,
|
||
gameState: clientState.currentGameState
|
||
});
|
||
}
|
||
}
|
||
|
||
|
||
// --- Обработчики событий 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) { // Кнопка из модалки GameOver
|
||
returnToMenuButton.addEventListener('click', () => {
|
||
if (!clientState.isLoggedIn) {
|
||
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)
|
||
});
|
||
}
|
||
|
||
// --- Обработчики событий 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) => {
|
||
if (!clientState.isLoggedIn) return;
|
||
console.log('[Gameplay] Received full gameState (e.g. on reconnect):', data);
|
||
|
||
// Обновляем состояние клиента (похоже на gameStarted)
|
||
clientState.currentGameId = data.gameId;
|
||
clientState.myPlayerId = data.yourPlayerId;
|
||
clientState.currentGameState = data.gameState; // Используем gameState вместо 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) {
|
||
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();
|
||
|
||
if (!clientState.isInGame || document.querySelector('.game-wrapper').style.display === 'none') {
|
||
ui.showGameScreen(); // Показываем игровой экран, если еще не там
|
||
}
|
||
initializeAbilityButtons(); // Переинициализируем кнопки способностей
|
||
|
||
// Лог при 'gameState' может быть уже накопленным, очищаем и добавляем новый
|
||
if (window.gameUI?.uiElements?.log?.list && data.log) {
|
||
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));
|
||
}
|
||
|
||
requestAnimationFrame(() => {
|
||
if (window.gameUI && typeof window.gameUI.updateUI === 'function') {
|
||
window.gameUI.updateUI();
|
||
}
|
||
});
|
||
// ui.hideGameOverModal(); // Делается в showGameScreen
|
||
// Таймер хода будет обновлен событием 'turnTimerUpdate'
|
||
});
|
||
|
||
socket.on('gameStateUpdate', (data) => {
|
||
if (!clientState.isLoggedIn || !clientState.isInGame || !clientState.currentGameId || !window.GAME_CONFIG) return;
|
||
|
||
clientState.currentGameState = data.gameState;
|
||
ui.updateGlobalWindowVariablesForUI(); // Обновляем window.gameState для ui.js
|
||
|
||
if (window.gameUI?.updateUI) window.gameUI.updateUI();
|
||
|
||
// Добавляем только новые логи, если они есть в этом частичном обновлении
|
||
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;
|
||
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 playerWon = data.winnerId === clientState.myPlayerId;
|
||
clientState.currentGameState = data.finalGameState; // Обновляем состояние последним
|
||
// clientState.isInGame = false; // НЕ СБРАСЫВАЕМ ЗДЕСЬ, чтобы UI показывал экран GameOver. Сбросится при выходе в меню.
|
||
|
||
ui.updateGlobalWindowVariablesForUI(); // Обновляем window.gameState для ui.js
|
||
|
||
if (window.gameUI?.updateUI) 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 покажет модалку
|
||
}
|
||
|
||
if (returnToMenuButton) returnToMenuButton.disabled = false; // Активируем кнопку "Вернуться в меню"
|
||
|
||
ui.setGameStatusMessage("Игра окончена. " + (playerWon ? "Вы победили!" : "Вы проиграли."));
|
||
|
||
// Обновляем UI таймера, чтобы показать "Конец" или скрыть
|
||
if (window.gameUI?.updateTurnTimerDisplay) {
|
||
window.gameUI.updateTurnTimerDisplay(null, false, clientState.currentGameState?.gameMode);
|
||
}
|
||
// Контролы должны быть заблокированы, т.к. игра окончена (ui.js->updateUI это сделает)
|
||
});
|
||
|
||
socket.on('opponentDisconnected', (data) => {
|
||
if (!clientState.isLoggedIn || !clientState.isInGame || !clientState.currentGameId || !window.GAME_CONFIG) return;
|
||
|
||
const name = data.disconnectedCharacterName || clientState.opponentBaseStatsServer?.name || 'Противник';
|
||
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 || clientState.currentGameState.isGameOver) {
|
||
// Если игра окончена или не в игре, обновляем таймер соответственно
|
||
if (window.gameUI?.updateTurnTimerDisplay && !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);
|
||
}
|
||
// Логика включения/выключения контролов на основе isMyActualTurn
|
||
// обычно выполняется в window.gameUI.updateUI(), которая вызывается после gameStateUpdate.
|
||
// Если turnTimerUpdate приходит отдельно и должен влиять на контролы, то нужно добавить:
|
||
// if (isMyActualTurn) enableGameControls(); else disableGameControls();
|
||
// Но это может конфликтовать с логикой в updateUI(). Обычно updateUI() - главный источник правды.
|
||
});
|
||
|
||
// Начальная деактивация игровых контролов при загрузке модуля
|
||
disableGameControls();
|
||
} |