Обработка ситуаций рекконекта. Доработка 3.
This commit is contained in:
parent
3973ac748c
commit
a8e383fb3c
@ -24,16 +24,14 @@ class PlayerConnectionHandler {
|
|||||||
|
|
||||||
if (existingPlayerByIdentifier) {
|
if (existingPlayerByIdentifier) {
|
||||||
console.warn(`[PCH ${this.gameId}] Идентификатор ${identifier} уже связан с ролью игрока ${existingPlayerByIdentifier.id} (сокет ${existingPlayerByIdentifier.socket?.id}). Обрабатывается как возможное переподключение (вызов handlePlayerReconnected).`);
|
console.warn(`[PCH ${this.gameId}] Идентификатор ${identifier} уже связан с ролью игрока ${existingPlayerByIdentifier.id} (сокет ${existingPlayerByIdentifier.socket?.id}). Обрабатывается как возможное переподключение (вызов handlePlayerReconnected).`);
|
||||||
// Делегируем handlePlayerReconnected, который разберется, новый ли это сокет, был ли игрок отключен и т.д.
|
|
||||||
return this.handlePlayerReconnected(existingPlayerByIdentifier.id, socket);
|
return this.handlePlayerReconnected(existingPlayerByIdentifier.id, socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверка на максимальное количество игроков
|
|
||||||
if (this.mode === 'pvp' && this.playerCount >= 2) {
|
if (this.mode === 'pvp' && this.playerCount >= 2) {
|
||||||
socket.emit('gameError', { message: 'Эта PvP игра уже заполнена.' });
|
socket.emit('gameError', { message: 'Эта PvP игра уже заполнена.' });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.mode === 'ai' && this.playerCount >= 1) { // В AI режиме только 1 человек
|
if (this.mode === 'ai' && this.playerCount >= 1) {
|
||||||
socket.emit('gameError', { message: 'К AI игре может присоединиться только один игрок.'});
|
socket.emit('gameError', { message: 'К AI игре может присоединиться только один игрок.'});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -52,7 +50,6 @@ class PlayerConnectionHandler {
|
|||||||
const firstPlayerInfo = Object.values(this.players).find(p => p.id === GAME_CONFIG.PLAYER_ID);
|
const firstPlayerInfo = Object.values(this.players).find(p => p.id === GAME_CONFIG.PLAYER_ID);
|
||||||
if (firstPlayerInfo && firstPlayerInfo.chosenCharacterKey === actualCharacterKey) {
|
if (firstPlayerInfo && firstPlayerInfo.chosenCharacterKey === actualCharacterKey) {
|
||||||
const allKeys = dataUtils.getAllCharacterKeys ? dataUtils.getAllCharacterKeys() : ['elena', 'almagest', 'balard'];
|
const allKeys = dataUtils.getAllCharacterKeys ? dataUtils.getAllCharacterKeys() : ['elena', 'almagest', 'balard'];
|
||||||
// Исключаем Баларда из автовыбора для второго игрока, если первый не Балард
|
|
||||||
const disallowedKeyForOpponent = firstPlayerInfo.chosenCharacterKey === 'balard' ? null : 'balard';
|
const disallowedKeyForOpponent = firstPlayerInfo.chosenCharacterKey === 'balard' ? null : 'balard';
|
||||||
const otherKey = allKeys.find(k => k !== firstPlayerInfo.chosenCharacterKey && k !== disallowedKeyForOpponent);
|
const otherKey = allKeys.find(k => k !== firstPlayerInfo.chosenCharacterKey && k !== disallowedKeyForOpponent);
|
||||||
actualCharacterKey = otherKey || (actualCharacterKey === 'elena' ? 'almagest' : 'elena');
|
actualCharacterKey = otherKey || (actualCharacterKey === 'elena' ? 'almagest' : 'elena');
|
||||||
@ -63,8 +60,6 @@ class PlayerConnectionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Удаление старой записи, если сокет для этой роли уже существует, но с другим ID
|
|
||||||
// Это может произойти при очень быстрой смене сокета до срабатывания disconnect старого.
|
|
||||||
const oldPlayerSocketEntryForRole = Object.entries(this.players).find(([sid, pInfo]) => pInfo.id === assignedPlayerId);
|
const oldPlayerSocketEntryForRole = Object.entries(this.players).find(([sid, pInfo]) => pInfo.id === assignedPlayerId);
|
||||||
if (oldPlayerSocketEntryForRole) {
|
if (oldPlayerSocketEntryForRole) {
|
||||||
const [oldSocketId, oldPlayerInfo] = oldPlayerSocketEntryForRole;
|
const [oldSocketId, oldPlayerInfo] = oldPlayerSocketEntryForRole;
|
||||||
@ -73,11 +68,10 @@ class PlayerConnectionHandler {
|
|||||||
try {
|
try {
|
||||||
if (oldPlayerInfo.socket.connected) oldPlayerInfo.socket.disconnect(true);
|
if (oldPlayerInfo.socket.connected) oldPlayerInfo.socket.disconnect(true);
|
||||||
} catch (e) { console.error(`[PCH ${this.gameId}] Ошибка при дисконнекте старого сокета в addPlayer: ${e.message}`); }
|
} catch (e) { console.error(`[PCH ${this.gameId}] Ошибка при дисконнекте старого сокета в addPlayer: ${e.message}`); }
|
||||||
delete this.players[oldSocketId]; // Удаляем по старому socket.id
|
delete this.players[oldSocketId];
|
||||||
if (this.playerSockets[assignedPlayerId] === oldPlayerInfo.socket) {
|
if (this.playerSockets[assignedPlayerId] === oldPlayerInfo.socket) {
|
||||||
delete this.playerSockets[assignedPlayerId]; // Удаляем из авторитетных, если это был он
|
delete this.playerSockets[assignedPlayerId];
|
||||||
}
|
}
|
||||||
// playerCount не уменьшаем, т.к. это замена, а не полноценный уход игрока из игры
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,11 +86,18 @@ class PlayerConnectionHandler {
|
|||||||
this.playerSockets[assignedPlayerId] = socket;
|
this.playerSockets[assignedPlayerId] = socket;
|
||||||
this.playerCount++;
|
this.playerCount++;
|
||||||
|
|
||||||
|
console.log(`[PCH ${this.gameId}] Socket ${socket.id} (identifier: ${identifier}) ATTEMPTING to join room ${this.gameId} in addPlayer.`);
|
||||||
try {
|
try {
|
||||||
socket.join(this.gameId);
|
socket.join(this.gameId);
|
||||||
console.log(`[PCH ${this.gameId}] Сокет ${socket.id} (identifier: ${identifier}) присоединен к комнате ${this.gameId} (addPlayer).`);
|
const roomData = this.io.sockets.adapter.rooms.get(this.gameId);
|
||||||
|
const socketsInRoomAfterJoin = roomData ? Array.from(roomData) : [];
|
||||||
|
const isSocketInRoom = socketsInRoomAfterJoin.includes(socket.id);
|
||||||
|
console.log(`[PCH ${this.gameId}] Socket ${socket.id} FINISHED join attempt (addPlayer). Room data type: ${typeof roomData}. Sockets in room ${this.gameId} NOW: [${socketsInRoomAfterJoin.join(', ')}]. Is newSocket (${socket.id}) in room? ${isSocketInRoom}`);
|
||||||
|
if (!isSocketInRoom) {
|
||||||
|
console.error(`[PCH ${this.gameId}] CRITICAL FAILURE (addPlayer): Socket ${socket.id} was NOT found in room ${this.gameId} immediately after join command!`);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`[PCH ${this.gameId}] КРИТИЧЕСКАЯ ОШИБКА при socket.join в addPlayer для ${identifier}: ${e.message}.`);
|
console.error(`[PCH ${this.gameId}] CRITICAL ERROR during socket.join in addPlayer for ${identifier}: ${e.message}. Stack: ${e.stack}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (assignedPlayerId === GAME_CONFIG.PLAYER_ID) this.gameInstance.setPlayerCharacterKey(actualCharacterKey);
|
if (assignedPlayerId === GAME_CONFIG.PLAYER_ID) this.gameInstance.setPlayerCharacterKey(actualCharacterKey);
|
||||||
@ -124,7 +125,7 @@ class PlayerConnectionHandler {
|
|||||||
} catch (e) { console.warn(`[PCH ${this.gameId}] Ошибка при playerInfo.socket.leave в removePlayer для ${socketId}: ${e.message}`); }
|
} catch (e) { console.warn(`[PCH ${this.gameId}] Ошибка при playerInfo.socket.leave в removePlayer для ${socketId}: ${e.message}`); }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!playerInfo.isTemporarilyDisconnected) { // Уменьшаем счетчик только если это был активный игрок
|
if (!playerInfo.isTemporarilyDisconnected) {
|
||||||
this.playerCount--;
|
this.playerCount--;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +133,7 @@ class PlayerConnectionHandler {
|
|||||||
if (this.playerSockets[playerRole]?.id === socketId) {
|
if (this.playerSockets[playerRole]?.id === socketId) {
|
||||||
delete this.playerSockets[playerRole];
|
delete this.playerSockets[playerRole];
|
||||||
}
|
}
|
||||||
this.clearReconnectTimer(playerRole); // Очищаем таймер переподключения для этой роли
|
this.clearReconnectTimer(playerRole);
|
||||||
|
|
||||||
console.log(`[PCH ${this.gameId}] Игрок ${playerIdentifier} удален. Активных игроков сейчас: ${this.playerCount}.`);
|
console.log(`[PCH ${this.gameId}] Игрок ${playerIdentifier} удален. Активных игроков сейчас: ${this.playerCount}.`);
|
||||||
this.gameInstance.handlePlayerPermanentlyLeft(playerRole, playerInfo.chosenCharacterKey, reason);
|
this.gameInstance.handlePlayerPermanentlyLeft(playerRole, playerInfo.chosenCharacterKey, reason);
|
||||||
@ -144,12 +145,10 @@ class PlayerConnectionHandler {
|
|||||||
|
|
||||||
handlePlayerPotentiallyLeft(playerIdRole, identifier, characterKey, disconnectedSocketId) {
|
handlePlayerPotentiallyLeft(playerIdRole, identifier, characterKey, disconnectedSocketId) {
|
||||||
console.log(`[PCH ${this.gameId}] handlePlayerPotentiallyLeft для роли ${playerIdRole}, id ${identifier}, char ${characterKey}, disconnectedSocketId ${disconnectedSocketId}`);
|
console.log(`[PCH ${this.gameId}] handlePlayerPotentiallyLeft для роли ${playerIdRole}, id ${identifier}, char ${characterKey}, disconnectedSocketId ${disconnectedSocketId}`);
|
||||||
// Находим запись игрока по роли и идентификатору, т.к. disconnectedSocketId может быть уже старым
|
|
||||||
const playerEntry = Object.values(this.players).find(p => p.id === playerIdRole && p.identifier === identifier);
|
const playerEntry = Object.values(this.players).find(p => p.id === playerIdRole && p.identifier === identifier);
|
||||||
|
|
||||||
if (!playerEntry || !playerEntry.socket) {
|
if (!playerEntry || !playerEntry.socket) {
|
||||||
console.warn(`[PCH ${this.gameId}] Запись игрока по роли/id (${identifier}/${playerIdRole}) не найдена, или у нее нет сокета. disconnectedSocketId: ${disconnectedSocketId}`);
|
console.warn(`[PCH ${this.gameId}] Запись игрока по роли/id (${identifier}/${playerIdRole}) не найдена, или у нее нет сокета. disconnectedSocketId: ${disconnectedSocketId}`);
|
||||||
// Если запись по disconnectedSocketId все еще существует (например, старая запись), удаляем ее.
|
|
||||||
if (this.players[disconnectedSocketId]) {
|
if (this.players[disconnectedSocketId]) {
|
||||||
console.warn(`[PCH ${this.gameId}] Найдена запись по disconnectedSocketId ${disconnectedSocketId} (без playerEntry по роли/id), удаляем ее через removePlayer.`);
|
console.warn(`[PCH ${this.gameId}] Найдена запись по disconnectedSocketId ${disconnectedSocketId} (без playerEntry по роли/id), удаляем ее через removePlayer.`);
|
||||||
this.removePlayer(disconnectedSocketId, 'stale_socket_disconnect_no_active_entry');
|
this.removePlayer(disconnectedSocketId, 'stale_socket_disconnect_no_active_entry');
|
||||||
@ -157,18 +156,14 @@ class PlayerConnectionHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если сокет, который отключился, не является текущим авторитетным сокетом для этого игрока,
|
|
||||||
// значит, игрок уже переподключился с новым сокетом. Это запоздалое событие.
|
|
||||||
if (playerEntry.socket.id !== disconnectedSocketId) {
|
if (playerEntry.socket.id !== disconnectedSocketId) {
|
||||||
console.log(`[PCH ${this.gameId}] Событие отключения для УСТАРЕВШЕГО сокета ${disconnectedSocketId} для игрока ${identifier} (Роль ${playerIdRole}). Текущий активный сокет: ${playerEntry.socket.id}. Игнорируем.`);
|
console.log(`[PCH ${this.gameId}] Событие отключения для УСТАРЕВШЕГО сокета ${disconnectedSocketId} для игрока ${identifier} (Роль ${playerIdRole}). Текущий активный сокет: ${playerEntry.socket.id}. Игнорируем.`);
|
||||||
// Удаляем запись по устаревшему disconnectedSocketId, если она еще есть
|
|
||||||
if (this.players[disconnectedSocketId]) {
|
if (this.players[disconnectedSocketId]) {
|
||||||
delete this.players[disconnectedSocketId];
|
delete this.players[disconnectedSocketId];
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Далее, playerEntry.socket.id === disconnectedSocketId (отключился текущий авторитетный сокет)
|
|
||||||
if (this.gameInstance.gameState && this.gameInstance.gameState.isGameOver) {
|
if (this.gameInstance.gameState && this.gameInstance.gameState.isGameOver) {
|
||||||
console.log(`[PCH ${this.gameId}] Игра уже завершена, не обрабатываем потенциальный выход для ${identifier}.`);
|
console.log(`[PCH ${this.gameId}] Игра уже завершена, не обрабатываем потенциальный выход для ${identifier}.`);
|
||||||
return;
|
return;
|
||||||
@ -227,16 +222,15 @@ class PlayerConnectionHandler {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
const timeoutId = setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
const timerInfo = this.reconnectTimers[playerIdRole]; // Получаем ссылку на объект таймера
|
const timerInfo = this.reconnectTimers[playerIdRole];
|
||||||
if (timerInfo) { // Проверяем, что объект еще существует
|
if (timerInfo) {
|
||||||
if (timerInfo.updateIntervalId) {
|
if (timerInfo.updateIntervalId) {
|
||||||
clearInterval(timerInfo.updateIntervalId);
|
clearInterval(timerInfo.updateIntervalId);
|
||||||
timerInfo.updateIntervalId = null;
|
timerInfo.updateIntervalId = null;
|
||||||
}
|
}
|
||||||
timerInfo.timerId = null; // Помечаем, что основной таймаут сработал или очищен
|
timerInfo.timerId = null;
|
||||||
}
|
}
|
||||||
// Удаляем всю запись о таймере для этой роли, если она еще есть
|
if (this.reconnectTimers[playerIdRole]) { // Удаляем только если все еще существует
|
||||||
if (this.reconnectTimers[playerIdRole]) {
|
|
||||||
delete this.reconnectTimers[playerIdRole];
|
delete this.reconnectTimers[playerIdRole];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,7 +239,7 @@ class PlayerConnectionHandler {
|
|||||||
console.log(`[PCH ${this.gameId}] Таймаут переподключения для ${identifier} (${playerIdRole}). Удаляем игрока.`);
|
console.log(`[PCH ${this.gameId}] Таймаут переподключения для ${identifier} (${playerIdRole}). Удаляем игрока.`);
|
||||||
this.removePlayer(stillDiscPlayer.socket.id, "reconnect_timeout");
|
this.removePlayer(stillDiscPlayer.socket.id, "reconnect_timeout");
|
||||||
} else {
|
} else {
|
||||||
console.log(`[PCH ${this.gameId}] Таймаут переподключения для ${identifier} (${playerIdRole}), но игрок уже не (или не был) isTemporarilyDisconnected. Или не найден.`);
|
console.log(`[PCH ${this.gameId}] Таймаут переподключения для ${identifier} (${playerIdRole}), но игрок уже не (или не был) isTemporarilyDisconnected, или не найден.`);
|
||||||
}
|
}
|
||||||
}, reconnectDuration);
|
}, reconnectDuration);
|
||||||
this.reconnectTimers[playerIdRole] = { timerId: timeoutId, updateIntervalId: updateInterval, startTimeMs: reconnectStartTime, durationMs: reconnectDuration };
|
this.reconnectTimers[playerIdRole] = { timerId: timeoutId, updateIntervalId: updateInterval, startTimeMs: reconnectStartTime, durationMs: reconnectDuration };
|
||||||
@ -267,34 +261,33 @@ class PlayerConnectionHandler {
|
|||||||
const oldSocket = playerEntry.socket;
|
const oldSocket = playerEntry.socket;
|
||||||
const wasTemporarilyDisconnected = playerEntry.isTemporarilyDisconnected;
|
const wasTemporarilyDisconnected = playerEntry.isTemporarilyDisconnected;
|
||||||
|
|
||||||
// Обновляем сокет в playerEntry и в this.players / this.playerSockets, если сокет новый
|
|
||||||
if (oldSocket && oldSocket.id !== newSocket.id) {
|
if (oldSocket && oldSocket.id !== newSocket.id) {
|
||||||
console.log(`[PCH ${this.gameId}] Новый сокет ${newSocket.id} для игрока ${identifier}. Старый сокет: ${oldSocket.id}. Обновляем записи.`);
|
console.log(`[PCH ${this.gameId}] Новый сокет ${newSocket.id} для игрока ${identifier}. Старый сокет: ${oldSocket.id}. Обновляем записи.`);
|
||||||
if (this.players[oldSocket.id]) delete this.players[oldSocket.id];
|
if (this.players[oldSocket.id]) delete this.players[oldSocket.id];
|
||||||
if (oldSocket.connected) {
|
if (oldSocket.connected) {
|
||||||
console.log(`[PCH ${this.gameId}] Отключаем старый "подвисший" сокет ${oldSocket.id}.`);
|
console.log(`[PCH ${this.gameId}] Отключаем старый "подвисший" сокет ${oldSocket.id}.`);
|
||||||
oldSocket.disconnect(true); // true - close an underlying connection
|
oldSocket.disconnect(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
playerEntry.socket = newSocket;
|
playerEntry.socket = newSocket;
|
||||||
this.players[newSocket.id] = playerEntry; // Обновляем/добавляем запись с новым socket.id
|
this.players[newSocket.id] = playerEntry;
|
||||||
if (oldSocket && oldSocket.id !== newSocket.id && this.players[oldSocket.id] === playerEntry) {
|
if (oldSocket && oldSocket.id !== newSocket.id && this.players[oldSocket.id] === playerEntry) {
|
||||||
// Если playerEntry был взят по старому socket.id, и этот ID теперь должен быть удален
|
|
||||||
delete this.players[oldSocket.id];
|
delete this.players[oldSocket.id];
|
||||||
}
|
}
|
||||||
this.playerSockets[playerIdRole] = newSocket;
|
this.playerSockets[playerIdRole] = newSocket;
|
||||||
|
|
||||||
// Всегда заново присоединяем сокет к комнате (Socket.IO handle'ит дубликаты)
|
console.log(`[PCH ${this.gameId}] Socket ${newSocket.id} (identifier: ${identifier}) ATTEMPTING to join room ${this.gameId} in handlePlayerReconnected.`);
|
||||||
console.log(`[PCH ${this.gameId}] Socket ${newSocket.id} (identifier: ${identifier}) attempting to join room ${this.gameId}.`);
|
|
||||||
try {
|
try {
|
||||||
newSocket.join(this.gameId);
|
newSocket.join(this.gameId);
|
||||||
const socketsInRoomAfterJoin = Array.from(this.io.sockets.adapter.rooms.get(this.gameId) || []);
|
const roomData = this.io.sockets.adapter.rooms.get(this.gameId);
|
||||||
console.log(`[PCH ${this.gameId}] Socket ${newSocket.id} finished join attempt. Sockets in room ${this.gameId} NOW: [${socketsInRoomAfterJoin.join(', ')}]. Expected to include: ${newSocket.id}`);
|
const socketsInRoomAfterJoin = roomData ? Array.from(roomData) : [];
|
||||||
if (!socketsInRoomAfterJoin.includes(newSocket.id)) {
|
const isSocketInRoom = socketsInRoomAfterJoin.includes(newSocket.id);
|
||||||
console.error(`[PCH ${this.gameId}] CRITICAL: Socket ${newSocket.id} DID NOT APPEAR IN ROOM ${this.gameId} immediately after join! Client might not receive room-based events.`);
|
console.log(`[PCH ${this.gameId}] Socket ${newSocket.id} FINISHED join attempt (handlePlayerReconnected). Room data type: ${typeof roomData}. Sockets in room ${this.gameId} NOW: [${socketsInRoomAfterJoin.join(', ')}]. Is newSocket (${newSocket.id}) in room? ${isSocketInRoom}`);
|
||||||
|
if (!isSocketInRoom) {
|
||||||
|
console.error(`[PCH ${this.gameId}] CRITICAL FAILURE (handlePlayerReconnected): Socket ${newSocket.id} was NOT found in room ${this.gameId} immediately after join command!`);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`[PCH ${this.gameId}] CRITICAL ERROR during newSocket.join(${this.gameId}): ${e.message}.`);
|
console.error(`[PCH ${this.gameId}] CRITICAL ERROR during newSocket.join(${this.gameId}) in handlePlayerReconnected: ${e.message}. Stack: ${e.stack}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wasTemporarilyDisconnected) {
|
if (wasTemporarilyDisconnected) {
|
||||||
@ -304,10 +297,9 @@ class PlayerConnectionHandler {
|
|||||||
playerEntry.isTemporarilyDisconnected = false;
|
playerEntry.isTemporarilyDisconnected = false;
|
||||||
this.playerCount++;
|
this.playerCount++;
|
||||||
} else {
|
} else {
|
||||||
console.log(`[PCH ${this.gameId}] Игрок ${identifier} (Роль: ${playerIdRole}) переподключился/запросил состояние, не будучи помеченным как 'temporarilyDisconnected'. Old socket ID: ${oldSocket?.id}, New socket ID: ${newSocket.id}. Player count (${this.playerCount}) not changed.`);
|
console.log(`[PCH ${this.gameId}] Игрок ${identifier} (Роль: ${playerIdRole}) переподключился/запросил состояние, не будучи помеченным как 'temporarilyDisconnected'. Old socket ID: ${oldSocket?.id}, New socket ID: ${newSocket.id}. Player count (${this.playerCount}) не изменен.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновление имени
|
|
||||||
if (this.gameInstance.gameState && this.gameInstance.gameState[playerIdRole]?.name) {
|
if (this.gameInstance.gameState && this.gameInstance.gameState[playerIdRole]?.name) {
|
||||||
playerEntry.name = this.gameInstance.gameState[playerIdRole].name;
|
playerEntry.name = this.gameInstance.gameState[playerIdRole].name;
|
||||||
} else {
|
} else {
|
||||||
@ -317,36 +309,31 @@ class PlayerConnectionHandler {
|
|||||||
console.log(`[PCH ${this.gameId}] Имя игрока ${identifier} (${playerIdRole}) обновлено/установлено на: ${playerEntry.name}`);
|
console.log(`[PCH ${this.gameId}] Имя игрока ${identifier} (${playerIdRole}) обновлено/установлено на: ${playerEntry.name}`);
|
||||||
|
|
||||||
this.gameInstance.addToLog(`🔌 Игрок ${playerEntry.name || identifier} снова в игре! (Сессия обновлена)`, GAME_CONFIG.LOG_TYPE_SYSTEM);
|
this.gameInstance.addToLog(`🔌 Игрок ${playerEntry.name || identifier} снова в игре! (Сессия обновлена)`, GAME_CONFIG.LOG_TYPE_SYSTEM);
|
||||||
this.sendFullGameStateOnReconnect(newSocket, playerEntry, playerIdRole); // Отправляем полное состояние
|
this.sendFullGameStateOnReconnect(newSocket, playerEntry, playerIdRole);
|
||||||
|
|
||||||
// Логика возобновления игры/таймера
|
|
||||||
if (wasTemporarilyDisconnected && this.pausedTurnState) {
|
if (wasTemporarilyDisconnected && this.pausedTurnState) {
|
||||||
this.resumeGameLogicAfterReconnect(playerIdRole); // Здесь возобновляется таймер, если он был на паузе
|
this.resumeGameLogicAfterReconnect(playerIdRole);
|
||||||
} else if (!wasTemporarilyDisconnected) {
|
} else if (!wasTemporarilyDisconnected) {
|
||||||
// Игрок не был temp disconnected. Таймер на сервере, если шел, то продолжал идти.
|
|
||||||
// Клиент получил новое состояние. Принудительный join выше должен был обеспечить доставку тиков.
|
|
||||||
// Дополнительно "пнем" таймер, чтобы он отправил текущее состояние, если он активен.
|
|
||||||
console.log(`[PCH ${this.gameId}] Player was not temp disconnected. Forcing timer update if active (for socket ${newSocket.id}).`);
|
console.log(`[PCH ${this.gameId}] Player was not temp disconnected. Forcing timer update if active (for socket ${newSocket.id}).`);
|
||||||
if (this.gameInstance.turnTimer && this.gameInstance.turnTimer.onTickCallback) {
|
if (this.gameInstance.turnTimer && this.gameInstance.turnTimer.onTickCallback) {
|
||||||
const tt = this.gameInstance.turnTimer;
|
const tt = this.gameInstance.turnTimer;
|
||||||
if (tt.isCurrentlyRunning && !tt.isManuallyPausedState) { // Если таймер реально работает
|
if (tt.isCurrentlyRunning && !tt.isManuallyPausedState) {
|
||||||
const elapsedTime = Date.now() - tt.segmentStartTimeMs;
|
const elapsedTime = Date.now() - tt.segmentStartTimeMs;
|
||||||
const currentRemaining = Math.max(0, tt.segmentDurationMs - elapsedTime);
|
const currentRemaining = Math.max(0, tt.segmentDurationMs - elapsedTime);
|
||||||
console.log(`[PCH ${this.gameId}] Forcing onTickCallback. Remaining: ${currentRemaining}, ForPlayer: ${tt.isConfiguredForPlayerSlotTurn}, ManualPause: ${tt.isManuallyPausedState}`);
|
console.log(`[PCH ${this.gameId}] Forcing onTickCallback (not temp disc). Remaining: ${currentRemaining}, ForPlayer: ${tt.isConfiguredForPlayerSlotTurn}, ManualPause: ${tt.isManuallyPausedState}`);
|
||||||
tt.onTickCallback(currentRemaining, tt.isConfiguredForPlayerSlotTurn, tt.isManuallyPausedState);
|
tt.onTickCallback(currentRemaining, tt.isConfiguredForPlayerSlotTurn, tt.isManuallyPausedState);
|
||||||
} else if (tt.isConfiguredForAiMove && !tt.isCurrentlyRunning) {
|
} else if (tt.isConfiguredForAiMove && !tt.isCurrentlyRunning) {
|
||||||
console.log(`[PCH ${this.gameId}] Forcing onTickCallback for AI move state.`);
|
console.log(`[PCH ${this.gameId}] Forcing onTickCallback for AI move state (not temp disc).`);
|
||||||
tt.onTickCallback(tt.initialTurnDurationMs, tt.isConfiguredForPlayerSlotTurn, false); // false = not paused by PCH
|
tt.onTickCallback(tt.initialTurnDurationMs, tt.isConfiguredForPlayerSlotTurn, false);
|
||||||
} else if (tt.isManuallyPausedState) {
|
} else if (tt.isManuallyPausedState) {
|
||||||
console.log(`[PCH ${this.gameId}] Forcing onTickCallback for manually paused state. Remaining: ${tt.segmentDurationMs}`);
|
console.log(`[PCH ${this.gameId}] Forcing onTickCallback for manually paused state (not temp disc). Remaining: ${tt.segmentDurationMs}`);
|
||||||
tt.onTickCallback(tt.segmentDurationMs, tt.isConfiguredForPlayerSlotTurn, true); // true = paused by PCH
|
tt.onTickCallback(tt.segmentDurationMs, tt.isConfiguredForPlayerSlotTurn, true);
|
||||||
} else if (!tt.isCurrentlyRunning && !tt.isManuallyPausedState && !this.isGameEffectivelyPaused() && this.gameInstance.gameState && !this.gameInstance.gameState.isGameOver) {
|
} else if (!tt.isCurrentlyRunning && !tt.isManuallyPausedState && !this.isGameEffectivelyPaused() && this.gameInstance.gameState && !this.gameInstance.gameState.isGameOver) {
|
||||||
// Таймер не работает, не на ручной паузе, игра не на общей паузе - возможно, его нужно запустить
|
|
||||||
const gs = this.gameInstance.gameState;
|
const gs = this.gameInstance.gameState;
|
||||||
const isHisTurnNow = (gs.isPlayerTurn && playerIdRole === GAME_CONFIG.PLAYER_ID) || (!gs.isPlayerTurn && playerIdRole === GAME_CONFIG.OPPONENT_ID && this.mode === 'pvp');
|
const isHisTurnNow = (gs.isPlayerTurn && playerIdRole === GAME_CONFIG.PLAYER_ID) || (!gs.isPlayerTurn && playerIdRole === GAME_CONFIG.OPPONENT_ID && this.mode === 'pvp');
|
||||||
const isAiTurnNow = this.mode === 'ai' && !gs.isPlayerTurn;
|
const isAiTurnNow = this.mode === 'ai' && !gs.isPlayerTurn;
|
||||||
if(isHisTurnNow || isAiTurnNow) {
|
if(isHisTurnNow || isAiTurnNow) {
|
||||||
console.log(`[PCH ${this.gameId}] Timer not running & not paused. Attempting to start for ${playerIdRole}. HisTurn: ${isHisTurnNow}, AITurn: ${isAiTurnNow}`);
|
console.log(`[PCH ${this.gameId}] Timer not running & not paused (not temp disc). Attempting to start for ${playerIdRole}. HisTurn: ${isHisTurnNow}, AITurn: ${isAiTurnNow}`);
|
||||||
this.gameInstance.turnTimer.start(gs.isPlayerTurn, isAiTurnNow);
|
this.gameInstance.turnTimer.start(gs.isPlayerTurn, isAiTurnNow);
|
||||||
if (isAiTurnNow && !this.gameInstance.turnTimer.getIsConfiguredForAiMove?.()) {
|
if (isAiTurnNow && !this.gameInstance.turnTimer.getIsConfiguredForAiMove?.()) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -361,16 +348,9 @@ class PlayerConnectionHandler {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} else { // playerEntry не найден для этой роли и идентификатора
|
} else {
|
||||||
console.warn(`[PCH ${this.gameId}] Попытка переподключения для ${identifier} (Роль ${playerIdRole}), но запись playerEntry не найдена. Это новый игрок?`);
|
console.warn(`[PCH ${this.gameId}] Попытка переподключения для ${identifier} (Роль ${playerIdRole}), но запись playerEntry не найдена. GameManager должен обработать это как нового игрока, если необходимо.`);
|
||||||
// Это может быть сценарий, когда игрок впервые присоединяется к игре,
|
newSocket.emit('gameError', { message: 'Не удалось найти вашу игровую сессию для переподключения. Возможно, она истекла.' });
|
||||||
// и GameManager.handleRequestGameState вызвал этот метод.
|
|
||||||
// В этом случае, нам нужно вызвать addPlayer.
|
|
||||||
// Однако, если addPlayer уже был вызван и не нашел existingPlayerByIdentifier,
|
|
||||||
// то эта ветка означает, что что-то пошло не так с логикой GM или состоянием PCH.
|
|
||||||
// Для чистоты, handlePlayerReconnected не должен добавлять нового игрока.
|
|
||||||
// Если игрок не найден, это значит, что его сессии нет.
|
|
||||||
newSocket.emit('gameError', { message: 'Не удалось найти вашу игровую сессию для переподключения. Попробуйте создать игру заново.' });
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -442,23 +422,17 @@ class PlayerConnectionHandler {
|
|||||||
if (!this.isGameEffectivelyPaused() && this.gameInstance.gameState && !this.gameInstance.gameState.isGameOver) {
|
if (!this.isGameEffectivelyPaused() && this.gameInstance.gameState && !this.gameInstance.gameState.isGameOver) {
|
||||||
if (Object.keys(this.reconnectTimers).length === 0) {
|
if (Object.keys(this.reconnectTimers).length === 0) {
|
||||||
const currentTurnIsForPlayerInGS = this.gameInstance.gameState.isPlayerTurn;
|
const currentTurnIsForPlayerInGS = this.gameInstance.gameState.isPlayerTurn;
|
||||||
// isAiMakingMove для TurnTimer.resume/start должно быть true, если это ход AI, И он сейчас не на паузе из-за дисконнекта игрока
|
|
||||||
const isAiTurnAndShouldAct = this.mode === 'ai' && !currentTurnIsForPlayerInGS;
|
const isAiTurnAndShouldAct = this.mode === 'ai' && !currentTurnIsForPlayerInGS;
|
||||||
let resumedFromPausedState = false;
|
let resumedFromPausedState = false;
|
||||||
|
|
||||||
if (this.pausedTurnState && typeof this.pausedTurnState.remainingTime === 'number') {
|
if (this.pausedTurnState && typeof this.pausedTurnState.remainingTime === 'number') {
|
||||||
// pausedTurnState.forPlayerRoleIsPlayer - это isConfiguredForPlayerSlotTurn таймера
|
|
||||||
// pausedTurnState.isAiCurrentlyMoving - это isConfiguredForAiMove таймера
|
|
||||||
const gsTurnMatchesPausedTurnPlayerPerspective = (currentTurnIsForPlayerInGS === this.pausedTurnState.forPlayerRoleIsPlayer);
|
const gsTurnMatchesPausedTurnPlayerPerspective = (currentTurnIsForPlayerInGS === this.pausedTurnState.forPlayerRoleIsPlayer);
|
||||||
// const gsAiMatchesPausedAi = (isAiTurnAndShouldAct === this.pausedTurnState.isAiCurrentlyMoving);
|
|
||||||
|
|
||||||
// Возобновляем, если ход в GS совпадает с тем, для кого был таймер, ИЛИ если это был ход AI
|
|
||||||
if (gsTurnMatchesPausedTurnPlayerPerspective) {
|
if (gsTurnMatchesPausedTurnPlayerPerspective) {
|
||||||
console.log(`[PCH ${this.gameId}] Возобновляем таймер хода из pausedTurnState. Время: ${this.pausedTurnState.remainingTime}мс. Для игрока (в pausedState): ${this.pausedTurnState.forPlayerRoleIsPlayer}. GS ход игрока: ${currentTurnIsForPlayerInGS}. AI ход (в pausedState): ${this.pausedTurnState.isAiCurrentlyMoving}`);
|
console.log(`[PCH ${this.gameId}] Возобновляем таймер хода из pausedTurnState. Время: ${this.pausedTurnState.remainingTime}мс. Для игрока (в pausedState): ${this.pausedTurnState.forPlayerRoleIsPlayer}. GS ход игрока: ${currentTurnIsForPlayerInGS}. AI ход (в pausedState): ${this.pausedTurnState.isAiCurrentlyMoving}`);
|
||||||
this.gameInstance.turnTimer.resume(
|
this.gameInstance.turnTimer.resume(
|
||||||
this.pausedTurnState.remainingTime,
|
this.pausedTurnState.remainingTime,
|
||||||
this.pausedTurnState.forPlayerRoleIsPlayer, // Для кого был таймер
|
this.pausedTurnState.forPlayerRoleIsPlayer,
|
||||||
this.pausedTurnState.isAiCurrentlyMoving // Был ли это AI ход, когда остановили
|
this.pausedTurnState.isAiCurrentlyMoving
|
||||||
);
|
);
|
||||||
resumedFromPausedState = true;
|
resumedFromPausedState = true;
|
||||||
} else {
|
} else {
|
||||||
@ -478,7 +452,15 @@ class PlayerConnectionHandler {
|
|||||||
}, GAME_CONFIG.DELAY_OPPONENT_TURN);
|
}, GAME_CONFIG.DELAY_OPPONENT_TURN);
|
||||||
}
|
}
|
||||||
} else if (!resumedFromPausedState && this.gameInstance.turnTimer && this.gameInstance.turnTimer.isActive()){
|
} else if (!resumedFromPausedState && this.gameInstance.turnTimer && this.gameInstance.turnTimer.isActive()){
|
||||||
console.log(`[PCH ${this.gameId}] Таймер уже был активен при попытке перезапуска после реконнекта (pausedTurnState не использовался/неактуален). Ничего не делаем с таймером, он должен сам слать обновления.`);
|
console.log(`[PCH ${this.gameId}] Таймер уже был активен при попытке перезапуска после реконнекта (pausedTurnState не использовался/неактуален). Отправляем текущее состояние таймера.`);
|
||||||
|
// Если таймер уже активен, но мы здесь, значит, это мог быть реконнект НЕ isTemporarilyDisconnected.
|
||||||
|
// "Пнем" таймер, чтобы он отправил свое состояние.
|
||||||
|
const tt = this.gameInstance.turnTimer;
|
||||||
|
if (tt.onTickCallback && tt.isCurrentlyRunning && !tt.isManuallyPausedState) {
|
||||||
|
const elapsedTime = Date.now() - tt.segmentStartTimeMs;
|
||||||
|
const currentRemaining = Math.max(0, tt.segmentDurationMs - elapsedTime);
|
||||||
|
tt.onTickCallback(currentRemaining, tt.isConfiguredForPlayerSlotTurn, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(`[PCH ${this.gameId}] Возобновление логики таймера отложено, есть другие активные таймеры реконнекта: ${Object.keys(this.reconnectTimers)}`);
|
console.log(`[PCH ${this.gameId}] Возобновление логики таймера отложено, есть другие активные таймеры реконнекта: ${Object.keys(this.reconnectTimers)}`);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user