/*
** Command & Conquer Generals Zero Hour(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 "Compression.h"
#include "strtok_r.h"
#include "Common/AudioEventRTS.h"
#include "Common/CRCDebug.h"
#include "Common/Debug.h"
#include "Common/File.h"
#include "Common/GameAudio.h"
#include "Common/LocalFileSystem.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/RandomValue.h"
#include "Common/Recorder.h"
#include "GameClient/Diplomacy.h"
#include "GameClient/GameText.h"
#include "GameClient/MessageBox.h"
#include "GameNetwork/ConnectionManager.h"
#include "GameNetwork/LANAPICallbacks.h"
#include "GameNetwork/NAT.h"
#include "GameNetwork/NetCommandWrapperList.h"
#include "GameNetwork/NetworkUtil.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/ScriptActions.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/VictoryConditions.h"
#include "GameClient/DisconnectMenu.h"
#include "GameClient/InGameUI.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
/**
* Le destructor.
*/
ConnectionManager::~ConnectionManager(void)
{
if (m_localUser != NULL) {
m_localUser->deleteInstance();
m_localUser = NULL;
}
// m_transport = NULL; // Network will delete transports; we just forget them
if (m_transport != NULL) {
delete m_transport;
m_transport = NULL;
}
for (Int i = 0; i < MAX_SLOTS; ++i) {
if (m_frameData[i] != NULL) {
m_frameData[i]->deleteInstance();
m_frameData[i] = NULL;
}
}
for (i = 0; i < NUM_CONNECTIONS; ++i) {
if (m_connections[i] != NULL) {
m_connections[i]->deleteInstance();
m_connections[i] = NULL;
}
}
if (TheDisconnectMenu != NULL) {
// This is done here since TheDisconnectMenu should only be there if we are in a network game.
delete TheDisconnectMenu;
TheDisconnectMenu = NULL;
}
if (m_disconnectManager != NULL) {
delete m_disconnectManager;
m_disconnectManager = NULL;
}
if (m_pendingCommands != NULL) {
m_pendingCommands->deleteInstance();
m_pendingCommands = NULL;
}
if (m_relayedCommands != NULL) {
m_relayedCommands->deleteInstance();
m_relayedCommands = NULL;
}
if (m_netCommandWrapperList != NULL) {
m_netCommandWrapperList->deleteInstance();
m_netCommandWrapperList = NULL;
}
s_fileCommandMap.clear();
s_fileRecipientMaskMap.clear();
for (i = 0; i < MAX_SLOTS; ++i) {
s_fileProgressMap[i].clear();
}
}
/**
* Le constructor
*/
ConnectionManager::ConnectionManager(void)
{
for (Int i = 0; i < MAX_SLOTS; ++i) {
m_frameData[i] = NULL;
}
m_transport = NULL;
m_disconnectManager = NULL;
m_pendingCommands = NULL;
m_relayedCommands = NULL;
m_localAddr = 0;
m_localPort = 0;
m_netCommandWrapperList = NULL;
m_localUser = NULL;
m_localUser = newInstance(User);
}
/**
* Initialize the connection manager and any subsystems.
*/
void ConnectionManager::init()
{
// if (m_transport == NULL) {
// m_transport = new Transport;
// }
// m_transport->reset();
for (UnsignedInt i = 0; i < NUM_CONNECTIONS; ++i) {
m_connections[i] = NULL;
}
if (m_pendingCommands == NULL) {
m_pendingCommands = newInstance(NetCommandList);
m_pendingCommands->init();
}
m_pendingCommands->reset();
if (m_relayedCommands == NULL) {
m_relayedCommands = newInstance(NetCommandList);
m_relayedCommands->init();
}
m_relayedCommands->reset();
m_localSlot = -1;
#ifdef MEMORYPOOL_DEBUG
TheMemoryPoolFactory->debugSetInitFillerIndex(m_localSlot);
#endif
m_packetRouterSlot = 0; /// @todo The LAN/WOL interface should be telling us who the packet router is based on machine specs passed around through game options.
for (i = 0; i < MAX_SLOTS; ++i) {
m_packetRouterFallback[i] = -1;
}
for (i = 0; i < MAX_SLOTS; ++i) {
if (m_frameData[i] != NULL) {
m_frameData[i]->deleteInstance();
m_frameData[i] = NULL;
}
}
// m_averageFps = 30; // since 30 fps is the desired rate, we'll start off at that.
// m_averageLatency = (Real)0.2; // 200ms seems like a good starting point.
for (i = 0; i < MAX_SLOTS; ++i) {
m_fpsAverages[i] = -1;
}
for (i = 0; i < MAX_SLOTS; ++i) {
m_latencyAverages[i] = 0.0; // using zero since all floating point standards should be able to specify 0.0 accurately.
}
m_smallestPacketArrivalCushion = -1;
m_frameMetrics.init();
TheDisconnectMenu = NEW DisconnectMenu;
TheDisconnectMenu->init();
m_disconnectManager = NEW DisconnectManager;
m_disconnectManager->init();
TheDisconnectMenu->attachDisconnectManager(m_disconnectManager);
TheDisconnectMenu->hideScreen();
m_netCommandWrapperList = newInstance(NetCommandWrapperList);
m_netCommandWrapperList->init();
s_fileCommandMap.clear();
s_fileRecipientMaskMap.clear();
for (i = 0; i < MAX_SLOTS; ++i) {
s_fileProgressMap[i].clear();
}
}
/**
* Reset the connection manager and any subsystems.
*/
void ConnectionManager::reset()
{
// if (m_transport == NULL) {
// m_transport = new Transport;
// }
// m_transport->reset();
if (m_transport != NULL) {
delete m_transport;
m_transport = NULL;
}
for (Int i = 0; i < NUM_CONNECTIONS; ++i) {
if (m_connections[i] != NULL) {
m_connections[i]->deleteInstance();
m_connections[i] = NULL;
}
}
for (i=0; ideleteInstance();
m_frameData[i] = NULL;
}
}
if (m_pendingCommands == NULL) {
m_pendingCommands = newInstance(NetCommandList);
m_pendingCommands->init();
}
m_pendingCommands->reset();
if (m_relayedCommands == NULL) {
m_relayedCommands = newInstance(NetCommandList);
m_relayedCommands->init();
}
m_relayedCommands->reset();
if (m_netCommandWrapperList == NULL) {
m_netCommandWrapperList = newInstance(NetCommandWrapperList);
m_netCommandWrapperList->init();
}
m_netCommandWrapperList->reset();
m_localSlot = -1;
#ifdef MEMORYPOOL_DEBUG
TheMemoryPoolFactory->debugSetInitFillerIndex(m_localSlot);
#endif
m_packetRouterSlot = -1;
for (i = 0; i < TheGlobalData->m_networkFPSHistoryLength; ++i) {
m_fpsAverages[i] = -1;
}
for (i = 0; i < TheGlobalData->m_networkLatencyHistoryLength; ++i) {
m_latencyAverages[i] = 0.0;
}
for (i = 0; i < MAX_SLOTS; ++i) {
m_packetRouterFallback[i] = -1;
}
m_frameMetrics.reset();
}
UnsignedInt ConnectionManager::getPingFrame()
{
return (m_disconnectManager)?m_disconnectManager->getPingFrame():0;
}
Int ConnectionManager::getPingsSent()
{
return (m_disconnectManager)?m_disconnectManager->getPingsSent():0;
}
Int ConnectionManager::getPingsRecieved()
{
return (m_disconnectManager)?m_disconnectManager->getPingsRecieved():0;
}
Bool ConnectionManager::isPlayerConnected( Int playerID )
{
return ( playerID == m_localSlot || (m_connections[playerID] && !m_connections[playerID]->isQuitting()) );
}
void ConnectionManager::attachTransport(Transport *transport) {
if (m_transport != NULL) {
delete m_transport;
m_transport = NULL;
}
m_transport = transport;
}
/**
* zero out the command counts for the given frames. Presently this is used for
* the start of a game since there won't be any commands for the first few frames due to runahead.
*/
void ConnectionManager::zeroFrames(UnsignedInt startingFrame, UnsignedInt numFrames) {
for (Int i = 0; i < MAX_SLOTS; ++i) {
if (m_frameData[i] != NULL) {
// DEBUG_LOG(("Calling zeroFrames on player %d, starting frame %d, numFrames %d\n", i, startingFrame, numFrames));
m_frameData[i]->zeroFrames(startingFrame, numFrames);
}
}
}
/**
* Destroy any game messages that are left over due to the run ahead.
*/
void ConnectionManager::destroyGameMessages() {
for (Int i = 0; i < MAX_SLOTS; ++i) {
// Need to destroy these game messages because when the game ends, there are
// still some game messages left over because of the run ahead aspect of
// network play.
if (m_frameData[i] != NULL) {
m_frameData[i]->destroyGameMessages();
}
}
}
/**
* ConnectionManager::doRelay()
* Queries the transport for commands that need to be relayed to another client.
* Get those commands and relay them to the appropriate Connection(s). We make the
* assumption that a command will only be relayed once.
*/
void ConnectionManager::doRelay() {
static Int numPackets = 0;
static Int numCommands = 0;
NetPacket *packet = NULL;
for (Int i = 0; i < MAX_MESSAGES; ++i) {
if (m_transport->m_inBuffer[i].length != 0) {
// This transport buffer has yet to be processed.
// make a NetPacket out of this data so it can be broken up into individual commands.
packet = newInstance(NetPacket)(&(m_transport->m_inBuffer[i]));
//DEBUG_LOG(("ConnectionManager::doRelay() - got a packet with %d commands\n", packet->getNumCommands()));
//LOGBUFFER( packet->getData(), packet->getLength() );
// Get the command list from the packet.
NetCommandList *cmdList = packet->getCommandList();
NetCommandRef *cmd = cmdList->getFirstMessage();
// Iterate through the commands in this packet and send them to the proper connections.
while (cmd != NULL) {
//DEBUG_LOG(("ConnectionManager::doRelay() - Looking at a command of type %s\n",
//GetAsciiNetCommandType(cmd->getCommand()->getNetCommandType()).str()));
if (CommandRequiresAck(cmd->getCommand())) {
ackCommand(cmd, m_localSlot);
}
if (!processNetCommand(cmd)) {
sendRemoteCommand(cmd);
}
cmd = cmd->getNext();
++numCommands;
}
++numPackets;
// Delete this packet since we won't be needing it anymore.
packet->deleteInstance();
packet = NULL;
cmdList->deleteInstance();
cmdList = NULL;
// signal that this has been processed.
m_transport->m_inBuffer[i].length = 0;
}
}
NetCommandList *cmdList = m_netCommandWrapperList->getReadyCommands();
NetCommandRef *cmd = cmdList->getFirstMessage();
while (cmd != NULL) {
if (CommandRequiresAck(cmd->getCommand())) {
ackCommand(cmd, m_localSlot);
}
if (!processNetCommand(cmd)) {
sendRemoteCommand(cmd);
}
cmd = cmd->getNext();
++numCommands;
}
++numPackets;
// Delete this packet since we won't be needing it anymore.
packet->deleteInstance();
packet = NULL;
cmdList->deleteInstance();
cmdList = NULL;
}
/**
* This is where the non-synchronized network commands should be processed.
* Return TRUE if the command should not be relayed. Return FALSE if it should be relayed.
*/
Bool ConnectionManager::processNetCommand(NetCommandRef *ref) {
NetCommandMsg *msg = ref->getCommand();
if ((msg->getNetCommandType() == NETCOMMANDTYPE_ACKSTAGE1) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_ACKSTAGE2) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_ACKBOTH)) {
processAck(msg);
return FALSE;
}
if ((m_connections[msg->getPlayerID()] == NULL) && (msg->getPlayerID() != m_localSlot)) {
// if this is from a player that is no longer in the game, then ignore them.
return TRUE;
}
if (msg->getNetCommandType() == NETCOMMANDTYPE_WRAPPER) {
processWrapper(ref); // need to send the NetCommandRef since we have to construct the relay for the wrapped command.
return FALSE;
}
if ((msg->getPlayerID() >= 0) && (msg->getPlayerID() < MAX_SLOTS) && (msg->getPlayerID() != m_localSlot)) {
if (m_connections[msg->getPlayerID()] == NULL) {
return TRUE;
}
}
// Don't allow an out of date command to be sent through.
// Its unnecessary traffic and it could cause problems.
//
// This was a fix for a command count bug where a command would be
// executed, then a command for that old frame would be added to the
// FrameData for that frame + 256, and would screw up the command count.
if (IsCommandSynchronized(msg->getNetCommandType())) {
if (ref->getCommand()->getExecutionFrame() < TheGameLogic->getFrame()) {
return TRUE;
}
}
if (msg->getNetCommandType() == NETCOMMANDTYPE_FRAMEINFO) {
processFrameInfo((NetFrameCommandMsg *)msg);
// need to set the relay so we don't send it to ourselves.
UnsignedByte relay = ref->getRelay();
relay = relay & (0xff ^ (1 << m_localSlot));
ref->setRelay(relay);
return FALSE;
}
if (msg->getNetCommandType() == NETCOMMANDTYPE_PROGRESS)
{
//DEBUG_LOG(("ConnectionManager::processNetCommand - got a progress net command from player %d\n", msg->getPlayerID()));
processProgress((NetProgressCommandMsg *) msg);
// need to set the relay so we don't send it to ourselves.
UnsignedByte relay = ref->getRelay();
relay = relay & (0xff ^ (1 << m_localSlot));
ref->setRelay(relay);
return FALSE;
}
if (msg->getNetCommandType() == NETCOMMANDTYPE_TIMEOUTSTART)
{
DEBUG_LOG(("ConnectionManager::processNetCommand - got a TimeOut GameStart net command from player %d\n", msg->getPlayerID()));
processTimeOutGameStart(msg);
return FALSE;
}
if (msg->getNetCommandType() == NETCOMMANDTYPE_RUNAHEADMETRICS) {
processRunAheadMetrics((NetRunAheadMetricsCommandMsg *)msg);
return TRUE;
}
if (msg->getNetCommandType() == NETCOMMANDTYPE_KEEPALIVE) {
return TRUE;
}
if ((msg->getNetCommandType() > NETCOMMANDTYPE_DISCONNECTSTART) && (msg->getNetCommandType() < NETCOMMANDTYPE_DISCONNECTEND)) {
m_disconnectManager->processDisconnectCommand(ref, this);
return TRUE;
}
if (msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTCHAT) {
processDisconnectChat((NetDisconnectChatCommandMsg *)msg);
}
if (msg->getNetCommandType() == NETCOMMANDTYPE_LOADCOMPLETE)
{
DEBUG_LOG(("ConnectionManager::processNetCommand - got a Load Complete net command from player %d\n", msg->getPlayerID()));
processLoadComplete(msg);
return FALSE;
}
if (msg->getNetCommandType() == NETCOMMANDTYPE_CHAT) {
processChat((NetChatCommandMsg *)msg);
return FALSE;
}
if (msg->getNetCommandType() == NETCOMMANDTYPE_FILE) {
processFile((NetFileCommandMsg *)msg);
return FALSE;
}
if (msg->getNetCommandType() == NETCOMMANDTYPE_FILEANNOUNCE) {
processFileAnnounce((NetFileAnnounceCommandMsg *)msg);
return FALSE;
}
if (msg->getNetCommandType() == NETCOMMANDTYPE_FILEPROGRESS) {
processFileProgress((NetFileProgressCommandMsg *)msg);
return FALSE;
}
if (msg->getNetCommandType() == NETCOMMANDTYPE_FRAMERESENDREQUEST) {
processFrameResendRequest((NetFrameResendRequestCommandMsg *)msg);
return TRUE;
}
return FALSE;
}
void ConnectionManager::processFrameResendRequest(NetFrameResendRequestCommandMsg *msg) {
// first make sure this is a valid slot
Int playerID = msg->getPlayerID();
if ((playerID < 0) || (playerID >= MAX_SLOTS)) {
return;
}
// make sure this player is still in our game.
if ((m_connections[playerID] == NULL) || (m_connections[playerID]->isQuitting() == TRUE)) {
return;
}
sendFrameDataToPlayer(playerID, msg->getFrameToResend());
}
/**
* We have received a wrapper for a command too big to fit in a packet.
*/
void ConnectionManager::processWrapper(NetCommandRef *ref)
{
NetWrapperCommandMsg *wrapperMsg = (NetWrapperCommandMsg *)(ref->getCommand());
UnsignedShort commandID = wrapperMsg->getWrappedCommandID();
DEBUG_LOG(("ConnectionManager::processWrapper() - wrapped commandID is %d, commandID is %d\n",
commandID, wrapperMsg->getID()));
Int origProgress = 0;
FileCommandMap::iterator fcIt = s_fileCommandMap.find(commandID);
if (fcIt != s_fileCommandMap.end())
{
origProgress = s_fileProgressMap[m_localSlot][commandID];
}
DEBUG_LOG(("ConnectionManager::processWrapper() - origProgress[%d] == %d for command %d\n",
m_localSlot, origProgress, commandID));
m_netCommandWrapperList->processWrapper(ref);
if (fcIt != s_fileCommandMap.end())
{
Int newProgress = m_netCommandWrapperList->getPercentComplete(commandID);
DEBUG_LOG(("ConnectionManager::processWrapper() - newProgress[%d] == %d for command %d\n",
m_localSlot, newProgress, commandID));
if (newProgress > origProgress && newProgress < 100)
{
DEBUG_LOG(("ConnectionManager::processWrapper() - sending a NetFileProgressCommandMsg\n"));
s_fileProgressMap[m_localSlot][commandID] = newProgress;
Int progressMask = 0xff ^ (1 << m_localSlot);
NetFileProgressCommandMsg *msg = newInstance(NetFileProgressCommandMsg);
msg->setPlayerID(m_localSlot);
msg->setID(0);
if (DoesCommandRequireACommandID(msg->getNetCommandType()))
{
msg->setID(GenerateNextCommandID());
}
msg->setFileID(commandID);
msg->setProgress(newProgress);
sendLocalCommand(msg, progressMask);
processFileProgress(msg);
msg->detach();
}
}
}
/**
* A client has sent us their run ahead metrics, lets store them away for future calculations.
*/
void ConnectionManager::processRunAheadMetrics(NetRunAheadMetricsCommandMsg *msg)
{
UnsignedInt player = msg->getPlayerID();
if ((player >= 0) && (player < MAX_SLOTS) && (isPlayerConnected(player))) {
m_latencyAverages[player] = msg->getAverageLatency();
m_fpsAverages[player] = msg->getAverageFps();
//DEBUG_LOG(("ConnectionManager::processRunAheadMetrics - player %d, fps = %d, latency = %f\n", player, msg->getAverageFps(), msg->getAverageLatency()));
if (m_fpsAverages[player] > 100) {
// limit the reported frame rate average to 100. This is done because if a
// user alt-tab's out of the game their frame rate climbs to in the neighborhood of
// 300, that was deemed "ugly" by the powers that be.
m_fpsAverages[player] = 100;
}
}
}
void ConnectionManager::processDisconnectChat(NetDisconnectChatCommandMsg *msg)
{
UnicodeString unitext;
UnicodeString name;
UnsignedByte playerID = msg->getPlayerID();
if (playerID == m_localSlot) {
name = m_localUser->GetName();
} else if (isPlayerConnected(playerID)) {
name = m_connections[playerID]->getUser()->GetName();
}
unitext.format(L"[%ls] %ls", name.str(), msg->getText().str());
// DEBUG_LOG(("ConnectionManager::processDisconnectChat - got message from player %d, message is %ls\n", playerID, unitext.str()));
TheDisconnectMenu->showChat(unitext); // <-- need to implement this
}
void ConnectionManager::processChat(NetChatCommandMsg *msg)
{
UnicodeString unitext;
UnicodeString name;
UnsignedByte playerID = msg->getPlayerID();
//DEBUG_LOG(("processChat(): playerID = %d\n", playerID));
if (playerID == m_localSlot) {
name = m_localUser->GetName();
//DEBUG_LOG(("connection is NULL, using %ls\n", name.str()));
} else if (((m_connections[playerID] != NULL) && (m_connections[playerID]->isQuitting() == FALSE))) {
name = m_connections[playerID]->getUser()->GetName();
//DEBUG_LOG(("connection is non-NULL, using %ls\n", name.str()));
}
unitext.format(L"[%ls] %ls", name.str(), msg->getText().str());
// DEBUG_LOG(("ConnectionManager::processChat - got message from player %d (mask %8.8X), message is %ls\n", playerID, msg->getPlayerMask(), unitext.str()));
AsciiString playerName;
playerName.format("player%d", msg->getPlayerID());
const Player *player = ThePlayerList->findPlayerWithNameKey( TheNameKeyGenerator->nameToKey( playerName ) );
if (!player)
{
TheInGameUI->message(UnicodeString(L"%ls"), unitext.str());
return;
}
Bool fromObserver = !player->isPlayerActive();
Bool amIObserver = !ThePlayerList->getLocalPlayer()->isPlayerActive();
Bool canSeeChat = amIObserver || !fromObserver && !TheGameInfo->getConstSlot(playerID)->isMuted();
if ( ((1<getPlayerMask() ) && canSeeChat )
{
RGBColor rgb;
rgb.setFromInt(player->getPlayerColor());
TheInGameUI->messageColor(&rgb, UnicodeString(L"%ls"), unitext.str());
// feedback for received chat messages in-game
AudioEventRTS audioEvent("GUICommunicatorIncoming");
TheAudio->addAudioEvent(&audioEvent);
}
}
void ConnectionManager::processFile(NetFileCommandMsg *msg)
{
#ifdef _INTERNAL
UnicodeString log;
log.format(L"Saw file transfer: '%hs' of %d bytes from %d", msg->getPortableFilename().str(), msg->getFileLength(), msg->getPlayerID());
DEBUG_LOG(("%ls\n", log.str()));
#endif
if (TheFileSystem->doesFileExist(msg->getRealFilename().str()))
{
DEBUG_LOG(("File exists already!\n"));
//return;
}
UnsignedByte *buf = msg->getFileData();
Int len = msg->getFileLength();
// uncompress Targas
#ifdef COMPRESS_TARGAS
Bool deleteBuf = FALSE;
if (msg->getFilename().endsWith(".tga") && CompressionManager::isDataCompressed(buf, len))
{
Int uncompLen = CompressionManager::getUncompressedSize(buf, len);
UnsignedByte *uncompBuffer = NEW UnsignedByte[uncompLen];
Int actualLen = CompressionManager::decompressData(buf, len, uncompBuffer, uncompLen);
if (actualLen == uncompLen)
{
DEBUG_LOG(("Uncompressed Targa after map transfer\n"));
deleteBuf = TRUE;
buf = uncompBuffer;
len = uncompLen;
}
else
{
DEBUG_LOG(("Failed to uncompress Targa after map transfer\n"));
delete[] uncompBuffer; // failed to decompress, so just use the source
}
}
#endif // COMPRESS_TARGAS
File *fp = TheFileSystem->openFile(msg->getRealFilename().str(), File::CREATE | File::BINARY | File::WRITE);
if (fp)
{
fp->write(buf, len);
fp->close();
fp = NULL;
DEBUG_LOG(("Wrote %d bytes to file %s!\n",len,msg->getRealFilename().str()));
}
else
{
DEBUG_LOG(("Cannot open file!\n"));
}
DEBUG_LOG(("ConnectionManager::processFile() - sending a NetFileProgressCommandMsg\n"));
Int commandID = msg->getID();
Int newProgress = 100;
s_fileProgressMap[m_localSlot][commandID] = newProgress;
Int progressMask = 0xff ^ (1 << m_localSlot);
NetFileProgressCommandMsg *progressMsg = newInstance(NetFileProgressCommandMsg);
progressMsg->setPlayerID(m_localSlot);
progressMsg->setID(0);
if (DoesCommandRequireACommandID(progressMsg->getNetCommandType()))
{
progressMsg->setID(GenerateNextCommandID());
}
progressMsg->setFileID(commandID);
progressMsg->setProgress(newProgress);
sendLocalCommand(progressMsg, progressMask);
processFileProgress(progressMsg);
progressMsg->detach();
#ifdef COMPRESS_TARGAS
if (deleteBuf)
{
delete[] buf;
buf = NULL;
}
#endif // COMPRESS_TARGAS
}
void ConnectionManager::processFileAnnounce(NetFileAnnounceCommandMsg *msg)
{
DEBUG_LOG(("ConnectionManager::processFileAnnounce() - expecting '%s' (%s) in command %d\n", msg->getPortableFilename().str(), msg->getRealFilename().str(), msg->getFileID()));
s_fileCommandMap[msg->getFileID()] = msg->getRealFilename();
s_fileRecipientMaskMap[msg->getFileID()] = msg->getPlayerMask();
for (Int i=0; igetPlayerMask() )
{
s_fileProgressMap[i][msg->getFileID()] = 0;
}
else
{
s_fileProgressMap[i][msg->getFileID()] = 100; // they don't need to get it, so they're already done.
}
}
}
void ConnectionManager::processFileProgress(NetFileProgressCommandMsg *msg)
{
DEBUG_LOG(("ConnectionManager::processFileProgress() - command %d is at %d%%\n",
msg->getFileID(), msg->getProgress()));
Int oldProgress = s_fileProgressMap[msg->getPlayerID()][msg->getFileID()];
s_fileProgressMap[msg->getPlayerID()][msg->getFileID()] = max(oldProgress, msg->getProgress());
}
void ConnectionManager::processProgress( NetProgressCommandMsg *msg )
{
TheGameLogic->processProgress(msg->getPlayerID(), msg->getPercentage());
}
void ConnectionManager::processLoadComplete( NetCommandMsg *msg )
{
TheGameLogic->processProgressComplete(msg->getPlayerID());
}
void ConnectionManager::processTimeOutGameStart( NetCommandMsg *msg )
{
TheGameLogic->timeOutGameStart();
}
/**
* Another client has sent us the command count for a new frame.
*/
void ConnectionManager::processFrameInfo(NetFrameCommandMsg *msg) {
//stupid frame info, why don't you process yourself?
UnsignedInt playerID = msg->getPlayerID();
if ((playerID >= 0) && (playerID < MAX_SLOTS)) {
if (m_frameData[playerID] != NULL) {
// DEBUG_LOG(("ConnectionManager::processFrameInfo - player %d, frame %d, command count %d, received on frame %d\n", playerID, msg->getExecutionFrame(), msg->getCommandCount(), TheGameLogic->getFrame()));
m_frameData[playerID]->setFrameCommandCount(msg->getExecutionFrame(), msg->getCommandCount());
}
}
}
/**
* We just got a stage 1 ack from someone. So we should remove it from the connection that sent it so
* it doesn't keep resending it.
*/
void ConnectionManager::processAckStage1(NetCommandMsg *msg) {
#if defined(_DEBUG) || defined(_INTERNAL)
Bool doDebug = (msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTFRAME) ? TRUE : FALSE;
#endif
UnsignedByte playerID = msg->getPlayerID();
NetCommandRef *ref = NULL;
#if defined(_DEBUG) || defined(_INTERNAL)
if (doDebug == TRUE) {
DEBUG_LOG(("ConnectionManager::processAck - processing ack for command %d from player %d\n", ((NetAckStage1CommandMsg *)msg)->getCommandID(), playerID));
}
#endif
if ((playerID >= 0) && (playerID < NUM_CONNECTIONS)) {
if (m_connections[playerID] != NULL) {
ref = m_connections[playerID]->processAck(msg);
}
} else {
DEBUG_ASSERTCRASH((playerID >= 0) && (playerID < NUM_CONNECTIONS), ("ConnectionManager::processAck - %d is an invalid player number"));
}
if (ref != NULL) {
if (ref->getCommand()->getNetCommandType() == NETCOMMANDTYPE_FRAMEINFO) {
m_frameMetrics.processLatencyResponse(((NetFrameCommandMsg *)(ref->getCommand()))->getExecutionFrame());
}
ref->deleteInstance();
ref = NULL;
}
}
/**
* We just got a stage 2 ack from someone. So remove it from the pending commands list so it doesn't
* get sent in the case of a new packet router.
*/
void ConnectionManager::processAckStage2(NetCommandMsg *msg) {
UnsignedShort commandID = 0;
UnsignedByte playerID = 0;
if (msg->getNetCommandType() == NETCOMMANDTYPE_ACKSTAGE2) {
commandID = ((NetAckStage2CommandMsg *)msg)->getCommandID();
playerID = ((NetAckStage2CommandMsg *)msg)->getOriginalPlayerID();
} else if (msg->getNetCommandType() == NETCOMMANDTYPE_ACKBOTH) {
commandID = ((NetAckBothCommandMsg *)msg)->getCommandID();
playerID = ((NetAckBothCommandMsg *)msg)->getOriginalPlayerID();
} else {
return;
}
NetCommandRef *ref = m_pendingCommands->findMessage(commandID, playerID);
if (ref != NULL) {
//DEBUG_LOG(("ConnectionManager::processAckStage2 - removing command %d from the pending commands list.\n", commandID));
DEBUG_ASSERTCRASH((m_localSlot == playerID), ("Found a command in the pending commands list that wasn't originated by the local player"));
m_pendingCommands->removeMessage(ref);
ref->deleteInstance();
ref = NULL;
} else {
//DEBUG_LOG(("ConnectionManager::processAckStage2 - Couldn't find command %d from player %d in the pending commands list.\n", commandID, playerID));
}
ref = m_relayedCommands->findMessage(commandID, playerID);
if (ref != NULL) {
//DEBUG_LOG(("ConnectionManager::processAckStage2 - found command ID %d from player %d in the relayed commands list.\n", commandID, playerID));
UnsignedByte relay = ref->getRelay();
//DEBUG_LOG(("ConnectionManager::processAckStage2 - relay was %d and is now ", relay));
relay = relay & ~(1 << msg->getPlayerID());
//DEBUG_LOG(("%d\n", relay));
if (relay == 0) {
//DEBUG_LOG(("ConnectionManager::processAckStage2 - relay is 0, removing command from the relayed commands list.\n"));
m_relayedCommands->removeMessage(ref);
NetAckStage2CommandMsg *ackmsg = newInstance(NetAckStage2CommandMsg)(ref->getCommand());
sendLocalCommand(ackmsg, 1 << ackmsg->getOriginalPlayerID());
ref->deleteInstance();
ref = NULL;
ackmsg->detach();
ackmsg = NULL;
} else {
ref->setRelay(relay);
}
}
}
/**
* We just got a "both" ack from someone. So process it as both a stage 1 and stage 2 ack.
*/
void ConnectionManager::processAck(NetCommandMsg *msg) {
if ((msg->getNetCommandType() == NETCOMMANDTYPE_ACKSTAGE1) || (msg->getNetCommandType() == NETCOMMANDTYPE_ACKBOTH)) {
processAckStage1(msg);
}
if ((msg->getNetCommandType() == NETCOMMANDTYPE_ACKSTAGE2) || (msg->getNetCommandType() == NETCOMMANDTYPE_ACKBOTH)) {
processAckStage2(msg);
}
}
/**
* A player has just left our game. Delete their connection and frame data manager.
* return codes are:
* PLAYERLEAVECODE_UNKNOWN - player didn't have a valid slot number.
* PLAYERLEAVECODE_CLIENT - someone in the game that wasn't us or the packet router.
* PLAYERLEAVECODE_LOCAL - We are leaving the game, we could also be the packet router.
* PLAYERLEAVECODE_PACKETROUTER - The packet router left the game.
*
* If we are leaving and are also the packet router, it will return the PLAYERLEAVECODE_LOCAL return code.
*/
PlayerLeaveCode ConnectionManager::processPlayerLeave(NetPlayerLeaveCommandMsg *msg) {
UnsignedByte playerID = msg->getLeavingPlayerID();
if ((playerID != m_localSlot) && (m_connections[playerID] != NULL)) {
DEBUG_LOG(("ConnectionManager::processPlayerLeave() - setQuitting() on player %d on frame %d\n", playerID, TheGameLogic->getFrame()));
m_connections[playerID]->setQuitting();
}
DEBUG_ASSERTCRASH(m_frameData[playerID]->getIsQuitting() == FALSE, ("Player %d is already quitting", playerID));
if ((playerID != m_localSlot) && (m_frameData[playerID] != NULL) && (m_frameData[playerID]->getIsQuitting() == FALSE)) {
DEBUG_LOG(("ConnectionManager::processPlayerLeave - setQuitFrame on player %d for frame %d\n", playerID, TheGameLogic->getFrame()+1));
m_frameData[playerID]->setQuitFrame(TheGameLogic->getFrame() + FRAMES_TO_KEEP + 1);
}
if (playerID == m_localSlot)
{
// we're leaving, so mark our connections and frame datas to go away.
for (Int i=0; iclearCommandsExceptFrom(m_localSlot);
m_connections[i]->setQuitting();
}
}
}
PlayerLeaveCode code = disconnectPlayer(playerID);
DEBUG_LOG(("ConnectionManager::processPlayerLeave() - just disconnected player %d with ret code %d\n", playerID, code));
if (code == PLAYERLEAVECODE_PACKETROUTER)
resendPendingCommands();
PopulateInGameDiplomacyPopup();
return code;
}
UnsignedInt ConnectionManager::getPacketRouterFallbackSlot(Int packetRouterNumber) {
if ((packetRouterNumber >= 0) && (packetRouterNumber < MAX_SLOTS)) {
return m_packetRouterFallback[packetRouterNumber];
}
return MAX_SLOTS;
}
UnsignedInt ConnectionManager::getPacketRouterSlot() {
return m_packetRouterSlot;
}
Bool ConnectionManager::areAllQueuesEmpty(void) {
Bool retval = TRUE;
for (Int i = 0; (i < MAX_SLOTS) && retval; ++i) {
if (m_connections[i] != NULL) {
if (m_connections[i]->isQueueEmpty() == FALSE) {
//DEBUG_LOG(("ConnectionManager::areAllQueuesEmpty() - m_connections[%d] is not empty\n", i));
//m_connections[i]->debugPrintCommands();
retval = FALSE;
}
}
}
return retval;
}
Bool ConnectionManager::canILeave() {
return areAllQueuesEmpty();
}
/**
* The local player is leaving. Tell the local player as well as the other players
* to remove this player at the specified frame.
*/
void ConnectionManager::handleLocalPlayerLeaving(UnsignedInt frame) {
NetPlayerLeaveCommandMsg *msg = newInstance(NetPlayerLeaveCommandMsg);
msg->setLeavingPlayerID(m_localSlot);
msg->setExecutionFrame(frame);
if (DoesCommandRequireACommandID(msg->getNetCommandType())) {
msg->setID(GenerateNextCommandID());
}
msg->setPlayerID(m_localSlot);
DEBUG_LOG(("ConnectionManager::handleLocalPlayerLeaving - Local player leaving on frame %d\n", frame));
DEBUG_ASSERTCRASH(m_packetRouterSlot >= 0, ("ConnectionManager::handleLocalPlayerLeaving, packet router is %d, illegal value.", m_packetRouterSlot));
sendLocalCommand(msg);
msg->detach();
}
/**
* We just got a message that needs to be ack'd, so ack it!
*/
void ConnectionManager::ackCommand(NetCommandRef *ref, UnsignedInt localSlot) {
NetCommandMsg *msg = ref->getCommand();
NetCommandMsg *ackmsg;
UnsignedShort commandID;
UnsignedByte originalPlayerID;
UnsignedByte sendRelay = 0;
// Make send relay a bitmask for the connections that the relay will actually be sent to. This is
// necessary to determine whether or not we have to wait to send a stage 2 ack.
for (Int i = 0; i < MAX_SLOTS; ++i) {
if (((m_connections[i] != NULL) && (m_connections[i]->isQuitting() == FALSE))) {
sendRelay = sendRelay | (1 << i);
}
}
#if defined(_DEBUG) || defined(_INTERNAL)
Bool doDebug = (msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTFRAME) ? TRUE : FALSE;
#endif
sendRelay = sendRelay & ref->getRelay();
if (sendRelay == 0) {
NetAckBothCommandMsg *bothmsg = newInstance(NetAckBothCommandMsg)(ref->getCommand());
ackmsg = bothmsg;
commandID = bothmsg->getCommandID();
originalPlayerID = bothmsg->getOriginalPlayerID();
#if defined(_DEBUG) || defined(_INTERNAL)
if (doDebug) {
DEBUG_LOG(("ConnectionManager::ackCommand - doing ack both for command %d from player %d\n", bothmsg->getCommandID(), bothmsg->getOriginalPlayerID()));
}
#endif
} else {
NetAckStage1CommandMsg *stage1msg = newInstance(NetAckStage1CommandMsg)(ref->getCommand());
ackmsg = stage1msg;
commandID = stage1msg->getCommandID();
originalPlayerID = stage1msg->getOriginalPlayerID();
#if defined(_DEBUG) || defined(_INTERNAL)
if (doDebug) {
DEBUG_LOG(("ConnectionManager::ackCommand - doing ack stage 1 for command %d from player %d\n", stage1msg->getCommandID(), stage1msg->getOriginalPlayerID()));
}
#endif
}
ackmsg->setPlayerID(localSlot); // Tell the player who this ack is coming from.
if (CommandRequiresDirectSend(msg) && CommandRequiresAck(msg)) {
// Send this ack directly back to the sending player, don't go through the packet router.
if ((msg->getPlayerID() >= 0) && (msg->getPlayerID() < MAX_SLOTS)) {
if (m_connections[msg->getPlayerID()] != NULL) {
m_connections[msg->getPlayerID()]->sendNetCommandMsg(ackmsg, 1 << msg->getPlayerID());
}
}
} else {
// The local connection may be the packet router, in that case, the connection would be NULL. So do something about it!
if ((m_packetRouterSlot >= 0) && (m_packetRouterSlot < MAX_SLOTS)) {
if (m_connections[m_packetRouterSlot] != NULL) {
// DEBUG_LOG(("ConnectionManager::ackCommand - acking command %d from player %d to packet router.\n", commandID, m_packetRouterSlot));
m_connections[m_packetRouterSlot]->sendNetCommandMsg(ackmsg, 1 << m_packetRouterSlot);
} else if (m_localSlot == m_packetRouterSlot) {
// we are the packet router, send the ack to the player that sent the command.
if ((msg->getPlayerID() >= 0) && (msg->getPlayerID() < MAX_SLOTS)) {
if (m_connections[msg->getPlayerID()] != NULL) {
// DEBUG_LOG(("ConnectionManager::ackCommand - acking command %d from player %d directly to player.\n", commandID, msg->getPlayerID()));
m_connections[msg->getPlayerID()]->sendNetCommandMsg(ackmsg, 1 << msg->getPlayerID());
} else {
// DEBUG_ASSERTCRASH(m_connections[msg->getPlayerID()] != NULL, ("Connection to player is NULL"));
}
} else {
DEBUG_ASSERTCRASH((msg->getPlayerID() >= 0) && (msg->getPlayerID() < MAX_SLOTS), ("Command sent by an invalid player ID."));
}
} else {
DEBUG_ASSERTCRASH(m_connections[m_packetRouterSlot] != NULL, ("Connection to packet router is NULL"));
}
} else {
DEBUG_ASSERTCRASH((m_packetRouterSlot >= 0) && (m_packetRouterSlot < MAX_SLOTS), ("I don't know who the packet router is."));
}
}
ackmsg->detach();
}
/**
* This is where we relay a command from one client to others (including ourselves).
* This should only be done by the current packet router.
*/
void ConnectionManager::sendRemoteCommand(NetCommandRef *msg) {
UnsignedByte actualRelay = 0;
if (msg->getCommand() == NULL) {
return;
}
DEBUG_LOG(("ConnectionManager::sendRemoteCommand - sending net command %d of type %s from player %d, relay is 0x%x\n",
msg->getCommand()->getID(), GetAsciiNetCommandType(msg->getCommand()->getNetCommandType()).str(), msg->getCommand()->getPlayerID(), msg->getRelay()));
UnsignedByte relay = msg->getRelay();
if ((relay & (1 << m_localSlot)) && (m_frameData[msg->getCommand()->getPlayerID()] != NULL)) {
if (IsCommandSynchronized(msg->getCommand()->getNetCommandType())) {
DEBUG_LOG(("ConnectionManager::sendRemoteCommand - adding net command of type %s to player %d for frame %d\n", GetAsciiNetCommandType(msg->getCommand()->getNetCommandType()).str(), msg->getCommand()->getPlayerID(), msg->getCommand()->getExecutionFrame()));
m_frameData[msg->getCommand()->getPlayerID()]->addNetCommandMsg(msg->getCommand());
}
}
for (Int i = 0; i < MAX_SLOTS; ++i) {
if ((relay & (1 << i)) && ((m_connections[i] != NULL) && (m_connections[i]->isQuitting() == FALSE))) {
DEBUG_LOG(("ConnectionManager::sendRemoteCommand - relaying command %d to player %d\n", msg->getCommand()->getID(), i));
m_connections[i]->sendNetCommandMsg(msg->getCommand(), 1 << i);
actualRelay = actualRelay | (1 << i);
}
}
if ((actualRelay != 0) && (CommandRequiresAck(msg->getCommand()) == TRUE)) {
NetCommandRef *ref = m_relayedCommands->addMessage(msg->getCommand());
if (ref != NULL) {
ref->setRelay(actualRelay);
//DEBUG_LOG(("ConnectionManager::sendRemoteCommand - command %d added to relayed commands with relay %d\n", msg->getCommand()->getID(), ref->getRelay()));
}
}
// Do some metrics to find the minimum packet arrival cushion.
if (IsCommandSynchronized(msg->getCommand()->getNetCommandType())) {
// DEBUG_LOG(("ConnectionManager::sendRemoteCommand - about to call allCommandsReady\n"));
if (allCommandsReady(msg->getCommand()->getExecutionFrame(), TRUE)) {
UnsignedInt cushion = msg->getCommand()->getExecutionFrame() - TheGameLogic->getFrame();
if ((cushion < m_smallestPacketArrivalCushion) || (m_smallestPacketArrivalCushion == -1)) {
m_smallestPacketArrivalCushion = cushion;
}
m_frameMetrics.addCushion(cushion);
// DEBUG_LOG(("Adding %d to cushion for frame %d\n", cushion, msg->getCommand()->getExecutionFrame()));
}
}
}
/**
* ConnectionManager::update
* Update the connections. Tell them to do the receive and send. Also relay
* commands to their final destinations as necessary.
*/
void ConnectionManager::update(Bool isInGame) {
//
// 1. do this
// 2. do that
// 3. do something else
// 4. blow something up
// 5. bust some cap
//
if ((m_localAddr == 0) || (m_localPort == 0)) {
// we don't have a local address or port yet, this is bad.
DEBUG_ASSERTCRASH((m_localAddr != 0) && (m_localPort != 0), ("ConnectionManager doesn't have a local address."));
return;
}
m_transport->doRecv();
if (isInGame) {
m_disconnectManager->update(this);
}
// take the packets from the transport, break them up into commands, and give them to the appropriate connections.
doRelay();
// send any necessary keep-alive packets.
doKeepAlive();
for (Int i = 0; i < NUM_CONNECTIONS; ++i) {
if (m_connections[i] != NULL) {
/*
if (m_connections[i]->isQueueEmpty() == FALSE) {
// DEBUG_LOG(("ConnectionManager::update - calling doSend on connection %d\n", i));
}
*/
m_connections[i]->doSend();
if (m_connections[i]->isQuitting() && m_connections[i]->isQueueEmpty())
{
DEBUG_LOG(("ConnectionManager::update - deleting connection for slot %d\n", i));
m_connections[i]->deleteInstance();
m_connections[i] = NULL;
}
}
if ((m_frameData[i] != NULL) && (m_frameData[i]->getIsQuitting() == TRUE)) {
if (m_frameData[i]->getQuitFrame() == TheGameLogic->getFrame()) {
DEBUG_LOG(("ConnectionManager::update - deleting frame data for slot %d on quitting frame %d\n", i, m_frameData[i]->getQuitFrame()));
m_frameData[i]->deleteInstance();
m_frameData[i] = NULL;
}
}
}
m_transport->doSend();
}
void ConnectionManager::updateRunAhead(Int oldRunAhead, Int frameRate, Bool didSelfSlug, Int nextExecutionFrame) {
static time_t lasttimesent = 0;
time_t curTime = timeGetTime();
if ((lasttimesent == 0) || ((curTime - lasttimesent) > TheGlobalData->m_networkRunAheadMetricsTime)) {
if (m_localSlot == m_packetRouterSlot) {
// We are the packet router, time to compute a new run ahead for this game.
m_latencyAverages[m_localSlot] = m_frameMetrics.getAverageLatency();
// since we are now using the display frame rate rather than the logic frame rate to get our average FPS,
// it doesn't make sense to send the desired logic frame rate if we "slugged" ourself.
// if (didSelfSlug) {
// m_fpsAverages[m_localSlot] = frameRate;
// } else {
m_fpsAverages[m_localSlot] = m_frameMetrics.getAverageFPS();
// }
if (didSelfSlug) {
//DEBUG_LOG(("ConnectionManager::updateRunAhead - local player run ahead metrics, fps = %d, actual fps = %d, latency = %f, didSelfSlug = true\n", m_fpsAverages[m_localSlot], m_frameMetrics.getAverageFPS(), m_latencyAverages[m_localSlot]));
} else {
//DEBUG_LOG(("ConnectionManager::updateRunAhead - local player run ahead metrics, fps = %d, latency = %f, didSelfSlug = false\n", m_fpsAverages[m_localSlot], m_latencyAverages[m_localSlot]));
}
Int minFps;
Int minFpsPlayer;
getMinimumFps(minFps, minFpsPlayer);
DEBUG_LOG(("ConnectionManager::updateRunAhead - max latency = %f, min fps = %d, min fps player = %d old FPS = %d\n", getMaximumLatency(), minFps, minFpsPlayer, frameRate));
if ((minFps >= ((frameRate * 9) / 10)) && (minFps < frameRate)) {
// if the minimum fps is within 10% of the desired framerate, then keep the current minimum fps.
minFps = frameRate;
}
if (minFps < 5) {
minFps = 5; // Absolutely do not run below 5 fps.
}
if (minFps > TheGlobalData->m_framesPerSecondLimit) {
minFps = TheGlobalData->m_framesPerSecondLimit; // Cap to 30 FPS.
}
DEBUG_LOG(("ConnectionManager::updateRunAhead - minFps after adjustment is %d\n", minFps));
Int newRunAhead = (Int)((getMaximumLatency() / 2.0) * (Real)minFps);
newRunAhead += (newRunAhead * TheGlobalData->m_networkRunAheadSlack) / 100; // Add in 10% of slack to the run ahead in case of network hiccups.
if (newRunAhead < MIN_RUNAHEAD) {
newRunAhead = MIN_RUNAHEAD; // make sure its at least MIN_RUNAHEAD.
}
if (newRunAhead > (MAX_FRAMES_AHEAD / 2)) {
newRunAhead = MAX_FRAMES_AHEAD / 2; // dont let run ahead get out of hand.
}
NetRunAheadCommandMsg *msg = newInstance(NetRunAheadCommandMsg);
msg->setPlayerID(m_localSlot);
if (DoesCommandRequireACommandID(msg->getNetCommandType())) {
msg->setID(GenerateNextCommandID());
}
// needs to be set to the greater of getExecutionFrame and TheGameLogic->getFrame() + oldRunAhead
// This prevents the case of...
// run ahead starts at 30
// run ahead changes to 10 at frame 31 (the command was created on frame 1)
// run ahead changes to 10 at frame 56 (the command was created on frame 46)
// notice that 56 is within the previous run ahead of 30 which has triggered
// the frame command count being set for frames 1 through 60 since run ahead
// didn't change for the first time till frame 31. This creates an extra command
// for frame 56 that isn't accounted for in the frame command count that is sent
// out in the NetFrameCommandMsg. sheesh.
if (nextExecutionFrame > (TheGameLogic->getFrame() + oldRunAhead)) {
msg->setExecutionFrame(nextExecutionFrame);
} else {
msg->setExecutionFrame(TheGameLogic->getFrame() + oldRunAhead);
}
msg->setRunAhead(newRunAhead);
msg->setFrameRate(minFps);
//DEBUG_LOG(("ConnectionManager::updateRunAhead - new run ahead = %d, new frame rate = %d, execution frame %d\n", newRunAhead, minFps, msg->getExecutionFrame()));
sendLocalCommand(msg, 0xff ^ (1 << minFpsPlayer)); // Send the packet to everyone but the lowest FPS player.
NetRunAheadCommandMsg *msg2 = newInstance(NetRunAheadCommandMsg);
msg2->setPlayerID(m_localSlot);
if (DoesCommandRequireACommandID(msg2->getNetCommandType())) {
/*
* Ok there needs to be a big friggin comment about this change...
* What happens is that the two run ahead commands get sent to different players
* using different command ID's. So player 1 has the run ahead command as command x
* and player 2 has the command as command x+1. This is all good except when it comes
* to players being disconnected. With the new disconnect scheme player 1 could potentially
* send his run ahead command to player 2 (or the other way around) to let player 2 catch
* up to him. So if player 2 has his run ahead command as x+1 and now he gets player 1's
* command list with the run ahead command listed as command x, he won't see them as being
* the same command and will now think he has two different run ahead commands for that frame
* and thus his command list will have an extra command and he will never be able to recover.
* So to fix this we have to use the same command ID for both run ahead commands. That way
* when the commands are copied places for the disconnect screen they will be seen as the
* same command, and all will be good.
*/
// msg2->setID(GenerateNextCommandID());
msg2->setID(msg->getID());
}
if (nextExecutionFrame > (TheGameLogic->getFrame() + oldRunAhead)) {
msg2->setExecutionFrame(nextExecutionFrame);
} else {
msg2->setExecutionFrame(TheGameLogic->getFrame() + oldRunAhead);
}
// Let the player with the slowest FPS run a little faster than the other computers...
// just in case they are able to. Then we might be able to run the game faster which would be good.
Int newMinFps = (minFps * 11) / 10;
if (newMinFps == minFps) {
newMinFps = minFps + 1;
}
if (newMinFps > 30) {
newMinFps = 30; // Cap FPS to 30.
}
msg2->setRunAhead(newRunAhead);
msg2->setFrameRate(newMinFps);
sendLocalCommand(msg2, 1 << minFpsPlayer);
msg->detach();
msg2->detach();
} else {
// We are not the packet router, send our metrics info to the packet router.
NetRunAheadMetricsCommandMsg *msg = newInstance(NetRunAheadMetricsCommandMsg);
msg->setPlayerID(m_localSlot);
if (DoesCommandRequireACommandID(msg->getNetCommandType())) {
msg->setID(GenerateNextCommandID());
}
msg->setAverageLatency(m_frameMetrics.getAverageLatency());
// see above for explanation.
// if (didSelfSlug) {
// msg->setAverageFps(frameRate);
// } else {
msg->setAverageFps(m_frameMetrics.getAverageFPS());
// }
if (didSelfSlug) {
//DEBUG_LOG(("ConnectionManager::updateRunAhead - average latency = %f, average fps = %d, actual fps = %d, didSelfSlug = true\n", m_frameMetrics.getAverageLatency(), m_frameMetrics.getAverageFPS(), m_frameMetrics.getAverageFPS()));
} else {
//DEBUG_LOG(("ConnectionManager::updateRunAhead - average latency = %f, average fps = %d, didSelfSlug = false\n", m_frameMetrics.getAverageLatency(), m_frameMetrics.getAverageFPS()));
}
m_connections[m_packetRouterSlot]->sendNetCommandMsg(msg, 1 << m_packetRouterSlot);
msg->detach();
}
lasttimesent = curTime;
}
}
Real ConnectionManager::getMaximumLatency() {
// This works for 2 player games because the latency for the packet router is always 0.
Real lat1 = 0.0;
Real lat2 = 0.0;
for (Int i = 0; i < MAX_SLOTS; ++i) {
if (isPlayerConnected(i)) {
if (m_latencyAverages[i] != 0.0) {
if (m_latencyAverages[i] > lat1) {
lat2 = lat1;
lat1 = m_latencyAverages[i];
} else if (m_latencyAverages[i] > lat2) {
lat2 = m_latencyAverages[i];
}
}
}
}
return (lat1 + lat2);
}
void ConnectionManager::getMinimumFps(Int &minFps, Int &minFpsPlayer) {
minFps = -1;
minFpsPlayer = -1;
// DEBUG_LOG(("ConnectionManager::getMinimumFps -"));
for (Int i = 0; i < MAX_SLOTS; ++i) {
if ((m_connections[i] != NULL) || (i == m_localSlot)) {
// DEBUG_LOG((" %d: %d,", i, m_fpsAverages[i]));
if (m_fpsAverages[i] != -1) {
if ((minFps == -1) || (m_fpsAverages[i] < minFps)) {
minFps = m_fpsAverages[i];
minFpsPlayer = i;
}
}
}
}
// DEBUG_LOG(("\n"));
}
UnsignedInt ConnectionManager::getMinimumCushion() {
return m_frameMetrics.getMinimumCushion();
}
/**
* The commands for the given frame are all ready, time to send out our command count for that frame.
*/
void ConnectionManager::processFrameTick(UnsignedInt frame) {
if ((m_frameData[m_localSlot] == NULL) || (m_frameData[m_localSlot]->getIsQuitting() == TRUE)) {
// if the local frame data stuff is NULL, we must be leaving the game.
return;
}
UnsignedShort commandCount = m_frameData[m_localSlot]->getCommandCount(frame);
NetFrameCommandMsg *msg = newInstance(NetFrameCommandMsg);
msg->setExecutionFrame(frame);
msg->setCommandCount(commandCount);
if (DoesCommandRequireACommandID(msg->getNetCommandType())) {
msg->setID(GenerateNextCommandID());
}
msg->setPlayerID(m_localSlot);
m_frameMetrics.doPerFrameMetrics(frame);
DEBUG_LOG(("ConnectionManager::processFrameTick - sending frame info for frame %d, ID %d, command count %d\n", frame, msg->getID(), commandCount));
sendLocalCommand(msg, 0xff & ~(1 << m_localSlot));
msg->detach();
}
/**
* Set the local address.
*/
void ConnectionManager::setLocalAddress(UnsignedInt ip, UnsignedInt port) {
DEBUG_LOG(("ConnectionManager::setLocalAddress() - local address is %X:%d\n", ip, port));
m_localAddr = ip;
m_localPort = port;
}
/**
* Initialize the transport object
*/
void ConnectionManager::initTransport() {
DEBUG_ASSERTCRASH((m_transport == NULL), ("m_transport already exists when trying to init it."));
DEBUG_LOG(("ConnectionManager::initTransport - Initializing Transport\n"));
if (m_transport != NULL) {
delete m_transport;
m_transport = NULL;
}
m_transport = new Transport;
m_transport->reset();
m_transport->init(m_localAddr, m_localPort);
}
/**
* This is where the commands from the local client are sent to the other clients in
* the game. This is also where the local commands are put into the frame data for
* future execution.
*/
void ConnectionManager::sendLocalGameMessage(GameMessage *msg, UnsignedInt frame) {
UnsignedShort currentID = 0;
if (DoesCommandRequireACommandID(NETCOMMANDTYPE_GAMECOMMAND)) {
currentID = GenerateNextCommandID();
}
NetCommandMsg *netmsg = newInstance(NetGameCommandMsg)(msg);
netmsg->setExecutionFrame(frame);
netmsg->setPlayerID(m_localSlot);
netmsg->setID(currentID);
sendLocalCommand(netmsg);
netmsg->detach();
}
/**
* This is a NetCommandMsg that originated on the local computer. Send this to everyone specified
* in the relay field. Commands sent in this way go through the packet router.
*/
void ConnectionManager::sendLocalCommand(NetCommandMsg *msg, UnsignedByte relay /* = 0xff by default*/) {
if (CommandRequiresDirectSend(msg) || (m_packetRouterSlot < 0) || (m_packetRouterSlot >= MAX_SLOTS) || (m_connections[m_packetRouterSlot] == NULL)) {
sendLocalCommandDirect(msg, relay);
return;
}
msg->attach();
DEBUG_LOG(("ConnectionManager::sendLocalCommand - sending net command %d of type %s\n", msg->getID(),
GetAsciiNetCommandType(msg->getNetCommandType()).str()));
if (relay & (1 << m_localSlot)) {
DEBUG_LOG(("ConnectionManager::sendLocalCommand - adding net command of type %s to player %d for frame %d\n", GetAsciiNetCommandType(msg->getNetCommandType()).str(), msg->getPlayerID(), msg->getExecutionFrame()));
m_frameData[m_localSlot]->addNetCommandMsg(msg);
}
// Send the packet to everyone else
if (m_localSlot == m_packetRouterSlot) {
// I am the packet router, I need to send this packet to everyone individually.
for (Int i = 0; i < MAX_SLOTS; ++i) {
// Send it to all open connections.
if (((m_connections[i] != NULL) && (m_connections[i]->isQuitting() == FALSE)) && (relay & (1 << i))) {
// Set the relay mask to only go to this player so he knows not to relay it to anyone else.
UnsignedByte temprelay = 1 << i;
m_connections[i]->sendNetCommandMsg(msg, temprelay); // This will create a new copy of netmsg for this connection.
}
}
} else {
// Send the command to everyone else via the packet router.
UnsignedByte temprelay = relay & ~(1 << m_localSlot); // Tell the packet router to relay the message to everyone but myself.
// Hopefully the packet router is smart enough to not send it
// to slots that are not in the game.
m_connections[m_packetRouterSlot]->sendNetCommandMsg(msg, temprelay); // This will create a new copy of netmsg for this connection.
if (CommandRequiresAck(msg)) {
NetCommandRef *ref = m_pendingCommands->addMessage(msg);
//DEBUG_LOG(("ConnectionManager::sendLocalCommand - added command %d to pending commands list.\n", msg->getID()));
if (ref != NULL) {
ref->setRelay(temprelay);
}
}
}
msg->detach(); // detach from the command msg.
}
/**
* This is a NetCommandMsg that originated on the local computer. Send this to everyone specified
* in the relay field. Commands sent in this way do not go through the packet router.
*/
void ConnectionManager::sendLocalCommandDirect(NetCommandMsg *msg, UnsignedByte relay) {
msg->attach();
if (((relay & (1 << m_localSlot)) != 0) && (m_frameData[m_localSlot] != NULL)) {
if (IsCommandSynchronized(msg->getNetCommandType()) == TRUE) {
DEBUG_LOG(("ConnectionManager::sendLocalCommandDirect - adding net command of type %s to player %d for frame %d\n", GetAsciiNetCommandType(msg->getNetCommandType()).str(), msg->getPlayerID(), msg->getExecutionFrame()));
m_frameData[m_localSlot]->addNetCommandMsg(msg);
}
}
for (Int i = 0; i < MAX_SLOTS; ++i) {
if ((relay & (1 << i)) != 0) {
if ((m_connections[i] != NULL) && (m_connections[i]->isQuitting() == FALSE)) {
UnsignedByte temprelay = 1 << i;
m_connections[i]->sendNetCommandMsg(msg, temprelay);
DEBUG_LOG(("ConnectionManager::sendLocalCommandDirect - Sending direct command %d of type %s to player %d\n", msg->getID(), GetAsciiNetCommandType(msg->getNetCommandType()).str(), i));
}
}
}
msg->detach();
}
Int commandsReadyDebugSpewage = 0;
/**
* Returns true if all the commands for the given frame are ready to be executed.
*/
Bool ConnectionManager::allCommandsReady(UnsignedInt frame, Bool justTesting /* = FALSE */) {
Bool retval = TRUE;
FrameDataReturnType frameRetVal;
// retval = FALSE; // ****for testing purposes only!!!!!!****
for (Int i = 0; (i < MAX_SLOTS) && retval; ++i) {
if ((m_frameData[i] != NULL) && (m_frameData[i]->getIsQuitting() == FALSE)) {
/*
if (!(m_frameData[i]->allCommandsReady(frame, (frame != commandsReadyDebugSpewage) && (justTesting == FALSE)))) {
if ((frame != commandsReadyDebugSpewage) && (justTesting == FALSE)) {
DEBUG_LOG(("ConnectionManager::allCommandsReady, frame %d player %d not ready.\n", frame, i));
commandsReadyDebugSpewage = frame;
}
retval = FALSE;
} else {
// DEBUG_LOG(("ConnectionManager::allCommandsReady, frame %d player %d is ready.\n", frame, i));
}
*/
frameRetVal = m_frameData[i]->allCommandsReady(frame, (frame != commandsReadyDebugSpewage) && (justTesting == FALSE));
if (frameRetVal == FRAMEDATA_NOTREADY) {
retval = FALSE;
} else if (frameRetVal == FRAMEDATA_RESEND) {
requestFrameDataResend(i, frame);
retval = FALSE;
}
}
}
if (frameRetVal == FRAMEDATA_RESEND) {
// this frame's data is really screwed up, we need to clean it out so it can be resent to us.
for (i = 0; i < MAX_SLOTS; ++i) {
if ((m_frameData[i] != NULL) && (i != m_localSlot)) {
m_frameData[i]->resetFrame(frame, FALSE);
}
}
}
if ((retval == TRUE) && (justTesting == FALSE)) {
m_disconnectManager->allCommandsReady(TheGameLogic->getFrame(), this);
retval = m_disconnectManager->allowedToContinue(); // allow the disconnect manager to keep us on this frame
// in case we are waiting for a new packet router or something.
}
return retval;
}
void ConnectionManager::handleAllCommandsReady(void)
{
m_disconnectManager->allCommandsReady(TheGameLogic->getFrame(), this, FALSE);
}
/**
* Only call this after making sure that all the commands are there for this frame.
* After calling this the commands for this frame will be removed from the connection.
*
* BGC - To account for the case where the host disconnects without sending the
* same commands to all players, we now have to keep around the last 'run ahead'
* frames so we can potentially send those commands to the other players in the
* game so they can catch up.
*/
NetCommandList *ConnectionManager::getFrameCommandList(UnsignedInt frame)
{
NetCommandList *retlist = newInstance(NetCommandList);
retlist->init();
for (Int i = 0; i < MAX_SLOTS; ++i) {
if (m_frameData[i] != NULL) {
retlist->appendList(m_frameData[i]->getFrameCommandList(frame));
if (frame > FRAMES_TO_KEEP) {
m_frameData[i]->resetFrame(frame - FRAMES_TO_KEEP); // After getting the commands for that frame from this
// FrameDataManager object, we need to tell it that we're
// done with the messages for that frame.
DEBUG_LOG(("getFrameCommandList - called reset frame on player %d for frame %d\n", i, frame - FRAMES_TO_KEEP));
}
}
}
return retlist; // retlist deallocated by calling function.
}
void ConnectionManager::setFrameGrouping(time_t frameGrouping) {
// Since we are the packet router, we should send more packets per second since we
// may become the latency bottleneck for sending packets from one player to the next.
// This is probably ok since the packet router should have the fastest connection of all
// the players in the game.
if (m_localSlot == m_packetRouterSlot) {
frameGrouping = frameGrouping / 2;
}
for (Int i = 0; i < MAX_SLOTS; ++i) {
if (m_connections[i] != NULL) {
m_connections[i]->setFrameGrouping(frameGrouping);
}
}
}
/*
void ConnectionManager::determineRouterFallbackPlan() {
memset(m_packetRouterFallback, 0, sizeof(m_packetRouterFallback));
Int curnum = 1;
for (Int i = 0; i < MAX_SLOTS; ++i) {
if (m_connections[i] != NULL) {
m_packetRouterFallback[i] = curnum;
if (curnum == 1) {
m_packetRouterSlot = i;
}
++curnum;
}
}
}
*/
void ConnectionManager::doKeepAlive() {
static Int nextIndex = 0;
static time_t startTime = 0;
time_t curTime = timeGetTime();
if (startTime == 0) {
startTime = curTime;
return;
}
time_t numSeconds = (curTime - startTime) / 1000;
while ((nextIndex <= numSeconds) && (nextIndex < MAX_SLOTS)) {
// DEBUG_LOG(("ConnectionManager::doKeepAlive - trying to send keep alive message to player %d\n", nextIndex));
if (m_connections[nextIndex] != NULL) {
NetKeepAliveCommandMsg *msg = newInstance(NetKeepAliveCommandMsg);
msg->setPlayerID(m_localSlot);
if (DoesCommandRequireACommandID(msg->getNetCommandType()) == TRUE) {
msg->setID(GenerateNextCommandID());
}
// DEBUG_LOG(("ConnectionManager::doKeepAlive - sending keep alive message to player %d\n", nextIndex));
sendLocalCommandDirect(msg, 1 << nextIndex);
msg->detach();
}
++nextIndex;
}
if (nextIndex == MAX_SLOTS) {
nextIndex = 0;
startTime = curTime;
}
}
PlayerLeaveCode ConnectionManager::disconnectPlayer(Int slot) {
// Need to do the deletion of the slot's connection and frame data here.
PlayerLeaveCode retval = PLAYERLEAVECODE_CLIENT;
DEBUG_LOG(("ConnectionManager::disconnectPlayer - disconnecting slot %d on frame %d\n", slot, TheGameLogic->getFrame()));
if ((slot < 0) || (slot >= MAX_SLOTS)) {
return PLAYERLEAVECODE_UNKNOWN;
}
if (TheGameInfo)
{
GameSlot *gSlot = TheGameInfo->getSlot( slot );
if (gSlot && !gSlot->lastFrameInGame())
{
DEBUG_LOG(("ConnectionManager::disconnectPlayer(%d) - slot is last in the game on frame %d\n",
slot, TheGameLogic->getFrame()));
gSlot->setLastFrameInGame(TheGameLogic->getFrame());
}
}
UnicodeString unicodeName;
unicodeName = getPlayerName(slot);
if (unicodeName.getLength() > 0 && m_connections[slot]) {
TheInGameUI->message("Network:PlayerLeftGame", unicodeName.str());
// People are boneheads. Also play a sound
static AudioEventRTS leftGameSound("GUIMessageReceived");
TheAudio->addAudioEvent(&leftGameSound);
}
if ((m_frameData[slot] != NULL) && (m_frameData[slot]->getIsQuitting() == FALSE)) {
DEBUG_LOG(("ConnectionManager::disconnectPlayer - deleting player %d frame data\n", slot));
m_frameData[slot]->deleteInstance();
m_frameData[slot] = NULL;
}
if (m_connections[slot] != NULL && !m_connections[slot]->isQuitting()) {
DEBUG_LOG(("ConnectionManager::disconnectPlayer - deleting player %d connection\n", slot));
m_connections[slot]->deleteInstance();
m_connections[slot] = NULL;
}
// if (playerID == m_localSlot) {
// TheMessageStream->appendMessage(GameMessage::MSG_CLEAR_GAME_DATA);
// }
if (slot == m_packetRouterSlot) {
Int index = 0;
while ((index < (MAX_SLOTS-1)) && (m_packetRouterFallback[index] != m_packetRouterSlot)) {
++index;
}
++index;
m_packetRouterSlot = m_packetRouterFallback[index];
DEBUG_LOG(("Packet router left. New packet router is slot %d\n", m_packetRouterSlot));
retval = PLAYERLEAVECODE_PACKETROUTER;
}
if (m_localSlot == slot) {
DEBUG_LOG(("Disconnecting self\n"));
retval = PLAYERLEAVECODE_LOCAL;
}
// Take the player out of the fallback plan
Int fallbackindex = 0;
while ((fallbackindex < MAX_SLOTS) && (m_packetRouterFallback[fallbackindex] != slot)) {
++fallbackindex;
}
for (Int i = fallbackindex; i < MAX_SLOTS-1; ++i) {
m_packetRouterFallback[i] = m_packetRouterFallback[i+1];
}
m_packetRouterFallback[MAX_SLOTS-1] = -1;
return retval;
}
void ConnectionManager::quitGame() {
// Need to do the NetDisconnectPlayerCommandMsg creation and sending here.
NetDisconnectPlayerCommandMsg *disconnectMsg = newInstance(NetDisconnectPlayerCommandMsg);
disconnectMsg->setDisconnectSlot(m_localSlot);
disconnectMsg->setPlayerID(m_localSlot);
if (DoesCommandRequireACommandID(disconnectMsg->getNetCommandType())) {
disconnectMsg->setID(GenerateNextCommandID());
}
//DEBUG_LOG(("ConnectionManager::disconnectLocalPlayer - about to send disconnect command\n"));
sendLocalCommandDirect(disconnectMsg, 0xff ^ (1 << m_localSlot));
//DEBUG_LOG(("ConnectionManager::disconnectLocalPlayer - about to flush connections\n"));
flushConnections(); // need to do this so our packet actually gets sent before the connections are deleted.
//DEBUG_LOG(("ConnectionManager::disconnectLocalPlayer - done flushing connections\n"));
disconnectMsg->detach();
disconnectLocalPlayer();
}
void ConnectionManager::disconnectLocalPlayer() {
// kill the frame data and the connections for all the other players.
DEBUG_LOG(("ConnectionManager::disconnectLocalPlayer()\n"));
for (Int i = 0; i < MAX_SLOTS; ++i) {
if (i != m_localSlot) {
disconnectPlayer(i);
}
}
}
/**
* Takes all the commands that are ready to send and sends them right now.
*/
void ConnectionManager::flushConnections() {
for (Int i = 0; i < MAX_SLOTS; ++i) {
if (m_connections[i] != NULL) {
// DEBUG_LOG(("ConnectionManager::flushConnections - flushing connection to player %d\n", i));
/*
if (m_connections[i]->isQueueEmpty()) {
// DEBUG_LOG(("ConnectionManager::flushConnections - connection queue empty\n"));
}
*/
m_connections[i]->doSend();
}
}
if (m_transport != NULL) {
m_transport->doSend();
}
}
void ConnectionManager::resendPendingCommands() {
//DEBUG_LOG(("ConnectionManager::resendPendingCommands()\n"));
if (m_pendingCommands == NULL) {
return;
}
NetCommandRef *ref = m_pendingCommands->getFirstMessage();
while (ref != NULL) {
//DEBUG_LOG(("ConnectionManager::resendPendingCommands - resending command %d\n", ref->getCommand()->getID()));
sendLocalCommand(ref->getCommand(), ref->getRelay());
ref = ref->getNext();
}
}
UnsignedInt ConnectionManager::getLocalPlayerID() {
return m_localSlot;
}
UnicodeString ConnectionManager::getPlayerName(Int playerNum) {
UnicodeString retval;
if( playerNum == m_localSlot ) {
retval = m_localUser->GetName();
} else if (((m_connections[playerNum] != NULL) && (m_connections[playerNum]->isQuitting() == FALSE))) {
retval = m_connections[playerNum]->getUser()->GetName();
}
return retval;
}
/**
* Take a user list and make connections and frame data manager objects for each of the players.
* For now, this is also how we'll determine the packet router fallback plan.
*/
void ConnectionManager::parseUserList(const GameInfo *game)
{
if (!game)
return;
Int i;
Int numUsers = 0;
m_localSlot = -1;
DEBUG_LOG(("Local slot is %d\n", game->getLocalSlotNum()));
for (i=0; igetConstSlot(i); // badness, but since we cast right back to const, we should be ok
if (slot->isHuman())
{
if (game->getLocalSlotNum() == i)
{
m_localSlot = i;
m_localUser->setName(slot->getName());
}
if (m_localSlot != i)
{
m_connections[i] = newInstance(Connection)();
m_connections[i]->init();
m_connections[i]->attachTransport(m_transport);
// UnsignedShort port = (TheNAT)?TheNAT->getSlotPort(i):8088;
UnsignedShort port = slot->getPort();
m_connections[i]->setUser(newInstance(User)(slot->getName(), slot->getIP(), port));
m_frameData[i] = newInstance(FrameDataManager)(FALSE);
DEBUG_LOG(("Remote user is at %X:%d\n", slot->getIP(), slot->getPort()));
}
else
{
DEBUG_LOG(("Local user is %d (%X:%d)\n", m_localSlot, slot->getIP(), slot->getPort()));
m_frameData[i] = newInstance(FrameDataManager)(TRUE);
}
m_frameData[i]->init();
m_frameData[i]->reset();
m_packetRouterFallback[numUsers] = i;
++numUsers;
}
}
#ifdef MEMORYPOOL_DEBUG
TheMemoryPoolFactory->debugSetInitFillerIndex(m_localSlot);
#endif
/*
if ( numUsers < 2 || m_localSlot == -1 )
{
DEBUG_CRASH(("FAILED parseUserList - network game won't work as expected\n"));
return;
}
char * list = strdup(buf);
char *listPtr = list;
if (!list)
return;
User users[MAX_SLOTS];
int localUser = -1;
int i;
for (i=0; i> 24) & 0xff,
(m_localAddr >> 16) & 0xff,
(m_localAddr >> 8) & 0xff,
m_localAddr & 0xff,
m_localPort));
int numUsers = 0;
while ( (userStr=strtok_r(listPtr, ",", &listPos)) != NULL )
{
listPtr = NULL;
char *pos = NULL;
nameStr = strtok_r(userStr, "@", &pos);
addrStr = strtok_r(NULL, "@:", &pos);
portStr = strtok_r(NULL, ": ", &pos);
if (!portStr || numUsers >= MAX_SLOTS)
{
DEBUG_LOG(("ConnectionManager::parseUserList - (numUsers = %d) FAILED parseUserList with list [%s]\n", numUsers, buf));
return;
}
addrAsciiStr = addrStr;
UnsignedInt addr = ResolveIP(addrAsciiStr);
UnsignedInt port = atoi(portStr);
// if ((m_localAddr != addr) || (m_localPort != port)) {
if (loginName.compare(nameStr) != 0) {
m_connections[numUsers] = newInstance(Connection)();
m_connections[numUsers]->init();
m_connections[numUsers]->attachTransport(m_transport);
m_connections[numUsers]->setUser(newInstance(User)(nameStr, addr, port));
m_frameData[numUsers] = newInstance(FrameDataManager)(FALSE);
DEBUG_LOG(("ConnectionManager::parseUserList - User %d is %s\n", numUsers, nameStr));
} else {
m_localSlot = numUsers;
m_localUser.setName(nameStr);
DEBUG_LOG(("ConnectionManager::parseUserList - User %d is %s\n", numUsers, nameStr));
DEBUG_LOG(("Local user is %d\n", m_localSlot));
m_frameData[numUsers] = newInstance(FrameDataManager)(TRUE);
}
m_frameData[numUsers]->init();
m_frameData[numUsers]->reset();
m_packetRouterFallback[numUsers] = numUsers;
numUsers++;
}
if (numUsers < 2 || m_localSlot == -1)
{
DEBUG_LOG(("ConnectionManager::parseUserList - FAILED (local user = %d, num players = %d) with list [%s]\n", m_localSlot, numUsers, buf));
return;
}
if (list != NULL) {
free(list); // from the strdup above.
list = NULL;
}
*/
}
/**
* Return the number of incoming bytes per second averaged over 30 sec.
*/
Real ConnectionManager::getIncomingBytesPerSecond( void )
{
if (m_transport)
return m_transport->getIncomingBytesPerSecond();
else
return 0.0;
}
/**
* Return the number of incoming packets per second averaged over the last 30 sec.
*/
Real ConnectionManager::getIncomingPacketsPerSecond( void )
{
if (m_transport)
return m_transport->getIncomingPacketsPerSecond();
else
return 0.0;
}
/**
* Return the number of outgoing bytes per second averaged over the last 30 sec.
*/
Real ConnectionManager::getOutgoingBytesPerSecond( void )
{
if (m_transport)
return m_transport->getOutgoingBytesPerSecond();
else
return 0.0;
}
/**
* Return the number of outgoing packets per second averaged over the last 30 sec.
*/
Real ConnectionManager::getOutgoingPacketsPerSecond( void )
{
if (m_transport) {
return m_transport->getOutgoingPacketsPerSecond();
} else {
return 0.0;
}
}
/**
* Return the number of bytes not from generals clients received per second averaged over the last 30 sec.
*/
Real ConnectionManager::getUnknownBytesPerSecond( void )
{
if (m_transport)
return m_transport->getUnknownBytesPerSecond();
else
return 0.0;
}
/**
* Return the number ov packets not from generals clients received per second averaged over the last 30 sec.
*/
Real ConnectionManager::getUnknownPacketsPerSecond( void )
{
if (m_transport)
return m_transport->getUnknownPacketsPerSecond();
else
return 0.0;
}
/**
* Return the smallest packet arrival cushion since this was last called.
*/
UnsignedInt ConnectionManager::getPacketArrivalCushion() {
UnsignedInt retval = m_smallestPacketArrivalCushion;
m_smallestPacketArrivalCushion = -1;
return retval;
}
void ConnectionManager::sendChat(UnicodeString text, Int playerMask, UnsignedInt executionFrame)
{
NetChatCommandMsg *msg = newInstance(NetChatCommandMsg);
msg->setText(text);
msg->setPlayerMask(playerMask);
msg->setPlayerID(m_localSlot);
msg->setID(0);
msg->setExecutionFrame(executionFrame);
if (DoesCommandRequireACommandID(msg->getNetCommandType()))
{
msg->setID(GenerateNextCommandID());
}
DEBUG_LOG(("Chat message has ID of %d, mask of %8.8X, text of %ls\n", msg->getID(), msg->getPlayerMask(), msg->getText().str()));
sendLocalCommand(msg, 0xff ^ (1 << m_localSlot));
processChat(msg);
msg->detach();
}
void ConnectionManager::sendDisconnectChat(UnicodeString text) {
NetDisconnectChatCommandMsg *msg = newInstance(NetDisconnectChatCommandMsg);
msg->setPlayerID(m_localSlot);
if (DoesCommandRequireACommandID(msg->getNetCommandType())) {
msg->setID(GenerateNextCommandID());
}
msg->setText(text);
sendLocalCommandDirect(msg, 0xff ^ (1 << m_localSlot));
processDisconnectChat(msg);
}
UnsignedShort ConnectionManager::sendFileAnnounce(AsciiString path, UnsignedByte playerMask)
{
File *theFile = TheLocalFileSystem->openFile(path.str());
if (!theFile || !theFile->size())
{
UnicodeString log;
log.format(L"Not sending file '%hs' to %X\n", path.str(), playerMask);
DEBUG_LOG(("%ls\n", log.str()));
if (TheLAN)
TheLAN->OnChat(UnicodeString(L"sendFile"), 0, log, LANAPI::LANCHAT_SYSTEM);
return 0;
}
theFile->close();
Int announceMask = 0xff ^ (1 << m_localSlot);
NetFileAnnounceCommandMsg *announceMsg = newInstance(NetFileAnnounceCommandMsg);
announceMsg->setPlayerID(m_localSlot);
if (DoesCommandRequireACommandID(announceMsg->getNetCommandType()) == TRUE) {
announceMsg->setID(GenerateNextCommandID());
}
announceMsg->setRealFilename(path);
announceMsg->setPlayerMask(playerMask);
UnsignedShort fileID = GenerateNextCommandID();
announceMsg->setFileID(fileID);
DEBUG_LOG(("ConnectionManager::sendFileAnnounce() - creating announce message with ID of %d from %d to mask %X for '%s' going to %X as command %d\n",
announceMsg->getID(), announceMsg->getPlayerID(), announceMask, announceMsg->getRealFilename().str(),
announceMsg->getPlayerMask(), announceMsg->getFileID()));
processFileAnnounce(announceMsg); // set up things for the host
DEBUG_LOG(("Sending file announce to %X\n", announceMask));
sendLocalCommand(announceMsg, announceMask);
announceMsg->detach();
return fileID;
}
void ConnectionManager::sendFile(AsciiString path, UnsignedByte playerMask, UnsignedShort commandID)
{
File *theFile = TheLocalFileSystem->openFile(path.str());
if (!theFile || !theFile->size())
{
UnicodeString log;
log.format(L"Not sending file '%hs' to %X\n", path.str(), playerMask);
DEBUG_LOG(("%ls\n", log.str()));
if (TheLAN)
TheLAN->OnChat(UnicodeString(L"sendFile"), 0, log, LANAPI::LANCHAT_SYSTEM);
return;
}
Int len = theFile->size();
char *buf = theFile->readEntireAndClose();
// compress Targas
#ifdef COMPRESS_TARGAS
char *compressedBuf = NULL;
Int compressedLen = path.endsWith(".tga")?CompressionManager::getMaxCompressedSize(len, CompressionManager::getPreferredCompression()):0;
Int compressedSize = 0;
if (compressedLen)
compressedSize = CompressionManager::compressData(CompressionManager::getPreferredCompression(),
buf, len, compressedBuf, compressedLen);
if (compressedBuf && !compressedSize)
{
delete[] compressedBuf;
compressedBuf = NULL;
}
#endif // COMPRESS_TARGAS
NetFileCommandMsg *fileMsg = newInstance(NetFileCommandMsg);
fileMsg->setPlayerID(m_localSlot);
fileMsg->setID(commandID);
fileMsg->setRealFilename(path);
#ifdef COMPRESS_TARGAS
if (compressedBuf)
{
DEBUG_LOG(("Compressed '%s' from %d to %d (%g%%) before transfer\n", path.str(), len, compressedSize,
(Real)compressedSize/(Real)len*100.0f));
fileMsg->setFileData((unsigned char *)compressedBuf, compressedSize);
}
else
#endif // COMPRESS_TARGAS
{
fileMsg->setFileData((unsigned char *)buf, len);
}
DEBUG_LOG(("ConnectionManager::sendFile() - creating file message with ID of %d for '%s' going to %X from %d, size of %d\n",
fileMsg->getID(), fileMsg->getRealFilename().str(), playerMask, fileMsg->getPlayerID(), fileMsg->getFileLength()));
delete[] buf;
buf = NULL;
#ifdef COMPRESS_TARGAS
if (compressedBuf)
{
delete[] compressedBuf;
compressedBuf = NULL;
}
#endif // COMPRESS_TARGAS
DEBUG_LOG(("Sending file: '%s', len %d, to %X\n", path.str(), len, playerMask));
sendLocalCommand(fileMsg, playerMask);
fileMsg->detach();
}
Int ConnectionManager::getFileTransferProgress(Int playerID, AsciiString path)
{
FileCommandMap::iterator commandIt = s_fileCommandMap.begin();
while (commandIt != s_fileCommandMap.end())
{
//DEBUG_LOG(("ConnectionManager::getFileTransferProgress(%s): looking at existing transfer of '%s'\n",
// path.str(), commandIt->second.str()));
if (commandIt->second == path)
{
return s_fileProgressMap[playerID][commandIt->first];
}
++commandIt;
}
//DEBUG_LOG(("Falling back to 0, since we couldn't find the map\n"));
DEBUG_LOG(("ConnectionManager::getFileTransferProgress: path %s not found\n",path.str()));
return 0;
}
void ConnectionManager::voteForPlayerDisconnect(Int slot) {
if (m_disconnectManager != NULL) {
m_disconnectManager->voteForPlayerDisconnect(slot, this);
}
}
Int ConnectionManager::getNumPlayers()
{
Int retval = 0;
for (Int i = 0; i < MAX_SLOTS; ++i)
{
if (isPlayerConnected(i)) {
++retval;
}
}
return retval;
}
void ConnectionManager::updateLoadProgress( Int progress )
{
NetProgressCommandMsg *msg = newInstance(NetProgressCommandMsg);
msg->setPercentage( progress );
msg->setPlayerID( m_localSlot );
if (DoesCommandRequireACommandID(msg->getNetCommandType()) == TRUE) {
msg->setID(GenerateNextCommandID());
}
processProgress(msg);
sendLocalCommand(msg, 0xff ^ (1 << m_localSlot));
msg->detach();
}
void ConnectionManager::loadProgressComplete()
{
NetCommandMsg *msg = newInstance(NetCommandMsg);
msg->setPlayerID( m_localSlot );
if (DoesCommandRequireACommandID(msg->getNetCommandType()) == TRUE) {
msg->setID(GenerateNextCommandID());
}
msg->setNetCommandType(NETCOMMANDTYPE_LOADCOMPLETE);
processLoadComplete(msg);
sendLocalCommand(msg, 0xff ^ (1 << m_localSlot));
msg->detach();
}
void ConnectionManager::sendTimeOutGameStart()
{
NetCommandMsg *msg = newInstance(NetCommandMsg);
msg->setPlayerID( m_localSlot );
msg->setNetCommandType(NETCOMMANDTYPE_TIMEOUTSTART);
if (DoesCommandRequireACommandID(msg->getNetCommandType()) == TRUE) {
msg->setID(GenerateNextCommandID());
}
processTimeOutGameStart(msg);
sendLocalCommand(msg, 0xff ^ (1 << m_localSlot));
msg->detach();
}
Bool ConnectionManager::isPacketRouter( void )
{
return m_localSlot == m_packetRouterSlot;
}
Int ConnectionManager::getAverageFPS( void )
{
return m_frameMetrics.getAverageFPS();
}
Int ConnectionManager::getSlotAverageFPS(Int slot) {
if ((slot < 0) || (slot >= MAX_SLOTS)) {
return -1;
}
if ((m_packetRouterSlot != m_localSlot) && (slot == m_localSlot)) {
// our framerate data isn't valid for other players unless we are the
// packet router, so don't fake someone out.
return -1;
}
return m_fpsAverages[slot];
}
#if defined(_DEBUG) || defined(_INTERNAL)
void ConnectionManager::debugPrintConnectionCommands() {
DEBUG_LOG(("ConnectionManager::debugPrintConnectionCommands - begin commands\n"));
for (Int i = 0; i < MAX_SLOTS; ++i) {
if (m_connections[i] != NULL) {
DEBUG_LOG(("ConnectionManager::debugPrintConnectionCommands - commands for connection %d\n", i));
m_connections[i]->debugPrintCommands();
}
}
DEBUG_LOG(("ConnectionManager::debugPrintConnectionCommands - end commands\n"));
}
#endif
void ConnectionManager::notifyOthersOfCurrentFrame(Int frame) {
NetDisconnectFrameCommandMsg *msg = newInstance(NetDisconnectFrameCommandMsg);
msg->setPlayerID(m_localSlot);
msg->setDisconnectFrame(frame);
if (DoesCommandRequireACommandID(msg->getNetCommandType())) {
msg->setID(GenerateNextCommandID());
}
DEBUG_LOG(("ConnectionManager::notifyOthersOfCurrentFrame - sending disconnect frame of %d, command ID = %d\n", frame, msg->getID()));
sendLocalCommandDirect(msg, 0xff ^ (1 << m_localSlot));
NetCommandRef *ref = NEW_NETCOMMANDREF(msg);
ref->setRelay(1 << m_localSlot);
m_disconnectManager->processDisconnectCommand(ref, this);
ref->deleteInstance();
msg->detach();
DEBUG_LOG(("ConnectionManager::notifyOthersOfCurrentFrame - start screen on debug stuff\n"));
#if defined(_DEBUG) || defined(_INTERNAL)
debugPrintConnectionCommands();
#endif
}
void ConnectionManager::notifyOthersOfNewFrame(UnsignedInt frame) {
NetDisconnectScreenOffCommandMsg *msg = newInstance(NetDisconnectScreenOffCommandMsg);
msg->setPlayerID(m_localSlot);
msg->setNewFrame(frame);
if (DoesCommandRequireACommandID(msg->getNetCommandType())) {
msg->setID(GenerateNextCommandID());
}
sendLocalCommandDirect(msg, 0xff ^ (1 << m_localSlot));
NetCommandRef *ref = NEW_NETCOMMANDREF(msg);
ref->setRelay(1 << m_localSlot);
m_disconnectManager->processDisconnectCommand(ref, this);
ref->deleteInstance();
msg->detach();
}
void ConnectionManager::sendFrameDataToPlayer(UnsignedInt playerID, UnsignedInt startingFrame) {
DEBUG_LOG(("ConnectionManager::sendFrameDataToPlayer - sending frame data to player %d starting with frame %d\n", playerID, startingFrame));
for (UnsignedInt frame = startingFrame; frame < TheGameLogic->getFrame(); ++frame) {
sendSingleFrameToPlayer(playerID, frame);
}
DEBUG_LOG(("ConnectionManager::sendFrameDataToPlayer - done sending commands to player %d\n", playerID));
}
void ConnectionManager::sendSingleFrameToPlayer(UnsignedInt playerID, UnsignedInt frame) {
if ((TheGameLogic->getFrame() - FRAMES_TO_KEEP) > frame) {
DEBUG_LOG(("ConnectionManager::sendSingleFrameToPlayer - player %d requested frame %d when we are on frame %d, this is too far in the past.\n", playerID, frame, TheGameLogic->getFrame()));
return;
}
UnsignedByte relay = 1 << playerID;
DEBUG_LOG(("ConnectionManager::sendFrameDataToPlayer - sending data for frame %d\n", frame));
for (Int i = 0; i < MAX_SLOTS; ++i) {
if ((m_frameData[i] != NULL) && (i != playerID)) { // no need to send his own commands to him.
NetCommandList *list = m_frameData[i]->getFrameCommandList(frame);
if (list != NULL) {
NetCommandRef *ref = list->getFirstMessage();
while (ref != NULL) {
DEBUG_LOG(("ConnectionManager::sendFrameDataToPlayer - sending command %d from player %d to player %d using relay 0x%x\n", ref->getCommand()->getID(), i, playerID, relay));
sendLocalCommandDirect(ref->getCommand(), relay);
ref = ref->getNext();
}
}
UnsignedInt frameCommandCount = m_frameData[i]->getFrameCommandCount(frame);
NetFrameCommandMsg *msg = newInstance(NetFrameCommandMsg);
msg->setExecutionFrame(frame);
msg->setCommandCount(frameCommandCount);
if (DoesCommandRequireACommandID(msg->getNetCommandType())) {
msg->setID(GenerateNextCommandID());
}
msg->setPlayerID(i);
DEBUG_LOG(("ConnectionManager::sendFrameDataToPlayer - sending frame info from player %d to player %d for frame %d with command count %d and ID %d and relay %d\n", i, playerID, msg->getExecutionFrame(), msg->getCommandCount(), msg->getID(), relay));
sendLocalCommandDirect(msg, relay);
msg->detach();
}
}
}
UnsignedInt ConnectionManager::getNextPacketRouterSlot(UnsignedInt playerID) {
Int index = 0;
while ((index < (MAX_SLOTS-1)) && (m_packetRouterFallback[index] != playerID)) {
++index;
}
++index;
return m_packetRouterFallback[index];
}
void ConnectionManager::requestFrameDataResend(Int playerID, UnsignedInt frame) {
NetFrameResendRequestCommandMsg *msg = newInstance(NetFrameResendRequestCommandMsg);
msg->setPlayerID(m_localSlot);
msg->setFrameToResend(frame);
if (DoesCommandRequireACommandID(msg->getNetCommandType())) {
msg->setID(GenerateNextCommandID());
}
if (isPlayerConnected(playerID) == FALSE) {
playerID = 0;
while ((playerID < MAX_SLOTS) && (isPlayerConnected(playerID) == FALSE)) {
++playerID;
}
}
if (playerID < MAX_SLOTS) {
sendLocalCommandDirect(msg, 1 << playerID);
}
msg->detach();
}