439 lines
27 KiB
JavaScript
439 lines
27 KiB
JavaScript
// /server/game/GameManager.js
|
||
const { v4: uuidv4 } = require('uuid');
|
||
const GameInstance = require('./instance/GameInstance');
|
||
const dataUtils = require('../data/dataUtils');
|
||
const GAME_CONFIG = require('../core/config');
|
||
|
||
class GameManager {
|
||
constructor(io) {
|
||
this.io = io;
|
||
this.games = {}; // { gameId: GameInstance }
|
||
this.userIdentifierToGameId = {}; // { userId: gameId }
|
||
this.pendingPvPGames = []; // Массив gameId ожидающих PvP игр
|
||
console.log("[GameManager] Initialized.");
|
||
}
|
||
|
||
_removePreviousPendingGames(currentSocketId, identifier, excludeGameId = null) {
|
||
console.log(`[GameManager._removePreviousPendingGames] User: ${identifier}, Socket: ${currentSocketId}, Exclude: ${excludeGameId}`);
|
||
const oldPendingGameId = this.userIdentifierToGameId[identifier];
|
||
|
||
if (oldPendingGameId && oldPendingGameId !== excludeGameId && this.games[oldPendingGameId]) {
|
||
const gameToRemove = this.games[oldPendingGameId];
|
||
// Используем game.playerCount (или аналогичный метод GameInstance, если он инкапсулирует это)
|
||
if (gameToRemove.mode === 'pvp' &&
|
||
gameToRemove.playerCount === 1 && // Предполагаем, GameInstance.playerCount - это активные игроки
|
||
gameToRemove.ownerIdentifier === identifier &&
|
||
this.pendingPvPGames.includes(oldPendingGameId)) {
|
||
console.log(`[GameManager._removePreviousPendingGames] User ${identifier} creating/joining new. Removing previous pending PvP game: ${oldPendingGameId}`);
|
||
this._cleanupGame(oldPendingGameId, 'owner_action_removed_pending_game');
|
||
}
|
||
}
|
||
}
|
||
|
||
createGame(socket, mode = 'ai', chosenCharacterKey = null, identifier) {
|
||
console.log(`[GameManager.createGame] User: ${identifier} (Socket: ${socket.id}), Mode: ${mode}, Char: ${chosenCharacterKey || 'Default'}`);
|
||
|
||
const existingGameId = this.userIdentifierToGameId[identifier];
|
||
if (existingGameId && this.games[existingGameId]) {
|
||
const existingGame = this.games[existingGameId];
|
||
// Используем game.playerCount
|
||
console.warn(`[GameManager.createGame] User ${identifier} already in game ${existingGameId}. Mode: ${existingGame.mode}, Players: ${existingGame.playerCount}, Owner: ${existingGame.ownerIdentifier}, GameOver: ${existingGame.gameState?.isGameOver}`);
|
||
|
||
if (existingGame.gameState && !existingGame.gameState.isGameOver) {
|
||
if (existingGame.mode === 'pvp' && existingGame.playerCount === 1 && existingGame.ownerIdentifier === identifier) {
|
||
socket.emit('gameError', { message: 'Вы уже создали PvP игру и ожидаете оппонента.' });
|
||
} else {
|
||
socket.emit('gameError', { message: 'Вы уже находитесь в активной игре.' });
|
||
}
|
||
this.handleRequestGameState(socket, identifier);
|
||
return;
|
||
} else {
|
||
this._cleanupGame(existingGameId, `stale_finished_on_create_${identifier}`);
|
||
}
|
||
}
|
||
this._removePreviousPendingGames(socket.id, identifier);
|
||
|
||
const gameId = uuidv4();
|
||
console.log(`[GameManager.createGame] New GameID: ${gameId}`);
|
||
const game = new GameInstance(gameId, this.io, mode, this);
|
||
this.games[gameId] = game;
|
||
|
||
const charKeyForPlayer = mode === 'ai' ? (chosenCharacterKey || 'elena') : (chosenCharacterKey || 'elena');
|
||
|
||
// addPlayer в GameInstance теперь bool, а не объект с результатом
|
||
if (game.addPlayer(socket, charKeyForPlayer, identifier)) {
|
||
this.userIdentifierToGameId[identifier] = gameId;
|
||
// Получаем роль и актуальный ключ из GameInstance после добавления
|
||
const playerInfo = Object.values(game.players).find(p => p.identifier === identifier);
|
||
const assignedPlayerId = playerInfo?.id;
|
||
const actualCharacterKey = playerInfo?.chosenCharacterKey;
|
||
|
||
if (!assignedPlayerId || !actualCharacterKey) {
|
||
console.error(`[GameManager.createGame] CRITICAL: Failed to get player role/charKey after addPlayer for ${identifier} in game ${gameId}. Cleaning up.`);
|
||
this._cleanupGame(gameId, 'player_info_missing_after_add');
|
||
socket.emit('gameError', { message: 'Ошибка сервера при создании роли в игре.' });
|
||
return;
|
||
}
|
||
|
||
console.log(`[GameManager.createGame] Player ${identifier} added to game ${gameId} as ${assignedPlayerId}. User map updated.`);
|
||
socket.emit('gameCreated', {
|
||
gameId: gameId,
|
||
mode: mode,
|
||
yourPlayerId: assignedPlayerId,
|
||
chosenCharacterKey: actualCharacterKey
|
||
});
|
||
|
||
if (mode === 'ai') {
|
||
if (game.initializeGame()) {
|
||
game.startGame();
|
||
} else {
|
||
this._cleanupGame(gameId, 'init_fail_ai_create_gm');
|
||
}
|
||
} else if (mode === 'pvp') {
|
||
game.initializeGame(); // Инициализирует первого игрока
|
||
if (!this.pendingPvPGames.includes(gameId)) {
|
||
this.pendingPvPGames.push(gameId);
|
||
}
|
||
socket.emit('waitingForOpponent');
|
||
this.broadcastAvailablePvPGames();
|
||
}
|
||
} else {
|
||
console.error(`[GameManager.createGame] game.addPlayer (instance method) failed for ${identifier} in ${gameId}. Cleaning up.`);
|
||
this._cleanupGame(gameId, 'player_add_failed_in_instance_gm');
|
||
}
|
||
}
|
||
|
||
joinGame(socket, gameIdToJoin, identifier, chosenCharacterKey = null) {
|
||
console.log(`[GameManager.joinGame] User: ${identifier} (Socket: ${socket.id}) attempts to join ${gameIdToJoin} with char ${chosenCharacterKey || 'Default'}`);
|
||
const game = this.games[gameIdToJoin];
|
||
|
||
if (!game) { socket.emit('gameError', { message: 'Игра с таким ID не найдена.' }); return; }
|
||
if (game.gameState?.isGameOver) { socket.emit('gameError', { message: 'Эта игра уже завершена.' }); this._cleanupGame(gameIdToJoin, `attempt_join_finished_${identifier}`); return; }
|
||
if (game.mode !== 'pvp') { socket.emit('gameError', { message: 'К этой игре нельзя присоединиться (не PvP).' }); return; }
|
||
|
||
const playerInfoInGame = Object.values(game.players).find(p => p.identifier === identifier);
|
||
if (game.playerCount >= 2 && !playerInfoInGame?.isTemporarilyDisconnected) {
|
||
socket.emit('gameError', { message: 'Эта PvP игра уже заполнена.' }); return;
|
||
}
|
||
if (game.ownerIdentifier === identifier && !playerInfoInGame?.isTemporarilyDisconnected) {
|
||
socket.emit('gameError', { message: 'Вы не можете присоединиться к своей же ожидающей игре как новый игрок.' }); this.handleRequestGameState(socket, identifier); return;
|
||
}
|
||
|
||
const existingGameIdOfUser = this.userIdentifierToGameId[identifier];
|
||
if (existingGameIdOfUser && existingGameIdOfUser !== gameIdToJoin) {
|
||
const otherGame = this.games[existingGameIdOfUser];
|
||
if (otherGame && !otherGame.gameState?.isGameOver) { socket.emit('gameError', { message: 'Вы уже в другой активной игре.' }); this.handleRequestGameState(socket, identifier); return; }
|
||
else if (otherGame?.gameState?.isGameOver) this._cleanupGame(existingGameIdOfUser, `stale_finished_on_join_${identifier}`);
|
||
}
|
||
this._removePreviousPendingGames(socket.id, identifier, gameIdToJoin);
|
||
|
||
const charKeyForJoin = chosenCharacterKey || 'elena';
|
||
if (game.addPlayer(socket, charKeyForJoin, identifier)) {
|
||
this.userIdentifierToGameId[identifier] = gameIdToJoin;
|
||
const joinedPlayerInfo = Object.values(game.players).find(p => p.identifier === identifier); // Получаем инфо после добавления
|
||
|
||
if (!joinedPlayerInfo || !joinedPlayerInfo.id || !joinedPlayerInfo.chosenCharacterKey) {
|
||
console.error(`[GameManager.joinGame] CRITICAL: Failed to get player role/charKey after addPlayer for ${identifier} joining ${gameIdToJoin}. Cleaning up.`);
|
||
// Не вызываем _cleanupGame здесь, т.к. игра могла быть уже с одним игроком.
|
||
// GameInstance.addPlayer должен был бы вернуть false и не изменить playerCount.
|
||
socket.emit('gameError', { message: 'Ошибка сервера при назначении роли в игре.' });
|
||
return;
|
||
}
|
||
console.log(`[GameManager.joinGame] Player ${identifier} added/reconnected to ${gameIdToJoin} as ${joinedPlayerInfo.id}.`);
|
||
socket.emit('gameCreated', {
|
||
gameId: gameIdToJoin,
|
||
mode: game.mode,
|
||
yourPlayerId: joinedPlayerInfo.id,
|
||
chosenCharacterKey: joinedPlayerInfo.chosenCharacterKey
|
||
});
|
||
|
||
if (game.playerCount === 2) { // Используем game.playerCount из GameInstance
|
||
console.log(`[GameManager.joinGame] Game ${gameIdToJoin} is now full. Initializing and starting.`);
|
||
if (game.initializeGame()) {
|
||
game.startGame();
|
||
} else {
|
||
this._cleanupGame(gameIdToJoin, 'full_init_fail_pvp_join_gm'); return;
|
||
}
|
||
const idx = this.pendingPvPGames.indexOf(gameIdToJoin);
|
||
if (idx > -1) this.pendingPvPGames.splice(idx, 1);
|
||
this.broadcastAvailablePvPGames();
|
||
}
|
||
}
|
||
}
|
||
|
||
findAndJoinRandomPvPGame(socket, chosenCharacterKeyForCreation = 'elena', identifier) {
|
||
// ... (Логика findAndJoinRandomPvPGame без изменений, использует game.playerCount)
|
||
console.log(`[GameManager.findRandomPvPGame] User: ${identifier} (Socket: ${socket.id}), CharForCreation: ${chosenCharacterKeyForCreation}`);
|
||
const existingGameId = this.userIdentifierToGameId[identifier];
|
||
if (existingGameId && this.games[existingGameId]) {
|
||
const existingGame = this.games[existingGameId];
|
||
if (!existingGame.gameState?.isGameOver) {
|
||
socket.emit('gameError', { message: 'Вы уже в активной или ожидающей игре.' });
|
||
this.handleRequestGameState(socket, identifier); return;
|
||
} else {
|
||
this._cleanupGame(existingGameId, `stale_finished_on_find_random_${identifier}`);
|
||
}
|
||
}
|
||
this._removePreviousPendingGames(socket.id, identifier);
|
||
|
||
let gameIdToJoin = null;
|
||
for (const id of [...this.pendingPvPGames]) {
|
||
const pendingGame = this.games[id];
|
||
if (pendingGame && pendingGame.mode === 'pvp' &&
|
||
pendingGame.playerCount === 1 &&
|
||
pendingGame.ownerIdentifier !== identifier &&
|
||
!pendingGame.gameState?.isGameOver) {
|
||
gameIdToJoin = id; break;
|
||
} else if (pendingGame?.gameState?.isGameOver) {
|
||
this._cleanupGame(id, `stale_finished_pending_on_find_random`);
|
||
}
|
||
}
|
||
|
||
if (gameIdToJoin) {
|
||
console.log(`[GameManager.findRandomPvPGame] Found pending game ${gameIdToJoin} for ${identifier}. Joining...`);
|
||
const randomJoinCharKey = ['elena', 'almagest', 'balard'][Math.floor(Math.random() * 3)];
|
||
this.joinGame(socket, gameIdToJoin, identifier, randomJoinCharKey);
|
||
} else {
|
||
console.log(`[GameManager.findRandomPvPGame] No suitable pending game. Creating new PvP game for ${identifier}.`);
|
||
this.createGame(socket, 'pvp', chosenCharacterKeyForCreation, identifier);
|
||
}
|
||
}
|
||
|
||
handlePlayerAction(identifier, actionData) {
|
||
const gameId = this.userIdentifierToGameId[identifier];
|
||
console.log(`[GameManager.handlePlayerAction] User: ${identifier}, Action: ${actionData?.actionType}, GameID: ${gameId}`);
|
||
const game = this.games[gameId];
|
||
if (game) {
|
||
if (game.gameState?.isGameOver) {
|
||
const playerSocket = Object.values(game.players).find(p => p.identifier === identifier)?.socket; // Находим сокет по identifier
|
||
if (playerSocket) this.handleRequestGameState(playerSocket, identifier);
|
||
return;
|
||
}
|
||
game.processPlayerAction(identifier, actionData); // Передаем identifier
|
||
} else {
|
||
delete this.userIdentifierToGameId[identifier];
|
||
const clientSocket = this._findClientSocketByIdentifier(identifier);
|
||
if (clientSocket) clientSocket.emit('gameNotFound', { message: 'Ваша игровая сессия не найдена при действии.' });
|
||
}
|
||
}
|
||
|
||
handlePlayerSurrender(identifier) {
|
||
// ... (Логика handlePlayerSurrender без изменений)
|
||
const gameId = this.userIdentifierToGameId[identifier];
|
||
console.log(`[GameManager.handlePlayerSurrender] User: ${identifier} surrendered. GameID from map: ${gameId}`);
|
||
const game = this.games[gameId];
|
||
if (game) {
|
||
if (game.gameState?.isGameOver) {
|
||
console.warn(`[GameManager.handlePlayerSurrender] User ${identifier} in game ${gameId} surrender, but game ALREADY OVER.`);
|
||
if (this.userIdentifierToGameId[identifier] === gameId) delete this.userIdentifierToGameId[identifier];
|
||
return;
|
||
}
|
||
if (typeof game.playerDidSurrender === 'function') game.playerDidSurrender(identifier);
|
||
else { console.error(`[GameManager.handlePlayerSurrender] CRITICAL: GameInstance ${gameId} missing playerDidSurrender!`); this._cleanupGame(gameId, "surrender_missing_method"); }
|
||
} else {
|
||
if (this.userIdentifierToGameId[identifier]) delete this.userIdentifierToGameId[identifier];
|
||
}
|
||
}
|
||
|
||
handleLeaveAiGame(identifier) {
|
||
const gameId = this.userIdentifierToGameId[identifier];
|
||
console.log(`[GameManager.handleLeaveAiGame] User: ${identifier} leaving AI game. GameID from map: ${gameId}`);
|
||
const game = this.games[gameId];
|
||
if (game) {
|
||
if (game.gameState?.isGameOver) {
|
||
console.warn(`[GameManager.handleLeaveAiGame] User ${identifier} game ${gameId} leaving, but game ALREADY OVER.`);
|
||
return;
|
||
}
|
||
if (game.mode === 'ai') {
|
||
if (typeof game.playerExplicitlyLeftAiGame === 'function') {
|
||
game.playerExplicitlyLeftAiGame(identifier);
|
||
} else {
|
||
console.error(`[GameManager.handleLeaveAiGame] CRITICAL: GameInstance ${gameId} missing playerExplicitlyLeftAiGame! Cleaning up directly.`);
|
||
this._cleanupGame(gameId, "leave_ai_missing_method");
|
||
}
|
||
} else {
|
||
console.warn(`[GameManager.handleLeaveAiGame] User ${identifier} sent leaveAiGame, but game ${gameId} is not AI mode (${game.mode}).`);
|
||
}
|
||
} else {
|
||
if (this.userIdentifierToGameId[identifier]) delete this.userIdentifierToGameId[identifier];
|
||
}
|
||
}
|
||
|
||
_findClientSocketByIdentifier(identifier) {
|
||
// ... (код без изменений)
|
||
for (const sid of this.io.sockets.sockets.keys()) {
|
||
const s = this.io.sockets.sockets.get(sid);
|
||
if (s && s.userData && s.userData.userId === identifier && s.connected) return s;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
handleDisconnect(socketId, identifier) {
|
||
const gameIdFromMap = this.userIdentifierToGameId[identifier];
|
||
console.log(`[GameManager.handleDisconnect] Socket: ${socketId}, User: ${identifier}, GameID from map: ${gameIdFromMap}`);
|
||
const game = gameIdFromMap ? this.games[gameIdFromMap] : null;
|
||
|
||
if (game) {
|
||
if (game.gameState?.isGameOver) {
|
||
console.log(`[GameManager.handleDisconnect] Game ${gameIdFromMap} for user ${identifier} (socket ${socketId}) ALREADY OVER.`);
|
||
return;
|
||
}
|
||
|
||
// Находим информацию об игроке в инстансе игры по identifier
|
||
const playerInfoInGame = Object.values(game.players).find(p => p.identifier === identifier);
|
||
|
||
if (playerInfoInGame && playerInfoInGame.socket?.id === socketId && !playerInfoInGame.isTemporarilyDisconnected) {
|
||
console.log(`[GameManager.handleDisconnect] Disconnecting socket ${socketId} matches active player ${identifier} (Role: ${playerInfoInGame.id}) in game ${gameIdFromMap}. Notifying GameInstance.`);
|
||
// Передаем роль, идентификатор и ключ персонажа в GameInstance
|
||
if (typeof game.handlePlayerPotentiallyLeft === 'function') {
|
||
game.handlePlayerPotentiallyLeft(playerInfoInGame.id, identifier, playerInfoInGame.chosenCharacterKey);
|
||
} else {
|
||
console.error(`[GameManager.handleDisconnect] CRITICAL: GameInstance ${gameIdFromMap} missing handlePlayerPotentiallyLeft!`);
|
||
this._cleanupGame(gameIdFromMap, "missing_reconnect_logic_on_disconnect_gm");
|
||
}
|
||
} else if (playerInfoInGame && playerInfoInGame.socket?.id !== socketId) {
|
||
console.log(`[GameManager.handleDisconnect] Disconnected socket ${socketId} is STALE for user ${identifier}. Active socket in game: ${playerInfoInGame.socket?.id}.`);
|
||
} else if (playerInfoInGame && playerInfoInGame.isTemporarilyDisconnected) {
|
||
console.log(`[GameManager.handleDisconnect] User ${identifier} (socket ${socketId}) disconnected while already temp disconnected. Reconnect timer handles final cleanup.`);
|
||
} else if (!playerInfoInGame) {
|
||
console.warn(`[GameManager.handleDisconnect] User ${identifier} mapped to game ${gameIdFromMap}, but not found in game.players. Clearing map.`);
|
||
if (this.userIdentifierToGameId[identifier] === gameIdFromMap) delete this.userIdentifierToGameId[identifier];
|
||
}
|
||
} else {
|
||
if (this.userIdentifierToGameId[identifier]) {
|
||
delete this.userIdentifierToGameId[identifier];
|
||
}
|
||
}
|
||
}
|
||
|
||
_cleanupGame(gameId, reason = 'unknown') {
|
||
// ... (Код _cleanupGame почти без изменений, но использует game.playerCount и game.ownerIdentifier)
|
||
console.log(`[GameManager._cleanupGame] Attempting cleanup for GameID: ${gameId}, Reason: ${reason}`);
|
||
const game = this.games[gameId];
|
||
|
||
if (!game) {
|
||
const pendingIdx = this.pendingPvPGames.indexOf(gameId);
|
||
if (pendingIdx > -1) { this.pendingPvPGames.splice(pendingIdx, 1); this.broadcastAvailablePvPGames(); }
|
||
for (const idKey in this.userIdentifierToGameId) { if (this.userIdentifierToGameId[idKey] === gameId) delete this.userIdentifierToGameId[idKey]; }
|
||
return false;
|
||
}
|
||
|
||
console.log(`[GameManager._cleanupGame] Cleaning up game ${game.id}. Owner: ${game.ownerIdentifier}. Reason: ${reason}. Players in game: ${game.playerCount}`);
|
||
if (typeof game.turnTimer?.clear === 'function') game.turnTimer.clear();
|
||
if (typeof game.clearAllReconnectTimers === 'function') game.clearAllReconnectTimers(); // Вызываем у GameInstance
|
||
|
||
if (game.gameState && !game.gameState.isGameOver) {
|
||
game.gameState.isGameOver = true;
|
||
}
|
||
|
||
let playersCleanedFromMap = 0;
|
||
Object.values(game.players).forEach(pInfo => { // Игроки теперь в game.players
|
||
if (pInfo?.identifier && this.userIdentifierToGameId[pInfo.identifier] === gameId) {
|
||
delete this.userIdentifierToGameId[pInfo.identifier];
|
||
playersCleanedFromMap++;
|
||
}
|
||
});
|
||
if (game.ownerIdentifier && this.userIdentifierToGameId[game.ownerIdentifier] === gameId &&
|
||
!Object.values(game.players).some(p=>p.identifier === game.ownerIdentifier)) { // Проверка, если владелец не в списке игроков
|
||
delete this.userIdentifierToGameId[game.ownerIdentifier];
|
||
playersCleanedFromMap++;
|
||
}
|
||
|
||
const pendingIdx = this.pendingPvPGames.indexOf(gameId);
|
||
if (pendingIdx > -1) this.pendingPvPGames.splice(pendingIdx, 1);
|
||
|
||
delete this.games[gameId];
|
||
console.log(`[GameManager._cleanupGame] Game ${game.id} instance deleted. Games left: ${Object.keys(this.games).length}. Pending: ${this.pendingPvPGames.length}. User map size: ${Object.keys(this.userIdentifierToGameId).length}`);
|
||
this.broadcastAvailablePvPGames();
|
||
return true;
|
||
}
|
||
|
||
getAvailablePvPGamesListForClient() {
|
||
// ... (Код без изменений, использует game.playerCount и game.ownerIdentifier)
|
||
return this.pendingPvPGames
|
||
.map(gameId => {
|
||
const game = this.games[gameId];
|
||
if (game && game.mode === 'pvp' && game.playerCount === 1 && (!game.gameState || !game.gameState.isGameOver)) {
|
||
// Находим первого игрока (владельца) в инстансе игры
|
||
const p1Entry = Object.values(game.players).find(p => p.id === GAME_CONFIG.PLAYER_ID && !p.isTemporarilyDisconnected);
|
||
let p1Username = 'Игрок';
|
||
let p1CharName = 'Неизвестный';
|
||
const ownerId = game.ownerIdentifier;
|
||
|
||
if (p1Entry && p1Entry.socket?.userData) {
|
||
p1Username = p1Entry.socket.userData.username || `User#${String(p1Entry.identifier).substring(0,4)}`;
|
||
const charData = dataUtils.getCharacterBaseStats(p1Entry.chosenCharacterKey);
|
||
p1CharName = charData?.name || p1Entry.chosenCharacterKey || 'Не выбран';
|
||
} else if (ownerId){ // Фоллбэк на поиск по ownerId, если p1Entry не найден или без userData
|
||
const ownerSocket = this._findClientSocketByIdentifier(ownerId);
|
||
p1Username = ownerSocket?.userData?.username || `Owner#${String(ownerId).substring(0,4)}`;
|
||
const ownerCharKey = game.playerCharacterKey; // Ключ персонажа первого игрока из GameInstance
|
||
const charData = ownerCharKey ? dataUtils.getCharacterBaseStats(ownerCharKey) : null;
|
||
p1CharName = charData?.name || ownerCharKey || 'Не выбран';
|
||
}
|
||
return { id: gameId, status: `Ожидает (${p1Username} за ${p1CharName})`, ownerIdentifier: ownerId };
|
||
}
|
||
return null;
|
||
})
|
||
.filter(info => info !== null);
|
||
}
|
||
|
||
broadcastAvailablePvPGames() {
|
||
const list = this.getAvailablePvPGamesListForClient();
|
||
this.io.emit('availablePvPGamesList', list);
|
||
}
|
||
|
||
handleRequestGameState(socket, identifier) {
|
||
const gameIdFromMap = this.userIdentifierToGameId[identifier];
|
||
console.log(`[GameManager.handleRequestGameState] User: ${identifier} (Socket: ${socket.id}) requests state. GameID from map: ${gameIdFromMap}`);
|
||
const game = gameIdFromMap ? this.games[gameIdFromMap] : null;
|
||
|
||
if (game) {
|
||
const playerInfoInGame = Object.values(game.players).find(p => p.identifier === identifier); // Ищем по identifier
|
||
console.log(`[GameManager.handleRequestGameState] Game ${gameIdFromMap} found. PlayerInfo: ${playerInfoInGame ? `Role: ${playerInfoInGame.id}, TempDisco: ${playerInfoInGame.isTemporarilyDisconnected}` : 'Not found in game.players'}`);
|
||
|
||
if (playerInfoInGame) {
|
||
if (game.gameState?.isGameOver) {
|
||
socket.emit('gameNotFound', { message: 'Ваша предыдущая игра уже завершена.' });
|
||
if(this.userIdentifierToGameId[identifier] === gameIdFromMap) delete this.userIdentifierToGameId[identifier];
|
||
return;
|
||
}
|
||
// Передаем РОЛЬ и НОВЫЙ СОКЕТ в GameInstance для обработки реконнекта
|
||
if (typeof game.handlePlayerReconnected === 'function') {
|
||
const reconnected = game.handlePlayerReconnected(playerInfoInGame.id, socket);
|
||
// ... (обработка результата reconnected, если нужно)
|
||
} else {
|
||
console.error(`[GameManager.handleRequestGameState] CRITICAL: GameInstance ${game.id} missing handlePlayerReconnected!`);
|
||
this._handleGameRecoveryError(socket, game.id, identifier, 'gi_missing_reconnect_method_gm');
|
||
}
|
||
} else {
|
||
this._handleGameRecoveryError(socket, gameIdFromMap, identifier, 'player_not_in_gi_players_reconnect_gm');
|
||
}
|
||
} else {
|
||
socket.emit('gameNotFound', { message: 'Активная игровая сессия не найдена.' });
|
||
if (this.userIdentifierToGameId[identifier]) delete this.userIdentifierToGameId[identifier];
|
||
}
|
||
}
|
||
|
||
_handleGameRecoveryError(socket, gameId, identifier, reasonCode) {
|
||
// ... (код без изменений)
|
||
console.error(`[GameManager._handleGameRecoveryError] Error recovering game (ID: ${gameId || 'N/A'}) for user ${identifier}. Reason: ${reasonCode}.`);
|
||
socket.emit('gameError', { message: 'Ошибка сервера при восстановлении состояния игры.' });
|
||
if (gameId && this.games[gameId]) { // Проверяем, что игра еще существует перед очисткой
|
||
this._cleanupGame(gameId, `recovery_error_${reasonCode}_for_${identifier}`);
|
||
} else if (this.userIdentifierToGameId[identifier]) {
|
||
// Если игра уже удалена, но пользователь все еще к ней привязан
|
||
const problematicGameId = this.userIdentifierToGameId[identifier];
|
||
if (this.games[problematicGameId]) { // Если она все же есть
|
||
this._cleanupGame(problematicGameId, `recovery_error_stale_map_${identifier}_reason_${reasonCode}`);
|
||
} else { // Если ее нет, просто чистим карту
|
||
delete this.userIdentifierToGameId[identifier];
|
||
}
|
||
}
|
||
// Если после _cleanupGame пользователь все еще привязан (маловероятно, но для гарантии)
|
||
if (this.userIdentifierToGameId[identifier]) delete this.userIdentifierToGameId[identifier];
|
||
socket.emit('gameNotFound', { message: 'Ваша игровая сессия была завершена из-за ошибки.' });
|
||
}
|
||
}
|
||
|
||
module.exports = GameManager; |