bc/bc.js

228 lines
15 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.

// bc.js - Главный файл сервера Battle Club
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const path = require('path');
// Импорт серверных модулей
const auth = require('./server_modules/auth');
const GameManager = require('./server_modules/gameManager');
const db = require('./server_modules/db'); // Импорт для инициализации соединения с БД (хотя пул создается при require)
const GAME_CONFIG = require('./server_modules/config'); // Конфиг игры
// gameData импортируется внутри GameInstance и GameLogic
const app = express();
const server = http.createServer(app);
// Настройка Socket.IO
// cors options могут потребоваться, если клиент и сервер работают на разных портах/доменах
const io = new Server(server, {
cors: {
origin: "*", // Разрешить подключение с любого домена (для разработки). В продакшене лучше указать конкретный домен клиента.
methods: ["GET", "POST"]
}
});
// Раздача статических файлов из папки 'public'
app.use(express.static(path.join(__dirname, 'public')));
// Создаем экземпляр GameManager
const gameManager = new GameManager(io);
// Хранилище информации о залогиненных пользователях по socket.id
// В более сложном приложении здесь может быть Redis или другое внешнее хранилище сессий
const loggedInUsers = {}; // { socket.id: { userId: ..., username: ... } }
// Обработка подключений Socket.IO
io.on('connection', (socket) => {
console.log(`[Socket.IO] Пользователь подключился: ${socket.id}`);
// Привязываем user data к сокету (пока пустые)
socket.userData = null; // { userId: ..., username: ... }
// При подключении клиента, если он уже залогинен (например, по cookie/token, что здесь не реализовано,
// но может быть добавлено), нужно восстановить его user data и проверить, не в игре ли он.
// В текущей простой реализации, мы полагаемся на то, что клиент после коннекта сам отправит логин,
// если он был залогинен. Но если бы была проверка сессии, логика была бы тут.
// Добавляем вызов handleRequestGameState при коннекте, если есть user data (для примера,
// но для полной реализации нужны cookies/токены)
// if (socket.userData?.userId) { // Эта проверка сработает только после успешного логина в текущей сессии
// gameManager.handleRequestGameState(socket, socket.userData.userId); // Передаем объект socket
// }
// --- Обработчики событий Аутентификации ---
socket.on('register', async (data) => {
console.log(`[Socket.IO] Register attempt for username: "${data?.username}" from ${socket.id}`);
const result = await auth.registerUser(data?.username, data?.password);
if (result.success) {
console.log(`[Socket.IO] Registration successful for ${result.username} (${result.userId})`);
} else {
console.warn(`[Socket.IO] Registration failed for "${data?.username}": ${result.message}`);
}
socket.emit('registerResponse', result);
});
socket.on('login', async (data) => {
console.log(`[Socket.IO] Login attempt for username: "${data?.username}" from ${socket.id}`);
const result = await auth.loginUser(data?.username, data?.password);
if (result.success) {
console.log(`[Socket.IO] Login successful for ${result.username} (${result.userId}). Assigning to socket ${socket.id}.`);
// Сохраняем информацию о пользователе в сессии сокета
socket.userData = { userId: result.userId, username: result.username };
loggedInUsers[socket.id] = socket.userData;
// Проверяем, есть ли у пользователя активная игра при логине (если он был отключен)
// ИСПРАВЛЕНИЕ: Передаем объект socket
gameManager.handleRequestGameState(socket, socket.userData.userId);
} else {
console.warn(`[Socket.IO] Login failed for "${data?.username}": ${result.message}`);
socket.userData = null; // Убеждаемся, что данные пользователя на сокете сброшены
if (loggedInUsers[socket.id]) delete loggedInUsers[socket.id];
}
socket.emit('loginResponse', result);
});
socket.on('logout', () => {
console.log(`[Socket.IO] Logout for user ${socket.userData?.username || socket.id}`);
// Уведомляем gameManager о дисконнекте (для корректного выхода из игры, если в ней был)
// Game Manager сам очистит ссылку socketToGame[socket.id] при handleDisconnect
// ИСПРАВЛЕНИЕ: Передаем userId или socket.id в handleDisconnect
gameManager.handleDisconnect(socket.id, socket.userData?.userId || socket.id);
// Очищаем информацию о пользователе на сокете и в хранилище
socket.userData = null;
if (loggedInUsers[socket.id]) delete loggedInUsers[socket.id];
// Клиент должен сам переключиться на экран аутентификации
});
// --- Обработчики событий Управления Играми ---
socket.on('createGame', (data) => {
// Пользователь, даже не залогиненный, может создать AI игру (идентифицируется по socket.id)
// Для PvP игры нужна аутентификация (идентификация по userId)
const identifier = socket.userData?.userId || socket.id; // Используем userId для залогиненных, socket.id для гостей
const mode = data?.mode || 'ai';
if (mode === 'pvp' && !socket.userData) {
socket.emit('gameError', { message: 'Необходимо войти в систему для создания PvP игры.' });
return;
}
console.log(`[Socket.IO] Create Game request from ${socket.userData?.username || socket.id} (Identifier: ${identifier}). Mode: ${mode}, Character: ${data?.characterKey}`);
const characterKey = data?.characterKey || 'elena'; // По умолчанию Елена
gameManager.createGame(socket, mode, characterKey, identifier); // Передаем идентификатор
});
socket.on('joinGame', (data) => {
if (!socket.userData) { // Проверяем, залогинен ли пользователь
socket.emit('gameError', { message: 'Необходимо войти в систему для присоединения к игре.' });
return;
}
console.log(`[Socket.IO] Join Game request from ${socket.userData.username} (${socket.id}). Game ID: ${data?.gameId}`);
const gameId = data?.gameId;
const identifier = socket.userData.userId; // Присоединиться может только залогиненный
if (gameId) {
gameManager.joinGame(socket, gameId, identifier); // Передаем идентификатор
} else {
socket.emit('gameError', { message: 'Не указан ID игры для присоединения.' });
}
});
socket.on('findRandomGame', (data) => {
if (!socket.userData) { // Проверяем, залогинен ли пользователь
socket.emit('gameError', { message: 'Необходимо войти в систему для поиска игры.' });
return;
}
console.log(`[Socket.IO] Find Random Game request from ${socket.userData.username} (${socket.id}). Preferred Character: ${data?.characterKey}`);
const characterKey = data?.characterKey || 'elena'; // Предпочитаемый персонаж для создания, если не найдено
const identifier = socket.userData.userId; // Ищет и создает только залогиненный
gameManager.findAndJoinRandomPvPGame(socket, characterKey, identifier); // Передаем идентификатор
});
socket.on('requestPvPGameList', () => {
// Список игр доступен всем, даже не залогиненным, но присоединиться можно только залогиненным
// if (!socket.userData) {
// socket.emit('gameError', { message: 'Необходимо войти в систему для просмотра игр.' });
// return;
// }
console.log(`[Socket.IO] Request PvP Game List from ${socket.userData?.username || socket.id}`);
const availableGames = gameManager.getAvailablePvPGamesListForClient();
socket.emit('availablePvPGamesList', availableGames);
});
// Обработчик для клиента, запрашивающего состояние игры (например, при переподключении)
socket.on('requestGameState', () => {
// Запрашивать состояние игры может только залогиненный пользователь, т.к. только у них есть userId для идентификации
if (!socket.userData) {
console.log(`[Socket.IO] Request Game State from unauthenticated socket ${socket.id}.`);
socket.emit('gameNotFound', { message: 'Необходимо войти для восстановления игры.' });
return;
}
console.log(`[Socket.IO] Request Game State from ${socket.userData.username} (${socket.id}).`);
// ИСПРАВЛЕНИЕ: Передаем объект socket и identifier (userId)
gameManager.handleRequestGameState(socket, socket.userData.userId);
});
// --- Обработчик события Игрового Действия ---
socket.on('playerAction', (actionData) => {
// Действие в игре может совершить как залогиненный (PvP), так и не залогиненный (AI) игрок.
// Используем userId для залогиненных, socket.id для гостей.
const identifier = socket.userData?.userId || socket.id;
// Game Manager сам проверит, находится ли идентификатор в игре и его ли сейчас ход
// ИСПРАВЛЕНИЕ: Передаем идентификатор вместо socket.id
gameManager.handlePlayerAction(identifier, actionData); // Передаем идентификатор
});
// --- Обработчик отключения сокета ---
socket.on('disconnect', (reason) => {
const identifier = socket.userData?.userId || socket.id; // Используем userId для залогиненных, socket.id для гостей
console.log(`[Socket.IO] Пользователь отключился: ${socket.id} (Причина: ${reason}). Identifier: ${identifier}`);
// Уведомляем gameManager о дисконнекте, чтобы он обновил состояние игры.
// Передаем идентификатор пользователя.
gameManager.handleDisconnect(socket.id, identifier); // Передаем как socketId, так и identifier
// Удаляем пользователя из списка залогиненных, если был там
if (loggedInUsers[socket.id]) {
delete loggedInUsers[socket.id];
}
// Если сокет не был залогинен, его identifier был socket.id.
// Связь userIdentifierToGameId будет очищена в gameManager.handleDisconnect, если игра пуста.
});
// Опционально: отправка списка активных игр на сервере для отладки (по запросу с консоли или админки)
// global.getActiveGames = () => gameManager.getActiveGamesList();
// console.log("Type getActiveGames() in server console to list games.");
});
// Запуск HTTP сервера
const PORT = process.env.PORT || 3200; // Использовать порт из переменных окружения или 3000 по умолчанию
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
console.log(`Serving static files from: ${path.join(__dirname, 'public')}`);
// console.log("Database connection pool created/checked (from db.js require)."); // db.js уже логирует
});
// Обработка необработанных промис-ошибок
process.on('unhandledRejection', (reason, promise) => {
console.error('[UNHANDLED REJECTION] Unhandled Rejection at:', promise, 'reason:', reason);
// Логировать ошибку, возможно, завершить процесс в продакшене
});
process.on('uncaughtException', (err) => {
console.error('[UNCAUGHT EXCEPTION] Caught exception:', err);
// Логировать ошибку, выполнить очистку ресурсов, и завершить процесс
// В продакшене здесь может быть более сложная логика, например, graceful shutdown
// process.exit(1); // Аварийное завершение процесса - можно раскомментировать в продакшене
});