DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

sys_lobby.cpp (136561B)


      1 /*
      2 ===========================================================================
      3 
      4 Doom 3 BFG Edition GPL Source Code
      5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 
      6 
      7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").  
      8 
      9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
     10 it under the terms of the GNU General Public License as published by
     11 the Free Software Foundation, either version 3 of the License, or
     12 (at your option) any later version.
     13 
     14 Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
     15 but WITHOUT ANY WARRANTY; without even the implied warranty of
     16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     17 GNU General Public License for more details.
     18 
     19 You should have received a copy of the GNU General Public License
     20 along with Doom 3 BFG Edition Source Code.  If not, see <http://www.gnu.org/licenses/>.
     21 
     22 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.
     23 
     24 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.
     25 
     26 ===========================================================================
     27 */
     28 #pragma hdrstop
     29 #include "../idlib/precompiled.h"
     30 #include "sys_lobby.h"
     31 
     32 extern idCVar net_connectTimeoutInSeconds;
     33 extern idCVar net_headlessServer;
     34 
     35 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)" );
     36 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." );
     37 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." );
     38 
     39 // NOTE - The snapshot exchange does the bandwidth challenge
     40 idCVar net_bw_challenge_enable( "net_bw_challenge_enable", "0", CVAR_BOOL, "Enable pre game bandwidth challenge for throttling snap rate" ); 
     41 
     42 idCVar net_bw_test_interval( "net_bw_test_interval", "33", CVAR_INTEGER, "MS - how often to send packets in bandwidth test" );
     43 idCVar net_bw_test_numPackets( "net_bw_test_numPackets", "30", CVAR_INTEGER, "Number of bandwidth challenge packets to send" );
     44 idCVar net_bw_test_packetSizeBytes( "net_bw_test_packetSizeBytes", "1024", CVAR_INTEGER, "Size of each packet to send out" );
     45 idCVar net_bw_test_timeout( "net_bw_test_timeout", "500", CVAR_INTEGER, "MS after receiving a bw test packet that client will time out" );
     46 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" );
     47 
     48 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" );
     49 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" );
     50 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 %" );
     51 
     52 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" );
     53 
     54 idCVar net_skipGoodbye( "net_skipGoodbye", "0", CVAR_BOOL, "" ); 
     55 
     56 extern unsigned long NetGetVersionChecksum();
     57 
     58 /*
     59 ========================
     60 idLobby::idLobby
     61 ========================
     62 */
     63 idLobby::idLobby() {
     64 	lobbyType				= TYPE_INVALID;
     65 	sessionCB				= NULL;
     66 
     67 	localReadSS				= NULL;
     68 	objMemory				= NULL;
     69 	haveSubmittedSnaps		= false;
     70 
     71 	state					= STATE_IDLE;	
     72 	failedReason			= FAILED_UNKNOWN;
     73 
     74 	host					= -1;
     75 	peerIndexOnHost			= -1;
     76 	isHost					= false;
     77 	needToDisplayMigrateMsg	= false;
     78 	migrateMsgFlags			= 0;
     79 	
     80 	partyToken				= 0;		// will be initialized later
     81 	loaded					= false;
     82 	respondToArbitrate		= false;
     83 	waitForPartyOk			= false;
     84 	startLoadingFromHost	= false;
     85 	
     86 	nextSendPingValuesTime	= 0;
     87 	lastPingValuesRecvTime	= 0;
     88 
     89 	nextSendMigrationGameTime = 0;
     90 	nextSendMigrationGamePeer = 0;
     91 
     92 	bandwidthChallengeStartTime = 0;
     93 	bandwidthChallengeEndTime	= 0;
     94 	bandwidthChallengeFinished	= false;
     95 	bandwidthChallengeNumGoodSeq = 0;
     96 
     97 	lastSnapBspHistoryUpdateSequence = -1;
     98 
     99 	assert( userList.Max() == freeUsers.Max() );
    100 	assert( userList.Max() == userPool.Max() );
    101 
    102 	userPool.SetNum( userPool.Max() );
    103 
    104 	assert( freeUsers.Num() == 0 );
    105 	assert( freeUsers.Num() == 0 );
    106 
    107 	// Initialize free user list
    108 	for ( int i = 0; i < userPool.Num(); i++ ) {
    109 		freeUsers.Append( &userPool[i] );
    110 	}
    111 	
    112 	showHostLeftTheSession	= false;
    113 	connectIsFromInvite		= false;
    114 }
    115 
    116 /*
    117 ========================
    118 idLobby::Initialize
    119 ========================
    120 */
    121 void idLobby::Initialize( lobbyType_t sessionType_, idSessionCallbacks * callbacks ) {
    122 	assert( callbacks != NULL );
    123 
    124 	lobbyType = sessionType_;
    125 	sessionCB	= callbacks;
    126 
    127 	if ( lobbyType == GetActingGameStateLobbyType() ) {
    128 		// only needed in multiplayer mode
    129 		objMemory		= (uint8*)Mem_Alloc( SNAP_OBJ_JOB_MEMORY, TAG_NETWORKING );
    130 		lzwData			= (lzwCompressionData_t*)Mem_Alloc( sizeof( lzwCompressionData_t ), TAG_NETWORKING );
    131 	}
    132 }
    133 
    134 //===============================================================================
    135 //	** BEGIN PUBLIC INTERFACE ***
    136 //===============================================================================
    137 
    138 /*
    139 ========================
    140 idLobby::StartHosting
    141 ========================
    142 */
    143 void idLobby::StartHosting( const idMatchParameters & parms_ ) {
    144 	parms = parms_;
    145 
    146 	// Allow common to modify the parms
    147 	common->OnStartHosting( parms );
    148 
    149 	Shutdown();		// Make sure we're in a shutdown state before proceeding
    150 
    151 	assert( GetNumLobbyUsers() == 0 );
    152 	assert( lobbyBackend == NULL );
    153 
    154 	// Get the skill level of all the players that will eventually go into the lobby
    155 	StartCreating();
    156 }
    157 
    158 /*
    159 ========================
    160 idLobby::StartFinding
    161 ========================
    162 */
    163 void idLobby::StartFinding( const idMatchParameters & parms_ ) {
    164 	parms = parms_;
    165 
    166 	Shutdown();		// Make sure we're in a shutdown state before proceeding
    167 
    168 	assert( GetNumLobbyUsers() == 0 );
    169 	assert( lobbyBackend == NULL );
    170 
    171 	// Clear search results
    172 	searchResults.Clear();
    173 
    174 	lobbyBackend = sessionCB->FindLobbyBackend( parms, sessionCB->GetPartyLobby().GetNumLobbyUsers(), sessionCB->GetPartyLobby().GetAverageSessionLevel(), idLobbyBackend::TYPE_GAME );
    175 
    176 	SetState( STATE_SEARCHING );
    177 }
    178 
    179 /*
    180 ========================
    181 idLobby::Pump
    182 ========================
    183 */
    184 void idLobby::Pump() {
    185 	
    186 	// Check the heartbeat of all our peers, make sure we shouldn't disconnect from peers that haven't sent a heartbeat in awhile
    187 	CheckHeartBeats();
    188 
    189 	UpdateHostMigration();
    190 
    191 	UpdateLocalSessionUsers();
    192 
    193 	switch ( state ) {
    194 		case STATE_IDLE:					State_Idle();					break;
    195 		case STATE_CREATE_LOBBY_BACKEND:	State_Create_Lobby_Backend();	break;
    196 		case STATE_SEARCHING:				State_Searching();				break;
    197 		case STATE_OBTAINING_ADDRESS:		State_Obtaining_Address();		break;
    198 		case STATE_CONNECT_HELLO_WAIT:		State_Connect_Hello_Wait();		break;
    199 		case STATE_FINALIZE_CONNECT:		State_Finalize_Connect();		break;
    200 		case STATE_FAILED:													break;
    201 		default:	
    202 			idLib::Error( "idLobby::Pump:  Unknown state." );
    203 	}
    204 }
    205 
    206 /*
    207 ========================
    208 idLobby::ProcessSnapAckQueue
    209 ========================
    210 */
    211 void idLobby::ProcessSnapAckQueue() {
    212 	SCOPED_PROFILE_EVENT( "ProcessSnapAckQueue" );
    213 
    214 	const int SNAP_ACKS_TO_PROCESS_PER_FRAME = 1;
    215 
    216 	int numProcessed = 0;
    217 
    218 	while ( snapDeltaAckQueue.Num() > 0 && numProcessed < SNAP_ACKS_TO_PROCESS_PER_FRAME ) {
    219 		if ( ApplySnapshotDeltaInternal( snapDeltaAckQueue[0].p, snapDeltaAckQueue[0].snapshotNumber ) ) {
    220 			numProcessed++;
    221 		}
    222 		snapDeltaAckQueue.RemoveIndex( 0 );
    223 	}
    224 }
    225 
    226 /*
    227 ========================
    228 idLobby::Shutdown
    229 ========================
    230 */
    231 void idLobby::Shutdown( bool retainMigrationInfo, bool skipGoodbye ) {
    232 
    233 	// Cancel host migration if we were in the process of it and this is the session type that was migrating
    234 	if ( !retainMigrationInfo && migrationInfo.state != MIGRATE_NONE ) {
    235 		idLib::Printf( "Cancelling host migration on %s.\n", GetLobbyName() );
    236 		EndMigration();
    237 	}
    238 
    239 	failedReason = FAILED_UNKNOWN;
    240 
    241 	if ( lobbyBackend == NULL ) {
    242 		NET_VERBOSE_PRINT( "NET: ShutdownLobby (already shutdown) (%s)\n", GetLobbyName() );
    243 
    244 		// If we don't have this lobbyBackend type, we better be properly shutdown for this lobby
    245 		assert( GetNumLobbyUsers() == 0 );
    246 		assert( host == -1 );
    247 		assert( peerIndexOnHost == -1 );
    248 		assert( !isHost );
    249 		assert( lobbyType != GetActingGameStateLobbyType() || !loaded );
    250 		assert( lobbyType != GetActingGameStateLobbyType() || !respondToArbitrate );
    251 		assert( snapDeltaAckQueue.Num() == 0 );
    252 
    253 		// Make sure we don't have old peers connected to this lobby
    254 		for ( int p = 0; p < peers.Num(); p++ ) {
    255 			assert( peers[p].GetConnectionState() == CONNECTION_FREE );
    256 		}
    257 
    258 		state = STATE_IDLE;	
    259 
    260 		return;
    261 	}
    262 
    263 	NET_VERBOSE_PRINT( "NET: ShutdownLobby (%s)\n", GetLobbyName() );
    264 	
    265 	for ( int p = 0; p < peers.Num(); p++ ) {
    266 		if ( peers[p].GetConnectionState() != CONNECTION_FREE ) {
    267 			SetPeerConnectionState( p, CONNECTION_FREE, skipGoodbye );		// This will send goodbye's
    268 		}
    269 	}
    270 	
    271 	// Remove any users that weren't handled in ResetPeers
    272 	// (this will happen as a client, because we won't get the reliable msg from the server since we are severing the connection)
    273 	for ( int i = 0; i < GetNumLobbyUsers(); i++ ) {
    274 		lobbyUser_t * user = GetLobbyUser( i );
    275 		UnregisterUser( user );
    276 	}
    277 
    278 	FreeAllUsers();
    279 
    280 	host					= -1;
    281 	peerIndexOnHost			= -1;
    282 	isHost					= false;
    283 	needToDisplayMigrateMsg	= false;
    284 	migrationDlg			= GDM_INVALID;
    285 
    286 	partyToken				= 0;		// Reset our party token so we recompute
    287 	loaded					= false;
    288 	respondToArbitrate		= false;
    289 	waitForPartyOk			= false;
    290 	startLoadingFromHost	= false;
    291 
    292 	snapDeltaAckQueue.Clear();
    293 
    294 	// Shutdown the lobbyBackend
    295 	if ( !retainMigrationInfo ) {
    296 		sessionCB->DestroyLobbyBackend( lobbyBackend );
    297 		lobbyBackend = NULL;
    298 	}
    299 
    300 	state = STATE_IDLE;
    301 }
    302 
    303 /*
    304 ========================
    305 idLobby::HandlePacket
    306 ========================
    307 */
    308 void idLobby::HandlePacket( lobbyAddress_t & remoteAddress, idBitMsg fragMsg, idPacketProcessor::sessionId_t sessionID ) {
    309 	SCOPED_PROFILE_EVENT( "HandlePacket" );
    310 
    311 	// msg will hold a fully constructed msg using the packet processor
    312 	byte msgBuffer[ idPacketProcessor::MAX_MSG_SIZE ];
    313 
    314 	idBitMsg msg;
    315 	msg.InitWrite( msgBuffer, sizeof( msgBuffer ) );
    316 
    317 	int peerNum		= FindPeer( remoteAddress, sessionID );
    318 	int type		= idPacketProcessor::RETURN_TYPE_NONE;
    319 	int	userData	= 0;
    320 
    321 	if ( peerNum >= 0 ) {
    322 		if ( !peers[peerNum].IsActive() ) {
    323 			idLib::Printf( "NET: Received in-band packet from peer %s with no active connection.\n", remoteAddress.ToString() );
    324 			return;
    325 		}
    326 		type = peers[ peerNum ].packetProc->ProcessIncoming( Sys_Milliseconds(), peers[peerNum].sessionID, fragMsg, msg, userData, peerNum );
    327 	} else {
    328 		if ( !idPacketProcessor::ProcessConnectionlessIncoming( fragMsg, msg, userData ) ) {
    329 			idLib::Printf( "ProcessConnectionlessIncoming FAILED from %s.\n", remoteAddress.ToString() );
    330 			// Not a valid connectionless packet
    331 			return;
    332 		}
    333 			
    334 		// Valid connectionless packets are always RETURN_TYPE_OOB
    335 		type = idPacketProcessor::RETURN_TYPE_OOB;
    336 			
    337 		// Find the peer this connectionless msg should go to
    338 		peerNum = FindPeer( remoteAddress, sessionID, true );
    339 	}
    340 
    341 	if ( type == idPacketProcessor::RETURN_TYPE_NONE ) {
    342 		// This packet is not necessarily invalid, it could be a start or middle of a fragmented packet that's not fully constructed.
    343 		return;
    344 	}
    345 
    346 	if ( peerNum >= 0 ) {
    347 		// Update their heart beat (only if we've received a valid packet (we've checked type == idPacketProcessor::RETURN_TYPE_NONE))
    348 		peers[peerNum].lastHeartBeat = Sys_Milliseconds();	
    349 	}
    350 
    351 	// Handle server query requests.  We do this before the STATE_IDLE check.  This is so we respond.
    352 	// We may want to change this to just ignore the request if we are idle, and change the timeout time
    353 	// on the requesters part to just timeout faster.
    354 	if ( type == idPacketProcessor::RETURN_TYPE_OOB ) {	
    355 		if ( userData == OOB_MATCH_QUERY || userData == OOB_SYSTEMLINK_QUERY ) {
    356 			sessionCB->HandleServerQueryRequest( remoteAddress, msg, userData );
    357 			return;
    358 		}
    359 		if ( userData == OOB_MATCH_QUERY_ACK ) {
    360 			sessionCB->HandleServerQueryAck( remoteAddress, msg );
    361 			return;
    362 		}
    363 	}		
    364 
    365 	if ( type == idPacketProcessor::RETURN_TYPE_OOB ) {	
    366 		if ( userData == OOB_VOICE_AUDIO ) {
    367 			sessionCB->HandleOobVoiceAudio( remoteAddress, msg );
    368 		} else if ( userData == OOB_HELLO ) {
    369 			// Handle new peer connect request
    370 			peerNum = HandleInitialPeerConnection( msg, remoteAddress, peerNum );
    371 			return;
    372 		} else if ( userData == OOB_MIGRATE_INVITE ) {
    373 			NET_VERBOSE_PRINT( "NET: Migration invite for session %s from %s (state = %s)\n", GetLobbyName(), remoteAddress.ToString(), session->GetStateString() );
    374 
    375 			// Get connection info
    376 			lobbyConnectInfo_t connectInfo;
    377 			connectInfo.ReadFromMsg( msg );
    378 
    379 			if ( lobbyBackend != NULL && lobbyBackend->GetState() != idLobbyBackend::STATE_FAILED && lobbyBackend->IsOwnerOfConnectInfo( connectInfo ) ) {		// Ignore duplicate invites
    380 				idLib::Printf( "NET: Already migrated to %s.\n", remoteAddress.ToString() );
    381 				return;
    382 			}
    383 
    384 			if ( migrationInfo.state == MIGRATE_NONE ) {
    385 				if ( IsPeer() && host >= 0 && host < peers.Num() && Sys_Milliseconds() - peers[host].lastHeartBeat > 8 * 1000 ) {
    386 					// Force migration early if we get an invite, and it has been some time since we've heard from the host
    387 					PickNewHost();
    388 				} else {
    389 					idLib::Printf( "NET: Ignoring migration invite because we are not migrating %s\n", remoteAddress.ToString() );
    390 					SendGoodbye( remoteAddress );	// So they can remove us from their invite list
    391 					return;
    392 				}
    393 			}
    394 
    395 			if ( !sessionCB->PreMigrateInvite( *this ) ) {
    396 				NET_VERBOSE_PRINT( "NET: sessionCB->PreMigrateInvite( *this ) failed from %s\n", remoteAddress.ToString() );
    397 				return;
    398 			}
    399 
    400 			// If we are also becoming a new host, see who wins
    401 			if ( migrationInfo.state == MIGRATE_BECOMING_HOST ) {
    402 				int inviteIndex = FindMigrationInviteIndex( remoteAddress );
    403 
    404 				if ( inviteIndex != -1 ) {
    405 					// We found them in our list, check to make sure our ping is better
    406 					int				ping1	= migrationInfo.ourPingMs;
    407 					lobbyUserID_t	userId1 = migrationInfo.ourUserId;
    408 					int				ping2	= migrationInfo.invites[inviteIndex].pingMs;
    409 					lobbyUserID_t	userId2 = migrationInfo.invites[inviteIndex].userId;
    410 
    411 					if ( IsBetterHost( ping1, userId1, ping2, userId2 ) ) {
    412 						idLib::Printf( "NET: Ignoring migration invite from %s, since our ping is better (%i / %i).\n", remoteAddress.ToString(), ping1, ping2 );
    413 						return;
    414 					}
    415 				}
    416 			}
    417 
    418 			bool fromGame = msg.ReadBool();
    419 
    420 			// Kill the current lobbyBackend
    421 			Shutdown();
    422 
    423 			// Connect to the lobby
    424 			ConnectTo( connectInfo, true );		// Pass in true for the invite flag, so we can connect to invite only lobby if we need to
    425 
    426 			if ( verify( sessionCB != NULL ) ) {
    427 				if ( sessionCB->BecomingPeer( *this ) ) {
    428 					migrationInfo.persistUntilGameEndsData.wasMigratedJoin = true;
    429 					migrationInfo.persistUntilGameEndsData.wasMigratedGame = fromGame;
    430 				}
    431 			}
    432 
    433 		} else if ( userData == OOB_GOODBYE || userData == OOB_GOODBYE_W_PARTY || userData == OOB_GOODBYE_FULL ) {
    434 			HandleGoodbyeFromPeer( peerNum, remoteAddress, userData );
    435 			return;
    436 		} else if ( userData == OOB_RESOURCE_LIST ) {
    437 
    438 			if ( !verify( lobbyType == GetActingGameStateLobbyType() ) ) {
    439 				return;
    440 			}
    441 
    442 			if ( peerNum != host ) {
    443 				NET_VERBOSE_PRINT( "NET: Resource list from non-host %i, %s\n", peerNum, remoteAddress.ToString() );
    444 				return;
    445 			}
    446 
    447 			if ( peerNum >= 0 && !peers[peerNum].IsConnected() ) {
    448 				NET_VERBOSE_PRINT( "NET: Resource list from host with no game connection: %i, %s\n", peerNum, remoteAddress.ToString() );
    449 				return;
    450 			}
    451 		} else if ( userData == OOB_BANDWIDTH_TEST ) {
    452 			int seqNum = msg.ReadLong();
    453 			// TODO: We should read the random data and verify the MD5 checksum
    454 
    455 			int time = Sys_Milliseconds();
    456 			bool inOrder = ( seqNum == 0 || peers[peerNum].bandwidthSequenceNum + 1 == seqNum );
    457 			int timeSinceLast = 0;
    458 
    459 			if ( bandwidthChallengeStartTime <= 0 ) {
    460 				// Reset the test
    461 				NET_VERBOSE_PRINT( "\nNET: Starting bandwidth test @ %d\n", time );
    462 				bandwidthChallengeStartTime = time;
    463 				peers[peerNum].bandwidthSequenceNum = 0;
    464 				peers[peerNum].bandwidthTestBytes = peers[peerNum].packetProc->GetIncomingBytes();
    465 			} else {
    466 				timeSinceLast = time - (bandwidthChallengeEndTime - session->GetTitleStorageInt( "net_bw_test_timeout", net_bw_test_timeout.GetInteger() ) );
    467 			}
    468 
    469 			if ( inOrder ) {
    470 				bandwidthChallengeNumGoodSeq++;
    471 			}
    472 
    473 			bandwidthChallengeEndTime = time + session->GetTitleStorageInt( "net_bw_test_timeout", net_bw_test_timeout.GetInteger() );
    474 			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() );
    475 			peers[peerNum].bandwidthSequenceNum = seqNum;
    476 
    477 		} else {
    478 			NET_VERBOSE_PRINT( "NET: Unknown oob packet %d from %s (%d)\n", userData, remoteAddress.ToString(), peerNum );
    479 		}
    480 	} else if ( type == idPacketProcessor::RETURN_TYPE_INBAND ) {
    481 		// Process in-band message
    482 		if ( peerNum < 0 ) {
    483 			idLib::Printf( "NET: In-band message from unknown peer: %s\n", remoteAddress.ToString() );
    484 			return;
    485 		}
    486 			
    487 		if ( !verify( peers[ peerNum ].address.Compare( remoteAddress ) ) ) {
    488 			idLib::Printf( "NET: Peer with wrong address: %i, %s\n", peerNum, remoteAddress.ToString() );
    489 			return;
    490 		}
    491 
    492 		// Handle reliable
    493 		int numReliable = peers[ peerNum ].packetProc->GetNumReliables();
    494 		for ( int r = 0; r < numReliable; r++ ) {
    495 			// Just in case one of the reliable msg's cause this peer to disconnect
    496 			// (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)
    497 			peerNum	= FindPeer( remoteAddress, sessionID );
    498 
    499 			if ( peerNum == -1 ) {
    500 				idLib::Printf( "NET: Dropped peer while processing reliable msg's: %i, %s\n", peerNum, remoteAddress.ToString() );
    501 				break;
    502 			}
    503 
    504 			const byte * reliableData = peers[ peerNum ].packetProc->GetReliable( r );
    505 			int reliableSize = peers[ peerNum ].packetProc->GetReliableSize( r );
    506 			idBitMsg reliableMsg( reliableData, reliableSize );
    507 			reliableMsg.SetSize( reliableSize );
    508 
    509 			HandleReliableMsg( peerNum, reliableMsg );
    510 		}
    511 	
    512 		if ( peerNum == -1 || !peers[ peerNum ].IsConnected() ) {
    513 			// If the peer still has no connection after HandleReliableMsg, then something is wrong.
    514 			// (We could have been in CONNECTION_CONNECTING state for this session type, but the first message
    515 			// we should receive from the server is the ack, otherwise, something went wrong somewhere)
    516 			idLib::Printf( "NET: In-band message from host with no active connection: %i, %s\n", peerNum, remoteAddress.ToString() );
    517 			return;
    518 		}
    519 
    520 		// Handle unreliable part (if any)
    521 		if ( msg.GetRemainingData() > 0 && loaded ) {
    522 			if ( !verify( lobbyType == GetActingGameStateLobbyType() ) ) {
    523 				idLib::Printf( "NET: Snapshot msg for non game session lobby %s\n", remoteAddress.ToString() );
    524 				return;
    525 			}
    526 				
    527 			if ( peerNum == host ) {
    528 				idSnapShot	localSnap;
    529 				int			sequence = -1;
    530 				int			baseseq = -1;
    531 				bool		fullSnap = false;
    532 				localReadSS = &localSnap;
    533 
    534 				// If we are the peer, we assume we only receive snapshot data on the in-band channel
    535 				const byte * deltaData = msg.GetReadData() + msg.GetReadCount();
    536 				int deltaLength = msg.GetRemainingData();
    537 
    538 				if ( peers[ peerNum ].snapProc->ReceiveSnapshotDelta( deltaData, deltaLength, 0, sequence, baseseq, localSnap, fullSnap ) ) {						
    539 						
    540 					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() ) );
    541 						
    542 					if ( sessionCB->GetState() != idSession::INGAME && sequence != -1 ) {
    543 						int seq = peers[ peerNum ].snapProc->GetLastAppendedSequence();
    544 
    545 						// 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
    546 						byte ackbuffer[32];
    547 						idBitMsg ackmsg( ackbuffer, sizeof( ackbuffer ) );
    548 						ackmsg.WriteLong( seq );
    549 
    550 						// Add incoming BPS for QoS
    551 						float incomingBPS = peers[ peerNum ].receivedBps;
    552 						if ( peers[ peerNum ].receivedBpsIndex != seq ) {
    553 							incomingBPS = idMath::ClampFloat( 0.0f, static_cast<float>( idLobby::BANDWIDTH_REPORTING_MAX ), peers[host].packetProc->GetIncomingRateBytes() );
    554 							peers[ peerNum ].receivedBpsIndex = seq;
    555 							peers[ peerNum ].receivedBps = incomingBPS;
    556 						}
    557 
    558 						ackmsg.WriteQuantizedUFloat< idLobby::BANDWIDTH_REPORTING_MAX, idLobby::BANDWIDTH_REPORTING_BITS >( incomingBPS );
    559 						QueueReliableMessage( host, RELIABLE_SNAPSHOT_ACK, ackbuffer, sizeof( ackbuffer ) );
    560 					}					
    561 				}
    562 
    563 				if ( fullSnap ) {
    564 					sessionCB->ReceivedFullSnap();
    565 					common->NetReceiveSnapshot( localSnap );
    566 				}
    567 
    568 				localReadSS = NULL;
    569 					
    570 			} else {
    571 				// If we are the host, we assume we only receive usercmds on the inband channel
    572 
    573 				int snapNum = 0;
    574 				uint16 receivedBps_quantized = 0;
    575 
    576 				byte usercmdBuffer[idPacketProcessor::MAX_FINAL_PACKET_SIZE];
    577 
    578 				lzwCompressionData_t lzwData;
    579 				idLZWCompressor lzwCompressor( &lzwData );
    580 				lzwCompressor.Start( const_cast<byte *>( msg.GetReadData() ) + msg.GetReadCount(), msg.GetRemainingData() );
    581 				lzwCompressor.ReadAgnostic( snapNum );
    582 				lzwCompressor.ReadAgnostic( receivedBps_quantized );
    583 				int usercmdSize = lzwCompressor.Read( usercmdBuffer, sizeof( usercmdBuffer ), true );
    584 				lzwCompressor.End();
    585 
    586 				float receivedBps = ( receivedBps_quantized / (float)( BIT( idLobby::BANDWIDTH_REPORTING_BITS ) - 1 ) ) * (float)idLobby::BANDWIDTH_REPORTING_MAX;
    587 				if ( peers[ peerNum ].receivedBpsIndex != snapNum ) {
    588 					peers[ peerNum ].receivedBps = receivedBps;
    589 					peers[ peerNum ].receivedBpsIndex = snapNum;
    590 				}
    591 
    592 				if ( snapNum < 50 ) {
    593 					NET_VERBOSE_PRINT( "NET: peer %d ack'd snapNum %d\n", peerNum, snapNum );
    594 				}
    595 				ApplySnapshotDelta( peerNum, snapNum );
    596 
    597 				idBitMsg usercmdMsg( (const byte *)usercmdBuffer, usercmdSize );
    598 				common->NetReceiveUsercmds( peerNum, usercmdMsg );
    599 			}
    600 		}
    601 	}
    602 }
    603 
    604 /*
    605 ========================
    606 idLobby::HasActivePeers
    607 ========================
    608 */
    609 bool idLobby::HasActivePeers() const {
    610 	for ( int p = 0; p < peers.Num(); p++ ) {
    611 		if ( peers[p].GetConnectionState() != CONNECTION_FREE ) {	
    612 			return true;
    613 		}
    614 	}
    615 	
    616 	return false;
    617 }
    618 
    619 /*
    620 ========================
    621 idLobby::NumFreeSlots
    622 ========================
    623 */
    624 int idLobby::NumFreeSlots() const {
    625 	if ( parms.matchFlags & MATCH_JOIN_IN_PROGRESS ) {
    626 		return parms.numSlots - GetNumConnectedUsers();
    627 	} else {
    628 		return parms.numSlots - GetNumLobbyUsers();
    629 	}
    630 }
    631 
    632 //===============================================================================
    633 //	** END PUBLIC INTERFACE ***
    634 //===============================================================================
    635 
    636 //===============================================================================
    637 //	** BEGIN STATE CODE ***
    638 //===============================================================================
    639 
    640 const char * idLobby::stateToString[ NUM_STATES ] = {
    641 	ASSERT_ENUM_STRING( STATE_IDLE, 0 ),
    642 	ASSERT_ENUM_STRING( STATE_CREATE_LOBBY_BACKEND, 1 ),
    643 	ASSERT_ENUM_STRING( STATE_SEARCHING, 2 ),
    644 	ASSERT_ENUM_STRING( STATE_OBTAINING_ADDRESS, 3 ),
    645 	ASSERT_ENUM_STRING( STATE_CONNECT_HELLO_WAIT, 4 ),
    646 	ASSERT_ENUM_STRING( STATE_FINALIZE_CONNECT, 5 ),
    647 	ASSERT_ENUM_STRING( STATE_FAILED, 6 ),
    648 };
    649 
    650 /*
    651 ========================
    652 idLobby::State_Idle
    653 ========================
    654 */
    655 void idLobby::State_Idle() {
    656 	// If lobbyBackend is in a failed state, shutdown, go to a failed state ourself, and return
    657 	if ( lobbyBackend != NULL && lobbyBackend->GetState() == idLobbyBackend::STATE_FAILED ) {
    658 		HandleConnectionAttemptFailed();
    659 		common->Dialog().ClearDialog( GDM_MIGRATING );
    660 		common->Dialog().ClearDialog( GDM_MIGRATING_WAITING );
    661 		common->Dialog().ClearDialog( GDM_MIGRATING_RELAUNCHING );
    662 		return;
    663 	}
    664 
    665 	if ( migrationInfo.persistUntilGameEndsData.hasGameData && sessionCB->GetState() <= idSession::IDLE ) {
    666 		// This can happen with 'leaveGame' or 'disconnect' since those paths don't go through endMatch
    667 		// This seems like an ok catch all place but there may be a better way to handle this
    668 		ResetAllMigrationState();
    669 		common->Dialog().ClearDialog( GDM_MIGRATING );
    670 		common->Dialog().ClearDialog( GDM_MIGRATING_WAITING );
    671 		common->Dialog().ClearDialog( GDM_MIGRATING_RELAUNCHING );
    672 	}
    673 }
    674 
    675 /*
    676 ========================
    677 idLobby::State_Create_Lobby_Backend
    678 ========================
    679 */
    680 void idLobby::State_Create_Lobby_Backend() {
    681 	if ( !verify( lobbyBackend != NULL ) ) { 
    682 		SetState( STATE_FAILED );
    683 		return;
    684 	}
    685 
    686 	assert( lobbyBackend != NULL );
    687 
    688 	if ( migrationInfo.state == MIGRATE_BECOMING_HOST ) {
    689 		const int DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS = session->GetTitleStorageInt( "DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS", 30 );
    690 
    691 		// If we are taking too long, cancel the connection
    692 		if ( DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS > 0 ) {
    693 			if ( Sys_Milliseconds() - migrationInfo.migrationStartTime > 1000 * DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS ) {
    694 				SetState( STATE_FAILED );
    695 				return;
    696 			}
    697 		}
    698 	}
    699 
    700 	if ( lobbyBackend->GetState() == idLobbyBackend::STATE_CREATING ) {
    701 		return;		// Busy but valid
    702 	}
    703 
    704 	if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) {
    705 		SetState( STATE_FAILED );
    706 		return;
    707 	}
    708 
    709 	// Success
    710 	InitStateLobbyHost();
    711 	
    712 	// Set state to idle to signify to session we are done creating
    713 	SetState( STATE_IDLE );
    714 }
    715 
    716 /*
    717 ========================
    718 idLobby::State_Searching
    719 ========================
    720 */
    721 void idLobby::State_Searching() {
    722 	if ( !verify( lobbyBackend != NULL ) ) { 
    723 		SetState( STATE_FAILED );
    724 		return;
    725 	}
    726 
    727 	if ( lobbyBackend->GetState() == idLobbyBackend::STATE_SEARCHING ) {
    728 		return;		// Busy but valid
    729 	}
    730 
    731 	if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) {
    732 		SetState( STATE_FAILED );		// Any other lobbyBackend state is invalid
    733 		return;
    734 	}
    735 	
    736 	// Done searching, get results from lobbyBackend
    737 	lobbyBackend->GetSearchResults( searchResults );
    738 
    739 	if ( searchResults.Num() == 0 ) {
    740 		// If we didn't get any results, set state to failed
    741 		SetState( STATE_FAILED );
    742 		return;
    743 	}
    744 
    745 	extern idCVar net_maxSearchResultsToTry;
    746 	const int maxSearchResultsToTry = session->GetTitleStorageInt( "net_maxSearchResultsToTry", net_maxSearchResultsToTry.GetInteger() );
    747 
    748 	if ( searchResults.Num() > maxSearchResultsToTry ) {
    749 		searchResults.SetNum( maxSearchResultsToTry );
    750 	}
    751 
    752 	// Set state to idle to signify we are done searching
    753 	SetState( STATE_IDLE );
    754 }
    755 
    756 /*
    757 ========================
    758 idLobby::State_Obtaining_Address
    759 ========================
    760 */
    761 void idLobby::State_Obtaining_Address() {
    762 	if ( lobbyBackend->GetState() == idLobbyBackend::STATE_OBTAINING_ADDRESS ) {
    763 		return;		// Valid but not ready
    764 	}
    765 	
    766 	if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) {
    767 		// There was an error, signify to caller
    768 		failedReason = migrationInfo.persistUntilGameEndsData.wasMigratedJoin ? FAILED_MIGRATION_CONNECT_FAILED : FAILED_CONNECT_FAILED;
    769 		NET_VERBOSE_PRINT("idLobby::State_Obtaining_Address: the lobby backend failed." );
    770 		SetState( STATE_FAILED );
    771 		return;
    772 	}
    773 
    774 	//
    775 	//	We have the address of the lobbyBackend, we can now send a hello packet
    776 	//
    777 
    778 	// This will be the host for this lobby type
    779 	host = AddPeer( hostAddress, GenerateSessionID() );
    780 
    781 	// Record start time of connection attempt to the host
    782 	helloStartTime		= Sys_Milliseconds();
    783 	lastConnectRequest	= helloStartTime;
    784 	connectionAttempts	= 0;
    785 
    786 	// Change state to connecting
    787 	SetState( STATE_CONNECT_HELLO_WAIT );
    788 
    789 	// Send first connect attempt now (we'll send more periodically if we fail to receive an ack)
    790 	// (we do this after changing state, since the function expects we're in the right state)
    791 	SendConnectionRequest();
    792 }
    793 
    794 /*
    795 ========================
    796 idLobby::State_Finalize_Connect
    797 ========================
    798 */
    799 void idLobby::State_Finalize_Connect() {
    800 	if ( lobbyBackend->GetState() == idLobbyBackend::STATE_CREATING ) {
    801 		// Valid but busy
    802 		return;
    803 	}
    804 
    805 	if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) {
    806 		// Any other state not valid, failed
    807 		SetState( STATE_FAILED );
    808 		return;
    809 	}
    810 	
    811 	// Success
    812 	SetState( STATE_IDLE );
    813 
    814 	// Tell session mgr if this was a migration
    815 	if ( migrationInfo.persistUntilGameEndsData.wasMigratedJoin ) {
    816 		sessionCB->BecamePeer( *this );
    817 	}
    818 }
    819 
    820 /*
    821 ========================
    822 idLobby::State_Connect_Hello_Wait
    823 ========================
    824 */
    825 void idLobby::State_Connect_Hello_Wait() {
    826 	if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) {
    827 		// If the lobbyBackend is in an error state, shut everything down
    828 		NET_VERBOSE_PRINT( "NET: Lobby is no longer ready while waiting for lobbyType %s hello.\n", GetLobbyName() );
    829 		HandleConnectionAttemptFailed();
    830 		return;
    831 	}
    832 
    833 	int time = Sys_Milliseconds();
    834 	
    835 	const int timeoutMs = session->GetTitleStorageInt( "net_connectTimeoutInSeconds", net_connectTimeoutInSeconds.GetInteger() ) * 1000;
    836 
    837 	if ( timeoutMs != 0 && time - helloStartTime > timeoutMs ) {
    838 		NET_VERBOSE_PRINT( "NET: Timeout waiting for lobbyType %s for party hello.\n", GetLobbyName() );
    839 		HandleConnectionAttemptFailed();
    840 		return;
    841 	}
    842 
    843 	if ( connectionAttempts < MAX_CONNECT_ATTEMPTS ) {
    844 		assert( connectionAttempts >= 1 );		// Should have at least the initial connection attempt
    845 
    846  		// See if we need to send another hello request
    847 		// (keep getting more frequent to increase chance due to possible packet loss, but clamp to MIN_CONNECT_FREQUENCY seconds)
    848 		// TODO: We could eventually make timing out a function of actual number of attempts rather than just plain time.
    849 		int resendTime = Max( MIN_CONNECT_FREQUENCY_IN_SECONDS, CONNECT_REQUEST_FREQUENCY_IN_SECONDS / connectionAttempts ) * 1000;
    850 		
    851 		if ( time - lastConnectRequest > resendTime ) {
    852 			SendConnectionRequest();
    853 			lastConnectRequest = time;
    854 		}
    855 	}
    856 }
    857 
    858 /*
    859 ========================
    860 idLobby::SetState
    861 ========================
    862 */
    863 void idLobby::SetState( lobbyState_t newState ) {	
    864 	assert( newState < NUM_STATES );
    865 	assert( state < NUM_STATES );
    866 
    867 	verify_array_size( stateToString, NUM_STATES );
    868 
    869 	if ( state == newState ) {
    870 		NET_VERBOSE_PRINT( "NET: idLobby::SetState: State SAME %s for session %s\n", stateToString[ newState ], GetLobbyName() );
    871 		return;
    872 	}
    873 
    874 	// Set the current state
    875 	NET_VERBOSE_PRINT( "NET: idLobby::SetState: State changing from %s to %s for session %s\n", stateToString[ state ], stateToString[ newState ], GetLobbyName() );
    876 
    877 	state = newState;
    878 }
    879 
    880 //===============================================================================
    881 //	** END STATE CODE ***
    882 //===============================================================================
    883 
    884 /*
    885 ========================
    886 idLobby::StartCreating
    887 ========================
    888 */
    889 void idLobby::StartCreating() {
    890 	assert( lobbyBackend == NULL );
    891 	assert( state == STATE_IDLE );
    892 
    893 	float skillLevel = GetAverageLocalUserLevel( true );
    894 
    895 	lobbyBackend = sessionCB->CreateLobbyBackend( parms, skillLevel, (idLobbyBackend::lobbyBackendType_t)lobbyType );
    896 
    897 	SetState( STATE_CREATE_LOBBY_BACKEND );	
    898 }
    899 
    900 /*
    901 ========================
    902 idLobby::FindPeer
    903 ========================
    904 */
    905 int idLobby::FindPeer( const lobbyAddress_t & remoteAddress, idPacketProcessor::sessionId_t sessionID, bool ignoreSessionID ) {
    906 
    907 	bool connectionless = ( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_PARTY || 
    908 							sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME || 
    909 							sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME_STATE );
    910 	
    911 	if ( connectionless && !ignoreSessionID ) {
    912 		return -1;		// This was meant to be connectionless. FindPeer is meant for connected (or connecting) peers
    913 	}
    914 
    915 	for ( int p = 0; p < peers.Num(); p++ ) {
    916 		if ( peers[p].GetConnectionState() == CONNECTION_FREE ) {
    917 			continue;
    918 		}
    919 
    920 		if ( peers[p].address.Compare( remoteAddress ) ) {
    921 			if ( connectionless && ignoreSessionID ) {
    922 				return p;
    923 			}
    924 
    925 			// Using a rolling check, so that we account for possible packet loss, and out of order issues
    926 			if ( IsPeer() ) {
    927 				idPacketProcessor::sessionId_t searchStart = peers[p].sessionID;
    928 	
    929 				// Since we only roll the code between matches, we should only need to look ahead a couple increments.
    930 				// Worse case, if the stars line up, the client doesn't see the new sessionId, and times out, and gets booted.
    931 				// This should be impossible though, since the timings won't be possible considering how long it takes to end the match, 
    932 				// and restart, and then restart again.
    933 				int numTries = 2;
    934 
    935 				while ( numTries-- > 0 && searchStart != sessionID ) {
    936 					searchStart = IncrementSessionID( searchStart );
    937 					if ( searchStart == sessionID ) {
    938 						idLib::Printf( "NET: Rolling session ID check found new ID: %i\n", searchStart );
    939 						if ( peers[p].packetProc != NULL ) {
    940 							peers[p].packetProc->VerifyEmptyReliableQueue( RELIABLE_GAME_DATA, RELIABLE_DUMMY_MSG );
    941 						}
    942 						peers[p].sessionID = searchStart;
    943 						break;
    944 					}
    945 				}
    946 			}
    947 
    948 			if ( peers[p].sessionID != sessionID ) {
    949 				continue;
    950 			}
    951 			return p;
    952 		}
    953 	}
    954 	return -1;
    955 }
    956 
    957 /*
    958 ========================
    959 idLobby::FindAnyPeer
    960 Find a peer when we don't know the session id, and we don't care since it's a connectionless msg
    961 ========================
    962 */
    963 int idLobby::FindAnyPeer( const lobbyAddress_t & remoteAddress ) const {
    964 
    965 	for ( int p = 0; p < peers.Num(); p++ ) {
    966 		if ( peers[p].GetConnectionState() == CONNECTION_FREE ) {
    967 			continue;
    968 		}
    969 
    970 		if ( peers[p].address.Compare( remoteAddress ) ) {
    971 			return p;
    972 		}
    973 	}
    974 	return -1;
    975 }
    976 
    977 /*
    978 ========================
    979 idLobby::FindFreePeer
    980 ========================
    981 */
    982 int idLobby::FindFreePeer() const {
    983 	
    984 	// Return the first non active peer
    985 	for ( int p = 0; p < peers.Num(); p++ ) {
    986 		if ( !peers[p].IsActive()  ) {
    987 			return p;
    988 		}
    989 	}
    990 	return -1;
    991 }
    992 
    993 /*
    994 ========================
    995 idLobby::AddPeer
    996 ========================
    997 */
    998 int idLobby::AddPeer( const lobbyAddress_t & remoteAddress, idPacketProcessor::sessionId_t sessionID ) { 
    999 	// First, make sure we don't already have this peer
   1000 	int p = FindPeer( remoteAddress, sessionID );
   1001 	assert( p == -1 );		// When using session ID's, we SHOULDN'T find this remoteAddress/sessionID combo
   1002 
   1003 	if ( p == -1 ) {
   1004 		// If we didn't find the peer, we need to add a new one
   1005 		
   1006 		p = FindFreePeer();
   1007 
   1008 		if ( p == -1 ) {
   1009 			peer_t newPeer;
   1010 			p = peers.Append( newPeer );
   1011 		}
   1012 		
   1013 		peer_t & peer = peers[p];
   1014 		
   1015 		peer.ResetAllData();
   1016 		
   1017 		assert( peer.connectionState == CONNECTION_FREE );
   1018 
   1019 		peer.address	= remoteAddress;
   1020 
   1021 		peer.sessionID	= sessionID;
   1022 
   1023 		NET_VERBOSE_PRINT( "NET: Added peer %s at index %i\n", remoteAddress.ToString(), p );
   1024 	} else {
   1025 		NET_VERBOSE_PRINT( "NET: Found peer %s at index %i\n", remoteAddress.ToString(), p );
   1026 	}
   1027 
   1028 	SetPeerConnectionState( p, CONNECTION_CONNECTING );
   1029 	
   1030 	if ( lobbyType == GetActingGameStateLobbyType() ) {
   1031 		// Reset various flags used in game mode
   1032 		peers[p].ResetMatchData();
   1033 	}
   1034 
   1035 	return p;
   1036 }
   1037 
   1038 /*
   1039 ========================
   1040 idLobby::DisconnectPeerFromSession
   1041 ========================
   1042 */
   1043 void idLobby::DisconnectPeerFromSession( int p ) {
   1044 	if ( !verify( IsHost() ) ) {
   1045 		return;
   1046 	}
   1047 	
   1048 	peer_t & peer = peers[p];
   1049 
   1050 	if ( peer.GetConnectionState() != CONNECTION_FREE ) {
   1051 		SetPeerConnectionState( p, CONNECTION_FREE );
   1052 	}
   1053 }
   1054 
   1055 /*
   1056 ========================
   1057 idLobby::DisconnectAllPeers
   1058 ========================
   1059 */
   1060 void idLobby::DisconnectAllPeers() {
   1061 	for ( int p = 0; p < peers.Num(); p++ ) {
   1062 		DisconnectPeerFromSession( p );
   1063 	}
   1064 }
   1065 
   1066 /*
   1067 ========================
   1068 idLobby::SendGoodbye
   1069 ========================
   1070 */
   1071 void idLobby::SendGoodbye( const lobbyAddress_t & remoteAddress, bool wasFull ) { 
   1072 
   1073 	if ( net_skipGoodbye.GetBool() ) {
   1074 		return;
   1075 	}
   1076 
   1077 	NET_VERBOSE_PRINT( "NET: Sending goodbye to %s for %s (wasFull = %i)\n", remoteAddress.ToString(), GetLobbyName(), wasFull );
   1078 
   1079 	static const int NUM_REDUNDANT_GOODBYES = 10;
   1080 	
   1081 	int msgType = OOB_GOODBYE;
   1082 
   1083 	if ( wasFull ) {
   1084 		msgType = OOB_GOODBYE_FULL;
   1085 	} else if ( lobbyType == TYPE_GAME && ( sessionCB->GetSessionOptions() & idSession::OPTION_LEAVE_WITH_PARTY ) && !( parms.matchFlags & MATCH_PARTY_INVITE_PLACEHOLDER ) ) {
   1086 		msgType = OOB_GOODBYE_W_PARTY;
   1087 	}
   1088 	
   1089 	for ( int i = 0; i < NUM_REDUNDANT_GOODBYES; i++ ) { 
   1090 		SendConnectionLess( remoteAddress, msgType ); 
   1091 	} 
   1092 }
   1093 
   1094 /*
   1095 ========================
   1096 idLobby::SetPeerConnectionState
   1097 ========================
   1098 */
   1099 void idLobby::SetPeerConnectionState( int p, connectionState_t newState, bool skipGoodbye ) {
   1100 
   1101 	if ( !verify( p >= 0 && p < peers.Num() ) ) {
   1102 		idLib::Printf( "NET: SetPeerConnectionState invalid peer index %i\n", p );
   1103 		return;
   1104 	}
   1105 
   1106 	peer_t & peer = peers[p];
   1107 
   1108 	const lobbyType_t actingGameStateLobbyType = GetActingGameStateLobbyType();
   1109 
   1110 	if ( peer.GetConnectionState() == newState ) {
   1111 		idLib::Printf( "NET: SetPeerConnectionState: Peer already in state %i\n", newState );
   1112 		assert( 0 );	// This case means something is most likely bad, and it's the programmers fault
   1113 		assert( ( peer.packetProc != NULL ) == peer.IsActive() );
   1114 		assert( ( ( peer.snapProc != NULL ) == peer.IsActive() ) == ( actingGameStateLobbyType == lobbyType ) );
   1115 		return;
   1116 	}
   1117 
   1118 	if ( newState == CONNECTION_CONNECTING ) {
   1119 		//mem.PushHeap();
   1120 
   1121 		// We better be coming from a free connection state if we are trying to connect
   1122 		assert( peer.GetConnectionState() == CONNECTION_FREE );
   1123 
   1124 		assert( peer.packetProc == NULL );
   1125 		peer.packetProc = new ( TAG_NETWORKING )idPacketProcessor();
   1126 
   1127 		if ( lobbyType == actingGameStateLobbyType ) {
   1128 			assert( peer.snapProc == NULL );
   1129 			peer.snapProc = new ( TAG_NETWORKING )idSnapshotProcessor();
   1130 		}
   1131 
   1132 		//mem.PopHeap();
   1133 	} else if ( newState == CONNECTION_ESTABLISHED ) {
   1134 		// If we are marking this peer as connected for the first time, make sure this peer was actually trying to connect.
   1135 		assert( peer.GetConnectionState() == CONNECTION_CONNECTING );
   1136 	} else if ( newState == CONNECTION_FREE ) {
   1137 		// If we are freeing this connection and we had an established connection before, make sure to send a goodbye
   1138 		if ( peer.GetConnectionState() == CONNECTION_ESTABLISHED && !skipGoodbye ) {
   1139 			idLib::Printf("SetPeerConnectionState: Sending goodbye to peer %s from session %s\n", peer.address.ToString(), GetLobbyName() );
   1140 			SendGoodbye( peer.address );
   1141 		}
   1142 	}
   1143 
   1144 	peer.connectionState = newState;
   1145 
   1146 	if ( !peer.IsActive() ) {
   1147 		if ( peer.packetProc != NULL ) {
   1148 			delete peer.packetProc;
   1149 			peer.packetProc = NULL;
   1150 		}
   1151 
   1152 		if ( peer.snapProc != NULL ) {
   1153 			assert( lobbyType == actingGameStateLobbyType );
   1154 			delete peer.snapProc;
   1155 			peer.snapProc = NULL;
   1156 		}
   1157 	}
   1158 
   1159 	// Do this in case we disconnected the peer
   1160 	if ( IsHost() ) {
   1161 		RemoveUsersWithDisconnectedPeers();
   1162 	}
   1163 }
   1164 
   1165 /*
   1166 ========================
   1167 idLobby::QueueReliableMessage
   1168 ========================
   1169 */
   1170 void idLobby::QueueReliableMessage( int p, byte type, const byte * data, int dataLen ) {
   1171 	if ( !verify( p >= 0 && p < peers.Num() ) ) {
   1172 		return;
   1173 	}
   1174 
   1175 	peer_t & peer = peers[p];
   1176 	
   1177 	if ( !peer.IsConnected() ) {
   1178 		// Don't send to this peer if we don't have an established connection of this session type
   1179 		NET_VERBOSE_PRINT( "NET: Not sending reliable type %i to peer %i because connectionState is %i\n", type, p, peer.GetConnectionState() );
   1180 		return;
   1181 	}
   1182 
   1183 	if ( peer.packetProc->NumQueuedReliables() > 2 ) {
   1184 		idLib::PrintfIf( false, "NET: peer.packetProc->NumQueuedReliables() > 2: %i (%i / %s)\n", peer.packetProc->NumQueuedReliables(), p, peer.address.ToString() );
   1185 	}
   1186 
   1187 	if ( !peer.packetProc->QueueReliableMessage( type, data, dataLen ) ) {
   1188 		// For now, when this happens, disconnect from all session types
   1189 		NET_VERBOSE_PRINT( "NET: Dropping peer because we overflowed his reliable message queue\n" );
   1190 		if ( IsHost() ) {
   1191 			// Disconnect peer from this session type
   1192 			DisconnectPeerFromSession( p );
   1193 		} else {
   1194 			Shutdown();		// Shutdown session if we can't queue the reliable
   1195 		}
   1196 	}
   1197 }
   1198 
   1199 /*
   1200 ========================
   1201 idLobby::GetNumConnectedPeers
   1202 ========================
   1203 */
   1204 int idLobby::GetNumConnectedPeers() const {
   1205 	int numConnected = 0;
   1206 	for ( int i = 0; i < peers.Num(); i++ ) {
   1207 		if ( peers[i].IsConnected() ) {
   1208 			numConnected++;
   1209 		}
   1210 	}
   1211 
   1212 	return numConnected;
   1213 }
   1214 
   1215 /*
   1216 ========================
   1217 idLobby::GetNumConnectedPeersInGame
   1218 ========================
   1219 */
   1220 int idLobby::GetNumConnectedPeersInGame() const {
   1221 	int numActive = 0;
   1222 	for ( int i = 0; i < peers.Num(); i++ ) {
   1223 		if ( peers[i].IsConnected() && peers[i].inGame ) {
   1224 			numActive++;
   1225 		}
   1226 	}
   1227 
   1228 	return numActive;
   1229 }
   1230 
   1231 
   1232 /*
   1233 ========================
   1234 idLobby::SendMatchParmsToPeers
   1235 ========================
   1236 */
   1237 void idLobby::SendMatchParmsToPeers() {	
   1238 	if ( !IsHost() ) {
   1239 		return;
   1240 	}
   1241 
   1242 	if ( GetNumConnectedPeers() == 0 ) {
   1243 		return;
   1244 	}
   1245 
   1246 	byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ];
   1247 	idBitMsg msg( buffer, sizeof( buffer ) );
   1248 	parms.Write( msg );
   1249 	
   1250 	for ( int p = 0; p < peers.Num(); p++ ) {
   1251 		if ( !peers[p].IsConnected() ) {
   1252 			continue;
   1253 		}
   1254 		QueueReliableMessage( p, RELIABLE_MATCH_PARMS, msg.GetReadData(), msg.GetSize() );
   1255 	}
   1256 }
   1257 
   1258 /*
   1259 ========================
   1260 STATIC idLobby::IsReliablePlayerToPlayerType
   1261 ========================
   1262 */
   1263 bool idLobby::IsReliablePlayerToPlayerType( byte type ) {
   1264 	return ( type >= RELIABLE_PLAYER_TO_PLAYER_BEGIN ) && ( type < RELIABLE_PLAYER_TO_PLAYER_END );
   1265 }
   1266 
   1267 /*
   1268 ========================
   1269 idLobby::HandleReliablePlayerToPlayerMsg
   1270 ========================
   1271 */
   1272 void idLobby::HandleReliablePlayerToPlayerMsg( int peerNum, idBitMsg & msg, int type ) {
   1273 	reliablePlayerToPlayerHeader_t info;
   1274 	int c, b;
   1275 	msg.SaveReadState( c, b ); // in case we need to forward or fail
   1276 
   1277 	if ( !info.Read( this, msg ) ) {
   1278 		idLib::Warning( "NET: Ignoring invalid reliable player to player message" );
   1279 		msg.RestoreReadState( c, b );
   1280 		return;
   1281 	}
   1282 
   1283 	const bool isForLocalPlayer = IsSessionUserIndexLocal( info.toSessionUserIndex );
   1284 
   1285 	if ( isForLocalPlayer ) {
   1286 		HandleReliablePlayerToPlayerMsg( info, msg, type );
   1287 	} else if ( IsHost() ) {
   1288 		const int targetPeer = PeerIndexForSessionUserIndex( info.toSessionUserIndex );
   1289 		msg.RestoreReadState( c, b );
   1290 		// forward the rest of the data
   1291 		const byte * data = msg.GetReadData() + msg.GetReadCount();
   1292 		int dataLen = msg.GetSize() - msg.GetReadCount();
   1293 
   1294 		QueueReliableMessage( targetPeer, type, data, dataLen );
   1295 	} else {
   1296 		idLib::Warning( "NET: Can't forward reliable message for remote player: I'm not the host" );
   1297 	}
   1298 }
   1299 
   1300 /*
   1301 ========================
   1302 idLobby::HandleReliablePlayerToPlayerMsg
   1303 ========================
   1304 */
   1305 void idLobby::HandleReliablePlayerToPlayerMsg( const reliablePlayerToPlayerHeader_t & info, idBitMsg & msg, int reliableType ) {
   1306 #if 0
   1307 	// Remember that the reliablePlayerToPlayerHeader_t was already removed from the msg
   1308 	reliablePlayerToPlayer_t type = (reliablePlayerToPlayer_t)( reliableType - RELIABLE_PLAYER_TO_PLAYER_BEGIN );
   1309 
   1310 	switch( type ) {
   1311 		case RELIABLE_PLAYER_TO_PLAYER_VOICE_EVENT: {
   1312 			sessionCB->HandleReliableVoiceEvent( *this, info.fromSessionUserIndex, info.toSessionUserIndex, msg );
   1313 			break;
   1314 		}
   1315 
   1316 		default: {
   1317 			idLib::Warning( "NET: Ignored unknown player to player reliable type %i", (int) type );
   1318 		}
   1319 	};
   1320 #endif
   1321 }
   1322 
   1323 /*
   1324 ========================
   1325 idLobby::SendConnectionLess
   1326 ========================
   1327 */
   1328 void idLobby::SendConnectionLess( const lobbyAddress_t & remoteAddress, byte type, const byte * data, int dataLen ) {
   1329 	idBitMsg msg( data, dataLen );
   1330 	msg.SetSize( dataLen );
   1331 
   1332 	byte buffer[ idPacketProcessor::MAX_OOB_MSG_SIZE ];
   1333 	idBitMsg processedMsg( buffer, sizeof( buffer ) );
   1334 
   1335 	// Process the send
   1336 	idPacketProcessor::ProcessConnectionlessOutgoing( msg, processedMsg, lobbyType, type );
   1337 
   1338 	const bool useDirectPort = ( lobbyType == TYPE_GAME_STATE );
   1339 
   1340 	// Send it
   1341 	sessionCB->SendRawPacket( remoteAddress, processedMsg.GetReadData(), processedMsg.GetSize(), useDirectPort ); 
   1342 }
   1343 
   1344 /*
   1345 ========================
   1346 idLobby::SendConnectionRequest
   1347 ========================
   1348 */
   1349 void idLobby::SendConnectionRequest() {
   1350 	// Some sanity checking
   1351 	assert( state == STATE_CONNECT_HELLO_WAIT );
   1352 	assert( peers[host].GetConnectionState() == CONNECTION_CONNECTING );
   1353 	assert( GetNumLobbyUsers() == 0 );
   1354 
   1355 	// Buffer to hold connect msg
   1356 	byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ];
   1357 	idBitMsg msg( buffer, sizeof( buffer ) );
   1358 
   1359 	// Add the current version info to the handshake
   1360 	const unsigned long localChecksum = NetGetVersionChecksum();
   1361 	
   1362 	NET_VERBOSE_PRINT( "NET: version = %i\n", localChecksum );
   1363 
   1364 	msg.WriteLong( localChecksum );
   1365 	msg.WriteUShort( peers[host].sessionID );
   1366 	msg.WriteBool( connectIsFromInvite );
   1367 
   1368 	// We use InitSessionUsersFromLocalUsers here to copy the current local users over to session users simply to have a list
   1369 	// to send on the initial connection attempt.  We immediately clear our session user list once sent.
   1370 	InitSessionUsersFromLocalUsers( true );
   1371 
   1372 	if ( GetNumLobbyUsers() > 0 ) {
   1373 		// Fill up the msg with the users on this machine
   1374 		msg.WriteByte( GetNumLobbyUsers() );
   1375 
   1376 		for ( int u = 0; u < GetNumLobbyUsers(); u++ ) {
   1377 			GetLobbyUser( u )->WriteToMsg( msg );
   1378 		}
   1379 	} else {
   1380 		FreeAllUsers();
   1381 		SetState( STATE_FAILED );
   1382 
   1383 		return;
   1384 	}
   1385 
   1386 	// We just used these users to fill up the msg above, we will get the real list from the server if we connect.
   1387 	FreeAllUsers();
   1388 
   1389 	NET_VERBOSE_PRINT( "NET: Sending hello to: %s (lobbyType: %s, session ID %i, attempt: %i)\n", hostAddress.ToString(), GetLobbyName(), peers[host].sessionID, connectionAttempts );
   1390 
   1391 	SendConnectionLess( hostAddress, OOB_HELLO, msg.GetReadData(), msg.GetSize() );
   1392 	
   1393 	connectionAttempts++;
   1394 }
   1395 
   1396 /*
   1397 ========================
   1398 idLobby::ConnectTo
   1399 
   1400 Fires off a request to get the address of a lobbyBackend owner, and then attempts to connect (eventually handled in HandleObtainingLobbyOwnerAddress)
   1401 ========================
   1402 */
   1403 void idLobby::ConnectTo( const lobbyConnectInfo_t & connectInfo, bool fromInvite ) {
   1404 	NET_VERBOSE_PRINT( "NET: idSessionLocal::ConnectTo: fromInvite = %i\n", fromInvite );
   1405 
   1406 	// Make sure current session is shutdown
   1407 	Shutdown();
   1408 
   1409 	connectIsFromInvite = fromInvite;
   1410 
   1411 	lobbyBackend = sessionCB->JoinFromConnectInfo( connectInfo, (idLobbyBackend::lobbyBackendType_t)lobbyType );
   1412 
   1413 	// First, we need the address of the lobbyBackend owner
   1414 	lobbyBackend->GetOwnerAddress( hostAddress );
   1415 
   1416 	SetState( STATE_OBTAINING_ADDRESS );
   1417 
   1418 }
   1419 
   1420 /*
   1421 ========================
   1422 idLobby::HandleGoodbyeFromPeer
   1423 ========================
   1424 */
   1425 void idLobby::HandleGoodbyeFromPeer( int peerNum, lobbyAddress_t & remoteAddress, int msgType ) {
   1426 	if ( migrationInfo.state != MIGRATE_NONE ) {
   1427 		// If this peer is on our invite list, remove them
   1428 		for ( int i = 0; i < migrationInfo.invites.Num(); i++ ) {
   1429 			if ( migrationInfo.invites[i].address.Compare( remoteAddress, true ) ) {
   1430 				migrationInfo.invites.RemoveIndex( i );
   1431 				break;
   1432 			}
   1433 		}
   1434 	}
   1435 
   1436 	if ( peerNum < 0 ) {
   1437 		NET_VERBOSE_PRINT( "NET: Goodbye from unknown peer %s on session %s\n", remoteAddress.ToString(), GetLobbyName() );
   1438 		return;
   1439 	}
   1440 
   1441 	if ( peers[peerNum].GetConnectionState() == CONNECTION_FREE ) {
   1442 		NET_VERBOSE_PRINT( "NET: Goodbye from peer %s on session %s that is not connected\n", remoteAddress.ToString(), GetLobbyName() );
   1443 		return;
   1444 	}
   1445 
   1446 	if ( IsHost() ) {
   1447 		// Goodbye from peer, remove him
   1448 		NET_VERBOSE_PRINT( "NET: Goodbye from peer %s, on session %s\n", remoteAddress.ToString(), GetLobbyName() );
   1449 		DisconnectPeerFromSession( peerNum );
   1450 	} else {
   1451 		// Let session handler take care of this
   1452 		NET_VERBOSE_PRINT( "NET: Goodbye from host %s, on session %s\n", remoteAddress.ToString(), GetLobbyName() );
   1453 		sessionCB->GoodbyeFromHost( *this, peerNum, remoteAddress, msgType );
   1454 	}
   1455 }
   1456 
   1457 /*
   1458 ========================
   1459 idLobby::HandleGoodbyeFromPeer
   1460 ========================
   1461 */
   1462 void idLobby::HandleConnectionAttemptFailed() {
   1463 	Shutdown();
   1464 	failedReason = migrationInfo.persistUntilGameEndsData.wasMigratedJoin ? FAILED_MIGRATION_CONNECT_FAILED : FAILED_CONNECT_FAILED;
   1465 	SetState( STATE_FAILED );
   1466 
   1467 	if ( migrationInfo.persistUntilGameEndsData.wasMigratedJoin ) {
   1468 		sessionCB->FailedGameMigration( *this );
   1469 	}
   1470 
   1471 	ResetAllMigrationState();
   1472 
   1473 	needToDisplayMigrateMsg = false;
   1474 	migrateMsgFlags			= 0;
   1475 }
   1476 
   1477 /*
   1478 ========================
   1479 idLobby::ConnectToNextSearchResult
   1480 ========================
   1481 */
   1482 bool idLobby::ConnectToNextSearchResult() {
   1483 	if ( lobbyType != TYPE_GAME ) {
   1484 		return false;		// Only game sessions use matchmaking searches
   1485 	}
   1486 
   1487 	// End current session lobby (this WON'T free search results)
   1488 	Shutdown();
   1489 
   1490 	if ( searchResults.Num() == 0 ) {
   1491 		return false;		// No more search results to connect to, give up
   1492 	}
   1493 	
   1494 	// Get next search result
   1495 	lobbyConnectInfo_t connectInfo = searchResults[0];
   1496 
   1497 	// Remove this search result
   1498 	searchResults.RemoveIndex( 0 );
   1499 
   1500 	// If we are connecting to a game lobby, tell our party to connect to this lobby as well
   1501 	if ( lobbyType == TYPE_GAME && sessionCB->GetPartyLobby().IsLobbyActive() ) {
   1502 		sessionCB->GetPartyLobby().SendMembersToLobby( lobbyType, connectInfo, true );
   1503 	}
   1504 
   1505 	// Attempt to connect the lobby
   1506 	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
   1507 
   1508 	// Clear the "Lobby was Full" dialog in case it's up, since we are going to try to connect to a different lobby now
   1509 	common->Dialog().ClearDialog( GDM_LOBBY_FULL );
   1510 
   1511 	return true;	// Notify caller we are attempting to connect
   1512 }
   1513 
   1514 /*
   1515 ========================
   1516 idLobby::CheckVersion
   1517 ========================
   1518 */
   1519 bool idLobby::CheckVersion( idBitMsg & msg, lobbyAddress_t peerAddress ) {
   1520 	const unsigned long remoteChecksum = msg.ReadLong();
   1521 
   1522 	if ( net_checkVersion.GetInteger() == 1 ) {
   1523 		const unsigned long localChecksum = NetGetVersionChecksum();
   1524 
   1525 		NET_VERBOSE_PRINT( "NET: Comparing handshake version - localChecksum = %i, remoteChecksum = %i\n", localChecksum, remoteChecksum );
   1526 		return ( remoteChecksum == localChecksum );
   1527 	}
   1528 	return true;
   1529 }
   1530 
   1531 /*
   1532 ========================
   1533 idLobby::VerifyNumConnectingUsers
   1534 Make sure number of users connecting is valid, and make sure we have enough room
   1535 ========================
   1536 */
   1537 bool idLobby::VerifyNumConnectingUsers( idBitMsg & msg ) {
   1538 	int c, b;
   1539 	msg.SaveReadState( c, b );
   1540 	const int numUsers = msg.ReadByte();
   1541 	msg.RestoreReadState( c, b );
   1542 
   1543 	const int numFreeSlots = NumFreeSlots();
   1544 
   1545 	NET_VERBOSE_PRINT( "NET: VerifyNumConnectingUsers %i users, %i free slots for %s\n", numUsers, numFreeSlots, GetLobbyName() );
   1546 
   1547 	if ( numUsers <= 0 || numUsers > MAX_PLAYERS - 1 ) {
   1548 		NET_VERBOSE_PRINT( "NET: Invalid numUsers %i\n", numUsers );
   1549 		return false;
   1550 	} else if ( numUsers > numFreeSlots ) {
   1551 		NET_VERBOSE_PRINT( "NET: %i slots requested, but only %i are available\n", numUsers, numFreeSlots );
   1552 		return false;
   1553 	} else if ( lobbyType == TYPE_PARTY && sessionCB->GetState() >= idSession::GAME_LOBBY && sessionCB->GetGameLobby().IsLobbyActive() && !IsMigrating() ) {
   1554 		const int numFreeGameSlots = sessionCB->GetGameLobby().NumFreeSlots();
   1555 
   1556 		if ( numUsers > numFreeGameSlots ) {
   1557 			NET_VERBOSE_PRINT( "NET: %i slots requested, but only %i are available on the active game session\n", numUsers, numFreeGameSlots );
   1558 			return false;
   1559 		}
   1560 	}
   1561 
   1562 	return true;
   1563 }
   1564 
   1565 /*
   1566 ========================
   1567 idLobby::VerifyLobbyUserIDs
   1568 ========================
   1569 */
   1570 bool idLobby::VerifyLobbyUserIDs( idBitMsg & msg ) {
   1571 	int c, b;
   1572 	msg.SaveReadState( c, b );
   1573 	const int numUsers = msg.ReadByte();
   1574 
   1575 	// Add the new users to our own list
   1576 	for ( int u = 0; u < numUsers; u++ ) {
   1577 		lobbyUser_t newUser;
   1578 		
   1579 		// Read in the new user
   1580 		newUser.ReadFromMsg( msg );
   1581 
   1582 		if ( GetLobbyUserIndexByID( newUser.lobbyUserID, true ) != -1 ) {
   1583 			msg.RestoreReadState( c, b );
   1584 			return false;
   1585 		}
   1586 	}
   1587 
   1588 	msg.RestoreReadState( c, b );
   1589 
   1590 	return true;
   1591 }
   1592 
   1593 /*
   1594 ========================
   1595 idLobby::HandleInitialPeerConnection
   1596 Received on an initial peer connect request (OOB_HELLO)
   1597 ========================
   1598 */
   1599 int idLobby::HandleInitialPeerConnection( idBitMsg & msg, const lobbyAddress_t & peerAddress, int peerNum ) {
   1600 	if ( net_ignoreConnects.GetInteger() > 0 ) {
   1601 		if ( net_ignoreConnects.GetInteger() == 2 ) {
   1602 			SendGoodbye( peerAddress );
   1603 		}
   1604 		return -1;
   1605 	}
   1606 
   1607 	if ( !IsHost() ) {
   1608 		NET_VERBOSE_PRINT( "NET: Got connectionless hello from peer %s on session, and we are not a host\n", peerAddress.ToString() );
   1609 		SendGoodbye( peerAddress );
   1610 		return -1;
   1611 	}
   1612 	
   1613 	// See if this is a peer migrating to us, if so, remove them from our invite list
   1614 	bool migrationInvite = false;
   1615 	int migrationGameData = -1;
   1616 
   1617 	
   1618 	for ( int i = migrationInfo.invites.Num() - 1; i >= 0; i-- ) {
   1619 		if ( migrationInfo.invites[i].address.Compare( peerAddress, true ) ) {
   1620 				
   1621 			migrationGameData = migrationInfo.invites[i].migrationGameData;
   1622 			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)
   1623 			migrationInvite = true;
   1624 			NET_VERBOSE_PRINT( "^2NET: Response from migration invite %s. GameData: %d\n", peerAddress.ToString(), migrationGameData );
   1625 		}
   1626 	}
   1627 
   1628 	if ( !MatchTypeIsJoinInProgress( parms.matchFlags ) && lobbyType == TYPE_GAME && migrationInfo.persistUntilGameEndsData.wasMigratedHost && IsMigratedStatsGame() && !migrationInvite ) {
   1629 		// No matter what, don't let people join migrated game sessions that are going to continue on to the same game
   1630 		// Not on invite list in a migrated game session - bounce him
   1631 		NET_VERBOSE_PRINT( "NET: Denying game connection from %s since not on migration invite list\n", peerAddress.ToString() );
   1632 		for ( int i = migrationInfo.invites.Num() - 1; i >= 0; i-- ) {
   1633 			NET_VERBOSE_PRINT( "   Invite[%d] addr: %s\n", i, migrationInfo.invites[i].address.ToString() );
   1634 		}
   1635 		SendGoodbye( peerAddress );
   1636 		return -1;
   1637 	}
   1638 
   1639 	
   1640 	if ( MatchTypeIsJoinInProgress( parms.matchFlags ) ) {
   1641 		// If this is for a game connection, make sure we have a game lobby
   1642 		if ( ( lobbyType == TYPE_GAME || lobbyType == TYPE_GAME_STATE ) && sessionCB->GetState() < idSession::GAME_LOBBY ) {
   1643 			NET_VERBOSE_PRINT( "NET: Denying game connection from %s because we don't have a game lobby\n", peerAddress.ToString() );
   1644 			SendGoodbye( peerAddress );
   1645 			return -1;
   1646 		}
   1647 	} else {
   1648 		// If this is for a game connection, make sure we are in the game lobby
   1649 		if ( lobbyType == TYPE_GAME && sessionCB->GetState() != idSession::GAME_LOBBY ) {
   1650 			NET_VERBOSE_PRINT( "NET: Denying game connection from %s while not in game lobby\n", peerAddress.ToString() );
   1651 			SendGoodbye( peerAddress );
   1652 			return -1;
   1653 		}
   1654 	
   1655 		// If this is for a party connection, make sure we are not in game, unless this was for host migration invite
   1656 		if ( !migrationInvite && lobbyType == TYPE_PARTY && ( sessionCB->GetState() == idSession::INGAME || sessionCB->GetState() == idSession::LOADING ) ) {
   1657 			NET_VERBOSE_PRINT( "NET: Denying party connection from %s because we were already in a game\n", peerAddress.ToString() );
   1658 			SendGoodbye( peerAddress );
   1659 			return -1;
   1660 		}
   1661 	}
   1662 
   1663 	if ( !CheckVersion( msg, peerAddress ) ) {
   1664 		idLib::Printf( "NET: Denying user %s with wrong version number\n", peerAddress.ToString() );
   1665 		SendGoodbye( peerAddress );
   1666 		return -1;
   1667 	}
   1668 
   1669 	idPacketProcessor::sessionId_t sessionID = msg.ReadUShort();
   1670 
   1671 	// Check to see if this is a peer trying to connect with a different sessionID
   1672 	// If the peer got abruptly disconnected, the peer could be trying to reconnect from a non clean disconnect
   1673 	if ( peerNum >= 0 ) {
   1674 		peer_t & existingPeer = peers[peerNum];
   1675 
   1676 		assert( existingPeer.GetConnectionState() != CONNECTION_FREE );
   1677 
   1678 		if ( existingPeer.sessionID == sessionID ) {
   1679 			return peerNum;		// If this is the same sessionID, then assume redundant connection attempt
   1680 		}
   1681 		
   1682 		//
   1683 		// This peer must be trying to reconnect from a previous abrupt disconnect
   1684 		//
   1685 
   1686 		NET_VERBOSE_PRINT( "NET: Reconnecting peer %s for session %s\n", peerAddress.ToString(), GetLobbyName() );
   1687 
   1688 		// Assume a peer is trying to reconnect from a non clean disconnect
   1689 		// We want to set the connection back to FREE manually, so we don't send a goodbye
   1690 		existingPeer.connectionState = CONNECTION_FREE;
   1691 		
   1692 		if ( existingPeer.packetProc != NULL ) {
   1693 			delete existingPeer.packetProc;
   1694 			existingPeer.packetProc = NULL;
   1695 		}
   1696 		
   1697 		if ( existingPeer.snapProc != NULL ) {
   1698 			assert( lobbyType == TYPE_GAME );		// Only games sessions should be creating snap processors
   1699 			delete existingPeer.snapProc;
   1700 			existingPeer.snapProc = NULL;
   1701 		}
   1702 
   1703 		RemoveUsersWithDisconnectedPeers();
   1704 
   1705 		peerNum = -1;
   1706 	}
   1707 
   1708 	// See if this was from an invite we sent out. If it wasn't, make sure we aren't invite only
   1709 	const bool fromInvite = msg.ReadBool();
   1710 
   1711 	if ( !fromInvite && MatchTypeInviteOnly( parms.matchFlags ) ) {
   1712 		idLib::Printf( "NET: Denying user %s because they were not invited to an invite only match\n", peerAddress.ToString() );
   1713 		SendGoodbye( peerAddress );
   1714 		return -1;
   1715 	}
   1716 
   1717 	// Make sure we have room for the users connecting
   1718 	if ( !VerifyNumConnectingUsers( msg ) ) {
   1719 		NET_VERBOSE_PRINT( "NET: Denying connection from %s in session %s due to being out of user slots\n", peerAddress.ToString(), GetLobbyName() );
   1720 		SendGoodbye( peerAddress, true );
   1721 		return -1;
   1722 	}
   1723 
   1724 	// Make sure there are no lobby id conflicts
   1725 	if ( !verify( VerifyLobbyUserIDs( msg ) ) ) {
   1726 		NET_VERBOSE_PRINT( "NET: Denying connection from %s in session %s due to lobby id conflict\n", peerAddress.ToString(), GetLobbyName() );
   1727 		SendGoodbye( peerAddress, true );
   1728 		return -1;
   1729 	}
   1730 
   1731 	// Calling AddPeer will set our connectionState to this peer as CONNECTION_CONNECTING (which will get set to CONNECTION_ESTABLISHED below)
   1732 	peerNum = AddPeer( peerAddress, sessionID );
   1733 
   1734 	peer_t & newPeer = peers[peerNum];
   1735 
   1736 	assert( newPeer.GetConnectionState() == CONNECTION_CONNECTING );
   1737 	assert( lobbyType != GetActingGameStateLobbyType() || newPeer.snapProc != NULL );
   1738 	
   1739 	// First, add users from this new peer to our user list 
   1740 	// (which will then forward the list to all peers except peerNum)
   1741 	AddUsersFromMsg( msg, peerNum );
   1742 
   1743 	// Mark the peer as connected for this session type
   1744 	SetPeerConnectionState( peerNum, CONNECTION_ESTABLISHED );
   1745 	
   1746 	// Update their heart beat to current
   1747 	newPeer.lastHeartBeat = Sys_Milliseconds();
   1748 
   1749 	byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ];
   1750 	idBitMsg outmsg( buffer, sizeof( buffer ) );
   1751 
   1752 	// Let them know their peer index on this host
   1753 	// peerIndexOnHost (put this here so it shows up in search results when finding out where it's used/referenced)
   1754 	outmsg.WriteLong( peerNum );
   1755 
   1756 	// If they are connecting to our party lobby, let them know the party token
   1757 	if ( lobbyType == TYPE_PARTY ) {
   1758 		outmsg.WriteLong( GetPartyTokenAsHost() );
   1759 	}
   1760 
   1761 	if ( lobbyType == TYPE_GAME || lobbyType == TYPE_GAME_STATE ) {
   1762 		// If this is a game session, reset the loading and ingame flags
   1763 		newPeer.loaded = false;
   1764 		newPeer.inGame = false;
   1765 	}
   1766 	
   1767 	// Write out current match parms
   1768 	parms.Write( outmsg );
   1769 
   1770 	// Send list of existing users to this new peer 
   1771 	// (the users from the new peer will also be in this list, since we already called AddUsersFromMsg)
   1772 	outmsg.WriteByte( GetNumLobbyUsers() );
   1773 
   1774 	for ( int u = 0; u < GetNumLobbyUsers(); u++ ) {
   1775 		GetLobbyUser( u )->WriteToMsg( outmsg );
   1776 	}
   1777 
   1778 	lobbyBackend->FillMsgWithPostConnectInfo( outmsg );
   1779 
   1780 	NET_VERBOSE_PRINT( "NET: Sending response to %s, lobbyType %s, sessionID %i\n", peerAddress.ToString(), GetLobbyName(), sessionID );
   1781 
   1782 	QueueReliableMessage( peerNum, RELIABLE_HELLO, outmsg.GetReadData(), outmsg.GetSize() );
   1783 
   1784 	if ( MatchTypeIsJoinInProgress( parms.matchFlags ) ) {
   1785 		// If have an active game lobby, and someone joins our party, tell them to join our game
   1786 		if ( lobbyType == TYPE_PARTY && sessionCB->GetState() >= idSession::GAME_LOBBY ) {
   1787 			SendPeerMembersToLobby( peerNum, TYPE_GAME, false );
   1788 		}
   1789 
   1790 		// We are are ingame, then start the client loading immediately
   1791 		if ( ( lobbyType == TYPE_GAME || lobbyType == TYPE_GAME_STATE ) && sessionCB->GetState() >= idSession::LOADING ) {
   1792 			idLib::Printf( "******* JOIN IN PROGRESS ********\n" );
   1793 			if ( sessionCB->GetState() == idSession::INGAME ) {
   1794 				newPeer.pauseSnapshots = true;		// Since this player joined in progress, let game dictate when to start sending snaps
   1795 			}
   1796 			QueueReliableMessage( peerNum, idLobby::RELIABLE_START_LOADING );
   1797 		}
   1798 	} else {
   1799 		// If we are in a game lobby, and someone joins our party, tell them to join our game
   1800 		if ( lobbyType == TYPE_PARTY && sessionCB->GetState() == idSession::GAME_LOBBY ) {
   1801 			SendPeerMembersToLobby( peerNum, TYPE_GAME, false );
   1802 		}
   1803 	}
   1804 
   1805 	// Send mic status of the current lobby to applicable peers
   1806 	SendPeersMicStatusToNewUsers( peerNum );
   1807 
   1808 	// If we made is this far, update the users migration game data index
   1809 	for ( int u = 0; u < GetNumLobbyUsers(); u++ ) {
   1810 		if ( GetLobbyUser( u )->peerIndex == peerNum ) {
   1811 			GetLobbyUser( u )->migrationGameData = migrationGameData;
   1812 		}
   1813 	}
   1814 
   1815 	return peerNum;
   1816 }
   1817 
   1818 /*
   1819 ========================
   1820 idLobby::InitStateLobbyHost
   1821 ========================
   1822 */
   1823 void idLobby::InitStateLobbyHost() {
   1824 	assert( lobbyBackend != NULL );
   1825 
   1826 	// We will be the host
   1827 	isHost = true;
   1828 
   1829 	if ( net_headlessServer.GetBool() ) {
   1830 		return;		// Don't add any players to headless server
   1831 	}
   1832 
   1833 	if ( migrationInfo.state != MIGRATE_NONE  ) {
   1834 		migrationInfo.persistUntilGameEndsData.wasMigratedHost = true; // InitSessionUsersFromLocalUsers needs to know this
   1835 		migrationInfo.persistUntilGameEndsData.hasRelaunchedMigratedGame = false;
   1836 		// migrationDlg = GDM_MIGRATING_WAITING;
   1837 	}
   1838 
   1839 	// Initialize the initial user list for this lobby
   1840 	InitSessionUsersFromLocalUsers( MatchTypeIsOnline( parms.matchFlags ) );
   1841 
   1842 	// Set the session's hostAddress to the local players' address.
   1843 	const int myUserIndex = GetLobbyUserIndexByLocalUserHandle( sessionCB->GetSignInManager().GetMasterLocalUserHandle() );
   1844 	if ( myUserIndex != -1 ) {
   1845 		hostAddress = GetLobbyUser( myUserIndex )->address;
   1846 	}
   1847 
   1848 	// Since we are the host, we have to register our initial session users with the lobby
   1849 	// All additional users will join through AddUsersFromMsg, and RegisterUser is handled in there from here on out.
   1850 	// Peers will add users exclusively through AddUsersFromMsg.
   1851 	for ( int i = 0; i < GetNumLobbyUsers(); i++ ) {
   1852 		lobbyUser_t * user = GetLobbyUser( i );
   1853 		RegisterUser( user );
   1854 		if ( lobbyType == TYPE_PARTY ) {
   1855 			user->partyToken = GetPartyTokenAsHost();
   1856 		}
   1857 	}
   1858 
   1859 	// Set the lobbies skill level
   1860 	lobbyBackend->UpdateLobbySkill( GetAverageSessionLevel() );
   1861 
   1862 	// Make sure and register all the addresses of the invites we'll send out as the new host
   1863 	if ( migrationInfo.state != MIGRATE_NONE ) {
   1864 		// Tell the session that we became the host, so the session mgr can adjust state if needed
   1865 		sessionCB->BecameHost( *this );
   1866 
   1867 		// Register this address with this lobbyBackend
   1868 		for ( int i = 0; i < migrationInfo.invites.Num(); i++ ) {
   1869 			lobbyBackend->RegisterAddress( migrationInfo.invites[i].address );
   1870 		}
   1871 	}
   1872 }
   1873 
   1874 /*
   1875 ========================
   1876 idLobby::SendMembersToLobby
   1877 ========================
   1878 */
   1879 void idLobby::SendMembersToLobby( lobbyType_t destLobbyType, const lobbyConnectInfo_t & connectInfo, bool waitForOtherMembers ) {
   1880 
   1881 	// It's not our job to send party members to a game if we aren't the party host
   1882 	if ( !IsHost() ) {
   1883 		return;
   1884 	}
   1885 
   1886 	// Send the message to all connected peers
   1887 	for ( int i = 0; i < peers.Num(); i++ ) {
   1888 		if ( peers[ i ].IsConnected() ) {
   1889 			SendPeerMembersToLobby( i, destLobbyType, connectInfo, waitForOtherMembers );
   1890 		}
   1891 	}
   1892 }
   1893 
   1894 /*
   1895 ========================
   1896 idLobby::SendMembersToLobby
   1897 ========================
   1898 */
   1899 void idLobby::SendMembersToLobby( idLobby & destLobby, bool waitForOtherMembers ) {
   1900 	if ( destLobby.lobbyBackend == NULL ) {
   1901 		return;		// We don't have a game lobbyBackend to get an address for
   1902 	}
   1903 
   1904 	lobbyConnectInfo_t connectInfo = destLobby.lobbyBackend->GetConnectInfo();
   1905 
   1906 	SendMembersToLobby( destLobby.lobbyType, connectInfo, waitForOtherMembers );
   1907 }
   1908 
   1909 /*
   1910 ========================
   1911 idLobby::SendPeerMembersToLobby
   1912 Give the address of a game lobby to a particular peer, notifying that peer to send a hello to the same server.
   1913 ========================
   1914 */
   1915 void idLobby::SendPeerMembersToLobby( int peerIndex, lobbyType_t destLobbyType, const lobbyConnectInfo_t & connectInfo, bool waitForOtherMembers ) {
   1916 	// It's not our job to send party members to a game if we aren't the party host
   1917 	if ( !IsHost() ) {
   1918 		return;
   1919 	}
   1920 
   1921 	assert( peerIndex >= 0 );
   1922 	assert( peerIndex < peers.Num() );
   1923 	peer_t & peer = peers[ peerIndex ];
   1924 
   1925 	NET_VERBOSE_PRINT( "NET: Sending peer %i (%s) to game lobby\n", peerIndex, peer.address.ToString() );
   1926 
   1927 	if ( !peer.IsConnected() ) {
   1928 		idLib::Warning( "NET: Can't send peer %i to game lobby: peer isn't in party", peerIndex );
   1929 		return;
   1930 	}
   1931 
   1932 	byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ];
   1933 	idBitMsg outmsg( buffer, sizeof( buffer ) );
   1934 
   1935 	// Have lobby fill out msg with connection info
   1936 	connectInfo.WriteToMsg( outmsg );
   1937 
   1938 	outmsg.WriteByte( destLobbyType );
   1939 	outmsg.WriteBool( waitForOtherMembers );
   1940 
   1941 	QueueReliableMessage( peerIndex, RELIABLE_CONNECT_AND_MOVE_TO_LOBBY, outmsg.GetReadData(), outmsg.GetSize() );
   1942 }
   1943 
   1944 /*
   1945 ========================
   1946 idLobby::SendPeerMembersToLobby
   1947 
   1948 Give the address of a game lobby to a particular peer, notifying that peer to send a hello to the same server.
   1949 ========================
   1950 */
   1951 void idLobby::SendPeerMembersToLobby( int peerIndex, lobbyType_t destLobbyType, bool waitForOtherMembers ) {
   1952 	idLobby * lobby = sessionCB->GetLobbyFromType( destLobbyType );
   1953 
   1954 	if ( !verify( lobby != NULL ) ) {
   1955 		return;
   1956 	}
   1957 
   1958 	if ( !verify( lobby->lobbyBackend != NULL ) ) {
   1959 		return;
   1960 	}
   1961 
   1962 	lobbyConnectInfo_t connectInfo = lobby->lobbyBackend->GetConnectInfo();
   1963 
   1964 	SendPeerMembersToLobby( peerIndex, destLobbyType, connectInfo, waitForOtherMembers );
   1965 }
   1966 
   1967 /*
   1968 ========================
   1969 idLobby::NotifyPartyOfLeavingGameLobby
   1970 ========================
   1971 */
   1972 void idLobby::NotifyPartyOfLeavingGameLobby() {
   1973 	if ( lobbyType != TYPE_PARTY ) {
   1974 		return;		// We are not a party lobby
   1975 	}
   1976 
   1977 	if ( !IsHost() ) {
   1978 		return;		// We are not the host of a party lobby, we can't do this
   1979 	}
   1980 
   1981 	if ( !( sessionCB->GetSessionOptions() & idSession::OPTION_LEAVE_WITH_PARTY ) ) {
   1982 		return;		// Options aren't set to notify party of leaving
   1983 	}
   1984 
   1985 	// Tell our party to leave the game they are in
   1986 	for ( int i = 0; i < peers.Num(); i++ ) {
   1987 		if ( peers[ i ].IsConnected() ) {
   1988 			QueueReliableMessage( i, RELIABLE_PARTY_LEAVE_GAME_LOBBY );
   1989 		}
   1990 	}
   1991 }
   1992 
   1993 /*
   1994 ========================
   1995 idLobby::GetPartyTokenAsHost
   1996 ========================
   1997 */
   1998 uint32 idLobby::GetPartyTokenAsHost() {
   1999 	assert( lobbyType == TYPE_PARTY );
   2000 	assert( IsHost() );
   2001 
   2002 	if ( partyToken == 0 ) {
   2003 		// I don't know if this is mathematically sound, but it seems reasonable.
   2004 		// Don't do this at app startup (i.e. in the constructor) or it will be a lot less random.
   2005 		unsigned long seed = Sys_Milliseconds(); // time app has been running
   2006 		idLocalUser * masterUser = session->GetSignInManager().GetMasterLocalUser();
   2007 		if ( masterUser != NULL ) {
   2008 			seed += idStr::Hash( masterUser->GetGamerTag() );
   2009 		}
   2010 		partyToken = idRandom( seed ).RandomInt();
   2011 		idLib::Printf( "NET: PartyToken is %u (seed = %u)\n", partyToken, seed );
   2012 	}
   2013 	return partyToken;
   2014 }
   2015 
   2016 /*
   2017 ========================
   2018 idLobby::EncodeSessionID
   2019 ========================
   2020 */
   2021 idPacketProcessor::sessionId_t idLobby::EncodeSessionID( uint32 key ) const {
   2022 	assert( sizeof( uint32 ) >= sizeof( idPacketProcessor::sessionId_t ) );
   2023 	const int numBits = sizeof( idPacketProcessor::sessionId_t ) * 8 - idPacketProcessor::NUM_LOBBY_TYPE_BITS;
   2024 	const uint32 mask = ( 1 << numBits ) - 1;
   2025 	idPacketProcessor::sessionId_t sessionID = ( key & mask ) << idPacketProcessor::NUM_LOBBY_TYPE_BITS;
   2026 	sessionID |= ( lobbyType + 1 );
   2027 	return sessionID;
   2028 }
   2029 
   2030 /*
   2031 ========================
   2032 idLobby::EncodeSessionID
   2033 ========================
   2034 */
   2035 void idLobby::DecodeSessionID( idPacketProcessor::sessionId_t sessionID, uint32 & key ) const {
   2036 	assert( sizeof( uint32 ) >= sizeof( idPacketProcessor::sessionId_t ) );
   2037 	key = sessionID >> idPacketProcessor::NUM_LOBBY_TYPE_BITS;
   2038 }
   2039 
   2040 /*
   2041 ========================
   2042 idLobby::GenerateSessionID
   2043 ========================
   2044 */
   2045 idPacketProcessor::sessionId_t idLobby::GenerateSessionID() const {
   2046 	idPacketProcessor::sessionId_t sessionID = EncodeSessionID( Sys_Milliseconds() );
   2047 	
   2048 	// Make sure we can use it
   2049 	while ( !SessionIDCanBeUsedForInBand( sessionID ) ) {
   2050 		sessionID = IncrementSessionID( sessionID );
   2051 	}
   2052 	
   2053 	return sessionID;
   2054 }
   2055 
   2056 /*
   2057 ========================
   2058 idLobby::SessionIDCanBeUsedForInBand
   2059 ========================
   2060 */
   2061 bool idLobby::SessionIDCanBeUsedForInBand( idPacketProcessor::sessionId_t sessionID ) const {
   2062 	if ( sessionID == idPacketProcessor::SESSION_ID_INVALID ) {
   2063 		return false;
   2064 	}
   2065 
   2066 	if ( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_PARTY ) {
   2067 		return false;
   2068 	}
   2069 	
   2070 	if ( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME ) {
   2071 		return false;
   2072 	}
   2073 
   2074 	if ( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME_STATE ) {
   2075 		return false;
   2076 	}
   2077 
   2078 	return true;
   2079 }
   2080 
   2081 /*
   2082 ========================
   2083 idLobby::IncrementSessionID
   2084 ========================
   2085 */
   2086 idPacketProcessor::sessionId_t idLobby::IncrementSessionID( idPacketProcessor::sessionId_t sessionID ) const {
   2087 	// Increment, taking into account valid id's
   2088 	while ( 1 ) {
   2089 		uint32 key = 0;
   2090 		
   2091 		DecodeSessionID( sessionID, key );
   2092 		
   2093 		key++;
   2094 		
   2095 		sessionID = EncodeSessionID( key );
   2096 	
   2097 		if ( SessionIDCanBeUsedForInBand( sessionID ) ) {
   2098 			break;
   2099 		}
   2100 	}
   2101 	
   2102 	return sessionID;
   2103 }
   2104 
   2105 #define VERIFY_CONNECTED_PEER( p, sessionType_, msgType )				\
   2106 	if ( !verify( lobbyType == sessionType_ ) ) {						\
   2107 		idLib::Printf( "NET: " #msgType ", peer:%s invalid session type for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ );	\
   2108 		return;															\
   2109 	}																	\
   2110 	if ( peers[p].GetConnectionState() != CONNECTION_ESTABLISHED ) {	\
   2111 		idLib::Printf( "NET: " #msgType ", peer:%s not connected for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ );	\
   2112 		return;															\
   2113 	}
   2114 
   2115 #define VERIFY_CONNECTING_PEER( p, sessionType_, msgType )				\
   2116 	if ( !verify( lobbyType == sessionType_ ) ) {						\
   2117 		idLib::Printf( "NET: " #msgType ", peer:%s invalid session type for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ );	\
   2118 		return;															\
   2119 	}																	\
   2120 	if ( peers[p].GetConnectionState() != CONNECTION_CONNECTING ) {		\
   2121 		idLib::Printf( "NET: " #msgType ", peer:%s not connecting for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ );	\
   2122 		return;															\
   2123 	}
   2124 
   2125 #define VERIFY_FROM_HOST( p, sessionType_, msgType )					\
   2126 	VERIFY_CONNECTED_PEER( p, sessionType_, msgType );					\
   2127 	if ( p != host ) {													\
   2128 		idLib::Printf( "NET: "#msgType", not from "#sessionType_" host: %s\n", peer.address.ToString() );	\
   2129 		return;															\
   2130 	}																	\
   2131 
   2132 #define VERIFY_FROM_CONNECTING_HOST( p, sessionType_, msgType )			\
   2133 	VERIFY_CONNECTING_PEER( p, sessionType_, msgType );					\
   2134 	if ( p != host ) {													\
   2135 		idLib::Printf( "NET: "#msgType", not from "#sessionType_" host: %s\n", peer.address.ToString() );	\
   2136 		return;															\
   2137 	}																	\
   2138 
   2139 /*
   2140 ========================
   2141 idLobby::HandleHelloAck
   2142 ========================
   2143 */
   2144 void idLobby::HandleHelloAck( int p, idBitMsg & msg ) {
   2145 	peer_t & peer = peers[p];
   2146 
   2147 	if ( state != STATE_CONNECT_HELLO_WAIT ) {
   2148 		idLib::Printf( "NET: Hello ack for session type %s while not waiting for hello.\n", GetLobbyName() );
   2149 		SendGoodbye( peer.address );		// We send a customary goodbye to make sure we are not in their list anymore
   2150 		return;
   2151 	}
   2152 	if ( p != host ) {
   2153 		// This shouldn't be possible
   2154 		idLib::Printf( "NET: Hello ack for session type %s, not from correct host.\n", GetLobbyName() );
   2155 		SendGoodbye( peer.address );		// We send a customary goodbye to make sure we are not in their list anymore
   2156 		return;
   2157 	}
   2158 
   2159 	assert( GetNumLobbyUsers() == 0 );
   2160 
   2161 	NET_VERBOSE_PRINT( "NET: Hello ack for session type %s from %s\n", GetLobbyName(), peer.address.ToString() );
   2162 
   2163 	// We are now connected to this session type
   2164 	SetPeerConnectionState( p, CONNECTION_ESTABLISHED );
   2165 
   2166 	// Obtain what our peer index is on the host is
   2167 	peerIndexOnHost = msg.ReadLong();
   2168 
   2169 	// If we connected to a party lobby, get the party token from the lobby owner
   2170 	if ( lobbyType == TYPE_PARTY ) {
   2171 		partyToken = msg.ReadLong();
   2172 	}
   2173 
   2174 	// Read match parms
   2175 	parms.Read( msg );
   2176 
   2177 	// Update lobbyBackend with parms
   2178 	if ( lobbyBackend != NULL ) {
   2179 		lobbyBackend->UpdateMatchParms( parms );
   2180 	}
   2181 
   2182 	// Populate the user list with the one from the host (which will also include our local users)
   2183 	// This ensures the user lists are kept in sync
   2184 	FreeAllUsers();
   2185 	AddUsersFromMsg( msg, p );
   2186 
   2187 	// Make sure the host has a current heartbeat
   2188 	peer.lastHeartBeat = Sys_Milliseconds();
   2189 
   2190 	lobbyBackend->PostConnectFromMsg( msg );
   2191 
   2192 	// Tell the lobby controller to finalize the connection
   2193 	SetState( STATE_FINALIZE_CONNECT );
   2194 
   2195 	//
   2196 	// Success - We've received an ack from the server, letting us know we've been registered with the lobbies
   2197 	//
   2198 }
   2199 
   2200 /*
   2201 ========================
   2202 idLobby::GetLobbyUserName
   2203 ========================
   2204 */
   2205 const char * idLobby::GetLobbyUserName( lobbyUserID_t lobbyUserID ) const { 
   2206 	const int index = GetLobbyUserIndexByID( lobbyUserID );
   2207 	const lobbyUser_t * user = GetLobbyUser( index );
   2208 
   2209 	if ( user == NULL ) {
   2210 		for ( int i = 0; i < disconnectedUsers.Num(); i++ ) {
   2211 			if ( disconnectedUsers[i].lobbyUserID.CompareIgnoreLobbyType( lobbyUserID ) ) {
   2212 				return disconnectedUsers[i].gamertag;
   2213 			}
   2214 		}
   2215 		return INVALID_LOBBY_USER_NAME;
   2216 	}
   2217 
   2218 	return user->gamertag;
   2219 }
   2220 
   2221 /*
   2222 ========================
   2223 idLobby::GetLobbyUserSkinIndex
   2224 ========================
   2225 */
   2226 int idLobby::GetLobbyUserSkinIndex( lobbyUserID_t lobbyUserID ) const {
   2227 	const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
   2228 	const lobbyUser_t * user = GetLobbyUser( userIndex );
   2229 	return user ? user->selectedSkin : 0;
   2230 }
   2231 
   2232 /*
   2233 ========================
   2234 idLobby::GetLobbyUserWeaponAutoSwitch
   2235 ========================
   2236 */
   2237 bool idLobby::GetLobbyUserWeaponAutoSwitch( lobbyUserID_t lobbyUserID ) const {
   2238 	const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
   2239 	const lobbyUser_t * user = GetLobbyUser( userIndex );
   2240 	return user ? user->weaponAutoSwitch : true;
   2241 }
   2242 
   2243 /*
   2244 ========================
   2245 idLobby::GetLobbyUserWeaponAutoReload
   2246 ========================
   2247 */
   2248 bool idLobby::GetLobbyUserWeaponAutoReload( lobbyUserID_t lobbyUserID ) const {
   2249 	const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
   2250 	const lobbyUser_t * user = GetLobbyUser( userIndex );
   2251 	return user ? user->weaponAutoReload: true;
   2252 }
   2253 
   2254 /*
   2255 ========================
   2256 idLobby::GetLobbyUserLevel
   2257 ========================
   2258 */
   2259 int idLobby::GetLobbyUserLevel( lobbyUserID_t lobbyUserID ) const {
   2260 	const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
   2261 	const lobbyUser_t * user = GetLobbyUser( userIndex );
   2262 	return user ? user->level : 0;
   2263 }
   2264 
   2265 /*
   2266 ========================
   2267 idLobby::GetLobbyUserQoS
   2268 ========================
   2269 */
   2270 int idLobby::GetLobbyUserQoS( lobbyUserID_t lobbyUserID ) const {
   2271 	const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
   2272 
   2273 	if ( IsHost() && IsSessionUserIndexLocal( userIndex ) ) {
   2274 		return 0;		// Local users on the host of the active session have 0 ping
   2275 	}
   2276 
   2277 	const lobbyUser_t * user = GetLobbyUser( userIndex );
   2278 
   2279 	if ( !verify( user != NULL ) ) {
   2280 		return 0;
   2281 	}
   2282 
   2283 	return user->pingMs;
   2284 }
   2285 
   2286 /*
   2287 ========================
   2288 idLobby::GetLobbyUserTeam
   2289 ========================
   2290 */
   2291 int idLobby::GetLobbyUserTeam( lobbyUserID_t lobbyUserID ) const {
   2292 	const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
   2293 	const lobbyUser_t * user = GetLobbyUser( userIndex );
   2294 	return user ? user->teamNumber : 0;
   2295 }
   2296 
   2297 /*
   2298 ========================
   2299 idLobby::SetLobbyUserTeam
   2300 ========================
   2301 */
   2302 bool idLobby::SetLobbyUserTeam( lobbyUserID_t lobbyUserID, int teamNumber ) {
   2303 	const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
   2304 	lobbyUser_t * user = GetLobbyUser( userIndex );
   2305 
   2306 	if ( user != NULL ) {
   2307 		if ( teamNumber != user->teamNumber ) {
   2308 			user->teamNumber = teamNumber;
   2309 			if ( IsHost() ) {
   2310 				byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ];
   2311 				idBitMsg msg( buffer, sizeof( buffer ) );
   2312 				CreateUserUpdateMessage( userIndex, msg );
   2313 				idBitMsg readMsg;
   2314 				readMsg.InitRead( buffer, msg.GetSize() );
   2315 				UpdateSessionUserOnPeers( readMsg );
   2316 			}
   2317 			return true;
   2318 		}
   2319 	}
   2320 	return false;
   2321 }
   2322 
   2323 /*
   2324 ========================
   2325 idLobby::GetLobbyUserPartyToken
   2326 ========================
   2327 */
   2328 int idLobby::GetLobbyUserPartyToken( lobbyUserID_t lobbyUserID ) const {
   2329 	const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
   2330 	const lobbyUser_t * user = GetLobbyUser( userIndex );
   2331 	return user ? user->partyToken : 0;
   2332 }
   2333 
   2334 /*
   2335 ========================
   2336 idLobby::GetProfileFromLobbyUser
   2337 ========================
   2338 */
   2339 idPlayerProfile * idLobby::GetProfileFromLobbyUser( lobbyUserID_t lobbyUserID ) {
   2340 	const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
   2341 
   2342 	idPlayerProfile * profile = NULL;
   2343 
   2344 	idLocalUser * localUser = GetLocalUserFromLobbyUserIndex( userIndex );
   2345 	
   2346 	if ( localUser != NULL ) {
   2347 		profile = localUser->GetProfile();
   2348 	}
   2349 	
   2350 	if ( profile == NULL ) {
   2351 		// Whoops
   2352 		profile = session->GetSignInManager().GetDefaultProfile();
   2353 		//idLib::Warning( "Returning fake profile until the code is fixed to handle NULL profiles." );
   2354 	}
   2355 	
   2356 	return profile;
   2357 }
   2358 
   2359 /*
   2360 ========================
   2361 idLobby::GetLocalUserFromLobbyUser
   2362 ========================
   2363 */
   2364 idLocalUser * idLobby::GetLocalUserFromLobbyUser( lobbyUserID_t lobbyUserID ) {
   2365 	const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
   2366 
   2367 	return GetLocalUserFromLobbyUserIndex( userIndex );
   2368 }
   2369 
   2370 /*
   2371 ========================
   2372 idLobby::GetNumLobbyUsersOnTeam
   2373 ========================
   2374 */
   2375 int idLobby::GetNumLobbyUsersOnTeam( int teamNumber ) const {
   2376 	int numTeam = 0;
   2377 	for ( int i = 0; i < GetNumLobbyUsers(); ++i ) {
   2378 		if ( GetLobbyUser( i )->teamNumber == teamNumber ) {
   2379 			++numTeam;
   2380 		}
   2381 	}
   2382 	return numTeam;
   2383 }
   2384 
   2385 /*
   2386 ========================
   2387 idLobby::GetPeerName
   2388 ========================
   2389 */
   2390 const char * idLobby::GetPeerName( int peerNum ) const {
   2391 
   2392 	for ( int i = 0; i < GetNumLobbyUsers(); ++i ) {
   2393 		if ( !verify( GetLobbyUser( i ) != NULL ) ) {
   2394 			continue;
   2395 		}
   2396 
   2397 		if ( GetLobbyUser( i )->peerIndex == peerNum ) {
   2398 			return GetLobbyUserName( GetLobbyUser( i )->lobbyUserID );
   2399 		}
   2400 	}
   2401 
   2402 	return INVALID_LOBBY_USER_NAME;
   2403 }
   2404 
   2405 /*
   2406 ========================
   2407 idLobby::HandleReliableMsg
   2408 ========================
   2409 */
   2410 void idLobby::HandleReliableMsg( int p, idBitMsg & msg ) {
   2411 	peer_t & peer = peers[p];
   2412 
   2413 	int reliableType = msg.ReadByte();
   2414 	
   2415 	//idLib::Printf(" Received reliable msg: %i \n", reliableType );
   2416 
   2417 	const lobbyType_t actingGameStateLobbyType = GetActingGameStateLobbyType();
   2418 
   2419 	if ( reliableType == RELIABLE_HELLO ) {		
   2420 		VERIFY_FROM_CONNECTING_HOST( p, lobbyType, RELIABLE_HELLO );
   2421 		// This is sent from the host acking a request to join the game lobby
   2422 		HandleHelloAck( p, msg );
   2423 		return;		
   2424 	} else if ( reliableType == RELIABLE_USER_CONNECT_REQUEST ) {
   2425 		VERIFY_CONNECTED_PEER( p, lobbyType, RELIABLE_USER_CONNECT_REQUEST );
   2426 
   2427 		// This message is sent from a peer requesting for a new user to join the game lobby
   2428 		// This will be sent while we are in a game lobby as a host.  otherwise, denied.
   2429 		NET_VERBOSE_PRINT( "NET: RELIABLE_USER_CONNECT_REQUEST (%s) from %s\n", GetLobbyName(), peer.address.ToString() );
   2430 		
   2431 		idSession::sessionState_t expectedState = ( lobbyType == TYPE_PARTY ) ? idSession::PARTY_LOBBY : idSession::GAME_LOBBY;
   2432 
   2433 		if ( sessionCB->GetState() == expectedState && IsHost() && NumFreeSlots() > 0 ) {	// This assumes only one user in the msg
   2434 			// Add user to session, which will also forward the operation to all other peers
   2435 			AddUsersFromMsg( msg, p );
   2436 		} else {
   2437 			// Let peer know user couldn't be added
   2438 			HandleUserConnectFailure( p, msg, RELIABLE_USER_CONNECT_DENIED );
   2439 		}
   2440 	} else if ( reliableType == RELIABLE_USER_CONNECT_DENIED ) {
   2441 		// This message is sent back from the host when a RELIABLE_PARTY_USER_CONNECT_REQUEST failed
   2442 		VERIFY_FROM_HOST( p, lobbyType, RELIABLE_PARTY_USER_CONNECT_DENIED );
   2443 		
   2444 		// Remove this user from the sign-in manager, so we don't keep trying to add them
   2445 		if ( !sessionCB->GetSignInManager().RemoveLocalUserByHandle( localUserHandle_t( msg.ReadLong() ) ) ) {
   2446 			NET_VERBOSE_PRINT( "NET: RELIABLE_PARTY_USER_CONNECT_DENIED, local user not found\n" );
   2447 			return;
   2448 		}
   2449 	} else if ( reliableType == RELIABLE_KICK_PLAYER ) {
   2450 		VERIFY_FROM_HOST( p, lobbyType, RELIABLE_KICK_PLAYER );
   2451 		common->Dialog().AddDialog( GDM_KICKED, DIALOG_ACCEPT, NULL, NULL, false );
   2452 		if ( sessionCB->GetPartyLobby().IsHost() ) {
   2453 			session->SetSessionOption( idSession::OPTION_LEAVE_WITH_PARTY );
   2454 		}
   2455 		session->Cancel();
   2456 	} else if ( reliableType == RELIABLE_HEADSET_STATE ) {
   2457 		HandleHeadsetStateChange( p, msg );
   2458 	} else if ( reliableType == RELIABLE_USER_CONNECTED ) {
   2459 		// This message is sent back from the host when users have connected, and we need to update our lists to reflect that
   2460 		VERIFY_FROM_HOST( p, lobbyType, RELIABLE_USER_CONNECTED );
   2461 
   2462 		NET_VERBOSE_PRINT( "NET: RELIABLE_USER_CONNECTED (%s) from %s\n", GetLobbyName(), peer.address.ToString() );
   2463 		AddUsersFromMsg( msg, p );
   2464 	} else if ( reliableType == RELIABLE_USER_DISCONNECTED ) {
   2465 		// This message is sent back from the host when users have diconnected, and we need to update our lists to reflect that
   2466 		VERIFY_FROM_HOST( p, lobbyType, RELIABLE_USER_DISCONNECTED );
   2467 
   2468 		ProcessUserDisconnectMsg( msg );
   2469 	} else if ( reliableType == RELIABLE_MATCH_PARMS ) {
   2470 		parms.Read( msg );
   2471 		// Update lobby with parms
   2472 		if ( lobbyBackend != NULL ) {
   2473 			lobbyBackend->UpdateMatchParms( parms );
   2474 		}
   2475 	} else if ( reliableType == RELIABLE_START_LOADING ) {
   2476 		// This message is sent from the host to start loading a map
   2477 		VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_START_LOADING );
   2478 
   2479 		NET_VERBOSE_PRINT( "NET: RELIABLE_START_LOADING from %s\n", peer.address.ToString() );
   2480 
   2481 		startLoadingFromHost = true;
   2482 	} else if ( reliableType == RELIABLE_LOADING_DONE ) {
   2483 		// This message is sent from the peers to state they are done loading the map
   2484 		VERIFY_CONNECTED_PEER( p, actingGameStateLobbyType, RELIABLE_LOADING_DONE );
   2485 
   2486 		unsigned long networkChecksum = 0;
   2487 		networkChecksum = msg.ReadLong();
   2488 
   2489 		peer.networkChecksum = networkChecksum;
   2490 		peer.loaded = true;
   2491 	} else if ( reliableType == RELIABLE_IN_GAME ) {
   2492 		VERIFY_CONNECTED_PEER( p, actingGameStateLobbyType, RELIABLE_IN_GAME );
   2493 
   2494 		peer.inGame = true;
   2495 	} else if ( reliableType == RELIABLE_SNAPSHOT_ACK ) {
   2496 		VERIFY_CONNECTED_PEER( p, actingGameStateLobbyType, RELIABLE_SNAPSHOT_ACK );
   2497 
   2498 		// update our base state for his last received snapshot
   2499 		int snapNum = msg.ReadLong();
   2500 		float receivedBps = msg.ReadQuantizedUFloat< BANDWIDTH_REPORTING_MAX, BANDWIDTH_REPORTING_BITS >();
   2501 
   2502 		// Update reported received bps
   2503 		if ( peer.receivedBpsIndex != snapNum ) {
   2504 			// Only do this the first time we get reported bps per snapshot. Subsequent ACKs of the same shot will usually have lower reported bps
   2505 			// due to more time elapsing but not receiving a new ss
   2506 			peer.receivedBps = receivedBps;
   2507 			peer.receivedBpsIndex = snapNum;
   2508 		}
   2509 
   2510 		ApplySnapshotDelta( p, snapNum );
   2511 		
   2512 		//idLib::Printf( "NET: Peer %d Ack'd snapshot %d\n", p, snapNum );
   2513 		NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va( "NET: Peer %d Ack'd snapshot %d\n", p, snapNum ) );
   2514 
   2515 	} else if ( reliableType == RELIABLE_RESOURCE_ACK ) {
   2516 	} else if ( reliableType == RELIABLE_UPDATE_MATCH_PARMS ) {
   2517 		VERIFY_CONNECTED_PEER( p, TYPE_GAME, RELIABLE_UPDATE_MATCH_PARMS );
   2518 		int msgType = msg.ReadLong();
   2519 		sessionCB->HandlePeerMatchParamUpdate( p, msgType );
   2520 
   2521 	} else if ( reliableType == RELIABLE_MATCHFINISHED ) {
   2522 		VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_MATCHFINISHED );
   2523 
   2524 		sessionCB->ClearMigrationState();
   2525 
   2526 	} else if ( reliableType == RELIABLE_ENDMATCH ) {
   2527 		VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_ENDMATCH );
   2528 
   2529 		sessionCB->EndMatchInternal();
   2530 
   2531 	}  else if ( reliableType == RELIABLE_ENDMATCH_PREMATURE ) {
   2532 		VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_ENDMATCH_PREMATURE );	
   2533 
   2534 		sessionCB->EndMatchInternal( true );
   2535 
   2536 	} else if ( reliableType == RELIABLE_START_MATCH_GAME_LOBBY_HOST ) {
   2537 		// This message should be from the host of the game lobby, telling us (as the host of the GameStateLobby) to start loading
   2538 		VERIFY_CONNECTED_PEER( p, TYPE_GAME_STATE, RELIABLE_START_MATCH_GAME_LOBBY_HOST );
   2539 
   2540 		if ( session->GetState() >= idSession::LOADING ) {
   2541 			NET_VERBOSE_PRINT( "NET: RELIABLE_START_MATCH_GAME_LOBBY_HOST already loading\n" );
   2542 			return;
   2543 		}
   2544 
   2545 		// Read match parms, and start loading
   2546 		parms.Read( msg );
   2547 
   2548 		// Send these new match parms to currently connected peers
   2549 		SendMatchParmsToPeers();
   2550 
   2551 		startLoadingFromHost = true;		// Hijack this flag
   2552 	} else if ( reliableType == RELIABLE_ARBITRATE ) {
   2553 		VERIFY_CONNECTED_PEER( p, TYPE_GAME, RELIABLE_ARBITRATE );
   2554 		// Host telling us to arbitrate
   2555 		// 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
   2556 		respondToArbitrate = true;
   2557 	} else if ( reliableType == RELIABLE_ARBITRATE_OK ) {
   2558 		VERIFY_CONNECTED_PEER( p, TYPE_GAME, RELIABLE_ARBITRATE_OK );
   2559 		
   2560 		NET_VERBOSE_PRINT( "NET: Got an arbitration ok from %d\n", p );
   2561 
   2562 		everyoneArbitrated = true;
   2563 		for ( int i = 0; i < GetNumLobbyUsers(); i++ ) {
   2564 			lobbyUser_t * user = GetLobbyUser( i );
   2565 			if ( !verify( user != NULL ) ) {
   2566 				continue;
   2567 			}
   2568 			if ( user->peerIndex == p ) {
   2569 				user->arbitrationAcked = true;
   2570 			} else if ( !user->arbitrationAcked ) {
   2571 				everyoneArbitrated = false;
   2572 			}
   2573 		}
   2574 
   2575 		if ( everyoneArbitrated ) {
   2576 			NET_VERBOSE_PRINT( "NET: Everyone says they registered for arbitration, verifying\n" );
   2577 			lobbyBackend->Arbitrate();
   2578 			//sessionCB->EveryoneArbitrated();
   2579 			return;
   2580 		}
   2581 	} else if ( reliableType == RELIABLE_POST_STATS ) {
   2582 		VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_POST_STATS );
   2583 		sessionCB->RecvLeaderboardStats( msg );
   2584 	} else if ( reliableType == RELIABLE_SESSION_USER_MODIFIED ) {
   2585 		VERIFY_CONNECTED_PEER( p, lobbyType, RELIABLE_SESSION_USER_MODIFIED );
   2586 		UpdateSessionUserOnPeers( msg );
   2587 
   2588 	} else if ( reliableType == RELIABLE_UPDATE_SESSION_USER ) {
   2589 		VERIFY_FROM_HOST( p, lobbyType, RELIABLE_UPDATE_SESSION_USER );
   2590 		HandleUpdateSessionUser( msg );
   2591 	} else if ( reliableType == RELIABLE_CONNECT_AND_MOVE_TO_LOBBY ) {		
   2592 		VERIFY_FROM_HOST( p, lobbyType, RELIABLE_CONNECT_AND_MOVE_TO_LOBBY );
   2593 		
   2594 		NET_VERBOSE_PRINT( "NET: RELIABLE_CONNECT_AND_MOVE_TO_LOBBY\n" );
   2595 
   2596 		if ( IsHost() ) {
   2597 			idLib::Printf( "RELIABLE_CONNECT_AND_MOVE_TO_LOBBY: We are the host.\n" ); 
   2598 			return;
   2599 		}
   2600 
   2601 		// Get connection info
   2602 		lobbyConnectInfo_t connectInfo;
   2603 		connectInfo.ReadFromMsg( msg );
   2604 
   2605 		const lobbyType_t	destLobbyType	= (lobbyType_t)msg.ReadByte();
   2606 		const bool			waitForMembers	= msg.ReadBool();
   2607 
   2608 		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)
   2609 
   2610 		sessionCB->ConnectAndMoveToLobby( destLobbyType, connectInfo, waitForMembers );
   2611 	} else if ( reliableType == RELIABLE_PARTY_CONNECT_OK ) {
   2612 		VERIFY_FROM_HOST( p, TYPE_PARTY, RELIABLE_PARTY_CONNECT_OK );
   2613 		if ( !sessionCB->GetGameLobby().waitForPartyOk ) {
   2614 			idLib::Printf( "RELIABLE_PARTY_CONNECT_OK: Wasn't waiting for ok.\n" ); 
   2615 		}
   2616 		sessionCB->GetGameLobby().waitForPartyOk = false;
   2617 	} else if ( reliableType == RELIABLE_PARTY_LEAVE_GAME_LOBBY ) {		
   2618 		VERIFY_FROM_HOST( p, TYPE_PARTY, RELIABLE_PARTY_LEAVE_GAME_LOBBY );
   2619 		
   2620 		NET_VERBOSE_PRINT( "NET: RELIABLE_PARTY_LEAVE_GAME_LOBBY\n" );
   2621 		
   2622 		if ( sessionCB->GetState() != idSession::GAME_LOBBY ) {
   2623 			idLib::Printf( "RELIABLE_PARTY_LEAVE_GAME_LOBBY: Not in a game lobby, ignoring.\n" ); 
   2624 			return;
   2625 		}
   2626 
   2627 		if ( IsHost() ) {
   2628 			idLib::Printf( "RELIABLE_PARTY_LEAVE_GAME_LOBBY: Host of party, ignoring.\n" ); 
   2629 			return;
   2630 		}
   2631 
   2632 		sessionCB->LeaveGameLobby();
   2633 	} else if ( IsReliablePlayerToPlayerType( reliableType ) ) {
   2634 		HandleReliablePlayerToPlayerMsg( p, msg, reliableType );
   2635 	} else if ( reliableType == RELIABLE_PING ) {
   2636 		HandleReliablePing( p, msg );
   2637 	} else if ( reliableType == RELIABLE_PING_VALUES ) {
   2638 		HandlePingValues( msg );
   2639 	} else if ( reliableType == RELIABLE_BANDWIDTH_VALUES ) {
   2640 		HandleBandwidhTestValue( p, msg );
   2641 	} else if ( reliableType == RELIABLE_MIGRATION_GAME_DATA ) {
   2642 		HandleMigrationGameData( msg );
   2643 	} else if ( reliableType >= RELIABLE_GAME_DATA ) {
   2644 
   2645 		VERIFY_CONNECTED_PEER( p, lobbyType, RELIABLE_GAME_DATA );
   2646 
   2647 		common->NetReceiveReliable( p, reliableType - RELIABLE_GAME_DATA, msg );
   2648 	} else if ( reliableType == RELIABLE_DUMMY_MSG ) {
   2649 		// Ignore dummy msg's
   2650 		NET_VERBOSE_PRINT( "NET: ignoring dummy msg from %s\n", peer.address.ToString() );
   2651 	} else {
   2652 		NET_VERBOSE_PRINT( "NET: Unknown reliable packet type %d from %s\n", reliableType, peer.address.ToString() );
   2653 	}
   2654 }
   2655 
   2656 /*
   2657 ========================
   2658 idLobby::GetTotalOutgoingRate
   2659 ========================
   2660 */
   2661 int idLobby::GetTotalOutgoingRate() {
   2662 	int totalSendRate = 0;
   2663 	for ( int p = 0; p < peers.Num(); p++ ) {		
   2664 		const peer_t & peer = peers[p];
   2665 		
   2666 		if ( !peer.IsConnected() ) {
   2667 			continue;
   2668 		}
   2669 		
   2670 		const idPacketProcessor & proc = *peer.packetProc;
   2671 
   2672 		totalSendRate += proc.GetOutgoingRateBytes();
   2673 	}
   2674 	return totalSendRate;
   2675 }
   2676 
   2677 /*
   2678 ========================
   2679 idLobby::DrawDebugNetworkHUD
   2680 ========================
   2681 */
   2682 extern idCVar net_forceUpstream;
   2683 void idLobby::DrawDebugNetworkHUD() const {
   2684 	int		totalSendRate = 0;
   2685 	int		totalRecvRate = 0;
   2686 	float	totalSentMB = 0.0f;
   2687 	float	totalRecvMB = 0.0f;
   2688 	
   2689 	const float Y_OFFSET	= 20.0f;
   2690 	const float X_OFFSET	= 20.0f;
   2691 	const float Y_SPACING	= 15.0f;
   2692 
   2693 	float curY = Y_OFFSET;
   2694 
   2695 	int numLines = ( net_forceUpstream.GetFloat() != 0.0f ? 6: 5 );
   2696 
   2697 	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 );
   2698 
   2699 	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 );
   2700 	curY += Y_SPACING;
   2701 
   2702 	renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------------------------------------------------------------------------", colorGreen, false );
   2703 	curY += Y_SPACING;
   2704 
   2705 	for ( int p = 0; p < peers.Num(); p++ ) {		
   2706 		const peer_t & peer = peers[p];
   2707 		
   2708 		if ( !peer.IsConnected() ) {
   2709 			continue;
   2710 		}
   2711 		
   2712 		const idPacketProcessor & proc = *peer.packetProc;
   2713 		
   2714 		totalSendRate += proc.GetOutgoingRateBytes();
   2715 		totalRecvRate += proc.GetIncomingRateBytes();
   2716 		float sentKps = (float)proc.GetOutgoingRateBytes() / 1024.0f;
   2717 		float recvKps = (float)proc.GetIncomingRateBytes() / 1024.0f;
   2718 		float sentMB = (float)proc.GetOutgoingBytes() / ( 1024.0f * 1024.0f );
   2719 		float recvMB = (float)proc.GetIncomingBytes() / ( 1024.0f * 1024.0f );
   2720 		
   2721 		totalSentMB += sentMB;
   2722 		totalRecvMB += recvMB;
   2723 		
   2724 		idVec4 color = sentKps > 20.0f ? colorRed : colorGreen;
   2725 
   2726 		int resourcePercent = 0;
   2727 		idStr name = peer.address.ToString();
   2728 		
   2729 		name += lobbyType == TYPE_PARTY ? "(P": "(G";
   2730 		name += host == p ? ":H)" : ":C)";
   2731 
   2732 		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 );
   2733 		curY += Y_SPACING;
   2734 	}
   2735 
   2736 	renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------------------------------------------------------------------------", colorGreen, false );
   2737 	curY += Y_SPACING;
   2738 
   2739 	float totalSentKps = (float)totalSendRate / 1024.0f;
   2740 	float totalRecvKps = (float)totalRecvRate / 1024.0f;
   2741 	
   2742 	idVec4 color = totalSentKps > 100.0f ? colorRed : colorGreen;
   2743 	
   2744 	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 );
   2745 	curY += Y_SPACING;
   2746 
   2747 	if ( net_forceUpstream.GetFloat() != 0.0f ) {
   2748 		float upstreamDropRate = session->GetUpstreamDropRate();
   2749 		float upstreamQueuedRate = session->GetUpstreamQueueRate();
   2750 
   2751 		int queuedBytes = session->GetQueuedBytes();
   2752 		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 );
   2753 	}
   2754 }
   2755 
   2756 /*
   2757 ========================
   2758 idLobby::DrawDebugNetworkHUD2
   2759 ========================
   2760 */
   2761 void idLobby::DrawDebugNetworkHUD2() const {
   2762 	int		totalSendRate = 0;
   2763 	int		totalRecvRate = 0;
   2764 
   2765 	const float Y_OFFSET	= 20.0f;
   2766 	const float X_OFFSET	= 20.0f;
   2767 	const float Y_SPACING	= 15.0f;
   2768 
   2769 	float	curY = Y_OFFSET;
   2770 	
   2771 	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 );	
   2772 
   2773 	const char* stateName = session->GetStateString();
   2774 
   2775 	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 );
   2776 
   2777 	renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), va("State: %s. Local time: %d", stateName, Sys_Milliseconds() ), colorGreen, false );
   2778 	curY += Y_SPACING;
   2779 
   2780 	renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "Peer           | Sent kB/s | Recv kB/s | L | R | Resources", colorGreen, false );
   2781 	curY += Y_SPACING;
   2782 
   2783 	renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------", colorGreen, false );
   2784 	curY += Y_SPACING;
   2785 
   2786 	for ( int p = 0; p < peers.Num(); p++ ) {
   2787 
   2788 		if ( !peers[ p ].IsConnected() ) {
   2789 			continue;
   2790 		}
   2791 
   2792 		idPacketProcessor & proc = *peers[ p ].packetProc;
   2793 
   2794 		totalSendRate += proc.GetOutgoingRate2();
   2795 		totalRecvRate += proc.GetIncomingRate2();
   2796 		float sentKps = ( float )proc.GetOutgoingRate2() / 1024.0f;
   2797 		float recvKps = ( float )proc.GetIncomingRate2() / 1024.0f;
   2798 
   2799 		// should probably complement that with a bandwidth reading
   2800 		// right now I am mostly concerned about fragmentation and the latency spikes it will cause
   2801 		idVec4 color = proc.TickFragmentAccumulator() ? colorRed : colorGreen;
   2802 		
   2803 		int rLoaded = peers[ p ].numResources;
   2804 		int rTotal = 0;
   2805 
   2806 		// show the names of the clients connected to the server. Also make sure it looks reasonably good.
   2807 		idStr peerName;
   2808 		if ( IsHost() ) {
   2809 			peerName = GetPeerName( p );
   2810 
   2811 			int MAX_PEERNAME_LENGTH = 10;
   2812 			int nameLength = peerName.Length();
   2813 			if ( nameLength > MAX_PEERNAME_LENGTH ) {
   2814 				peerName = peerName.Left( MAX_PEERNAME_LENGTH );
   2815 			} else if ( nameLength < MAX_PEERNAME_LENGTH ) {
   2816 				idStr filler;
   2817 				filler.Fill( ' ', MAX_PEERNAME_LENGTH );
   2818 				peerName += filler.Left( MAX_PEERNAME_LENGTH - nameLength );
   2819 			}
   2820 		} else {
   2821 			peerName = "Local     ";
   2822 		}
   2823 
   2824 		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 );
   2825 		curY += Y_SPACING;
   2826 	}
   2827 
   2828 	renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------", colorGreen, false );
   2829 	curY += Y_SPACING;
   2830 
   2831 	float totalSentKps = (float)totalSendRate / 1024.0f;
   2832 	float totalRecvKps = (float)totalRecvRate / 1024.0f;
   2833 
   2834 	renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Total | %2.02f KB/s | %2.02f KB/s", totalSentKps, totalRecvKps ), colorGreen, false );
   2835 }
   2836 
   2837 
   2838 /*
   2839 ========================
   2840 idLobby::DrawDebugNetworkHUD_ServerSnapshotMetrics
   2841 ========================
   2842 */
   2843 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" );
   2844 void idLobby::DrawDebugNetworkHUD_ServerSnapshotMetrics( bool draw ) {
   2845 	const float Y_OFFSET	= 20.0f;
   2846 	const float X_OFFSET	= 20.0f;
   2847 	const float Y_SPACING	= 15.0f;
   2848 	idVec4 color = colorWhite;
   2849 
   2850 	float	curY = Y_OFFSET;
   2851 
   2852 	if ( !draw ) {
   2853 		for ( int p=0; p < peers.Num(); p++ ) {
   2854 			for ( int i=0; i < peers[p].debugGraphs.Num(); i++ ) {
   2855 				if ( peers[p].debugGraphs[i] != NULL ) {
   2856 					peers[p].debugGraphs[i]->Enable( false );
   2857 				} else {
   2858 					return;
   2859 				}
   2860 			}
   2861 		}
   2862 		return;
   2863 	}
   2864 
   2865 	static int lastTime = 0;
   2866 	int time = Sys_Milliseconds();
   2867 
   2868 	for ( int p = 0; p < peers.Num(); p++ ) {
   2869 
   2870 		peer_t & peer = peers[p];
   2871 
   2872 		if ( !peer.IsConnected() ) {
   2873 			continue;
   2874 		}
   2875 
   2876 		idPacketProcessor * packetProc = peer.packetProc;
   2877 		idSnapshotProcessor * snapProc = peer.snapProc;
   2878 
   2879 		if ( !verify( packetProc != NULL && snapProc != NULL ) ) {
   2880 			continue;
   2881 		}
   2882 
   2883 		int snapSeq = snapProc->GetSnapSequence();
   2884 		int snapBase = snapProc->GetBaseSequence();
   2885 		int deltaSeq = snapSeq - snapBase;
   2886 		bool throttled = peer.throttledSnapRate > common->GetSnapRate();
   2887 
   2888 		int numLines =  net_forceUpstream.GetBool() ? 5 : 4;
   2889 
   2890 		const int width = renderSystem->GetWidth()/2.0f - (X_OFFSET * 2);
   2891 
   2892 		enum netDebugGraphs_t {
   2893 			GRAPH_SNAPSENT,
   2894 			GRAPH_OUTGOING,
   2895 			GRAPH_INCOMINGREPORTED,
   2896 			GRAPH_MAX
   2897 		};
   2898 
   2899 		peer.debugGraphs.SetNum( GRAPH_MAX, NULL );
   2900 		for ( int i=0; i < GRAPH_MAX; i++ ) {
   2901 			// Initialize graphs 
   2902 			if ( peer.debugGraphs[i] == NULL ) {
   2903 				peer.debugGraphs[i] = console->CreateGraph( 500 );
   2904 				if ( !verify( peer.debugGraphs[i] != NULL ) ) {
   2905 					continue;
   2906 				}
   2907 				
   2908 				peer.debugGraphs[i]->SetPosition( X_OFFSET - 10.0f + width, curY - 10.0f, width , Y_SPACING * numLines );
   2909 			}
   2910 			
   2911 			peer.debugGraphs[i]->Enable( true );		
   2912 		}
   2913 
   2914 		renderSystem->DrawFilled( idVec4( 0.0f, 0.0f, 0.0f, 0.7f ), X_OFFSET - 10.0f, curY - 10.0f, width, ( Y_SPACING * numLines ) + 20.0f );
   2915 
   2916 		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 );
   2917 		curY += Y_SPACING;
   2918 
   2919 		renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "SnapSeq %d  BaseSeq %d  Delta %d  Queue %d", snapSeq, snapBase, deltaSeq, snapProc->GetSnapQueueSize() ), color, false );
   2920 		curY += Y_SPACING;
   2921 
   2922 		renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Reliables: %d / %d bytes Reliable Ack: %d", packetProc->NumQueuedReliables(), packetProc->GetReliableDataSize(), packetProc->NeedToSendReliableAck() ), color, false );
   2923 		curY += Y_SPACING;
   2924 
   2925 		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 );
   2926 		curY += Y_SPACING;
   2927 
   2928 		if ( net_forceUpstream.GetFloat() != 0.0f ) {
   2929 			float upstreamDropRate = session->GetUpstreamDropRate();
   2930 			float upstreamQueuedRate = session->GetUpstreamQueueRate();
   2931 			int queuedBytes = session->GetQueuedBytes();
   2932 			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 );
   2933 			
   2934 		}
   2935 
   2936 		curY += Y_SPACING;		
   2937 
   2938 		
   2939 
   2940 		if ( peer.debugGraphs[GRAPH_SNAPSENT] != NULL ) {
   2941 			if ( peer.lastSnapTime > lastTime ) {
   2942 				peer.debugGraphs[GRAPH_SNAPSENT]->SetValue(-1, 1.0f, colorBlue );
   2943 			} else {
   2944 				peer.debugGraphs[GRAPH_SNAPSENT]->SetValue(-1, 0.0f, colorBlue );
   2945 			}
   2946 		}
   2947 
   2948 		if ( peer.debugGraphs[GRAPH_OUTGOING] != NULL ) {
   2949 			idVec4 bgColor( vec4_zero );
   2950 			peer.debugGraphs[GRAPH_OUTGOING]->SetBackgroundColor( bgColor );
   2951 
   2952 			idVec4 lineColor = colorLtGrey;
   2953 			lineColor.w	 = 0.5f;
   2954 			float outgoingRate = peer.sentBpsHistory[ peer.receivedBpsIndex % MAX_BPS_HISTORY ];
   2955 			// peer.packetProc->GetOutgoingRateBytes()
   2956 			peer.debugGraphs[GRAPH_OUTGOING]->SetValue(-1, idMath::ClampFloat( 0.0f, 1.0f, outgoingRate / net_debughud3_bps_max.GetFloat() ), lineColor );
   2957 		}
   2958 
   2959 
   2960 		if ( peer.debugGraphs[GRAPH_INCOMINGREPORTED] != NULL ) {			
   2961 			idVec4 lineColor = colorYellow;
   2962 			extern idCVar net_peer_throttle_bps_peer_threshold_pct;
   2963 			extern idCVar net_peer_throttle_bps_host_threshold;
   2964 			
   2965 			if ( peer.packetProc->GetOutgoingRateBytes() > net_peer_throttle_bps_host_threshold.GetFloat() ) {
   2966 				float pct = peer.packetProc->GetOutgoingRateBytes() > 0.0f ? peer.receivedBps / peer.packetProc->GetOutgoingRateBytes() : 0.0f;
   2967 				if ( pct < net_peer_throttle_bps_peer_threshold_pct.GetFloat() ) {
   2968 					lineColor = colorRed;
   2969 				} else {
   2970 					lineColor = colorGreen;
   2971 				}
   2972 			}
   2973 			idVec4 bgColor( vec4_zero );
   2974 			peer.debugGraphs[GRAPH_INCOMINGREPORTED]->SetBackgroundColor( bgColor );
   2975 			peer.debugGraphs[GRAPH_INCOMINGREPORTED]->SetFillMode( idDebugGraph::GRAPH_LINE );
   2976 			peer.debugGraphs[GRAPH_INCOMINGREPORTED]->SetValue(-1, idMath::ClampFloat( 0.0f, 1.0f, peer.receivedBps / net_debughud3_bps_max.GetFloat() ), lineColor );
   2977 		}
   2978 	
   2979 
   2980 
   2981 		// Skip down
   2982 		curY += ( Y_SPACING * 2.0f);
   2983 	}
   2984 
   2985 	lastTime = time;
   2986 }
   2987 
   2988 /*
   2989 ========================
   2990 idLobby::CheckHeartBeats
   2991 ========================
   2992 */
   2993 void idLobby::CheckHeartBeats() {
   2994 	// Disconnect peers that haven't responded within net_peerTimeoutInSeconds
   2995 	int time = Sys_Milliseconds();
   2996 
   2997 	int timeoutInMs = session->GetTitleStorageInt( "net_peerTimeoutInSeconds", net_peerTimeoutInSeconds.GetInteger() ) * 1000;
   2998 
   2999 	if ( sessionCB->GetState() < idSession::LOADING && migrationInfo.state == MIGRATE_NONE ) {
   3000 		// Use shorter timeout in lobby (TCR)
   3001 		timeoutInMs = session->GetTitleStorageInt( "net_peerTimeoutInSeconds_Lobby", net_peerTimeoutInSeconds_Lobby.GetInteger() ) * 1000;
   3002 	}
   3003 
   3004 	if ( timeoutInMs > 0 ) {
   3005 		for ( int p = 0; p < peers.Num(); p++ ) {		
   3006 			if ( peers[p].IsConnected() ) {
   3007 
   3008 				bool peerTimeout = false;
   3009 
   3010 				if( time - peers[p].lastHeartBeat > timeoutInMs ) {
   3011 					peerTimeout = true;
   3012 				}
   3013 
   3014 				// if reliable queue is almost full, disconnect the peer. 
   3015 				// (this seems reasonable since the reliable queue is set to 64 currently. In practice we should never 
   3016 				// have more than 3 or 4 queued)
   3017 				if ( peers[ p ].packetProc->NumQueuedReliables() > idPacketProcessor::MAX_RELIABLE_QUEUE - 1 ) {
   3018 					peerTimeout = true;
   3019 				}
   3020 				
   3021 				if( peerTimeout ) {
   3022 					// Disconnect the peer from any sessions we are a host of
   3023 					if ( IsHost() ) {
   3024 						idLib::Printf("Peer %i timed out for %s session @ %d (lastHeartBeat %d)\n", p, GetLobbyName(), time, peers[p].lastHeartBeat );
   3025 						DisconnectPeerFromSession( p );
   3026 					}
   3027 
   3028 					// Handle peers not receiving a heartbeat from the host in awhile
   3029 					if ( IsPeer() ) {
   3030 						if ( migrationInfo.state != MIGRATE_PICKING_HOST ) {
   3031 							idLib::Printf("Host timed out for %s session\n", GetLobbyName() );
   3032 
   3033 							// Pick a host for this session
   3034 							PickNewHost();
   3035 						}
   3036 					}
   3037 				}
   3038 			}
   3039 		}
   3040 	}
   3041 
   3042 	if ( IsHost() && lobbyType == GetActingGameStateLobbyType() ) {
   3043 		for ( int p = 0; p < peers.Num(); p++ ) {		
   3044 			if ( !peers[p].IsConnected() ) {
   3045 				continue;
   3046 			}
   3047 
   3048 			CheckPeerThrottle( p );
   3049 		}
   3050 	}
   3051 }
   3052 
   3053 /*
   3054 ========================
   3055 idLobby::CheckHeartBeats
   3056 ========================
   3057 */
   3058 bool idLobby::IsLosingConnectionToHost() const {
   3059 	if ( !verify( IsPeer() && host >= 0 && host < peers.Num() ) ) {
   3060 		return false;
   3061 	}
   3062 
   3063 	if ( !peers[ host ].IsConnected() ) {
   3064 		return true;
   3065 	}
   3066 
   3067 	int time = Sys_Milliseconds();
   3068 
   3069 	int timeoutInMs = session->GetTitleStorageInt( "net_peerTimeoutInSeconds", net_peerTimeoutInSeconds.GetInteger() ) * 1000;
   3070 
   3071 	// return true if heartbeat > half the timeout length
   3072 	if ( timeoutInMs > 0 &&  time - peers[ host ].lastHeartBeat > timeoutInMs / 2 ) {
   3073 		return true;
   3074 	}
   3075 
   3076 	// return true if reliable queue is more than half full
   3077 	// (this seems reasonable since the reliable queue is set to 64 currently. In practice we should never 
   3078 	// have more than 3 or 4 queued)
   3079 	if ( peers[ host ].packetProc->NumQueuedReliables() > idPacketProcessor::MAX_RELIABLE_QUEUE / 2 ) {
   3080 		return true;
   3081 	}
   3082 
   3083 	return false;
   3084 }
   3085 
   3086 /*
   3087 ========================
   3088 idLobby::IsMigratedStatsGame
   3089 ========================
   3090 */
   3091 bool idLobby::IsMigratedStatsGame() const {
   3092 	if ( !IsLobbyActive() ) {
   3093 		return false;
   3094 	}
   3095 
   3096 	if ( lobbyType != TYPE_GAME ) {
   3097 		return false;		// Only game session migrates games stats
   3098 	}
   3099 
   3100 	if ( !MatchTypeHasStats( parms.matchFlags ) ) {
   3101 		return false;		// Only stats games migrate stats
   3102 	}
   3103 
   3104 	if ( !MatchTypeIsRanked( parms.matchFlags ) ) {
   3105 		return false;		// Only ranked games should migrate stats into new game
   3106 	}
   3107 
   3108 	return migrationInfo.persistUntilGameEndsData.wasMigratedGame && migrationInfo.persistUntilGameEndsData.hasGameData;
   3109 }
   3110 
   3111 /*
   3112 ========================
   3113 idLobby::ShouldRelaunchMigrationGame
   3114 returns true if we are hosting a migrated game and we had valid migration data
   3115 ========================
   3116 */
   3117 bool idLobby::ShouldRelaunchMigrationGame() const { 
   3118 	if ( IsMigrating() ) {
   3119 		return false;		// Don't relaunch until all clients have reconnected
   3120 	}
   3121 
   3122 	if ( !IsMigratedStatsGame() ) {
   3123 		return false;		// If we are not migrating stats, we don't want to relaunch a new game
   3124 	}
   3125 
   3126 	if ( !migrationInfo.persistUntilGameEndsData.wasMigratedHost ) {
   3127 		return false;		// Only relaunch if we are the host
   3128 	}
   3129 
   3130 	if ( migrationInfo.persistUntilGameEndsData.hasRelaunchedMigratedGame ) {
   3131 		return false;		// We already relaunched this game
   3132 	}
   3133 
   3134 	return true;
   3135 }
   3136 
   3137 /*
   3138 ========================
   3139 idLobby::ShouldShowMigratingDialog
   3140 ========================
   3141 */
   3142 bool idLobby::ShouldShowMigratingDialog() const {
   3143 	if ( IsMigrating() ) {
   3144 		return true;	// If we are in the process of truly migrating, then definitely return true
   3145 	}
   3146 
   3147 	if ( sessionCB->GetState() == idSession::INGAME ) {
   3148 		return false;
   3149 	}
   3150 
   3151 	// We're either waiting on the server (which could be us) to relaunch, so show the dialog
   3152 	return IsMigratedStatsGame() && sessionCB->GetState() != idSession::INGAME;
   3153 }
   3154 
   3155 /*
   3156 ========================
   3157 idLobby::IsMigrating
   3158 ========================
   3159 */
   3160 bool idLobby::IsMigrating() const {
   3161 	return migrationInfo.state != idLobby::MIGRATE_NONE;
   3162 }
   3163 
   3164 /*
   3165 ========================
   3166 idLobby::PingPeers
   3167 Host only.
   3168 ========================
   3169 */
   3170 void idLobby::PingPeers() {
   3171 	if ( !verify( IsHost() ))  {
   3172 		return;
   3173 	}
   3174 
   3175 	const int now = Sys_Milliseconds();
   3176 
   3177 	pktPing_t packet;
   3178 	memset( &packet, 0, sizeof( packet ) ); // We're gonna memset like it's 1999.
   3179 	packet.timestamp = now;
   3180 
   3181 	byte packetCopy[ sizeof( packet ) ];
   3182 	idBitMsg msg( packetCopy, sizeof( packetCopy ) );
   3183 	msg.WriteLong( packet.timestamp );
   3184 
   3185 	for ( int i = 0; i < peers.Num(); ++i ) {
   3186 		peer_t & peer = peers[ i ];
   3187 		if ( !peer.IsConnected() ) {
   3188 			continue;
   3189 		}
   3190 		if ( peer.nextPing <= now ) {
   3191 			peer.nextPing = now + PING_INTERVAL_MS;
   3192 			QueueReliableMessage( i, RELIABLE_PING, msg.GetReadData(), msg.GetSize() );
   3193 		}
   3194 	}
   3195 }
   3196 
   3197 /*
   3198 ========================
   3199 idLobby::ThrottlePeerSnapRate
   3200 ========================
   3201 */
   3202 void idLobby::ThrottlePeerSnapRate( int p ) {
   3203 	if ( !verify( IsHost() ) || !verify( p >= 0 ) ) {
   3204 		return;
   3205 	}
   3206 
   3207 	peers[p].throttledSnapRate = common->GetSnapRate() * 2;
   3208 	idLib::Printf( "^1Throttling peer %d %s!\n", p, GetPeerName(p) );
   3209 	idLib::Printf( "  New snaprate: %d\n", peers[p].throttledSnapRate / 1000 );
   3210 }
   3211 
   3212 /*
   3213 ========================
   3214 idLobby::SaturatePeers
   3215 ========================
   3216 */
   3217 void idLobby::BeginBandwidthTest() {
   3218 	if ( !verify( IsHost() ) )  {
   3219 		idLib::Warning("Bandwidth test should only be done on host");
   3220 		return;
   3221 	}
   3222 
   3223 	if ( bandwidthChallengeStartTime > 0 ) {
   3224 		idLib::Warning("Already started bandwidth test");
   3225 		return;
   3226 	}
   3227 
   3228 	int time = Sys_Milliseconds();
   3229 	bandwidthChallengeStartTime = time;
   3230 	bandwidthChallengeEndTime = 0;
   3231 	bandwidthChallengeFinished = false;
   3232 	bandwidthChallengeNumGoodSeq = 0;
   3233 
   3234 	for ( int p = 0; p < peers.Num(); ++p ) {
   3235 		if ( !peers[ p ].IsConnected() ) {
   3236 			continue;
   3237 		}
   3238 
   3239 		if ( !verify( peers[ p ].packetProc != NULL ) ) {
   3240 			continue;
   3241 		}
   3242 
   3243 		peers[ p ].bandwidthSequenceNum = 0;
   3244 		peers[ p ].bandwidthChallengeStartSendTime = 0;
   3245 		peers[ p ].bandwidthChallengeResults = false;
   3246 		peers[ p ].bandwidthChallengeSendComplete	 = false;
   3247 		peers[ p ].bandwidthTestBytes = peers[ p ].packetProc->GetOutgoingBytes(); // cache this off so we can see the difference when we are done
   3248 	}
   3249 }
   3250 
   3251 /*
   3252 ========================
   3253 idLobby::SaturatePeers
   3254 ========================
   3255 */
   3256 bool idLobby::BandwidthTestStarted() {
   3257 	return bandwidthChallengeStartTime != 0;
   3258 }
   3259 /*
   3260 ========================
   3261 idLobby::ServerUpdateBandwidthTest
   3262 ========================
   3263 */
   3264 void idLobby::ServerUpdateBandwidthTest() {
   3265 	if ( bandwidthChallengeStartTime <= 0 ) {
   3266 		// Not doing a test
   3267 		return;
   3268 	}
   3269 	
   3270 	if ( !verify( IsHost() ))  {
   3271 		return;
   3272 	}
   3273 	
   3274 	int time = Sys_Milliseconds();
   3275 
   3276 	if ( bandwidthChallengeFinished ) {
   3277 		// test is over
   3278 		return;
   3279 	}
   3280 
   3281 	idRandom random;
   3282 	random.SetSeed( time );
   3283 
   3284 	bool sentAll = true;
   3285 	bool recAll = true;
   3286 
   3287 	for ( int i = 0; i < peers.Num(); ++i ) {
   3288 		peer_t & peer = peers[ i ];
   3289 		if ( !peer.IsConnected() ) {
   3290 			continue;
   3291 		}
   3292 
   3293 		if ( peer.bandwidthChallengeResults ) {
   3294 			continue;
   3295 		}
   3296 		recAll = false;
   3297 
   3298 		if ( peer.bandwidthChallengeSendComplete ) {
   3299 			continue;
   3300 		}
   3301 		sentAll = false;
   3302 
   3303 		if ( time - peer.bandwidthTestLastSendTime < session->GetTitleStorageInt( "net_bw_test_interval", net_bw_test_interval.GetInteger() ) ) {
   3304 			continue;
   3305 		}
   3306 
   3307 		if ( peer.packetProc->HasMoreFragments() ) {
   3308 			continue;
   3309 		}
   3310 
   3311 		if ( peer.bandwidthChallengeStartSendTime == 0 ) {
   3312 			peer.bandwidthChallengeStartSendTime = time;
   3313 		}
   3314 
   3315 		peer.bandwidthTestLastSendTime = time;
   3316 
   3317 		// Ok, send him a big packet
   3318 		byte buffer[ idPacketProcessor::MAX_OOB_MSG_SIZE ];		// <---- NOTE - When calling ProcessOutgoingMsg with true for oob, we can't go over this size
   3319 		idBitMsg msg( buffer, sizeof(buffer) );
   3320 
   3321 		msg.WriteLong( peer.bandwidthSequenceNum++ );
   3322 
   3323 		unsigned int randomSize = Min( (unsigned int)(sizeof(buffer) - 12), (unsigned int)session->GetTitleStorageInt( "net_bw_test_packetSizeBytes", net_bw_test_packetSizeBytes.GetInteger() ) );
   3324 		msg.WriteLong( randomSize );
   3325 		
   3326 		for ( unsigned int j=0; j < randomSize; j++ ) {
   3327 			msg.WriteByte( random.RandomInt( 255 ) );
   3328 		}
   3329 		
   3330 		unsigned int checksum = MD5_BlockChecksum( &buffer[8], randomSize );
   3331 		msg.WriteLong( checksum );
   3332 
   3333 		NET_VERBOSE_PRINT("Net: Sending bw challenge to peer %d time %d packet size %d\n", i, time, msg.GetSize() );
   3334 
   3335 		ProcessOutgoingMsg( i, buffer, msg.GetSize(), true, OOB_BANDWIDTH_TEST );
   3336 
   3337 		if ( session->GetTitleStorageInt( "net_bw_test_numPackets", net_bw_test_numPackets.GetInteger() ) > 0 && peer.bandwidthSequenceNum >= net_bw_test_numPackets.GetInteger() ) {
   3338 			int sentBytes = peers[i].packetProc->GetOutgoingBytes() - peers[i].bandwidthTestBytes; // FIXME: this won't include the last sent msg
   3339 			peers[i].bandwidthTestBytes = sentBytes; // this now means total bytes sent (we don't care about starting/ending total bytes sent to peer)
   3340 			peers[i].bandwidthChallengeSendComplete = true;
   3341 
   3342 			NET_VERBOSE_PRINT("Sent enough packets to peer %d for bandwidth test in %dms. Total bytes: %d\n", i, time - bandwidthChallengeStartTime, sentBytes );
   3343 		}
   3344 	}
   3345 
   3346 	if ( sentAll ) {
   3347 		if ( bandwidthChallengeEndTime == 0 ) {
   3348 			// We finished sending all our packets, set the timeout time
   3349 			bandwidthChallengeEndTime = time + session->GetTitleStorageInt( "net_bw_test_host_timeout", net_bw_test_host_timeout.GetInteger() );
   3350 			NET_VERBOSE_PRINT("Net: finished sending BWC to peers. Waiting until %d to hear back\n", bandwidthChallengeEndTime );
   3351 		}
   3352 	}
   3353 	
   3354 	if ( recAll ) {
   3355 		bandwidthChallengeFinished = true;
   3356 		bandwidthChallengeStartTime = 0;
   3357 
   3358 	} else if ( bandwidthChallengeEndTime != 0 && bandwidthChallengeEndTime < time ) {
   3359 		// Timed out waiting for someone - throttle them and move on
   3360 		NET_VERBOSE_PRINT("^2Net: timed out waiting for bandwidth challenge results \n");
   3361 		for ( int i=0; i < peers.Num(); i++ ) {
   3362 			NET_VERBOSE_PRINT("  Peer[%d] %s. SentAll: %d  RecAll: %d\n", i, GetPeerName(i), peers[i].bandwidthChallengeSendComplete, peers[i].bandwidthChallengeResults );
   3363 			if ( peers[i].bandwidthChallengeSendComplete && !peers[i].bandwidthChallengeResults ) {
   3364 				ThrottlePeerSnapRate( i );
   3365 			}
   3366 		}
   3367 		bandwidthChallengeFinished = true;
   3368 		bandwidthChallengeStartTime = 0;
   3369 	}
   3370 }
   3371 
   3372 /*
   3373 ========================
   3374 idLobby::UpdateBandwidthTest
   3375 This will be called on clients to check current state of bandwidth testing
   3376 ========================
   3377 */
   3378 void idLobby::ClientUpdateBandwidthTest() {
   3379 	if ( !verify( !IsHost() ) || !verify( host >= 0 ) )  {
   3380 		return;
   3381 	}
   3382 
   3383 	if ( !peers[host].IsConnected() ) {
   3384 		return;
   3385 	}
   3386 
   3387 	if ( bandwidthChallengeStartTime <= 0 ) {
   3388 		// Not doing a test
   3389 		return;
   3390 	}
   3391 
   3392 	int time = Sys_Milliseconds();
   3393 	if ( bandwidthChallengeEndTime > time ) {
   3394 		// Test is still going on
   3395 		return;
   3396 	}
   3397 
   3398 	// Its been long enough since we last received bw test msg. So lets send the results to the server
   3399 	byte buffer[ idPacketProcessor::MAX_MSG_SIZE ];
   3400 	idBitMsg msg( buffer, sizeof( buffer ) );
   3401 
   3402 	// Send total time it took to receive all the msgs
   3403 	// (note, subtract net_bw_test_timeout to get 'last recevied bandwidth test packet')
   3404 	// (^^ Note if the last packet is fragmented and we never get it, this is technically wrong!)
   3405 	int totalTime = ( bandwidthChallengeEndTime - session->GetTitleStorageInt( "net_bw_test_timeout", net_bw_test_timeout.GetInteger() ) ) - bandwidthChallengeStartTime;
   3406 	msg.WriteLong( totalTime );
   3407 
   3408 	// Send total number of complete, in order packets we got
   3409 	msg.WriteLong( bandwidthChallengeNumGoodSeq );
   3410 
   3411 	// Send the overall average bandwidth in KBS
   3412 	// Note that sending the number of good packets is not enough. If the packets going out are fragmented, and we 
   3413 	// drop fragments, the number of good sequences will be lower than the bandwidth we actually received.
   3414 	int totalIncomingBytes = peers[host].packetProc->GetIncomingBytes() - peers[host].bandwidthTestBytes;
   3415 	msg.WriteLong( totalIncomingBytes );
   3416 
   3417 	idLib::Printf("^3Finished Bandwidth test: \n");
   3418 	idLib::Printf("  Total time: %d\n", totalTime );
   3419 	idLib::Printf("  Num good packets: %d\n", bandwidthChallengeNumGoodSeq );
   3420 	idLib::Printf("  Total received byes: %d\n\n", totalIncomingBytes );
   3421 
   3422 	bandwidthChallengeStartTime = 0;
   3423 	bandwidthChallengeNumGoodSeq = 0;
   3424 
   3425 	QueueReliableMessage( host, RELIABLE_BANDWIDTH_VALUES, msg.GetReadData(), msg.GetSize() );
   3426 }
   3427 
   3428 /*
   3429 ========================
   3430 idLobby::HandleBandwidhTestValue
   3431 ========================
   3432 */
   3433 void idLobby::HandleBandwidhTestValue( int p, idBitMsg & msg ) {
   3434 	if ( !IsHost() ) {
   3435 		return;
   3436 	}
   3437 
   3438 	idLib::Printf("Received RELIABLE_BANDWIDTH_CHECK %d\n", Sys_Milliseconds() );
   3439 
   3440 	if ( bandwidthChallengeStartTime < 0 || bandwidthChallengeFinished ) {
   3441 		idLib::Warning("Received bandwidth test results too early from peer %d", p );
   3442 		return;
   3443 	}
   3444 
   3445 	int totalTime = msg.ReadLong();
   3446 	int totalGoodSeq = msg.ReadLong();
   3447 	int totalReceivedBytes = msg.ReadLong();
   3448 
   3449 	// This is the % of complete packets we received. If the packets used in the BWC are big enough to fragment, then pctPackets
   3450 	// will be lower than bytesPct (we will have received a larger PCT of overall bandwidth than PCT of full packets received).
   3451 	// Im not sure if this is a useful distinction or not, but it may be good to compare against for now.
   3452 	float pctPackets = peers[p].bandwidthSequenceNum > 0 ? (float) totalGoodSeq / (float)peers[p].bandwidthSequenceNum : -1.0f;
   3453 
   3454 	// This is the % of total bytes sent/bytes received.
   3455 	float bytesPct = peers[p].bandwidthTestBytes > 0 ? (float) totalReceivedBytes / (float)peers[p].bandwidthTestBytes : -1.0f;
   3456 
   3457 	// Calculate overall bandwidth for the test. That is, total amount received over time.
   3458 	// We may want to expand this to also factor in an average instantaneous rate. 
   3459 	// For now we are mostly concerned with culling out poor performing clients
   3460 	float peerKBS = -1.0f;
   3461 	if ( verify( totalTime > 0 ) ) {
   3462 		peerKBS = ( (float)totalReceivedBytes / 1024.0f ) / MS2SEC(totalTime);
   3463 	}
   3464 
   3465 	int totalSendTime = peers[p].bandwidthTestLastSendTime - peers[p].bandwidthChallengeStartSendTime;
   3466 	float outgoingKBS = -1.0f;
   3467 	if ( verify( totalSendTime > 0 ) ) {
   3468 		outgoingKBS = ( (float)peers[p].bandwidthTestBytes / 1024.0f ) / MS2SEC(totalSendTime);
   3469 	}
   3470 
   3471 	float pctKBS = peerKBS / outgoingKBS;
   3472 
   3473 	bool failedRate = ( pctKBS < session->GetTitleStorageFloat( "net_bw_test_throttle_rate_pct", net_bw_test_throttle_rate_pct.GetFloat() ) );
   3474 	bool failedByte = ( bytesPct < session->GetTitleStorageFloat( "net_bw_test_throttle_byte_pct", net_bw_test_throttle_byte_pct.GetFloat() ) );
   3475 	bool failedSeq	= ( pctPackets < session->GetTitleStorageFloat( "net_bw_test_throttle_seq_pct", net_bw_test_throttle_seq_pct.GetFloat() ) );
   3476 
   3477 	idLib::Printf("^3Finished Bandwidth test %s: \n", GetPeerName(p) );
   3478 	idLib::Printf("  Total time: %dms\n", totalTime );
   3479 	idLib::Printf("  %sNum good packets: %d  (%.2f%)\n", ( failedSeq ? "^1" : "^2" ), totalGoodSeq, pctPackets );
   3480 	idLib::Printf("  %sTotal received bytes: %d  (%.2f%)\n", ( failedByte ? "^1" : "^2" ), totalReceivedBytes, bytesPct );
   3481 	idLib::Printf("  %sEffective downstream: %.2fkbs (host: %.2fkbs) -> %.2f%\n\n", ( failedRate ? "^1" : "^2" ), peerKBS, outgoingKBS, pctKBS );
   3482 
   3483 	// If shittConnection(totalTime, totalGoodSeq/totalSeq, totalReceivedBytes/totalSentBytes)
   3484 	//	throttle this user: 
   3485 	//	peers[p].throttledSnapRate = baseSnapRate * 2
   3486 	if ( failedRate || failedByte || failedSeq ) {		
   3487 		ThrottlePeerSnapRate( p );
   3488 	}
   3489 	
   3490 
   3491 	// See if we are finished
   3492 	peers[p].bandwidthChallengeResults = true;
   3493 	bandwidthChallengeFinished = true;
   3494 	for ( int i=0; i < peers.Num(); i++ ) {
   3495 		if ( peers[i].bandwidthChallengeSendComplete && !peers[i].bandwidthChallengeResults ) {
   3496 			bandwidthChallengeFinished = false;
   3497 		}
   3498 	}
   3499 
   3500 	if ( bandwidthChallengeFinished ) {
   3501 		bandwidthChallengeStartTime = 0;
   3502 	}
   3503 }
   3504 
   3505 /*
   3506 ========================
   3507 idLobby::SendPingValues
   3508 Host only
   3509 Periodically send all peers' pings to all peers (for the UI).
   3510 ========================
   3511 */
   3512 void idLobby::SendPingValues() {
   3513 	if ( !verify( IsHost() ) )  {
   3514 		// paranoia
   3515 		return;
   3516 	}
   3517 
   3518 	const int now = Sys_Milliseconds();
   3519 
   3520 	if ( nextSendPingValuesTime > now ) {
   3521 		return;
   3522 	}
   3523 
   3524 	nextSendPingValuesTime = now + PING_INTERVAL_MS;
   3525 
   3526 	pktPingValues_t packet;
   3527 
   3528 	memset( &packet, 0, sizeof(packet) );
   3529 
   3530 	for( int i = 0; i < peers.Max(); ++i ) {
   3531 		if ( i >= peers.Num() ) {
   3532 			packet.pings[ i ] = -1;
   3533 		} else if ( peers[ i ].IsConnected() ) {
   3534 			packet.pings[ i ] = peers[ i ].lastPingRtt;
   3535 		} else {
   3536 			packet.pings[ i ] = -1;
   3537 		}
   3538 	}
   3539 
   3540 	byte packetCopy[ sizeof(packet) ];
   3541 	idBitMsg msg( packetCopy, sizeof(packetCopy) );
   3542 	for( int i = 0; i < peers.Max(); ++i ) {
   3543 		msg.WriteShort( packet.pings[ i ] );
   3544 	}
   3545 
   3546 	for ( int i = 0; i < peers.Num(); i++ ) {
   3547 		if ( peers[ i ].IsConnected() ) {
   3548 			QueueReliableMessage( i, RELIABLE_PING_VALUES, msg.GetReadData(), msg.GetSize() );
   3549 		}
   3550 	}
   3551 }
   3552 
   3553 /*
   3554 ========================
   3555 idLobby::PumpPings
   3556 Host: Periodically determine the round-trip time for a packet to all peers, and tell everyone
   3557 	what everyone else's ping to the host is so they can display it in the UI.
   3558 Client: Indicate to the player when the server hasn't updated the ping values in too long.
   3559 	This is usually going to preceed a connection timeout.
   3560 ========================
   3561 */
   3562 void idLobby::PumpPings() {
   3563 	if ( IsHost() ) {
   3564 		// Calculate ping to all peers
   3565 		PingPeers();
   3566 		// Send the hosts calculated ping values to each peer to everyone has updated ping times
   3567 		SendPingValues();
   3568 		// Do bandwidth testing 
   3569 		ServerUpdateBandwidthTest();
   3570 		// Send Migration Data
   3571 		SendMigrationGameData();
   3572 	} else if ( IsPeer() ) {
   3573 		ClientUpdateBandwidthTest();
   3574 
   3575 		if ( lastPingValuesRecvTime + PING_INTERVAL_MS + 1000 < Sys_Milliseconds() && migrationInfo.state == MIGRATE_NONE ) {
   3576 			for ( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex ) {
   3577 				lobbyUser_t * user = GetLobbyUser( userIndex );
   3578 				if ( !verify( user != NULL ) ) {
   3579 					continue;
   3580 				}
   3581 				user->pingMs = 999999;
   3582 			}
   3583 		}
   3584 	}
   3585 }
   3586 
   3587 /*
   3588 ========================
   3589 idLobby::HandleReliablePing
   3590 ========================
   3591 */
   3592 void idLobby::HandleReliablePing( int p, idBitMsg & msg ) {
   3593 	int c, b;
   3594 	msg.SaveReadState( c, b );
   3595 
   3596 	pktPing_t ping;
   3597 
   3598 	memset( &ping, 0, sizeof( ping ) );
   3599 	if ( !verify( sizeof( ping ) <= msg.GetRemainingData() ) ) {
   3600 		NET_VERBOSE_PRINT( "NET: Ignoring ping from peer %i because packet was the wrong size\n", p );
   3601 		return;
   3602 	}
   3603 
   3604 	ping.timestamp = msg.ReadLong();
   3605 
   3606 	if ( IsHost() ) {
   3607 		// we should probably verify here whether or not this ping was solicited or not
   3608 		HandlePingReply( p, ping );
   3609 	} else {
   3610 		// this means the server is requesting a ping, so reply
   3611 		msg.RestoreReadState( c, b );
   3612 		QueueReliableMessage( p, RELIABLE_PING, msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() );
   3613 	}
   3614 }
   3615 
   3616 /*
   3617 ========================
   3618 idLobby::HandlePingReply
   3619 ========================
   3620 */
   3621 void idLobby::HandlePingReply( int p, const pktPing_t & ping ) {
   3622 	const int now = Sys_Milliseconds();
   3623 
   3624 	const int rtt = now - ping.timestamp;
   3625 	peers[p].lastPingRtt = rtt;
   3626 
   3627 	for ( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex ) {
   3628 		lobbyUser_t * u = GetLobbyUser( userIndex );
   3629 		if ( u->peerIndex == p ) {
   3630 			u->pingMs = rtt;
   3631 		}
   3632 	}
   3633 }
   3634 
   3635 /*
   3636 ========================
   3637 idLobby::HandlePingValues
   3638 ========================
   3639 */
   3640 void idLobby::HandlePingValues( idBitMsg & msg ) {
   3641 	pktPingValues_t packet;
   3642 	memset( &packet, 0, sizeof( packet ) );
   3643 	for( int i = 0; i < peers.Max(); ++i ) {
   3644 		 packet.pings[ i ] = msg.ReadShort();
   3645 	}
   3646 
   3647 	assert( IsPeer() );
   3648 
   3649 	lastPingValuesRecvTime = Sys_Milliseconds();
   3650 
   3651 	for ( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex ) {
   3652 		lobbyUser_t * u = GetLobbyUser( userIndex );
   3653 		if ( u->peerIndex != -1 && verify( u->peerIndex >= 0 && u->peerIndex < MAX_PEERS ) ) {
   3654 			u->pingMs = packet.pings[ u->peerIndex ];
   3655 		} else {
   3656 			u->pingMs = 0;
   3657 		}
   3658 	}
   3659 	
   3660 	// Stuff our ping in the hosts slot
   3661 	if ( peerIndexOnHost != -1 && verify( peerIndexOnHost >= 0 && peerIndexOnHost < MAX_PEERS ) ) {
   3662 		peers[host].lastPingRtt = packet.pings[ peerIndexOnHost ];
   3663 	} else {
   3664 		peers[host].lastPingRtt = 0;
   3665 	}
   3666 }
   3667 
   3668 /*
   3669 ========================
   3670 idLobby::SendAnotherFragment
   3671 Other than connectionless sends, this should be the chokepoint for sending packets to peers.
   3672 ========================
   3673 */
   3674 bool idLobby::SendAnotherFragment( int p ) {
   3675 	peer_t & peer = peers[p];
   3676 	
   3677 	if ( !peer.IsConnected() ) {	// Not connected to any mode (party or game), so no need to send
   3678 		return false;
   3679 	}
   3680 	
   3681 	if ( !peer.packetProc->HasMoreFragments() ) {
   3682 		return false;		// No fragments to send for this peer
   3683 	}
   3684 	
   3685 	if ( !CanSendMoreData( p ) ) {
   3686 		return false;		// We need to throttle the sends so we don't saturate the connection
   3687 	}
   3688 
   3689 	int time = Sys_Milliseconds();
   3690 
   3691 	if ( time - peer.lastFragmentSendTime < 2 ) {
   3692 		NET_VERBOSE_PRINT("Too soon to send another packet. Delta: %d \n", ( time-peer.lastFragmentSendTime) );
   3693 		return false;		// Too soon to send another fragment
   3694 	}
   3695 	
   3696 	peer.lastFragmentSendTime = time;
   3697 
   3698 	bool sentFragment = false;
   3699 	
   3700 	while ( true ) {
   3701 		idBitMsg msg;
   3702 		// We use the final packet size here because it has been processed, and no more headers will be added
   3703 		byte buffer[ idPacketProcessor::MAX_FINAL_PACKET_SIZE ];	
   3704 		msg.InitWrite( buffer, sizeof( buffer ) );
   3705 		
   3706 		if ( !peers[p].packetProc->GetSendFragment( time, peers[p].sessionID, msg ) ) {
   3707 			break;
   3708 		}
   3709 
   3710 		const bool useDirectPort = ( lobbyType == TYPE_GAME_STATE );
   3711 
   3712 		msg.BeginReading();
   3713 		sessionCB->SendRawPacket( peers[p].address, msg.GetReadData(), msg.GetSize(), useDirectPort ); 
   3714 		sentFragment = true;
   3715 		break;		// Comment this out to send all fragments in one burst
   3716 	}
   3717 
   3718 	if ( peer.packetProc->HasMoreFragments() ) {
   3719 		NET_VERBOSE_PRINT("More packets left after ::SendAnotherFragment\n");
   3720 	}
   3721 		
   3722 	return sentFragment;
   3723 }
   3724 
   3725 /*
   3726 ========================
   3727 idLobby::CanSendMoreData
   3728 ========================
   3729 */
   3730 bool idLobby::CanSendMoreData( int p ) {
   3731 	if ( !verify( p >= 0 && p < peers.Num() ) ) {
   3732 		NET_VERBOSE_PRINT( "NET: CanSendMoreData %i NO: not a peer\n", p );
   3733 		return false;
   3734 	}
   3735 	peer_t & peer = peers[p];
   3736 	if ( !peer.IsConnected() ) {
   3737 		NET_VERBOSE_PRINT( "NET: CanSendMoreData %i NO: not connected\n", p );
   3738 		return false;
   3739 	}
   3740 
   3741 	return peer.packetProc->CanSendMoreData();
   3742 }
   3743 
   3744 /*
   3745 ========================
   3746 idLobby::ProcessOutgoingMsg
   3747 ========================
   3748 */
   3749 void idLobby::ProcessOutgoingMsg( int p, const void * data, int size, bool isOOB, int userData ) {
   3750 
   3751 	peer_t & peer = peers[p];
   3752 	
   3753 	if ( peer.GetConnectionState() != CONNECTION_ESTABLISHED ) {
   3754 		idLib::Printf( "peer.GetConnectionState() != CONNECTION_ESTABLISHED\n" );
   3755 		return;	// Peer not fully connected for this session type, return
   3756 	}
   3757 
   3758 	if ( peer.packetProc->HasMoreFragments() ) {
   3759 		idLib::Error( "FATAL: Attempt to process a packet while fragments still need to be sent.\n" ); // We can't handle this case
   3760 	}
   3761 
   3762 	int currentTime = Sys_Milliseconds();
   3763 	
   3764 	// if ( currentTime - peer.lastProcTime < 30 ) {
   3765 	//	 idLib::Printf("ProcessOutgoingMsg called within %dms %s\n", (currentTime - peer.lastProcTime), GetLobbyName() );
   3766 	// }
   3767 
   3768 	peer.lastProcTime = currentTime;
   3769 
   3770 	if ( !isOOB ) {
   3771 		// Keep track of the last time an in-band packet was sent 
   3772 		// (used for things like knowing when reliables could have been last sent)
   3773 		peer.lastInBandProcTime = peer.lastProcTime;
   3774 	}
   3775 		
   3776 	idBitMsg msg;
   3777 	msg.InitRead( (byte*)data, size );
   3778 	peer.packetProc->ProcessOutgoing( currentTime, msg, isOOB, userData );
   3779 }
   3780 
   3781 /*
   3782 ========================
   3783 idLobby::ResendReliables
   3784 ========================
   3785 */
   3786 void idLobby::ResendReliables( int p ) {
   3787 
   3788 	peer_t & peer = peers[p];
   3789 	
   3790 	if ( !peer.IsConnected() ) {
   3791 		return;
   3792 	}
   3793 	
   3794 	if ( peer.packetProc->HasMoreFragments() ) {
   3795 		return;		// We can't send more data while fragments are still being sent out
   3796 	}
   3797 	
   3798 	if ( !CanSendMoreData( p ) ) {
   3799 		return;
   3800 	}
   3801 
   3802 	int time = Sys_Milliseconds();
   3803 
   3804 	const int DEFAULT_MIN_RESEND		= 20;		// Quicker resend while not in game to speed up resource transmission acks
   3805 	const int DEFAULT_MIN_RESEND_INGAME	= 100;
   3806 
   3807 	int resendWait = DEFAULT_MIN_RESEND_INGAME;
   3808 
   3809 	if ( sessionCB->GetState() == idSession::INGAME ) {
   3810 		// setup some minimum waits and account for ping
   3811 		resendWait = Max( DEFAULT_MIN_RESEND_INGAME, peer.lastPingRtt / 2 );
   3812 		if ( lobbyType == TYPE_PARTY ) {
   3813 			resendWait = Max( 500, resendWait ); // party session does not need fast frequency at all once in game
   3814 		}
   3815 	} else {
   3816 		// don't trust the ping when still loading stuff
   3817 		// need to resend fast to speed up transmission of network decls
   3818 		resendWait = DEFAULT_MIN_RESEND;
   3819 	}
   3820 
   3821 	if ( time - peer.lastInBandProcTime < resendWait ) {
   3822 		// no need to resend reliables if they went out on an in-band packet recently
   3823 		return;
   3824 	}
   3825 
   3826 	if ( peer.packetProc->NumQueuedReliables() > 0 || peer.packetProc->NeedToSendReliableAck() ) {
   3827 		//NET_VERBOSE_PRINT( "NET: ResendReliables %s\n", GetLobbyName() );
   3828 		ProcessOutgoingMsg( p, NULL, 0, false, 0 );		// Force an empty unreliable msg so any reliables will get processed as well
   3829 	}
   3830 }
   3831 
   3832 /*
   3833 ========================
   3834 idLobby::PumpPackets
   3835 ========================
   3836 */
   3837 void idLobby::PumpPackets() {
   3838 	int newTime = Sys_Milliseconds();
   3839 		
   3840 	for ( int p = 0; p < peers.Num(); p++ ) {		
   3841 		if ( peers[p].IsConnected() ) {
   3842 			peers[p].packetProc->RefreshRates( newTime );
   3843 		}
   3844 	}
   3845 
   3846 	// Resend reliable msg's (do this before we send out the fragments)
   3847 	for ( int p = 0; p < peers.Num(); p++ ) {		
   3848 		ResendReliables( p );
   3849 	}
   3850 	
   3851 	// 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
   3852 	// NOTE - We used to only send these to the host, but the host needs to also send these to clients
   3853 	for ( int p = 0; p < peers.Num(); p++ ) {		
   3854 		if ( !peers[p].IsConnected() || peers[p].packetProc->HasMoreFragments() ) {
   3855 			continue;
   3856 		}
   3857 		if ( newTime - peers[p].lastProcTime > 1000 * PEER_HEARTBEAT_IN_SECONDS ) {
   3858 			//NET_VERBOSE_PRINT( "NET: ProcessOutgoing Heartbeat %s\n", GetLobbyName() );
   3859 			ProcessOutgoingMsg( p, NULL, 0, false, 0 );		
   3860 		}
   3861 	}
   3862 
   3863 	// Send any unsent fragments for each peer (do this last)
   3864 	for ( int p = 0; p < peers.Num(); p++ ) {
   3865 		SendAnotherFragment( p );
   3866 	}
   3867 }
   3868 
   3869 /*
   3870 ========================
   3871 idLobby::UpdateMatchParms
   3872 ========================
   3873 */
   3874 void idLobby::UpdateMatchParms( const idMatchParameters & p ) {
   3875 	if ( !IsHost() ) {
   3876 		return;
   3877 	}
   3878 
   3879 	parms = p;
   3880 
   3881 	// Update lobbyBackend with parms
   3882 	if ( lobbyBackend != NULL ) {
   3883 		lobbyBackend->UpdateMatchParms( parms );
   3884 	}
   3885 	
   3886 	SendMatchParmsToPeers();
   3887 }
   3888 
   3889 /*
   3890 ========================
   3891 idLobby::GetHostUserName
   3892 ========================
   3893 */
   3894 const char * idLobby::GetHostUserName() const {
   3895 	if ( !IsLobbyActive() ) {
   3896 		return INVALID_LOBBY_USER_NAME;
   3897 	}
   3898 	return GetPeerName( -1 );		// This will just grab the first user with this peerIndex (which should be the host)
   3899 }
   3900 
   3901 /*
   3902 ========================
   3903 idLobby::SendReliable
   3904 ========================
   3905 */
   3906 void idLobby::SendReliable( int type, idBitMsg & msg, bool callReceiveReliable /*= true*/, peerMask_t sessionUserMask /*= MAX_UNSIGNED_TYPE( peerMask_t ) */ ) {
   3907 	//assert( lobbyType == GetActingGameStateLobbyType() );
   3908 
   3909 	assert( type < 256 ); // QueueReliable only accepts a byte for message type
   3910 
   3911 	// the queuing below sends the whole message
   3912 	// 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
   3913 	assert( msg.GetReadCount() + msg.GetReadBit() == 0 );
   3914 
   3915 	if ( callReceiveReliable ) {
   3916 		// NOTE: this will put the msg's read status to fully read - which is why the assert check is above
   3917 		common->NetReceiveReliable( -1, type, msg );
   3918 	}
   3919 
   3920 	uint32 sentPeerMask = 0;
   3921 	for ( int i = 0; i < GetNumLobbyUsers(); ++i ) {
   3922 		lobbyUser_t * user = GetLobbyUser( i );
   3923 		if ( user->peerIndex == -1 ) {
   3924 			continue;
   3925 		}
   3926 
   3927 		// We only care about sending these to peers in our party lobby
   3928 		if ( user->IsDisconnected() ) {
   3929 			continue;
   3930 		}
   3931 
   3932 		// Don't sent to a user if they are in the exlusion session user mask
   3933 		if ( sessionUserMask != 0 && ( sessionUserMask & ( BIT( i ) ) ) == 0 ) {
   3934 			continue;
   3935 		}
   3936 
   3937 		const int peerIndex = user->peerIndex;
   3938 
   3939 		if ( peerIndex >= peers.Num() ) {
   3940 			continue;
   3941 		}
   3942 
   3943 		peer_t & peer = peers[peerIndex];
   3944 
   3945 		if ( !peer.IsConnected() ) {
   3946 			continue;
   3947 		}
   3948 
   3949 		if ( ( sentPeerMask & ( 1 << user->peerIndex ) ) == 0 ) {
   3950 			QueueReliableMessage( user->peerIndex, idLobby::RELIABLE_GAME_DATA + type, msg.GetReadData(), msg.GetSize() );
   3951 			sentPeerMask |= 1 << user->peerIndex;
   3952 		}
   3953 	}
   3954 }
   3955 
   3956 /*
   3957 ========================
   3958 idLobby::SendReliableToLobbyUser
   3959 can only be used on the server. will take care of calling locally if addressed to player 0
   3960 ========================
   3961 */
   3962 void idLobby::SendReliableToLobbyUser( lobbyUserID_t lobbyUserID, int type, idBitMsg & msg ) {
   3963 	assert( lobbyType == GetActingGameStateLobbyType() );
   3964 	assert( type < 256 );			// QueueReliable only accepts a byte for message type
   3965 	assert( IsHost() );				// This function should only be called in the server atm
   3966 	const int peerIndex = PeerIndexFromLobbyUser( lobbyUserID );
   3967 	if ( peerIndex >= 0 ) {
   3968 		// will send the remainder of a message that was started reading through, but not handling a partial byte read
   3969 		assert( msg.GetReadBit() == 0 );
   3970 		QueueReliableMessage( peerIndex, idLobby::RELIABLE_GAME_DATA + type, msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() );
   3971 	} else {
   3972 		common->NetReceiveReliable( -1, type, msg );
   3973 	}
   3974 }
   3975 
   3976 /*
   3977 ========================
   3978 idLobby::SendReliableToHost
   3979 will make sure to invoke locally if used on the server
   3980 ========================
   3981 */
   3982 void idLobby::SendReliableToHost( int type, idBitMsg & msg ) {
   3983 	assert( lobbyType == GetActingGameStateLobbyType() );
   3984 
   3985 	if ( IsHost() ) {
   3986 		common->NetReceiveReliable( -1, type, msg );
   3987 	} else {
   3988 		// will send the remainder of a message that was started reading through, but not handling a partial byte read
   3989 		assert( msg.GetReadBit() == 0 );
   3990 		QueueReliableMessage( host, idLobby::RELIABLE_GAME_DATA + type, msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() );
   3991 	}
   3992 }
   3993 
   3994 /*
   3995 ================================================================================================
   3996 idLobby::reliablePlayerToPlayerHeader_t
   3997 ================================================================================================
   3998 */
   3999 
   4000 /*
   4001 ========================
   4002 idLobby::reliablePlayerToPlayerHeader_t::reliablePlayerToPlayerHeader_t
   4003 ========================
   4004 */
   4005 idLobby::reliablePlayerToPlayerHeader_t::reliablePlayerToPlayerHeader_t() : fromSessionUserIndex( -1 ), toSessionUserIndex( -1 ) {
   4006 }
   4007 
   4008 /*
   4009 ========================
   4010 idSessionLocal::reliablePlayerToPlayerHeader_t::Read
   4011 ========================
   4012 */
   4013 bool idLobby::reliablePlayerToPlayerHeader_t::Read( idLobby * lobby, idBitMsg & msg ) {
   4014 	assert( lobby != NULL );
   4015 
   4016 	lobbyUserID_t lobbyUserIDFrom;
   4017 	lobbyUserID_t lobbyUserIDTo;
   4018 
   4019 	lobbyUserIDFrom.ReadFromMsg( msg );
   4020 	lobbyUserIDTo.ReadFromMsg( msg );
   4021 
   4022 	fromSessionUserIndex	= lobby->GetLobbyUserIndexByID( lobbyUserIDFrom );
   4023 	toSessionUserIndex		= lobby->GetLobbyUserIndexByID( lobbyUserIDTo );
   4024 
   4025 	if ( !verify( lobby->GetLobbyUser( fromSessionUserIndex ) != NULL ) ) {
   4026 		return false;
   4027 	}
   4028 
   4029 	if ( !verify( lobby->GetLobbyUser( toSessionUserIndex ) != NULL ) ) {
   4030 		return false;
   4031 	}
   4032 
   4033 	return true;
   4034 }
   4035 
   4036 /*
   4037 ========================
   4038 idLobby::reliablePlayerToPlayerHeader_t::Write
   4039 ========================
   4040 */
   4041 bool idLobby::reliablePlayerToPlayerHeader_t::Write( idLobby * lobby, idBitMsg & msg ) {
   4042 
   4043 	
   4044 	if ( !verify( lobby->GetLobbyUser( fromSessionUserIndex ) != NULL ) ) {
   4045 		return false;
   4046 	}
   4047 
   4048 	if ( !verify( lobby->GetLobbyUser( toSessionUserIndex ) != NULL ) ) {
   4049 		return false;
   4050 	}
   4051 	
   4052 	lobby->GetLobbyUser( fromSessionUserIndex )->lobbyUserID.WriteToMsg( msg );
   4053 	lobby->GetLobbyUser( toSessionUserIndex )->lobbyUserID.WriteToMsg( msg );
   4054 
   4055 	return true;
   4056 }
   4057 
   4058 /*
   4059 ========================
   4060 idLobby::GetNumActiveLobbyUsers
   4061 ========================
   4062 */
   4063 int idLobby::GetNumActiveLobbyUsers() const {
   4064 	int numActive = 0;
   4065 	for ( int i = 0; i < GetNumLobbyUsers(); ++i ) {
   4066 		if ( !GetLobbyUser( i )->IsDisconnected() ) {
   4067 			numActive++;
   4068 		}
   4069 	}
   4070 	return numActive;
   4071 }
   4072 
   4073 /*
   4074 ========================
   4075 idLobby::AllPeersInGame
   4076 ========================
   4077 */
   4078 bool idLobby::AllPeersInGame() const {
   4079 	assert( lobbyType == GetActingGameStateLobbyType() );		// This function doesn't make sense on a party lobby currently
   4080 
   4081 	for ( int p = 0; p < peers.Num(); p++ ) {
   4082 		if ( peers[p].IsConnected() && !peers[p].inGame ) {
   4083 			return false;
   4084 		}
   4085 	}
   4086 	
   4087 	return true;
   4088 }
   4089 
   4090 /*
   4091 ========================
   4092 idLobby::PeerIndexFromLobbyUser
   4093 ========================
   4094 */
   4095 int	idLobby::PeerIndexFromLobbyUser( lobbyUserID_t lobbyUserID ) const {
   4096 	const int lobbyUserIndex = GetLobbyUserIndexByID( lobbyUserID );
   4097 
   4098 	const lobbyUser_t * user = GetLobbyUser( lobbyUserIndex );
   4099 
   4100 	if ( user == NULL ) {
   4101 		// This needs to be OK for bot support ( or else add bots at the session level )
   4102 		return -1;
   4103 	}
   4104 
   4105 	return user->peerIndex;
   4106 }
   4107 
   4108 /*
   4109 ========================
   4110 idLobby::GetPeerTimeSinceLastPacket
   4111 ========================
   4112 */
   4113 int idLobby::GetPeerTimeSinceLastPacket( int peerIndex ) const {
   4114 	if ( peerIndex < 0 ) {
   4115 		return 0;
   4116 	}
   4117 	return Sys_Milliseconds() - peers[peerIndex].lastHeartBeat;
   4118 }
   4119 
   4120 /*
   4121 ========================
   4122 idLobby::GetActingGameStateLobbyType
   4123 ========================
   4124 */
   4125 idLobby::lobbyType_t idLobby::GetActingGameStateLobbyType() const {
   4126 	extern idCVar net_useGameStateLobby;
   4127 	return ( net_useGameStateLobby.GetBool() ) ? TYPE_GAME_STATE : TYPE_GAME;
   4128 }
   4129 
   4130 //========================================================================================================================
   4131 //	idLobby::peer_t
   4132 //========================================================================================================================
   4133 
   4134 /*
   4135 ========================
   4136 idLobby::peer_t::GetConnectionState
   4137 ========================
   4138 */
   4139 idLobby::connectionState_t idLobby::peer_t::GetConnectionState() const {
   4140 	return connectionState;
   4141 }