/* ** 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. // // // //////////////////////////////////////////////////////////////////////////////// // FILE: Network.cpp //////////////////////////////////////////////////// // Implementation of Network singleton // Author: Matthew D. Campbell, July 2001 /////////////////////////////////////////////////////////////////////////////// // SYSTEM INCLUDES //////////////////////////////////////////////////////////// // USER INCLUDES ////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #include "Common/GameEngine.h" #include "Common/MessageStream.h" #include "Common/Player.h" #include "Common/PlayerList.h" #include "GameNetwork/NetworkInterface.h" #include "GameNetwork/Udp.h" #include "GameNetwork/Transport.h" #include "strtok_r.h" #include "GameClient/Shell.h" #include "Common/CRCDebug.h" #include "GameLogic/GameLogic.h" #include "Common/RandomValue.h" #include "GameLogic/ScriptActions.h" #include "GameLogic/ScriptEngine.h" #include "Common/Recorder.h" #include "GameClient/MessageBox.h" #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif #if defined(DEBUG_CRC) Int NET_CRC_INTERVAL = 1; #else Int NET_CRC_INTERVAL = 100; #endif // DEFINES //////////////////////////////////////////////////////////////////// #define RESEND_INTERVAL 1 // PRIVATE TYPES ////////////////////////////////////////////////////////////// /** * Connection message - encapsulating info kept by the connection layer about each * packet. These structs make up the in/out buffers at the connection layer. */ #pragma pack(push, 1) struct ConnectionMessage { Int id; NetMessageFlags flags; UnsignedByte data[MAX_MESSAGE_LEN]; time_t lastSendTime; Int retries; Int length; }; #pragma pack(pop) static const int CmdMsgLen = 6; //< Minimum size of a command packet (Int + Unsigned Short) // PRIVATE DATA /////////////////////////////////////////////////////////////// // PUBLIC DATA //////////////////////////////////////////////////////////////// /// The Network singleton instance NetworkInterface *TheNetwork = NULL; // PRIVATE PROTOTYPES ///////////////////////////////////////////////////////// /** * The Network class is used to instantiate a singleton which * implements the interface to all Network operations such as message stream processing and network communications. */ class Network : public NetworkInterface { public: //--------------------------------------------------------------------------------------- // Setup / Teardown functions Network(); ~Network(); void init( void ); ///< Initialize or re-initialize the instance void reset( void ); ///< Reinitialize the network void update( void ); ///< Process command list void liteupdate( void ); ///< Do a lightweight update to send packets and pass messages. Bool deinit( void ); ///< Shutdown connections, release memory void setLocalAddress(UnsignedInt ip, UnsignedInt port); inline UnsignedInt getRunAhead(void) { return m_runAhead; } inline UnsignedInt getFrameRate(void) { return m_frameRate; } UnsignedInt getPacketArrivalCushion(void); ///< Returns the smallest packet arrival cushion since this was last called. Bool isFrameDataReady( void ); void parseUserList( const GameInfo *game ); void startGame(void); ///< Sets the network game frame counter to -1 void sendChat(UnicodeString text, Int playerMask); void sendDisconnectChat(UnicodeString text); void sendFile(AsciiString path, UnsignedByte playerMask, UnsignedShort commandID); UnsignedShort sendFileAnnounce(AsciiString path, UnsignedByte playerMask); Int getFileTransferProgress(Int playerID, AsciiString path); Bool areAllQueuesEmpty(void); void quitGame(); virtual void selfDestructPlayer(Int index); void voteForPlayerDisconnect(Int slot); virtual Bool isPacketRouter( void ); // Bandwidth metrics Real getIncomingBytesPerSecond( void ); Real getIncomingPacketsPerSecond( void ); Real getOutgoingBytesPerSecond( void ); Real getOutgoingPacketsPerSecond( void ); Real getUnknownBytesPerSecond( void ); Real getUnknownPacketsPerSecond( void ); // Multiplayer Load Progress Functions void updateLoadProgress( Int percent ); void loadProgressComplete( void ); void sendTimeOutGameStart( void ); #if defined(_INTERNAL) || defined(_DEBUG) // Disconnect screen testing virtual void toggleNetworkOn(); #endif // Exposing some info contained in the Connection Manager UnsignedInt getLocalPlayerID( void ); UnicodeString getPlayerName(Int playerNum); Int getNumPlayers(void ); Int getAverageFPS() { return m_conMgr->getAverageFPS(); } Int getSlotAverageFPS(Int slot); void attachTransport(Transport *transport); void initTransport(); void setSawCRCMismatch( void ); Bool sawCRCMismatch( void ) { return m_sawCRCMismatch; } Bool isPlayerConnected( Int playerID ); void notifyOthersOfCurrentFrame(); ///< Tells all the other players what frame we are on. void notifyOthersOfNewFrame(UnsignedInt frame); ///< Tells all the other players that we are on a new frame. Int getExecutionFrame(); ///< Returns the next valid frame for simultaneous command execution. // For disconnect blame assignment UnsignedInt getPingFrame(); Int getPingsSent(); Int getPingsRecieved(); protected: void GetCommandsFromCommandList(); ///< Remove commands from TheCommandList and put them on the Network command list. void SendCommandsToConnectionManager(); ///< Send the new commands to the ConnectionManager Bool AllCommandsReady(UnsignedInt frame); ///< Do we have all the commands for the given frame? void RelayCommandsToCommandList(UnsignedInt frame); ///< Put the commands for the given frame onto TheCommandList. Bool isTransferCommand(GameMessage *msg); ///< Is this a command that needs to be transfered to the other clients? Bool processCommand(GameMessage *msg); ///< Whatever needs to be done as a result of this command, do it now. void processFrameSynchronizedNetCommand(NetCommandRef *msg); ///< If there is a network command that needs to be executed at the same frame number on all clients, it happens here. void processRunAheadCommand(NetRunAheadCommandMsg *msg); ///< Do what needs to be done when we get a new run ahead command. void processDestroyPlayerCommand(NetDestroyPlayerCommandMsg *msg); ///< Do what needs to be done when we need to destroy a player. void endOfGameCheck(); ///< Checks to see if its ok to leave this game. If it is, send the apropriate command to the game logic. Bool timeForNewFrame(); ConnectionManager *m_conMgr; ///< The connection manager object UnsignedInt m_lastFrame; ///< The last game logic frame that was processed. NetLocalStatus m_localStatus; ///< My local status as a player in this game. Int m_runAhead; ///< The current run ahead of the game. Int m_frameRate; Int m_lastExecutionFrame; ///< The highest frame number that a command could have been executed on. Int m_lastFrameCompleted; Bool m_didSelfSlug; __int64 m_perfCountFreq; ///< The frequency of the performance counter. __int64 m_nextFrameTime; ///< When did we execute the last frame? For slugging the GameLogic... Bool m_frameDataReady; ///< Is the frame data for the next frame ready to be executed by TheGameLogic? // CRC info Bool m_checkCRCsThisFrame; Bool m_sawCRCMismatch; std::vector m_CRC[MAX_SLOTS]; std::list m_playersToDisconnect; GameWindow *m_messageWindow; #if defined(_DEBUG) || defined(_INTERNAL) Bool m_networkOn; #endif }; UnsignedInt Network::getPingFrame() { return (m_conMgr)?m_conMgr->getPingFrame():0; } Int Network::getPingsSent() { return (m_conMgr)?m_conMgr->getPingsSent():0; } Int Network::getPingsRecieved() { return (m_conMgr)?m_conMgr->getPingsRecieved():0; } Bool Network::isPlayerConnected( Int playerID ) { if (playerID == getLocalPlayerID()) { return m_localStatus == NETLOCALSTATUS_INGAME || m_localStatus == NETLOCALSTATUS_LEAVING; } return m_conMgr->isPlayerConnected(playerID); } // PRIVATE FUNCTIONS ////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // PUBLIC FUNCTIONS /////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /** * This creates a network object and returns it. */ NetworkInterface *NetworkInterface::createNetwork() { return NEW Network; } //////////////////////////////////////////////////////////////////////////////// /** * The constructor. */ Network::Network() { //Added By Sadullah Nader //Initializations inserted m_checkCRCsThisFrame = FALSE; m_didSelfSlug = FALSE; m_frameDataReady = FALSE; m_sawCRCMismatch = FALSE; // m_conMgr = NULL; m_messageWindow = NULL; #if defined(_DEBUG) || defined(_INTERNAL) m_networkOn = TRUE; #endif } /** * The destructor. */ Network::~Network() { deinit(); } /** * This basically releases all the memory. */ Bool Network::deinit( void ) { if (m_conMgr) { m_conMgr->destroyGameMessages(); delete m_conMgr; m_conMgr = NULL; } if (m_messageWindow) { TheWindowManager->winDestroy(m_messageWindow); m_messageWindow = NULL; } return true; } /** * Takes the network back to the initial state. */ void Network::reset() { init(); } /** * Initializes all the network subsystems. */ void Network::init() { if (!deinit()) { DEBUG_LOG(("Could not deinit network prior to init!\n")); return; } m_conMgr = NEW ConnectionManager; m_conMgr->init(); m_lastFrame = 0; m_runAhead = min(max(30, MIN_RUNAHEAD), MAX_FRAMES_AHEAD/2); ///< @todo: don't hard-code the run-ahead. m_frameRate = 30; m_lastExecutionFrame = m_runAhead - 1; // subtract 1 since we're starting on frame 0 m_lastFrameCompleted = m_runAhead - 1; // subtract 1 since we're starting on frame 0 m_frameDataReady = FALSE; m_didSelfSlug = FALSE; m_localStatus = NETLOCALSTATUS_PREGAME; QueryPerformanceFrequency((LARGE_INTEGER *)&m_perfCountFreq); m_nextFrameTime = 0; m_sawCRCMismatch = FALSE; m_checkCRCsThisFrame = FALSE; DEBUG_LOG(("Network timing values:\n")); DEBUG_LOG(("NetworkFPSHistoryLength: %d\n", TheGlobalData->m_networkFPSHistoryLength)); DEBUG_LOG(("NetworkLatencyHistoryLength: %d\n", TheGlobalData->m_networkLatencyHistoryLength)); DEBUG_LOG(("NetworkRunAheadMetricsTime: %d\n", TheGlobalData->m_networkRunAheadMetricsTime)); DEBUG_LOG(("NetworkCushionHistoryLength: %d\n", TheGlobalData->m_networkCushionHistoryLength)); DEBUG_LOG(("NetworkRunAheadSlack: %d\n", TheGlobalData->m_networkRunAheadSlack)); DEBUG_LOG(("NetworkKeepAliveDelay: %d\n", TheGlobalData->m_networkKeepAliveDelay)); DEBUG_LOG(("NetworkDisconnectTime: %d\n", TheGlobalData->m_networkDisconnectTime)); DEBUG_LOG(("NetworkPlayerTimeoutTime: %d\n", TheGlobalData->m_networkPlayerTimeoutTime)); DEBUG_LOG(("NetworkDisconnectScreenNotifyTime: %d\n", TheGlobalData->m_networkDisconnectScreenNotifyTime)); DEBUG_LOG(("Other network stuff:\n")); DEBUG_LOG(("FRAME_DATA_LENGTH = %d\n", FRAME_DATA_LENGTH)); DEBUG_LOG(("FRAMES_TO_KEEP = %d\n", FRAMES_TO_KEEP)); #if defined(_DEBUG) || defined(_INTERNAL) m_networkOn = TRUE; #endif return; } void Network::setSawCRCMismatch( void ) { m_sawCRCMismatch = TRUE; TheScriptActions->closeWindows( TRUE ); m_messageWindow = TheWindowManager->winCreateFromScript("Menus/CRCMismatch.wnd"); TheScriptEngine->startEndGameTimer(); TheRecorder->logCRCMismatch(); // dump GameLogic random seed DEBUG_LOG(("GameLogic frame = %d\n", TheGameLogic->getFrame())); DEBUG_LOG(("GetGameLogicRandomSeedCRC() = %d\n", GetGameLogicRandomSeedCRC())); // dump CRCs { DEBUG_LOG(("--- GameState Dump ---\n")); #ifdef DEBUG_CRC outputCRCDumpLines(); #endif DEBUG_LOG(("------ End Dump ------\n")); } { DEBUG_LOG(("--- DebugInfo Dump ---\n")); #ifdef DEBUG_CRC outputCRCDebugLines(); #endif DEBUG_LOG(("------ End Dump ------\n")); } } /** * Take a user list and build the connection queues and player lists and stuff like that. */ void Network::parseUserList( const GameInfo *game ) { if (!game) { DEBUG_LOG(("FAILED parseUserList with a NULL game\n")); return; } m_conMgr->parseUserList(game); // Now that we have the players in this game, we need to reset the FrameData stuff. m_conMgr->destroyGameMessages(); m_conMgr->zeroFrames(1, m_runAhead-1); ///< we zero out m_runAhead frames +1 because the game actually starts at frame 1. } /** * Guess what, we're starting a game! */ void Network::startGame() { } /** * Tell the network which ip address the user has chosen to use. Well ok, they probably didn't choose * it explicitly, but regardless, this is the one we're going to use. */ void Network::setLocalAddress(UnsignedInt ip, UnsignedInt port) { DEBUG_ASSERTCRASH(m_conMgr != NULL, ("Connection manager does not exist.")); if (m_conMgr != NULL) { m_conMgr->setLocalAddress(ip, port); } } /** * Tell the network to initialize the transport object */ void Network::initTransport() { DEBUG_ASSERTCRASH(m_conMgr != NULL, ("Connection manager does not exist.")); if (m_conMgr != NULL) { m_conMgr->initTransport(); } } void Network::attachTransport(Transport *transport) { DEBUG_ASSERTCRASH(m_conMgr != NULL, ("Connection manager does not exist.")); if (m_conMgr != NULL) { m_conMgr->attachTransport(transport); } } /** * Does this command need to be transfered to the other game clients? */ Bool Network::isTransferCommand(GameMessage *msg) { if ((msg != NULL) && ((msg->getType() > GameMessage::MSG_BEGIN_NETWORK_MESSAGES) && (msg->getType() < GameMessage::MSG_END_NETWORK_MESSAGES))) { return TRUE; } return FALSE; } /** * Take commands from TheCommandList and give them to the connection manager for transport. */ void Network::GetCommandsFromCommandList() { GameMessage *msg = TheCommandList->getFirstMessage(); GameMessage *next = NULL; while (msg != NULL) { next = msg->next(); if (isTransferCommand(msg)) { // Is this something we should be sending to the other players? if (m_localStatus == NETLOCALSTATUS_INGAME) { m_conMgr->sendLocalGameMessage(msg, getExecutionFrame()); } TheCommandList->removeMessage(msg); // This does not destroy msg's prev and next pointers, so they should still be valid. msg->deleteInstance(); } else { if (processCommand(msg)) { TheCommandList->removeMessage(msg); msg->deleteInstance(); } } msg = next; } } Int Network::getExecutionFrame() { Int logicFrame = TheGameLogic->getFrame() + m_runAhead; if (logicFrame > m_lastExecutionFrame) { m_lastExecutionFrame = logicFrame; return (logicFrame); } return m_lastExecutionFrame; } /** * This is where any processing that needs to be done for specific game messages. * Also check here to see if the logic frame number has changed to see if we need to * send our info for the last frame to the other players. * Return true if the message should be "eaten" by the network. */ Bool Network::processCommand(GameMessage *msg) { if ((m_lastFrame != TheGameLogic->getFrame()) || (m_localStatus == NETLOCALSTATUS_PREGAME)) { // If this is the start of a new game logic frame, then tell the connection manager that the last // frame is over and that it should now produce a frame info packet for the other players. if (m_localStatus == NETLOCALSTATUS_PREGAME) { // a sort-of-hack that prevents extraneous frames from being executed before the game actually starts. // Idealy this shouldn't be necessary, but I don't think its hurting anything by being here. if (TheGameLogic->getFrame() == 1) { m_localStatus = NETLOCALSTATUS_INGAME; NetCommandList *netcmdlist = m_conMgr->getFrameCommandList(0); // clear out frame 0 since we skipped it netcmdlist->deleteInstance(); } else { return FALSE; } } // Only send frame info packets for frames where we are actually going to be in the game. // The reason is so we don't have to wait for frame info packets to be sent that aren't going to // matter anyways when we actually try to leave the game ourselves. if (m_localStatus == NETLOCALSTATUS_INGAME) { Int executionFrame = getExecutionFrame(); // Send command counts for all the frames we can. for (Int i = m_lastFrameCompleted + 1; i < executionFrame; ++i) { m_conMgr->processFrameTick(i); //DEBUG_LOG(("Network::processCommand - calling processFrameTick for frame %d\n", i)); m_lastFrameCompleted = i; } } //DEBUG_LOG(("Next Execution Frame - %d, last frame completed - %d\n", getExecutionFrame(), m_lastFrameCompleted)); m_lastFrame = TheGameLogic->getFrame(); } // Are we leaving the game? // This has to happen after the check to see if this is the start of a new logic frame. // The reason is that we have to send all the frame info packets necessary to get to the // frame where everyone else is going to see that we left. if ((msg->getType() == GameMessage::MSG_CLEAR_GAME_DATA) && (m_localStatus == NETLOCALSTATUS_INGAME)) { Int executionFrame = getExecutionFrame(); DEBUG_LOG(("Network::processCommand - local player leaving, executionFrame = %d, player leaving on frame %d\n", executionFrame, executionFrame+1)); m_conMgr->handleLocalPlayerLeaving(executionFrame+1); m_conMgr->processFrameTick(executionFrame); // This is the last command we will execute, so send the command count. // Also, we are guaranteed not to send any more commands for this frame // since the local status will change to "Leaving" so we don't have to // worry about messing up the other players. m_conMgr->processFrameTick(executionFrame+1); // since we send it for executionFrame+1, we need to process both ticks m_lastFrameCompleted = executionFrame; DEBUG_LOG(("Network::processCommand - player leaving on frame %d\n", executionFrame)); m_localStatus = NETLOCALSTATUS_LEAVING; return TRUE; } return FALSE; } /** * returns true if all the commands are ready for the given frame. */ Bool Network::AllCommandsReady(UnsignedInt frame) { if (m_conMgr == NULL) { return TRUE; } if (m_localStatus == NETLOCALSTATUS_PREGAME) { return TRUE; } if (m_localStatus == NETLOCALSTATUS_POSTGAME) { return TRUE; } return m_conMgr->allCommandsReady(frame);// && m_conMgr->allCRCsReady(frame); } /** * Take commands from the connection manager and put them on TheCommandList. * The commands need to be put on in the same order across all clients. */ void Network::RelayCommandsToCommandList(UnsignedInt frame) { if ((m_conMgr == NULL) || (m_localStatus == NETLOCALSTATUS_PREGAME)) { return; } m_checkCRCsThisFrame = FALSE; NetCommandList *netcmdlist = m_conMgr->getFrameCommandList(frame); NetCommandRef *msg = netcmdlist->getFirstMessage(); while (msg != NULL) { NetCommandType cmdType = msg->getCommand()->getNetCommandType(); if (cmdType == NETCOMMANDTYPE_GAMECOMMAND) { //DEBUG_LOG(("Network::RelayCommandsToCommandList - appending command %d of type %s to command list on frame %d\n", msg->getCommand()->getID(), ((NetGameCommandMsg *)msg->getCommand())->constructGameMessage()->getCommandAsAsciiString().str(), TheGameLogic->getFrame())); TheCommandList->appendMessage(((NetGameCommandMsg *)msg->getCommand())->constructGameMessage()); } else { processFrameSynchronizedNetCommand(msg); } msg = msg->getNext(); } for (std::list::iterator selfDestructIt = m_playersToDisconnect.begin(); selfDestructIt != m_playersToDisconnect.end(); ++selfDestructIt) { //Int playerToDisconnect = *selfDestructIt; //GameMessage *msg = newInstance(GameMessage)( GameMessage::MSG_SELF_DESTRUCT ); //msg->friend_setPlayerIndex(playerToDisconnect); //msg->appendBooleanArgument(TRUE); //TheCommandList->appendMessage(msg); } m_playersToDisconnect.clear(); netcmdlist->deleteInstance(); } /** * This is where network commands that need to be executed on the same frame should be executed. */ void Network::processFrameSynchronizedNetCommand(NetCommandRef *msg) { NetCommandMsg *cmdMsg = msg->getCommand(); if (cmdMsg->getNetCommandType() == NETCOMMANDTYPE_PLAYERLEAVE) { PlayerLeaveCode retval = m_conMgr->processPlayerLeave((NetPlayerLeaveCommandMsg *)cmdMsg); if (retval == PLAYERLEAVECODE_LOCAL) { DEBUG_LOG(("Network::processFrameSynchronizedNetCommand - Local player left the game on frame %d.\n", TheGameLogic->getFrame())); m_localStatus = NETLOCALSTATUS_LEFT; } else if (retval == PLAYERLEAVECODE_PACKETROUTER) { DEBUG_LOG(("Network::processFrameSynchronizedNetCommand - Packet router left the game on frame %d\n", TheGameLogic->getFrame())); } else { DEBUG_LOG(("Network::processFrameSynchronizedNetCommand - Client left the game on frame %d\n", TheGameLogic->getFrame())); } } else if (cmdMsg->getNetCommandType() == NETCOMMANDTYPE_RUNAHEAD) { NetRunAheadCommandMsg *netmsg = (NetRunAheadCommandMsg *)cmdMsg; processRunAheadCommand(netmsg); DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("command to set run ahead to %d and frame rate to %d on frame %d actually executed on frame %d\n", netmsg->getRunAhead(), netmsg->getFrameRate(), netmsg->getExecutionFrame(), TheGameLogic->getFrame())); } else if (cmdMsg->getNetCommandType() == NETCOMMANDTYPE_DESTROYPLAYER) { NetDestroyPlayerCommandMsg *netmsg = (NetDestroyPlayerCommandMsg *)cmdMsg; processDestroyPlayerCommand(netmsg); //DEBUG_LOG(("CRC command (%8.8X) on frame %d actually executed on frame %d\n", netmsg->getCRC(), netmsg->getExecutionFrame(), TheGameLogic->getFrame())); } } void Network::processRunAheadCommand(NetRunAheadCommandMsg *msg) { m_runAhead = msg->getRunAhead(); m_frameRate = msg->getFrameRate(); time_t frameGrouping = (1000 * m_runAhead) / m_frameRate; // number of miliseconds between packet sends frameGrouping = frameGrouping / 2; // since we only want the latency for one way to be a factor. // DEBUG_LOG(("Network::processRunAheadCommand - trying to set frame grouping to %d. run ahead = %d, m_frameRate = %d\n", frameGrouping, m_runAhead, m_frameRate)); if (frameGrouping < 1) { frameGrouping = 1; // Having a value less than 1 doesn't make sense. } if (frameGrouping > 500) { frameGrouping = 500; // Max of a half a second. } m_conMgr->setFrameGrouping(frameGrouping); } void Network::processDestroyPlayerCommand(NetDestroyPlayerCommandMsg *msg) { UnsignedInt playerIndex = msg->getPlayerIndex(); DEBUG_ASSERTCRASH(playerIndex < MAX_SLOTS, ("Bad player index")); if (playerIndex >= MAX_SLOTS) return; AsciiString playerName; playerName.format("player%d", playerIndex); Player *pPlayer = ThePlayerList->findPlayerWithNameKey(NAMEKEY(playerName)); if (pPlayer) { GameMessage *msg = newInstance(GameMessage)(GameMessage::MSG_SELF_DESTRUCT); msg->appendBooleanArgument(FALSE); msg->friend_setPlayerIndex(pPlayer->getPlayerIndex()); TheCommandList->appendMessage(msg); } DEBUG_LOG(("Saw DestroyPlayer from %d about %d for frame %d on frame %d\n", msg->getPlayerID(), msg->getPlayerIndex(), msg->getExecutionFrame(), TheGameLogic->getFrame())); } /** * Service queues, process message stream, etc */ void Network::update( void ) { // // 1. Take Commands off TheCommandList, hand them off to the ConnectionManager. // 2. Call ConnectionManager->update; // 3. Check to see if all the commands for the next frame are there. // 4. If all commands are there, put that frame's commands on TheCommandList. // m_frameDataReady = FALSE; #if defined(_DEBUG) || defined(_INTERNAL) if (m_networkOn == FALSE) { return; } #endif GetCommandsFromCommandList(); // Remove commands from TheCommandList and send them to the connection manager. if (m_conMgr != NULL) { if (m_localStatus == NETLOCALSTATUS_INGAME) { m_conMgr->updateRunAhead(m_runAhead, m_frameRate, m_didSelfSlug, getExecutionFrame()); m_didSelfSlug = FALSE; } //m_conMgr->update(); // Do the priority thing, packetize thing. This also calls the Transport::update function. // depacketizes the incoming packets and puts them on the Network command list. } liteupdate(); if ((m_localStatus == NETLOCALSTATUS_LEFT)) {// || (m_localStatus == NETLOCALSTATUS_LEAVING)) { endOfGameCheck(); } if (AllCommandsReady(TheGameLogic->getFrame())) { // If all the commands are ready for the next frame... m_conMgr->handleAllCommandsReady(); // DEBUG_LOG(("Network::update - frame %d is ready\n", TheGameLogic->getFrame())); if (timeForNewFrame()) { // This needs to come after any other pre-frame execution checks as this changes the timing variables. RelayCommandsToCommandList(TheGameLogic->getFrame()); // Put the commands for the next frame on TheCommandList. m_frameDataReady = TRUE; // Tell the GameEngine to run the commands for the new frame. } } } void Network::liteupdate() { #if defined(_DEBUG) || defined(_INTERNAL) if (m_networkOn == FALSE) { return; } #endif if (m_conMgr != NULL) { if (m_localStatus == NETLOCALSTATUS_PREGAME) { m_conMgr->update(FALSE); } else { m_conMgr->update(TRUE); } } } void Network::endOfGameCheck() { if (m_conMgr != NULL) { if (m_conMgr->canILeave()) { m_conMgr->disconnectLocalPlayer(); TheMessageStream->appendMessage(GameMessage::MSG_CLEAR_GAME_DATA); m_localStatus = NETLOCALSTATUS_POSTGAME; DEBUG_LOG(("Network::endOfGameCheck - about to show the shell\n")); } #if defined(_DEBUG) || defined(_INTERNAL) else { m_conMgr->debugPrintConnectionCommands(); } #endif } } Bool Network::timeForNewFrame() { __int64 curTime; QueryPerformanceCounter((LARGE_INTEGER *)&curTime); __int64 frameDelay = m_perfCountFreq / m_frameRate; /* * If we're pushing up against the edge of our run ahead, we should slow the framerate down a bit * to avoid being frozen by spikes in network lag. This will happen if another user's computer is * running too far behind us, so we need to slow down to let them catch up. */ if (m_conMgr != NULL) { Real cushion = m_conMgr->getMinimumCushion(); Real runAheadPercentage = m_runAhead * (TheGlobalData->m_networkRunAheadSlack / (Real)100.0); // If we are at least 50% into our slack, we need to slow down. if (cushion < runAheadPercentage) { // DEBUG_LOG(("Average cushion = %f, run ahead percentage = %f. Adjusting frameDelay from %I64d to ", cushion, runAheadPercentage, frameDelay)); frameDelay += frameDelay / 10; // temporarily decrease the frame rate by 20%. // DEBUG_LOG(("%I64d\n", frameDelay)); m_didSelfSlug = TRUE; // } else { // DEBUG_LOG(("Average cushion = %f, run ahead percentage = %f\n", cushion, runAheadPercentage)); } } // Check to see if we can run another frame. if (curTime >= m_nextFrameTime) { // DEBUG_LOG(("Allowing a new frame, frameDelay = %I64d, curTime - m_nextFrameTime = %I64d\n", frameDelay, curTime - m_nextFrameTime)); // if (m_nextFrameTime + frameDelay < curTime) { if ((m_nextFrameTime + (2 * frameDelay)) < curTime) { // If we get too far behind on our framerate we need to reset the nextFrameTime thing. m_nextFrameTime = curTime; // DEBUG_LOG(("Initializing m_nextFrameTime to %I64d\n", m_nextFrameTime)); } else { // Set the soonest possible starting time for the next frame. m_nextFrameTime += frameDelay; // DEBUG_LOG(("m_nextFrameTime = %I64d\n", m_nextFrameTime)); } return TRUE; } // DEBUG_LOG(("Slowing down frame rate. frame rate = %d, frame delay = %I64d, curTime - m_nextFrameTime = %I64d\n", m_frameRate, frameDelay, curTime - m_nextFrameTime)); return FALSE; } /** * Returns true if the game commands for the next frame have been put on the command list. */ Bool Network::isFrameDataReady() { return (m_frameDataReady || (m_localStatus == NETLOCALSTATUS_LEFT)); } /** * returns the number of incoming bytes per second averaged over the last 30 sec. */ Real Network::getIncomingBytesPerSecond( void ) { if (m_conMgr) return m_conMgr->getIncomingBytesPerSecond(); else return 0.0; } /** * returns the number of incoming packets per second averaged over the last 30 sec. */ Real Network::getIncomingPacketsPerSecond( void ) { if (m_conMgr) return m_conMgr->getIncomingPacketsPerSecond(); else return 0.0; } /** * returns the number of outgoing bytes per second averaged over the last 30 sec. */ Real Network::getOutgoingBytesPerSecond( void ) { if (m_conMgr) return m_conMgr->getOutgoingBytesPerSecond(); else return 0.0; } /** * returns the number of outgoing packets per second averaged over the last 30 sec. */ Real Network::getOutgoingPacketsPerSecond( void ) { if (m_conMgr) return m_conMgr->getOutgoingPacketsPerSecond(); else return 0.0; } /** * returns the number of bytes received per second that are not from a generals client averaged over 30 sec. */ Real Network::getUnknownBytesPerSecond( void ) { if (m_conMgr) return m_conMgr->getUnknownBytesPerSecond(); else return 0.0; } /** * returns the number of packets received per second that are not from a generals client averaged over 30 sec. */ Real Network::getUnknownPacketsPerSecond( void ) { if (m_conMgr) return m_conMgr->getUnknownPacketsPerSecond(); else return 0.0; } /** * returns the smallest packet arrival cushion since this was last called. */ UnsignedInt Network::getPacketArrivalCushion( void ) { if (m_conMgr) return m_conMgr->getPacketArrivalCushion(); else return 0; } /** * Sends a line of chat to the other players */ void Network::sendChat(UnicodeString text, Int playerMask) { Int executionFrame = getExecutionFrame(); m_conMgr->sendChat(text, playerMask, executionFrame); } /** * Sends a line of chat to the other players using the disconnect manager. */ void Network::sendDisconnectChat(UnicodeString text) { m_conMgr->sendDisconnectChat(text); } // send a file. woohoo. void Network::sendFile(AsciiString path, UnsignedByte playerMask, UnsignedShort commandID) { m_conMgr->sendFile(path, playerMask, commandID); } // send a file. woohoo. UnsignedShort Network::sendFileAnnounce(AsciiString path, UnsignedByte playerMask) { return m_conMgr->sendFileAnnounce(path, playerMask); } Int Network::getFileTransferProgress(Int playerID, AsciiString path) { return m_conMgr->getFileTransferProgress(playerID, path); } Bool Network::areAllQueuesEmpty(void) { return m_conMgr->canILeave(); } /** * Quit the game now. This should only be called from the disconnect screen. */ void Network::quitGame() { if (m_conMgr != NULL) { m_conMgr->quitGame(); } TheMessageStream->appendMessage(GameMessage::MSG_CLEAR_GAME_DATA); m_localStatus = NETLOCALSTATUS_POSTGAME; DEBUG_LOG(("Network::quitGame - quitting game...")); } void Network::selfDestructPlayer(Int index) { m_playersToDisconnect.push_back(index); } Bool Network::isPacketRouter( void ) { return m_conMgr && m_conMgr->isPacketRouter(); } /** * Register a vote towards a player being disconnected. */ void Network::voteForPlayerDisconnect(Int slot) { if (m_conMgr != NULL) { m_conMgr->voteForPlayerDisconnect(slot); } } void Network::updateLoadProgress( Int percent ) { if (m_conMgr != NULL) { m_conMgr->updateLoadProgress( percent ); } } void Network::loadProgressComplete( void ) { if (m_conMgr != NULL) { m_conMgr->loadProgressComplete(); } } void Network::sendTimeOutGameStart( void ) { if (m_conMgr != NULL) { m_conMgr->sendTimeOutGameStart(); } } UnsignedInt Network::getLocalPlayerID() { if (m_conMgr != NULL) { return m_conMgr->getLocalPlayerID(); } return 49; } UnicodeString Network::getPlayerName(Int playerNum) { if (playerNum == m_conMgr->getLocalPlayerID()) { if (m_localStatus != NETLOCALSTATUS_INGAME) { return UnicodeString::TheEmptyString; } } if (m_conMgr != NULL) { return m_conMgr->getPlayerName( playerNum ); } return UnicodeString::TheEmptyString; } Int Network::getNumPlayers() { if (m_conMgr != NULL) { return m_conMgr->getNumPlayers(); } return -1; } Int Network::getSlotAverageFPS(Int slot) { if (m_conMgr != NULL) { return m_conMgr->getSlotAverageFPS(slot); } return -1; } #if defined(_DEBUG) || defined(_INTERNAL) void Network::toggleNetworkOn() { if (m_networkOn == TRUE) { m_networkOn = FALSE; } else { m_networkOn = TRUE; } } #endif void Network::notifyOthersOfCurrentFrame() { if (m_conMgr != NULL) { m_conMgr->notifyOthersOfCurrentFrame(TheGameLogic->getFrame()); } } void Network::notifyOthersOfNewFrame(UnsignedInt frame) { if (m_conMgr != NULL) { m_conMgr->notifyOthersOfNewFrame(frame); } }