/*
** Command & Conquer Generals(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Recorder.h"
#include "GameClient/DisconnectMenu.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/GameLogic.h"
#include "GameNetwork/DisconnectManager.h"
#include "GameNetwork/NetworkInterface.h"
#include "GameNetwork/NetworkUtil.h"
#include "GameNetwork/GameSpy/PingThread.h"
#include "GameNetwork/GameSpy/GSConfig.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
DisconnectManager::DisconnectManager()
{
// Added By Sadullah Nader
// Initializations missing and needed
Int i;
m_currentPacketRouterIndex = 0;
m_lastFrame = 0;
m_lastFrameTime = 0;
m_lastKeepAliveSendTime = 0;
m_haveNotifiedOtherPlayersOfCurrentFrame = FALSE;
m_timeOfDisconnectScreenOn = 0;
for( i = 0; i < MAX_SLOTS; ++i) {
m_packetRouterFallback[i] = 0;
}
m_packetRouterTimeout = 0;
for( i = 0; i < MAX_SLOTS -1; ++i) {
m_playerTimeouts[i] = 0;
}
for( i = 0; i < MAX_SLOTS; ++i) {
for (Int j = 0; j < MAX_SLOTS; ++j) {
m_playerVotes[i][j].vote = FALSE;
m_playerVotes[i][j].frame = 0;
}
}
}
DisconnectManager::~DisconnectManager() {
}
void DisconnectManager::init() {
TheDisconnectMenu->hideScreen(); // make sure the screen starts out hidden.
m_lastFrame = 0;
m_lastFrameTime = -1;
m_lastKeepAliveSendTime = -1;
m_disconnectState = DISCONNECTSTATETYPE_SCREENOFF;
m_currentPacketRouterIndex = 0;
m_timeOfDisconnectScreenOn = 0;
for (Int i = 0; i < MAX_SLOTS; ++i) {
for (Int j = 0; j < MAX_SLOTS; ++j) {
m_playerVotes[i][j].vote = FALSE;
m_playerVotes[i][j].frame = 0;
}
}
for (i = 0; i < MAX_SLOTS; ++i) {
m_disconnectFrames[i] = 0;
m_disconnectFramesReceived[i] = FALSE;
}
m_pingFrame = 0;
m_pingsSent = 0;
m_pingsRecieved = 0;
}
void DisconnectManager::update(ConnectionManager *conMgr) {
if (m_lastFrameTime == -1) {
m_lastFrameTime = timeGetTime();
}
// The game logic stalls on the frame we are currently waiting for commands on,
// so we have to check for the current logic frame being one higher than
// the last one we had the commands ready for.
if (TheGameLogic->getFrame() == m_lastFrame) {
time_t curTime = timeGetTime();
if ((curTime - m_lastFrameTime) > TheGlobalData->m_networkDisconnectTime) {
if (m_disconnectState == DISCONNECTSTATETYPE_SCREENOFF) {
turnOnScreen(conMgr);
}
sendKeepAlive(conMgr);
}
} else {
nextFrame(TheGameLogic->getFrame(), conMgr);
}
if (m_disconnectState != DISCONNECTSTATETYPE_SCREENOFF) {
updateDisconnectStatus(conMgr);
// check to see if we need to send pings
if (m_pingFrame < TheGameLogic->getFrame())
{
time_t curTime = timeGetTime();
if ((curTime - m_lastFrameTime) > 10000) /// @todo: plug in some better measure here
{
m_pingFrame = TheGameLogic->getFrame();
m_pingsSent = 0;
m_pingsRecieved = 0;
// Send the pings
if (ThePinger)
{
//use next ping server
static int serverIndex = 0;
serverIndex++;
if( serverIndex >= TheGameSpyConfig->getPingServers().size() )
serverIndex = 0; //wrap back to first ping server
std::list::iterator it = TheGameSpyConfig->getPingServers().begin();
for( int i = 0; i < serverIndex; i++ )
it++;
PingRequest req;
req.hostname = it->str();
req.repetitions = 5;
req.timeout = 2000;
m_pingsSent = req.repetitions;
ThePinger->addRequest(req);
DEBUG_LOG(("DisconnectManager::update() - requesting %d pings of %d from %s\n",
req.repetitions, req.timeout, req.hostname.c_str()));
}
}
}
// update the ping thread, tracking pings if we are on the same frame
if (ThePinger)
{
PingResponse resp;
while (ThePinger->getResponse(resp))
{
if (m_pingFrame != TheGameLogic->getFrame())
{
// wrong frame - we're not pinging yet
DEBUG_LOG(("DisconnectManager::update() - discarding ping of %d from %s (%d reps)\n",
resp.avgPing, resp.hostname.c_str(), resp.repetitions));
}
else
{
// right frame
DEBUG_LOG(("DisconnectManager::update() - keeping ping of %d from %s (%d reps)\n",
resp.avgPing, resp.hostname.c_str(), resp.repetitions));
if (resp.avgPing < 2000)
{
m_pingsRecieved += resp.repetitions;
}
}
}
}
}
}
UnsignedInt DisconnectManager::getPingFrame()
{
return m_pingFrame;
}
Int DisconnectManager::getPingsSent()
{
return m_pingsSent;
}
Int DisconnectManager::getPingsRecieved()
{
return m_pingsRecieved;
}
void DisconnectManager::updateDisconnectStatus(ConnectionManager *conMgr) {
for (Int i = 0; i < MAX_SLOTS; ++i) {
if (conMgr->isPlayerConnected(i)) {
Int slot = translatedSlotPosition(i, conMgr->getLocalPlayerID());
if (slot != -1) {
time_t curTime = timeGetTime();
time_t newTime = TheGlobalData->m_networkPlayerTimeoutTime - (curTime - m_playerTimeouts[slot]);
// if someone is more than 2/3 timed out, lets get our frame numbers sync'd up. Also if someone is voted out
// lets do the same thing.
if (m_haveNotifiedOtherPlayersOfCurrentFrame == FALSE) {
if ((newTime < TheGlobalData->m_networkPlayerTimeoutTime / 3) || (isPlayerVotedOut(slot, conMgr) == TRUE)) {
TheNetwork->notifyOthersOfCurrentFrame();
m_haveNotifiedOtherPlayersOfCurrentFrame = TRUE;
}
DEBUG_LOG(("DisconnectManager::updateDisconnectStatus - curTime = %d, m_timeOfDisconnectScreenOn = %d, curTime - m_timeOfDisconnectScreenOn = %d\n", curTime, m_timeOfDisconnectScreenOn, curTime - m_timeOfDisconnectScreenOn));
if (m_timeOfDisconnectScreenOn != 0) {
if ((curTime - m_timeOfDisconnectScreenOn) > TheGlobalData->m_networkDisconnectScreenNotifyTime) {
TheNetwork->notifyOthersOfCurrentFrame();
m_haveNotifiedOtherPlayersOfCurrentFrame = TRUE;
}
}
}
if ((newTime < 0) || (isPlayerVotedOut(slot, conMgr) == TRUE)) {
newTime = 0;
DEBUG_LOG(("DisconnectManager::updateDisconnectStatus - player %d(translated slot %d) has been voted out or timed out\n", i, slot));
if (allOnSameFrame(conMgr) == TRUE) {
DEBUG_LOG(("DisconnectManager::updateDisconnectStatus - all on same frame\n"));
if (isLocalPlayerNextPacketRouter(conMgr) == TRUE) {
DEBUG_LOG(("DisconnectManager::updateDisconnectStatus - local player is next packet router\n"));
DEBUG_LOG(("DisconnectManager::updateDisconnectStatus - about to do the disconnect procedure for player %d\n", i));
sendDisconnectCommand(i, conMgr);
disconnectPlayer(i, conMgr);
sendPlayerDestruct(i, conMgr);
} else {
DEBUG_LOG(("DisconnectManager::updateDisconnectStatus - local player is not the next packet router\n"));
}
} else {
DEBUG_LOG(("DisconnectManager::updateDisconnectStatus - not all on same frame\n"));
}
}
TheDisconnectMenu->setPlayerTimeoutTime(slot, newTime);
}
}
}
}
void DisconnectManager::updateWaitForPacketRouter(ConnectionManager *conMgr) {
/*
time_t curTime = timeGetTime();
time_t newTime = TheGlobalData->m_networkPlayerTimeoutTime - (curTime - m_packetRouterTimeout);
if (newTime < 0) {
newTime = 0;
// The guy that we were hoping would be the new packet router isn't. We're screwed, get out of the game.
DEBUG_LOG(("DisconnectManager::updateWaitForPacketRouter - timed out waiting for new packet router, quitting game\n"));
TheNetwork->quitGame();
}
TheDisconnectMenu->setPacketRouterTimeoutTime(newTime);
*/
}
void DisconnectManager::processDisconnectCommand(NetCommandRef *ref, ConnectionManager *conMgr) {
NetCommandMsg *msg = ref->getCommand();
if (msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTKEEPALIVE) {
processDisconnectKeepAlive(msg, conMgr);
} else if (msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTPLAYER) {
processDisconnectPlayer(msg, conMgr);
} else if (msg->getNetCommandType() == NETCOMMANDTYPE_PACKETROUTERQUERY) {
processPacketRouterQuery(msg, conMgr);
} else if (msg->getNetCommandType() == NETCOMMANDTYPE_PACKETROUTERACK) {
processPacketRouterAck(msg, conMgr);
} else if (msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTVOTE) {
processDisconnectVote(msg, conMgr);
} else if (msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTFRAME) {
processDisconnectFrame(msg, conMgr);
} else if (msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTSCREENOFF) {
processDisconnectScreenOff(msg, conMgr);
}
}
void DisconnectManager::processDisconnectKeepAlive(NetCommandMsg *msg, ConnectionManager *conMgr) {
NetDisconnectKeepAliveCommandMsg *cmdMsg = (NetDisconnectKeepAliveCommandMsg *)msg;
Int slot = translatedSlotPosition(cmdMsg->getPlayerID(), conMgr->getLocalPlayerID());
if (slot != -1) {
resetPlayerTimeout(slot);
}
}
void DisconnectManager::processDisconnectPlayer(NetCommandMsg *msg, ConnectionManager *conMgr) {
NetDisconnectPlayerCommandMsg *cmdMsg = (NetDisconnectPlayerCommandMsg *)msg;
DEBUG_LOG(("DisconnectManager::processDisconnectPlayer - Got disconnect player command from player %d. Disconnecting player %d on frame %d\n", msg->getPlayerID(), cmdMsg->getDisconnectSlot(), cmdMsg->getDisconnectFrame()));
DEBUG_ASSERTCRASH(TheGameLogic->getFrame() == cmdMsg->getDisconnectFrame(), ("disconnecting player on the wrong frame!!!"));
disconnectPlayer(cmdMsg->getDisconnectSlot(), conMgr);
}
void DisconnectManager::processPacketRouterQuery(NetCommandMsg *msg, ConnectionManager *conMgr) {
NetPacketRouterQueryCommandMsg *cmdMsg = (NetPacketRouterQueryCommandMsg *)msg;
DEBUG_LOG(("DisconnectManager::processPacketRouterQuery - got a packet router query command from player %d\n", msg->getPlayerID()));
if (conMgr->getPacketRouterSlot() == conMgr->getLocalPlayerID()) {
NetPacketRouterAckCommandMsg *ackmsg = newInstance(NetPacketRouterAckCommandMsg);
ackmsg->setPlayerID(conMgr->getLocalPlayerID());
if (DoesCommandRequireACommandID(ackmsg->getNetCommandType()) == TRUE) {
ackmsg->setID(GenerateNextCommandID());
}
DEBUG_LOG(("DisconnectManager::processPacketRouterQuery - We are the new packet router, responding with an packet router ack. Local player is %d\n", ackmsg->getPlayerID()));
conMgr->sendLocalCommandDirect(ackmsg, 1 << cmdMsg->getPlayerID());
ackmsg->detach();
} else {
DEBUG_LOG(("DisconnectManager::processPacketRouterQuery - We are NOT the new packet router, these are not the droids you're looking for.\n"));
}
}
void DisconnectManager::processPacketRouterAck(NetCommandMsg *msg, ConnectionManager *conMgr) {
NetPacketRouterAckCommandMsg *cmdMsg = (NetPacketRouterAckCommandMsg *)msg;
DEBUG_LOG(("DisconnectManager::processPacketRouterAck - got packet router ack command from player %d\n", msg->getPlayerID()));
if (conMgr->getPacketRouterSlot() == cmdMsg->getPlayerID()) {
DEBUG_LOG(("DisconnectManager::processPacketRouterAck - packet router command is from who it should be.\n"));
resetPacketRouterTimeout();
Int currentPacketRouterSlot = conMgr->getPacketRouterSlot();
Int currentPacketRouterIndex = 0;
while ((currentPacketRouterSlot != conMgr->getPacketRouterFallbackSlot(currentPacketRouterIndex)) && (currentPacketRouterIndex < MAX_SLOTS)) {
++currentPacketRouterIndex;
}
DEBUG_ASSERTCRASH((currentPacketRouterIndex < MAX_SLOTS), ("Invalid packet router index"));
DEBUG_LOG(("DisconnectManager::processPacketRouterAck - New packet router confirmed, resending pending commands\n"));
conMgr->resendPendingCommands();
m_currentPacketRouterIndex = currentPacketRouterIndex;
DEBUG_LOG(("DisconnectManager::processPacketRouterAck - Setting disconnect state to screen on.\n"));
m_disconnectState = DISCONNECTSTATETYPE_SCREENON; ///< set it to screen on so that the next call to AllCommandsReady can set up everything for the next frame properly.
}
}
void DisconnectManager::processDisconnectVote(NetCommandMsg *msg, ConnectionManager *conMgr) {
NetDisconnectVoteCommandMsg *cmdMsg = (NetDisconnectVoteCommandMsg *)msg;
DEBUG_LOG(("DisconnectManager::processDisconnectVote - Got a disconnect vote for player %d command from player %d\n", cmdMsg->getSlot(), cmdMsg->getPlayerID()));
Int transSlot = translatedSlotPosition(msg->getPlayerID(), conMgr->getLocalPlayerID());
if (isPlayerInGame(transSlot, conMgr) == FALSE) {
// if they've been timed out, voted out, disconnected, don't count their vote.
return;
}
applyDisconnectVote(cmdMsg->getSlot(), cmdMsg->getVoteFrame(), cmdMsg->getPlayerID(), conMgr);
}
void DisconnectManager::processDisconnectFrame(NetCommandMsg *msg, ConnectionManager *conMgr) {
NetDisconnectFrameCommandMsg *cmdMsg = (NetDisconnectFrameCommandMsg *)msg;
UnsignedInt playerID = cmdMsg->getPlayerID();
if (m_disconnectFrames[playerID] >= cmdMsg->getDisconnectFrame()) {
// this message isn't valid, we have a disconnect frame that is later than this already.
return;
}
if (m_disconnectFramesReceived[playerID] == TRUE) {
DEBUG_LOG(("DisconnectManager::processDisconnectFrame - Got two disconnect frames without an intervening disconnect screen off command from player %d. Frames are %d and %d\n", playerID, m_disconnectFrames[playerID], cmdMsg->getDisconnectFrame()));
}
DEBUG_LOG(("DisconnectManager::processDisconnectFrame - about to call resetPlayersVotes for player %d\n", playerID));
resetPlayersVotes(playerID, cmdMsg->getDisconnectFrame()-1, conMgr);
m_disconnectFrames[playerID] = cmdMsg->getDisconnectFrame();
m_disconnectFramesReceived[playerID] = TRUE;
DEBUG_LOG(("DisconnectManager::processDisconnectFrame - Got a disconnect frame for player %d, frame = %d, local player is %d, local disconnect frame = %d, command id = %d\n", cmdMsg->getPlayerID(), cmdMsg->getDisconnectFrame(), conMgr->getLocalPlayerID(), m_disconnectFrames[conMgr->getLocalPlayerID()], cmdMsg->getID()));
if (playerID == conMgr->getLocalPlayerID()) {
DEBUG_LOG(("DisconnectManager::processDisconnectFrame - player %d is the local player\n", playerID));
// we just got the message from the local player, check to see if we need to send
// commands to anyone we already have heard from.
for (Int i = 0; i < MAX_SLOTS; ++i) {
if (i != playerID) {
Int transSlot = translatedSlotPosition(i, conMgr->getLocalPlayerID());
if (isPlayerInGame(transSlot, conMgr) == TRUE) {
if ((m_disconnectFrames[i] < m_disconnectFrames[playerID]) && (m_disconnectFramesReceived[i] == TRUE)) {
DEBUG_LOG(("DisconnectManager::processDisconnectFrame - I have more frames than player %d, my frame = %d, their frame = %d\n", i, m_disconnectFrames[conMgr->getLocalPlayerID()], m_disconnectFrames[i]));
conMgr->sendFrameDataToPlayer(i, m_disconnectFrames[i]);
}
}
}
}
} else if ((m_disconnectFrames[playerID] < m_disconnectFrames[conMgr->getLocalPlayerID()]) && (m_disconnectFramesReceived[playerID] == TRUE)) {
DEBUG_LOG(("DisconnectManager::processDisconnectFrame - I have more frames than player %d, my frame = %d, their frame = %d\n", playerID, m_disconnectFrames[conMgr->getLocalPlayerID()], m_disconnectFrames[playerID]));
conMgr->sendFrameDataToPlayer(playerID, m_disconnectFrames[playerID]);
}
}
void DisconnectManager::processDisconnectScreenOff(NetCommandMsg *msg, ConnectionManager *conMgr) {
NetDisconnectScreenOffCommandMsg *cmdMsg = (NetDisconnectScreenOffCommandMsg *)msg;
UnsignedInt playerID = cmdMsg->getPlayerID();
DEBUG_LOG(("DisconnectManager::processDisconnectScreenOff - got a screen off command from player %d for frame %d\n", cmdMsg->getPlayerID(), cmdMsg->getNewFrame()));
if ((playerID < 0) || (playerID >= MAX_SLOTS)) {
return;
}
UnsignedInt newFrame = cmdMsg->getNewFrame();
if (newFrame >= m_disconnectFrames[playerID]) {
DEBUG_LOG(("DisconnectManager::processDisconnectScreenOff - resetting the disconnect screen status for player %d\n", playerID));
m_disconnectFramesReceived[playerID] = FALSE;
m_disconnectFrames[playerID] = newFrame; // just in case we get packets out of order and the disconnect screen off message gets here before the disconnect frame message.
DEBUG_LOG(("DisconnectManager::processDisconnectScreenOff - about to call resetPlayersVotes for player %d\n", playerID));
resetPlayersVotes(playerID, cmdMsg->getNewFrame(), conMgr);
}
}
void DisconnectManager::applyDisconnectVote(Int slot, UnsignedInt frame, Int fromSlot, ConnectionManager *conMgr) {
m_playerVotes[slot][fromSlot].vote = TRUE;
m_playerVotes[slot][fromSlot].frame = frame;
Int numVotes = countVotesForPlayer(slot);
DEBUG_LOG(("DisconnectManager::applyDisconnectVote - added a vote to disconnect slot %d, from slot %d, for frame %d, current votes are %d\n", slot, fromSlot, frame, numVotes));
Int transSlot = translatedSlotPosition(slot, conMgr->getLocalPlayerID());
if (transSlot != -1) {
TheDisconnectMenu->updateVotes(transSlot, numVotes);
}
}
void DisconnectManager::nextFrame(UnsignedInt frame, ConnectionManager *conMgr) {
m_lastFrame = frame;
m_lastFrameTime = timeGetTime();
resetPlayerTimeouts(conMgr);
}
void DisconnectManager::allCommandsReady(UnsignedInt frame, ConnectionManager *conMgr, Bool waitForPacketRouter) {
if (m_disconnectState != DISCONNECTSTATETYPE_SCREENOFF) {
DEBUG_LOG(("DisconnectManager::allCommandsReady - setting screen state to off.\n"));
TheDisconnectMenu->hideScreen();
m_disconnectState = DISCONNECTSTATETYPE_SCREENOFF;
TheNetwork->notifyOthersOfNewFrame(frame);
// reset the votes since we're moving to a new frame.
for (Int i = 0; i < MAX_SLOTS; ++i) {
m_playerVotes[i][conMgr->getLocalPlayerID()].vote = FALSE;
}
DEBUG_LOG(("DisconnectManager::allCommandsReady - resetting m_timeOfDisconnectScreenOn\n"));
m_timeOfDisconnectScreenOn = 0;
}
}
Bool DisconnectManager::allowedToContinue() {
if (m_disconnectState != DISCONNECTSTATETYPE_SCREENOFF) {
return FALSE;
}
return TRUE;
}
void DisconnectManager::sendKeepAlive(ConnectionManager *conMgr) {
time_t curTime = timeGetTime();
if (((curTime - m_lastKeepAliveSendTime) > 500) || (m_lastKeepAliveSendTime == -1)) {
NetDisconnectKeepAliveCommandMsg *msg = newInstance(NetDisconnectKeepAliveCommandMsg);
msg->setPlayerID(conMgr->getLocalPlayerID());
if (DoesCommandRequireACommandID(msg->getNetCommandType()) == TRUE) {
msg->setID(GenerateNextCommandID());
}
conMgr->sendLocalCommandDirect(msg, 0xff ^ (1 << msg->getPlayerID()));
msg->detach();
m_lastKeepAliveSendTime = curTime;
}
}
void DisconnectManager::populateDisconnectScreen(ConnectionManager *conMgr) {
for (Int i = 0; i < MAX_SLOTS; ++i) {
UnicodeString name = conMgr->getPlayerName(i);
Int slot = translatedSlotPosition(i, conMgr->getLocalPlayerID());
if (slot != -1) {
TheDisconnectMenu->setPlayerName(slot, name);
Int numVotes = countVotesForPlayer(i);
TheDisconnectMenu->updateVotes(slot, numVotes);
}
}
}
Int DisconnectManager::translatedSlotPosition(Int slot, Int localSlot) {
if (slot < localSlot) {
return slot;
}
if (slot == localSlot) {
return -1;
}
return (slot - 1);
}
Int DisconnectManager::untranslatedSlotPosition(Int slot, Int localSlot) {
if (slot == -1) {
return localSlot;
}
if (slot < localSlot) {
return slot;
}
return (slot + 1);
}
void DisconnectManager::resetPlayerTimeouts(ConnectionManager *conMgr) {
// reset the player timeouts.
for (Int i = 0; i < MAX_SLOTS; ++i) {
Int slot = translatedSlotPosition(i, conMgr->getLocalPlayerID());
if (slot != -1) {
resetPlayerTimeout(slot);
}
}
}
void DisconnectManager::resetPlayerTimeout(Int slot) {
m_playerTimeouts[slot] = timeGetTime();
}
void DisconnectManager::resetPacketRouterTimeout() {
m_packetRouterTimeout = timeGetTime();
}
void DisconnectManager::turnOnScreen(ConnectionManager *conMgr) {
TheDisconnectMenu->showScreen();
DEBUG_LOG(("DisconnectManager::turnOnScreen - turning on screen on frame %d\n", TheGameLogic->getFrame()));
m_disconnectState = DISCONNECTSTATETYPE_SCREENON;
m_lastKeepAliveSendTime = -1;
populateDisconnectScreen(conMgr);
resetPlayerTimeouts(conMgr);
TheDisconnectMenu->hidePacketRouterTimeout();
m_haveNotifiedOtherPlayersOfCurrentFrame = FALSE;
m_timeOfDisconnectScreenOn = timeGetTime();
DEBUG_LOG(("DisconnectManager::turnOnScreen - turned on screen at time %d\n", m_timeOfDisconnectScreenOn));
}
void DisconnectManager::disconnectPlayer(Int slot, ConnectionManager *conMgr) {
DEBUG_LOG(("DisconnectManager::disconnectPlayer - Disconnecting slot number %d on frame %d\n", 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.\n"));
conMgr->resendPendingCommands();
}
}
}
void DisconnectManager::sendDisconnectCommand(Int slot, ConnectionManager *conMgr) {
DEBUG_LOG(("DisconnectManager::sendDisconnectCommand - Sending disconnect command for slot number %d\n", 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();
// 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());
}
conMgr->sendLocalCommand(msg);
DEBUG_LOG(("DisconnectManager::sendDisconnectCommand - Sending disconnect command for slot number %d for frame %d\n", slot, disconnectFrame));
msg->detach();
}
void DisconnectManager::sendVoteCommand(Int slot, ConnectionManager *conMgr) {
NetDisconnectVoteCommandMsg *msg = newInstance(NetDisconnectVoteCommandMsg);
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()));
msg->detach();
}
void DisconnectManager::voteForPlayerDisconnect(Int slot, ConnectionManager *conMgr) {
Int transSlot = untranslatedSlotPosition(slot, conMgr->getLocalPlayerID());
if (m_playerVotes[transSlot][conMgr->getLocalPlayerID()].vote == FALSE) {
m_playerVotes[transSlot][conMgr->getLocalPlayerID()].vote = TRUE;
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);
}
}
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"));
}
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 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);
}
if (packetRouterSlot == localSlot) {
return TRUE;
}
return FALSE;
}
Bool DisconnectManager::hasPlayerTimedOut(Int slot) {
if (slot == -1) {
return FALSE;
}
time_t newTime = TheGlobalData->m_networkPlayerTimeoutTime - (timeGetTime() - m_playerTimeouts[slot]);
if (newTime <= 0) {
return TRUE;
}
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();
}
DEBUG_LOG(("Queueing DestroyPlayer %d for frame %d on frame %d as command %d\n",
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();
}
// 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;
}
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;
}
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;
}
if (isPlayerVotedOut(slot, conMgr) == TRUE) {
return FALSE;
}
if (hasPlayerTimedOut(slot) == TRUE) {
return FALSE;
}
return TRUE;
}
void DisconnectManager::playerHasAdvancedAFrame(Int slot, UnsignedInt frame) {
// if they have advanced beyond the frame they had been previously disconnecting on.
if (frame >= m_disconnectFrames[slot]) {
m_disconnectFrames[slot] = frame; // just in case we get a disconnect frame command after this is called.
m_disconnectFramesReceived[slot] = FALSE;
}
}
Int DisconnectManager::countVotesForPlayer(Int slot) {
if ((slot < 0) || (slot >= MAX_SLOTS)) {
return 0;
}
Int retval = 0;
for (Int i = 0; i < MAX_SLOTS; ++i) {
// using TheGameLogic->getFrame() cause we might not have sent our disconnect frame yet.
if ((m_playerVotes[slot][i].vote == TRUE) && (m_playerVotes[slot][i].frame == TheGameLogic->getFrame())) {
++retval;
}
}
return retval;
}
void DisconnectManager::resetPlayersVotes(Int playerID, UnsignedInt frame, ConnectionManager *conMgr) {
DEBUG_LOG(("DisconnectManager::resetPlayersVotes - resetting player %d's votes on frame %d\n", playerID, frame));
// we need to reset this player's votes that happened before or on the given frame.
for(Int i = 0; i < MAX_SLOTS; ++i) {
if (m_playerVotes[i][playerID].frame <= frame) {
DEBUG_LOG(("DisconnectManager::resetPlayersVotes - resetting player %d's vote for player %d from frame %d on frame %d\n", playerID, i, m_playerVotes[i][playerID].frame, frame));
m_playerVotes[i][playerID].vote = FALSE;
}
}
Int numVotes = countVotesForPlayer(playerID);
DEBUG_LOG(("DisconnectManager::resetPlayersVotes - after adjusting votes, player %d has %d votes\n", playerID, numVotes));
Int transSlot = translatedSlotPosition(playerID, conMgr->getLocalPlayerID());
if (transSlot != -1) {
TheDisconnectMenu->updateVotes(transSlot, numVotes);
}
}