/*
** 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);
}
}