400 lines
18 KiB
JavaScript
400 lines
18 KiB
JavaScript
// /public/js/client.js
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
const socket = io({
|
||
});
|
||
|
||
let currentGameState = null;
|
||
let myPlayerId = null;
|
||
let currentGameId = null;
|
||
let playerBaseStatsServer = null;
|
||
let opponentBaseStatsServer = null;
|
||
let playerAbilitiesServer = null;
|
||
let opponentAbilitiesServer = null;
|
||
|
||
const attackButton = document.getElementById('button-attack');
|
||
const abilitiesGrid = document.getElementById('abilities-grid');
|
||
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 gameWrapper = document.querySelector('.game-wrapper');
|
||
const restartGameButton = document.getElementById('restart-game-button');
|
||
const gameOverScreen = document.getElementById('game-over-screen');
|
||
|
||
function hideGameOverModal() {
|
||
const hiddenClass = (window.GAME_CONFIG && window.GAME_CONFIG.CSS_CLASS_HIDDEN) ? window.GAME_CONFIG.CSS_CLASS_HIDDEN : 'hidden';
|
||
if (gameOverScreen && !gameOverScreen.classList.contains(hiddenClass)) {
|
||
gameOverScreen.classList.add(hiddenClass);
|
||
if (gameUI && gameUI.uiElements && gameUI.uiElements.gameOver && gameUI.uiElements.gameOver.modalContent) {
|
||
gameUI.uiElements.gameOver.modalContent.style.transform = 'scale(0.8) translateY(30px)';
|
||
gameUI.uiElements.gameOver.modalContent.style.opacity = '0';
|
||
}
|
||
}
|
||
}
|
||
|
||
function showGameSetupScreen() {
|
||
hideGameOverModal();
|
||
if (gameWrapper) gameWrapper.style.display = 'none';
|
||
if (gameSetupDiv) gameSetupDiv.style.display = 'block';
|
||
setGameStatusMessage("Выберите режим игры или присоединитесь к существующей.");
|
||
updateAvailableGamesList([]);
|
||
if (gameIdInput) gameIdInput.value = '';
|
||
}
|
||
|
||
function showGameScreen() {
|
||
hideGameOverModal();
|
||
if (gameSetupDiv) gameSetupDiv.style.display = 'none';
|
||
if (gameWrapper) gameWrapper.style.display = 'flex';
|
||
setGameStatusMessage("");
|
||
}
|
||
|
||
function setGameStatusMessage(message, isError = false) {
|
||
if (gameStatusMessage) {
|
||
gameStatusMessage.textContent = message;
|
||
gameStatusMessage.style.display = message ? 'block' : 'none';
|
||
gameStatusMessage.style.color = isError ? 'var(--damage-color, red)' : 'var(--turn-color, yellow)';
|
||
}
|
||
}
|
||
|
||
function initializeAbilityButtons() {
|
||
if (!gameUI || !gameUI.uiElements || !gameUI.uiElements.controls.abilitiesGrid || !window.GAME_CONFIG) {
|
||
console.error("Cannot initialize abilities UI: missing gameUI elements or GAME_CONFIG.");
|
||
return;
|
||
}
|
||
const grid = gameUI.uiElements.controls.abilitiesGrid;
|
||
grid.innerHTML = '';
|
||
const config = window.GAME_CONFIG;
|
||
|
||
const abilitiesToDisplay = (myPlayerId === config.PLAYER_ID) ? playerAbilitiesServer : opponentAbilitiesServer;
|
||
const baseStatsForResource = (myPlayerId === config.PLAYER_ID) ? playerBaseStatsServer : opponentBaseStatsServer;
|
||
const resourceName = baseStatsForResource ? baseStatsForResource.resourceName : "Ресурс";
|
||
|
||
if (!abilitiesToDisplay || abilitiesToDisplay.length === 0) {
|
||
grid.innerHTML = '<p class="placeholder-text">Нет доступных способностей.</p>';
|
||
return;
|
||
}
|
||
|
||
abilitiesToDisplay.forEach(ability => {
|
||
const button = document.createElement('button');
|
||
button.id = `ability-btn-${ability.id}`;
|
||
button.classList.add(config.CSS_CLASS_ABILITY_BUTTON || 'ability-button');
|
||
button.dataset.abilityId = ability.id;
|
||
|
||
let descriptionText = ability.description;
|
||
if (typeof ability.descriptionFunction === 'function') {
|
||
const targetStatsForDesc = (myPlayerId === config.PLAYER_ID) ? opponentBaseStatsServer : playerBaseStatsServer;
|
||
descriptionText = ability.descriptionFunction(config, targetStatsForDesc);
|
||
}
|
||
|
||
let title = `${ability.name} (${ability.cost} ${resourceName}) - ${descriptionText}`;
|
||
let cooldown = ability.cooldown;
|
||
if (myPlayerId === config.OPPONENT_ID) {
|
||
if (ability.internalCooldownFromConfig && config[ability.internalCooldownFromConfig]) {
|
||
cooldown = config[ability.internalCooldownFromConfig];
|
||
} else if (ability.internalCooldownValue) {
|
||
cooldown = ability.internalCooldownValue;
|
||
}
|
||
}
|
||
if (cooldown) title += ` (КД: ${cooldown} х.)`;
|
||
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);
|
||
grid.appendChild(button);
|
||
});
|
||
}
|
||
|
||
function updateAvailableGamesList(games) {
|
||
if (!availableGamesDiv) return;
|
||
availableGamesDiv.innerHTML = '<h3>Доступные PvP игры:</h3>';
|
||
if (games && games.length > 0) {
|
||
const ul = document.createElement('ul');
|
||
games.forEach(game => {
|
||
if (game) {
|
||
const li = document.createElement('li');
|
||
li.textContent = `ID: ${game.id.substring(0, 8)}... - ${game.status || 'Ожидает игрока'}`;
|
||
const joinBtn = document.createElement('button');
|
||
joinBtn.textContent = 'Присоединиться';
|
||
joinBtn.dataset.gameId = game.id;
|
||
joinBtn.addEventListener('click', (e) => {
|
||
const idToJoin = e.target.dataset.gameId;
|
||
socket.emit('joinGame', { gameId: idToJoin });
|
||
});
|
||
li.appendChild(joinBtn);
|
||
ul.appendChild(li);
|
||
}
|
||
});
|
||
availableGamesDiv.appendChild(ul);
|
||
} else {
|
||
availableGamesDiv.innerHTML += '<p>Нет доступных игр. Создайте свою!</p>';
|
||
}
|
||
}
|
||
|
||
if (attackButton) {
|
||
attackButton.addEventListener('click', () => {
|
||
if (currentGameId && currentGameState && !currentGameState.isGameOver) {
|
||
socket.emit('playerAction', { actionType: 'attack' });
|
||
}
|
||
});
|
||
}
|
||
|
||
function handleAbilityButtonClick(event) {
|
||
const button = event.currentTarget;
|
||
const abilityId = button.dataset.abilityId;
|
||
if (currentGameId && abilityId && currentGameState && !currentGameState.isGameOver) {
|
||
socket.emit('playerAction', {
|
||
actionType: 'ability',
|
||
abilityId: abilityId
|
||
});
|
||
}
|
||
}
|
||
|
||
if (restartGameButton) {
|
||
restartGameButton.addEventListener('click', () => {
|
||
console.log("[Client] Restart button clicked. currentGameId:", currentGameId, "currentGameState:", currentGameState);
|
||
if (currentGameId && currentGameState && currentGameState.isGameOver) {
|
||
console.log("[Client] Sending 'requestRestart' for game ID:", currentGameId);
|
||
socket.emit('requestRestart', { gameId: currentGameId });
|
||
setGameStatusMessage("Запрос на рестарт отправлен...");
|
||
restartGameButton.disabled = true;
|
||
} else {
|
||
console.warn("[Client] Cannot request restart. Conditions not met:",
|
||
{ currentGameId, currentGameStateExists: !!currentGameState, isGameOver: currentGameState?.isGameOver }
|
||
);
|
||
if (!currentGameId) {
|
||
alert("Ошибка: ID текущей игры не определен. Невозможно запросить рестарт.");
|
||
showGameSetupScreen();
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
if (createAIGameButton) {
|
||
createAIGameButton.addEventListener('click', () => {
|
||
socket.emit('createGame', { mode: 'ai' });
|
||
setGameStatusMessage("Создание игры против AI...");
|
||
});
|
||
}
|
||
if (createPvPGameButton) {
|
||
createPvPGameButton.addEventListener('click', () => {
|
||
socket.emit('createGame', { mode: 'pvp' });
|
||
setGameStatusMessage("Создание PvP игры...");
|
||
});
|
||
}
|
||
if (joinPvPGameButton && gameIdInput) {
|
||
joinPvPGameButton.addEventListener('click', () => {
|
||
const gameIdToJoin = gameIdInput.value.trim();
|
||
if (gameIdToJoin) {
|
||
console.log("[Client] Attempting to join game with ID:", gameIdToJoin);
|
||
socket.emit('joinGame', { gameId: gameIdToJoin });
|
||
setGameStatusMessage(`Присоединение к игре ${gameIdToJoin}...`);
|
||
} else {
|
||
setGameStatusMessage("Пожалуйста, введите ID игры для присоединения.", true);
|
||
}
|
||
});
|
||
}
|
||
if (findRandomPvPGameButton) {
|
||
findRandomPvPGameButton.addEventListener('click', () => {
|
||
socket.emit('findRandomPvPGame');
|
||
setGameStatusMessage("Поиск случайной PvP игры...");
|
||
});
|
||
}
|
||
|
||
socket.on('connect', () => {
|
||
console.log('Успешно подключено к серверу. ID сокета:', socket.id);
|
||
showGameSetupScreen();
|
||
socket.emit('requestAvailablePvPGames');
|
||
});
|
||
|
||
socket.on('disconnect', (reason) => {
|
||
console.log('Отключено от сервера:', reason);
|
||
setGameStatusMessage(`Отключено от сервера: ${reason}. Попробуйте обновить страницу.`, true);
|
||
|
||
currentGameState = null;
|
||
currentGameId = null;
|
||
myPlayerId = null;
|
||
|
||
hideGameOverModal();
|
||
showGameSetupScreen();
|
||
});
|
||
|
||
socket.on('gameCreated', (data) => {
|
||
currentGameId = data.gameId;
|
||
myPlayerId = data.yourPlayerId;
|
||
console.log(`Игра создана/присоединена: ${currentGameId}, Режим: ${data.mode}, Вы играете за: ${myPlayerId}`);
|
||
|
||
if (data.clientConfig) {
|
||
window.GAME_CONFIG = { ...(window.GAME_CONFIG || {}), ...data.clientConfig };
|
||
}
|
||
|
||
const playerConfigId = (window.GAME_CONFIG && window.GAME_CONFIG.PLAYER_ID) ? window.GAME_CONFIG.PLAYER_ID : 'player';
|
||
if (data.mode === 'pvp') {
|
||
if (gameIdInput) gameIdInput.value = currentGameId;
|
||
if (myPlayerId === playerConfigId) {
|
||
setGameStatusMessage(`PvP игра создана. ID для друга: ${currentGameId}. Ожидание второго игрока...`);
|
||
} else {
|
||
setGameStatusMessage(`Присоединились к PvP игре. Ожидание начала...`);
|
||
}
|
||
} else {
|
||
setGameStatusMessage(`Игра против AI создана. Ожидание начала...`);
|
||
}
|
||
});
|
||
|
||
socket.on('gameStarted', (data) => {
|
||
console.log('Событие "gameStarted" получено:', data);
|
||
currentGameId = data.gameId;
|
||
myPlayerId = data.yourPlayerId;
|
||
currentGameState = data.initialGameState;
|
||
playerBaseStatsServer = data.playerBaseStats;
|
||
opponentBaseStatsServer = data.opponentBaseStats;
|
||
playerAbilitiesServer = data.playerAbilities;
|
||
opponentAbilitiesServer = data.opponentAbilities;
|
||
|
||
if (data.clientConfig) {
|
||
window.GAME_CONFIG = { ...data.clientConfig };
|
||
} else if (!window.GAME_CONFIG) {
|
||
console.warn("Server did not send clientConfig in 'gameStarted'. UI might rely on defaults or fail.");
|
||
window.GAME_CONFIG = { PLAYER_ID: 'player', OPPONENT_ID: 'opponent', CSS_CLASS_HIDDEN: 'hidden' };
|
||
}
|
||
|
||
window.gameState = currentGameState;
|
||
window.gameData = {
|
||
playerBaseStats: playerBaseStatsServer,
|
||
opponentBaseStats: opponentBaseStatsServer,
|
||
playerAbilities: playerAbilitiesServer,
|
||
opponentAbilities: opponentAbilitiesServer
|
||
};
|
||
window.myPlayerId = myPlayerId;
|
||
|
||
showGameScreen();
|
||
initializeAbilityButtons();
|
||
|
||
if (gameUI && gameUI.uiElements && gameUI.uiElements.log && gameUI.uiElements.log.list) {
|
||
gameUI.uiElements.log.list.innerHTML = '';
|
||
}
|
||
if (gameUI && typeof gameUI.addToLog === 'function' && data.log) {
|
||
data.log.forEach(logEntry => gameUI.addToLog(logEntry.message, logEntry.type));
|
||
}
|
||
|
||
if (gameUI && typeof gameUI.updateUI === 'function') {
|
||
gameUI.updateUI();
|
||
}
|
||
|
||
hideGameOverModal();
|
||
if (restartGameButton) {
|
||
restartGameButton.disabled = true;
|
||
restartGameButton.dataset.gameIdForRestart = '';
|
||
}
|
||
setGameStatusMessage("");
|
||
});
|
||
|
||
socket.on('gameStateUpdate', (data) => {
|
||
currentGameState = data.gameState;
|
||
window.gameState = currentGameState;
|
||
|
||
if (gameUI && typeof gameUI.updateUI === 'function') {
|
||
gameUI.updateUI();
|
||
}
|
||
if (gameUI && typeof gameUI.addToLog === 'function' && data.log) {
|
||
data.log.forEach(logEntry => gameUI.addToLog(logEntry.message, logEntry.type));
|
||
}
|
||
});
|
||
|
||
socket.on('logUpdate', (data) => {
|
||
if (gameUI && typeof gameUI.addToLog === 'function' && data.log) {
|
||
data.log.forEach(logEntry => gameUI.addToLog(logEntry.message, logEntry.type));
|
||
}
|
||
});
|
||
|
||
socket.on('gameOver', (data) => {
|
||
console.log('Игра окончена:', data);
|
||
currentGameState = data.finalGameState;
|
||
window.gameState = currentGameState;
|
||
|
||
if (gameUI && typeof gameUI.updateUI === 'function') {
|
||
gameUI.updateUI();
|
||
}
|
||
if (gameUI && typeof gameUI.addToLog === 'function' && data.log) {
|
||
data.log.forEach(logEntry => gameUI.addToLog(logEntry.message, logEntry.type));
|
||
}
|
||
if (gameUI && typeof gameUI.showGameOver === 'function') {
|
||
const playerWon = data.winnerId === myPlayerId;
|
||
gameUI.showGameOver(playerWon, data.reason);
|
||
if (restartGameButton) {
|
||
restartGameButton.disabled = false;
|
||
restartGameButton.dataset.gameIdForRestart = currentGameId;
|
||
}
|
||
}
|
||
setGameStatusMessage("Игра окончена. " + (data.winnerId === myPlayerId ? "Вы победили!" : "Вы проиграли."));
|
||
});
|
||
|
||
socket.on('waitingForOpponent', () => {
|
||
setGameStatusMessage("Ожидание присоединения оппонента...");
|
||
});
|
||
|
||
socket.on('opponentDisconnected', (data) => {
|
||
const systemLogType = (window.GAME_CONFIG && window.GAME_CONFIG.LOG_TYPE_SYSTEM) ? window.GAME_CONFIG.LOG_TYPE_SYSTEM : 'system';
|
||
if (gameUI && typeof gameUI.addToLog === 'function') {
|
||
gameUI.addToLog("Противник отключился.", systemLogType);
|
||
}
|
||
if (!currentGameState || !currentGameState.isGameOver) {
|
||
setGameStatusMessage("Противник отключился. Игра прервана. Вы можете начать новую.", true);
|
||
}
|
||
});
|
||
|
||
socket.on('turnNotification', (data) => {
|
||
});
|
||
|
||
socket.on('waitingForRestartVote', (data) => {
|
||
const systemLogType = (window.GAME_CONFIG && window.GAME_CONFIG.LOG_TYPE_SYSTEM) ? window.GAME_CONFIG.LOG_TYPE_SYSTEM : 'system';
|
||
if (gameUI && typeof gameUI.addToLog === 'function') {
|
||
gameUI.addToLog(
|
||
`${data.voterCharacterName || 'Игрок'} (${data.voterRole}) проголосовал(а) за рестарт. Нужно еще ${data.votesNeeded} голосов.`,
|
||
systemLogType
|
||
);
|
||
}
|
||
setGameStatusMessage(`Один из игроков (${data.voterCharacterName}) проголосовал за рестарт. Ожидание вашего решения или решения другого игрока.`);
|
||
if (restartGameButton && currentGameState && currentGameState.isGameOver) {
|
||
restartGameButton.disabled = false;
|
||
}
|
||
});
|
||
|
||
socket.on('gameError', (data) => {
|
||
console.error('Ошибка от сервера:', data.message);
|
||
const systemLogType = (window.GAME_CONFIG && window.GAME_CONFIG.LOG_TYPE_SYSTEM) ? window.GAME_CONFIG.LOG_TYPE_SYSTEM : 'system';
|
||
if (gameUI && typeof gameUI.addToLog === 'function') {
|
||
gameUI.addToLog(`Ошибка: ${data.message}`, systemLogType);
|
||
}
|
||
setGameStatusMessage(`Ошибка: ${data.message}`, true);
|
||
});
|
||
|
||
socket.on('availablePvPGamesList', (games) => {
|
||
updateAvailableGamesList(games);
|
||
});
|
||
|
||
socket.on('noPendingGamesFound', (data) => {
|
||
setGameStatusMessage(data.message || "Свободных игр не найдено. Создана новая для вас, ожидайте оппонента.");
|
||
updateAvailableGamesList([]);
|
||
});
|
||
|
||
showGameSetupScreen();
|
||
}); |