bc/server/bc.js
svoboda200786 ab32fc96bf Обновить server/bc.js
явное указание айпи сервера
2025-05-20 09:25:46 +00:00

193 lines
11 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.

// /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.BC_INTERNAL_PORT || 3200; // Внутренний порт для bc.js
const HOSTNAME = '127.0.0.1'; // Слушать ТОЛЬКО на localhost
server.listen(PORT, HOSTNAME, () => { // Явно указываем HOSTNAME
console.log(`Battle Club HTTP Application Server running at http://${HOSTNAME}:${PORT}`);
console.log(`This server should only be accessed locally by the reverse proxy.`);
console.log(`Serving static files from: ${path.join(__dirname, 'public')}`);
});
// Обработка необработанных промис-ошибок
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); // Аварийное завершение процесса
});