bc/public/js/client.js
2025-05-13 04:14:01 +00:00

543 lines
25 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/client.js
document.addEventListener('DOMContentLoaded', () => {
const socket = io({
// Опции Socket.IO, если нужны
});
// --- Состояние клиента ---
let currentGameState = null;
let myPlayerId = null; // Технический ID слота ('player' или 'opponent')
let myCharacterKey = null; // Ключ моего персонажа ('elena' или 'almagest')
let opponentCharacterKey = null; // Ключ персонажа оппонента
let currentGameId = null;
let playerBaseStatsServer = null; // Статы персонажа, которым УПРАВЛЯЕТ этот клиент
let opponentBaseStatsServer = null; // Статы персонажа-противника этого клиента
let playerAbilitiesServer = null; // Способности персонажа, которым УПРАВЛЯЕТ этот клиент
let opponentAbilitiesServer = null; // Способности персонажа-противника
let isLoggedIn = false;
let loggedInUsername = '';
// --- DOM Элементы ---
// Аутентификация
const authSection = document.getElementById('auth-section');
const registerForm = document.getElementById('register-form');
const loginForm = document.getElementById('login-form');
const authMessage = document.getElementById('auth-message');
const userInfoDiv = document.getElementById('user-info');
const loggedInUsernameSpan = document.getElementById('logged-in-username');
const logoutButton = document.getElementById('logout-button');
// Настройка игры
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 pvpCharacterRadios = document.querySelectorAll('input[name="pvp-character"]');
// Игровая Арена
const gameWrapper = document.querySelector('.game-wrapper');
const attackButton = document.getElementById('button-attack');
const restartGameButton = document.getElementById('restart-game-button');
const gameOverScreen = document.getElementById('game-over-screen');
console.log('Client.js DOMContentLoaded. Initializing elements...');
// --- Функции управления UI ---
function showAuthScreen() {
console.log('[UI] Showing Auth Screen');
if (authSection) authSection.style.display = 'block';
if (userInfoDiv) userInfoDiv.style.display = 'none';
if (gameSetupDiv) gameSetupDiv.style.display = 'none';
if (gameWrapper) gameWrapper.style.display = 'none';
hideGameOverModal();
setGameStatusMessage("Войдите или зарегистрируйтесь для начала игры.");
setAuthMessage("");
}
function showGameSelectionScreen(username) {
console.log('[UI] Showing Game Selection Screen for:', username);
if (authSection) authSection.style.display = 'none';
if (userInfoDiv) {
userInfoDiv.style.display = 'block';
if(loggedInUsernameSpan) loggedInUsernameSpan.textContent = username;
}
if (gameSetupDiv) gameSetupDiv.style.display = 'block';
if (gameWrapper) gameWrapper.style.display = 'none';
hideGameOverModal();
setGameStatusMessage("Выберите режим игры или присоединитесь к существующей.");
socket.emit('requestPvPGameList');
updateAvailableGamesList([]);
if (gameIdInput) gameIdInput.value = '';
const elenaRadio = document.getElementById('char-elena');
if (elenaRadio) elenaRadio.checked = true;
}
function showGameScreen() {
console.log('[UI] Showing Game Screen');
hideGameOverModal();
if (authSection) authSection.style.display = 'none';
if (userInfoDiv) userInfoDiv.style.display = 'block';
if (gameSetupDiv) gameSetupDiv.style.display = 'none';
if (gameWrapper) gameWrapper.style.display = 'flex';
setGameStatusMessage("");
}
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 (window.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 setAuthMessage(message, isError = false) {
if (authMessage) {
authMessage.textContent = message;
authMessage.className = isError ? 'error' : 'success';
authMessage.style.display = message ? 'block' : 'none';
}
}
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 getSelectedCharacterKey() {
let selectedKey = 'elena';
if (pvpCharacterRadios) {
pvpCharacterRadios.forEach(radio => {
if (radio.checked) {
selectedKey = radio.value;
}
});
}
return selectedKey;
}
// --- Инициализация кнопок и обработчиков ---
if (registerForm) {
registerForm.addEventListener('submit', (e) => {
e.preventDefault();
const usernameInput = document.getElementById('register-username');
const passwordInput = document.getElementById('register-password');
if (usernameInput && passwordInput) {
socket.emit('register', { username: usernameInput.value, password: passwordInput.value });
} else {
setAuthMessage("Ошибка: поля ввода не найдены.", true);
}
});
}
if (loginForm) {
loginForm.addEventListener('submit', (e) => {
e.preventDefault();
const usernameInput = document.getElementById('login-username');
const passwordInput = document.getElementById('login-password');
if (usernameInput && passwordInput) {
socket.emit('login', { username: usernameInput.value, password: passwordInput.value });
} else {
setAuthMessage("Ошибка: поля ввода не найдены.", true);
}
});
}
if (logoutButton) {
logoutButton.addEventListener('click', () => {
socket.emit('logout');
isLoggedIn = false;
loggedInUsername = '';
currentGameId = null; currentGameState = null; myPlayerId = null;
myCharacterKey = null; opponentCharacterKey = null;
showAuthScreen();
setGameStatusMessage("Вы вышли из системы.");
});
}
if (createAIGameButton) {
createAIGameButton.addEventListener('click', () => {
if (!isLoggedIn) {
setGameStatusMessage("Пожалуйста, войдите, чтобы начать игру.", true); return;
}
socket.emit('createGame', { mode: 'ai', characterKey: 'elena' });
setGameStatusMessage("Создание игры против AI...");
});
}
if (createPvPGameButton) {
createPvPGameButton.addEventListener('click', () => {
if (!isLoggedIn) {
setGameStatusMessage("Пожалуйста, войдите, чтобы начать игру.", true); return;
}
const selectedCharacter = getSelectedCharacterKey();
socket.emit('createGame', { mode: 'pvp', characterKey: selectedCharacter });
setGameStatusMessage(`Создание PvP игры за ${selectedCharacter === 'elena' ? 'Елену' : 'Альмагест'}...`);
});
}
if (joinPvPGameButton && gameIdInput) {
joinPvPGameButton.addEventListener('click', () => {
if (!isLoggedIn) {
setGameStatusMessage("Пожалуйста, войдите, чтобы присоединиться к игре.", true); return;
}
const gameIdToJoin = gameIdInput.value.trim();
if (gameIdToJoin) {
socket.emit('joinGame', { gameId: gameIdToJoin });
setGameStatusMessage(`Присоединение к игре ${gameIdToJoin}...`);
} else {
setGameStatusMessage("Пожалуйста, введите ID игры для присоединения.", true);
}
});
}
if (findRandomPvPGameButton) {
findRandomPvPGameButton.addEventListener('click', () => {
if (!isLoggedIn) {
setGameStatusMessage("Пожалуйста, войдите, чтобы найти игру.", true); return;
}
const selectedCharacter = getSelectedCharacterKey();
socket.emit('findRandomGame', { characterKey: selectedCharacter });
setGameStatusMessage(`Поиск случайной PvP игры (предпочтение: ${selectedCharacter === 'elena' ? 'Елена' : 'Альмагест'})...`);
});
}
if (attackButton) {
attackButton.addEventListener('click', () => {
if (currentGameId && currentGameState && !currentGameState.isGameOver && isLoggedIn) {
socket.emit('playerAction', { actionType: 'attack' });
}
});
}
function handleAbilityButtonClick(event) {
const button = event.currentTarget;
const abilityId = button.dataset.abilityId;
if (currentGameId && abilityId && currentGameState && !currentGameState.isGameOver && isLoggedIn) {
socket.emit('playerAction', { actionType: 'ability', abilityId: abilityId });
}
}
if (restartGameButton) {
restartGameButton.addEventListener('click', () => {
if (currentGameId && currentGameState && currentGameState.isGameOver && isLoggedIn) {
socket.emit('requestRestart', { gameId: currentGameId });
setGameStatusMessage("Запрос на рестарт отправлен...");
restartGameButton.disabled = true;
} else {
if (!currentGameId && isLoggedIn) {
alert("Ошибка: ID текущей игры не определен. Невозможно запросить рестарт.");
showGameSelectionScreen(loggedInUsername);
} else if (!isLoggedIn) {
showAuthScreen();
}
}
});
}
// --- Функции для UI игры ---
function initializeAbilityButtons() {
const abilitiesGrid = document.getElementById('abilities-grid');
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 = playerAbilitiesServer;
const baseStatsForResource = playerBaseStatsServer;
if (!abilitiesToDisplay || abilitiesToDisplay.length === 0 || !baseStatsForResource) {
abilitiesGrid.innerHTML = '<p class="placeholder-text">Нет доступных способностей.</p>';
return;
}
const resourceName = baseStatsForResource.resourceName || "Ресурс";
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 = opponentBaseStatsServer;
descriptionText = ability.descriptionFunction(config, targetStatsForDesc);
}
let title = `${ability.name} (${ability.cost} ${resourceName}) - ${descriptionText}`;
let cooldown = ability.cooldown;
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);
abilitiesGrid.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 && game.id) {
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) => {
if (!isLoggedIn) {
setGameStatusMessage("Пожалуйста, войдите, чтобы присоединиться к игре.", true); return;
}
socket.emit('joinGame', { gameId: e.target.dataset.gameId });
});
li.appendChild(joinBtn);
ul.appendChild(li);
}
});
availableGamesDiv.appendChild(ul);
} else {
availableGamesDiv.innerHTML += '<p>Нет доступных игр. Создайте свою!</p>';
}
}
// --- Обработчики событий Socket.IO ---
socket.on('connect', () => {
console.log('[Client] Socket connected to server! Socket ID:', socket.id);
if (!isLoggedIn) {
showAuthScreen();
} else {
showGameSelectionScreen(loggedInUsername);
}
});
socket.on('disconnect', (reason) => {
console.log('[Client] Disconnected from server:', reason);
setGameStatusMessage(`Отключено от сервера: ${reason}. Попробуйте обновить страницу.`, true);
if (currentGameId) {
currentGameState = null; currentGameId = null; myPlayerId = null;
myCharacterKey = null; opponentCharacterKey = null;
if (isLoggedIn && gameWrapper && gameWrapper.style.display !== 'none') {
showGameSelectionScreen(loggedInUsername);
} else if (!isLoggedIn){
showAuthScreen();
}
}
hideGameOverModal();
});
socket.on('registerResponse', (data) => {
setAuthMessage(data.message, !data.success);
if (data.success && registerForm) registerForm.reset();
});
socket.on('loginResponse', (data) => {
setAuthMessage(data.message, !data.success);
if (data.success) {
isLoggedIn = true; loggedInUsername = data.username;
setAuthMessage(""); showGameSelectionScreen(data.username);
} else {
isLoggedIn = false; loggedInUsername = '';
}
});
socket.on('gameCreated', (data) => {
if (!isLoggedIn) return;
currentGameId = data.gameId;
myPlayerId = data.yourPlayerId;
console.log(`[Client] Game created/joined: ${currentGameId}, Mode: ${data.mode}, You (${loggedInUsername}) are in slot: ${myPlayerId}`);
if (data.mode === 'pvp') {
if (gameIdInput) gameIdInput.value = currentGameId;
setGameStatusMessage(`PvP игра ${currentGameId.substring(0,8)}... создана. ID для друга: ${currentGameId}. Ожидание второго игрока...`);
} else {
setGameStatusMessage(`Игра против AI ${currentGameId.substring(0,8)}... создана. Ожидание начала...`);
}
});
socket.on('gameStarted', (data) => {
if (!isLoggedIn) return;
console.log('[Client] Event "gameStarted" received:', data);
currentGameId = data.gameId;
myPlayerId = data.yourPlayerId;
currentGameState = data.initialGameState;
playerBaseStatsServer = data.playerBaseStats;
opponentBaseStatsServer = data.opponentBaseStats;
playerAbilitiesServer = data.playerAbilities;
opponentAbilitiesServer = data.opponentAbilities;
myCharacterKey = playerBaseStatsServer?.characterKey;
opponentCharacterKey = opponentBaseStatsServer?.characterKey;
console.log(`[Client] Game started! My Slot ID: ${myPlayerId}, My Character: ${myCharacterKey}, Opponent Character: ${opponentCharacterKey}`);
if (data.clientConfig) {
window.GAME_CONFIG = { ...data.clientConfig };
} else if (!window.GAME_CONFIG) {
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 (window.gameUI && gameUI.uiElements?.log?.list) {
gameUI.uiElements.log.list.innerHTML = '';
}
if (window.gameUI && typeof gameUI.addToLog === 'function' && data.log) {
data.log.forEach(logEntry => gameUI.addToLog(logEntry.message, logEntry.type));
}
if (window.gameUI && typeof gameUI.updateUI === 'function') {
gameUI.updateUI();
}
hideGameOverModal();
if (restartGameButton) {
restartGameButton.disabled = true;
restartGameButton.dataset.gameIdForRestart = '';
}
setGameStatusMessage("");
});
socket.on('gameStateUpdate', (data) => {
if (!isLoggedIn || !currentGameId) return;
currentGameState = data.gameState;
window.gameState = currentGameState;
if (window.gameUI && typeof gameUI.updateUI === 'function') {
gameUI.updateUI();
}
if (window.gameUI && typeof gameUI.addToLog === 'function' && data.log) {
data.log.forEach(logEntry => gameUI.addToLog(logEntry.message, logEntry.type));
}
});
socket.on('logUpdate', (data) => {
if (!isLoggedIn || !currentGameId) return;
if (window.gameUI && typeof gameUI.addToLog === 'function' && data.log) {
data.log.forEach(logEntry => gameUI.addToLog(logEntry.message, logEntry.type));
}
});
socket.on('gameOver', (data) => {
if (!isLoggedIn || !currentGameId) return;
console.log('[Client] Game over:', data);
currentGameState = data.finalGameState;
window.gameState = currentGameState;
if (window.gameUI && typeof gameUI.updateUI === 'function') gameUI.updateUI();
if (window.gameUI && typeof gameUI.addToLog === 'function' && data.log) {
data.log.forEach(logEntry => gameUI.addToLog(logEntry.message, logEntry.type));
}
if (window.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', () => {
if (!isLoggedIn) return;
setGameStatusMessage("Ожидание присоединения оппонента...");
});
socket.on('opponentDisconnected', (data) => {
if (!isLoggedIn || !currentGameId) return;
const systemLogType = (window.GAME_CONFIG?.LOG_TYPE_SYSTEM) || 'system';
if (window.gameUI && typeof gameUI.addToLog === 'function') {
gameUI.addToLog("Противник отключился.", systemLogType);
}
if (currentGameState && !currentGameState.isGameOver) {
setGameStatusMessage("Противник отключился. Игра завершена. Вы можете начать новую.", true);
}
});
socket.on('turnNotification', (data) => {
// console.log("[Client] Turn notification for slot:", data.currentTurn, "My Slot ID is:", myPlayerId);
});
socket.on('waitingForRestartVote', (data) => {
if (!isLoggedIn || !currentGameId) return;
const systemLogType = (window.GAME_CONFIG?.LOG_TYPE_SYSTEM) || 'system';
const voterName = data.voterCharacterName || 'Игрок';
if (window.gameUI && typeof gameUI.addToLog === 'function') {
gameUI.addToLog(
`${voterName} (${data.voterRole}) проголосовал(а) за рестарт. Нужно еще ${data.votesNeeded} голосов.`,
systemLogType
);
}
setGameStatusMessage(`Игрок ${voterName} проголосовал за рестарт. Ожидание вашего решения или решения другого игрока.`);
if (restartGameButton && currentGameState?.isGameOver) {
restartGameButton.disabled = false;
}
});
socket.on('gameError', (data) => {
console.error('[Client] Server error:', data.message);
const systemLogType = (window.GAME_CONFIG?.LOG_TYPE_SYSTEM) || 'system';
if (isLoggedIn && window.gameUI && typeof gameUI.addToLog === 'function' && currentGameId && currentGameState && !currentGameState.isGameOver) {
gameUI.addToLog(`Ошибка: ${data.message}`, systemLogType);
}
setGameStatusMessage(`Ошибка: ${data.message}`, true);
});
socket.on('availablePvPGamesList', (games) => {
if (!isLoggedIn) return;
updateAvailableGamesList(games);
});
socket.on('noPendingGamesFound', (data) => {
if (!isLoggedIn) return;
setGameStatusMessage(data.message || "Свободных игр не найдено. Создана новая для вас, ожидайте оппонента.");
updateAvailableGamesList([]);
if (data.gameId) {
currentGameId = data.gameId;
myPlayerId = data.yourPlayerId;
if (gameIdInput) gameIdInput.value = currentGameId;
console.log(`[Client] New game ${currentGameId} created after no pending games found. My slot: ${myPlayerId}`);
}
});
// --- Начальное состояние UI ---
showAuthScreen();
});