doom3-bfg/neo/sys/sys_lobby.cpp

4142 lines
133 KiB
C++
Raw Normal View History

2012-11-26 18:58:24 +00:00
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../idlib/precompiled.h"
#include "sys_lobby.h"
extern idCVar net_connectTimeoutInSeconds;
extern idCVar net_headlessServer;
idCVar net_checkVersion( "net_checkVersion", "0", CVAR_INTEGER, "Check for matching version when clients connect. 0: normal rules, 1: force check, otherwise no check (pass always)" );
idCVar net_peerTimeoutInSeconds( "net_peerTimeoutInSeconds", "30", CVAR_INTEGER, "If the host hasn't received a response from a peer in this amount of time (in seconds), the peer will be disconnected." );
idCVar net_peerTimeoutInSeconds_Lobby( "net_peerTimeoutInSeconds_Lobby", "20", CVAR_INTEGER, "If the host hasn't received a response from a peer in this amount of time (in seconds), the peer will be disconnected." );
// NOTE - The snapshot exchange does the bandwidth challenge
idCVar net_bw_challenge_enable( "net_bw_challenge_enable", "0", CVAR_BOOL, "Enable pre game bandwidth challenge for throttling snap rate" );
idCVar net_bw_test_interval( "net_bw_test_interval", "33", CVAR_INTEGER, "MS - how often to send packets in bandwidth test" );
idCVar net_bw_test_numPackets( "net_bw_test_numPackets", "30", CVAR_INTEGER, "Number of bandwidth challenge packets to send" );
idCVar net_bw_test_packetSizeBytes( "net_bw_test_packetSizeBytes", "1024", CVAR_INTEGER, "Size of each packet to send out" );
idCVar net_bw_test_timeout( "net_bw_test_timeout", "500", CVAR_INTEGER, "MS after receiving a bw test packet that client will time out" );
idCVar net_bw_test_host_timeout( "net_bw_test_host_timeout", "3000", CVAR_INTEGER, "How long host will wait in MS to hear bw results from peers" );
idCVar net_bw_test_throttle_rate_pct( "net_bw_test_throttle_rate_pct", "0.80", CVAR_FLOAT, "Min rate % a peer must match in bandwidth challenge before being throttled. 1.0=perfect, 0.0=received nothing" );
idCVar net_bw_test_throttle_byte_pct( "net_bw_test_throttle_byte_pct", "0.80", CVAR_FLOAT, "Min byte % a peer must match in bandwidth challenge before being throttled. 1.0=perfect (received everything) 0.0=Received nothing" );
idCVar net_bw_test_throttle_seq_pct( "net_bw_test_throttle_seq_pct", "0.80", CVAR_FLOAT, "Min sequence % a peer must match in bandwidth test before being throttled. 1.0=perfect. This score will be more adversely affected by packet loss than byte %" );
idCVar net_ignoreConnects( "net_ignoreConnects", "0", CVAR_INTEGER, "Test as if no one can connect to me. 0 = off, 1 = ignore with no reply, 2 = send goodbye" );
idCVar net_skipGoodbye( "net_skipGoodbye", "0", CVAR_BOOL, "" );
extern unsigned long NetGetVersionChecksum();
/*
========================
idLobby::idLobby
========================
*/
idLobby::idLobby() {
lobbyType = TYPE_INVALID;
sessionCB = NULL;
localReadSS = NULL;
objMemory = NULL;
haveSubmittedSnaps = false;
state = STATE_IDLE;
failedReason = FAILED_UNKNOWN;
host = -1;
peerIndexOnHost = -1;
isHost = false;
needToDisplayMigrateMsg = false;
migrateMsgFlags = 0;
partyToken = 0; // will be initialized later
loaded = false;
respondToArbitrate = false;
waitForPartyOk = false;
startLoadingFromHost = false;
nextSendPingValuesTime = 0;
lastPingValuesRecvTime = 0;
nextSendMigrationGameTime = 0;
nextSendMigrationGamePeer = 0;
bandwidthChallengeStartTime = 0;
bandwidthChallengeEndTime = 0;
bandwidthChallengeFinished = false;
bandwidthChallengeNumGoodSeq = 0;
lastSnapBspHistoryUpdateSequence = -1;
assert( userList.Max() == freeUsers.Max() );
assert( userList.Max() == userPool.Max() );
userPool.SetNum( userPool.Max() );
assert( freeUsers.Num() == 0 );
assert( freeUsers.Num() == 0 );
// Initialize free user list
for ( int i = 0; i < userPool.Num(); i++ ) {
freeUsers.Append( &userPool[i] );
}
showHostLeftTheSession = false;
connectIsFromInvite = false;
}
/*
========================
idLobby::Initialize
========================
*/
void idLobby::Initialize( lobbyType_t sessionType_, idSessionCallbacks * callbacks ) {
assert( callbacks != NULL );
lobbyType = sessionType_;
sessionCB = callbacks;
if ( lobbyType == GetActingGameStateLobbyType() ) {
// only needed in multiplayer mode
objMemory = (uint8*)Mem_Alloc( SNAP_OBJ_JOB_MEMORY, TAG_NETWORKING );
lzwData = (lzwCompressionData_t*)Mem_Alloc( sizeof( lzwCompressionData_t ), TAG_NETWORKING );
}
}
//===============================================================================
// ** BEGIN PUBLIC INTERFACE ***
//===============================================================================
/*
========================
idLobby::StartHosting
========================
*/
void idLobby::StartHosting( const idMatchParameters & parms_ ) {
parms = parms_;
// Allow common to modify the parms
common->OnStartHosting( parms );
Shutdown(); // Make sure we're in a shutdown state before proceeding
assert( GetNumLobbyUsers() == 0 );
assert( lobbyBackend == NULL );
// Get the skill level of all the players that will eventually go into the lobby
StartCreating();
}
/*
========================
idLobby::StartFinding
========================
*/
void idLobby::StartFinding( const idMatchParameters & parms_ ) {
parms = parms_;
Shutdown(); // Make sure we're in a shutdown state before proceeding
assert( GetNumLobbyUsers() == 0 );
assert( lobbyBackend == NULL );
// Clear search results
searchResults.Clear();
lobbyBackend = sessionCB->FindLobbyBackend( parms, sessionCB->GetPartyLobby().GetNumLobbyUsers(), sessionCB->GetPartyLobby().GetAverageSessionLevel(), idLobbyBackend::TYPE_GAME );
SetState( STATE_SEARCHING );
}
/*
========================
idLobby::Pump
========================
*/
void idLobby::Pump() {
// Check the heartbeat of all our peers, make sure we shouldn't disconnect from peers that haven't sent a heartbeat in awhile
CheckHeartBeats();
UpdateHostMigration();
UpdateLocalSessionUsers();
switch ( state ) {
case STATE_IDLE: State_Idle(); break;
case STATE_CREATE_LOBBY_BACKEND: State_Create_Lobby_Backend(); break;
case STATE_SEARCHING: State_Searching(); break;
case STATE_OBTAINING_ADDRESS: State_Obtaining_Address(); break;
case STATE_CONNECT_HELLO_WAIT: State_Connect_Hello_Wait(); break;
case STATE_FINALIZE_CONNECT: State_Finalize_Connect(); break;
case STATE_FAILED: break;
default:
idLib::Error( "idLobby::Pump: Unknown state." );
}
}
/*
========================
idLobby::ProcessSnapAckQueue
========================
*/
void idLobby::ProcessSnapAckQueue() {
SCOPED_PROFILE_EVENT( "ProcessSnapAckQueue" );
const int SNAP_ACKS_TO_PROCESS_PER_FRAME = 1;
int numProcessed = 0;
while ( snapDeltaAckQueue.Num() > 0 && numProcessed < SNAP_ACKS_TO_PROCESS_PER_FRAME ) {
if ( ApplySnapshotDeltaInternal( snapDeltaAckQueue[0].p, snapDeltaAckQueue[0].snapshotNumber ) ) {
numProcessed++;
}
snapDeltaAckQueue.RemoveIndex( 0 );
}
}
/*
========================
idLobby::Shutdown
========================
*/
void idLobby::Shutdown( bool retainMigrationInfo, bool skipGoodbye ) {
// Cancel host migration if we were in the process of it and this is the session type that was migrating
if ( !retainMigrationInfo && migrationInfo.state != MIGRATE_NONE ) {
idLib::Printf( "Cancelling host migration on %s.\n", GetLobbyName() );
EndMigration();
}
failedReason = FAILED_UNKNOWN;
if ( lobbyBackend == NULL ) {
NET_VERBOSE_PRINT( "NET: ShutdownLobby (already shutdown) (%s)\n", GetLobbyName() );
// If we don't have this lobbyBackend type, we better be properly shutdown for this lobby
assert( GetNumLobbyUsers() == 0 );
assert( host == -1 );
assert( peerIndexOnHost == -1 );
assert( !isHost );
assert( lobbyType != GetActingGameStateLobbyType() || !loaded );
assert( lobbyType != GetActingGameStateLobbyType() || !respondToArbitrate );
assert( snapDeltaAckQueue.Num() == 0 );
// Make sure we don't have old peers connected to this lobby
for ( int p = 0; p < peers.Num(); p++ ) {
assert( peers[p].GetConnectionState() == CONNECTION_FREE );
}
state = STATE_IDLE;
return;
}
NET_VERBOSE_PRINT( "NET: ShutdownLobby (%s)\n", GetLobbyName() );
for ( int p = 0; p < peers.Num(); p++ ) {
if ( peers[p].GetConnectionState() != CONNECTION_FREE ) {
SetPeerConnectionState( p, CONNECTION_FREE, skipGoodbye ); // This will send goodbye's
}
}
// Remove any users that weren't handled in ResetPeers
// (this will happen as a client, because we won't get the reliable msg from the server since we are severing the connection)
for ( int i = 0; i < GetNumLobbyUsers(); i++ ) {
lobbyUser_t * user = GetLobbyUser( i );
UnregisterUser( user );
}
FreeAllUsers();
host = -1;
peerIndexOnHost = -1;
isHost = false;
needToDisplayMigrateMsg = false;
migrationDlg = GDM_INVALID;
partyToken = 0; // Reset our party token so we recompute
loaded = false;
respondToArbitrate = false;
waitForPartyOk = false;
startLoadingFromHost = false;
snapDeltaAckQueue.Clear();
// Shutdown the lobbyBackend
if ( !retainMigrationInfo ) {
sessionCB->DestroyLobbyBackend( lobbyBackend );
lobbyBackend = NULL;
}
state = STATE_IDLE;
}
/*
========================
idLobby::HandlePacket
========================
*/
void idLobby::HandlePacket( lobbyAddress_t & remoteAddress, idBitMsg fragMsg, idPacketProcessor::sessionId_t sessionID ) {
SCOPED_PROFILE_EVENT( "HandlePacket" );
// msg will hold a fully constructed msg using the packet processor
byte msgBuffer[ idPacketProcessor::MAX_MSG_SIZE ];
idBitMsg msg;
msg.InitWrite( msgBuffer, sizeof( msgBuffer ) );
int peerNum = FindPeer( remoteAddress, sessionID );
int type = idPacketProcessor::RETURN_TYPE_NONE;
int userData = 0;
if ( peerNum >= 0 ) {
if ( !peers[peerNum].IsActive() ) {
idLib::Printf( "NET: Received in-band packet from peer %s with no active connection.\n", remoteAddress.ToString() );
return;
}
type = peers[ peerNum ].packetProc->ProcessIncoming( Sys_Milliseconds(), peers[peerNum].sessionID, fragMsg, msg, userData, peerNum );
} else {
if ( !idPacketProcessor::ProcessConnectionlessIncoming( fragMsg, msg, userData ) ) {
idLib::Printf( "ProcessConnectionlessIncoming FAILED from %s.\n", remoteAddress.ToString() );
// Not a valid connectionless packet
return;
}
// Valid connectionless packets are always RETURN_TYPE_OOB
type = idPacketProcessor::RETURN_TYPE_OOB;
// Find the peer this connectionless msg should go to
peerNum = FindPeer( remoteAddress, sessionID, true );
}
if ( type == idPacketProcessor::RETURN_TYPE_NONE ) {
// This packet is not necessarily invalid, it could be a start or middle of a fragmented packet that's not fully constructed.
return;
}
if ( peerNum >= 0 ) {
// Update their heart beat (only if we've received a valid packet (we've checked type == idPacketProcessor::RETURN_TYPE_NONE))
peers[peerNum].lastHeartBeat = Sys_Milliseconds();
}
// Handle server query requests. We do this before the STATE_IDLE check. This is so we respond.
// We may want to change this to just ignore the request if we are idle, and change the timeout time
// on the requesters part to just timeout faster.
if ( type == idPacketProcessor::RETURN_TYPE_OOB ) {
if ( userData == OOB_MATCH_QUERY || userData == OOB_SYSTEMLINK_QUERY ) {
sessionCB->HandleServerQueryRequest( remoteAddress, msg, userData );
return;
}
if ( userData == OOB_MATCH_QUERY_ACK ) {
sessionCB->HandleServerQueryAck( remoteAddress, msg );
return;
}
}
if ( type == idPacketProcessor::RETURN_TYPE_OOB ) {
if ( userData == OOB_VOICE_AUDIO ) {
sessionCB->HandleOobVoiceAudio( remoteAddress, msg );
} else if ( userData == OOB_HELLO ) {
// Handle new peer connect request
peerNum = HandleInitialPeerConnection( msg, remoteAddress, peerNum );
return;
} else if ( userData == OOB_MIGRATE_INVITE ) {
NET_VERBOSE_PRINT( "NET: Migration invite for session %s from %s (state = %s)\n", GetLobbyName(), remoteAddress.ToString(), session->GetStateString() );
// Get connection info
lobbyConnectInfo_t connectInfo;
connectInfo.ReadFromMsg( msg );
if ( lobbyBackend != NULL && lobbyBackend->GetState() != idLobbyBackend::STATE_FAILED && lobbyBackend->IsOwnerOfConnectInfo( connectInfo ) ) { // Ignore duplicate invites
idLib::Printf( "NET: Already migrated to %s.\n", remoteAddress.ToString() );
return;
}
if ( migrationInfo.state == MIGRATE_NONE ) {
if ( IsPeer() && host >= 0 && host < peers.Num() && Sys_Milliseconds() - peers[host].lastHeartBeat > 8 * 1000 ) {
// Force migration early if we get an invite, and it has been some time since we've heard from the host
PickNewHost();
} else {
idLib::Printf( "NET: Ignoring migration invite because we are not migrating %s\n", remoteAddress.ToString() );
SendGoodbye( remoteAddress ); // So they can remove us from their invite list
return;
}
}
if ( !sessionCB->PreMigrateInvite( *this ) ) {
NET_VERBOSE_PRINT( "NET: sessionCB->PreMigrateInvite( *this ) failed from %s\n", remoteAddress.ToString() );
return;
}
// If we are also becoming a new host, see who wins
if ( migrationInfo.state == MIGRATE_BECOMING_HOST ) {
int inviteIndex = FindMigrationInviteIndex( remoteAddress );
if ( inviteIndex != -1 ) {
// We found them in our list, check to make sure our ping is better
int ping1 = migrationInfo.ourPingMs;
lobbyUserID_t userId1 = migrationInfo.ourUserId;
int ping2 = migrationInfo.invites[inviteIndex].pingMs;
lobbyUserID_t userId2 = migrationInfo.invites[inviteIndex].userId;
if ( IsBetterHost( ping1, userId1, ping2, userId2 ) ) {
idLib::Printf( "NET: Ignoring migration invite from %s, since our ping is better (%i / %i).\n", remoteAddress.ToString(), ping1, ping2 );
return;
}
}
}
bool fromGame = msg.ReadBool();
// Kill the current lobbyBackend
Shutdown();
// Connect to the lobby
ConnectTo( connectInfo, true ); // Pass in true for the invite flag, so we can connect to invite only lobby if we need to
if ( verify( sessionCB != NULL ) ) {
if ( sessionCB->BecomingPeer( *this ) ) {
migrationInfo.persistUntilGameEndsData.wasMigratedJoin = true;
migrationInfo.persistUntilGameEndsData.wasMigratedGame = fromGame;
}
}
} else if ( userData == OOB_GOODBYE || userData == OOB_GOODBYE_W_PARTY || userData == OOB_GOODBYE_FULL ) {
HandleGoodbyeFromPeer( peerNum, remoteAddress, userData );
return;
} else if ( userData == OOB_RESOURCE_LIST ) {
if ( !verify( lobbyType == GetActingGameStateLobbyType() ) ) {
return;
}
if ( peerNum != host ) {
NET_VERBOSE_PRINT( "NET: Resource list from non-host %i, %s\n", peerNum, remoteAddress.ToString() );
return;
}
if ( peerNum >= 0 && !peers[peerNum].IsConnected() ) {
NET_VERBOSE_PRINT( "NET: Resource list from host with no game connection: %i, %s\n", peerNum, remoteAddress.ToString() );
return;
}
} else if ( userData == OOB_BANDWIDTH_TEST ) {
int seqNum = msg.ReadLong();
// TODO: We should read the random data and verify the MD5 checksum
int time = Sys_Milliseconds();
bool inOrder = ( seqNum == 0 || peers[peerNum].bandwidthSequenceNum + 1 == seqNum );
int timeSinceLast = 0;
if ( bandwidthChallengeStartTime <= 0 ) {
// Reset the test
NET_VERBOSE_PRINT( "\nNET: Starting bandwidth test @ %d\n", time );
bandwidthChallengeStartTime = time;
peers[peerNum].bandwidthSequenceNum = 0;
peers[peerNum].bandwidthTestBytes = peers[peerNum].packetProc->GetIncomingBytes();
} else {
timeSinceLast = time - (bandwidthChallengeEndTime - session->GetTitleStorageInt( "net_bw_test_timeout", net_bw_test_timeout.GetInteger() ) );
}
if ( inOrder ) {
bandwidthChallengeNumGoodSeq++;
}
bandwidthChallengeEndTime = time + session->GetTitleStorageInt( "net_bw_test_timeout", net_bw_test_timeout.GetInteger() );
NET_VERBOSE_PRINT( " NET: %sRecevied OOB bandwidth test %d delta time: %d incoming rate: %.2f incoming rate 2: %d\n", inOrder ? "^2" : "^1", seqNum, timeSinceLast, peers[peerNum].packetProc->GetIncomingRateBytes(), peers[peerNum].packetProc->GetIncomingRate2() );
peers[peerNum].bandwidthSequenceNum = seqNum;
} else {
NET_VERBOSE_PRINT( "NET: Unknown oob packet %d from %s (%d)\n", userData, remoteAddress.ToString(), peerNum );
}
} else if ( type == idPacketProcessor::RETURN_TYPE_INBAND ) {
// Process in-band message
if ( peerNum < 0 ) {
idLib::Printf( "NET: In-band message from unknown peer: %s\n", remoteAddress.ToString() );
return;
}
if ( !verify( peers[ peerNum ].address.Compare( remoteAddress ) ) ) {
idLib::Printf( "NET: Peer with wrong address: %i, %s\n", peerNum, remoteAddress.ToString() );
return;
}
// Handle reliable
int numReliable = peers[ peerNum ].packetProc->GetNumReliables();
for ( int r = 0; r < numReliable; r++ ) {
// Just in case one of the reliable msg's cause this peer to disconnect
// (this can happen when our party/game host is the same, he quits the game lobby, and sends a reliable msg for us to leave the game)
peerNum = FindPeer( remoteAddress, sessionID );
if ( peerNum == -1 ) {
idLib::Printf( "NET: Dropped peer while processing reliable msg's: %i, %s\n", peerNum, remoteAddress.ToString() );
break;
}
const byte * reliableData = peers[ peerNum ].packetProc->GetReliable( r );
int reliableSize = peers[ peerNum ].packetProc->GetReliableSize( r );
idBitMsg reliableMsg( reliableData, reliableSize );
reliableMsg.SetSize( reliableSize );
HandleReliableMsg( peerNum, reliableMsg );
}
if ( peerNum == -1 || !peers[ peerNum ].IsConnected() ) {
// If the peer still has no connection after HandleReliableMsg, then something is wrong.
// (We could have been in CONNECTION_CONNECTING state for this session type, but the first message
// we should receive from the server is the ack, otherwise, something went wrong somewhere)
idLib::Printf( "NET: In-band message from host with no active connection: %i, %s\n", peerNum, remoteAddress.ToString() );
return;
}
// Handle unreliable part (if any)
if ( msg.GetRemainingData() > 0 && loaded ) {
if ( !verify( lobbyType == GetActingGameStateLobbyType() ) ) {
idLib::Printf( "NET: Snapshot msg for non game session lobby %s\n", remoteAddress.ToString() );
return;
}
if ( peerNum == host ) {
idSnapShot localSnap;
int sequence = -1;
int baseseq = -1;
bool fullSnap = false;
localReadSS = &localSnap;
// If we are the peer, we assume we only receive snapshot data on the in-band channel
const byte * deltaData = msg.GetReadData() + msg.GetReadCount();
int deltaLength = msg.GetRemainingData();
if ( peers[ peerNum ].snapProc->ReceiveSnapshotDelta( deltaData, deltaLength, 0, sequence, baseseq, localSnap, fullSnap ) ) {
NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va( "NET: Got %s snapshot %d delta'd against %d. SS Time: %d\n", ( fullSnap ? "partial" : "full" ), sequence, baseseq, localSnap.GetTime() ) );
if ( sessionCB->GetState() != idSession::INGAME && sequence != -1 ) {
int seq = peers[ peerNum ].snapProc->GetLastAppendedSequence();
// When we aren't in the game, we need to send this as reliable msg's, since usercmds won't be taking care of it for us
byte ackbuffer[32];
idBitMsg ackmsg( ackbuffer, sizeof( ackbuffer ) );
ackmsg.WriteLong( seq );
// Add incoming BPS for QoS
float incomingBPS = peers[ peerNum ].receivedBps;
if ( peers[ peerNum ].receivedBpsIndex != seq ) {
incomingBPS = idMath::ClampFloat( 0.0f, static_cast<float>( idLobby::BANDWIDTH_REPORTING_MAX ), peers[host].packetProc->GetIncomingRateBytes() );
peers[ peerNum ].receivedBpsIndex = seq;
peers[ peerNum ].receivedBps = incomingBPS;
}
ackmsg.WriteQuantizedUFloat< idLobby::BANDWIDTH_REPORTING_MAX, idLobby::BANDWIDTH_REPORTING_BITS >( incomingBPS );
QueueReliableMessage( host, RELIABLE_SNAPSHOT_ACK, ackbuffer, sizeof( ackbuffer ) );
}
}
if ( fullSnap ) {
sessionCB->ReceivedFullSnap();
common->NetReceiveSnapshot( localSnap );
}
localReadSS = NULL;
} else {
// If we are the host, we assume we only receive usercmds on the inband channel
int snapNum = 0;
uint16 receivedBps_quantized = 0;
byte usercmdBuffer[idPacketProcessor::MAX_FINAL_PACKET_SIZE];
lzwCompressionData_t lzwData;
idLZWCompressor lzwCompressor( &lzwData );
lzwCompressor.Start( const_cast<byte *>( msg.GetReadData() ) + msg.GetReadCount(), msg.GetRemainingData() );
lzwCompressor.ReadAgnostic( snapNum );
lzwCompressor.ReadAgnostic( receivedBps_quantized );
int usercmdSize = lzwCompressor.Read( usercmdBuffer, sizeof( usercmdBuffer ), true );
lzwCompressor.End();
float receivedBps = ( receivedBps_quantized / (float)( BIT( idLobby::BANDWIDTH_REPORTING_BITS ) - 1 ) ) * (float)idLobby::BANDWIDTH_REPORTING_MAX;
if ( peers[ peerNum ].receivedBpsIndex != snapNum ) {
peers[ peerNum ].receivedBps = receivedBps;
peers[ peerNum ].receivedBpsIndex = snapNum;
}
if ( snapNum < 50 ) {
NET_VERBOSE_PRINT( "NET: peer %d ack'd snapNum %d\n", peerNum, snapNum );
}
ApplySnapshotDelta( peerNum, snapNum );
idBitMsg usercmdMsg( (const byte *)usercmdBuffer, usercmdSize );
common->NetReceiveUsercmds( peerNum, usercmdMsg );
}
}
}
}
/*
========================
idLobby::HasActivePeers
========================
*/
bool idLobby::HasActivePeers() const {
for ( int p = 0; p < peers.Num(); p++ ) {
if ( peers[p].GetConnectionState() != CONNECTION_FREE ) {
return true;
}
}
return false;
}
/*
========================
idLobby::NumFreeSlots
========================
*/
int idLobby::NumFreeSlots() const {
if ( parms.matchFlags & MATCH_JOIN_IN_PROGRESS ) {
return parms.numSlots - GetNumConnectedUsers();
} else {
return parms.numSlots - GetNumLobbyUsers();
}
}
//===============================================================================
// ** END PUBLIC INTERFACE ***
//===============================================================================
//===============================================================================
// ** BEGIN STATE CODE ***
//===============================================================================
const char * idLobby::stateToString[ NUM_STATES ] = {
ASSERT_ENUM_STRING( STATE_IDLE, 0 ),
ASSERT_ENUM_STRING( STATE_CREATE_LOBBY_BACKEND, 1 ),
ASSERT_ENUM_STRING( STATE_SEARCHING, 2 ),
ASSERT_ENUM_STRING( STATE_OBTAINING_ADDRESS, 3 ),
ASSERT_ENUM_STRING( STATE_CONNECT_HELLO_WAIT, 4 ),
ASSERT_ENUM_STRING( STATE_FINALIZE_CONNECT, 5 ),
ASSERT_ENUM_STRING( STATE_FAILED, 6 ),
};
/*
========================
idLobby::State_Idle
========================
*/
void idLobby::State_Idle() {
// If lobbyBackend is in a failed state, shutdown, go to a failed state ourself, and return
if ( lobbyBackend != NULL && lobbyBackend->GetState() == idLobbyBackend::STATE_FAILED ) {
HandleConnectionAttemptFailed();
common->Dialog().ClearDialog( GDM_MIGRATING );
common->Dialog().ClearDialog( GDM_MIGRATING_WAITING );
common->Dialog().ClearDialog( GDM_MIGRATING_RELAUNCHING );
return;
}
if ( migrationInfo.persistUntilGameEndsData.hasGameData && sessionCB->GetState() <= idSession::IDLE ) {
// This can happen with 'leaveGame' or 'disconnect' since those paths don't go through endMatch
// This seems like an ok catch all place but there may be a better way to handle this
ResetAllMigrationState();
common->Dialog().ClearDialog( GDM_MIGRATING );
common->Dialog().ClearDialog( GDM_MIGRATING_WAITING );
common->Dialog().ClearDialog( GDM_MIGRATING_RELAUNCHING );
}
}
/*
========================
idLobby::State_Create_Lobby_Backend
========================
*/
void idLobby::State_Create_Lobby_Backend() {
if ( !verify( lobbyBackend != NULL ) ) {
SetState( STATE_FAILED );
return;
}
assert( lobbyBackend != NULL );
if ( migrationInfo.state == MIGRATE_BECOMING_HOST ) {
const int DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS = session->GetTitleStorageInt( "DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS", 30 );
// If we are taking too long, cancel the connection
if ( DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS > 0 ) {
if ( Sys_Milliseconds() - migrationInfo.migrationStartTime > 1000 * DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS ) {
SetState( STATE_FAILED );
return;
}
}
}
if ( lobbyBackend->GetState() == idLobbyBackend::STATE_CREATING ) {
return; // Busy but valid
}
if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) {
SetState( STATE_FAILED );
return;
}
// Success
InitStateLobbyHost();
// Set state to idle to signify to session we are done creating
SetState( STATE_IDLE );
}
/*
========================
idLobby::State_Searching
========================
*/
void idLobby::State_Searching() {
if ( !verify( lobbyBackend != NULL ) ) {
SetState( STATE_FAILED );
return;
}
if ( lobbyBackend->GetState() == idLobbyBackend::STATE_SEARCHING ) {
return; // Busy but valid
}
if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) {
SetState( STATE_FAILED ); // Any other lobbyBackend state is invalid
return;
}
// Done searching, get results from lobbyBackend
lobbyBackend->GetSearchResults( searchResults );
if ( searchResults.Num() == 0 ) {
// If we didn't get any results, set state to failed
SetState( STATE_FAILED );
return;
}
extern idCVar net_maxSearchResultsToTry;
const int maxSearchResultsToTry = session->GetTitleStorageInt( "net_maxSearchResultsToTry", net_maxSearchResultsToTry.GetInteger() );
if ( searchResults.Num() > maxSearchResultsToTry ) {
searchResults.SetNum( maxSearchResultsToTry );
}
// Set state to idle to signify we are done searching
SetState( STATE_IDLE );
}
/*
========================
idLobby::State_Obtaining_Address
========================
*/
void idLobby::State_Obtaining_Address() {
if ( lobbyBackend->GetState() == idLobbyBackend::STATE_OBTAINING_ADDRESS ) {
return; // Valid but not ready
}
if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) {
// There was an error, signify to caller
failedReason = migrationInfo.persistUntilGameEndsData.wasMigratedJoin ? FAILED_MIGRATION_CONNECT_FAILED : FAILED_CONNECT_FAILED;
NET_VERBOSE_PRINT("idLobby::State_Obtaining_Address: the lobby backend failed." );
SetState( STATE_FAILED );
return;
}
//
// We have the address of the lobbyBackend, we can now send a hello packet
//
// This will be the host for this lobby type
host = AddPeer( hostAddress, GenerateSessionID() );
// Record start time of connection attempt to the host
helloStartTime = Sys_Milliseconds();
lastConnectRequest = helloStartTime;
connectionAttempts = 0;
// Change state to connecting
SetState( STATE_CONNECT_HELLO_WAIT );
// Send first connect attempt now (we'll send more periodically if we fail to receive an ack)
// (we do this after changing state, since the function expects we're in the right state)
SendConnectionRequest();
}
/*
========================
idLobby::State_Finalize_Connect
========================
*/
void idLobby::State_Finalize_Connect() {
if ( lobbyBackend->GetState() == idLobbyBackend::STATE_CREATING ) {
// Valid but busy
return;
}
if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) {
// Any other state not valid, failed
SetState( STATE_FAILED );
return;
}
// Success
SetState( STATE_IDLE );
// Tell session mgr if this was a migration
if ( migrationInfo.persistUntilGameEndsData.wasMigratedJoin ) {
sessionCB->BecamePeer( *this );
}
}
/*
========================
idLobby::State_Connect_Hello_Wait
========================
*/
void idLobby::State_Connect_Hello_Wait() {
if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) {
// If the lobbyBackend is in an error state, shut everything down
NET_VERBOSE_PRINT( "NET: Lobby is no longer ready while waiting for lobbyType %s hello.\n", GetLobbyName() );
HandleConnectionAttemptFailed();
return;
}
int time = Sys_Milliseconds();
const int timeoutMs = session->GetTitleStorageInt( "net_connectTimeoutInSeconds", net_connectTimeoutInSeconds.GetInteger() ) * 1000;
if ( timeoutMs != 0 && time - helloStartTime > timeoutMs ) {
NET_VERBOSE_PRINT( "NET: Timeout waiting for lobbyType %s for party hello.\n", GetLobbyName() );
HandleConnectionAttemptFailed();
return;
}
if ( connectionAttempts < MAX_CONNECT_ATTEMPTS ) {
assert( connectionAttempts >= 1 ); // Should have at least the initial connection attempt
// See if we need to send another hello request
// (keep getting more frequent to increase chance due to possible packet loss, but clamp to MIN_CONNECT_FREQUENCY seconds)
// TODO: We could eventually make timing out a function of actual number of attempts rather than just plain time.
int resendTime = Max( MIN_CONNECT_FREQUENCY_IN_SECONDS, CONNECT_REQUEST_FREQUENCY_IN_SECONDS / connectionAttempts ) * 1000;
if ( time - lastConnectRequest > resendTime ) {
SendConnectionRequest();
lastConnectRequest = time;
}
}
}
/*
========================
idLobby::SetState
========================
*/
void idLobby::SetState( lobbyState_t newState ) {
assert( newState < NUM_STATES );
assert( state < NUM_STATES );
verify_array_size( stateToString, NUM_STATES );
if ( state == newState ) {
NET_VERBOSE_PRINT( "NET: idLobby::SetState: State SAME %s for session %s\n", stateToString[ newState ], GetLobbyName() );
return;
}
// Set the current state
NET_VERBOSE_PRINT( "NET: idLobby::SetState: State changing from %s to %s for session %s\n", stateToString[ state ], stateToString[ newState ], GetLobbyName() );
state = newState;
}
//===============================================================================
// ** END STATE CODE ***
//===============================================================================
/*
========================
idLobby::StartCreating
========================
*/
void idLobby::StartCreating() {
assert( lobbyBackend == NULL );
assert( state == STATE_IDLE );
float skillLevel = GetAverageLocalUserLevel( true );
lobbyBackend = sessionCB->CreateLobbyBackend( parms, skillLevel, (idLobbyBackend::lobbyBackendType_t)lobbyType );
SetState( STATE_CREATE_LOBBY_BACKEND );
}
/*
========================
idLobby::FindPeer
========================
*/
int idLobby::FindPeer( const lobbyAddress_t & remoteAddress, idPacketProcessor::sessionId_t sessionID, bool ignoreSessionID ) {
bool connectionless = ( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_PARTY ||
sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME ||
sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME_STATE );
if ( connectionless && !ignoreSessionID ) {
return -1; // This was meant to be connectionless. FindPeer is meant for connected (or connecting) peers
}
for ( int p = 0; p < peers.Num(); p++ ) {
if ( peers[p].GetConnectionState() == CONNECTION_FREE ) {
continue;
}
if ( peers[p].address.Compare( remoteAddress ) ) {
if ( connectionless && ignoreSessionID ) {
return p;
}
// Using a rolling check, so that we account for possible packet loss, and out of order issues
if ( IsPeer() ) {
idPacketProcessor::sessionId_t searchStart = peers[p].sessionID;
// Since we only roll the code between matches, we should only need to look ahead a couple increments.
// Worse case, if the stars line up, the client doesn't see the new sessionId, and times out, and gets booted.
// This should be impossible though, since the timings won't be possible considering how long it takes to end the match,
// and restart, and then restart again.
int numTries = 2;
while ( numTries-- > 0 && searchStart != sessionID ) {
searchStart = IncrementSessionID( searchStart );
if ( searchStart == sessionID ) {
idLib::Printf( "NET: Rolling session ID check found new ID: %i\n", searchStart );
if ( peers[p].packetProc != NULL ) {
peers[p].packetProc->VerifyEmptyReliableQueue( RELIABLE_GAME_DATA, RELIABLE_DUMMY_MSG );
}
peers[p].sessionID = searchStart;
break;
}
}
}
if ( peers[p].sessionID != sessionID ) {
continue;
}
return p;
}
}
return -1;
}
/*
========================
idLobby::FindAnyPeer
Find a peer when we don't know the session id, and we don't care since it's a connectionless msg
========================
*/
int idLobby::FindAnyPeer( const lobbyAddress_t & remoteAddress ) const {
for ( int p = 0; p < peers.Num(); p++ ) {
if ( peers[p].GetConnectionState() == CONNECTION_FREE ) {
continue;
}
if ( peers[p].address.Compare( remoteAddress ) ) {
return p;
}
}
return -1;
}
/*
========================
idLobby::FindFreePeer
========================
*/
int idLobby::FindFreePeer() const {
// Return the first non active peer
for ( int p = 0; p < peers.Num(); p++ ) {
if ( !peers[p].IsActive() ) {
return p;
}
}
return -1;
}
/*
========================
idLobby::AddPeer
========================
*/
int idLobby::AddPeer( const lobbyAddress_t & remoteAddress, idPacketProcessor::sessionId_t sessionID ) {
// First, make sure we don't already have this peer
int p = FindPeer( remoteAddress, sessionID );
assert( p == -1 ); // When using session ID's, we SHOULDN'T find this remoteAddress/sessionID combo
if ( p == -1 ) {
// If we didn't find the peer, we need to add a new one
p = FindFreePeer();
if ( p == -1 ) {
peer_t newPeer;
p = peers.Append( newPeer );
}
peer_t & peer = peers[p];
peer.ResetAllData();
assert( peer.connectionState == CONNECTION_FREE );
peer.address = remoteAddress;
peer.sessionID = sessionID;
NET_VERBOSE_PRINT( "NET: Added peer %s at index %i\n", remoteAddress.ToString(), p );
} else {
NET_VERBOSE_PRINT( "NET: Found peer %s at index %i\n", remoteAddress.ToString(), p );
}
SetPeerConnectionState( p, CONNECTION_CONNECTING );
if ( lobbyType == GetActingGameStateLobbyType() ) {
// Reset various flags used in game mode
peers[p].ResetMatchData();
}
return p;
}
/*
========================
idLobby::DisconnectPeerFromSession
========================
*/
void idLobby::DisconnectPeerFromSession( int p ) {
if ( !verify( IsHost() ) ) {
return;
}
peer_t & peer = peers[p];
if ( peer.GetConnectionState() != CONNECTION_FREE ) {
SetPeerConnectionState( p, CONNECTION_FREE );
}
}
/*
========================
idLobby::DisconnectAllPeers
========================
*/
void idLobby::DisconnectAllPeers() {
for ( int p = 0; p < peers.Num(); p++ ) {
DisconnectPeerFromSession( p );
}
}
/*
========================
idLobby::SendGoodbye
========================
*/
void idLobby::SendGoodbye( const lobbyAddress_t & remoteAddress, bool wasFull ) {
if ( net_skipGoodbye.GetBool() ) {
return;
}
NET_VERBOSE_PRINT( "NET: Sending goodbye to %s for %s (wasFull = %i)\n", remoteAddress.ToString(), GetLobbyName(), wasFull );
static const int NUM_REDUNDANT_GOODBYES = 10;
int msgType = OOB_GOODBYE;
if ( wasFull ) {
msgType = OOB_GOODBYE_FULL;
} else if ( lobbyType == TYPE_GAME && ( sessionCB->GetSessionOptions() & idSession::OPTION_LEAVE_WITH_PARTY ) && !( parms.matchFlags & MATCH_PARTY_INVITE_PLACEHOLDER ) ) {
msgType = OOB_GOODBYE_W_PARTY;
}
for ( int i = 0; i < NUM_REDUNDANT_GOODBYES; i++ ) {
SendConnectionLess( remoteAddress, msgType );
}
}
/*
========================
idLobby::SetPeerConnectionState
========================
*/
void idLobby::SetPeerConnectionState( int p, connectionState_t newState, bool skipGoodbye ) {
if ( !verify( p >= 0 && p < peers.Num() ) ) {
idLib::Printf( "NET: SetPeerConnectionState invalid peer index %i\n", p );
return;
}
peer_t & peer = peers[p];
const lobbyType_t actingGameStateLobbyType = GetActingGameStateLobbyType();
if ( peer.GetConnectionState() == newState ) {
idLib::Printf( "NET: SetPeerConnectionState: Peer already in state %i\n", newState );
assert( 0 ); // This case means something is most likely bad, and it's the programmers fault
assert( ( peer.packetProc != NULL ) == peer.IsActive() );
assert( ( ( peer.snapProc != NULL ) == peer.IsActive() ) == ( actingGameStateLobbyType == lobbyType ) );
return;
}
if ( newState == CONNECTION_CONNECTING ) {
//mem.PushHeap();
// We better be coming from a free connection state if we are trying to connect
assert( peer.GetConnectionState() == CONNECTION_FREE );
assert( peer.packetProc == NULL );
peer.packetProc = new ( TAG_NETWORKING )idPacketProcessor();
if ( lobbyType == actingGameStateLobbyType ) {
assert( peer.snapProc == NULL );
peer.snapProc = new ( TAG_NETWORKING )idSnapshotProcessor();
}
//mem.PopHeap();
} else if ( newState == CONNECTION_ESTABLISHED ) {
// If we are marking this peer as connected for the first time, make sure this peer was actually trying to connect.
assert( peer.GetConnectionState() == CONNECTION_CONNECTING );
} else if ( newState == CONNECTION_FREE ) {
// If we are freeing this connection and we had an established connection before, make sure to send a goodbye
if ( peer.GetConnectionState() == CONNECTION_ESTABLISHED && !skipGoodbye ) {
idLib::Printf("SetPeerConnectionState: Sending goodbye to peer %s from session %s\n", peer.address.ToString(), GetLobbyName() );
SendGoodbye( peer.address );
}
}
peer.connectionState = newState;
if ( !peer.IsActive() ) {
if ( peer.packetProc != NULL ) {
delete peer.packetProc;
peer.packetProc = NULL;
}
if ( peer.snapProc != NULL ) {
assert( lobbyType == actingGameStateLobbyType );
delete peer.snapProc;
peer.snapProc = NULL;
}
}
// Do this in case we disconnected the peer
if ( IsHost() ) {
RemoveUsersWithDisconnectedPeers();
}
}
/*
========================
idLobby::QueueReliableMessage
========================
*/
void idLobby::QueueReliableMessage( int p, byte type, const byte * data, int dataLen ) {
if ( !verify( p >= 0 && p < peers.Num() ) ) {
return;
}
peer_t & peer = peers[p];
if ( !peer.IsConnected() ) {
// Don't send to this peer if we don't have an established connection of this session type
NET_VERBOSE_PRINT( "NET: Not sending reliable type %i to peer %i because connectionState is %i\n", type, p, peer.GetConnectionState() );
return;
}
if ( peer.packetProc->NumQueuedReliables() > 2 ) {
idLib::PrintfIf( false, "NET: peer.packetProc->NumQueuedReliables() > 2: %i (%i / %s)\n", peer.packetProc->NumQueuedReliables(), p, peer.address.ToString() );
}
if ( !peer.packetProc->QueueReliableMessage( type, data, dataLen ) ) {
// For now, when this happens, disconnect from all session types
NET_VERBOSE_PRINT( "NET: Dropping peer because we overflowed his reliable message queue\n" );
if ( IsHost() ) {
// Disconnect peer from this session type
DisconnectPeerFromSession( p );
} else {
Shutdown(); // Shutdown session if we can't queue the reliable
}
}
}
/*
========================
idLobby::GetNumConnectedPeers
========================
*/
int idLobby::GetNumConnectedPeers() const {
int numConnected = 0;
for ( int i = 0; i < peers.Num(); i++ ) {
if ( peers[i].IsConnected() ) {
numConnected++;
}
}
return numConnected;
}
/*
========================
idLobby::GetNumConnectedPeersInGame
========================
*/
int idLobby::GetNumConnectedPeersInGame() const {
int numActive = 0;
for ( int i = 0; i < peers.Num(); i++ ) {
if ( peers[i].IsConnected() && peers[i].inGame ) {
numActive++;
}
}
return numActive;
}
/*
========================
idLobby::SendMatchParmsToPeers
========================
*/
void idLobby::SendMatchParmsToPeers() {
if ( !IsHost() ) {
return;
}
if ( GetNumConnectedPeers() == 0 ) {
return;
}
byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ];
idBitMsg msg( buffer, sizeof( buffer ) );
parms.Write( msg );
for ( int p = 0; p < peers.Num(); p++ ) {
if ( !peers[p].IsConnected() ) {
continue;
}
QueueReliableMessage( p, RELIABLE_MATCH_PARMS, msg.GetReadData(), msg.GetSize() );
}
}
/*
========================
STATIC idLobby::IsReliablePlayerToPlayerType
========================
*/
bool idLobby::IsReliablePlayerToPlayerType( byte type ) {
return ( type >= RELIABLE_PLAYER_TO_PLAYER_BEGIN ) && ( type < RELIABLE_PLAYER_TO_PLAYER_END );
}
/*
========================
idLobby::HandleReliablePlayerToPlayerMsg
========================
*/
void idLobby::HandleReliablePlayerToPlayerMsg( int peerNum, idBitMsg & msg, int type ) {
reliablePlayerToPlayerHeader_t info;
int c, b;
msg.SaveReadState( c, b ); // in case we need to forward or fail
if ( !info.Read( this, msg ) ) {
idLib::Warning( "NET: Ignoring invalid reliable player to player message" );
msg.RestoreReadState( c, b );
return;
}
const bool isForLocalPlayer = IsSessionUserIndexLocal( info.toSessionUserIndex );
if ( isForLocalPlayer ) {
HandleReliablePlayerToPlayerMsg( info, msg, type );
} else if ( IsHost() ) {
const int targetPeer = PeerIndexForSessionUserIndex( info.toSessionUserIndex );
msg.RestoreReadState( c, b );
// forward the rest of the data
const byte * data = msg.GetReadData() + msg.GetReadCount();
int dataLen = msg.GetSize() - msg.GetReadCount();
QueueReliableMessage( targetPeer, type, data, dataLen );
} else {
idLib::Warning( "NET: Can't forward reliable message for remote player: I'm not the host" );
}
}
/*
========================
idLobby::HandleReliablePlayerToPlayerMsg
========================
*/
void idLobby::HandleReliablePlayerToPlayerMsg( const reliablePlayerToPlayerHeader_t & info, idBitMsg & msg, int reliableType ) {
#if 0
// Remember that the reliablePlayerToPlayerHeader_t was already removed from the msg
reliablePlayerToPlayer_t type = (reliablePlayerToPlayer_t)( reliableType - RELIABLE_PLAYER_TO_PLAYER_BEGIN );
switch( type ) {
case RELIABLE_PLAYER_TO_PLAYER_VOICE_EVENT: {
sessionCB->HandleReliableVoiceEvent( *this, info.fromSessionUserIndex, info.toSessionUserIndex, msg );
break;
}
default: {
idLib::Warning( "NET: Ignored unknown player to player reliable type %i", (int) type );
}
};
#endif
}
/*
========================
idLobby::SendConnectionLess
========================
*/
void idLobby::SendConnectionLess( const lobbyAddress_t & remoteAddress, byte type, const byte * data, int dataLen ) {
idBitMsg msg( data, dataLen );
msg.SetSize( dataLen );
byte buffer[ idPacketProcessor::MAX_OOB_MSG_SIZE ];
idBitMsg processedMsg( buffer, sizeof( buffer ) );
// Process the send
idPacketProcessor::ProcessConnectionlessOutgoing( msg, processedMsg, lobbyType, type );
const bool useDirectPort = ( lobbyType == TYPE_GAME_STATE );
// Send it
sessionCB->SendRawPacket( remoteAddress, processedMsg.GetReadData(), processedMsg.GetSize(), useDirectPort );
}
/*
========================
idLobby::SendConnectionRequest
========================
*/
void idLobby::SendConnectionRequest() {
// Some sanity checking
assert( state == STATE_CONNECT_HELLO_WAIT );
assert( peers[host].GetConnectionState() == CONNECTION_CONNECTING );
assert( GetNumLobbyUsers() == 0 );
// Buffer to hold connect msg
byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ];
idBitMsg msg( buffer, sizeof( buffer ) );
// Add the current version info to the handshake
const unsigned long localChecksum = NetGetVersionChecksum();
NET_VERBOSE_PRINT( "NET: version = %i\n", localChecksum );
msg.WriteLong( localChecksum );
msg.WriteUShort( peers[host].sessionID );
msg.WriteBool( connectIsFromInvite );
// We use InitSessionUsersFromLocalUsers here to copy the current local users over to session users simply to have a list
// to send on the initial connection attempt. We immediately clear our session user list once sent.
InitSessionUsersFromLocalUsers( true );
if ( GetNumLobbyUsers() > 0 ) {
// Fill up the msg with the users on this machine
msg.WriteByte( GetNumLobbyUsers() );
for ( int u = 0; u < GetNumLobbyUsers(); u++ ) {
GetLobbyUser( u )->WriteToMsg( msg );
}
} else {
FreeAllUsers();
SetState( STATE_FAILED );
return;
}
// We just used these users to fill up the msg above, we will get the real list from the server if we connect.
FreeAllUsers();
NET_VERBOSE_PRINT( "NET: Sending hello to: %s (lobbyType: %s, session ID %i, attempt: %i)\n", hostAddress.ToString(), GetLobbyName(), peers[host].sessionID, connectionAttempts );
SendConnectionLess( hostAddress, OOB_HELLO, msg.GetReadData(), msg.GetSize() );
connectionAttempts++;
}
/*
========================
idLobby::ConnectTo
Fires off a request to get the address of a lobbyBackend owner, and then attempts to connect (eventually handled in HandleObtainingLobbyOwnerAddress)
========================
*/
void idLobby::ConnectTo( const lobbyConnectInfo_t & connectInfo, bool fromInvite ) {
NET_VERBOSE_PRINT( "NET: idSessionLocal::ConnectTo: fromInvite = %i\n", fromInvite );
// Make sure current session is shutdown
Shutdown();
connectIsFromInvite = fromInvite;
lobbyBackend = sessionCB->JoinFromConnectInfo( connectInfo, (idLobbyBackend::lobbyBackendType_t)lobbyType );
// First, we need the address of the lobbyBackend owner
lobbyBackend->GetOwnerAddress( hostAddress );
SetState( STATE_OBTAINING_ADDRESS );
}
/*
========================
idLobby::HandleGoodbyeFromPeer
========================
*/
void idLobby::HandleGoodbyeFromPeer( int peerNum, lobbyAddress_t & remoteAddress, int msgType ) {
if ( migrationInfo.state != MIGRATE_NONE ) {
// If this peer is on our invite list, remove them
for ( int i = 0; i < migrationInfo.invites.Num(); i++ ) {
if ( migrationInfo.invites[i].address.Compare( remoteAddress, true ) ) {
migrationInfo.invites.RemoveIndex( i );
break;
}
}
}
if ( peerNum < 0 ) {
NET_VERBOSE_PRINT( "NET: Goodbye from unknown peer %s on session %s\n", remoteAddress.ToString(), GetLobbyName() );
return;
}
if ( peers[peerNum].GetConnectionState() == CONNECTION_FREE ) {
NET_VERBOSE_PRINT( "NET: Goodbye from peer %s on session %s that is not connected\n", remoteAddress.ToString(), GetLobbyName() );
return;
}
if ( IsHost() ) {
// Goodbye from peer, remove him
NET_VERBOSE_PRINT( "NET: Goodbye from peer %s, on session %s\n", remoteAddress.ToString(), GetLobbyName() );
DisconnectPeerFromSession( peerNum );
} else {
// Let session handler take care of this
NET_VERBOSE_PRINT( "NET: Goodbye from host %s, on session %s\n", remoteAddress.ToString(), GetLobbyName() );
sessionCB->GoodbyeFromHost( *this, peerNum, remoteAddress, msgType );
}
}
/*
========================
idLobby::HandleGoodbyeFromPeer
========================
*/
void idLobby::HandleConnectionAttemptFailed() {
Shutdown();
failedReason = migrationInfo.persistUntilGameEndsData.wasMigratedJoin ? FAILED_MIGRATION_CONNECT_FAILED : FAILED_CONNECT_FAILED;
SetState( STATE_FAILED );
if ( migrationInfo.persistUntilGameEndsData.wasMigratedJoin ) {
sessionCB->FailedGameMigration( *this );
}
ResetAllMigrationState();
needToDisplayMigrateMsg = false;
migrateMsgFlags = 0;
}
/*
========================
idLobby::ConnectToNextSearchResult
========================
*/
bool idLobby::ConnectToNextSearchResult() {
if ( lobbyType != TYPE_GAME ) {
return false; // Only game sessions use matchmaking searches
}
// End current session lobby (this WON'T free search results)
Shutdown();
if ( searchResults.Num() == 0 ) {
return false; // No more search results to connect to, give up
}
// Get next search result
lobbyConnectInfo_t connectInfo = searchResults[0];
// Remove this search result
searchResults.RemoveIndex( 0 );
// If we are connecting to a game lobby, tell our party to connect to this lobby as well
if ( lobbyType == TYPE_GAME && sessionCB->GetPartyLobby().IsLobbyActive() ) {
sessionCB->GetPartyLobby().SendMembersToLobby( lobbyType, connectInfo, true );
}
// Attempt to connect the lobby
ConnectTo( connectInfo, true ); // Pass in true for invite, since searches are for matchmaking, and we should always be able to connect to those types of matches
// Clear the "Lobby was Full" dialog in case it's up, since we are going to try to connect to a different lobby now
common->Dialog().ClearDialog( GDM_LOBBY_FULL );
return true; // Notify caller we are attempting to connect
}
/*
========================
idLobby::CheckVersion
========================
*/
bool idLobby::CheckVersion( idBitMsg & msg, lobbyAddress_t peerAddress ) {
const unsigned long remoteChecksum = msg.ReadLong();
if ( net_checkVersion.GetInteger() == 1 ) {
const unsigned long localChecksum = NetGetVersionChecksum();
NET_VERBOSE_PRINT( "NET: Comparing handshake version - localChecksum = %i, remoteChecksum = %i\n", localChecksum, remoteChecksum );
return ( remoteChecksum == localChecksum );
}
return true;
}
/*
========================
idLobby::VerifyNumConnectingUsers
Make sure number of users connecting is valid, and make sure we have enough room
========================
*/
bool idLobby::VerifyNumConnectingUsers( idBitMsg & msg ) {
int c, b;
msg.SaveReadState( c, b );
const int numUsers = msg.ReadByte();
msg.RestoreReadState( c, b );
const int numFreeSlots = NumFreeSlots();
NET_VERBOSE_PRINT( "NET: VerifyNumConnectingUsers %i users, %i free slots for %s\n", numUsers, numFreeSlots, GetLobbyName() );
if ( numUsers <= 0 || numUsers > MAX_PLAYERS - 1 ) {
NET_VERBOSE_PRINT( "NET: Invalid numUsers %i\n", numUsers );
return false;
} else if ( numUsers > numFreeSlots ) {
NET_VERBOSE_PRINT( "NET: %i slots requested, but only %i are available\n", numUsers, numFreeSlots );
return false;
} else if ( lobbyType == TYPE_PARTY && sessionCB->GetState() >= idSession::GAME_LOBBY && sessionCB->GetGameLobby().IsLobbyActive() && !IsMigrating() ) {
const int numFreeGameSlots = sessionCB->GetGameLobby().NumFreeSlots();
if ( numUsers > numFreeGameSlots ) {
NET_VERBOSE_PRINT( "NET: %i slots requested, but only %i are available on the active game session\n", numUsers, numFreeGameSlots );
return false;
}
}
return true;
}
/*
========================
idLobby::VerifyLobbyUserIDs
========================
*/
bool idLobby::VerifyLobbyUserIDs( idBitMsg & msg ) {
int c, b;
msg.SaveReadState( c, b );
const int numUsers = msg.ReadByte();
// Add the new users to our own list
for ( int u = 0; u < numUsers; u++ ) {
lobbyUser_t newUser;
// Read in the new user
newUser.ReadFromMsg( msg );
if ( GetLobbyUserIndexByID( newUser.lobbyUserID, true ) != -1 ) {
msg.RestoreReadState( c, b );
return false;
}
}
msg.RestoreReadState( c, b );
return true;
}
/*
========================
idLobby::HandleInitialPeerConnection
Received on an initial peer connect request (OOB_HELLO)
========================
*/
int idLobby::HandleInitialPeerConnection( idBitMsg & msg, const lobbyAddress_t & peerAddress, int peerNum ) {
if ( net_ignoreConnects.GetInteger() > 0 ) {
if ( net_ignoreConnects.GetInteger() == 2 ) {
SendGoodbye( peerAddress );
}
return -1;
}
if ( !IsHost() ) {
NET_VERBOSE_PRINT( "NET: Got connectionless hello from peer %s on session, and we are not a host\n", peerAddress.ToString() );
SendGoodbye( peerAddress );
return -1;
}
// See if this is a peer migrating to us, if so, remove them from our invite list
bool migrationInvite = false;
int migrationGameData = -1;
for ( int i = migrationInfo.invites.Num() - 1; i >= 0; i-- ) {
if ( migrationInfo.invites[i].address.Compare( peerAddress, true ) ) {
migrationGameData = migrationInfo.invites[i].migrationGameData;
migrationInfo.invites.RemoveIndex( i ); // Remove this peer from the list, since this peer will now be connected (or rejected, either way we don't want to keep sending invites)
migrationInvite = true;
NET_VERBOSE_PRINT( "^2NET: Response from migration invite %s. GameData: %d\n", peerAddress.ToString(), migrationGameData );
}
}
if ( !MatchTypeIsJoinInProgress( parms.matchFlags ) && lobbyType == TYPE_GAME && migrationInfo.persistUntilGameEndsData.wasMigratedHost && IsMigratedStatsGame() && !migrationInvite ) {
// No matter what, don't let people join migrated game sessions that are going to continue on to the same game
// Not on invite list in a migrated game session - bounce him
NET_VERBOSE_PRINT( "NET: Denying game connection from %s since not on migration invite list\n", peerAddress.ToString() );
for ( int i = migrationInfo.invites.Num() - 1; i >= 0; i-- ) {
NET_VERBOSE_PRINT( " Invite[%d] addr: %s\n", i, migrationInfo.invites[i].address.ToString() );
}
SendGoodbye( peerAddress );
return -1;
}
if ( MatchTypeIsJoinInProgress( parms.matchFlags ) ) {
// If this is for a game connection, make sure we have a game lobby
if ( ( lobbyType == TYPE_GAME || lobbyType == TYPE_GAME_STATE ) && sessionCB->GetState() < idSession::GAME_LOBBY ) {
NET_VERBOSE_PRINT( "NET: Denying game connection from %s because we don't have a game lobby\n", peerAddress.ToString() );
SendGoodbye( peerAddress );
return -1;
}
} else {
// If this is for a game connection, make sure we are in the game lobby
if ( lobbyType == TYPE_GAME && sessionCB->GetState() != idSession::GAME_LOBBY ) {
NET_VERBOSE_PRINT( "NET: Denying game connection from %s while not in game lobby\n", peerAddress.ToString() );
SendGoodbye( peerAddress );
return -1;
}
// If this is for a party connection, make sure we are not in game, unless this was for host migration invite
if ( !migrationInvite && lobbyType == TYPE_PARTY && ( sessionCB->GetState() == idSession::INGAME || sessionCB->GetState() == idSession::LOADING ) ) {
NET_VERBOSE_PRINT( "NET: Denying party connection from %s because we were already in a game\n", peerAddress.ToString() );
SendGoodbye( peerAddress );
return -1;
}
}
if ( !CheckVersion( msg, peerAddress ) ) {
idLib::Printf( "NET: Denying user %s with wrong version number\n", peerAddress.ToString() );
SendGoodbye( peerAddress );
return -1;
}
idPacketProcessor::sessionId_t sessionID = msg.ReadUShort();
// Check to see if this is a peer trying to connect with a different sessionID
// If the peer got abruptly disconnected, the peer could be trying to reconnect from a non clean disconnect
if ( peerNum >= 0 ) {
peer_t & existingPeer = peers[peerNum];
assert( existingPeer.GetConnectionState() != CONNECTION_FREE );
if ( existingPeer.sessionID == sessionID ) {
return peerNum; // If this is the same sessionID, then assume redundant connection attempt
}
//
// This peer must be trying to reconnect from a previous abrupt disconnect
//
NET_VERBOSE_PRINT( "NET: Reconnecting peer %s for session %s\n", peerAddress.ToString(), GetLobbyName() );
// Assume a peer is trying to reconnect from a non clean disconnect
// We want to set the connection back to FREE manually, so we don't send a goodbye
existingPeer.connectionState = CONNECTION_FREE;
if ( existingPeer.packetProc != NULL ) {
delete existingPeer.packetProc;
existingPeer.packetProc = NULL;
}
if ( existingPeer.snapProc != NULL ) {
assert( lobbyType == TYPE_GAME ); // Only games sessions should be creating snap processors
delete existingPeer.snapProc;
existingPeer.snapProc = NULL;
}
RemoveUsersWithDisconnectedPeers();
peerNum = -1;
}
// See if this was from an invite we sent out. If it wasn't, make sure we aren't invite only
const bool fromInvite = msg.ReadBool();
if ( !fromInvite && MatchTypeInviteOnly( parms.matchFlags ) ) {
idLib::Printf( "NET: Denying user %s because they were not invited to an invite only match\n", peerAddress.ToString() );
SendGoodbye( peerAddress );
return -1;
}
// Make sure we have room for the users connecting
if ( !VerifyNumConnectingUsers( msg ) ) {
NET_VERBOSE_PRINT( "NET: Denying connection from %s in session %s due to being out of user slots\n", peerAddress.ToString(), GetLobbyName() );
SendGoodbye( peerAddress, true );
return -1;
}
// Make sure there are no lobby id conflicts
if ( !verify( VerifyLobbyUserIDs( msg ) ) ) {
NET_VERBOSE_PRINT( "NET: Denying connection from %s in session %s due to lobby id conflict\n", peerAddress.ToString(), GetLobbyName() );
SendGoodbye( peerAddress, true );
return -1;
}
// Calling AddPeer will set our connectionState to this peer as CONNECTION_CONNECTING (which will get set to CONNECTION_ESTABLISHED below)
peerNum = AddPeer( peerAddress, sessionID );
peer_t & newPeer = peers[peerNum];
assert( newPeer.GetConnectionState() == CONNECTION_CONNECTING );
assert( lobbyType != GetActingGameStateLobbyType() || newPeer.snapProc != NULL );
// First, add users from this new peer to our user list
// (which will then forward the list to all peers except peerNum)
AddUsersFromMsg( msg, peerNum );
// Mark the peer as connected for this session type
SetPeerConnectionState( peerNum, CONNECTION_ESTABLISHED );
// Update their heart beat to current
newPeer.lastHeartBeat = Sys_Milliseconds();
byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ];
idBitMsg outmsg( buffer, sizeof( buffer ) );
// Let them know their peer index on this host
// peerIndexOnHost (put this here so it shows up in search results when finding out where it's used/referenced)
outmsg.WriteLong( peerNum );
// If they are connecting to our party lobby, let them know the party token
if ( lobbyType == TYPE_PARTY ) {
outmsg.WriteLong( GetPartyTokenAsHost() );
}
if ( lobbyType == TYPE_GAME || lobbyType == TYPE_GAME_STATE ) {
// If this is a game session, reset the loading and ingame flags
newPeer.loaded = false;
newPeer.inGame = false;
}
// Write out current match parms
parms.Write( outmsg );
// Send list of existing users to this new peer
// (the users from the new peer will also be in this list, since we already called AddUsersFromMsg)
outmsg.WriteByte( GetNumLobbyUsers() );
for ( int u = 0; u < GetNumLobbyUsers(); u++ ) {
GetLobbyUser( u )->WriteToMsg( outmsg );
}
lobbyBackend->FillMsgWithPostConnectInfo( outmsg );
NET_VERBOSE_PRINT( "NET: Sending response to %s, lobbyType %s, sessionID %i\n", peerAddress.ToString(), GetLobbyName(), sessionID );
QueueReliableMessage( peerNum, RELIABLE_HELLO, outmsg.GetReadData(), outmsg.GetSize() );
if ( MatchTypeIsJoinInProgress( parms.matchFlags ) ) {
// If have an active game lobby, and someone joins our party, tell them to join our game
if ( lobbyType == TYPE_PARTY && sessionCB->GetState() >= idSession::GAME_LOBBY ) {
SendPeerMembersToLobby( peerNum, TYPE_GAME, false );
}
// We are are ingame, then start the client loading immediately
if ( ( lobbyType == TYPE_GAME || lobbyType == TYPE_GAME_STATE ) && sessionCB->GetState() >= idSession::LOADING ) {
idLib::Printf( "******* JOIN IN PROGRESS ********\n" );
if ( sessionCB->GetState() == idSession::INGAME ) {
newPeer.pauseSnapshots = true; // Since this player joined in progress, let game dictate when to start sending snaps
}
QueueReliableMessage( peerNum, idLobby::RELIABLE_START_LOADING );
}
} else {
// If we are in a game lobby, and someone joins our party, tell them to join our game
if ( lobbyType == TYPE_PARTY && sessionCB->GetState() == idSession::GAME_LOBBY ) {
SendPeerMembersToLobby( peerNum, TYPE_GAME, false );
}
}
// Send mic status of the current lobby to applicable peers
SendPeersMicStatusToNewUsers( peerNum );
// If we made is this far, update the users migration game data index
for ( int u = 0; u < GetNumLobbyUsers(); u++ ) {
if ( GetLobbyUser( u )->peerIndex == peerNum ) {
GetLobbyUser( u )->migrationGameData = migrationGameData;
}
}
return peerNum;
}
/*
========================
idLobby::InitStateLobbyHost
========================
*/
void idLobby::InitStateLobbyHost() {
assert( lobbyBackend != NULL );
// We will be the host
isHost = true;
if ( net_headlessServer.GetBool() ) {
return; // Don't add any players to headless server
}
if ( migrationInfo.state != MIGRATE_NONE ) {
migrationInfo.persistUntilGameEndsData.wasMigratedHost = true; // InitSessionUsersFromLocalUsers needs to know this
migrationInfo.persistUntilGameEndsData.hasRelaunchedMigratedGame = false;
// migrationDlg = GDM_MIGRATING_WAITING;
}
// Initialize the initial user list for this lobby
InitSessionUsersFromLocalUsers( MatchTypeIsOnline( parms.matchFlags ) );
// Set the session's hostAddress to the local players' address.
const int myUserIndex = GetLobbyUserIndexByLocalUserHandle( sessionCB->GetSignInManager().GetMasterLocalUserHandle() );
if ( myUserIndex != -1 ) {
hostAddress = GetLobbyUser( myUserIndex )->address;
}
// Since we are the host, we have to register our initial session users with the lobby
// All additional users will join through AddUsersFromMsg, and RegisterUser is handled in there from here on out.
// Peers will add users exclusively through AddUsersFromMsg.
for ( int i = 0; i < GetNumLobbyUsers(); i++ ) {
lobbyUser_t * user = GetLobbyUser( i );
RegisterUser( user );
if ( lobbyType == TYPE_PARTY ) {
user->partyToken = GetPartyTokenAsHost();
}
}
// Set the lobbies skill level
lobbyBackend->UpdateLobbySkill( GetAverageSessionLevel() );
// Make sure and register all the addresses of the invites we'll send out as the new host
if ( migrationInfo.state != MIGRATE_NONE ) {
// Tell the session that we became the host, so the session mgr can adjust state if needed
sessionCB->BecameHost( *this );
// Register this address with this lobbyBackend
for ( int i = 0; i < migrationInfo.invites.Num(); i++ ) {
lobbyBackend->RegisterAddress( migrationInfo.invites[i].address );
}
}
}
/*
========================
idLobby::SendMembersToLobby
========================
*/
void idLobby::SendMembersToLobby( lobbyType_t destLobbyType, const lobbyConnectInfo_t & connectInfo, bool waitForOtherMembers ) {
// It's not our job to send party members to a game if we aren't the party host
if ( !IsHost() ) {
return;
}
// Send the message to all connected peers
for ( int i = 0; i < peers.Num(); i++ ) {
if ( peers[ i ].IsConnected() ) {
SendPeerMembersToLobby( i, destLobbyType, connectInfo, waitForOtherMembers );
}
}
}
/*
========================
idLobby::SendMembersToLobby
========================
*/
void idLobby::SendMembersToLobby( idLobby & destLobby, bool waitForOtherMembers ) {
if ( destLobby.lobbyBackend == NULL ) {
return; // We don't have a game lobbyBackend to get an address for
}
lobbyConnectInfo_t connectInfo = destLobby.lobbyBackend->GetConnectInfo();
SendMembersToLobby( destLobby.lobbyType, connectInfo, waitForOtherMembers );
}
/*
========================
idLobby::SendPeerMembersToLobby
Give the address of a game lobby to a particular peer, notifying that peer to send a hello to the same server.
========================
*/
void idLobby::SendPeerMembersToLobby( int peerIndex, lobbyType_t destLobbyType, const lobbyConnectInfo_t & connectInfo, bool waitForOtherMembers ) {
// It's not our job to send party members to a game if we aren't the party host
if ( !IsHost() ) {
return;
}
assert( peerIndex >= 0 );
assert( peerIndex < peers.Num() );
peer_t & peer = peers[ peerIndex ];
NET_VERBOSE_PRINT( "NET: Sending peer %i (%s) to game lobby\n", peerIndex, peer.address.ToString() );
if ( !peer.IsConnected() ) {
idLib::Warning( "NET: Can't send peer %i to game lobby: peer isn't in party", peerIndex );
return;
}
byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ];
idBitMsg outmsg( buffer, sizeof( buffer ) );
// Have lobby fill out msg with connection info
connectInfo.WriteToMsg( outmsg );
outmsg.WriteByte( destLobbyType );
outmsg.WriteBool( waitForOtherMembers );
QueueReliableMessage( peerIndex, RELIABLE_CONNECT_AND_MOVE_TO_LOBBY, outmsg.GetReadData(), outmsg.GetSize() );
}
/*
========================
idLobby::SendPeerMembersToLobby
Give the address of a game lobby to a particular peer, notifying that peer to send a hello to the same server.
========================
*/
void idLobby::SendPeerMembersToLobby( int peerIndex, lobbyType_t destLobbyType, bool waitForOtherMembers ) {
idLobby * lobby = sessionCB->GetLobbyFromType( destLobbyType );
if ( !verify( lobby != NULL ) ) {
return;
}
if ( !verify( lobby->lobbyBackend != NULL ) ) {
return;
}
lobbyConnectInfo_t connectInfo = lobby->lobbyBackend->GetConnectInfo();
SendPeerMembersToLobby( peerIndex, destLobbyType, connectInfo, waitForOtherMembers );
}
/*
========================
idLobby::NotifyPartyOfLeavingGameLobby
========================
*/
void idLobby::NotifyPartyOfLeavingGameLobby() {
if ( lobbyType != TYPE_PARTY ) {
return; // We are not a party lobby
}
if ( !IsHost() ) {
return; // We are not the host of a party lobby, we can't do this
}
if ( !( sessionCB->GetSessionOptions() & idSession::OPTION_LEAVE_WITH_PARTY ) ) {
return; // Options aren't set to notify party of leaving
}
// Tell our party to leave the game they are in
for ( int i = 0; i < peers.Num(); i++ ) {
if ( peers[ i ].IsConnected() ) {
QueueReliableMessage( i, RELIABLE_PARTY_LEAVE_GAME_LOBBY );
}
}
}
/*
========================
idLobby::GetPartyTokenAsHost
========================
*/
uint32 idLobby::GetPartyTokenAsHost() {
assert( lobbyType == TYPE_PARTY );
assert( IsHost() );
if ( partyToken == 0 ) {
// I don't know if this is mathematically sound, but it seems reasonable.
// Don't do this at app startup (i.e. in the constructor) or it will be a lot less random.
unsigned long seed = Sys_Milliseconds(); // time app has been running
idLocalUser * masterUser = session->GetSignInManager().GetMasterLocalUser();
if ( masterUser != NULL ) {
seed += idStr::Hash( masterUser->GetGamerTag() );
}
partyToken = idRandom( seed ).RandomInt();
idLib::Printf( "NET: PartyToken is %u (seed = %u)\n", partyToken, seed );
}
return partyToken;
}
/*
========================
idLobby::EncodeSessionID
========================
*/
idPacketProcessor::sessionId_t idLobby::EncodeSessionID( uint32 key ) const {
assert( sizeof( uint32 ) >= sizeof( idPacketProcessor::sessionId_t ) );
const int numBits = sizeof( idPacketProcessor::sessionId_t ) * 8 - idPacketProcessor::NUM_LOBBY_TYPE_BITS;
const uint32 mask = ( 1 << numBits ) - 1;
idPacketProcessor::sessionId_t sessionID = ( key & mask ) << idPacketProcessor::NUM_LOBBY_TYPE_BITS;
sessionID |= ( lobbyType + 1 );
return sessionID;
}
/*
========================
idLobby::EncodeSessionID
========================
*/
void idLobby::DecodeSessionID( idPacketProcessor::sessionId_t sessionID, uint32 & key ) const {
assert( sizeof( uint32 ) >= sizeof( idPacketProcessor::sessionId_t ) );
key = sessionID >> idPacketProcessor::NUM_LOBBY_TYPE_BITS;
}
/*
========================
idLobby::GenerateSessionID
========================
*/
idPacketProcessor::sessionId_t idLobby::GenerateSessionID() const {
idPacketProcessor::sessionId_t sessionID = EncodeSessionID( Sys_Milliseconds() );
// Make sure we can use it
while ( !SessionIDCanBeUsedForInBand( sessionID ) ) {
sessionID = IncrementSessionID( sessionID );
}
return sessionID;
}
/*
========================
idLobby::SessionIDCanBeUsedForInBand
========================
*/
bool idLobby::SessionIDCanBeUsedForInBand( idPacketProcessor::sessionId_t sessionID ) const {
if ( sessionID == idPacketProcessor::SESSION_ID_INVALID ) {
return false;
}
if ( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_PARTY ) {
return false;
}
if ( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME ) {
return false;
}
if ( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME_STATE ) {
return false;
}
return true;
}
/*
========================
idLobby::IncrementSessionID
========================
*/
idPacketProcessor::sessionId_t idLobby::IncrementSessionID( idPacketProcessor::sessionId_t sessionID ) const {
// Increment, taking into account valid id's
while ( 1 ) {
uint32 key = 0;
DecodeSessionID( sessionID, key );
key++;
sessionID = EncodeSessionID( key );
if ( SessionIDCanBeUsedForInBand( sessionID ) ) {
break;
}
}
return sessionID;
}
#define VERIFY_CONNECTED_PEER( p, sessionType_, msgType ) \
if ( !verify( lobbyType == sessionType_ ) ) { \
idLib::Printf( "NET: " #msgType ", peer:%s invalid session type for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ ); \
return; \
} \
if ( peers[p].GetConnectionState() != CONNECTION_ESTABLISHED ) { \
idLib::Printf( "NET: " #msgType ", peer:%s not connected for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ ); \
return; \
}
#define VERIFY_CONNECTING_PEER( p, sessionType_, msgType ) \
if ( !verify( lobbyType == sessionType_ ) ) { \
idLib::Printf( "NET: " #msgType ", peer:%s invalid session type for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ ); \
return; \
} \
if ( peers[p].GetConnectionState() != CONNECTION_CONNECTING ) { \
idLib::Printf( "NET: " #msgType ", peer:%s not connecting for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ ); \
return; \
}
#define VERIFY_FROM_HOST( p, sessionType_, msgType ) \
VERIFY_CONNECTED_PEER( p, sessionType_, msgType ); \
if ( p != host ) { \
idLib::Printf( "NET: "#msgType", not from "#sessionType_" host: %s\n", peer.address.ToString() ); \
return; \
} \
#define VERIFY_FROM_CONNECTING_HOST( p, sessionType_, msgType ) \
VERIFY_CONNECTING_PEER( p, sessionType_, msgType ); \
if ( p != host ) { \
idLib::Printf( "NET: "#msgType", not from "#sessionType_" host: %s\n", peer.address.ToString() ); \
return; \
} \
/*
========================
idLobby::HandleHelloAck
========================
*/
void idLobby::HandleHelloAck( int p, idBitMsg & msg ) {
peer_t & peer = peers[p];
if ( state != STATE_CONNECT_HELLO_WAIT ) {
idLib::Printf( "NET: Hello ack for session type %s while not waiting for hello.\n", GetLobbyName() );
SendGoodbye( peer.address ); // We send a customary goodbye to make sure we are not in their list anymore
return;
}
if ( p != host ) {
// This shouldn't be possible
idLib::Printf( "NET: Hello ack for session type %s, not from correct host.\n", GetLobbyName() );
SendGoodbye( peer.address ); // We send a customary goodbye to make sure we are not in their list anymore
return;
}
assert( GetNumLobbyUsers() == 0 );
NET_VERBOSE_PRINT( "NET: Hello ack for session type %s from %s\n", GetLobbyName(), peer.address.ToString() );
// We are now connected to this session type
SetPeerConnectionState( p, CONNECTION_ESTABLISHED );
// Obtain what our peer index is on the host is
peerIndexOnHost = msg.ReadLong();
// If we connected to a party lobby, get the party token from the lobby owner
if ( lobbyType == TYPE_PARTY ) {
partyToken = msg.ReadLong();
}
// Read match parms
parms.Read( msg );
// Update lobbyBackend with parms
if ( lobbyBackend != NULL ) {
lobbyBackend->UpdateMatchParms( parms );
}
// Populate the user list with the one from the host (which will also include our local users)
// This ensures the user lists are kept in sync
FreeAllUsers();
AddUsersFromMsg( msg, p );
// Make sure the host has a current heartbeat
peer.lastHeartBeat = Sys_Milliseconds();
lobbyBackend->PostConnectFromMsg( msg );
// Tell the lobby controller to finalize the connection
SetState( STATE_FINALIZE_CONNECT );
//
// Success - We've received an ack from the server, letting us know we've been registered with the lobbies
//
}
/*
========================
idLobby::GetLobbyUserName
========================
*/
const char * idLobby::GetLobbyUserName( lobbyUserID_t lobbyUserID ) const {
const int index = GetLobbyUserIndexByID( lobbyUserID );
const lobbyUser_t * user = GetLobbyUser( index );
if ( user == NULL ) {
for ( int i = 0; i < disconnectedUsers.Num(); i++ ) {
if ( disconnectedUsers[i].lobbyUserID.CompareIgnoreLobbyType( lobbyUserID ) ) {
return disconnectedUsers[i].gamertag;
}
}
return INVALID_LOBBY_USER_NAME;
}
return user->gamertag;
}
/*
========================
idLobby::GetLobbyUserSkinIndex
========================
*/
int idLobby::GetLobbyUserSkinIndex( lobbyUserID_t lobbyUserID ) const {
const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
const lobbyUser_t * user = GetLobbyUser( userIndex );
return user ? user->selectedSkin : 0;
}
/*
========================
idLobby::GetLobbyUserWeaponAutoSwitch
========================
*/
bool idLobby::GetLobbyUserWeaponAutoSwitch( lobbyUserID_t lobbyUserID ) const {
const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
const lobbyUser_t * user = GetLobbyUser( userIndex );
return user ? user->weaponAutoSwitch : true;
}
/*
========================
idLobby::GetLobbyUserWeaponAutoReload
========================
*/
bool idLobby::GetLobbyUserWeaponAutoReload( lobbyUserID_t lobbyUserID ) const {
const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
const lobbyUser_t * user = GetLobbyUser( userIndex );
return user ? user->weaponAutoReload: true;
}
/*
========================
idLobby::GetLobbyUserLevel
========================
*/
int idLobby::GetLobbyUserLevel( lobbyUserID_t lobbyUserID ) const {
const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
const lobbyUser_t * user = GetLobbyUser( userIndex );
return user ? user->level : 0;
}
/*
========================
idLobby::GetLobbyUserQoS
========================
*/
int idLobby::GetLobbyUserQoS( lobbyUserID_t lobbyUserID ) const {
const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
if ( IsHost() && IsSessionUserIndexLocal( userIndex ) ) {
return 0; // Local users on the host of the active session have 0 ping
}
const lobbyUser_t * user = GetLobbyUser( userIndex );
if ( !verify( user != NULL ) ) {
return 0;
}
return user->pingMs;
}
/*
========================
idLobby::GetLobbyUserTeam
========================
*/
int idLobby::GetLobbyUserTeam( lobbyUserID_t lobbyUserID ) const {
const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
const lobbyUser_t * user = GetLobbyUser( userIndex );
return user ? user->teamNumber : 0;
}
/*
========================
idLobby::SetLobbyUserTeam
========================
*/
bool idLobby::SetLobbyUserTeam( lobbyUserID_t lobbyUserID, int teamNumber ) {
const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
lobbyUser_t * user = GetLobbyUser( userIndex );
if ( user != NULL ) {
if ( teamNumber != user->teamNumber ) {
user->teamNumber = teamNumber;
if ( IsHost() ) {
byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ];
idBitMsg msg( buffer, sizeof( buffer ) );
CreateUserUpdateMessage( userIndex, msg );
idBitMsg readMsg;
readMsg.InitRead( buffer, msg.GetSize() );
UpdateSessionUserOnPeers( readMsg );
}
return true;
}
}
return false;
}
/*
========================
idLobby::GetLobbyUserPartyToken
========================
*/
int idLobby::GetLobbyUserPartyToken( lobbyUserID_t lobbyUserID ) const {
const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
const lobbyUser_t * user = GetLobbyUser( userIndex );
return user ? user->partyToken : 0;
}
/*
========================
idLobby::GetProfileFromLobbyUser
========================
*/
idPlayerProfile * idLobby::GetProfileFromLobbyUser( lobbyUserID_t lobbyUserID ) {
const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
idPlayerProfile * profile = NULL;
idLocalUser * localUser = GetLocalUserFromLobbyUserIndex( userIndex );
if ( localUser != NULL ) {
profile = localUser->GetProfile();
}
if ( profile == NULL ) {
// Whoops
profile = session->GetSignInManager().GetDefaultProfile();
//idLib::Warning( "Returning fake profile until the code is fixed to handle NULL profiles." );
}
return profile;
}
/*
========================
idLobby::GetLocalUserFromLobbyUser
========================
*/
idLocalUser * idLobby::GetLocalUserFromLobbyUser( lobbyUserID_t lobbyUserID ) {
const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
return GetLocalUserFromLobbyUserIndex( userIndex );
}
/*
========================
idLobby::GetNumLobbyUsersOnTeam
========================
*/
int idLobby::GetNumLobbyUsersOnTeam( int teamNumber ) const {
int numTeam = 0;
for ( int i = 0; i < GetNumLobbyUsers(); ++i ) {
if ( GetLobbyUser( i )->teamNumber == teamNumber ) {
++numTeam;
}
}
return numTeam;
}
/*
========================
idLobby::GetPeerName
========================
*/
const char * idLobby::GetPeerName( int peerNum ) const {
for ( int i = 0; i < GetNumLobbyUsers(); ++i ) {
if ( !verify( GetLobbyUser( i ) != NULL ) ) {
continue;
}
if ( GetLobbyUser( i )->peerIndex == peerNum ) {
return GetLobbyUserName( GetLobbyUser( i )->lobbyUserID );
}
}
return INVALID_LOBBY_USER_NAME;
}
/*
========================
idLobby::HandleReliableMsg
========================
*/
void idLobby::HandleReliableMsg( int p, idBitMsg & msg ) {
peer_t & peer = peers[p];
int reliableType = msg.ReadByte();
//idLib::Printf(" Received reliable msg: %i \n", reliableType );
const lobbyType_t actingGameStateLobbyType = GetActingGameStateLobbyType();
if ( reliableType == RELIABLE_HELLO ) {
VERIFY_FROM_CONNECTING_HOST( p, lobbyType, RELIABLE_HELLO );
// This is sent from the host acking a request to join the game lobby
HandleHelloAck( p, msg );
return;
} else if ( reliableType == RELIABLE_USER_CONNECT_REQUEST ) {
VERIFY_CONNECTED_PEER( p, lobbyType, RELIABLE_USER_CONNECT_REQUEST );
// This message is sent from a peer requesting for a new user to join the game lobby
// This will be sent while we are in a game lobby as a host. otherwise, denied.
NET_VERBOSE_PRINT( "NET: RELIABLE_USER_CONNECT_REQUEST (%s) from %s\n", GetLobbyName(), peer.address.ToString() );
idSession::sessionState_t expectedState = ( lobbyType == TYPE_PARTY ) ? idSession::PARTY_LOBBY : idSession::GAME_LOBBY;
if ( sessionCB->GetState() == expectedState && IsHost() && NumFreeSlots() > 0 ) { // This assumes only one user in the msg
// Add user to session, which will also forward the operation to all other peers
AddUsersFromMsg( msg, p );
} else {
// Let peer know user couldn't be added
HandleUserConnectFailure( p, msg, RELIABLE_USER_CONNECT_DENIED );
}
} else if ( reliableType == RELIABLE_USER_CONNECT_DENIED ) {
// This message is sent back from the host when a RELIABLE_PARTY_USER_CONNECT_REQUEST failed
VERIFY_FROM_HOST( p, lobbyType, RELIABLE_PARTY_USER_CONNECT_DENIED );
// Remove this user from the sign-in manager, so we don't keep trying to add them
if ( !sessionCB->GetSignInManager().RemoveLocalUserByHandle( localUserHandle_t( msg.ReadLong() ) ) ) {
NET_VERBOSE_PRINT( "NET: RELIABLE_PARTY_USER_CONNECT_DENIED, local user not found\n" );
return;
}
} else if ( reliableType == RELIABLE_KICK_PLAYER ) {
VERIFY_FROM_HOST( p, lobbyType, RELIABLE_KICK_PLAYER );
common->Dialog().AddDialog( GDM_KICKED, DIALOG_ACCEPT, NULL, NULL, false );
if ( sessionCB->GetPartyLobby().IsHost() ) {
session->SetSessionOption( idSession::OPTION_LEAVE_WITH_PARTY );
}
session->Cancel();
} else if ( reliableType == RELIABLE_HEADSET_STATE ) {
HandleHeadsetStateChange( p, msg );
} else if ( reliableType == RELIABLE_USER_CONNECTED ) {
// This message is sent back from the host when users have connected, and we need to update our lists to reflect that
VERIFY_FROM_HOST( p, lobbyType, RELIABLE_USER_CONNECTED );
NET_VERBOSE_PRINT( "NET: RELIABLE_USER_CONNECTED (%s) from %s\n", GetLobbyName(), peer.address.ToString() );
AddUsersFromMsg( msg, p );
} else if ( reliableType == RELIABLE_USER_DISCONNECTED ) {
// This message is sent back from the host when users have diconnected, and we need to update our lists to reflect that
VERIFY_FROM_HOST( p, lobbyType, RELIABLE_USER_DISCONNECTED );
ProcessUserDisconnectMsg( msg );
} else if ( reliableType == RELIABLE_MATCH_PARMS ) {
parms.Read( msg );
// Update lobby with parms
if ( lobbyBackend != NULL ) {
lobbyBackend->UpdateMatchParms( parms );
}
} else if ( reliableType == RELIABLE_START_LOADING ) {
// This message is sent from the host to start loading a map
VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_START_LOADING );
NET_VERBOSE_PRINT( "NET: RELIABLE_START_LOADING from %s\n", peer.address.ToString() );
startLoadingFromHost = true;
} else if ( reliableType == RELIABLE_LOADING_DONE ) {
// This message is sent from the peers to state they are done loading the map
VERIFY_CONNECTED_PEER( p, actingGameStateLobbyType, RELIABLE_LOADING_DONE );
unsigned long networkChecksum = 0;
networkChecksum = msg.ReadLong();
peer.networkChecksum = networkChecksum;
peer.loaded = true;
} else if ( reliableType == RELIABLE_IN_GAME ) {
VERIFY_CONNECTED_PEER( p, actingGameStateLobbyType, RELIABLE_IN_GAME );
peer.inGame = true;
} else if ( reliableType == RELIABLE_SNAPSHOT_ACK ) {
VERIFY_CONNECTED_PEER( p, actingGameStateLobbyType, RELIABLE_SNAPSHOT_ACK );
// update our base state for his last received snapshot
int snapNum = msg.ReadLong();
float receivedBps = msg.ReadQuantizedUFloat< BANDWIDTH_REPORTING_MAX, BANDWIDTH_REPORTING_BITS >();
// Update reported received bps
if ( peer.receivedBpsIndex != snapNum ) {
// Only do this the first time we get reported bps per snapshot. Subsequent ACKs of the same shot will usually have lower reported bps
// due to more time elapsing but not receiving a new ss
peer.receivedBps = receivedBps;
peer.receivedBpsIndex = snapNum;
}
ApplySnapshotDelta( p, snapNum );
//idLib::Printf( "NET: Peer %d Ack'd snapshot %d\n", p, snapNum );
NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va( "NET: Peer %d Ack'd snapshot %d\n", p, snapNum ) );
} else if ( reliableType == RELIABLE_RESOURCE_ACK ) {
} else if ( reliableType == RELIABLE_UPDATE_MATCH_PARMS ) {
VERIFY_CONNECTED_PEER( p, TYPE_GAME, RELIABLE_UPDATE_MATCH_PARMS );
int msgType = msg.ReadLong();
sessionCB->HandlePeerMatchParamUpdate( p, msgType );
} else if ( reliableType == RELIABLE_MATCHFINISHED ) {
VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_MATCHFINISHED );
sessionCB->ClearMigrationState();
} else if ( reliableType == RELIABLE_ENDMATCH ) {
VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_ENDMATCH );
sessionCB->EndMatchInternal();
} else if ( reliableType == RELIABLE_ENDMATCH_PREMATURE ) {
VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_ENDMATCH_PREMATURE );
sessionCB->EndMatchInternal( true );
} else if ( reliableType == RELIABLE_START_MATCH_GAME_LOBBY_HOST ) {
// This message should be from the host of the game lobby, telling us (as the host of the GameStateLobby) to start loading
VERIFY_CONNECTED_PEER( p, TYPE_GAME_STATE, RELIABLE_START_MATCH_GAME_LOBBY_HOST );
if ( session->GetState() >= idSession::LOADING ) {
NET_VERBOSE_PRINT( "NET: RELIABLE_START_MATCH_GAME_LOBBY_HOST already loading\n" );
return;
}
// Read match parms, and start loading
parms.Read( msg );
// Send these new match parms to currently connected peers
SendMatchParmsToPeers();
startLoadingFromHost = true; // Hijack this flag
} else if ( reliableType == RELIABLE_ARBITRATE ) {
VERIFY_CONNECTED_PEER( p, TYPE_GAME, RELIABLE_ARBITRATE );
// Host telling us to arbitrate
// Set a flag to do this later, since the lobby may not be in a state where it can fulfil the request at the moment
respondToArbitrate = true;
} else if ( reliableType == RELIABLE_ARBITRATE_OK ) {
VERIFY_CONNECTED_PEER( p, TYPE_GAME, RELIABLE_ARBITRATE_OK );
NET_VERBOSE_PRINT( "NET: Got an arbitration ok from %d\n", p );
everyoneArbitrated = true;
for ( int i = 0; i < GetNumLobbyUsers(); i++ ) {
lobbyUser_t * user = GetLobbyUser( i );
if ( !verify( user != NULL ) ) {
continue;
}
if ( user->peerIndex == p ) {
user->arbitrationAcked = true;
} else if ( !user->arbitrationAcked ) {
everyoneArbitrated = false;
}
}
if ( everyoneArbitrated ) {
NET_VERBOSE_PRINT( "NET: Everyone says they registered for arbitration, verifying\n" );
lobbyBackend->Arbitrate();
//sessionCB->EveryoneArbitrated();
return;
}
} else if ( reliableType == RELIABLE_POST_STATS ) {
VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_POST_STATS );
sessionCB->RecvLeaderboardStats( msg );
} else if ( reliableType == RELIABLE_SESSION_USER_MODIFIED ) {
VERIFY_CONNECTED_PEER( p, lobbyType, RELIABLE_SESSION_USER_MODIFIED );
UpdateSessionUserOnPeers( msg );
} else if ( reliableType == RELIABLE_UPDATE_SESSION_USER ) {
VERIFY_FROM_HOST( p, lobbyType, RELIABLE_UPDATE_SESSION_USER );
HandleUpdateSessionUser( msg );
} else if ( reliableType == RELIABLE_CONNECT_AND_MOVE_TO_LOBBY ) {
VERIFY_FROM_HOST( p, lobbyType, RELIABLE_CONNECT_AND_MOVE_TO_LOBBY );
NET_VERBOSE_PRINT( "NET: RELIABLE_CONNECT_AND_MOVE_TO_LOBBY\n" );
if ( IsHost() ) {
idLib::Printf( "RELIABLE_CONNECT_AND_MOVE_TO_LOBBY: We are the host.\n" );
return;
}
// Get connection info
lobbyConnectInfo_t connectInfo;
connectInfo.ReadFromMsg( msg );
const lobbyType_t destLobbyType = (lobbyType_t)msg.ReadByte();
const bool waitForMembers = msg.ReadBool();
assert( destLobbyType > lobbyType ); // Make sure this is a proper transition (i.e. TYPE_PARTY moves to TYPE_GAME, TYPE_GAME moves to TYPE_GAME_STATE)
sessionCB->ConnectAndMoveToLobby( destLobbyType, connectInfo, waitForMembers );
} else if ( reliableType == RELIABLE_PARTY_CONNECT_OK ) {
VERIFY_FROM_HOST( p, TYPE_PARTY, RELIABLE_PARTY_CONNECT_OK );
if ( !sessionCB->GetGameLobby().waitForPartyOk ) {
idLib::Printf( "RELIABLE_PARTY_CONNECT_OK: Wasn't waiting for ok.\n" );
}
sessionCB->GetGameLobby().waitForPartyOk = false;
} else if ( reliableType == RELIABLE_PARTY_LEAVE_GAME_LOBBY ) {
VERIFY_FROM_HOST( p, TYPE_PARTY, RELIABLE_PARTY_LEAVE_GAME_LOBBY );
NET_VERBOSE_PRINT( "NET: RELIABLE_PARTY_LEAVE_GAME_LOBBY\n" );
if ( sessionCB->GetState() != idSession::GAME_LOBBY ) {
idLib::Printf( "RELIABLE_PARTY_LEAVE_GAME_LOBBY: Not in a game lobby, ignoring.\n" );
return;
}
if ( IsHost() ) {
idLib::Printf( "RELIABLE_PARTY_LEAVE_GAME_LOBBY: Host of party, ignoring.\n" );
return;
}
sessionCB->LeaveGameLobby();
} else if ( IsReliablePlayerToPlayerType( reliableType ) ) {
HandleReliablePlayerToPlayerMsg( p, msg, reliableType );
} else if ( reliableType == RELIABLE_PING ) {
HandleReliablePing( p, msg );
} else if ( reliableType == RELIABLE_PING_VALUES ) {
HandlePingValues( msg );
} else if ( reliableType == RELIABLE_BANDWIDTH_VALUES ) {
HandleBandwidhTestValue( p, msg );
} else if ( reliableType == RELIABLE_MIGRATION_GAME_DATA ) {
HandleMigrationGameData( msg );
} else if ( reliableType >= RELIABLE_GAME_DATA ) {
VERIFY_CONNECTED_PEER( p, lobbyType, RELIABLE_GAME_DATA );
common->NetReceiveReliable( p, reliableType - RELIABLE_GAME_DATA, msg );
} else if ( reliableType == RELIABLE_DUMMY_MSG ) {
// Ignore dummy msg's
NET_VERBOSE_PRINT( "NET: ignoring dummy msg from %s\n", peer.address.ToString() );
} else {
NET_VERBOSE_PRINT( "NET: Unknown reliable packet type %d from %s\n", reliableType, peer.address.ToString() );
}
}
/*
========================
idLobby::GetTotalOutgoingRate
========================
*/
int idLobby::GetTotalOutgoingRate() {
int totalSendRate = 0;
for ( int p = 0; p < peers.Num(); p++ ) {
const peer_t & peer = peers[p];
if ( !peer.IsConnected() ) {
continue;
}
const idPacketProcessor & proc = *peer.packetProc;
totalSendRate += proc.GetOutgoingRateBytes();
}
return totalSendRate;
}
/*
========================
idLobby::DrawDebugNetworkHUD
========================
*/
extern idCVar net_forceUpstream;
void idLobby::DrawDebugNetworkHUD() const {
int totalSendRate = 0;
int totalRecvRate = 0;
float totalSentMB = 0.0f;
float totalRecvMB = 0.0f;
const float Y_OFFSET = 20.0f;
const float X_OFFSET = 20.0f;
const float Y_SPACING = 15.0f;
float curY = Y_OFFSET;
int numLines = ( net_forceUpstream.GetFloat() != 0.0f ? 6: 5 );
renderSystem->DrawFilled( idVec4( 0.0f, 0.0f, 0.0f, 0.7f ), X_OFFSET - 10.0f, curY - 10.0f, 1550, ( peers.Num() + numLines ) * Y_SPACING + 20.0f );
renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "# Peer | Sent kB/s | Recv kB/s | Sent MB | Recv MB | Ping | L | % | R.NM | R.SZ | R.AK | T", colorGreen, false );
curY += Y_SPACING;
renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------------------------------------------------------------------------", colorGreen, false );
curY += Y_SPACING;
for ( int p = 0; p < peers.Num(); p++ ) {
const peer_t & peer = peers[p];
if ( !peer.IsConnected() ) {
continue;
}
const idPacketProcessor & proc = *peer.packetProc;
totalSendRate += proc.GetOutgoingRateBytes();
totalRecvRate += proc.GetIncomingRateBytes();
float sentKps = (float)proc.GetOutgoingRateBytes() / 1024.0f;
float recvKps = (float)proc.GetIncomingRateBytes() / 1024.0f;
float sentMB = (float)proc.GetOutgoingBytes() / ( 1024.0f * 1024.0f );
float recvMB = (float)proc.GetIncomingBytes() / ( 1024.0f * 1024.0f );
totalSentMB += sentMB;
totalRecvMB += recvMB;
idVec4 color = sentKps > 20.0f ? colorRed : colorGreen;
int resourcePercent = 0;
idStr name = peer.address.ToString();
name += lobbyType == TYPE_PARTY ? "(P": "(G";
name += host == p ? ":H)" : ":C)";
renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "%i %22s | %2.02f kB/s | %2.02f kB/s | %2.02f MB | %2.02f MB |%4i ms | %i | %i%% | %i | %i | %i | %2.2f / %2.2f / %i", p, name.c_str(), sentKps, recvKps, sentMB, recvMB, peer.lastPingRtt, peer.loaded, resourcePercent, peer.packetProc->NumQueuedReliables(), peer.packetProc->GetReliableDataSize(), peer.packetProc->NeedToSendReliableAck(), peer.snapHz, peer.maxSnapBps, peer.failedPingRecoveries ), color, false );
curY += Y_SPACING;
}
renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------------------------------------------------------------------------", colorGreen, false );
curY += Y_SPACING;
float totalSentKps = (float)totalSendRate / 1024.0f;
float totalRecvKps = (float)totalRecvRate / 1024.0f;
idVec4 color = totalSentKps > 100.0f ? colorRed : colorGreen;
renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "# %20s | %2.02f KB/s | %2.02f KB/s | %2.02f MB | %2.02f MB", "", totalSentKps, totalRecvKps, totalSentMB, totalRecvMB ), color, false );
curY += Y_SPACING;
if ( net_forceUpstream.GetFloat() != 0.0f ) {
float upstreamDropRate = session->GetUpstreamDropRate();
float upstreamQueuedRate = session->GetUpstreamQueueRate();
int queuedBytes = session->GetQueuedBytes();
renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Queued: %d | Dropping: %2.02f kB/s Queue: %2.02f kB/s -> Effective %2.02f kB/s", queuedBytes, upstreamDropRate / 1024.0f, upstreamQueuedRate / 1024.0f, totalSentKps - ( upstreamDropRate / 1024.0f ) + ( upstreamQueuedRate / 1024.0f ) ), color, false );
}
}
/*
========================
idLobby::DrawDebugNetworkHUD2
========================
*/
void idLobby::DrawDebugNetworkHUD2() const {
int totalSendRate = 0;
int totalRecvRate = 0;
const float Y_OFFSET = 20.0f;
const float X_OFFSET = 20.0f;
const float Y_SPACING = 15.0f;
float curY = Y_OFFSET;
renderSystem->DrawFilled( idVec4( 0.0f, 0.0f, 0.0f, 0.7f ), X_OFFSET - 10.0f, curY - 10.0f, 550, (peers.Num() + 4) * Y_SPACING + 20.0f );
const char* stateName = session->GetStateString();
renderSystem->DrawFilled( idVec4( 1.0f, 1.0f, 1.0f, 0.7f ), X_OFFSET - 10.0f, curY - 10.0f, 550, (peers.Num() + 5) * Y_SPACING + 20.0f );
renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), va("State: %s. Local time: %d", stateName, Sys_Milliseconds() ), colorGreen, false );
curY += Y_SPACING;
renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "Peer | Sent kB/s | Recv kB/s | L | R | Resources", colorGreen, false );
curY += Y_SPACING;
renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------", colorGreen, false );
curY += Y_SPACING;
for ( int p = 0; p < peers.Num(); p++ ) {
if ( !peers[ p ].IsConnected() ) {
continue;
}
idPacketProcessor & proc = *peers[ p ].packetProc;
totalSendRate += proc.GetOutgoingRate2();
totalRecvRate += proc.GetIncomingRate2();
float sentKps = ( float )proc.GetOutgoingRate2() / 1024.0f;
float recvKps = ( float )proc.GetIncomingRate2() / 1024.0f;
// should probably complement that with a bandwidth reading
// right now I am mostly concerned about fragmentation and the latency spikes it will cause
idVec4 color = proc.TickFragmentAccumulator() ? colorRed : colorGreen;
int rLoaded = peers[ p ].numResources;
int rTotal = 0;
// show the names of the clients connected to the server. Also make sure it looks reasonably good.
idStr peerName;
if ( IsHost() ) {
peerName = GetPeerName( p );
int MAX_PEERNAME_LENGTH = 10;
int nameLength = peerName.Length();
if ( nameLength > MAX_PEERNAME_LENGTH ) {
peerName = peerName.Left( MAX_PEERNAME_LENGTH );
} else if ( nameLength < MAX_PEERNAME_LENGTH ) {
idStr filler;
filler.Fill( ' ', MAX_PEERNAME_LENGTH );
peerName += filler.Left( MAX_PEERNAME_LENGTH - nameLength );
}
} else {
peerName = "Local ";
}
renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "%i - %s | %2.02f kB/s | %2.02f kB/s | %i | %i | %d/%d", p, peerName.c_str(), sentKps, recvKps, peers[p].loaded, peers[p].address.UsingRelay(), rLoaded, rTotal ), color, false );
curY += Y_SPACING;
}
renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------", colorGreen, false );
curY += Y_SPACING;
float totalSentKps = (float)totalSendRate / 1024.0f;
float totalRecvKps = (float)totalRecvRate / 1024.0f;
renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Total | %2.02f KB/s | %2.02f KB/s", totalSentKps, totalRecvKps ), colorGreen, false );
}
/*
========================
idLobby::DrawDebugNetworkHUD_ServerSnapshotMetrics
========================
*/
idCVar net_debughud3_bps_max( "net_debughud3_bps_max", "5120.0f", CVAR_FLOAT, "Highest factor of server base snapRate that a client can be throttled" );
void idLobby::DrawDebugNetworkHUD_ServerSnapshotMetrics( bool draw ) {
const float Y_OFFSET = 20.0f;
const float X_OFFSET = 20.0f;
const float Y_SPACING = 15.0f;
idVec4 color = colorWhite;
float curY = Y_OFFSET;
if ( !draw ) {
for ( int p=0; p < peers.Num(); p++ ) {
for ( int i=0; i < peers[p].debugGraphs.Num(); i++ ) {
if ( peers[p].debugGraphs[i] != NULL ) {
peers[p].debugGraphs[i]->Enable( false );
} else {
return;
}
}
}
return;
}
static int lastTime = 0;
int time = Sys_Milliseconds();
for ( int p = 0; p < peers.Num(); p++ ) {
peer_t & peer = peers[p];
if ( !peer.IsConnected() ) {
continue;
}
idPacketProcessor * packetProc = peer.packetProc;
idSnapshotProcessor * snapProc = peer.snapProc;
if ( !verify( packetProc != NULL && snapProc != NULL ) ) {
continue;
}
int snapSeq = snapProc->GetSnapSequence();
int snapBase = snapProc->GetBaseSequence();
int deltaSeq = snapSeq - snapBase;
bool throttled = peer.throttledSnapRate > common->GetSnapRate();
int numLines = net_forceUpstream.GetBool() ? 5 : 4;
const int width = renderSystem->GetWidth()/2.0f - (X_OFFSET * 2);
enum netDebugGraphs_t {
GRAPH_SNAPSENT,
GRAPH_OUTGOING,
GRAPH_INCOMINGREPORTED,
GRAPH_MAX
};
peer.debugGraphs.SetNum( GRAPH_MAX, NULL );
for ( int i=0; i < GRAPH_MAX; i++ ) {
// Initialize graphs
if ( peer.debugGraphs[i] == NULL ) {
peer.debugGraphs[i] = console->CreateGraph( 500 );
if ( !verify( peer.debugGraphs[i] != NULL ) ) {
continue;
}
peer.debugGraphs[i]->SetPosition( X_OFFSET - 10.0f + width, curY - 10.0f, width , Y_SPACING * numLines );
}
peer.debugGraphs[i]->Enable( true );
}
renderSystem->DrawFilled( idVec4( 0.0f, 0.0f, 0.0f, 0.7f ), X_OFFSET - 10.0f, curY - 10.0f, width, ( Y_SPACING * numLines ) + 20.0f );
renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Peer %d - %s RTT %d %sPeerSnapRate: %d %s", p, GetPeerName( p ), peer.lastPingRtt, throttled ? "^1" : "^2", peer.throttledSnapRate/1000, throttled ? "^1Throttled" : "" ), color, false );
curY += Y_SPACING;
renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "SnapSeq %d BaseSeq %d Delta %d Queue %d", snapSeq, snapBase, deltaSeq, snapProc->GetSnapQueueSize() ), color, false );
curY += Y_SPACING;
renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Reliables: %d / %d bytes Reliable Ack: %d", packetProc->NumQueuedReliables(), packetProc->GetReliableDataSize(), packetProc->NeedToSendReliableAck() ), color, false );
curY += Y_SPACING;
renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Outgoing %.2f kB/s Reported %.2f kB/s Throttle: %.2f", peer.packetProc->GetOutgoingRateBytes() / 1024.0f, peers[p].receivedBps / 1024.0f, peer.receivedThrottle ), color, false );
curY += Y_SPACING;
if ( net_forceUpstream.GetFloat() != 0.0f ) {
float upstreamDropRate = session->GetUpstreamDropRate();
float upstreamQueuedRate = session->GetUpstreamQueueRate();
int queuedBytes = session->GetQueuedBytes();
renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Queued: %d | Dropping: %2.02f kB/s Queue: %2.02f kB/s ", queuedBytes, upstreamDropRate / 1024.0f, upstreamQueuedRate / 1024.0f ), color, false );
}
curY += Y_SPACING;
if ( peer.debugGraphs[GRAPH_SNAPSENT] != NULL ) {
if ( peer.lastSnapTime > lastTime ) {
peer.debugGraphs[GRAPH_SNAPSENT]->SetValue(-1, 1.0f, colorBlue );
} else {
peer.debugGraphs[GRAPH_SNAPSENT]->SetValue(-1, 0.0f, colorBlue );
}
}
if ( peer.debugGraphs[GRAPH_OUTGOING] != NULL ) {
idVec4 bgColor( vec4_zero );
peer.debugGraphs[GRAPH_OUTGOING]->SetBackgroundColor( bgColor );
idVec4 lineColor = colorLtGrey;
lineColor.w = 0.5f;
float outgoingRate = peer.sentBpsHistory[ peer.receivedBpsIndex % MAX_BPS_HISTORY ];
// peer.packetProc->GetOutgoingRateBytes()
peer.debugGraphs[GRAPH_OUTGOING]->SetValue(-1, idMath::ClampFloat( 0.0f, 1.0f, outgoingRate / net_debughud3_bps_max.GetFloat() ), lineColor );
}
if ( peer.debugGraphs[GRAPH_INCOMINGREPORTED] != NULL ) {
idVec4 lineColor = colorYellow;
extern idCVar net_peer_throttle_bps_peer_threshold_pct;
extern idCVar net_peer_throttle_bps_host_threshold;
if ( peer.packetProc->GetOutgoingRateBytes() > net_peer_throttle_bps_host_threshold.GetFloat() ) {
float pct = peer.packetProc->GetOutgoingRateBytes() > 0.0f ? peer.receivedBps / peer.packetProc->GetOutgoingRateBytes() : 0.0f;
if ( pct < net_peer_throttle_bps_peer_threshold_pct.GetFloat() ) {
lineColor = colorRed;
} else {
lineColor = colorGreen;
}
}
idVec4 bgColor( vec4_zero );
peer.debugGraphs[GRAPH_INCOMINGREPORTED]->SetBackgroundColor( bgColor );
peer.debugGraphs[GRAPH_INCOMINGREPORTED]->SetFillMode( idDebugGraph::GRAPH_LINE );
peer.debugGraphs[GRAPH_INCOMINGREPORTED]->SetValue(-1, idMath::ClampFloat( 0.0f, 1.0f, peer.receivedBps / net_debughud3_bps_max.GetFloat() ), lineColor );
}
// Skip down
curY += ( Y_SPACING * 2.0f);
}
lastTime = time;
}
/*
========================
idLobby::CheckHeartBeats
========================
*/
void idLobby::CheckHeartBeats() {
// Disconnect peers that haven't responded within net_peerTimeoutInSeconds
int time = Sys_Milliseconds();
int timeoutInMs = session->GetTitleStorageInt( "net_peerTimeoutInSeconds", net_peerTimeoutInSeconds.GetInteger() ) * 1000;
if ( sessionCB->GetState() < idSession::LOADING && migrationInfo.state == MIGRATE_NONE ) {
// Use shorter timeout in lobby (TCR)
timeoutInMs = session->GetTitleStorageInt( "net_peerTimeoutInSeconds_Lobby", net_peerTimeoutInSeconds_Lobby.GetInteger() ) * 1000;
}
if ( timeoutInMs > 0 ) {
for ( int p = 0; p < peers.Num(); p++ ) {
if ( peers[p].IsConnected() ) {
bool peerTimeout = false;
if( time - peers[p].lastHeartBeat > timeoutInMs ) {
peerTimeout = true;
}
// if reliable queue is almost full, disconnect the peer.
// (this seems reasonable since the reliable queue is set to 64 currently. In practice we should never
// have more than 3 or 4 queued)
if ( peers[ p ].packetProc->NumQueuedReliables() > idPacketProcessor::MAX_RELIABLE_QUEUE - 1 ) {
peerTimeout = true;
}
if( peerTimeout ) {
// Disconnect the peer from any sessions we are a host of
if ( IsHost() ) {
idLib::Printf("Peer %i timed out for %s session @ %d (lastHeartBeat %d)\n", p, GetLobbyName(), time, peers[p].lastHeartBeat );
DisconnectPeerFromSession( p );
}
// Handle peers not receiving a heartbeat from the host in awhile
if ( IsPeer() ) {
if ( migrationInfo.state != MIGRATE_PICKING_HOST ) {
idLib::Printf("Host timed out for %s session\n", GetLobbyName() );
// Pick a host for this session
PickNewHost();
}
}
}
}
}
}
if ( IsHost() && lobbyType == GetActingGameStateLobbyType() ) {
for ( int p = 0; p < peers.Num(); p++ ) {
if ( !peers[p].IsConnected() ) {
continue;
}
CheckPeerThrottle( p );
}
}
}
/*
========================
idLobby::CheckHeartBeats
========================
*/
bool idLobby::IsLosingConnectionToHost() const {
if ( !verify( IsPeer() && host >= 0 && host < peers.Num() ) ) {
return false;
}
if ( !peers[ host ].IsConnected() ) {
return true;
}
int time = Sys_Milliseconds();
int timeoutInMs = session->GetTitleStorageInt( "net_peerTimeoutInSeconds", net_peerTimeoutInSeconds.GetInteger() ) * 1000;
// return true if heartbeat > half the timeout length
if ( timeoutInMs > 0 && time - peers[ host ].lastHeartBeat > timeoutInMs / 2 ) {
return true;
}
// return true if reliable queue is more than half full
// (this seems reasonable since the reliable queue is set to 64 currently. In practice we should never
// have more than 3 or 4 queued)
if ( peers[ host ].packetProc->NumQueuedReliables() > idPacketProcessor::MAX_RELIABLE_QUEUE / 2 ) {
return true;
}
return false;
}
/*
========================
idLobby::IsMigratedStatsGame
========================
*/
bool idLobby::IsMigratedStatsGame() const {
if ( !IsLobbyActive() ) {
return false;
}
if ( lobbyType != TYPE_GAME ) {
return false; // Only game session migrates games stats
}
if ( !MatchTypeHasStats( parms.matchFlags ) ) {
return false; // Only stats games migrate stats
}
if ( !MatchTypeIsRanked( parms.matchFlags ) ) {
return false; // Only ranked games should migrate stats into new game
}
return migrationInfo.persistUntilGameEndsData.wasMigratedGame && migrationInfo.persistUntilGameEndsData.hasGameData;
}
/*
========================
idLobby::ShouldRelaunchMigrationGame
returns true if we are hosting a migrated game and we had valid migration data
========================
*/
bool idLobby::ShouldRelaunchMigrationGame() const {
if ( IsMigrating() ) {
return false; // Don't relaunch until all clients have reconnected
}
if ( !IsMigratedStatsGame() ) {
return false; // If we are not migrating stats, we don't want to relaunch a new game
}
if ( !migrationInfo.persistUntilGameEndsData.wasMigratedHost ) {
return false; // Only relaunch if we are the host
}
if ( migrationInfo.persistUntilGameEndsData.hasRelaunchedMigratedGame ) {
return false; // We already relaunched this game
}
return true;
}
/*
========================
idLobby::ShouldShowMigratingDialog
========================
*/
bool idLobby::ShouldShowMigratingDialog() const {
if ( IsMigrating() ) {
return true; // If we are in the process of truly migrating, then definitely return true
}
if ( sessionCB->GetState() == idSession::INGAME ) {
return false;
}
// We're either waiting on the server (which could be us) to relaunch, so show the dialog
return IsMigratedStatsGame() && sessionCB->GetState() != idSession::INGAME;
}
/*
========================
idLobby::IsMigrating
========================
*/
bool idLobby::IsMigrating() const {
return migrationInfo.state != idLobby::MIGRATE_NONE;
}
/*
========================
idLobby::PingPeers
Host only.
========================
*/
void idLobby::PingPeers() {
if ( !verify( IsHost() )) {
return;
}
const int now = Sys_Milliseconds();
pktPing_t packet;
memset( &packet, 0, sizeof( packet ) ); // We're gonna memset like it's 1999.
packet.timestamp = now;
byte packetCopy[ sizeof( packet ) ];
idBitMsg msg( packetCopy, sizeof( packetCopy ) );
msg.WriteLong( packet.timestamp );
for ( int i = 0; i < peers.Num(); ++i ) {
peer_t & peer = peers[ i ];
if ( !peer.IsConnected() ) {
continue;
}
if ( peer.nextPing <= now ) {
peer.nextPing = now + PING_INTERVAL_MS;
QueueReliableMessage( i, RELIABLE_PING, msg.GetReadData(), msg.GetSize() );
}
}
}
/*
========================
idLobby::ThrottlePeerSnapRate
========================
*/
void idLobby::ThrottlePeerSnapRate( int p ) {
if ( !verify( IsHost() ) || !verify( p >= 0 ) ) {
return;
}
peers[p].throttledSnapRate = common->GetSnapRate() * 2;
idLib::Printf( "^1Throttling peer %d %s!\n", p, GetPeerName(p) );
idLib::Printf( " New snaprate: %d\n", peers[p].throttledSnapRate / 1000 );
}
/*
========================
idLobby::SaturatePeers
========================
*/
void idLobby::BeginBandwidthTest() {
if ( !verify( IsHost() ) ) {
idLib::Warning("Bandwidth test should only be done on host");
return;
}
if ( bandwidthChallengeStartTime > 0 ) {
idLib::Warning("Already started bandwidth test");
return;
}
int time = Sys_Milliseconds();
bandwidthChallengeStartTime = time;
bandwidthChallengeEndTime = 0;
bandwidthChallengeFinished = false;
bandwidthChallengeNumGoodSeq = 0;
for ( int p = 0; p < peers.Num(); ++p ) {
if ( !peers[ p ].IsConnected() ) {
continue;
}
if ( !verify( peers[ p ].packetProc != NULL ) ) {
continue;
}
peers[ p ].bandwidthSequenceNum = 0;
peers[ p ].bandwidthChallengeStartSendTime = 0;
peers[ p ].bandwidthChallengeResults = false;
peers[ p ].bandwidthChallengeSendComplete = false;
peers[ p ].bandwidthTestBytes = peers[ p ].packetProc->GetOutgoingBytes(); // cache this off so we can see the difference when we are done
}
}
/*
========================
idLobby::SaturatePeers
========================
*/
bool idLobby::BandwidthTestStarted() {
return bandwidthChallengeStartTime != 0;
}
/*
========================
idLobby::ServerUpdateBandwidthTest
========================
*/
void idLobby::ServerUpdateBandwidthTest() {
if ( bandwidthChallengeStartTime <= 0 ) {
// Not doing a test
return;
}
if ( !verify( IsHost() )) {
return;
}
int time = Sys_Milliseconds();
if ( bandwidthChallengeFinished ) {
// test is over
return;
}
idRandom random;
random.SetSeed( time );
bool sentAll = true;
bool recAll = true;
for ( int i = 0; i < peers.Num(); ++i ) {
peer_t & peer = peers[ i ];
if ( !peer.IsConnected() ) {
continue;
}
if ( peer.bandwidthChallengeResults ) {
continue;
}
recAll = false;
if ( peer.bandwidthChallengeSendComplete ) {
continue;
}
sentAll = false;
if ( time - peer.bandwidthTestLastSendTime < session->GetTitleStorageInt( "net_bw_test_interval", net_bw_test_interval.GetInteger() ) ) {
continue;
}
if ( peer.packetProc->HasMoreFragments() ) {
continue;
}
if ( peer.bandwidthChallengeStartSendTime == 0 ) {
peer.bandwidthChallengeStartSendTime = time;
}
peer.bandwidthTestLastSendTime = time;
// Ok, send him a big packet
byte buffer[ idPacketProcessor::MAX_OOB_MSG_SIZE ]; // <---- NOTE - When calling ProcessOutgoingMsg with true for oob, we can't go over this size
idBitMsg msg( buffer, sizeof(buffer) );
msg.WriteLong( peer.bandwidthSequenceNum++ );
unsigned int randomSize = Min( (unsigned int)(sizeof(buffer) - 12), (unsigned int)session->GetTitleStorageInt( "net_bw_test_packetSizeBytes", net_bw_test_packetSizeBytes.GetInteger() ) );
msg.WriteLong( randomSize );
for ( unsigned int j=0; j < randomSize; j++ ) {
msg.WriteByte( random.RandomInt( 255 ) );
}
unsigned int checksum = MD5_BlockChecksum( &buffer[8], randomSize );
msg.WriteLong( checksum );
NET_VERBOSE_PRINT("Net: Sending bw challenge to peer %d time %d packet size %d\n", i, time, msg.GetSize() );
ProcessOutgoingMsg( i, buffer, msg.GetSize(), true, OOB_BANDWIDTH_TEST );
if ( session->GetTitleStorageInt( "net_bw_test_numPackets", net_bw_test_numPackets.GetInteger() ) > 0 && peer.bandwidthSequenceNum >= net_bw_test_numPackets.GetInteger() ) {
int sentBytes = peers[i].packetProc->GetOutgoingBytes() - peers[i].bandwidthTestBytes; // FIXME: this won't include the last sent msg
peers[i].bandwidthTestBytes = sentBytes; // this now means total bytes sent (we don't care about starting/ending total bytes sent to peer)
peers[i].bandwidthChallengeSendComplete = true;
NET_VERBOSE_PRINT("Sent enough packets to peer %d for bandwidth test in %dms. Total bytes: %d\n", i, time - bandwidthChallengeStartTime, sentBytes );
}
}
if ( sentAll ) {
if ( bandwidthChallengeEndTime == 0 ) {
// We finished sending all our packets, set the timeout time
bandwidthChallengeEndTime = time + session->GetTitleStorageInt( "net_bw_test_host_timeout", net_bw_test_host_timeout.GetInteger() );
NET_VERBOSE_PRINT("Net: finished sending BWC to peers. Waiting until %d to hear back\n", bandwidthChallengeEndTime );
}
}
if ( recAll ) {
bandwidthChallengeFinished = true;
bandwidthChallengeStartTime = 0;
} else if ( bandwidthChallengeEndTime != 0 && bandwidthChallengeEndTime < time ) {
// Timed out waiting for someone - throttle them and move on
NET_VERBOSE_PRINT("^2Net: timed out waiting for bandwidth challenge results \n");
for ( int i=0; i < peers.Num(); i++ ) {
NET_VERBOSE_PRINT(" Peer[%d] %s. SentAll: %d RecAll: %d\n", i, GetPeerName(i), peers[i].bandwidthChallengeSendComplete, peers[i].bandwidthChallengeResults );
if ( peers[i].bandwidthChallengeSendComplete && !peers[i].bandwidthChallengeResults ) {
ThrottlePeerSnapRate( i );
}
}
bandwidthChallengeFinished = true;
bandwidthChallengeStartTime = 0;
}
}
/*
========================
idLobby::UpdateBandwidthTest
This will be called on clients to check current state of bandwidth testing
========================
*/
void idLobby::ClientUpdateBandwidthTest() {
if ( !verify( !IsHost() ) || !verify( host >= 0 ) ) {
return;
}
if ( !peers[host].IsConnected() ) {
return;
}
if ( bandwidthChallengeStartTime <= 0 ) {
// Not doing a test
return;
}
int time = Sys_Milliseconds();
if ( bandwidthChallengeEndTime > time ) {
// Test is still going on
return;
}
// Its been long enough since we last received bw test msg. So lets send the results to the server
byte buffer[ idPacketProcessor::MAX_MSG_SIZE ];
idBitMsg msg( buffer, sizeof( buffer ) );
// Send total time it took to receive all the msgs
// (note, subtract net_bw_test_timeout to get 'last recevied bandwidth test packet')
// (^^ Note if the last packet is fragmented and we never get it, this is technically wrong!)
int totalTime = ( bandwidthChallengeEndTime - session->GetTitleStorageInt( "net_bw_test_timeout", net_bw_test_timeout.GetInteger() ) ) - bandwidthChallengeStartTime;
msg.WriteLong( totalTime );
// Send total number of complete, in order packets we got
msg.WriteLong( bandwidthChallengeNumGoodSeq );
// Send the overall average bandwidth in KBS
// Note that sending the number of good packets is not enough. If the packets going out are fragmented, and we
// drop fragments, the number of good sequences will be lower than the bandwidth we actually received.
int totalIncomingBytes = peers[host].packetProc->GetIncomingBytes() - peers[host].bandwidthTestBytes;
msg.WriteLong( totalIncomingBytes );
idLib::Printf("^3Finished Bandwidth test: \n");
idLib::Printf(" Total time: %d\n", totalTime );
idLib::Printf(" Num good packets: %d\n", bandwidthChallengeNumGoodSeq );
idLib::Printf(" Total received byes: %d\n\n", totalIncomingBytes );
bandwidthChallengeStartTime = 0;
bandwidthChallengeNumGoodSeq = 0;
QueueReliableMessage( host, RELIABLE_BANDWIDTH_VALUES, msg.GetReadData(), msg.GetSize() );
}
/*
========================
idLobby::HandleBandwidhTestValue
========================
*/
void idLobby::HandleBandwidhTestValue( int p, idBitMsg & msg ) {
if ( !IsHost() ) {
return;
}
idLib::Printf("Received RELIABLE_BANDWIDTH_CHECK %d\n", Sys_Milliseconds() );
if ( bandwidthChallengeStartTime < 0 || bandwidthChallengeFinished ) {
idLib::Warning("Received bandwidth test results too early from peer %d", p );
return;
}
int totalTime = msg.ReadLong();
int totalGoodSeq = msg.ReadLong();
int totalReceivedBytes = msg.ReadLong();
// This is the % of complete packets we received. If the packets used in the BWC are big enough to fragment, then pctPackets
// will be lower than bytesPct (we will have received a larger PCT of overall bandwidth than PCT of full packets received).
// Im not sure if this is a useful distinction or not, but it may be good to compare against for now.
float pctPackets = peers[p].bandwidthSequenceNum > 0 ? (float) totalGoodSeq / (float)peers[p].bandwidthSequenceNum : -1.0f;
// This is the % of total bytes sent/bytes received.
float bytesPct = peers[p].bandwidthTestBytes > 0 ? (float) totalReceivedBytes / (float)peers[p].bandwidthTestBytes : -1.0f;
// Calculate overall bandwidth for the test. That is, total amount received over time.
// We may want to expand this to also factor in an average instantaneous rate.
// For now we are mostly concerned with culling out poor performing clients
float peerKBS = -1.0f;
if ( verify( totalTime > 0 ) ) {
peerKBS = ( (float)totalReceivedBytes / 1024.0f ) / MS2SEC(totalTime);
}
int totalSendTime = peers[p].bandwidthTestLastSendTime - peers[p].bandwidthChallengeStartSendTime;
float outgoingKBS = -1.0f;
if ( verify( totalSendTime > 0 ) ) {
outgoingKBS = ( (float)peers[p].bandwidthTestBytes / 1024.0f ) / MS2SEC(totalSendTime);
}
float pctKBS = peerKBS / outgoingKBS;
bool failedRate = ( pctKBS < session->GetTitleStorageFloat( "net_bw_test_throttle_rate_pct", net_bw_test_throttle_rate_pct.GetFloat() ) );
bool failedByte = ( bytesPct < session->GetTitleStorageFloat( "net_bw_test_throttle_byte_pct", net_bw_test_throttle_byte_pct.GetFloat() ) );
bool failedSeq = ( pctPackets < session->GetTitleStorageFloat( "net_bw_test_throttle_seq_pct", net_bw_test_throttle_seq_pct.GetFloat() ) );
idLib::Printf("^3Finished Bandwidth test %s: \n", GetPeerName(p) );
idLib::Printf(" Total time: %dms\n", totalTime );
idLib::Printf(" %sNum good packets: %d (%.2f%)\n", ( failedSeq ? "^1" : "^2" ), totalGoodSeq, pctPackets );
idLib::Printf(" %sTotal received bytes: %d (%.2f%)\n", ( failedByte ? "^1" : "^2" ), totalReceivedBytes, bytesPct );
idLib::Printf(" %sEffective downstream: %.2fkbs (host: %.2fkbs) -> %.2f%\n\n", ( failedRate ? "^1" : "^2" ), peerKBS, outgoingKBS, pctKBS );
// If shittConnection(totalTime, totalGoodSeq/totalSeq, totalReceivedBytes/totalSentBytes)
// throttle this user:
// peers[p].throttledSnapRate = baseSnapRate * 2
if ( failedRate || failedByte || failedSeq ) {
ThrottlePeerSnapRate( p );
}
// See if we are finished
peers[p].bandwidthChallengeResults = true;
bandwidthChallengeFinished = true;
for ( int i=0; i < peers.Num(); i++ ) {
if ( peers[i].bandwidthChallengeSendComplete && !peers[i].bandwidthChallengeResults ) {
bandwidthChallengeFinished = false;
}
}
if ( bandwidthChallengeFinished ) {
bandwidthChallengeStartTime = 0;
}
}
/*
========================
idLobby::SendPingValues
Host only
Periodically send all peers' pings to all peers (for the UI).
========================
*/
void idLobby::SendPingValues() {
if ( !verify( IsHost() ) ) {
// paranoia
return;
}
const int now = Sys_Milliseconds();
if ( nextSendPingValuesTime > now ) {
return;
}
nextSendPingValuesTime = now + PING_INTERVAL_MS;
pktPingValues_t packet;
memset( &packet, 0, sizeof(packet) );
for( int i = 0; i < peers.Max(); ++i ) {
if ( i >= peers.Num() ) {
packet.pings[ i ] = -1;
} else if ( peers[ i ].IsConnected() ) {
packet.pings[ i ] = peers[ i ].lastPingRtt;
} else {
packet.pings[ i ] = -1;
}
}
byte packetCopy[ sizeof(packet) ];
idBitMsg msg( packetCopy, sizeof(packetCopy) );
for( int i = 0; i < peers.Max(); ++i ) {
msg.WriteShort( packet.pings[ i ] );
}
for ( int i = 0; i < peers.Num(); i++ ) {
if ( peers[ i ].IsConnected() ) {
QueueReliableMessage( i, RELIABLE_PING_VALUES, msg.GetReadData(), msg.GetSize() );
}
}
}
/*
========================
idLobby::PumpPings
Host: Periodically determine the round-trip time for a packet to all peers, and tell everyone
what everyone else's ping to the host is so they can display it in the UI.
Client: Indicate to the player when the server hasn't updated the ping values in too long.
This is usually going to preceed a connection timeout.
========================
*/
void idLobby::PumpPings() {
if ( IsHost() ) {
// Calculate ping to all peers
PingPeers();
// Send the hosts calculated ping values to each peer to everyone has updated ping times
SendPingValues();
// Do bandwidth testing
ServerUpdateBandwidthTest();
// Send Migration Data
SendMigrationGameData();
} else if ( IsPeer() ) {
ClientUpdateBandwidthTest();
if ( lastPingValuesRecvTime + PING_INTERVAL_MS + 1000 < Sys_Milliseconds() && migrationInfo.state == MIGRATE_NONE ) {
for ( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex ) {
lobbyUser_t * user = GetLobbyUser( userIndex );
if ( !verify( user != NULL ) ) {
continue;
}
user->pingMs = 999999;
}
}
}
}
/*
========================
idLobby::HandleReliablePing
========================
*/
void idLobby::HandleReliablePing( int p, idBitMsg & msg ) {
int c, b;
msg.SaveReadState( c, b );
pktPing_t ping;
memset( &ping, 0, sizeof( ping ) );
if ( !verify( sizeof( ping ) <= msg.GetRemainingData() ) ) {
NET_VERBOSE_PRINT( "NET: Ignoring ping from peer %i because packet was the wrong size\n", p );
return;
}
ping.timestamp = msg.ReadLong();
if ( IsHost() ) {
// we should probably verify here whether or not this ping was solicited or not
HandlePingReply( p, ping );
} else {
// this means the server is requesting a ping, so reply
msg.RestoreReadState( c, b );
QueueReliableMessage( p, RELIABLE_PING, msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() );
}
}
/*
========================
idLobby::HandlePingReply
========================
*/
void idLobby::HandlePingReply( int p, const pktPing_t & ping ) {
const int now = Sys_Milliseconds();
const int rtt = now - ping.timestamp;
peers[p].lastPingRtt = rtt;
for ( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex ) {
lobbyUser_t * u = GetLobbyUser( userIndex );
if ( u->peerIndex == p ) {
u->pingMs = rtt;
}
}
}
/*
========================
idLobby::HandlePingValues
========================
*/
void idLobby::HandlePingValues( idBitMsg & msg ) {
pktPingValues_t packet;
memset( &packet, 0, sizeof( packet ) );
for( int i = 0; i < peers.Max(); ++i ) {
packet.pings[ i ] = msg.ReadShort();
}
assert( IsPeer() );
lastPingValuesRecvTime = Sys_Milliseconds();
for ( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex ) {
lobbyUser_t * u = GetLobbyUser( userIndex );
if ( u->peerIndex != -1 && verify( u->peerIndex >= 0 && u->peerIndex < MAX_PEERS ) ) {
u->pingMs = packet.pings[ u->peerIndex ];
} else {
u->pingMs = 0;
}
}
// Stuff our ping in the hosts slot
if ( peerIndexOnHost != -1 && verify( peerIndexOnHost >= 0 && peerIndexOnHost < MAX_PEERS ) ) {
peers[host].lastPingRtt = packet.pings[ peerIndexOnHost ];
} else {
peers[host].lastPingRtt = 0;
}
}
/*
========================
idLobby::SendAnotherFragment
Other than connectionless sends, this should be the chokepoint for sending packets to peers.
========================
*/
bool idLobby::SendAnotherFragment( int p ) {
peer_t & peer = peers[p];
if ( !peer.IsConnected() ) { // Not connected to any mode (party or game), so no need to send
return false;
}
if ( !peer.packetProc->HasMoreFragments() ) {
return false; // No fragments to send for this peer
}
if ( !CanSendMoreData( p ) ) {
return false; // We need to throttle the sends so we don't saturate the connection
}
int time = Sys_Milliseconds();
if ( time - peer.lastFragmentSendTime < 2 ) {
NET_VERBOSE_PRINT("Too soon to send another packet. Delta: %d \n", ( time-peer.lastFragmentSendTime) );
return false; // Too soon to send another fragment
}
peer.lastFragmentSendTime = time;
bool sentFragment = false;
while ( true ) {
idBitMsg msg;
// We use the final packet size here because it has been processed, and no more headers will be added
byte buffer[ idPacketProcessor::MAX_FINAL_PACKET_SIZE ];
msg.InitWrite( buffer, sizeof( buffer ) );
if ( !peers[p].packetProc->GetSendFragment( time, peers[p].sessionID, msg ) ) {
break;
}
const bool useDirectPort = ( lobbyType == TYPE_GAME_STATE );
msg.BeginReading();
sessionCB->SendRawPacket( peers[p].address, msg.GetReadData(), msg.GetSize(), useDirectPort );
sentFragment = true;
break; // Comment this out to send all fragments in one burst
}
if ( peer.packetProc->HasMoreFragments() ) {
NET_VERBOSE_PRINT("More packets left after ::SendAnotherFragment\n");
}
return sentFragment;
}
/*
========================
idLobby::CanSendMoreData
========================
*/
bool idLobby::CanSendMoreData( int p ) {
if ( !verify( p >= 0 && p < peers.Num() ) ) {
NET_VERBOSE_PRINT( "NET: CanSendMoreData %i NO: not a peer\n", p );
return false;
}
peer_t & peer = peers[p];
if ( !peer.IsConnected() ) {
NET_VERBOSE_PRINT( "NET: CanSendMoreData %i NO: not connected\n", p );
return false;
}
return peer.packetProc->CanSendMoreData();
}
/*
========================
idLobby::ProcessOutgoingMsg
========================
*/
void idLobby::ProcessOutgoingMsg( int p, const void * data, int size, bool isOOB, int userData ) {
peer_t & peer = peers[p];
if ( peer.GetConnectionState() != CONNECTION_ESTABLISHED ) {
idLib::Printf( "peer.GetConnectionState() != CONNECTION_ESTABLISHED\n" );
return; // Peer not fully connected for this session type, return
}
if ( peer.packetProc->HasMoreFragments() ) {
idLib::Error( "FATAL: Attempt to process a packet while fragments still need to be sent.\n" ); // We can't handle this case
}
int currentTime = Sys_Milliseconds();
// if ( currentTime - peer.lastProcTime < 30 ) {
// idLib::Printf("ProcessOutgoingMsg called within %dms %s\n", (currentTime - peer.lastProcTime), GetLobbyName() );
// }
peer.lastProcTime = currentTime;
if ( !isOOB ) {
// Keep track of the last time an in-band packet was sent
// (used for things like knowing when reliables could have been last sent)
peer.lastInBandProcTime = peer.lastProcTime;
}
idBitMsg msg;
msg.InitRead( (byte*)data, size );
peer.packetProc->ProcessOutgoing( currentTime, msg, isOOB, userData );
}
/*
========================
idLobby::ResendReliables
========================
*/
void idLobby::ResendReliables( int p ) {
peer_t & peer = peers[p];
if ( !peer.IsConnected() ) {
return;
}
if ( peer.packetProc->HasMoreFragments() ) {
return; // We can't send more data while fragments are still being sent out
}
if ( !CanSendMoreData( p ) ) {
return;
}
int time = Sys_Milliseconds();
const int DEFAULT_MIN_RESEND = 20; // Quicker resend while not in game to speed up resource transmission acks
const int DEFAULT_MIN_RESEND_INGAME = 100;
int resendWait = DEFAULT_MIN_RESEND_INGAME;
if ( sessionCB->GetState() == idSession::INGAME ) {
// setup some minimum waits and account for ping
resendWait = Max( DEFAULT_MIN_RESEND_INGAME, peer.lastPingRtt / 2 );
if ( lobbyType == TYPE_PARTY ) {
resendWait = Max( 500, resendWait ); // party session does not need fast frequency at all once in game
}
} else {
// don't trust the ping when still loading stuff
// need to resend fast to speed up transmission of network decls
resendWait = DEFAULT_MIN_RESEND;
}
if ( time - peer.lastInBandProcTime < resendWait ) {
// no need to resend reliables if they went out on an in-band packet recently
return;
}
if ( peer.packetProc->NumQueuedReliables() > 0 || peer.packetProc->NeedToSendReliableAck() ) {
//NET_VERBOSE_PRINT( "NET: ResendReliables %s\n", GetLobbyName() );
ProcessOutgoingMsg( p, NULL, 0, false, 0 ); // Force an empty unreliable msg so any reliables will get processed as well
}
}
/*
========================
idLobby::PumpPackets
========================
*/
void idLobby::PumpPackets() {
int newTime = Sys_Milliseconds();
for ( int p = 0; p < peers.Num(); p++ ) {
if ( peers[p].IsConnected() ) {
peers[p].packetProc->RefreshRates( newTime );
}
}
// Resend reliable msg's (do this before we send out the fragments)
for ( int p = 0; p < peers.Num(); p++ ) {
ResendReliables( p );
}
// If we haven't sent anything to our peers in a long time, make sure to send an empty packet (so our heartbeat gets updated) so we don't get disconnected
// NOTE - We used to only send these to the host, but the host needs to also send these to clients
for ( int p = 0; p < peers.Num(); p++ ) {
if ( !peers[p].IsConnected() || peers[p].packetProc->HasMoreFragments() ) {
continue;
}
if ( newTime - peers[p].lastProcTime > 1000 * PEER_HEARTBEAT_IN_SECONDS ) {
//NET_VERBOSE_PRINT( "NET: ProcessOutgoing Heartbeat %s\n", GetLobbyName() );
ProcessOutgoingMsg( p, NULL, 0, false, 0 );
}
}
// Send any unsent fragments for each peer (do this last)
for ( int p = 0; p < peers.Num(); p++ ) {
SendAnotherFragment( p );
}
}
/*
========================
idLobby::UpdateMatchParms
========================
*/
void idLobby::UpdateMatchParms( const idMatchParameters & p ) {
if ( !IsHost() ) {
return;
}
parms = p;
// Update lobbyBackend with parms
if ( lobbyBackend != NULL ) {
lobbyBackend->UpdateMatchParms( parms );
}
SendMatchParmsToPeers();
}
/*
========================
idLobby::GetHostUserName
========================
*/
const char * idLobby::GetHostUserName() const {
if ( !IsLobbyActive() ) {
return INVALID_LOBBY_USER_NAME;
}
return GetPeerName( -1 ); // This will just grab the first user with this peerIndex (which should be the host)
}
/*
========================
idLobby::SendReliable
========================
*/
void idLobby::SendReliable( int type, idBitMsg & msg, bool callReceiveReliable /*= true*/, peerMask_t sessionUserMask /*= MAX_UNSIGNED_TYPE( peerMask_t ) */ ) {
//assert( lobbyType == GetActingGameStateLobbyType() );
assert( type < 256 ); // QueueReliable only accepts a byte for message type
// the queuing below sends the whole message
// I don't know if whole message is a good thing or a bad thing, but if the passed message has been read from already, this is most likely not going to do what the caller expects
assert( msg.GetReadCount() + msg.GetReadBit() == 0 );
if ( callReceiveReliable ) {
// NOTE: this will put the msg's read status to fully read - which is why the assert check is above
common->NetReceiveReliable( -1, type, msg );
}
uint32 sentPeerMask = 0;
for ( int i = 0; i < GetNumLobbyUsers(); ++i ) {
lobbyUser_t * user = GetLobbyUser( i );
if ( user->peerIndex == -1 ) {
continue;
}
// We only care about sending these to peers in our party lobby
if ( user->IsDisconnected() ) {
continue;
}
// Don't sent to a user if they are in the exlusion session user mask
if ( sessionUserMask != 0 && ( sessionUserMask & ( BIT( i ) ) ) == 0 ) {
continue;
}
const int peerIndex = user->peerIndex;
if ( peerIndex >= peers.Num() ) {
continue;
}
peer_t & peer = peers[peerIndex];
if ( !peer.IsConnected() ) {
continue;
}
if ( ( sentPeerMask & ( 1 << user->peerIndex ) ) == 0 ) {
QueueReliableMessage( user->peerIndex, idLobby::RELIABLE_GAME_DATA + type, msg.GetReadData(), msg.GetSize() );
sentPeerMask |= 1 << user->peerIndex;
}
}
}
/*
========================
idLobby::SendReliableToLobbyUser
can only be used on the server. will take care of calling locally if addressed to player 0
========================
*/
void idLobby::SendReliableToLobbyUser( lobbyUserID_t lobbyUserID, int type, idBitMsg & msg ) {
assert( lobbyType == GetActingGameStateLobbyType() );
assert( type < 256 ); // QueueReliable only accepts a byte for message type
assert( IsHost() ); // This function should only be called in the server atm
const int peerIndex = PeerIndexFromLobbyUser( lobbyUserID );
if ( peerIndex >= 0 ) {
// will send the remainder of a message that was started reading through, but not handling a partial byte read
assert( msg.GetReadBit() == 0 );
QueueReliableMessage( peerIndex, idLobby::RELIABLE_GAME_DATA + type, msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() );
} else {
common->NetReceiveReliable( -1, type, msg );
}
}
/*
========================
idLobby::SendReliableToHost
will make sure to invoke locally if used on the server
========================
*/
void idLobby::SendReliableToHost( int type, idBitMsg & msg ) {
assert( lobbyType == GetActingGameStateLobbyType() );
if ( IsHost() ) {
common->NetReceiveReliable( -1, type, msg );
} else {
// will send the remainder of a message that was started reading through, but not handling a partial byte read
assert( msg.GetReadBit() == 0 );
QueueReliableMessage( host, idLobby::RELIABLE_GAME_DATA + type, msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() );
}
}
/*
================================================================================================
idLobby::reliablePlayerToPlayerHeader_t
================================================================================================
*/
/*
========================
idLobby::reliablePlayerToPlayerHeader_t::reliablePlayerToPlayerHeader_t
========================
*/
idLobby::reliablePlayerToPlayerHeader_t::reliablePlayerToPlayerHeader_t() : fromSessionUserIndex( -1 ), toSessionUserIndex( -1 ) {
}
/*
========================
idSessionLocal::reliablePlayerToPlayerHeader_t::Read
========================
*/
bool idLobby::reliablePlayerToPlayerHeader_t::Read( idLobby * lobby, idBitMsg & msg ) {
assert( lobby != NULL );
lobbyUserID_t lobbyUserIDFrom;
lobbyUserID_t lobbyUserIDTo;
lobbyUserIDFrom.ReadFromMsg( msg );
lobbyUserIDTo.ReadFromMsg( msg );
fromSessionUserIndex = lobby->GetLobbyUserIndexByID( lobbyUserIDFrom );
toSessionUserIndex = lobby->GetLobbyUserIndexByID( lobbyUserIDTo );
if ( !verify( lobby->GetLobbyUser( fromSessionUserIndex ) != NULL ) ) {
return false;
}
if ( !verify( lobby->GetLobbyUser( toSessionUserIndex ) != NULL ) ) {
return false;
}
return true;
}
/*
========================
idLobby::reliablePlayerToPlayerHeader_t::Write
========================
*/
bool idLobby::reliablePlayerToPlayerHeader_t::Write( idLobby * lobby, idBitMsg & msg ) {
if ( !verify( lobby->GetLobbyUser( fromSessionUserIndex ) != NULL ) ) {
return false;
}
if ( !verify( lobby->GetLobbyUser( toSessionUserIndex ) != NULL ) ) {
return false;
}
lobby->GetLobbyUser( fromSessionUserIndex )->lobbyUserID.WriteToMsg( msg );
lobby->GetLobbyUser( toSessionUserIndex )->lobbyUserID.WriteToMsg( msg );
return true;
}
/*
========================
idLobby::GetNumActiveLobbyUsers
========================
*/
int idLobby::GetNumActiveLobbyUsers() const {
int numActive = 0;
for ( int i = 0; i < GetNumLobbyUsers(); ++i ) {
if ( !GetLobbyUser( i )->IsDisconnected() ) {
numActive++;
}
}
return numActive;
}
/*
========================
idLobby::AllPeersInGame
========================
*/
bool idLobby::AllPeersInGame() const {
assert( lobbyType == GetActingGameStateLobbyType() ); // This function doesn't make sense on a party lobby currently
for ( int p = 0; p < peers.Num(); p++ ) {
if ( peers[p].IsConnected() && !peers[p].inGame ) {
return false;
}
}
return true;
}
/*
========================
idLobby::PeerIndexFromLobbyUser
========================
*/
int idLobby::PeerIndexFromLobbyUser( lobbyUserID_t lobbyUserID ) const {
const int lobbyUserIndex = GetLobbyUserIndexByID( lobbyUserID );
const lobbyUser_t * user = GetLobbyUser( lobbyUserIndex );
if ( user == NULL ) {
// This needs to be OK for bot support ( or else add bots at the session level )
return -1;
}
return user->peerIndex;
}
/*
========================
idLobby::GetPeerTimeSinceLastPacket
========================
*/
int idLobby::GetPeerTimeSinceLastPacket( int peerIndex ) const {
if ( peerIndex < 0 ) {
return 0;
}
return Sys_Milliseconds() - peers[peerIndex].lastHeartBeat;
}
/*
========================
idLobby::GetActingGameStateLobbyType
========================
*/
idLobby::lobbyType_t idLobby::GetActingGameStateLobbyType() const {
extern idCVar net_useGameStateLobby;
return ( net_useGameStateLobby.GetBool() ) ? TYPE_GAME_STATE : TYPE_GAME;
}
//========================================================================================================================
// idLobby::peer_t
//========================================================================================================================
/*
========================
idLobby::peer_t::GetConnectionState
========================
*/
idLobby::connectionState_t idLobby::peer_t::GetConnectionState() const {
return connectionState;
}