190 lines
11 KiB
JavaScript
190 lines
11 KiB
JavaScript
// /server/bc.js - Главный файл сервера Battle Club
|
||
|
||
const express = require('express');
|
||
const http = require('http');
|
||
const { Server } = require('socket.io');
|
||
const path = require('path');
|
||
|
||
// Импорт серверных модулей из их новых местоположений
|
||
const authService = require('./auth/authService'); // Сервис аутентификации
|
||
const GameManager = require('./game/GameManager'); // Менеджер игр
|
||
const db = require('./core/db'); // Модуль базы данных (для инициализации)
|
||
const GAME_CONFIG = require('./core/config'); // Глобальный конфиг игры
|
||
// data.js (теперь data/index.js) и gameLogic.js (теперь game/logic/index.js)
|
||
// импортируются внутри GameManager и GameInstance или их компонентов.
|
||
|
||
const app = express();
|
||
const server = http.createServer(app);
|
||
|
||
// Настройка Socket.IO
|
||
const io = new Server(server, {
|
||
cors: {
|
||
origin: "https://pavel-chagovsky.com:3200", // Для разработки. В продакшене укажите домен клиента.
|
||
methods: ["GET", "POST"]
|
||
},
|
||
// Можно настроить pingInterval и pingTimeout для более быстрого обнаружения дисконнектов
|
||
// pingInterval: 10000, // 10 секунд
|
||
// pingTimeout: 5000, // 5 секунд (клиент должен ответить в течение этого времени)
|
||
});
|
||
|
||
// Раздача статических файлов из папки 'public'
|
||
// __dirname будет указывать на папку server/, поэтому нужно подняться на уровень выше
|
||
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: ... }
|
||
|
||
// --- Обработчики событий Аутентификации ---
|
||
socket.on('register', async (data) => {
|
||
console.log(`[Socket.IO] Register attempt for username: "${data?.username}" from ${socket.id}`);
|
||
const result = await authService.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 authService.loginUser(data?.username, data?.password);
|
||
if (result.success && result.userId && result.username) { // Убедимся, что userId и username есть
|
||
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; // Сохраняем для быстрого доступа, если нужно
|
||
|
||
// После успешного логина, просим GameManager проверить, не был ли этот пользователь в игре
|
||
if (gameManager && typeof gameManager.handleRequestGameState === 'function') {
|
||
gameManager.handleRequestGameState(socket, result.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', () => {
|
||
const username = socket.userData?.username || 'UnknownUser';
|
||
const userId = socket.userData?.userId;
|
||
console.log(`[Socket.IO] Logout request from user ${username} (ID: ${userId}, Socket: ${socket.id})`);
|
||
|
||
if (gameManager && typeof gameManager.handleDisconnect === 'function' && userId) {
|
||
// Уведомляем GameManager о "дисконнекте" этого пользователя из его игры, если он там был.
|
||
// handleDisconnect использует identifier (userId в данном случае) для поиска игры.
|
||
// Передаем socket.id на случай, если игра была AI и identifier был socket.id (хотя при logout должен быть userId).
|
||
gameManager.handleDisconnect(socket.id, userId);
|
||
}
|
||
|
||
if (loggedInUsers[socket.id]) {
|
||
delete loggedInUsers[socket.id];
|
||
}
|
||
socket.userData = null;
|
||
// Клиент сам обработает UI после logout (например, покажет экран логина)
|
||
// Можно отправить подтверждение, но обычно не требуется: socket.emit('logoutResponse', { success: true });
|
||
console.log(`[Socket.IO] User ${username} (Socket: ${socket.id}) logged out.`);
|
||
});
|
||
|
||
// --- Обработчики событий Управления Играми ---
|
||
// Все эти события делегируются в GameManager
|
||
|
||
socket.on('createGame', (data) => {
|
||
const identifier = socket.userData?.userId || socket.id; // userId для залогиненных, socket.id для гостей (AI игра)
|
||
const mode = data?.mode || 'ai';
|
||
|
||
if (mode === 'pvp' && !socket.userData) {
|
||
socket.emit('gameError', { message: 'Необходимо войти в систему для создания PvP игры.' });
|
||
return;
|
||
}
|
||
console.log(`[Socket.IO] Create Game from ${socket.userData?.username || socket.id} (ID: ${identifier}). Mode: ${mode}, Char: ${data?.characterKey}`);
|
||
gameManager.createGame(socket, mode, data?.characterKey, identifier);
|
||
});
|
||
|
||
socket.on('joinGame', (data) => {
|
||
if (!socket.userData?.userId) {
|
||
socket.emit('gameError', { message: 'Необходимо войти для присоединения к PvP игре.' });
|
||
return;
|
||
}
|
||
console.log(`[Socket.IO] Join Game from ${socket.userData.username} (ID: ${socket.userData.userId}). GameID: ${data?.gameId}`);
|
||
gameManager.joinGame(socket, data?.gameId, socket.userData.userId);
|
||
});
|
||
|
||
socket.on('findRandomGame', (data) => {
|
||
if (!socket.userData?.userId) {
|
||
socket.emit('gameError', { message: 'Необходимо войти для поиска случайной PvP игры.' });
|
||
return;
|
||
}
|
||
console.log(`[Socket.IO] Find Random Game from ${socket.userData.username} (ID: ${socket.userData.userId}). PrefChar: ${data?.characterKey}`);
|
||
gameManager.findAndJoinRandomPvPGame(socket, data?.characterKey, socket.userData.userId);
|
||
});
|
||
|
||
socket.on('requestPvPGameList', () => {
|
||
// 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', () => {
|
||
if (!socket.userData?.userId) {
|
||
// 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} (ID: ${socket.userData.userId}).`);
|
||
gameManager.handleRequestGameState(socket, socket.userData.userId);
|
||
});
|
||
|
||
// --- Обработчик события Игрового Действия ---
|
||
socket.on('playerAction', (actionData) => {
|
||
const identifier = socket.userData?.userId || socket.id; // Идентификатор для GameManager
|
||
// console.log(`[Socket.IO] Player Action from ${identifier} (socket ${socket.id}):`, actionData);
|
||
gameManager.handlePlayerAction(identifier, actionData);
|
||
});
|
||
|
||
// --- Обработчик отключения сокета ---
|
||
socket.on('disconnect', (reason) => {
|
||
const identifier = socket.userData?.userId || socket.id;
|
||
console.log(`[Socket.IO] Пользователь отключился: ${socket.id} (Причина: ${reason}). Identifier: ${identifier}`);
|
||
|
||
gameManager.handleDisconnect(socket.id, identifier); // Передаем и socketId, и identifier
|
||
|
||
if (loggedInUsers[socket.id]) {
|
||
delete loggedInUsers[socket.id];
|
||
}
|
||
// socket.userData очистится автоматически при уничтожении объекта socket
|
||
});
|
||
});
|
||
|
||
// Запуск HTTP сервера
|
||
const PORT = process.env.PORT || 3200;
|
||
server.listen(PORT, () => {
|
||
console.log(`[Server] Запущен на порту ${PORT}`);
|
||
console.log(`[Server] Раздача статики из: ${path.join(__dirname, '..', 'public')}`);
|
||
// db.js уже выводит сообщение о подключении к БД
|
||
});
|
||
|
||
// Обработка необработанных промис-ошибок
|
||
process.on('unhandledRejection', (reason, promise) => {
|
||
console.error('[Server FATAL] Unhandled Rejection at:', promise, 'reason:', reason);
|
||
// В продакшене здесь может быть более сложная логика или перезапуск процесса
|
||
// process.exit(1);
|
||
});
|
||
|
||
process.on('uncaughtException', (err) => {
|
||
console.error('[Server FATAL] Uncaught Exception:', err);
|
||
// Критическая ошибка, обычно требует перезапуска приложения
|
||
process.exit(1); // Аварийное завершение процесса
|
||
}); |