Добавление BASE_PATH
This commit is contained in:
parent
6e559be867
commit
31200c17e8
@ -6,12 +6,15 @@ export function initAuth(dependencies) {
|
|||||||
|
|
||||||
const { socket, clientState, ui } = dependencies;
|
const { socket, clientState, ui } = dependencies;
|
||||||
const { loginForm, registerForm, logoutButton } = ui.elements;
|
const { loginForm, registerForm, logoutButton } = ui.elements;
|
||||||
|
let APP_BASE_PATH = '';
|
||||||
|
|
||||||
console.log('[Auth.js DOM Check] loginForm in initAuth:', loginForm); // <--- ДОБАВЛЕНО
|
console.log('[Auth.js DOM Check] loginForm in initAuth:', loginForm); // <--- ДОБАВЛЕНО
|
||||||
console.log('[Auth.js DOM Check] registerForm in initAuth:', registerForm); // <--- ДОБАВЛЕНО
|
console.log('[Auth.js DOM Check] registerForm in initAuth:', registerForm); // <--- ДОБАВЛЕНО
|
||||||
console.log('[Auth.js DOM Check] logoutButton in initAuth:', logoutButton); // <--- ДОБАВЛЕНО
|
console.log('[Auth.js DOM Check] logoutButton in initAuth:', logoutButton); // <--- ДОБАВЛЕНО
|
||||||
|
if (window.location.pathname.startsWith('/battleclub')) {
|
||||||
const getApiUrl = (path) => `${window.location.origin}${path}`;
|
APP_BASE_PATH = '/battleclub';
|
||||||
|
}
|
||||||
|
const getApiUrl = (path) => `${window.location.origin}${APP_BASE_PATH}${path}`;
|
||||||
console.log('[Auth.js] API URLs will be relative to:', window.location.origin); // <--- ДОБАВЛЕНО
|
console.log('[Auth.js] API URLs will be relative to:', window.location.origin); // <--- ДОБАВЛЕНО
|
||||||
|
|
||||||
const JWT_TOKEN_KEY = 'jwtToken';
|
const JWT_TOKEN_KEY = 'jwtToken';
|
||||||
|
@ -218,7 +218,7 @@ export function initGameplay(dependencies) {
|
|||||||
handleGameDataReceived(data, 'gameStarted');
|
handleGameDataReceived(data, 'gameStarted');
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('gameState', (data) => {
|
socket.on('gameState', (data) => { // Это событие было добавлено для поддержки reconnect из старого GameInstance
|
||||||
handleGameDataReceived(data, 'gameState (reconnect)');
|
handleGameDataReceived(data, 'gameState (reconnect)');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -246,10 +246,10 @@ export function initGameplay(dependencies) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[CLIENT ${username}] gameStateUpdate - Clearing game status message as game is active.`);
|
console.log(`[CLIENT ${username}] gameStateUpdate - Clearing game status message as game is active.`);
|
||||||
ui.setGameStatusMessage("");
|
ui.setGameStatusMessage(""); // Очищаем статус, если игра активна
|
||||||
|
|
||||||
} else if (clientState.currentGameState && clientState.currentGameState.isGameOver) {
|
} else if (clientState.currentGameState && clientState.currentGameState.isGameOver) {
|
||||||
disableGameControls();
|
disableGameControls(); // Отключаем управление, если игра закончилась этим обновлением
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -270,34 +270,35 @@ export function initGameplay(dependencies) {
|
|||||||
|
|
||||||
socket.on('gameOver', (data) => {
|
socket.on('gameOver', (data) => {
|
||||||
if (!clientState.isLoggedIn || !clientState.currentGameId || !window.GAME_CONFIG) {
|
if (!clientState.isLoggedIn || !clientState.currentGameId || !window.GAME_CONFIG) {
|
||||||
|
// Если нет ID игры, но залогинен, возможно, стоит запросить состояние
|
||||||
if (!clientState.currentGameId && clientState.isLoggedIn) socket.emit('requestGameState');
|
if (!clientState.currentGameId && clientState.isLoggedIn) socket.emit('requestGameState');
|
||||||
else if (!clientState.isLoggedIn) ui.showAuthScreen();
|
else if (!clientState.isLoggedIn) ui.showAuthScreen(); // Если не залогинен, показать экран входа
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const username = clientState.loggedInUsername || 'N/A';
|
const username = clientState.loggedInUsername || 'N/A';
|
||||||
console.log(`[CLIENT ${username}] Event: gameOver.`);
|
console.log(`[CLIENT ${username}] Event: gameOver.`);
|
||||||
|
|
||||||
const playerWon = data.winnerId === clientState.myPlayerId;
|
const playerWon = data.winnerId === clientState.myPlayerId;
|
||||||
clientState.currentGameState = data.finalGameState;
|
clientState.currentGameState = data.finalGameState; // Обновляем состояние последним полученным
|
||||||
clientState.isInGame = false;
|
clientState.isInGame = false; // Игра точно закончена
|
||||||
|
|
||||||
ui.updateGlobalWindowVariablesForUI();
|
ui.updateGlobalWindowVariablesForUI(); // Обновляем глобальные переменные для ui.js
|
||||||
|
|
||||||
if (window.gameUI?.updateUI) requestAnimationFrame(() => window.gameUI.updateUI());
|
if (window.gameUI?.updateUI) requestAnimationFrame(() => window.gameUI.updateUI()); // Обновляем UI один раз
|
||||||
if (window.gameUI?.addToLog && data.log) {
|
if (window.gameUI?.addToLog && data.log) {
|
||||||
data.log.forEach(log => window.gameUI.addToLog(log.message, log.type));
|
data.log.forEach(log => window.gameUI.addToLog(log.message, log.type));
|
||||||
}
|
}
|
||||||
if (window.gameUI?.showGameOver) {
|
if (window.gameUI?.showGameOver) {
|
||||||
const oppKey = clientState.opponentBaseStatsServer?.characterKey;
|
const oppKey = clientState.opponentBaseStatsServer?.characterKey; // Используем сохраненные данные оппонента
|
||||||
window.gameUI.showGameOver(playerWon, data.reason, oppKey, data);
|
window.gameUI.showGameOver(playerWon, data.reason, oppKey, data);
|
||||||
}
|
}
|
||||||
if (returnToMenuButton) returnToMenuButton.disabled = false;
|
if (returnToMenuButton) returnToMenuButton.disabled = false;
|
||||||
// `ui.setGameStatusMessage` будет установлено специфичным сообщением о результате игры
|
// `ui.setGameStatusMessage` будет установлено специфичным сообщением о результате игры
|
||||||
// ui.setGameStatusMessage("Игра окончена. " + (playerWon ? "Вы победили!" : "Вы проиграли."));
|
// ui.setGameStatusMessage("Игра окончена. " + (playerWon ? "Вы победили!" : "Вы проиграли."));
|
||||||
if (window.gameUI?.updateTurnTimerDisplay) {
|
if (window.gameUI?.updateTurnTimerDisplay) {
|
||||||
window.gameUI.updateTurnTimerDisplay(null, false, clientState.currentGameState?.gameMode);
|
window.gameUI.updateTurnTimerDisplay(null, false, clientState.currentGameState?.gameMode); // Сбрасываем таймер
|
||||||
}
|
}
|
||||||
disableGameControls();
|
disableGameControls(); // Отключаем управление игрой
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('opponentDisconnected', (data) => {
|
socket.on('opponentDisconnected', (data) => {
|
||||||
@ -312,24 +313,28 @@ export function initGameplay(dependencies) {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
if (clientState.currentGameState && !clientState.currentGameState.isGameOver) {
|
if (clientState.currentGameState && !clientState.currentGameState.isGameOver) {
|
||||||
ui.setGameStatusMessage(`Противник (${name}) отключился. Ожидание...`, true);
|
ui.setGameStatusMessage(`Противник (${name}) отключился. Ожидание...`, true); // Показываем сообщение ожидания
|
||||||
disableGameControls();
|
disableGameControls(); // Отключаем управление на время ожидания
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('turnTimerUpdate', (data) => {
|
socket.on('turnTimerUpdate', (data) => {
|
||||||
|
// Проверяем, в игре ли мы и есть ли gameState, прежде чем обновлять таймер
|
||||||
if (!clientState.isInGame || !clientState.currentGameState || !window.GAME_CONFIG) {
|
if (!clientState.isInGame || !clientState.currentGameState || !window.GAME_CONFIG) {
|
||||||
|
// Если не в игре, но gameState есть (например, игра завершена, но экран еще не обновился),
|
||||||
|
// то таймер нужно сбросить/скрыть.
|
||||||
if (window.gameUI?.updateTurnTimerDisplay && clientState.currentGameState && !clientState.currentGameState.isGameOver) {
|
if (window.gameUI?.updateTurnTimerDisplay && clientState.currentGameState && !clientState.currentGameState.isGameOver) {
|
||||||
window.gameUI.updateTurnTimerDisplay(null, false, clientState.currentGameState.gameMode);
|
window.gameUI.updateTurnTimerDisplay(null, false, clientState.currentGameState.gameMode);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Если игра завершена, таймер не должен обновляться или должен быть сброшен
|
||||||
if (clientState.currentGameState.isGameOver) {
|
if (clientState.currentGameState.isGameOver) {
|
||||||
if (window.gameUI?.updateTurnTimerDisplay) {
|
if (window.gameUI?.updateTurnTimerDisplay) {
|
||||||
window.gameUI.updateTurnTimerDisplay(null, false, clientState.currentGameState.gameMode);
|
window.gameUI.updateTurnTimerDisplay(null, false, clientState.currentGameState.gameMode);
|
||||||
}
|
}
|
||||||
disableGameControls();
|
disableGameControls(); // Убедимся, что управление отключено
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,15 +349,23 @@ export function initGameplay(dependencies) {
|
|||||||
|
|
||||||
window.gameUI.updateTurnTimerDisplay(data.remainingTime, isMyActualTurn, clientState.currentGameState.gameMode);
|
window.gameUI.updateTurnTimerDisplay(data.remainingTime, isMyActualTurn, clientState.currentGameState.gameMode);
|
||||||
|
|
||||||
|
// Включаем/отключаем управление в зависимости от хода
|
||||||
if (isMyActualTurn) {
|
if (isMyActualTurn) {
|
||||||
enableGameControls();
|
enableGameControls();
|
||||||
} else {
|
} else {
|
||||||
disableGameControls();
|
disableGameControls();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Если таймер активен и игра не закончена, общее сообщение "Ожидание" должно быть снято
|
||||||
|
// (если оно не специфично для дисконнекта оппонента)
|
||||||
if (!clientState.currentGameState.isGameOver) {
|
if (!clientState.currentGameState.isGameOver) {
|
||||||
console.log(`[CLIENT ${username}] turnTimerUpdate - Clearing game status message as timer is active.`);
|
// Проверяем, не показывается ли уже сообщение о дисконнекте оппонента
|
||||||
ui.setGameStatusMessage("");
|
const statusMsgElement = document.getElementById('game-status-message');
|
||||||
|
const currentStatusText = statusMsgElement ? statusMsgElement.textContent : "";
|
||||||
|
if (!currentStatusText.toLowerCase().includes("отключился")) {
|
||||||
|
console.log(`[CLIENT ${username}] turnTimerUpdate - Clearing game status message as timer is active.`);
|
||||||
|
ui.setGameStatusMessage("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// /server/game/GameManager.js
|
// /server/game/GameManager.js
|
||||||
const { v4: uuidv4 } = require('uuid');
|
const { v4: uuidv4 } = require('uuid');
|
||||||
const GameInstance = require('./instance/GameInstance'); // Путь к GameInstance с геттерами
|
const GameInstance = require('./instance/GameInstance');
|
||||||
const dataUtils = require('../data/dataUtils');
|
const dataUtils = require('../data/dataUtils');
|
||||||
const GAME_CONFIG = require('../core/config');
|
const GAME_CONFIG = require('../core/config');
|
||||||
|
|
||||||
@ -13,46 +13,66 @@ class GameManager {
|
|||||||
console.log("[GameManager] Initialized.");
|
console.log("[GameManager] Initialized.");
|
||||||
}
|
}
|
||||||
|
|
||||||
_removePreviousPendingGames(currentSocketId, identifier, excludeGameId = null) {
|
// Модифицированная функция: теперь она просто удаляет ожидающую игру пользователя, если находит.
|
||||||
console.log(`[GameManager._removePreviousPendingGames] User: ${identifier}, Socket: ${currentSocketId}, Exclude: ${excludeGameId}`);
|
// excludeGameId здесь больше не так критичен, так как логика вызова изменится.
|
||||||
|
_cleanupPreviousPendingGameForUser(identifier, reasonSuffix = 'unknown_cleanup_reason') {
|
||||||
const oldPendingGameId = this.userIdentifierToGameId[identifier];
|
const oldPendingGameId = this.userIdentifierToGameId[identifier];
|
||||||
|
if (oldPendingGameId && this.games[oldPendingGameId]) {
|
||||||
if (oldPendingGameId && oldPendingGameId !== excludeGameId && this.games[oldPendingGameId]) {
|
|
||||||
const gameToRemove = this.games[oldPendingGameId];
|
const gameToRemove = this.games[oldPendingGameId];
|
||||||
// Используем gameToRemove.playerCount (через геттер)
|
|
||||||
if (gameToRemove.mode === 'pvp' &&
|
if (gameToRemove.mode === 'pvp' &&
|
||||||
gameToRemove.playerCount === 1 &&
|
gameToRemove.playerCount === 1 && // Убеждаемся, что это именно ожидающая игра с одним игроком
|
||||||
gameToRemove.ownerIdentifier === identifier &&
|
gameToRemove.ownerIdentifier === identifier && // И этот игрок - владелец
|
||||||
this.pendingPvPGames.includes(oldPendingGameId)) {
|
this.pendingPvPGames.includes(oldPendingGameId)) {
|
||||||
console.log(`[GameManager._removePreviousPendingGames] User ${identifier} creating/joining new. Removing previous pending PvP game: ${oldPendingGameId}`);
|
console.log(`[GameManager._cleanupPreviousPendingGameForUser] User ${identifier} performing new action. Removing previous pending PvP game: ${oldPendingGameId}. Reason: ${reasonSuffix}`);
|
||||||
this._cleanupGame(oldPendingGameId, 'owner_action_removed_pending_game');
|
this._cleanupGame(oldPendingGameId, `owner_action_removed_pending_game_${reasonSuffix}`);
|
||||||
|
// _cleanupGame должен удалить запись из userIdentifierToGameId
|
||||||
|
return true; // Успешно очистили
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false; // Нечего было очищать или условия не совпали
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
createGame(socket, mode = 'ai', chosenCharacterKey = null, identifier) {
|
createGame(socket, mode = 'ai', chosenCharacterKey = null, identifier) {
|
||||||
console.log(`[GameManager.createGame] User: ${identifier} (Socket: ${socket.id}), Mode: ${mode}, Char: ${chosenCharacterKey || 'Default'}`);
|
console.log(`[GameManager.createGame] User: ${identifier} (Socket: ${socket.id}), Mode: ${mode}, Char: ${chosenCharacterKey || 'Default'}`);
|
||||||
|
|
||||||
|
// 1. Проверить, не находится ли пользователь уже в ЗАВЕРШЕННОЙ, но не очищенной игре.
|
||||||
const existingGameId = this.userIdentifierToGameId[identifier];
|
const existingGameId = this.userIdentifierToGameId[identifier];
|
||||||
if (existingGameId && this.games[existingGameId]) {
|
if (existingGameId && this.games[existingGameId]) {
|
||||||
const existingGame = this.games[existingGameId];
|
const existingGame = this.games[existingGameId];
|
||||||
// Используем existingGame.playerCount (через геттер)
|
if (existingGame.gameState && existingGame.gameState.isGameOver) {
|
||||||
console.warn(`[GameManager.createGame] User ${identifier} already in game ${existingGameId}. Mode: ${existingGame.mode}, Players: ${existingGame.playerCount}, Owner: ${existingGame.ownerIdentifier}, GameOver: ${existingGame.gameState?.isGameOver}`);
|
console.warn(`[GameManager.createGame] User ${identifier} was in a finished game ${existingGameId}. Cleaning it up.`);
|
||||||
|
this._cleanupGame(existingGameId, `stale_finished_on_create_${identifier}`);
|
||||||
if (existingGame.gameState && !existingGame.gameState.isGameOver) {
|
// existingGameId в userIdentifierToGameId[identifier] должен был удалиться
|
||||||
// Используем existingGame.playerCount (через геттер)
|
} else if (existingGame.mode === mode && // Если это та же самая игра, к которой он пытается "пересоздать"
|
||||||
if (existingGame.mode === 'pvp' && existingGame.playerCount === 1 && existingGame.ownerIdentifier === identifier) {
|
((mode === 'ai' && existingGame.ownerIdentifier === identifier) ||
|
||||||
socket.emit('gameError', { message: 'Вы уже создали PvP игру и ожидаете оппонента.' });
|
(mode === 'pvp' && existingGame.ownerIdentifier === identifier && existingGame.playerCount === 1) ))
|
||||||
} else {
|
{
|
||||||
socket.emit('gameError', { message: 'Вы уже находитесь в активной игре.' });
|
console.warn(`[GameManager.createGame] User ${identifier} trying to recreate an existing identical game ${existingGameId}. Sending current state.`);
|
||||||
}
|
socket.emit('gameError', { message: mode === 'pvp' ? 'Вы уже создали PvP игру и ожидаете оппонента.' : 'Вы уже в игре с AI.' });
|
||||||
|
this.handleRequestGameState(socket, identifier);
|
||||||
|
return;
|
||||||
|
} else if (existingGame.mode !== mode || existingGame.ownerIdentifier !== identifier) {
|
||||||
|
// Если он в другой активной игре (не своей ожидающей)
|
||||||
|
socket.emit('gameError', { message: 'Вы уже находитесь в другой активной игре.' });
|
||||||
this.handleRequestGameState(socket, identifier);
|
this.handleRequestGameState(socket, identifier);
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
this._cleanupGame(existingGameId, `stale_finished_on_create_${identifier}`);
|
|
||||||
}
|
}
|
||||||
|
// Если это его собственная ожидающая PvP игра, мы ее удалим ниже.
|
||||||
}
|
}
|
||||||
this._removePreviousPendingGames(socket.id, identifier);
|
|
||||||
|
// 2. Удалить предыдущую ОЖИДАЮЩУЮ PvP игру этого пользователя, если он создает новую любую игру.
|
||||||
|
this._cleanupPreviousPendingGameForUser(identifier, `creating_new_game_mode_${mode}`);
|
||||||
|
|
||||||
|
// 3. Если после очистки пользователь все еще привязан к какой-то *другой* активной игре (не той, что только что очистили)
|
||||||
|
// Это может случиться, если _cleanupPreviousPendingGameForUser не нашла ожидающую, но он в другой игре.
|
||||||
|
const stillExistingGameId = this.userIdentifierToGameId[identifier];
|
||||||
|
if (stillExistingGameId && this.games[stillExistingGameId] && !this.games[stillExistingGameId].gameState?.isGameOver) {
|
||||||
|
socket.emit('gameError', { message: 'Вы уже находитесь в активной игре.' });
|
||||||
|
this.handleRequestGameState(socket, identifier);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const gameId = uuidv4();
|
const gameId = uuidv4();
|
||||||
console.log(`[GameManager.createGame] New GameID: ${gameId}`);
|
console.log(`[GameManager.createGame] New GameID: ${gameId}`);
|
||||||
@ -63,14 +83,13 @@ class GameManager {
|
|||||||
|
|
||||||
if (game.addPlayer(socket, charKeyForPlayer, identifier)) {
|
if (game.addPlayer(socket, charKeyForPlayer, identifier)) {
|
||||||
this.userIdentifierToGameId[identifier] = gameId;
|
this.userIdentifierToGameId[identifier] = gameId;
|
||||||
// Получаем роль и актуальный ключ из GameInstance через геттер game.players
|
|
||||||
const playerInfo = Object.values(game.players).find(p => p.identifier === identifier);
|
const playerInfo = Object.values(game.players).find(p => p.identifier === identifier);
|
||||||
const assignedPlayerId = playerInfo?.id;
|
const assignedPlayerId = playerInfo?.id;
|
||||||
const actualCharacterKey = playerInfo?.chosenCharacterKey;
|
const actualCharacterKey = playerInfo?.chosenCharacterKey;
|
||||||
|
|
||||||
if (!assignedPlayerId || !actualCharacterKey) {
|
if (!assignedPlayerId || !actualCharacterKey) {
|
||||||
console.error(`[GameManager.createGame] CRITICAL: Failed to get player role/charKey after addPlayer for ${identifier} in game ${gameId}. Cleaning up.`);
|
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');
|
this._cleanupGame(gameId, 'player_info_missing_after_add_on_create');
|
||||||
socket.emit('gameError', { message: 'Ошибка сервера при создании роли в игре.' });
|
socket.emit('gameError', { message: 'Ошибка сервера при создании роли в игре.' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -90,69 +109,98 @@ class GameManager {
|
|||||||
this._cleanupGame(gameId, 'init_fail_ai_create_gm');
|
this._cleanupGame(gameId, 'init_fail_ai_create_gm');
|
||||||
}
|
}
|
||||||
} else if (mode === 'pvp') {
|
} else if (mode === 'pvp') {
|
||||||
game.initializeGame();
|
if (game.initializeGame()) { // Для PvP инициализируем даже с одним игроком
|
||||||
if (!this.pendingPvPGames.includes(gameId)) {
|
if (!this.pendingPvPGames.includes(gameId)) {
|
||||||
this.pendingPvPGames.push(gameId);
|
this.pendingPvPGames.push(gameId);
|
||||||
|
}
|
||||||
|
socket.emit('waitingForOpponent');
|
||||||
|
this.broadcastAvailablePvPGames();
|
||||||
|
} else {
|
||||||
|
this._cleanupGame(gameId, 'init_fail_pvp_create_gm_single_player');
|
||||||
}
|
}
|
||||||
socket.emit('waitingForOpponent');
|
|
||||||
this.broadcastAvailablePvPGames();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error(`[GameManager.createGame] game.addPlayer (instance method) failed for ${identifier} in ${gameId}. Cleaning up.`);
|
console.error(`[GameManager.createGame] game.addPlayer failed for ${identifier} in ${gameId}. Cleaning up.`);
|
||||||
this._cleanupGame(gameId, 'player_add_failed_in_instance_gm');
|
this._cleanupGame(gameId, 'player_add_failed_in_instance_gm_on_create');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
joinGame(socket, gameIdToJoin, identifier, chosenCharacterKey = null) {
|
joinGame(socket, gameIdToJoin, identifier, chosenCharacterKey = null) {
|
||||||
console.log(`[GameManager.joinGame] User: ${identifier} (Socket: ${socket.id}) attempts to join ${gameIdToJoin} with char ${chosenCharacterKey || 'Default'}`);
|
console.log(`[GameManager.joinGame] User: ${identifier} (Socket: ${socket.id}) attempts to join ${gameIdToJoin} with char ${chosenCharacterKey || 'Default'}`);
|
||||||
const game = this.games[gameIdToJoin];
|
const gameToJoin = this.games[gameIdToJoin];
|
||||||
|
|
||||||
if (!game) { socket.emit('gameError', { message: 'Игра с таким ID не найдена.' }); return; }
|
if (!gameToJoin) { socket.emit('gameError', { message: 'Игра с таким ID не найдена.' }); return; }
|
||||||
if (game.gameState?.isGameOver) { socket.emit('gameError', { message: 'Эта игра уже завершена.' }); this._cleanupGame(gameIdToJoin, `attempt_join_finished_${identifier}`); return; }
|
if (gameToJoin.gameState?.isGameOver) { socket.emit('gameError', { message: 'Эта игра уже завершена.' }); this._cleanupGame(gameIdToJoin, `attempt_join_finished_game_${identifier}`); return; }
|
||||||
if (game.mode !== 'pvp') { socket.emit('gameError', { message: 'К этой игре нельзя присоединиться (не PvP).' }); return; }
|
if (gameToJoin.mode !== 'pvp') { socket.emit('gameError', { message: 'К этой игре нельзя присоединиться (не PvP).' }); return; }
|
||||||
|
|
||||||
// Используем геттер game.players
|
const playerInfoInTargetGame = Object.values(gameToJoin.players).find(p => p.identifier === identifier);
|
||||||
const playerInfoInGame = Object.values(game.players).find(p => p.identifier === identifier);
|
if (gameToJoin.playerCount >= 2 && !playerInfoInTargetGame?.isTemporarilyDisconnected) {
|
||||||
// Используем game.playerCount (через геттер)
|
|
||||||
if (game.playerCount >= 2 && !playerInfoInGame?.isTemporarilyDisconnected) {
|
|
||||||
socket.emit('gameError', { message: 'Эта PvP игра уже заполнена.' }); return;
|
socket.emit('gameError', { message: 'Эта PvP игра уже заполнена.' }); return;
|
||||||
}
|
}
|
||||||
if (game.ownerIdentifier === identifier && !playerInfoInGame?.isTemporarilyDisconnected) {
|
// Нельзя присоединиться к своей же игре, если ты ее владелец и не отключен временно
|
||||||
socket.emit('gameError', { message: 'Вы не можете присоединиться к своей же ожидающей игре как новый игрок.' }); this.handleRequestGameState(socket, identifier); return;
|
if (gameToJoin.ownerIdentifier === identifier && !playerInfoInTargetGame?.isTemporarilyDisconnected) {
|
||||||
|
// Это может быть ситуация, когда он уже в этой игре (например, обновил страницу и пытается "присоединиться" к своей же)
|
||||||
|
// handleRequestGameState должен корректно обработать реконнект
|
||||||
|
console.warn(`[GameManager.joinGame] User ${identifier} trying to join their own game ${gameIdToJoin} as a new player. Treating as reconnect request.`);
|
||||||
|
this.handleRequestGameState(socket, identifier);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingGameIdOfUser = this.userIdentifierToGameId[identifier];
|
// 1. Проверить, не находится ли пользователь уже в ЗАВЕРШЕННОЙ, но не очищенной игре.
|
||||||
if (existingGameIdOfUser && existingGameIdOfUser !== gameIdToJoin) {
|
const currentActiveGameId = this.userIdentifierToGameId[identifier];
|
||||||
const otherGame = this.games[existingGameIdOfUser];
|
if (currentActiveGameId && this.games[currentActiveGameId] && this.games[currentActiveGameId].gameState?.isGameOver) {
|
||||||
if (otherGame && !otherGame.gameState?.isGameOver) { socket.emit('gameError', { message: 'Вы уже в другой активной игре.' }); this.handleRequestGameState(socket, identifier); return; }
|
console.warn(`[GameManager.joinGame] User ${identifier} was in a finished game ${currentActiveGameId} while trying to join ${gameIdToJoin}. Cleaning old one.`);
|
||||||
else if (otherGame?.gameState?.isGameOver) this._cleanupGame(existingGameIdOfUser, `stale_finished_on_join_${identifier}`);
|
this._cleanupGame(currentActiveGameId, `stale_finished_on_join_attempt_${identifier}`);
|
||||||
}
|
}
|
||||||
this._removePreviousPendingGames(socket.id, identifier, gameIdToJoin);
|
|
||||||
|
// 2. Если пользователь УЖЕ ПРИВЯЗАН к какой-то ДРУГОЙ АКТИВНОЙ игре (не той, к которой пытается присоединиться),
|
||||||
|
// и это НЕ его собственная ожидающая PvP игра, то отказать.
|
||||||
|
// Если это ЕГО ОЖИДАЮЩАЯ PvP игра, то ее нужно удалить.
|
||||||
|
if (currentActiveGameId && currentActiveGameId !== gameIdToJoin && this.games[currentActiveGameId] && !this.games[currentActiveGameId].gameState?.isGameOver) {
|
||||||
|
const usersCurrentGame = this.games[currentActiveGameId];
|
||||||
|
if (usersCurrentGame.mode === 'pvp' &&
|
||||||
|
usersCurrentGame.playerCount === 1 &&
|
||||||
|
usersCurrentGame.ownerIdentifier === identifier &&
|
||||||
|
this.pendingPvPGames.includes(currentActiveGameId)) {
|
||||||
|
console.log(`[GameManager.joinGame] User ${identifier} is owner of pending game ${currentActiveGameId}, but wants to join ${gameIdToJoin}. Cleaning up old game.`);
|
||||||
|
this._cleanupPreviousPendingGameForUser(identifier, `joining_another_game_${gameIdToJoin}`);
|
||||||
|
} else {
|
||||||
|
// Пользователь в другой активной игре (не своей ожидающей)
|
||||||
|
socket.emit('gameError', { message: 'Вы уже находитесь в другой активной игре.' });
|
||||||
|
this.handleRequestGameState(socket, identifier); // Попытаться вернуть в ту игру
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const charKeyForJoin = chosenCharacterKey || 'elena';
|
const charKeyForJoin = chosenCharacterKey || 'elena';
|
||||||
if (game.addPlayer(socket, charKeyForJoin, identifier)) {
|
if (gameToJoin.addPlayer(socket, charKeyForJoin, identifier)) {
|
||||||
this.userIdentifierToGameId[identifier] = gameIdToJoin;
|
this.userIdentifierToGameId[identifier] = gameIdToJoin;
|
||||||
// Используем геттер game.players
|
const joinedPlayerInfo = Object.values(gameToJoin.players).find(p => p.identifier === identifier);
|
||||||
const joinedPlayerInfo = Object.values(game.players).find(p => p.identifier === identifier);
|
|
||||||
|
|
||||||
if (!joinedPlayerInfo || !joinedPlayerInfo.id || !joinedPlayerInfo.chosenCharacterKey) {
|
if (!joinedPlayerInfo || !joinedPlayerInfo.id || !joinedPlayerInfo.chosenCharacterKey) {
|
||||||
console.error(`[GameManager.joinGame] CRITICAL: Failed to get player role/charKey after addPlayer for ${identifier} joining ${gameIdToJoin}.`);
|
console.error(`[GameManager.joinGame] CRITICAL: Failed to get player role/charKey after addPlayer for ${identifier} joining ${gameIdToJoin}.`);
|
||||||
|
// Здесь важно НЕ удалять gameToJoin, так как в ней мог быть первый игрок.
|
||||||
|
// addPlayer должен был вернуть false и не изменить playerCount игры, если что-то пошло не так критично.
|
||||||
|
// Если addPlayer вернул true, но инфо нет, это проблема в addPlayer или GameInstance.
|
||||||
socket.emit('gameError', { message: 'Ошибка сервера при назначении роли в игре.' });
|
socket.emit('gameError', { message: 'Ошибка сервера при назначении роли в игре.' });
|
||||||
|
// Возможно, стоит удалить игрока из userIdentifierToGameId, если он не смог корректно добавиться
|
||||||
|
if (this.userIdentifierToGameId[identifier] === gameIdToJoin) delete this.userIdentifierToGameId[identifier];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log(`[GameManager.joinGame] Player ${identifier} added/reconnected to ${gameIdToJoin} as ${joinedPlayerInfo.id}.`);
|
console.log(`[GameManager.joinGame] Player ${identifier} added/reconnected to ${gameIdToJoin} as ${joinedPlayerInfo.id}.`);
|
||||||
socket.emit('gameCreated', {
|
socket.emit('gameCreated', { // Используем gameCreated для консистентности, т.к. клиент ожидает это для установки ID игры
|
||||||
gameId: gameIdToJoin,
|
gameId: gameIdToJoin,
|
||||||
mode: game.mode,
|
mode: gameToJoin.mode,
|
||||||
yourPlayerId: joinedPlayerInfo.id,
|
yourPlayerId: joinedPlayerInfo.id,
|
||||||
chosenCharacterKey: joinedPlayerInfo.chosenCharacterKey
|
chosenCharacterKey: joinedPlayerInfo.chosenCharacterKey
|
||||||
});
|
});
|
||||||
|
|
||||||
// Используем game.playerCount (через геттер)
|
if (gameToJoin.playerCount === 2) {
|
||||||
if (game.playerCount === 2) {
|
|
||||||
console.log(`[GameManager.joinGame] Game ${gameIdToJoin} is now full. Initializing and starting.`);
|
console.log(`[GameManager.joinGame] Game ${gameIdToJoin} is now full. Initializing and starting.`);
|
||||||
if (game.initializeGame()) {
|
// Убедимся, что игра еще раз инициализируется с обоими игроками, если нужно
|
||||||
game.startGame();
|
if (gameToJoin.initializeGame()) {
|
||||||
|
gameToJoin.startGame();
|
||||||
} else {
|
} else {
|
||||||
this._cleanupGame(gameIdToJoin, 'full_init_fail_pvp_join_gm'); return;
|
this._cleanupGame(gameIdToJoin, 'full_init_fail_pvp_join_gm'); return;
|
||||||
}
|
}
|
||||||
@ -160,40 +208,59 @@ class GameManager {
|
|||||||
if (idx > -1) this.pendingPvPGames.splice(idx, 1);
|
if (idx > -1) this.pendingPvPGames.splice(idx, 1);
|
||||||
this.broadcastAvailablePvPGames();
|
this.broadcastAvailablePvPGames();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// addPlayer вернул false, GameInstance должен был отправить причину через gameError
|
||||||
|
console.warn(`[GameManager.joinGame] gameToJoin.addPlayer returned false for user ${identifier} in game ${gameIdToJoin}.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
findAndJoinRandomPvPGame(socket, chosenCharacterKeyForCreation = 'elena', identifier) {
|
findAndJoinRandomPvPGame(socket, chosenCharacterKeyForCreation = 'elena', identifier) {
|
||||||
console.log(`[GameManager.findRandomPvPGame] User: ${identifier} (Socket: ${socket.id}), CharForCreation: ${chosenCharacterKeyForCreation}`);
|
console.log(`[GameManager.findRandomPvPGame] User: ${identifier} (Socket: ${socket.id}), CharForCreation: ${chosenCharacterKeyForCreation}`);
|
||||||
|
|
||||||
|
// 1. Проверить, не находится ли пользователь уже в ЗАВЕРШЕННОЙ, но не очищенной игре.
|
||||||
const existingGameId = this.userIdentifierToGameId[identifier];
|
const existingGameId = this.userIdentifierToGameId[identifier];
|
||||||
if (existingGameId && this.games[existingGameId]) {
|
if (existingGameId && this.games[existingGameId]) {
|
||||||
const existingGame = this.games[existingGameId];
|
const existingGame = this.games[existingGameId];
|
||||||
if (!existingGame.gameState?.isGameOver) {
|
if (existingGame.gameState && existingGame.gameState.isGameOver) {
|
||||||
|
this._cleanupGame(existingGameId, `stale_finished_on_find_random_${identifier}`);
|
||||||
|
} else {
|
||||||
|
// Если он уже в активной игре (своей ожидающей или другой)
|
||||||
socket.emit('gameError', { message: 'Вы уже в активной или ожидающей игре.' });
|
socket.emit('gameError', { message: 'Вы уже в активной или ожидающей игре.' });
|
||||||
this.handleRequestGameState(socket, identifier); return;
|
this.handleRequestGameState(socket, identifier); return;
|
||||||
} else {
|
|
||||||
this._cleanupGame(existingGameId, `stale_finished_on_find_random_${identifier}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._removePreviousPendingGames(socket.id, identifier);
|
|
||||||
|
// 2. Удалить предыдущую ОЖИДАЮЩУЮ PvP игру этого пользователя, если он ищет новую.
|
||||||
|
this._cleanupPreviousPendingGameForUser(identifier, `finding_random_game`);
|
||||||
|
|
||||||
|
// 3. Если после очистки пользователь все еще привязан к какой-то *другой* активной игре
|
||||||
|
const stillExistingGameId = this.userIdentifierToGameId[identifier];
|
||||||
|
if (stillExistingGameId && this.games[stillExistingGameId] && !this.games[stillExistingGameId].gameState?.isGameOver) {
|
||||||
|
socket.emit('gameError', { message: 'Вы уже находитесь в активной игре.' });
|
||||||
|
this.handleRequestGameState(socket, identifier);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
let gameIdToJoin = null;
|
let gameIdToJoin = null;
|
||||||
|
// Итерируем копию массива, чтобы избежать проблем при удалении элементов из pendingPvPGames внутри цикла
|
||||||
for (const id of [...this.pendingPvPGames]) {
|
for (const id of [...this.pendingPvPGames]) {
|
||||||
const pendingGame = this.games[id];
|
const pendingGame = this.games[id];
|
||||||
// Используем pendingGame.playerCount (через геттер)
|
|
||||||
if (pendingGame && pendingGame.mode === 'pvp' &&
|
if (pendingGame && pendingGame.mode === 'pvp' &&
|
||||||
pendingGame.playerCount === 1 &&
|
pendingGame.playerCount === 1 &&
|
||||||
pendingGame.ownerIdentifier !== identifier &&
|
pendingGame.ownerIdentifier !== identifier && // Не присоединяться к своей же игре через "случайный поиск"
|
||||||
!pendingGame.gameState?.isGameOver) {
|
!pendingGame.gameState?.isGameOver) {
|
||||||
gameIdToJoin = id; break;
|
gameIdToJoin = id; break;
|
||||||
} else if (pendingGame?.gameState?.isGameOver) {
|
} else if (!pendingGame || pendingGame.gameState?.isGameOver) { // Очистка "мертвых" ожидающих игр
|
||||||
|
// Это может случиться, если игра была удалена, но ID остался в pendingPvPGames
|
||||||
|
console.warn(`[GameManager.findRandomPvPGame] Found stale/finished pending game ${id}. Cleaning up.`);
|
||||||
this._cleanupGame(id, `stale_finished_pending_on_find_random`);
|
this._cleanupGame(id, `stale_finished_pending_on_find_random`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gameIdToJoin) {
|
if (gameIdToJoin) {
|
||||||
console.log(`[GameManager.findRandomPvPGame] Found pending game ${gameIdToJoin} for ${identifier}. Joining...`);
|
console.log(`[GameManager.findRandomPvPGame] Found pending game ${gameIdToJoin} for ${identifier}. Joining...`);
|
||||||
const randomJoinCharKey = ['elena', 'almagest', 'balard'][Math.floor(Math.random() * 3)];
|
const randomJoinCharKey = ['elena', 'almagest', 'balard'][Math.floor(Math.random() * 3)]; // TODO: Сделать выбор персонажа более умным
|
||||||
this.joinGame(socket, gameIdToJoin, identifier, randomJoinCharKey);
|
this.joinGame(socket, gameIdToJoin, identifier, randomJoinCharKey);
|
||||||
} else {
|
} else {
|
||||||
console.log(`[GameManager.findRandomPvPGame] No suitable pending game. Creating new PvP game for ${identifier}.`);
|
console.log(`[GameManager.findRandomPvPGame] No suitable pending game. Creating new PvP game for ${identifier}.`);
|
||||||
@ -203,20 +270,23 @@ class GameManager {
|
|||||||
|
|
||||||
handlePlayerAction(identifier, actionData) {
|
handlePlayerAction(identifier, actionData) {
|
||||||
const gameId = this.userIdentifierToGameId[identifier];
|
const gameId = this.userIdentifierToGameId[identifier];
|
||||||
console.log(`[GameManager.handlePlayerAction] User: ${identifier}, Action: ${actionData?.actionType}, GameID: ${gameId}`);
|
// console.log(`[GameManager.handlePlayerAction] User: ${identifier}, Action: ${actionData?.actionType}, GameID: ${gameId}`);
|
||||||
const game = this.games[gameId];
|
const game = this.games[gameId];
|
||||||
if (game) {
|
if (game) {
|
||||||
if (game.gameState?.isGameOver) {
|
if (game.gameState?.isGameOver) {
|
||||||
// Используем геттер game.players
|
|
||||||
const playerSocket = Object.values(game.players).find(p => p.identifier === identifier)?.socket;
|
const playerSocket = Object.values(game.players).find(p => p.identifier === identifier)?.socket;
|
||||||
if (playerSocket) this.handleRequestGameState(playerSocket, identifier);
|
if (playerSocket) {
|
||||||
|
console.warn(`[GameManager.handlePlayerAction] Action from ${identifier} for game ${gameId}, but game is over. Requesting state.`);
|
||||||
|
this.handleRequestGameState(playerSocket, identifier);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
game.processPlayerAction(identifier, actionData);
|
game.processPlayerAction(identifier, actionData);
|
||||||
} else {
|
} else {
|
||||||
|
console.warn(`[GameManager.handlePlayerAction] No game found for user ${identifier} (mapped to game ${gameId}). Clearing map entry.`);
|
||||||
delete this.userIdentifierToGameId[identifier];
|
delete this.userIdentifierToGameId[identifier];
|
||||||
const clientSocket = this._findClientSocketByIdentifier(identifier);
|
const clientSocket = this._findClientSocketByIdentifier(identifier);
|
||||||
if (clientSocket) clientSocket.emit('gameNotFound', { message: 'Ваша игровая сессия не найдена при действии.' });
|
if (clientSocket) clientSocket.emit('gameNotFound', { message: 'Ваша игровая сессия не найдена при совершении действия.' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,12 +297,13 @@ class GameManager {
|
|||||||
if (game) {
|
if (game) {
|
||||||
if (game.gameState?.isGameOver) {
|
if (game.gameState?.isGameOver) {
|
||||||
console.warn(`[GameManager.handlePlayerSurrender] User ${identifier} in game ${gameId} surrender, but game ALREADY OVER.`);
|
console.warn(`[GameManager.handlePlayerSurrender] User ${identifier} in game ${gameId} surrender, but game ALREADY OVER.`);
|
||||||
if (this.userIdentifierToGameId[identifier] === gameId) delete this.userIdentifierToGameId[identifier];
|
// Не удаляем из userIdentifierToGameId здесь, _cleanupGame сделает это, если игра действительно должна быть удалена.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (typeof game.playerDidSurrender === 'function') game.playerDidSurrender(identifier);
|
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 { console.error(`[GameManager.handlePlayerSurrender] CRITICAL: GameInstance ${gameId} missing playerDidSurrender!`); this._cleanupGame(gameId, "surrender_missing_method_gm"); }
|
||||||
} else {
|
} else {
|
||||||
|
console.warn(`[GameManager.handlePlayerSurrender] No game found for user ${identifier}. Clearing map entry.`);
|
||||||
if (this.userIdentifierToGameId[identifier]) delete this.userIdentifierToGameId[identifier];
|
if (this.userIdentifierToGameId[identifier]) delete this.userIdentifierToGameId[identifier];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -251,12 +322,13 @@ class GameManager {
|
|||||||
game.playerExplicitlyLeftAiGame(identifier);
|
game.playerExplicitlyLeftAiGame(identifier);
|
||||||
} else {
|
} else {
|
||||||
console.error(`[GameManager.handleLeaveAiGame] CRITICAL: GameInstance ${gameId} missing playerExplicitlyLeftAiGame! Cleaning up directly.`);
|
console.error(`[GameManager.handleLeaveAiGame] CRITICAL: GameInstance ${gameId} missing playerExplicitlyLeftAiGame! Cleaning up directly.`);
|
||||||
this._cleanupGame(gameId, "leave_ai_missing_method");
|
this._cleanupGame(gameId, "leave_ai_missing_method_gm");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn(`[GameManager.handleLeaveAiGame] User ${identifier} sent leaveAiGame, but game ${gameId} is not AI mode (${game.mode}).`);
|
console.warn(`[GameManager.handleLeaveAiGame] User ${identifier} sent leaveAiGame, but game ${gameId} is not AI mode (${game.mode}).`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
console.warn(`[GameManager.handleLeaveAiGame] No game found for user ${identifier}. Clearing map entry.`);
|
||||||
if (this.userIdentifierToGameId[identifier]) delete this.userIdentifierToGameId[identifier];
|
if (this.userIdentifierToGameId[identifier]) delete this.userIdentifierToGameId[identifier];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -276,10 +348,11 @@ class GameManager {
|
|||||||
|
|
||||||
if (game) {
|
if (game) {
|
||||||
if (game.gameState?.isGameOver) {
|
if (game.gameState?.isGameOver) {
|
||||||
console.log(`[GameManager.handleDisconnect] Game ${gameIdFromMap} for user ${identifier} (socket ${socketId}) ALREADY OVER.`);
|
console.log(`[GameManager.handleDisconnect] Game ${gameIdFromMap} for user ${identifier} (socket ${socketId}) ALREADY OVER. Game will be cleaned up by its own logic or next relevant action.`);
|
||||||
|
// Не удаляем из userIdentifierToGameId здесь, пусть это сделает _cleanupGame по завершению игры.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Используем геттер game.players
|
|
||||||
const playerInfoInGame = Object.values(game.players).find(p => p.identifier === identifier);
|
const playerInfoInGame = Object.values(game.players).find(p => p.identifier === identifier);
|
||||||
|
|
||||||
if (playerInfoInGame && playerInfoInGame.socket?.id === socketId && !playerInfoInGame.isTemporarilyDisconnected) {
|
if (playerInfoInGame && playerInfoInGame.socket?.id === socketId && !playerInfoInGame.isTemporarilyDisconnected) {
|
||||||
@ -291,15 +364,21 @@ class GameManager {
|
|||||||
this._cleanupGame(gameIdFromMap, "missing_reconnect_logic_on_disconnect_gm");
|
this._cleanupGame(gameIdFromMap, "missing_reconnect_logic_on_disconnect_gm");
|
||||||
}
|
}
|
||||||
} else if (playerInfoInGame && playerInfoInGame.socket?.id !== socketId) {
|
} 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}.`);
|
console.log(`[GameManager.handleDisconnect] Disconnected socket ${socketId} is STALE for user ${identifier}. Active socket in game: ${playerInfoInGame.socket?.id}. No action taken by GM.`);
|
||||||
} else if (playerInfoInGame && playerInfoInGame.isTemporarilyDisconnected) {
|
} else if (playerInfoInGame && playerInfoInGame.isTemporarilyDisconnected) {
|
||||||
console.log(`[GameManager.handleDisconnect] User ${identifier} (socket ${socketId}) disconnected while already temp disconnected. Reconnect timer handles final cleanup.`);
|
console.log(`[GameManager.handleDisconnect] User ${identifier} (socket ${socketId}) disconnected while ALREADY temp disconnected. Reconnect timer in GameInstance handles final cleanup.`);
|
||||||
} else if (!playerInfoInGame) {
|
} else if (!playerInfoInGame) {
|
||||||
console.warn(`[GameManager.handleDisconnect] User ${identifier} mapped to game ${gameIdFromMap}, but not found in game's player list. Clearing map.`);
|
// Это странная ситуация: пользователь привязан к игре, но его нет в списке игроков этой игры.
|
||||||
if (this.userIdentifierToGameId[identifier] === gameIdFromMap) delete this.userIdentifierToGameId[identifier];
|
// Это может случиться, если игра была очищена, но userIdentifierToGameId не обновился.
|
||||||
|
console.warn(`[GameManager.handleDisconnect] User ${identifier} mapped to game ${gameIdFromMap}, but not found in game.players. This might indicate a stale userIdentifierToGameId entry. Clearing map for this user.`);
|
||||||
|
if (this.userIdentifierToGameId[identifier] === gameIdFromMap) {
|
||||||
|
delete this.userIdentifierToGameId[identifier];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Если игра не найдена, но пользователь был к ней привязан, значит, карта устарела.
|
||||||
if (this.userIdentifierToGameId[identifier]) {
|
if (this.userIdentifierToGameId[identifier]) {
|
||||||
|
console.warn(`[GameManager.handleDisconnect] No game instance found for gameId ${gameIdFromMap} (user ${identifier}). Clearing stale map entry.`);
|
||||||
delete this.userIdentifierToGameId[identifier];
|
delete this.userIdentifierToGameId[identifier];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -310,41 +389,61 @@ class GameManager {
|
|||||||
const game = this.games[gameId];
|
const game = this.games[gameId];
|
||||||
|
|
||||||
if (!game) {
|
if (!game) {
|
||||||
|
// Если игры уже нет в this.games, но она есть в pendingPvPGames или userIdentifierToGameId,
|
||||||
|
// нужно все равно почистить эти структуры.
|
||||||
|
console.warn(`[GameManager._cleanupGame] Game instance for ${gameId} not found in this.games. Cleaning up associated records.`);
|
||||||
const pendingIdx = this.pendingPvPGames.indexOf(gameId);
|
const pendingIdx = this.pendingPvPGames.indexOf(gameId);
|
||||||
if (pendingIdx > -1) { this.pendingPvPGames.splice(pendingIdx, 1); this.broadcastAvailablePvPGames(); }
|
if (pendingIdx > -1) {
|
||||||
for (const idKey in this.userIdentifierToGameId) { if (this.userIdentifierToGameId[idKey] === gameId) delete this.userIdentifierToGameId[idKey]; }
|
this.pendingPvPGames.splice(pendingIdx, 1);
|
||||||
return false;
|
console.log(`[GameManager._cleanupGame] Removed ${gameId} from pendingPvPGames.`);
|
||||||
|
}
|
||||||
|
for (const idKey in this.userIdentifierToGameId) {
|
||||||
|
if (this.userIdentifierToGameId[idKey] === gameId) {
|
||||||
|
delete this.userIdentifierToGameId[idKey];
|
||||||
|
console.log(`[GameManager._cleanupGame] Removed mapping for user ${idKey} to game ${gameId}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.broadcastAvailablePvPGames(); // Обновляем список, так как ожидающая игра могла быть удалена
|
||||||
|
return false; // Игры не было для основной очистки
|
||||||
}
|
}
|
||||||
// Используем game.playerCount (через геттер)
|
|
||||||
console.log(`[GameManager._cleanupGame] Cleaning up game ${game.id}. Owner: ${game.ownerIdentifier}. Reason: ${reason}. Players in game: ${game.playerCount}`);
|
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.turnTimer?.clear === 'function') game.turnTimer.clear();
|
||||||
if (typeof game.clearAllReconnectTimers === 'function') game.clearAllReconnectTimers();
|
if (typeof game.clearAllReconnectTimers === 'function') game.clearAllReconnectTimers();
|
||||||
|
|
||||||
|
// Убедимся, что игра помечена как завершенная, если она еще не была
|
||||||
if (game.gameState && !game.gameState.isGameOver) {
|
if (game.gameState && !game.gameState.isGameOver) {
|
||||||
|
console.log(`[GameManager._cleanupGame] Marking game ${game.id} as game over.`);
|
||||||
game.gameState.isGameOver = true;
|
game.gameState.isGameOver = true;
|
||||||
|
// Можно также отправить финальное событие gameOver, если это не было сделано ранее
|
||||||
|
// game.io.to(game.id).emit('gameOver', { /* ... данные ... */ });
|
||||||
}
|
}
|
||||||
|
|
||||||
let playersCleanedFromMap = 0;
|
// Очищаем всех игроков этой игры из глобальной карты userIdentifierToGameId
|
||||||
// Используем геттер game.players
|
|
||||||
Object.values(game.players).forEach(pInfo => {
|
Object.values(game.players).forEach(pInfo => {
|
||||||
if (pInfo?.identifier && this.userIdentifierToGameId[pInfo.identifier] === gameId) {
|
if (pInfo?.identifier && this.userIdentifierToGameId[pInfo.identifier] === gameId) {
|
||||||
delete this.userIdentifierToGameId[pInfo.identifier];
|
delete this.userIdentifierToGameId[pInfo.identifier];
|
||||||
playersCleanedFromMap++;
|
console.log(`[GameManager._cleanupGame] Cleared userIdentifierToGameId for player ${pInfo.identifier}.`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Используем геттер game.players
|
// Дополнительная проверка для ownerIdentifier, если он не был в game.players
|
||||||
if (game.ownerIdentifier && this.userIdentifierToGameId[game.ownerIdentifier] === gameId &&
|
if (game.ownerIdentifier && this.userIdentifierToGameId[game.ownerIdentifier] === gameId) {
|
||||||
!Object.values(game.players).some(p=>p.identifier === game.ownerIdentifier)) {
|
if (!Object.values(game.players).some(p => p.identifier === game.ownerIdentifier)) {
|
||||||
delete this.userIdentifierToGameId[game.ownerIdentifier];
|
delete this.userIdentifierToGameId[game.ownerIdentifier];
|
||||||
playersCleanedFromMap++;
|
console.log(`[GameManager._cleanupGame] Cleared userIdentifierToGameId for owner ${game.ownerIdentifier} (was not in players list).`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const pendingIdx = this.pendingPvPGames.indexOf(gameId);
|
const pendingIdx = this.pendingPvPGames.indexOf(gameId);
|
||||||
if (pendingIdx > -1) this.pendingPvPGames.splice(pendingIdx, 1);
|
if (pendingIdx > -1) {
|
||||||
|
this.pendingPvPGames.splice(pendingIdx, 1);
|
||||||
|
console.log(`[GameManager._cleanupGame] Removed ${gameId} from pendingPvPGames.`);
|
||||||
|
}
|
||||||
|
|
||||||
delete this.games[gameId];
|
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}`);
|
console.log(`[GameManager._cleanupGame] Game ${gameId} instance deleted. Games left: ${Object.keys(this.games).length}. Pending: ${this.pendingPvPGames.length}. User map size: ${Object.keys(this.userIdentifierToGameId).length}`);
|
||||||
this.broadcastAvailablePvPGames();
|
this.broadcastAvailablePvPGames(); // Обновляем список, т.к. ожидающая игра могла быть удалена
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,26 +451,33 @@ class GameManager {
|
|||||||
return this.pendingPvPGames
|
return this.pendingPvPGames
|
||||||
.map(gameId => {
|
.map(gameId => {
|
||||||
const game = this.games[gameId];
|
const game = this.games[gameId];
|
||||||
// Используем game.playerCount (через геттер)
|
// Убедимся, что игра существует и действительно ожидает
|
||||||
if (game && game.mode === 'pvp' && game.playerCount === 1 && (!game.gameState || !game.gameState.isGameOver)) {
|
if (game && game.mode === 'pvp' && game.playerCount === 1 && (!game.gameState || !game.gameState.isGameOver)) {
|
||||||
// Используем геттер game.players
|
|
||||||
const p1Entry = Object.values(game.players).find(p => p.id === GAME_CONFIG.PLAYER_ID && !p.isTemporarilyDisconnected);
|
const p1Entry = Object.values(game.players).find(p => p.id === GAME_CONFIG.PLAYER_ID && !p.isTemporarilyDisconnected);
|
||||||
let p1Username = 'Игрок';
|
let p1Username = 'Игрок';
|
||||||
let p1CharName = 'Неизвестный';
|
let p1CharName = 'Неизвестный';
|
||||||
const ownerId = game.ownerIdentifier;
|
const ownerId = game.ownerIdentifier;
|
||||||
|
|
||||||
if (p1Entry && p1Entry.socket?.userData) {
|
if (p1Entry && p1Entry.socket?.userData) { // Приоритет данным из сокета, если есть
|
||||||
p1Username = p1Entry.socket.userData.username || `User#${String(p1Entry.identifier).substring(0,4)}`;
|
p1Username = p1Entry.socket.userData.username || `User#${String(p1Entry.identifier).substring(0,4)}`;
|
||||||
const charData = dataUtils.getCharacterBaseStats(p1Entry.chosenCharacterKey);
|
const charData = dataUtils.getCharacterBaseStats(p1Entry.chosenCharacterKey);
|
||||||
p1CharName = charData?.name || p1Entry.chosenCharacterKey || 'Не выбран';
|
p1CharName = charData?.name || p1Entry.chosenCharacterKey || 'Не выбран';
|
||||||
} else if (ownerId){
|
} else if (ownerId){ // Фоллбэк на данные по ownerId
|
||||||
const ownerSocket = this._findClientSocketByIdentifier(ownerId);
|
const ownerSocket = this._findClientSocketByIdentifier(ownerId); // Попытаться найти сокет владельца
|
||||||
p1Username = ownerSocket?.userData?.username || `Owner#${String(ownerId).substring(0,4)}`;
|
p1Username = ownerSocket?.userData?.username || `Owner#${String(ownerId).substring(0,4)}`;
|
||||||
const ownerCharKey = game.playerCharacterKey;
|
const ownerCharKey = game.playerCharacterKey;
|
||||||
const charData = ownerCharKey ? dataUtils.getCharacterBaseStats(ownerCharKey) : null;
|
const charData = ownerCharKey ? dataUtils.getCharacterBaseStats(ownerCharKey) : null;
|
||||||
p1CharName = charData?.name || ownerCharKey || 'Не выбран';
|
p1CharName = charData?.name || ownerCharKey || 'Не выбран';
|
||||||
|
} else if (p1Entry) { // Если есть p1Entry, но нет userData (маловероятно для создателя)
|
||||||
|
p1Username = `User#${String(p1Entry.identifier).substring(0,4)}`;
|
||||||
|
const charData = dataUtils.getCharacterBaseStats(p1Entry.chosenCharacterKey);
|
||||||
|
p1CharName = charData?.name || p1Entry.chosenCharacterKey || 'Не выбран';
|
||||||
}
|
}
|
||||||
return { id: gameId, status: `Ожидает (${p1Username} за ${p1CharName})`, ownerIdentifier: ownerId };
|
return { id: gameId, status: `Ожидает (${p1Username} за ${p1CharName})`, ownerIdentifier: ownerId };
|
||||||
|
} else if (game && (game.playerCount !== 1 || game.gameState?.isGameOver)) {
|
||||||
|
// Если игра есть в pending, но не соответствует условиям, ее нужно оттуда удалить
|
||||||
|
console.warn(`[GameManager.getAvailablePvPGamesListForClient] Game ${gameId} is in pendingPvPGames but is not a valid pending game (players: ${game.playerCount}, over: ${game.gameState?.isGameOver}). Removing.`);
|
||||||
|
this._cleanupGame(gameId, 'invalid_pending_game_in_list'); // Это вызовет broadcastAvailablePvPGames снова, поэтому мы делаем map на копии.
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
})
|
})
|
||||||
@ -389,47 +495,67 @@ class GameManager {
|
|||||||
const game = gameIdFromMap ? this.games[gameIdFromMap] : null;
|
const game = gameIdFromMap ? this.games[gameIdFromMap] : null;
|
||||||
|
|
||||||
if (game) {
|
if (game) {
|
||||||
// Используем геттер game.players
|
|
||||||
const playerInfoInGame = Object.values(game.players).find(p => p.identifier === identifier);
|
const playerInfoInGame = Object.values(game.players).find(p => p.identifier === identifier);
|
||||||
console.log(`[GameManager.handleRequestGameState] Game ${gameIdFromMap} found. PlayerInfo: ${playerInfoInGame ? `Role: ${playerInfoInGame.id}, TempDisco: ${playerInfoInGame.isTemporarilyDisconnected}` : 'Not found in game.players'}`);
|
// console.log(`[GameManager.handleRequestGameState] Game ${gameIdFromMap} found. PlayerInfo in game.players: ${playerInfoInGame ? `Role: ${playerInfoInGame.id}, TempDisco: ${playerInfoInGame.isTemporarilyDisconnected}` : 'Not found in game.players'}`);
|
||||||
|
|
||||||
if (playerInfoInGame) {
|
if (playerInfoInGame) { // Игрок действительно является частью этой игры
|
||||||
if (game.gameState?.isGameOver) {
|
if (game.gameState?.isGameOver) {
|
||||||
socket.emit('gameNotFound', { message: 'Ваша предыдущая игра уже завершена.' });
|
socket.emit('gameNotFound', { message: 'Ваша предыдущая игра уже завершена.' });
|
||||||
if(this.userIdentifierToGameId[identifier] === gameIdFromMap) delete this.userIdentifierToGameId[identifier];
|
// _cleanupGame должна быть вызвана, когда игра фактически завершается,
|
||||||
|
// здесь мы не должны удалять из userIdentifierToGameId, если игра еще есть в this.games.
|
||||||
|
// Если игра уже очищена, то game будет null.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (typeof game.handlePlayerReconnected === 'function') {
|
if (typeof game.handlePlayerReconnected === 'function') {
|
||||||
const reconnected = game.handlePlayerReconnected(playerInfoInGame.id, socket);
|
const reconnected = game.handlePlayerReconnected(playerInfoInGame.id, socket);
|
||||||
// ... (обработка результата reconnected, если нужно)
|
// reconnected может вернуть false, если реконнект не удался по внутренней причине GameInstance
|
||||||
|
if (!reconnected) {
|
||||||
|
console.warn(`[GameManager.handleRequestGameState] game.handlePlayerReconnected for ${identifier} in ${game.id} returned false.`);
|
||||||
|
// GameInstance должен был отправить ошибку клиенту, если нужно.
|
||||||
|
// Можно рассмотреть _handleGameRecoveryError, если это критично.
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error(`[GameManager.handleRequestGameState] CRITICAL: GameInstance ${game.id} missing handlePlayerReconnected!`);
|
console.error(`[GameManager.handleRequestGameState] CRITICAL: GameInstance ${game.id} missing handlePlayerReconnected!`);
|
||||||
this._handleGameRecoveryError(socket, game.id, identifier, 'gi_missing_reconnect_method_gm');
|
this._handleGameRecoveryError(socket, game.id, identifier, 'gi_missing_reconnect_method_gm_on_request');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this._handleGameRecoveryError(socket, gameIdFromMap, identifier, 'player_not_in_gi_players_reconnect_gm');
|
// Пользователь привязан к этой игре в userIdentifierToGameId, но его нет в списке игроков game.players
|
||||||
|
// Это может означать, что он был удален из игры (например, таймаут реконнекта), но userIdentifierToGameId не очистился.
|
||||||
|
console.warn(`[GameManager.handleRequestGameState] User ${identifier} mapped to game ${gameIdFromMap}, but NOT FOUND in game.players. Cleaning map & sending gameNotFound.`);
|
||||||
|
this._handleGameRecoveryError(socket, gameIdFromMap, identifier, 'player_not_in_gi_players_but_mapped_on_request');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Игра не найдена в this.games, но могла быть в userIdentifierToGameId
|
||||||
socket.emit('gameNotFound', { message: 'Активная игровая сессия не найдена.' });
|
socket.emit('gameNotFound', { message: 'Активная игровая сессия не найдена.' });
|
||||||
if (this.userIdentifierToGameId[identifier]) delete this.userIdentifierToGameId[identifier];
|
if (this.userIdentifierToGameId[identifier]) { // Если привязка была, удаляем ее
|
||||||
|
console.warn(`[GameManager.handleRequestGameState] No game instance found for gameId ${gameIdFromMap} (user ${identifier}). Clearing stale map entry.`);
|
||||||
|
delete this.userIdentifierToGameId[identifier];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleGameRecoveryError(socket, gameId, identifier, reasonCode) {
|
_handleGameRecoveryError(socket, gameId, identifier, reasonCode) {
|
||||||
console.error(`[GameManager._handleGameRecoveryError] Error recovering game (ID: ${gameId || 'N/A'}) for user ${identifier}. Reason: ${reasonCode}.`);
|
console.error(`[GameManager._handleGameRecoveryError] Error recovering game (ID: ${gameId || 'N/A'}) for user ${identifier}. Reason: ${reasonCode}.`);
|
||||||
socket.emit('gameError', { message: 'Ошибка сервера при восстановлении состояния игры.' });
|
socket.emit('gameError', { message: 'Ошибка сервера при восстановлении состояния игры. Попробуйте войти снова.' });
|
||||||
|
|
||||||
|
// Очищаем игру, если она существует и вызвала ошибку
|
||||||
if (gameId && this.games[gameId]) {
|
if (gameId && this.games[gameId]) {
|
||||||
this._cleanupGame(gameId, `recovery_error_${reasonCode}_for_${identifier}`);
|
this._cleanupGame(gameId, `recovery_error_gm_${reasonCode}_for_${identifier}`);
|
||||||
} else if (this.userIdentifierToGameId[identifier]) {
|
} else if (this.userIdentifierToGameId[identifier]) {
|
||||||
const problematicGameId = this.userIdentifierToGameId[identifier];
|
// Если игра уже удалена, но пользователь все еще к ней привязан в карте
|
||||||
if (this.games[problematicGameId]) {
|
const problematicGameIdForUser = this.userIdentifierToGameId[identifier];
|
||||||
this._cleanupGame(problematicGameId, `recovery_error_stale_map_${identifier}_reason_${reasonCode}`);
|
if (this.games[problematicGameIdForUser]) { // Если она все же есть (маловероятно, если gameId был null)
|
||||||
} else {
|
this._cleanupGame(problematicGameIdForUser, `recovery_error_stale_map_gm_${identifier}_reason_${reasonCode}`);
|
||||||
|
} else { // Если ее нет, просто чистим карту
|
||||||
delete this.userIdentifierToGameId[identifier];
|
delete this.userIdentifierToGameId[identifier];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.userIdentifierToGameId[identifier]) delete this.userIdentifierToGameId[identifier];
|
// Если после _cleanupGame пользователь все еще привязан (маловероятно, но для гарантии)
|
||||||
socket.emit('gameNotFound', { message: 'Ваша игровая сессия была завершена из-за ошибки.' });
|
if (this.userIdentifierToGameId[identifier]) {
|
||||||
|
delete this.userIdentifierToGameId[identifier];
|
||||||
|
}
|
||||||
|
// Отправляем gameNotFound, чтобы клиент перешел на экран логина или выбора игры
|
||||||
|
socket.emit('gameNotFound', { message: 'Ваша игровая сессия была завершена из-за ошибки. Пожалуйста, войдите снова.' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user