bc/server_modules/gameManager.js

479 lines
33 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_modules/gameManager.js
const { v4: uuidv4 } = require('uuid');
const GameInstance = require('./gameInstance');
const gameData = require('./data');
const GAME_CONFIG = require('./config');
class GameManager {
constructor(io) {
this.io = io;
this.games = {}; // { gameId: GameInstance }
this.userIdentifierToGameId = {}; // { userId|socketId: gameId }
this.pendingPvPGames = []; // [gameId]
}
_removePreviousPendingGames(currentSocketId, identifier, excludeGameId = null) {
const oldPendingGameId = this.userIdentifierToGameId[identifier];
if (oldPendingGameId && oldPendingGameId !== excludeGameId && this.games[oldPendingGameId]) {
const gameToRemove = this.games[oldPendingGameId];
if (gameToRemove.mode === 'pvp' && gameToRemove.playerCount === 1 && this.pendingPvPGames.includes(oldPendingGameId)) {
const oldOwnerInfo = Object.values(gameToRemove.players).find(p => p.id === GAME_CONFIG.PLAYER_ID);
if (oldOwnerInfo && (oldOwnerInfo.identifier === identifier)) {
console.log(`[GameManager] Пользователь ${identifier} (сокет: ${currentSocketId}) создал/присоединился к новой игре. Удаляем его предыдущую ожидающую игру: ${oldPendingGameId}`);
this._cleanupGame(oldPendingGameId, 'replaced_by_new_game');
}
} else {
if (this.userIdentifierToGameId[identifier] !== excludeGameId) {
console.warn(`[GameManager] Удаление потенциально некорректной ссылки userIdentifierToGameId[${identifier}] на игру ${oldPendingGameId}.`);
delete this.userIdentifierToGameId[identifier];
}
}
}
}
createGame(socket, mode = 'ai', chosenCharacterKey = 'elena', identifier) {
this._removePreviousPendingGames(socket.id, identifier);
if (this.userIdentifierToGameId[identifier] && this.games[this.userIdentifierToGameId[identifier]]) {
console.warn(`[GameManager] Пользователь ${identifier} (сокет: ${socket.id}) уже в игре ${this.userIdentifierToGameId[identifier]}. Игнорируем запрос на создание.`);
socket.emit('gameError', { message: 'Вы уже находитесь в активной или ожидающей игре.' });
this.handleRequestGameState(socket, identifier);
return;
}
const gameId = uuidv4();
const game = new GameInstance(gameId, this.io, mode, this);
game.ownerIdentifier = identifier;
this.games[gameId] = game;
const charKeyForInstance = (mode === 'pvp') ? chosenCharacterKey : 'elena';
if (game.addPlayer(socket, charKeyForInstance, identifier)) {
this.userIdentifierToGameId[identifier] = gameId;
console.log(`[GameManager] Игра создана: ${gameId} (режим: ${mode}) игроком ${identifier} (сокет: ${socket.id}, выбран: ${charKeyForInstance})`);
const assignedPlayerId = game.players[socket.id]?.id;
if (!assignedPlayerId) {
this._cleanupGame(gameId, 'player_add_failed');
console.error(`[GameManager] Ошибка при создании игры ${gameId}: Не удалось назначить ID игрока сокету ${socket.id} (идентификатор ${identifier}).`);
socket.emit('gameError', { message: 'Ошибка сервера при создании игры.' });
return;
}
socket.emit('gameCreated', { gameId: gameId, mode: mode, yourPlayerId: assignedPlayerId });
if ((game.mode === 'ai' && game.playerCount === 1) || (game.mode === 'pvp' && game.playerCount === 2)) {
console.log(`[GameManager] Игра ${gameId} готова к старту. Инициализация и запуск.`);
const isInitialized = game.initializeGame();
if (isInitialized) {
game.startGame();
} else {
console.error(`[GameManager] Не удалось запустить игру ${gameId}: initializeGame вернул false или gameState некорректен после инициализации.`);
this._cleanupGame(gameId, 'initialization_failed');
}
if (game.mode === 'pvp' && game.playerCount === 2) {
const gameIndex = this.pendingPvPGames.indexOf(gameId);
if (gameIndex > -1) this.pendingPvPGames.splice(gameIndex, 1);
this.broadcastAvailablePvPGames();
}
} else if (mode === 'pvp' && game.playerCount === 1) {
if (!this.pendingPvPGames.includes(gameId)) {
this.pendingPvPGames.push(gameId);
}
game.initializeGame(); // Частичная инициализация
this.broadcastAvailablePvPGames();
}
} else {
this._cleanupGame(gameId, 'player_add_failed');
console.warn(`[GameManager] Не удалось добавить игрока ${socket.id} (идентификатор ${identifier}) в игру ${gameId}. Игра удалена.`);
}
}
joinGame(socket, gameId, identifier) {
const game = this.games[gameId];
if (!game) { socket.emit('gameError', { message: 'Игра с таким ID не найдена.' }); return; }
if (game.mode !== 'pvp') { socket.emit('gameError', { message: 'К этой игре нельзя присоединиться как к PvP.' }); return; }
if (game.playerCount >= 2) { socket.emit('gameError', { message: 'Эта PvP игра уже заполнена.' }); return; }
if (this.userIdentifierToGameId[identifier] && this.games[this.userIdentifierToGameId[identifier]] && this.userIdentifierToGameId[identifier] !== gameId) {
console.warn(`[GameManager] Пользователь ${identifier} (сокет: ${socket.id}) уже в игре ${this.userIdentifierToGameId[identifier]}. Игнорируем запрос на присоединение.`);
socket.emit('gameError', { message: 'Вы уже находитесь в активной или ожидающей игре.' });
this.handleRequestGameState(socket, identifier);
return;
}
if (game.players[socket.id]) { socket.emit('gameError', { message: 'Вы уже в этой игре.' }); return; }
this._removePreviousPendingGames(socket.id, identifier, gameId);
if (game.addPlayer(socket, null, identifier)) {
this.userIdentifierToGameId[identifier] = gameId;
console.log(`[GameManager] Игрок ${identifier} (сокет: ${socket.id}) присоединился к PvP игре ${gameId}`);
if (game.mode === 'pvp' && game.playerCount === 2) {
console.log(`[GameManager] Игра ${gameId} готова к старту. Инициализация и запуск.`);
const isInitialized = game.initializeGame();
if (isInitialized) {
game.startGame();
} else {
console.error(`[GameManager] Не удалось запустить игру ${gameId}: initializeGame вернул false или gameState некорректен после инициализации.`);
this._cleanupGame(gameId, 'initialization_failed');
}
const gameIndex = this.pendingPvPGames.indexOf(gameId);
if (gameIndex > -1) this.pendingPvPGames.splice(gameIndex, 1);
this.broadcastAvailablePvPGames();
}
} else {
console.warn(`[GameManager] Не удалось добавить игрока ${socket.id} (идентификатор ${identifier}) в игру ${gameId}.`);
}
}
findAndJoinRandomPvPGame(socket, chosenCharacterKeyForCreation = 'elena', identifier) {
this._removePreviousPendingGames(socket.id, identifier);
if (this.userIdentifierToGameId[identifier] && this.games[this.userIdentifierToGameId[identifier]]) {
console.warn(`[GameManager] Пользователь ${identifier} (сокет: ${socket.id}) уже в игре ${this.userIdentifierToGameId[identifier]}. Игнорируем запрос на поиск.`);
socket.emit('gameError', { message: 'Вы уже находитесь в активной или ожидающей игре.' });
this.handleRequestGameState(socket, identifier);
return;
}
let gameIdToJoin = null;
const preferredOpponentKey = chosenCharacterKeyForCreation === 'elena' ? 'almagest' : 'elena';
for (const id of this.pendingPvPGames) {
const pendingGame = this.games[id];
if (pendingGame && pendingGame.mode === 'pvp' && pendingGame.playerCount === 1 && pendingGame.ownerIdentifier !== identifier) {
const firstPlayerInfo = Object.values(pendingGame.players).find(p => p.id === GAME_CONFIG.PLAYER_ID);
if (firstPlayerInfo && firstPlayerInfo.chosenCharacterKey === preferredOpponentKey) {
gameIdToJoin = id;
break;
}
if (!gameIdToJoin) gameIdToJoin = id;
}
}
if (gameIdToJoin) {
console.log(`[GameManager] Игрок ${identifier} (сокет: ${socket.id}) нашел игру ${gameIdToJoin} и присоединяется.`);
this.joinGame(socket, gameIdToJoin, identifier);
} else {
console.log(`[GameManager] Игрок ${identifier} (сокет: ${socket.id}) не нашел свободных игр. Создает новую.`);
this.createGame(socket, 'pvp', chosenCharacterKeyForCreation, identifier);
socket.emit('noPendingGamesFound', {
message: 'Свободных PvP игр не найдено. Создана новая игра для вас. Ожидайте противника.',
gameId: this.userIdentifierToGameId[identifier],
yourPlayerId: GAME_CONFIG.PLAYER_ID
});
}
}
handlePlayerAction(identifier, actionData) {
const gameId = this.userIdentifierToGameId[identifier];
const game = this.games[gameId];
if (game && game.players) {
const playerInfo = Object.values(game.players).find(p => p.identifier === identifier);
const currentSocketId = playerInfo?.socket?.id;
if (playerInfo && currentSocketId) {
const actualSocket = this.io.sockets.sockets.get(currentSocketId);
if (actualSocket && actualSocket.connected) {
game.processPlayerAction(currentSocketId, actionData);
} else {
console.warn(`[GameManager] Игрок ${identifier} отправил действие (${actionData?.actionType}), но его текущий сокет (${currentSocketId}) не найден или отключен.`);
}
} else {
console.warn(`[GameManager] Игрок ${identifier} отправил действие (${actionData?.actionType}) для игры ${gameId}, но его запись не найдена в game.players.`);
delete this.userIdentifierToGameId[identifier];
const playerSocket = this.io.sockets.sockets.get(identifier) || playerInfo?.socket;
if (playerSocket) {
playerSocket.emit('gameNotFound', { message: 'Ваша игровая сессия не найдена или завершена.' });
}
}
} else {
console.warn(`[GameManager] Игрок ${identifier} отправил действие (${actionData?.actionType}), но его игра (ID: ${gameId}) не найдена в GameManager.`);
delete this.userIdentifierToGameId[identifier];
const playerSocket = this.io.sockets.sockets.get(identifier);
if (playerSocket) {
playerSocket.emit('gameNotFound', { message: 'Ваша игровая сессия не найдена или завершена.' });
}
}
}
handleDisconnect(socketId, identifier) {
const gameId = this.userIdentifierToGameId[identifier];
const game = this.games[gameId];
if (game && game.players) {
const playerInfo = Object.values(game.players).find(p => p.identifier === identifier);
if (playerInfo) {
console.log(`[GameManager] Игрок ${identifier} (сокет: ${socketId}) отключился. В игре ${gameId}.`);
const disconnectedPlayerRole = playerInfo.id;
const disconnectedCharacterKey = playerInfo.chosenCharacterKey;
game.removePlayer(socketId); // Удаляем именно этот сокет
if (game.playerCount === 0) {
console.log(`[GameManager] Игра ${gameId} пуста после дисконнекта ${socketId} (идентификатор ${identifier}). Удаляем.`);
this._cleanupGame(gameId, 'player_count_zero_on_disconnect');
} else if (game.mode === 'pvp' && game.playerCount === 1 && game.gameState && !game.gameState.isGameOver) {
// Если это PvP игра и остался 1 игрок, и игра НЕ была завершена дисконнектом этого игрока
// (т.е. другой игрок еще в игре)
// Тогда игра переходит в состояние ожидания.
const remainingPlayerInfo = Object.values(game.players)[0]; // Единственный оставшийся игрок
if (remainingPlayerInfo) {
// Проверяем, что это не тот же игрок, что и отключившийся
if (remainingPlayerInfo.identifier !== identifier) {
game.endGameDueToDisconnect(socketId, disconnectedPlayerRole, disconnectedCharacterKey);
// _cleanupGame будет вызван из endGameDueToDisconnect
} else {
// Отключился единственный оставшийся игрок в ожидающей игре.
// _cleanupGame должен быть вызван.
console.log(`[GameManager] Отключился единственный игрок ${identifier} из ожидающей PvP игры ${gameId}. Удаляем игру.`);
this._cleanupGame(gameId, 'last_player_disconnected_from_pending');
}
} else {
// Оставшегося игрока нет, хотя playerCount > 0 - это ошибка, очищаем.
console.error(`[GameManager] Ошибка: playerCount > 0 в игре ${gameId}, но не найден оставшийся игрок после дисконнекта ${identifier}. Очищаем.`);
this._cleanupGame(gameId, 'error_no_remaining_player');
}
} else if (game.gameState && !game.gameState.isGameOver) {
// Если игра была активна (не ожидала) и еще не была завершена,
// дисконнект одного из игроков завершает игру.
game.endGameDueToDisconnect(socketId, disconnectedPlayerRole, disconnectedCharacterKey);
// _cleanupGame будет вызван из endGameDueToDisconnect
} else if (game.gameState?.isGameOver) {
// Если игра уже была завершена до этого дисконнекта, просто удаляем ссылку на игру для отключившегося.
console.log(`[GameManager] Игрок ${identifier} отключился из уже завершенной игры ${gameId}. Удаляем ссылку.`);
delete this.userIdentifierToGameId[identifier];
// _cleanupGame уже был вызван при завершении игры.
} else {
// Другие случаи (например, AI игра, где игрок остался, или ошибка)
console.log(`[GameManager] Игрок ${identifier} отключился из активной игры ${gameId} (mode: ${game.mode}, players: ${game.playerCount}). Удаляем ссылку.`);
delete this.userIdentifierToGameId[identifier];
}
} else {
console.warn(`[GameManager] Игрок с идентификатором ${identifier} (сокет: ${socketId}) не найден в game.players для игры ${gameId}.`);
delete this.userIdentifierToGameId[identifier];
}
} else {
console.log(`[GameManager] Отключился сокет ${socketId} (идентификатор ${identifier}). Игровая сессия по этому идентификатору не найдена.`);
delete this.userIdentifierToGameId[identifier];
}
}
_cleanupGame(gameId, reason = 'unknown_reason') {
const game = this.games[gameId];
if (!game) {
console.warn(`[GameManager] _cleanupGame called for unknown game ID: ${gameId}`);
return false;
}
console.log(`[GameManager] Cleaning up game ${gameId} (Mode: ${game.mode}, Reason: ${reason})...`);
// Очищаем таймеры, если они были активны
if (typeof game.clearTurnTimer === 'function') {
game.clearTurnTimer();
}
Object.values(game.players).forEach(playerInfo => {
if (playerInfo && playerInfo.identifier && this.userIdentifierToGameId[playerInfo.identifier] === gameId) {
delete this.userIdentifierToGameId[playerInfo.identifier];
console.log(`[GameManager] Removed userIdentifierToGameId for ${playerInfo.identifier}.`);
}
});
const pendingIndex = this.pendingPvPGames.indexOf(gameId);
if (pendingIndex > -1) {
this.pendingPvPGames.splice(pendingIndex, 1);
console.log(`[GameManager] Removed game ${gameId} from pendingPvPGames.`);
}
delete this.games[gameId];
console.log(`[GameManager] Deleted GameInstance for game ${gameId}.`);
this.broadcastAvailablePvPGames();
return true;
}
getAvailablePvPGamesListForClient() {
return this.pendingPvPGames
.map(gameId => {
const game = this.games[gameId];
if (game && game.mode === 'pvp' && game.playerCount === 1 && game.gameState && !game.gameState.isGameOver) {
let firstPlayerUsername = 'Игрок';
let firstPlayerCharacterName = '';
const firstPlayerInfo = Object.values(game.players).find(p => p.id === GAME_CONFIG.PLAYER_ID);
if (firstPlayerInfo) {
if (firstPlayerInfo.socket?.userData?.username) {
firstPlayerUsername = firstPlayerInfo.socket.userData.username;
} else {
firstPlayerUsername = `User#${String(firstPlayerInfo.identifier).substring(0, 6)}`;
}
const charKey = firstPlayerInfo.chosenCharacterKey;
if (charKey) {
const charBaseStats = this._getCharacterBaseData(charKey);
if (charBaseStats && charBaseStats.name) {
firstPlayerCharacterName = charBaseStats.name;
} else {
firstPlayerCharacterName = charKey;
}
}
} else {
console.warn(`[GameManager] getAvailablePvPGamesList: firstPlayerInfo (Player 1) не найдена для ожидающей игры ${gameId}.`);
firstPlayerUsername = 'Неизвестный игрок';
}
let statusString = `Ожидает 1 игрока (Создал: ${firstPlayerUsername}`;
if (firstPlayerCharacterName) statusString += ` за ${firstPlayerCharacterName}`;
statusString += `)`;
return { id: gameId, status: statusString };
}
if (game && !this.pendingPvPGames.includes(gameId)) { /* Game not pending */ }
else if (game && game.playerCount === 1 && (game.gameState?.isGameOver || !game.gameState)) { /* Game over or not initialized */ }
else if (game && game.playerCount === 2) { /* Game full */ }
else if (game && game.playerCount === 0) {
console.warn(`[GameManager] getAvailablePvPGamesList: Найдена пустая игра ${gameId} в games. Удаляем.`);
delete this.games[gameId];
}
return null;
})
.filter(info => info !== null);
}
broadcastAvailablePvPGames() {
const availableGames = this.getAvailablePvPGamesListForClient();
this.io.emit('availablePvPGamesList', availableGames);
console.log(`[GameManager] Обновлен список доступных PvP игр. Всего: ${availableGames.length}`);
}
getActiveGamesList() {
return Object.values(this.games).map(game => {
let playerSlotCharName = game.gameState?.player?.name || (game.playerCharacterKey ? this._getCharacterBaseData(game.playerCharacterKey)?.name : 'N/A (ожидание)');
let opponentSlotCharName = game.gameState?.opponent?.name || (game.opponentCharacterKey ? this._getCharacterBaseData(game.opponentCharacterKey)?.name : 'N/A (ожидание)');
const playerInSlot1 = Object.values(game.players).find(p => p.id === GAME_CONFIG.PLAYER_ID);
const playerInSlot2 = Object.values(game.players).find(p => p.id === GAME_CONFIG.OPPONENT_ID);
if (!playerInSlot1) playerSlotCharName = 'Пусто';
if (!playerInSlot2 && game.mode === 'pvp') opponentSlotCharName = 'Ожидание...';
if (!playerInSlot2 && game.mode === 'ai' && game.aiOpponent) opponentSlotCharName = 'Балард (AI)';
return {
id: game.id.substring(0, 8),
mode: game.mode,
playerCount: game.playerCount,
isGameOver: game.gameState ? game.gameState.isGameOver : 'N/A (Не инициализирована)',
playerSlot: playerSlotCharName,
opponentSlot: opponentSlotCharName,
ownerIdentifier: game.ownerIdentifier || 'N/A',
pending: this.pendingPvPGames.includes(game.id),
turn: game.gameState ? `Ход ${game.gameState.turnNumber}, ${game.gameState.isPlayerTurn ? (playerInSlot1?.identifier || 'Player Slot') : (playerInSlot2?.identifier || 'Opponent Slot')}` : 'N/A'
};
});
}
handleRequestGameState(socket, identifier) {
const gameId = this.userIdentifierToGameId[identifier];
let game = gameId ? this.games[gameId] : null;
if (game && game.players) {
const playerInfo = Object.values(game.players).find(p => p.identifier === identifier);
if (playerInfo) {
if (game.gameState?.isGameOver) {
console.log(`[GameManager] Reconnected user ${identifier} to game ${gameId} which is already over. Sending gameNotFound.`);
delete this.userIdentifierToGameId[identifier];
socket.emit('gameNotFound', { message: 'Ваша предыдущая игровая сессия уже завершена.' });
return;
}
console.log(`[GameManager] Found game ${gameId} for identifier ${identifier} (role ${playerInfo.id}). Reconnecting socket ${socket.id}.`);
const oldSocketId = playerInfo.socket?.id;
if (oldSocketId && oldSocketId !== socket.id && game.players[oldSocketId]) {
console.log(`[GameManager] Updating socket ID for player ${identifier} from ${oldSocketId} to ${socket.id} in game ${gameId}.`);
delete game.players[oldSocketId];
if (game.playerSockets[playerInfo.id]?.id === oldSocketId) {
delete game.playerSockets[playerInfo.id];
}
}
game.players[socket.id] = playerInfo;
game.players[socket.id].socket = socket;
game.playerSockets[playerInfo.id] = socket;
socket.join(game.id);
const playerCharDataForClient = this._getCharacterData(playerInfo.chosenCharacterKey);
const opponentActualSlotId = playerInfo.id === GAME_CONFIG.PLAYER_ID ? GAME_CONFIG.OPPONENT_ID : GAME_CONFIG.PLAYER_ID;
let opponentCharacterKeyForClient = game.gameState?.[opponentActualSlotId]?.characterKey || null;
if (!opponentCharacterKeyForClient) {
opponentCharacterKeyForClient = playerInfo.id === GAME_CONFIG.PLAYER_ID ? game.opponentCharacterKey : game.playerCharacterKey;
}
const opponentCharDataForClient = this._getCharacterData(opponentCharacterKeyForClient);
if (playerCharDataForClient && opponentCharDataForClient && game.gameState) {
const isGameReadyForPlay = (game.mode === 'ai' && game.playerCount === 1) || (game.mode === 'pvp' && game.playerCount === 2);
const isOpponentDefinedInState = game.gameState.opponent?.characterKey && game.gameState.opponent?.name !== 'Ожидание игрока...';
socket.emit('gameState', {
gameId: game.id, yourPlayerId: playerInfo.id, gameState: game.gameState,
playerBaseStats: playerCharDataForClient.baseStats, opponentBaseStats: opponentCharDataForClient.baseStats,
playerAbilities: playerCharDataForClient.abilities, opponentAbilities: opponentCharDataForClient.abilities,
log: game.consumeLogBuffer(), clientConfig: { ...GAME_CONFIG }
});
console.log(`[GameManager] Sent gameState to socket ${socket.id} (identifier: ${identifier}) for game ${game.id}.`);
if (!game.gameState.isGameOver && isGameReadyForPlay && !isOpponentDefinedInState) {
console.log(`[GameManager] Game ${game.id} found ready but not fully started on reconnect. Initializing/Starting.`);
const isInitialized = game.initializeGame();
if (isInitialized) {
game.startGame();
} else {
console.error(`[GameManager] Failed to initialize game ${game.id} on reconnect. Cannot start.`);
this.io.to(game.id).emit('gameError', { message: 'Ошибка сервера при старте игры после переподключения.' });
this._cleanupGame(gameId, 'reconnect_initialization_failed');
}
} else if (!isGameReadyForPlay) {
console.log(`[GameManager] Reconnected user ${identifier} to pending game ${gameId}. Sending gameState and waiting status.`);
socket.emit('waitingForOpponent');
} else if (game.gameState.isGameOver) { // Повторная проверка, т.к. startGame мог завершить игру
console.log(`[GameManager] Reconnected to game ${gameId} which is now over (after re-init). Sending gameNotFound.`);
delete this.userIdentifierToGameId[identifier];
socket.emit('gameNotFound', { message: 'Ваша игровая сессия завершилась во время переподключения.' });
} else {
console.log(`[GameManager] Reconnected user ${identifier} to active game ${gameId}. gameState sent.`);
// Важно: если игра активна, нужно отправить и текущее состояние таймера.
// Это можно сделать, вызвав game.startTurnTimer() (он отправит update),
// но только если это ход этого игрока и игра не AI (или ход игрока в AI).
// Или добавить отдельный метод в GameInstance для отправки текущего состояния таймера.
if (typeof game.startTurnTimer === 'function') { // Проверяем, что метод существует
// Перезапуск таймера здесь может быть некорректным, если ход не этого игрока
// Лучше, чтобы gameInstance сам отправлял 'turnTimerUpdate' при gameState
// Либо добавить специальный метод в gameInstance для отправки текущего значения таймера
// Пока оставим так, startTurnTimer сам проверит, чей ход.
game.startTurnTimer();
}
}
} else {
console.error(`[GameManager] Failed to send gameState to ${socket.id} (identifier ${identifier}) for game ${gameId}: missing character data or gameState.`);
socket.emit('gameError', { message: 'Ошибка сервера при восстановлении состояния игры.' });
this._cleanupGame(gameId, 'reconnect_send_failed');
socket.emit('gameNotFound', { message: 'Ваша игровая сессия в некорректном состоянии и была завершена.' });
}
} else {
console.warn(`[GameManager] Found game ${gameId} by identifier ${identifier}, but player with this identifier not found in game.players.`);
delete this.userIdentifierToGameId[identifier];
socket.emit('gameNotFound', { message: 'Ваша игровая сессия не найдена. Возможно, идентификатор пользователя некорректен.' });
}
} else {
console.log(`[GameManager] No active or pending game found for identifier ${identifier}.`);
socket.emit('gameNotFound', { message: 'Игровая сессия не найдена.' });
}
}
_getCharacterData(key) {
if (!key) { console.warn("GameManager::_getCharacterData called with null/undefined key."); return null; }
switch (key) {
case 'elena': return { baseStats: gameData.playerBaseStats, abilities: gameData.playerAbilities };
case 'balard': return { baseStats: gameData.opponentBaseStats, abilities: gameData.opponentAbilities };
case 'almagest': return { baseStats: gameData.almagestBaseStats, abilities: gameData.almagestAbilities };
default: console.error(`GameManager::_getCharacterData: Unknown character key "${key}"`); return null;
}
}
_getCharacterBaseData(key) {
const charData = this._getCharacterData(key);
return charData ? charData.baseStats : null;
}
_getCharacterAbilities(key) {
const charData = this._getCharacterData(key);
return charData ? charData.abilities : null;
}
}
module.exports = GameManager;