Загрузить файлы в «/»

This commit is contained in:
PsiMagistr 2025-05-15 16:08:11 +00:00
parent 44331cfcd2
commit dd35157869

268
bc.js
View File

@ -1,166 +1,228 @@
// bc.js (или server.js - ваш основной файл сервера) // bc.js - Главный файл сервера Battle Club
const express = require('express'); const express = require('express');
const http = require('http'); const http = require('http');
const socketIo = require('socket.io'); const { Server } = require('socket.io');
const path = require('path'); const path = require('path');
//hello6
// Серверные модули
const GameManager = require('./server_modules/gameManager');
const authController = require('./server_modules/auth'); // Ваш модуль аутентификации
// const GAME_CONFIG = require('./server_modules/config'); // Не используется напрямую здесь, но может быть полезен для отладки
const hostname = 'localhost'; // или '0.0.0.0' для доступа извне // Импорт серверных модулей
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 app = express();
const server = http.createServer(app); const server = http.createServer(app);
const io = socketIo(server, {
// Настройка Socket.IO
// cors options могут потребоваться, если клиент и сервер работают на разных портах/доменах
const io = new Server(server, {
cors: { cors: {
origin: "*", // Разрешить все источники для простоты разработки. В продакшене укажите конкретный домен клиента. origin: "*", // Разрешить подключение с любого домена (для разработки). В продакшене лучше указать конкретный домен клиента.
methods: ["GET", "POST"] methods: ["GET", "POST"]
} }
}); });
const PORT = process.env.PORT || 3200; // Раздача статических файлов из папки 'public'
// Статическое обслуживание файлов из папки 'public'
app.use(express.static(path.join(__dirname, 'public'))); app.use(express.static(path.join(__dirname, 'public')));
// Создание экземпляра GameManager // Создаем экземпляр GameManager
const gameManager = new GameManager(io); const gameManager = new GameManager(io);
// Хранилище информации о залогиненных пользователях по socket.id
// В более сложном приложении здесь может быть Redis или другое внешнее хранилище сессий
const loggedInUsers = {}; // { socket.id: { userId: ..., username: ... } }
// Обработка подключений Socket.IO
io.on('connection', (socket) => { io.on('connection', (socket) => {
console.log(`[Server BC.JS] New client connected: ${socket.id}`); console.log(`[Socket.IO] Пользователь подключился: ${socket.id}`);
// При подключении нового клиента, отправляем ему текущий список доступных PvP игр // Привязываем user data к сокету (пока пустые)
const availableGames = gameManager.getAvailablePvPGamesListForClient(); socket.userData = null; // { userId: ..., username: ... }
socket.emit('availablePvPGamesList', availableGames);
// Обработчик запроса на обновление списка PvP игр // При подключении клиента, если он уже залогинен (например, по cookie/token, что здесь не реализовано,
socket.on('requestPvPGameList', () => { // но может быть добавлено), нужно восстановить его user data и проверить, не в игре ли он.
const currentAvailableGames = gameManager.getAvailablePvPGamesListForClient(); // В текущей простой реализации, мы полагаемся на то, что клиент после коннекта сам отправит логин,
socket.emit('availablePvPGamesList', currentAvailableGames); // если он был залогинен. Но если бы была проверка сессии, логика была бы тут.
}); // Добавляем вызов handleRequestGameState при коннекте, если есть user data (для примера,
// но для полной реализации нужны cookies/токены)
// if (socket.userData?.userId) { // Эта проверка сработает только после успешного логина в текущей сессии
// gameManager.handleRequestGameState(socket, socket.userData.userId); // Передаем объект socket
// }
// --- Аутентификация ---
// --- Обработчики событий Аутентификации ---
socket.on('register', async (data) => { socket.on('register', async (data) => {
console.log(`[Server BC.JS] Received 'register' event from ${socket.id} with username: ${data?.username}`); console.log(`[Socket.IO] Register attempt for username: "${data?.username}" from ${socket.id}`);
if (!data || typeof data.username !== 'string' || typeof data.password !== 'string') { const result = await auth.registerUser(data?.username, data?.password);
socket.emit('registerResponse', { success: false, message: 'Некорректные данные запроса для регистрации.' }); if (result.success) {
return; console.log(`[Socket.IO] Registration successful for ${result.username} (${result.userId})`);
} else {
console.warn(`[Socket.IO] Registration failed for "${data?.username}": ${result.message}`);
} }
const result = await authController.registerUser(data.username, data.password);
socket.emit('registerResponse', result); socket.emit('registerResponse', result);
}); });
socket.on('login', async (data) => { socket.on('login', async (data) => {
console.log(`[Server BC.JS] Received 'login' event from ${socket.id} with username: ${data?.username}`); console.log(`[Socket.IO] Login attempt for username: "${data?.username}" from ${socket.id}`);
if (!data || typeof data.username !== 'string' || typeof data.password !== 'string') { const result = await auth.loginUser(data?.username, data?.password);
socket.emit('loginResponse', { success: false, message: 'Некорректные данные запроса для входа.' });
return;
}
const result = await authController.loginUser(data.username, data.password);
if (result.success) { 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 }; socket.userData = { userId: result.userId, username: result.username };
console.log(`[Server BC.JS] User ${result.username} (ID: ${result.userId}) associated with socket ${socket.id}. Welcome!`); 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.emit('loginResponse', result);
}); });
socket.on('logout', () => { socket.on('logout', () => {
const username = socket.userData?.username || socket.id; console.log(`[Socket.IO] Logout for user ${socket.userData?.username || socket.id}`);
console.log(`[Server BC.JS] Received 'logout' event from ${username}.`); // Уведомляем gameManager о дисконнекте (для корректного выхода из игры, если в ней был)
if (socket.userData) { // Game Manager сам очистит ссылку socketToGame[socket.id] при handleDisconnect
// При выходе пользователя, обрабатываем его возможное участие в играх // ИСПРАВЛЕНИЕ: Передаем userId или socket.id в handleDisconnect
gameManager.handleDisconnect(socket.id, socket.userData.userId); // Используем userId для более точной обработки gameManager.handleDisconnect(socket.id, socket.userData?.userId || socket.id);
delete socket.userData; // Удаляем данные пользователя из сокета
console.log(`[Server BC.JS] User data cleared for ${username}.`); // Очищаем информацию о пользователе на сокете и в хранилище
} socket.userData = null;
// Можно отправить подтверждение выхода, если нужно if (loggedInUsers[socket.id]) delete loggedInUsers[socket.id];
// socket.emit('logoutResponse', { success: true, message: 'Вы успешно вышли.' });
// Клиент должен сам переключиться на экран аутентификации
}); });
// --- Управление Играми --- // --- Обработчики событий Управления Играми ---
socket.on('createGame', (data) => { socket.on('createGame', (data) => {
if (!socket.userData) { // Пользователь, даже не залогиненный, может создать AI игру (идентифицируется по socket.id)
socket.emit('gameError', { message: "Ошибка: Вы не авторизованы для создания игры." }); // Для 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; return;
} }
const mode = data?.mode || 'ai'; // 'ai' или 'pvp'
const characterKey = (data?.characterKey === 'almagest') ? 'almagest' : 'elena'; // По умолчанию Елена console.log(`[Socket.IO] Create Game request from ${socket.userData?.username || socket.id} (Identifier: ${identifier}). Mode: ${mode}, Character: ${data?.characterKey}`);
console.log(`[Server BC.JS] User ${socket.userData.username} (socket: ${socket.id}) requests createGame. Mode: ${mode}, Character: ${characterKey}`);
gameManager.createGame(socket, mode, characterKey, socket.userData.userId); const characterKey = data?.characterKey || 'elena'; // По умолчанию Елена
gameManager.createGame(socket, mode, characterKey, identifier); // Передаем идентификатор
}); });
socket.on('joinGame', (data) => { socket.on('joinGame', (data) => {
if (!socket.userData) { if (!socket.userData) { // Проверяем, залогинен ли пользователь
socket.emit('gameError', { message: "Ошибка: Вы не авторизованы для присоединения к игре." }); socket.emit('gameError', { message: 'Необходимо войти в систему для присоединения к игре.' });
return; return;
} }
console.log(`[Server BC.JS] User ${socket.userData.username} (socket: ${socket.id}) requests joinGame for ID: ${data?.gameId}`); console.log(`[Socket.IO] Join Game request from ${socket.userData.username} (${socket.id}). Game ID: ${data?.gameId}`);
if (data && typeof data.gameId === 'string') { const gameId = data?.gameId;
gameManager.joinGame(socket, data.gameId, socket.userData.userId); const identifier = socket.userData.userId; // Присоединиться может только залогиненный
if (gameId) {
gameManager.joinGame(socket, gameId, identifier); // Передаем идентификатор
} else { } else {
socket.emit('gameError', { message: 'Ошибка присоединения: неверный формат ID игры.' }); socket.emit('gameError', { message: 'Не указан ID игры для присоединения.' });
} }
}); });
socket.on('findRandomGame', (data) => { socket.on('findRandomGame', (data) => {
if (!socket.userData) { if (!socket.userData) { // Проверяем, залогинен ли пользователь
socket.emit('gameError', { message: "Ошибка: Вы не авторизованы для поиска игры." }); socket.emit('gameError', { message: 'Необходимо войти в систему для поиска игры.' });
return; return;
} }
const characterKey = (data?.characterKey === 'almagest') ? 'almagest' : 'elena'; console.log(`[Socket.IO] Find Random Game request from ${socket.userData.username} (${socket.id}). Preferred Character: ${data?.characterKey}`);
console.log(`[Server BC.JS] User ${socket.userData.username} (socket: ${socket.id}) requests findRandomGame. Preferred Character: ${characterKey}`); const characterKey = data?.characterKey || 'elena'; // Предпочитаемый персонаж для создания, если не найдено
gameManager.findAndJoinRandomPvPGame(socket, characterKey, socket.userData.userId); const identifier = socket.userData.userId; // Ищет и создает только залогиненный
gameManager.findAndJoinRandomPvPGame(socket, characterKey, identifier); // Передаем идентификатор
}); });
// --- Игровые Действия --- socket.on('requestPvPGameList', () => {
socket.on('playerAction', (data) => { // Список игр доступен всем, даже не залогиненным, но присоединиться можно только залогиненным
// 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) { if (!socket.userData) {
// Если пользователь не авторизован, но пытается совершить действие (маловероятно при правильной логике клиента) console.log(`[Socket.IO] Request Game State from unauthenticated socket ${socket.id}.`);
socket.emit('gameError', { message: "Ошибка: Вы не авторизованы для совершения этого действия." }); socket.emit('gameNotFound', { message: 'Необходимо войти для восстановления игры.' });
return; return;
} }
// GameManager сам проверит, принадлежит ли этот сокет к активной игре console.log(`[Socket.IO] Request Game State from ${socket.userData.username} (${socket.id}).`);
gameManager.handlePlayerAction(socket.id, data); // ИСПРАВЛЕНИЕ: Передаем объект socket и identifier (userId)
gameManager.handleRequestGameState(socket, socket.userData.userId);
}); });
// Обработчик 'requestRestart' удален, так как эта функциональность заменена на "возврат в меню"
// --- Отключение Клиента --- // --- Обработчик события Игрового Действия ---
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) => { socket.on('disconnect', (reason) => {
const username = socket.userData?.username || socket.id; const identifier = socket.userData?.userId || socket.id; // Используем userId для залогиненных, socket.id для гостей
console.log(`[Server BC.JS] Client ${username} disconnected. Reason: ${reason}. Socket ID: ${socket.id}`); console.log(`[Socket.IO] Пользователь отключился: ${socket.id} (Причина: ${reason}). Identifier: ${identifier}`);
// Передаем userId, если он есть, для более точной обработки в GameManager
// (например, для удаления его ожидающих игр или корректного завершения активной игры) // Уведомляем gameManager о дисконнекте, чтобы он обновил состояние игры.
const userId = socket.userData?.userId; // Передаем идентификатор пользователя.
gameManager.handleDisconnect(socket.id, userId); gameManager.handleDisconnect(socket.id, identifier); // Передаем как socketId, так и identifier
// socket.userData автоматически очистится для этого объекта socket при его удалении из io.sockets
// Удаляем пользователя из списка залогиненных, если был там
if (loggedInUsers[socket.id]) {
delete loggedInUsers[socket.id];
}
// Если сокет не был залогинен, его identifier был socket.id.
// Связь userIdentifierToGameId будет очищена в gameManager.handleDisconnect, если игра пуста.
}); });
// Для отладки: вывод списка активных игр каждые N секунд // Опционально: отправка списка активных игр на сервере для отладки (по запросу с консоли или админки)
// setInterval(() => { // global.getActiveGames = () => gameManager.getActiveGamesList();
// console.log("--- Active Games ---"); // console.log("Type getActiveGames() in server console to list games.");
// const activeGames = gameManager.getActiveGamesList();
// if (activeGames.length > 0) {
// activeGames.forEach(game => {
// console.log(`ID: ${game.id}, Mode: ${game.mode}, Players: ${game.playerCount}, GameOver: ${game.isGameOver}, P1: ${game.playerSlot}, P2: ${game.opponentSlot}, Owner: ${game.ownerUserId}, Pending: ${game.pending}`);
// });
// } else {
// console.log("No active games.");
// }
// console.log("--- Pending PvP Games IDs ---");
// console.log(gameManager.pendingPvPGames.map(id => id.substring(0,8)));
// console.log("--- User to Pending Game Map ---");
// console.log(gameManager.userToPendingGame);
// console.log("---------------------");
// }, 30000); // Каждые 30 секунд
}); });
server.listen(PORT, hostname, () => { // Запуск HTTP сервера
console.log(`==== Medieval Clash Server ====`); const PORT = process.env.PORT || 3200; // Использовать порт из переменных окружения или 3000 по умолчанию
console.log(` Listening on http://${hostname}:${PORT}`); server.listen(PORT, () => {
console.log(` Public files served from: ${path.join(__dirname, 'public')}`); console.log(`Server running on port ${PORT}`);
console.log(` Waiting for connections...`); console.log(`Serving static files from: ${path.join(__dirname, 'public')}`);
console.log(`===============================`); // 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); // Аварийное завершение процесса - можно раскомментировать в продакшене
}); });