From c5e483d3c565e831933056ea77b2d775e84147f1 Mon Sep 17 00:00:00 2001 From: MrS-ibra Date: Thu, 25 Dec 2025 07:29:21 +0300 Subject: [PATCH 01/13] Update ConnectionManager.cpp --- Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp b/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp index ae432e692bf..67dc550a242 100644 --- a/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp +++ b/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp @@ -2517,8 +2517,6 @@ void ConnectionManager::notifyOthersOfCurrentFrame(Int frame) { m_disconnectManager->processDisconnectCommand(ref, this); deleteInstance(ref); - msg->detach(); - DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("ConnectionManager::notifyOthersOfCurrentFrame - start screen on debug stuff")); #if defined(RTS_DEBUG) debugPrintConnectionCommands(); @@ -2540,7 +2538,6 @@ void ConnectionManager::notifyOthersOfNewFrame(UnsignedInt frame) { m_disconnectManager->processDisconnectCommand(ref, this); deleteInstance(ref); - msg->detach(); } void ConnectionManager::sendFrameDataToPlayer(UnsignedInt playerID, UnsignedInt startingFrame) { From 43f5c85eec4c5330a70a0e3d811e9bcf972d1623 Mon Sep 17 00:00:00 2001 From: MrS-ibra Date: Thu, 25 Dec 2025 07:59:03 +0300 Subject: [PATCH 02/13] Update DisconnectManager.cpp --- Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp b/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp index 94beda05da6..f3694ad5ee3 100644 --- a/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp +++ b/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp @@ -608,6 +608,8 @@ void DisconnectManager::sendDisconnectCommand(Int slot, ConnectionManager *conMg NetDisconnectPlayerCommandMsg *msg = newInstance(NetDisconnectPlayerCommandMsg); msg->setDisconnectSlot(slot); msg->setDisconnectFrame(disconnectFrame); + DEBUG_LOG(("DBG_DISCONNECT: CREATED %s:%d id=%d slot=%d setFrame=%u localFrame=%u", + __FILE__, __LINE__, msg->getID(), msg->getDisconnectSlot(), msg->getDisconnectFrame(), TheGameLogic->getFrame())); msg->setPlayerID(conMgr->getLocalPlayerID()); if (DoesCommandRequireACommandID(msg->getNetCommandType())) { msg->setID(GenerateNextCommandID()); From 4990b27be319411c066d618a0895e312a9274703 Mon Sep 17 00:00:00 2001 From: MrS-ibra Date: Thu, 25 Dec 2025 08:00:02 +0300 Subject: [PATCH 03/13] Update ConnectionManager.cpp --- Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp b/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp index 67dc550a242..471bc682070 100644 --- a/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp +++ b/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp @@ -1935,16 +1935,18 @@ void ConnectionManager::quitGame() { NetDisconnectPlayerCommandMsg *disconnectMsg = newInstance(NetDisconnectPlayerCommandMsg); disconnectMsg->setDisconnectSlot(m_localSlot); disconnectMsg->setDisconnectFrame(TheGameLogic->getFrame()); + DEBUG_LOG(("DBG_DISCONNECT: CREATED %s:%d id=%d slot=%d setFrame=%u localFrame=%u", + __FILE__, __LINE__, msg->getID(), msg->getDisconnectSlot(), msg->getDisconnectFrame(), TheGameLogic->getFrame())); disconnectMsg->setPlayerID(m_localSlot); if (DoesCommandRequireACommandID(disconnectMsg->getNetCommandType())) { disconnectMsg->setID(GenerateNextCommandID()); } - //DEBUG_LOG(("ConnectionManager::disconnectLocalPlayer - about to send disconnect command")); + DEBUG_LOG(("ConnectionManager::disconnectLocalPlayer - about to send disconnect command")); sendLocalCommandDirect(disconnectMsg, 0xff ^ (1 << m_localSlot)); - //DEBUG_LOG(("ConnectionManager::disconnectLocalPlayer - about to flush connections")); + DEBUG_LOG(("ConnectionManager::disconnectLocalPlayer - about to flush connections")); flushConnections(); // need to do this so our packet actually gets sent before the connections are deleted. - //DEBUG_LOG(("ConnectionManager::disconnectLocalPlayer - done flushing connections")); + DEBUG_LOG(("ConnectionManager::disconnectLocalPlayer - done flushing connections")); disconnectMsg->detach(); From 24d63c262c33100e76ab3ba2671c27cce1bb8694 Mon Sep 17 00:00:00 2001 From: MrS-ibra Date: Thu, 25 Dec 2025 08:10:48 +0300 Subject: [PATCH 04/13] Update ConnectionManager.cpp --- Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp b/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp index 471bc682070..0630cef3771 100644 --- a/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp +++ b/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp @@ -1936,7 +1936,7 @@ void ConnectionManager::quitGame() { disconnectMsg->setDisconnectSlot(m_localSlot); disconnectMsg->setDisconnectFrame(TheGameLogic->getFrame()); DEBUG_LOG(("DBG_DISCONNECT: CREATED %s:%d id=%d slot=%d setFrame=%u localFrame=%u", - __FILE__, __LINE__, msg->getID(), msg->getDisconnectSlot(), msg->getDisconnectFrame(), TheGameLogic->getFrame())); + __FILE__, __LINE__, disconnectMsg->getID(), disconnectMsg->getDisconnectSlot(), disconnectMsg->getDisconnectFrame(), TheGameLogic->getFrame())); disconnectMsg->setPlayerID(m_localSlot); if (DoesCommandRequireACommandID(disconnectMsg->getNetCommandType())) { disconnectMsg->setID(GenerateNextCommandID()); From 0fa23ae880163e0d0338cd1f025c5b8845cbc9b5 Mon Sep 17 00:00:00 2001 From: MrS-ibra Date: Thu, 25 Dec 2025 08:55:09 +0300 Subject: [PATCH 05/13] Update ConnectionManager.cpp --- .../Source/GameNetwork/ConnectionManager.cpp | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp b/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp index 0630cef3771..9caea8ffb36 100644 --- a/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp +++ b/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp @@ -2563,12 +2563,22 @@ void ConnectionManager::sendSingleFrameToPlayer(UnsignedInt playerID, UnsignedIn if ((m_frameData[i] != NULL) && (i != playerID)) { // no need to send his own commands to him. NetCommandList *list = m_frameData[i]->getFrameCommandList(frame); if (list != NULL) { - NetCommandRef *ref = list->getFirstMessage(); - while (ref != NULL) { - DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("ConnectionManager::sendFrameDataToPlayer - sending command %d from player %d to player %d using relay 0x%x", ref->getCommand()->getID(), i, playerID, relay)); - sendLocalCommandDirect(ref->getCommand(), relay); - ref = ref->getNext(); - } +NetCommandRef *ref = list->getFirstMessage(); +while (ref != NULL) { + NetCommandRef *nextRef = ref->getNext(); + + // Skip re-sending disconnect-frame commands to avoid duplicate disconnect messages + if (ref->getCommand()->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTFRAME) { + ref = nextRef; + continue; + } + + DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("ConnectionManager::sendFrameDataToPlayer - sending command %d from player %d to player %d using relay 0x%x", + ref->getCommand()->getID(), i, playerID, relay)); + sendLocalCommandDirect(ref->getCommand(), relay); + + ref = nextRef; +} } UnsignedInt frameCommandCount = m_frameData[i]->getFrameCommandCount(frame); NetFrameCommandMsg *msg = newInstance(NetFrameCommandMsg); From cd59ed643f81af5052421ee1ee7a7efc561f8af2 Mon Sep 17 00:00:00 2001 From: MrS-ibra Date: Thu, 25 Dec 2025 09:19:11 +0300 Subject: [PATCH 06/13] Update DisconnectManager.cpp --- Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp b/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp index f3694ad5ee3..0373fb8acee 100644 --- a/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp +++ b/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp @@ -575,6 +575,8 @@ void DisconnectManager::disconnectPlayer(Int slot, ConnectionManager *conMgr) { } Int transSlot = translatedSlotPosition(slot, conMgr->getLocalPlayerID()); + DEBUG_LOG(("DBG_DP: disconnectPlayer called %s:%d slot=%d localPlayer=%d transSlot=%d", + __FILE__, __LINE__, slot, conMgr->getLocalPlayerID(), transSlot)); if (transSlot != -1) { // Ignore any disconnect commands that tell us to disconnect ourselves. From 9a5882bb951ebf3962312758d22215eed1cfd3cd Mon Sep 17 00:00:00 2001 From: MrS-ibra Date: Thu, 25 Dec 2025 09:38:38 +0300 Subject: [PATCH 07/13] Update DisconnectManager.cpp --- .../Source/GameNetwork/DisconnectManager.cpp | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp b/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp index 0373fb8acee..d75fbc7f971 100644 --- a/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp +++ b/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp @@ -559,42 +559,42 @@ void DisconnectManager::turnOnScreen(ConnectionManager *conMgr) { } void DisconnectManager::disconnectPlayer(Int slot, ConnectionManager *conMgr) { - DEBUG_LOG(("DisconnectManager::disconnectPlayer - Disconnecting slot number %d on frame %d", slot, TheGameLogic->getFrame())); - DEBUG_ASSERTCRASH((slot >= 0) && (slot < MAX_SLOTS), ("Attempting to disconnect an invalid slot number")); - if ((slot < 0) || (slot >= (MAX_SLOTS))) { - return; - } - - if (TheGameInfo) - { - GameSlot *gSlot = TheGameInfo->getSlot( slot ); - if (gSlot) - { - gSlot->markAsDisconnected(); - } - } - - Int transSlot = translatedSlotPosition(slot, conMgr->getLocalPlayerID()); + DEBUG_LOG(("DisconnectManager::disconnectPlayer - Disconnecting slot number %d on frame %d", slot, TheGameLogic->getFrame())); + DEBUG_ASSERTCRASH((slot >= 0) && (slot < MAX_SLOTS), ("Attempting to disconnect an invalid slot number")); + if ((slot < 0) || (slot >= (MAX_SLOTS))) { + return; + } + + if (TheGameInfo) + { + GameSlot *gSlot = TheGameInfo->getSlot( slot ); + if (gSlot) + { + gSlot->markAsDisconnected(); + } + } + + Int transSlot = translatedSlotPosition(slot, conMgr->getLocalPlayerID()); DEBUG_LOG(("DBG_DP: disconnectPlayer called %s:%d slot=%d localPlayer=%d transSlot=%d", - __FILE__, __LINE__, slot, conMgr->getLocalPlayerID(), transSlot)); + __FILE__, __LINE__, slot, conMgr->getLocalPlayerID(), transSlot)); - if (transSlot != -1) { - // Ignore any disconnect commands that tell us to disconnect ourselves. + if ((slot == conMgr->getLocalPlayerID()) || (transSlot == -1)) { + DEBUG_LOG(("DBG_DP: Ignoring disconnectPlayer for local slot %d (transSlot=%d)", slot, transSlot)); + return; + } - // Get the disconnecting player off the disconnect window. - UnicodeString uname = conMgr->getPlayerName(slot); - TheRecorder->logPlayerDisconnect(uname, slot); - TheDisconnectMenu->removePlayer(transSlot, uname); + UnicodeString uname = conMgr->getPlayerName(slot); + TheRecorder->logPlayerDisconnect(uname, slot); + TheDisconnectMenu->removePlayer(transSlot, uname); - PlayerLeaveCode retcode = conMgr->disconnectPlayer(slot); - DEBUG_ASSERTCRASH((retcode != PLAYERLEAVECODE_UNKNOWN), ("Invalid player leave code")); + PlayerLeaveCode retcode = conMgr->disconnectPlayer(slot); + DEBUG_ASSERTCRASH((retcode != PLAYERLEAVECODE_UNKNOWN), ("Invalid player leave code")); - if (retcode == PLAYERLEAVECODE_PACKETROUTER) { - DEBUG_LOG(("DisconnectManager::disconnectPlayer - disconnecting player was packet router.")); + if (retcode == PLAYERLEAVECODE_PACKETROUTER) { + DEBUG_LOG(("DisconnectManager::disconnectPlayer - disconnecting player was packet router.")); - conMgr->resendPendingCommands(); - } - } + conMgr->resendPendingCommands(); + } } void DisconnectManager::sendDisconnectCommand(Int slot, ConnectionManager *conMgr) { From 36725e425cefb7a51607211f1767ca31c447fe25 Mon Sep 17 00:00:00 2001 From: MrS-ibra Date: Thu, 25 Dec 2025 10:12:32 +0300 Subject: [PATCH 08/13] Update DisconnectManager.cpp --- .../Source/GameNetwork/DisconnectManager.cpp | 225 +++++++++--------- 1 file changed, 115 insertions(+), 110 deletions(-) diff --git a/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp b/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp index d75fbc7f971..c00908466ae 100644 --- a/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp +++ b/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp @@ -578,11 +578,15 @@ void DisconnectManager::disconnectPlayer(Int slot, ConnectionManager *conMgr) { DEBUG_LOG(("DBG_DP: disconnectPlayer called %s:%d slot=%d localPlayer=%d transSlot=%d", __FILE__, __LINE__, slot, conMgr->getLocalPlayerID(), transSlot)); + // If this disconnect targets the local player, ignore it here. + // Local disconnects should follow the normal local flow (queued destroy), + // not immediate UI removal. if ((slot == conMgr->getLocalPlayerID()) || (transSlot == -1)) { DEBUG_LOG(("DBG_DP: Ignoring disconnectPlayer for local slot %d (transSlot=%d)", slot, transSlot)); return; } + // For non-local players, remove from the disconnect UI and perform the disconnect. UnicodeString uname = conMgr->getPlayerName(slot); TheRecorder->logPlayerDisconnect(uname, slot); TheDisconnectMenu->removePlayer(transSlot, uname); @@ -598,162 +602,163 @@ void DisconnectManager::disconnectPlayer(Int slot, ConnectionManager *conMgr) { } void DisconnectManager::sendDisconnectCommand(Int slot, ConnectionManager *conMgr) { - DEBUG_LOG(("DisconnectManager::sendDisconnectCommand - Sending disconnect command for slot number %d", slot)); - DEBUG_ASSERTCRASH((slot >= 0) && (slot < MAX_SLOTS), ("Attempting to send a disconnect command for an invalid slot number")); - if ((slot < 0) || (slot >= (MAX_SLOTS))) { - return; - } + DEBUG_LOG(("DisconnectManager::sendDisconnectCommand - Sending disconnect command for slot number %d", slot)); + DEBUG_ASSERTCRASH((slot >= 0) && (slot < MAX_SLOTS), ("Attempting to send a disconnect command for an invalid slot number")); + if ((slot < 0) || (slot >= (MAX_SLOTS))) { + return; + } - UnsignedInt disconnectFrame = getMaxDisconnectFrame(); + UnsignedInt disconnectFrame = getMaxDisconnectFrame(); - // Need to do the NetDisconnectPlayerCommandMsg creation and sending here. - NetDisconnectPlayerCommandMsg *msg = newInstance(NetDisconnectPlayerCommandMsg); - msg->setDisconnectSlot(slot); - msg->setDisconnectFrame(disconnectFrame); + // Need to do the NetDisconnectPlayerCommandMsg creation and sending here. + NetDisconnectPlayerCommandMsg *msg = newInstance(NetDisconnectPlayerCommandMsg); + msg->setDisconnectSlot(slot); + msg->setDisconnectFrame(disconnectFrame); + msg->setPlayerID(conMgr->getLocalPlayerID()); + if (DoesCommandRequireACommandID(msg->getNetCommandType())) { + msg->setID(GenerateNextCommandID()); + } + // Log after ID has been assigned so the log shows the real command id. DEBUG_LOG(("DBG_DISCONNECT: CREATED %s:%d id=%d slot=%d setFrame=%u localFrame=%u", - __FILE__, __LINE__, msg->getID(), msg->getDisconnectSlot(), msg->getDisconnectFrame(), TheGameLogic->getFrame())); - msg->setPlayerID(conMgr->getLocalPlayerID()); - if (DoesCommandRequireACommandID(msg->getNetCommandType())) { - msg->setID(GenerateNextCommandID()); - } + __FILE__, __LINE__, msg->getID(), msg->getDisconnectSlot(), msg->getDisconnectFrame(), TheGameLogic->getFrame())); - conMgr->sendLocalCommand(msg); + conMgr->sendLocalCommand(msg); - DEBUG_LOG(("DisconnectManager::sendDisconnectCommand - Sending disconnect command for slot number %d for frame %d", slot, disconnectFrame)); + DEBUG_LOG(("DisconnectManager::sendDisconnectCommand - Sending disconnect command for slot number %d for frame %d", slot, disconnectFrame)); - msg->detach(); + msg->detach(); } void DisconnectManager::sendVoteCommand(Int slot, ConnectionManager *conMgr) { - NetDisconnectVoteCommandMsg *msg = newInstance(NetDisconnectVoteCommandMsg); + NetDisconnectVoteCommandMsg *msg = newInstance(NetDisconnectVoteCommandMsg); - msg->setPlayerID(conMgr->getLocalPlayerID()); - msg->setSlot(slot); - msg->setVoteFrame(TheGameLogic->getFrame()); - if (DoesCommandRequireACommandID(msg->getNetCommandType()) == TRUE) { - msg->setID(GenerateNextCommandID()); - } + msg->setPlayerID(conMgr->getLocalPlayerID()); + msg->setSlot(slot); + msg->setVoteFrame(TheGameLogic->getFrame()); + if (DoesCommandRequireACommandID(msg->getNetCommandType()) == TRUE) { + msg->setID(GenerateNextCommandID()); + } - conMgr->sendLocalCommandDirect(msg, 0xff & ~(1 << conMgr->getLocalPlayerID())); + conMgr->sendLocalCommandDirect(msg, 0xff & ~(1 << conMgr->getLocalPlayerID())); - msg->detach(); + msg->detach(); } void DisconnectManager::voteForPlayerDisconnect(Int slot, ConnectionManager *conMgr) { - Int transSlot = untranslatedSlotPosition(slot, conMgr->getLocalPlayerID()); + Int transSlot = untranslatedSlotPosition(slot, conMgr->getLocalPlayerID()); - if (m_playerVotes[transSlot][conMgr->getLocalPlayerID()].vote == FALSE) { - m_playerVotes[transSlot][conMgr->getLocalPlayerID()].vote = TRUE; + if (m_playerVotes[transSlot][conMgr->getLocalPlayerID()].vote == FALSE) { + m_playerVotes[transSlot][conMgr->getLocalPlayerID()].vote = TRUE; - sendVoteCommand(transSlot, conMgr); + sendVoteCommand(transSlot, conMgr); - // we use the game logic frame cause we might not have sent out our own disconnect frame yet. - applyDisconnectVote(transSlot, TheGameLogic->getFrame(), conMgr->getLocalPlayerID(), conMgr); - } + // we use the game logic frame cause we might not have sent out our own disconnect frame yet. + applyDisconnectVote(transSlot, TheGameLogic->getFrame(), conMgr->getLocalPlayerID(), conMgr); + } } void DisconnectManager::recalculatePacketRouterIndex(ConnectionManager *conMgr) { - Int currentPacketRouterSlot = conMgr->getPacketRouterSlot(); - m_currentPacketRouterIndex = 0; - while ((currentPacketRouterSlot != conMgr->getPacketRouterFallbackSlot(m_currentPacketRouterIndex)) && (m_currentPacketRouterIndex < MAX_SLOTS)) { - ++m_currentPacketRouterIndex; - } - DEBUG_ASSERTCRASH((m_currentPacketRouterIndex < MAX_SLOTS), ("Invalid packet router index")); + Int currentPacketRouterSlot = conMgr->getPacketRouterSlot(); + m_currentPacketRouterIndex = 0; + while ((currentPacketRouterSlot != conMgr->getPacketRouterFallbackSlot(m_currentPacketRouterIndex)) && (m_currentPacketRouterIndex < MAX_SLOTS)) { + ++m_currentPacketRouterIndex; + } + DEBUG_ASSERTCRASH((m_currentPacketRouterIndex < MAX_SLOTS), ("Invalid packet router index")); } Bool DisconnectManager::allOnSameFrame(ConnectionManager *conMgr) { - Bool retval = TRUE; - for (Int i = 0; (i < MAX_SLOTS) && (retval == TRUE); ++i) { - Int transSlot = translatedSlotPosition(i, conMgr->getLocalPlayerID()); - if (transSlot == -1) { - continue; - } - if ((conMgr->isPlayerConnected(i) == TRUE) && (isPlayerInGame(transSlot, conMgr) == TRUE)) { - // ok, i is someone who is in the game and hasn't timed out yet or been voted out. - if (m_disconnectFramesReceived[i] == FALSE) { - // we don't know what frame they are on yet. - retval = FALSE; - } - if ((m_disconnectFramesReceived[i] == TRUE) && (m_disconnectFrames[conMgr->getLocalPlayerID()] != m_disconnectFrames[i])) { - // We know their frame, but they aren't on the same frame as us. - retval = FALSE; - } - } - } - return retval; + Bool retval = TRUE; + for (Int i = 0; (i < MAX_SLOTS) && (retval == TRUE); ++i) { + Int transSlot = translatedSlotPosition(i, conMgr->getLocalPlayerID()); + if (transSlot == -1) { + continue; + } + if ((conMgr->isPlayerConnected(i) == TRUE) && (isPlayerInGame(transSlot, conMgr) == TRUE)) { + // ok, i is someone who is in the game and hasn't timed out yet or been voted out. + if (m_disconnectFramesReceived[i] == FALSE) { + // we don't know what frame they are on yet. + retval = FALSE; + } + if ((m_disconnectFramesReceived[i] == TRUE) && (m_disconnectFrames[conMgr->getLocalPlayerID()] != m_disconnectFrames[i])) { + // We know their frame, but they aren't on the same frame as us. + retval = FALSE; + } + } + } + return retval; } Bool DisconnectManager::isLocalPlayerNextPacketRouter(ConnectionManager *conMgr) { - UnsignedInt localSlot = conMgr->getLocalPlayerID(); - UnsignedInt packetRouterSlot = conMgr->getPacketRouterSlot(); - Int transSlot = translatedSlotPosition(packetRouterSlot, localSlot); - - // stop when we have found a packet router that is connected - while ((transSlot != -1) && (isPlayerInGame(transSlot, conMgr) == FALSE)) { - packetRouterSlot = conMgr->getNextPacketRouterSlot(packetRouterSlot); - if ((packetRouterSlot >= MAX_SLOTS) || (packetRouterSlot < 0)) { - // don't know who the next packet router is going to be, - // so this game is not going to go anywhere anymore. - DEBUG_CRASH(("no more players left to be the packet router, this shouldn't happen.")); - return FALSE; - } - transSlot = translatedSlotPosition(packetRouterSlot, localSlot); - } + UnsignedInt localSlot = conMgr->getLocalPlayerID(); + UnsignedInt packetRouterSlot = conMgr->getPacketRouterSlot(); + Int transSlot = translatedSlotPosition(packetRouterSlot, localSlot); + + // stop when we have found a packet router that is connected + while ((transSlot != -1) && (isPlayerInGame(transSlot, conMgr) == FALSE)) { + packetRouterSlot = conMgr->getNextPacketRouterSlot(packetRouterSlot); + if ((packetRouterSlot >= MAX_SLOTS) || (packetRouterSlot < 0)) { + // don't know who the next packet router is going to be, + // so this game is not going to go anywhere anymore. + DEBUG_CRASH(("no more players left to be the packet router, this shouldn't happen.")); + return FALSE; + } + transSlot = translatedSlotPosition(packetRouterSlot, localSlot); + } - if (packetRouterSlot == localSlot) { - return TRUE; - } + if (packetRouterSlot == localSlot) { + return TRUE; + } - return FALSE; + return FALSE; } Bool DisconnectManager::hasPlayerTimedOut(Int slot) { - if (slot == -1) { - return FALSE; - } + if (slot == -1) { + return FALSE; + } - time_t newTime = TheGlobalData->m_networkPlayerTimeoutTime - (timeGetTime() - m_playerTimeouts[slot]); - if (newTime <= 0) { - return TRUE; - } + time_t newTime = TheGlobalData->m_networkPlayerTimeoutTime - (timeGetTime() - m_playerTimeouts[slot]); + if (newTime <= 0) { + return TRUE; + } - return FALSE; + return FALSE; } // this function assumes that we are the packet router. (or at least that // we will be after everyone is getting disconnected) void DisconnectManager::sendPlayerDestruct(Int slot, ConnectionManager *conMgr) { - UnsignedShort currentID = 0; - if (DoesCommandRequireACommandID(NETCOMMANDTYPE_DESTROYPLAYER)) - { - currentID = GenerateNextCommandID(); - } + UnsignedShort currentID = 0; + if (DoesCommandRequireACommandID(NETCOMMANDTYPE_DESTROYPLAYER)) + { + currentID = GenerateNextCommandID(); + } - DEBUG_LOG(("Queueing DestroyPlayer %d for frame %d on frame %d as command %d", - slot, TheNetwork->getExecutionFrame()+1, TheGameLogic->getFrame(), currentID)); + DEBUG_LOG(("Queueing DestroyPlayer %d for frame %d on frame %d as command %d", + slot, TheNetwork->getExecutionFrame()+1, TheGameLogic->getFrame(), currentID)); - NetDestroyPlayerCommandMsg *netmsg = newInstance(NetDestroyPlayerCommandMsg); - netmsg->setExecutionFrame(TheNetwork->getExecutionFrame()+1); - netmsg->setPlayerID(conMgr->getLocalPlayerID()); - netmsg->setID(currentID); - netmsg->setPlayerIndex(slot); - conMgr->sendLocalCommand(netmsg); - netmsg->detach(); + NetDestroyPlayerCommandMsg *netmsg = newInstance(NetDestroyPlayerCommandMsg); + netmsg->setExecutionFrame(TheNetwork->getExecutionFrame()+1); + netmsg->setPlayerID(conMgr->getLocalPlayerID()); + netmsg->setID(currentID); + netmsg->setPlayerIndex(slot); + conMgr->sendLocalCommand(netmsg); + netmsg->detach(); } // the 'slot' variable is supposed to be a translated slot position. (translated slot meaning // that it is the player's position in the disconnect menu) Bool DisconnectManager::isPlayerVotedOut(Int slot, ConnectionManager *conMgr) { - if (slot == -1) { - // we can't vote out ourselves. - return FALSE; - } - Int transSlot = untranslatedSlotPosition(slot, conMgr->getLocalPlayerID()); - Int numVotes = countVotesForPlayer(transSlot); - if (numVotes >= (conMgr->getNumPlayers() - 1)) { - return TRUE; - } - return FALSE; + if (slot == -1) { + // we can't vote out ourselves. + return FALSE; + } + Int transSlot = untranslatedSlotPosition(slot, conMgr->getLocalPlayerID()); + Int numVotes = countVotesForPlayer(transSlot); + if (numVotes >= (conMgr->getNumPlayers() - 1)) { + return TRUE; + } + return FALSE; } UnsignedInt DisconnectManager::getMaxDisconnectFrame() { From e88c906e30dc6e9437ed73c49220355810715f7f Mon Sep 17 00:00:00 2001 From: MrS-ibra Date: Thu, 25 Dec 2025 11:02:12 +0300 Subject: [PATCH 09/13] Update ConnectionManager.cpp --- .../Source/GameNetwork/ConnectionManager.cpp | 128 +++++++++++------- 1 file changed, 78 insertions(+), 50 deletions(-) diff --git a/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp b/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp index 9caea8ffb36..16c727b7c21 100644 --- a/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp +++ b/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp @@ -1931,73 +1931,101 @@ PlayerLeaveCode ConnectionManager::disconnectPlayer(int64_t userID) #endif void ConnectionManager::quitGame() { - // Need to do the NetDisconnectPlayerCommandMsg creation and sending here. - NetDisconnectPlayerCommandMsg *disconnectMsg = newInstance(NetDisconnectPlayerCommandMsg); - disconnectMsg->setDisconnectSlot(m_localSlot); - disconnectMsg->setDisconnectFrame(TheGameLogic->getFrame()); + // Need to do the NetDisconnectPlayerCommandMsg creation and sending here. + NetDisconnectPlayerCommandMsg *disconnectMsg = newInstance(NetDisconnectPlayerCommandMsg); + disconnectMsg->setDisconnectSlot(m_localSlot); + disconnectMsg->setDisconnectFrame(TheGameLogic->getFrame()); + + // Ensure the message has a valid command ID before logging/sending. + if (DoesCommandRequireACommandID(disconnectMsg->getNetCommandType())) { + if (disconnectMsg->getID() == 0) { + disconnectMsg->setID(GenerateNextCommandID()); + } + } + + // Log after ID assignment so the log shows the real command id. DEBUG_LOG(("DBG_DISCONNECT: CREATED %s:%d id=%d slot=%d setFrame=%u localFrame=%u", - __FILE__, __LINE__, disconnectMsg->getID(), disconnectMsg->getDisconnectSlot(), disconnectMsg->getDisconnectFrame(), TheGameLogic->getFrame())); - disconnectMsg->setPlayerID(m_localSlot); - if (DoesCommandRequireACommandID(disconnectMsg->getNetCommandType())) { - disconnectMsg->setID(GenerateNextCommandID()); - } - DEBUG_LOG(("ConnectionManager::disconnectLocalPlayer - about to send disconnect command")); - sendLocalCommandDirect(disconnectMsg, 0xff ^ (1 << m_localSlot)); + __FILE__, __LINE__, disconnectMsg->getID(), disconnectMsg->getDisconnectSlot(), disconnectMsg->getDisconnectFrame(), TheGameLogic->getFrame())); + + disconnectMsg->setPlayerID(m_localSlot); + + DEBUG_LOG(("ConnectionManager::disconnectLocalPlayer - about to send disconnect command")); + sendLocalCommandDirect(disconnectMsg, 0xff ^ (1 << m_localSlot)); + + // Give the transport a chance to actually send the queued packet(s) before we tear down. + // Temporary test: wait up to 500ms for the outgoing queue to drain. + DEBUG_LOG(("DBG_LOCALDISCONNECT: waiting for send queue to drain %s:%d", __FILE__, __LINE__)); + const int kMaxWaitMs = 500; + int waited = 0; + while (waited < kMaxWaitMs) { + int pending = 0; + if (m_netCommandList) { + // getCount() should return number of queued outgoing commands; adjust if your API differs. + pending = m_netCommandList->getCount(); + } + // If no pending commands, break early. + if (pending == 0) { + break; + } + Sleep(10); // short sleep to yield; remove or replace with proper non-blocking wait in production. + waited += 10; + } + DEBUG_LOG(("DBG_LOCALDISCONNECT: done waiting, waited=%dms pending=%d %s:%d", waited, m_netCommandList ? m_netCommandList->getCount() : 0, __FILE__, __LINE__)); - DEBUG_LOG(("ConnectionManager::disconnectLocalPlayer - about to flush connections")); - flushConnections(); // need to do this so our packet actually gets sent before the connections are deleted. - DEBUG_LOG(("ConnectionManager::disconnectLocalPlayer - done flushing connections")); + DEBUG_LOG(("ConnectionManager::disconnectLocalPlayer - about to flush connections")); + flushConnections(); // ensure transport flush as well + DEBUG_LOG(("ConnectionManager::disconnectLocalPlayer - done flushing connections")); - disconnectMsg->detach(); + disconnectMsg->detach(); #if RTS_GENERALS - // if we get here, we hit Quit on the disconnect screen. Mark everyone as having disconnected from us - // so the online stats can give us appropriate feedback. - if (TheGameInfo) - { - for (Int i = 0; i < MAX_SLOTS; ++i) - { - GameSlot *gSlot = TheGameInfo->getSlot( i ); - if (gSlot && !gSlot->lastFrameInGame()) - { - gSlot->markAsDisconnected(); - } - } - } + // if we get here, we hit Quit on the disconnect screen. Mark everyone as having disconnected from us + // so the online stats can give us appropriate feedback. + if (TheGameInfo) + { + for (Int i = 0; i < MAX_SLOTS; ++i) + { + GameSlot *gSlot = TheGameInfo->getSlot( i ); + if (gSlot && !gSlot->lastFrameInGame()) + { + gSlot->markAsDisconnected(); + } + } + } #endif - disconnectLocalPlayer(); + disconnectLocalPlayer(); } void ConnectionManager::disconnectLocalPlayer() { - // kill the frame data and the connections for all the other players. - DEBUG_LOG(("ConnectionManager::disconnectLocalPlayer()")); - for (Int i = 0; i < MAX_SLOTS; ++i) { - if (i != m_localSlot) { - disconnectPlayer(i); - } - } + // kill the frame data and the connections for all the other players. + DEBUG_LOG(("ConnectionManager::disconnectLocalPlayer()")); + for (Int i = 0; i < MAX_SLOTS; ++i) { + if (i != m_localSlot) { + disconnectPlayer(i); + } + } } /** * Takes all the commands that are ready to send and sends them right now. */ void ConnectionManager::flushConnections() { - for (Int i = 0; i < MAX_SLOTS; ++i) { - if (m_connections[i] != NULL) { -// DEBUG_LOG(("ConnectionManager::flushConnections - flushing connection to player %d", i)); - /* - if (m_connections[i]->isQueueEmpty()) { -// DEBUG_LOG(("ConnectionManager::flushConnections - connection queue empty")); - } - */ - m_connections[i]->doSend(); - } - } + for (Int i = 0; i < MAX_SLOTS; ++i) { + if (m_connections[i] != NULL) { +// DEBUG_LOG(("ConnectionManager::flushConnections - flushing connection to player %d", i)); + /* + if (m_connections[i]->isQueueEmpty()) { +// DEBUG_LOG(("ConnectionManager::flushConnections - connection queue empty")); + } + */ + m_connections[i]->doSend(); + } + } - if (m_transport != NULL) { - m_transport->doSend(); - } + if (m_transport != NULL) { + m_transport->doSend(); + } } void ConnectionManager::resendPendingCommands() { From d803eeab1aa0e2d1da83c7c679b34975a663c981 Mon Sep 17 00:00:00 2001 From: MrS-ibra Date: Thu, 25 Dec 2025 11:14:43 +0300 Subject: [PATCH 10/13] Update ConnectionManager.cpp --- .../Source/GameNetwork/ConnectionManager.cpp | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp b/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp index 16c727b7c21..14ff90a795c 100644 --- a/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp +++ b/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp @@ -1945,32 +1945,44 @@ void ConnectionManager::quitGame() { // Log after ID assignment so the log shows the real command id. DEBUG_LOG(("DBG_DISCONNECT: CREATED %s:%d id=%d slot=%d setFrame=%u localFrame=%u", - __FILE__, __LINE__, disconnectMsg->getID(), disconnectMsg->getDisconnectSlot(), disconnectMsg->getDisconnectFrame(), TheGameLogic->getFrame())); + __FILE__, __LINE__, disconnectMsg->getID(), disconnectMsg->getDisconnectSlot(), + disconnectMsg->getDisconnectFrame(), TheGameLogic->getFrame())); disconnectMsg->setPlayerID(m_localSlot); DEBUG_LOG(("ConnectionManager::disconnectLocalPlayer - about to send disconnect command")); sendLocalCommandDirect(disconnectMsg, 0xff ^ (1 << m_localSlot)); - // Give the transport a chance to actually send the queued packet(s) before we tear down. - // Temporary test: wait up to 500ms for the outgoing queue to drain. - DEBUG_LOG(("DBG_LOCALDISCONNECT: waiting for send queue to drain %s:%d", __FILE__, __LINE__)); + // Temporary test: wait up to 500ms for the outgoing queues on each connection to drain. + DEBUG_LOG(("DBG_LOCALDISCONNECT: waiting for send queues to drain %s:%d", __FILE__, __LINE__)); const int kMaxWaitMs = 500; int waited = 0; while (waited < kMaxWaitMs) { - int pending = 0; - if (m_netCommandList) { - // getCount() should return number of queued outgoing commands; adjust if your API differs. - pending = m_netCommandList->getCount(); + bool anyPending = false; + + // Check each connection's outgoing queue. Use isQueueEmpty() which exists on Connection. + for (Int ci = 0; ci < MAX_SLOTS; ++ci) { + if (m_connections[ci] != NULL) { + if (!m_connections[ci]->isQueueEmpty()) { + anyPending = true; + break; + } + } + } + + // If transport has a send buffer, give it a chance to flush as well. + if (m_transport != NULL) { + m_transport->doSend(); } - // If no pending commands, break early. - if (pending == 0) { + + if (!anyPending) { break; } - Sleep(10); // short sleep to yield; remove or replace with proper non-blocking wait in production. + + Sleep(10); // short sleep to yield; replace with proper non-blocking wait in production. waited += 10; } - DEBUG_LOG(("DBG_LOCALDISCONNECT: done waiting, waited=%dms pending=%d %s:%d", waited, m_netCommandList ? m_netCommandList->getCount() : 0, __FILE__, __LINE__)); + DEBUG_LOG(("DBG_LOCALDISCONNECT: done waiting, waited=%dms %s:%d", waited, __FILE__, __LINE__)); DEBUG_LOG(("ConnectionManager::disconnectLocalPlayer - about to flush connections")); flushConnections(); // ensure transport flush as well From dad3c67f75c9fd4708ca482907596169f3230cea Mon Sep 17 00:00:00 2001 From: MrS-ibra Date: Thu, 25 Dec 2025 11:36:33 +0300 Subject: [PATCH 11/13] Update DisconnectManager.cpp --- .../Source/GameNetwork/DisconnectManager.cpp | 53 +++++++++++-------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp b/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp index c00908466ae..ccb3f1ee211 100644 --- a/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp +++ b/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp @@ -729,20 +729,27 @@ Bool DisconnectManager::hasPlayerTimedOut(Int slot) { // we will be after everyone is getting disconnected) void DisconnectManager::sendPlayerDestruct(Int slot, ConnectionManager *conMgr) { UnsignedShort currentID = 0; - if (DoesCommandRequireACommandID(NETCOMMANDTYPE_DESTROYPLAYER)) - { + if (DoesCommandRequireACommandID(NETCOMMANDTYPE_DESTROYPLAYER)) { currentID = GenerateNextCommandID(); } - DEBUG_LOG(("Queueing DestroyPlayer %d for frame %d on frame %d as command %d", - slot, TheNetwork->getExecutionFrame()+1, TheGameLogic->getFrame(), currentID)); + DEBUG_LOG(("DBG_SENDDESTRUCT: Queueing DestroyPlayer slot=%d id=%d execFrame=%d localFrame=%d", + slot, currentID, TheNetwork->getExecutionFrame()+1, TheGameLogic->getFrame())); NetDestroyPlayerCommandMsg *netmsg = newInstance(NetDestroyPlayerCommandMsg); netmsg->setExecutionFrame(TheNetwork->getExecutionFrame()+1); netmsg->setPlayerID(conMgr->getLocalPlayerID()); netmsg->setID(currentID); netmsg->setPlayerIndex(slot); + + // Send via normal routing (packet router) so the router can include it in its frame data. conMgr->sendLocalCommand(netmsg); + + // Also attempt a direct send to remaining peers to reduce the chance of the message + // being lost if the router path is interrupted. Use a relay that targets everyone except local. + UnsignedByte relay = 0xff & ~(1 << conMgr->getLocalPlayerID()); + conMgr->sendLocalCommandDirect(netmsg, relay); + netmsg->detach(); } @@ -762,31 +769,31 @@ Bool DisconnectManager::isPlayerVotedOut(Int slot, ConnectionManager *conMgr) { } UnsignedInt DisconnectManager::getMaxDisconnectFrame() { - UnsignedInt retval = 0; - for (Int i = 0; i < MAX_SLOTS; ++i) { - if (m_disconnectFrames[i] > retval) { - retval = m_disconnectFrames[i]; - } - } - return retval; + UnsignedInt retval = 0; + for (Int i = 0; i < MAX_SLOTS; ++i) { + if (m_disconnectFrames[i] > retval) { + retval = m_disconnectFrames[i]; + } + } + return retval; } Bool DisconnectManager::isPlayerInGame(Int slot, ConnectionManager *conMgr) { - Int transSlot = untranslatedSlotPosition(slot, conMgr->getLocalPlayerID()); - DEBUG_ASSERTCRASH((transSlot >= 0) && (transSlot < MAX_SLOTS), ("invalid slot number")); - if (((transSlot < 0) || (transSlot >= MAX_SLOTS)) || conMgr->isPlayerConnected(transSlot) == FALSE) { - return FALSE; - } + Int transSlot = untranslatedSlotPosition(slot, conMgr->getLocalPlayerID()); + DEBUG_ASSERTCRASH((transSlot >= 0) && (transSlot < MAX_SLOTS), ("invalid slot number")); + if (((transSlot < 0) || (transSlot >= MAX_SLOTS)) || conMgr->isPlayerConnected(transSlot) == FALSE) { + return FALSE; + } - if (isPlayerVotedOut(slot, conMgr) == TRUE) { - return FALSE; - } + if (isPlayerVotedOut(slot, conMgr) == TRUE) { + return FALSE; + } - if (hasPlayerTimedOut(slot) == TRUE) { - return FALSE; - } + if (hasPlayerTimedOut(slot) == TRUE) { + return FALSE; + } - return TRUE; + return TRUE; } void DisconnectManager::playerHasAdvancedAFrame(Int slot, UnsignedInt frame) { From 9146190c9e63fbc708c92149bdf2e8db00a2d2af Mon Sep 17 00:00:00 2001 From: MrS-ibra Date: Thu, 25 Dec 2025 11:55:39 +0300 Subject: [PATCH 12/13] Update DisconnectManager.cpp --- .../Source/GameNetwork/DisconnectManager.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp b/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp index ccb3f1ee211..d8fcba7fb7c 100644 --- a/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp +++ b/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp @@ -736,20 +736,29 @@ void DisconnectManager::sendPlayerDestruct(Int slot, ConnectionManager *conMgr) DEBUG_LOG(("DBG_SENDDESTRUCT: Queueing DestroyPlayer slot=%d id=%d execFrame=%d localFrame=%d", slot, currentID, TheNetwork->getExecutionFrame()+1, TheGameLogic->getFrame())); + // Primary message for the packet-router path NetDestroyPlayerCommandMsg *netmsg = newInstance(NetDestroyPlayerCommandMsg); netmsg->setExecutionFrame(TheNetwork->getExecutionFrame()+1); netmsg->setPlayerID(conMgr->getLocalPlayerID()); netmsg->setID(currentID); netmsg->setPlayerIndex(slot); - // Send via normal routing (packet router) so the router can include it in its frame data. + // Enqueue via normal routing so the packet router can include it in frame data conMgr->sendLocalCommand(netmsg); - // Also attempt a direct send to remaining peers to reduce the chance of the message - // being lost if the router path is interrupted. Use a relay that targets everyone except local. + // Create a separate instance for a direct send to remaining peers to reduce the chance + // of the message being lost if the router path is interrupted. This avoids ownership/race issues. + NetDestroyPlayerCommandMsg *netmsgDirect = newInstance(NetDestroyPlayerCommandMsg); + netmsgDirect->setExecutionFrame(netmsg->getExecutionFrame()); + netmsgDirect->setPlayerID(netmsg->getPlayerID()); + netmsgDirect->setID(netmsg->getID()); + netmsgDirect->setPlayerIndex(netmsg->getPlayerIndex()); + UnsignedByte relay = 0xff & ~(1 << conMgr->getLocalPlayerID()); - conMgr->sendLocalCommandDirect(netmsg, relay); + conMgr->sendLocalCommandDirect(netmsgDirect, relay); + // Detach both instances (balance newInstance) + netmsgDirect->detach(); netmsg->detach(); } From 8c1011cc1089f0f6d7ab5a3ba3679ac9c5ba46c8 Mon Sep 17 00:00:00 2001 From: MrS-ibra Date: Thu, 25 Dec 2025 12:13:48 +0300 Subject: [PATCH 13/13] Update Connection.cpp --- Core/GameEngine/Source/GameNetwork/Connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/GameEngine/Source/GameNetwork/Connection.cpp b/Core/GameEngine/Source/GameNetwork/Connection.cpp index 68128ff5204..8327fc86909 100644 --- a/Core/GameEngine/Source/GameNetwork/Connection.cpp +++ b/Core/GameEngine/Source/GameNetwork/Connection.cpp @@ -31,7 +31,7 @@ #include "../NGMP_include.h" #if defined(GENERALS_ONLINE) -enum { MaxQuitFlushTime = 5000 }; // wait this many milliseconds at most to retry things before quitting +enum { MaxQuitFlushTime = 30000 }; // wait this many milliseconds at most to retry things before quitting #else enum { MaxQuitFlushTime = 30000 }; // wait this many milliseconds at most to retry things before quitting #endif