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 diff --git a/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp b/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp index ae432e692bf..14ff90a795c 100644 --- a/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp +++ b/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp @@ -1931,71 +1931,113 @@ 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()); - 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)); - - //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")); - - disconnectMsg->detach(); + // 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); + + DEBUG_LOG(("ConnectionManager::disconnectLocalPlayer - about to send disconnect command")); + sendLocalCommandDirect(disconnectMsg, 0xff ^ (1 << m_localSlot)); + + // 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) { + 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 (!anyPending) { + break; + } + + Sleep(10); // short sleep to yield; replace with proper non-blocking wait in production. + waited += 10; + } + 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 + DEBUG_LOG(("ConnectionManager::disconnectLocalPlayer - done flushing connections")); + + 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(); - } - } - - if (m_transport != NULL) { - m_transport->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(); + } } void ConnectionManager::resendPendingCommands() { @@ -2517,8 +2559,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 +2580,6 @@ void ConnectionManager::notifyOthersOfNewFrame(UnsignedInt frame) { m_disconnectManager->processDisconnectCommand(ref, this); deleteInstance(ref); - msg->detach(); } void ConnectionManager::sendFrameDataToPlayer(UnsignedInt playerID, UnsignedInt startingFrame) { @@ -2564,12 +2603,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); diff --git a/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp b/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp index 94beda05da6..d8fcba7fb7c 100644 --- a/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp +++ b/Core/GameEngine/Source/GameNetwork/DisconnectManager.cpp @@ -559,225 +559,250 @@ 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()); - - if (transSlot != -1) { - // Ignore any disconnect commands that tell us to disconnect ourselves. - - // Get the disconnecting player off the disconnect window. - 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")); - - if (retcode == PLAYERLEAVECODE_PACKETROUTER) { - DEBUG_LOG(("DisconnectManager::disconnectPlayer - disconnecting player was packet router.")); - - conMgr->resendPendingCommands(); - } - } + 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)); + + // 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); + + 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.")); + + conMgr->resendPendingCommands(); + } } 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); - msg->setPlayerID(conMgr->getLocalPlayerID()); - if (DoesCommandRequireACommandID(msg->getNetCommandType())) { - msg->setID(GenerateNextCommandID()); - } + // 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())); - 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); - if (packetRouterSlot == localSlot) { - return TRUE; - } + // 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; + } - 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(("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); - DEBUG_LOG(("Queueing DestroyPlayer %d for frame %d on frame %d as command %d", - slot, TheNetwork->getExecutionFrame()+1, TheGameLogic->getFrame(), currentID)); + // Enqueue via normal routing so the packet router can include it in frame data + conMgr->sendLocalCommand(netmsg); - NetDestroyPlayerCommandMsg *netmsg = newInstance(NetDestroyPlayerCommandMsg); - netmsg->setExecutionFrame(TheNetwork->getExecutionFrame()+1); - netmsg->setPlayerID(conMgr->getLocalPlayerID()); - netmsg->setID(currentID); - netmsg->setPlayerIndex(slot); - conMgr->sendLocalCommand(netmsg); - netmsg->detach(); + // 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(netmsgDirect, relay); + + // Detach both instances (balance newInstance) + netmsgDirect->detach(); + 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() { - 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) {