DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

sys_session_local.cpp (130853B)


      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_session_local.h"
     31 #include "sys_voicechat.h"
     32 #include "sys_dedicated_server_search.h"
     33 
     34 
     35 idCVar ui_skinIndex( "ui_skinIndex", "0", CVAR_ARCHIVE, "Selected skin index" );
     36 idCVar ui_autoSwitch( "ui_autoSwitch", "1", CVAR_ARCHIVE | CVAR_BOOL, "auto switch weapon" );
     37 idCVar ui_autoReload( "ui_autoReload", "1", CVAR_ARCHIVE | CVAR_BOOL, "auto reload weapon" );
     38 
     39 idCVar net_maxSearchResults( "net_maxSearchResults", "25", CVAR_INTEGER, "Max results that are allowed to be returned in a search request" );
     40 idCVar net_maxSearchResultsToTry( "net_maxSearchResultsToTry", "5", CVAR_INTEGER, "Max results to try before giving up." );		// At 15 second timeouts per, 1 min 15 worth of connecting attempt time
     41 
     42 idCVar net_LobbyCoalesceTimeInSeconds( "net_LobbyCoalesceTimeInSeconds", "30", CVAR_INTEGER, "Time in seconds when a lobby will try to coalesce with another lobby when there is only one user." );
     43 idCVar net_LobbyRandomCoalesceTimeInSeconds( "net_LobbyRandomCoalesceTimeInSeconds", "3", CVAR_INTEGER, "Random time to add to net_LobbyCoalesceTimeInSeconds" );
     44 
     45 idCVar net_useGameStateLobby( "net_useGameStateLobby", "0", CVAR_BOOL, "" );
     46 //idCVar net_useGameStateLobby( "net_useGameStateLobby", "1", CVAR_BOOL, "" );
     47 
     48 #if !defined( ID_RETAIL ) || defined( ID_RETAIL_INTERNAL )
     49 idCVar net_ignoreTitleStorage( "net_ignoreTitleStorage", "0", CVAR_BOOL, "Ignore title storage" );
     50 #endif
     51 
     52 idCVar net_maxLoadResourcesTimeInSeconds( "net_maxLoadResourcesTimeInSeconds", "0", CVAR_INTEGER, "How long, in seconds, clients have to load resources. Used for loose asset builds." );
     53 idCVar net_migrateHost( "net_migrateHost", "-1", CVAR_INTEGER, "Become host of session (0 = party, 1 = game) for testing purposes" );
     54 extern idCVar net_debugBaseStates;
     55 
     56 idCVar net_testPartyMemberConnectFail( "net_testPartyMemberConnectFail", "-1", CVAR_INTEGER, "Force this party member index to fail to connect to games." );
     57 
     58 //FIXME: this could use a better name.
     59 idCVar net_offlineTransitionThreshold( "net_offlineTransitionThreshold", "1000", CVAR_INTEGER, "Time, in milliseconds, to wait before kicking back to the main menu when a profile losses backend connection during an online game");
     60 
     61 idCVar net_port( "net_port", "27015", CVAR_INTEGER, "host port number" ); // Port to host when using dedicated servers, port to broadcast on when looking for a dedicated server to connect to
     62 idCVar net_headlessServer( "net_headlessServer", "0", CVAR_BOOL, "toggle to automatically host a game and allow peer[0] to control menus" );
     63 
     64 const char * idSessionLocal::stateToString[ NUM_STATES ] = {
     65 	ASSERT_ENUM_STRING( STATE_PRESS_START, 0 ),
     66 	ASSERT_ENUM_STRING( STATE_IDLE, 1 ),
     67 	ASSERT_ENUM_STRING( STATE_PARTY_LOBBY_HOST, 2 ),
     68 	ASSERT_ENUM_STRING( STATE_PARTY_LOBBY_PEER, 3 ),
     69 	ASSERT_ENUM_STRING( STATE_GAME_LOBBY_HOST, 4 ),
     70 	ASSERT_ENUM_STRING( STATE_GAME_LOBBY_PEER, 5 ),
     71 	ASSERT_ENUM_STRING( STATE_GAME_STATE_LOBBY_HOST, 6 ),
     72 	ASSERT_ENUM_STRING( STATE_GAME_STATE_LOBBY_PEER, 7 ),
     73 	ASSERT_ENUM_STRING( STATE_CREATE_AND_MOVE_TO_PARTY_LOBBY, 8 ),
     74 	ASSERT_ENUM_STRING( STATE_CREATE_AND_MOVE_TO_GAME_LOBBY, 9 ),
     75 	ASSERT_ENUM_STRING( STATE_CREATE_AND_MOVE_TO_GAME_STATE_LOBBY, 10 ),
     76 	ASSERT_ENUM_STRING( STATE_FIND_OR_CREATE_MATCH, 11 ),
     77 	ASSERT_ENUM_STRING( STATE_CONNECT_AND_MOVE_TO_PARTY, 12 ),
     78 	ASSERT_ENUM_STRING( STATE_CONNECT_AND_MOVE_TO_GAME, 13 ),
     79 	ASSERT_ENUM_STRING( STATE_CONNECT_AND_MOVE_TO_GAME_STATE, 14 ),
     80 	ASSERT_ENUM_STRING( STATE_BUSY, 15 ),
     81 	ASSERT_ENUM_STRING( STATE_LOADING, 16 ),
     82 	ASSERT_ENUM_STRING( STATE_INGAME, 17 ),
     83 };
     84 
     85 struct netVersion_s {
     86 	netVersion_s() { sprintf( string, "%s.%d", ENGINE_VERSION, BUILD_NUMBER ); }
     87 	char	string[256];
     88 } netVersion;
     89 
     90 /*
     91 ========================
     92 NetGetVersionChecksum
     93 ========================
     94 */
     95 unsigned long NetGetVersionChecksum() {
     96 #if 0
     97 	return idStr( com_version.GetString() ).Hash();
     98 #else
     99 	unsigned long ret = 0;
    100 
    101 	CRC32_InitChecksum( ret );
    102 	CRC32_UpdateChecksum( ret, netVersion.string, idStr::Length( netVersion.string ) );
    103 	CRC32_FinishChecksum( ret );
    104 
    105 	NET_VERBOSE_PRINT( "NetGetVersionChecksum - string   : %s\n", netVersion.string );
    106 	NET_VERBOSE_PRINT( "NetGetVersionChecksum - checksum : %i\n", ret );
    107 	return ret; 
    108 #endif
    109 }
    110 
    111 /*
    112 ========================
    113 idSessionLocal::idSessionLocal
    114 ========================
    115 */
    116 idSessionLocal::idSessionLocal() :
    117 	processorSaveFiles( new (TAG_SAVEGAMES) idSaveGameProcessorSaveFiles ),
    118 	processorLoadFiles( new (TAG_SAVEGAMES) idSaveGameProcessorLoadFiles ),
    119 	processorDelete(	new (TAG_SAVEGAMES) idSaveGameProcessorDelete ),
    120 	processorEnumerate( new (TAG_SAVEGAMES) idSaveGameProcessorEnumerateGames ) {
    121 	InitBaseState();
    122 }
    123 
    124 /*
    125 ========================
    126 idSessionLocal::idSessionLocal
    127 ========================
    128 */
    129 idSessionLocal::~idSessionLocal() {
    130 	delete processorSaveFiles;
    131 	delete processorLoadFiles;
    132 	delete processorDelete;
    133 	delete processorEnumerate;
    134 	delete sessionCallbacks;
    135 }
    136 
    137 
    138 /*
    139 ========================
    140 idSessionLocal::InitBaseState
    141 ========================
    142 */
    143 void idSessionLocal::InitBaseState() {
    144 	
    145 	//assert( mem.IsGlobalHeap() );
    146 
    147 	localState						= STATE_PRESS_START;
    148 	sessionOptions					= 0;
    149 	currentID						= 0;
    150 
    151 	sessionCallbacks				= new (TAG_NETWORKING) idSessionLocalCallbacks( this );
    152 
    153 	connectType						= CONNECT_NONE;
    154 	connectTime						= 0;
    155 	
    156 	upstreamDropRate				= 0.0f;
    157 	upstreamDropRateTime			= 0;
    158 	upstreamQueueRate				= 0.0f;
    159 	upstreamQueueRateTime			= 0;
    160 	queuedBytes						= 0;
    161 
    162 	lastVoiceSendtime				= 0;
    163 	hasShownVoiceRestrictionDialog	= false;
    164 
    165 	isSysUIShowing					= false;
    166 
    167 	pendingInviteDevice				= 0;
    168 	pendingInviteMode				= PENDING_INVITE_NONE;
    169 
    170 	downloadedContent.Clear();
    171 	marketplaceHasNewContent		= false;
    172 
    173 	offlineTransitionTimerStart		= 0;
    174 	showMigratingInfoStartTime		= 0;
    175 	nextGameCoalesceTime			= 0;
    176 	gameLobbyWasCoalesced			= false;
    177 	numFullSnapsReceived			= 0;
    178 
    179 	flushedStats					= false;
    180 
    181 	titleStorageLoaded				= false;
    182 
    183 	droppedByHost					= false;
    184 	loadingID						= 0;
    185 
    186 	storedPeer						= -1;
    187 	storedMsgType					= -1;
    188 
    189 	inviteInfoRequested				= false;
    190 
    191 	enumerationHandle				= 0;
    192 
    193 	waitingOnGameStateMembersToLeaveTime	= 0;
    194 	waitingOnGameStateMembersToJoinTime		= 0;
    195 }
    196 
    197 /*
    198 ========================
    199 idSessionLocal::FinishDisconnect
    200 ========================
    201 */
    202 void idSessionLocal::FinishDisconnect() { 
    203 	GetPort().Close(); 
    204 	while ( sendQueue.Peek() != NULL ) {
    205 		sendQueue.RemoveFirst();
    206 	}
    207 	while ( recvQueue.Peek() != NULL ) {
    208 		recvQueue.RemoveFirst();
    209 	}
    210 }
    211 
    212 //====================================================================================
    213 
    214 idCVar net_connectTimeoutInSeconds( "net_connectTimeoutInSeconds", "15", CVAR_INTEGER, "timeout (in seconds) while connecting" );
    215 
    216 /*
    217 ========================
    218 idSessionLocal::CreatePartyLobby
    219 ========================
    220 */
    221 void idSessionLocal::CreatePartyLobby( const idMatchParameters & parms_ ) { 
    222 	NET_VERBOSE_PRINT( "NET: CreatePartyLobby\n" );
    223 
    224 	// Shutdown any possible party lobby
    225 	GetPartyLobby().Shutdown();
    226 	GetPartyLobby().ResetAllMigrationState();
    227 
    228 	// Shutdown any possible game lobby
    229 	GetGameLobby().Shutdown();
    230 	GetGameStateLobby().Shutdown();
    231 
    232 	// Start hosting a new party lobby
    233 	GetPartyLobby().StartHosting( parms_ );
    234 
    235 	connectType = CONNECT_NONE;
    236 	connectTime = Sys_Milliseconds();
    237 
    238 	// Wait for it to complete
    239 	SetState( STATE_CREATE_AND_MOVE_TO_PARTY_LOBBY );
    240 }
    241 
    242 /*
    243 ========================
    244 idSessionLocal::CreateMatch
    245 ========================
    246 */
    247 void idSessionLocal::CreateMatch( const idMatchParameters & p ) {
    248 	NET_VERBOSE_PRINT( "NET: CreateMatch\n" );
    249 
    250 	if ( ( p.matchFlags & MATCH_PARTY_INVITE_PLACEHOLDER ) && !GetPartyLobby().IsLobbyActive() ) {
    251 		NET_VERBOSE_PRINT( "NET: CreateMatch MATCH_PARTY_INVITE_PLACEHOLDER\n" );
    252 		CreatePartyLobby( p );
    253 		connectType = CONNECT_NONE;
    254 		return;
    255 	}
    256 
    257 	// Shutdown any possible game lobby
    258 	GetGameLobby().Shutdown();
    259 	GetGameStateLobby().Shutdown();
    260 	GetGameLobby().ResetAllMigrationState();
    261 
    262 	// Start hosting a new game lobby
    263 	GetGameLobby().StartHosting( p );
    264 
    265 	connectType = CONNECT_NONE;
    266 	connectTime = Sys_Milliseconds();
    267 
    268 	// Wait for it to complete
    269 	SetState( STATE_CREATE_AND_MOVE_TO_GAME_LOBBY );
    270 }
    271 
    272 /*
    273 ========================
    274 idSessionLocal::CreateGameStateLobby
    275 ========================
    276 */
    277 void idSessionLocal::CreateGameStateLobby( const idMatchParameters & p ) {
    278 	NET_VERBOSE_PRINT( "NET: CreateGameStateLobby\n" );
    279 
    280 	// Shutdown any possible game state lobby
    281 	GetGameStateLobby().Shutdown();
    282 	GetGameStateLobby().ResetAllMigrationState();
    283 
    284 	// Start hosting a new game lobby
    285 	GetGameStateLobby().StartHosting( p );
    286 
    287 	connectType = CONNECT_NONE;
    288 	connectTime = Sys_Milliseconds();
    289 
    290 	waitingOnGameStateMembersToLeaveTime	= 0;		// Make sure to reset
    291 	waitingOnGameStateMembersToJoinTime		= 0;
    292 
    293 	// Wait for it to complete
    294 	SetState( STATE_CREATE_AND_MOVE_TO_GAME_STATE_LOBBY );
    295 }
    296 
    297 /*
    298 ========================
    299 idSessionLocal::FindOrCreateMatch
    300 ========================
    301 */
    302 void idSessionLocal::FindOrCreateMatch( const idMatchParameters & p ) { 
    303 	NET_VERBOSE_PRINT( "NET: FindOrCreateMatch\n" );
    304 
    305 	if ( ( p.matchFlags & MATCH_PARTY_INVITE_PLACEHOLDER ) && !GetPartyLobby().IsLobbyActive() ) {
    306 		NET_VERBOSE_PRINT( "NET: FindOrCreateMatch MATCH_PARTY_INVITE_PLACEHOLDER\n" );
    307 		CreatePartyLobby( p );
    308 		connectType = CONNECT_FIND_OR_CREATE;
    309 		return;
    310 	}
    311 
    312 	// Shutdown any possible game lobby
    313 	GetGameLobby().Shutdown();
    314 	GetGameStateLobby().Shutdown();
    315 	GetGameLobby().ResetAllMigrationState();
    316 
    317 	// Start searching for a game
    318 	GetGameLobby().StartFinding( p );
    319 
    320 	connectType				= CONNECT_FIND_OR_CREATE;
    321 	connectTime				= Sys_Milliseconds();
    322 	gameLobbyWasCoalesced	= false;
    323 	numFullSnapsReceived	= 0;
    324 
    325 	// Wait for searching to complete
    326 	SetState( STATE_FIND_OR_CREATE_MATCH );
    327 }
    328 
    329 /*
    330 ========================
    331 idSessionLocal::StartLoading
    332 ========================
    333 */
    334 void idSessionLocal::StartLoading() {
    335 	NET_VERBOSE_PRINT( "NET: StartLoading\n" );
    336 
    337 	if ( MatchTypeIsOnline( GetActingGameStateLobby().parms.matchFlags ) ) {
    338 		if ( !GetActingGameStateLobby().IsHost() ) {
    339 			idLib::Warning( "Ignoring call to StartLoading because we are not the host.  state is %s", stateToString[ localState ] );
    340 			return;
    341 		}
    342 
    343 		for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) {
    344 			if ( GetActingGameStateLobby().peers[p].IsConnected() ) {
    345 				GetActingGameStateLobby().QueueReliableMessage( p, idLobby::RELIABLE_START_LOADING );
    346 				GetActingGameStateLobby().peers[p].startResourceLoadTime = Sys_Milliseconds();
    347 			}
    348 		}
    349 	}
    350 
    351 	VerifySnapshotInitialState();
    352 
    353 	SetState( STATE_LOADING );
    354 }
    355 
    356 /*
    357 ========================
    358 idSessionLocal::StartMatch
    359 ========================
    360 */
    361 void idSessionLocal::StartMatch() {
    362 	NET_VERBOSE_PRINT( "NET: StartMatch\n" );
    363 
    364 	if ( net_headlessServer.GetBool() ) {
    365 		StartLoading();		// This is so we can force start matches on headless servers to test performance using bots
    366 		return;
    367 	}
    368 
    369 	if ( localState != STATE_GAME_LOBBY_HOST ) {
    370 		idLib::Warning( "idSessionLocal::StartMatch called when not hosting game lobby" );
    371 		return;
    372 	}
    373 
    374 	assert( !GetGameStateLobby().IsLobbyActive() );
    375 
    376 	// make absolutely sure we only call StartMatch once per migrate
    377 	GetGameLobby().migrationInfo.persistUntilGameEndsData.hasRelaunchedMigratedGame = true;
    378 	
    379 	// Clear snap ack queue between games
    380 	GetGameLobby().snapDeltaAckQueue.Clear();
    381 
    382 	extern idCVar net_bw_challenge_enable;
    383 	if ( session->GetTitleStorageBool( "net_bw_challenge_enable", net_bw_challenge_enable.GetBool() ) && GetGameLobby().HasActivePeers() ) {
    384 	    GetGameLobby().bandwidthChallengeFinished = false;
    385 		StartOrContinueBandwidthChallenge( false );
    386 	}
    387 
    388 	if ( GetGameLobby().BandwidthTestStarted() ) {
    389 		// Put session in busy state 
    390 		NET_VERBOSE_PRINT( "NET: StartMatch -> Start Bandwidth Challenge\n" );
    391 		SetState( STATE_BUSY );
    392 	} else {
    393 		// Start loading
    394 		StartLoading();
    395 	}
    396 }
    397 
    398 /*
    399 ========================
    400 idSessionLocal::GetBackState
    401 ========================
    402 */
    403 idSessionLocal::sessionState_t idSessionLocal::GetBackState() {
    404 	sessionState_t currentState = GetState();
    405 
    406 	const bool isInGameLobby		= currentState == GAME_LOBBY;
    407 	const bool isInPartyLobby		= currentState == PARTY_LOBBY;
    408 	const bool isInGame				= currentState == INGAME || currentState == LOADING;		// Counting loading as ingame as far as what back state to go to
    409 	
    410 	if ( isInGame ) {
    411 		return GAME_LOBBY;		// If in the game, go back to game lobby
    412 	}
    413 
    414 	if ( !isInPartyLobby && isInGameLobby && ShouldHavePartyLobby() ) {
    415 		return PARTY_LOBBY;		// If in the game lobby, and we should have a party lobby, and we are the host, go back to party lobby
    416 	}
    417 
    418 	if ( currentState != IDLE ) {
    419 		return IDLE;			// From here, go to idle if we aren't there yet
    420 	}
    421 
    422 	return PRESS_START;			// Otherwise, go back to press start
    423 }
    424 
    425 /*
    426 ========================
    427 idSessionLocal::Cancel
    428 ========================
    429 */
    430 void idSessionLocal::Cancel() {
    431 	NET_VERBOSE_PRINT( "NET: Cancel\n" );
    432 
    433 	if ( localState == STATE_PRESS_START ) {
    434 		return;		// We're as far back as we can go
    435 	}
    436 
    437 	ClearVoiceGroups(); // this is here as a catch-all
    438 
    439 	// See what state we need to go to
    440 	switch ( GetBackState() ) {
    441 		case GAME_LOBBY:
    442 			EndMatch();		// End current match to go to game lobby
    443 			break;
    444 
    445 		case PARTY_LOBBY:
    446 			if ( GetPartyLobby().IsHost() ) {
    447 				if ( sessionOptions & OPTION_LEAVE_WITH_PARTY ) {
    448 					// NOTE - This will send a message on the team lobby channel, 
    449 					// so it won't be affected by the fact that we're shutting down the game lobby
    450 					GetPartyLobby().NotifyPartyOfLeavingGameLobby();
    451 				} else {
    452 					// Host wants to be alone, disconnect all peers from the party
    453 					GetPartyLobby().DisconnectAllPeers();		
    454 				}
    455 
    456 				// End the game lobby, and go back to party lobby as host
    457 				GetGameLobby().Shutdown();
    458 				GetGameStateLobby().Shutdown();
    459 				SetState( STATE_PARTY_LOBBY_HOST );
    460 
    461 				// Always remove this flag.  SendGoodbye uses this to determine if we should send a "leave with party"
    462 				// and we don't want this flag hanging around, and causing false positives when it's called in the future.
    463 				// Make them set this each time.
    464 				sessionOptions &= ~OPTION_LEAVE_WITH_PARTY;
    465 			} else {
    466 				// If we aren't the host of a party and we want to go back to one, we need to create a party now
    467 				CreatePartyLobby( GetPartyLobby().parms );
    468 			}
    469 			break;
    470 
    471 		case IDLE:
    472 			// Go back to main menu
    473 			GetGameLobby().Shutdown();
    474 			GetGameStateLobby().Shutdown();
    475 			GetPartyLobby().Shutdown();
    476 			SetState( STATE_IDLE );
    477 			break;
    478 
    479 		case PRESS_START:
    480 			// Go back to press start/main
    481 			GetGameLobby().Shutdown();
    482 			GetGameStateLobby().Shutdown();
    483 			GetPartyLobby().Shutdown();
    484 			SetState( STATE_PRESS_START );
    485 			break;
    486 	}
    487 
    488 	// Validate the current lobby immediately
    489 	ValidateLobbies();
    490 }
    491 
    492 /*
    493 ========================
    494 idSessionLocal::MoveToPressStart
    495 ========================
    496 */
    497 void idSessionLocal::MoveToPressStart() {	
    498 	if ( localState != STATE_PRESS_START ) {
    499 		assert( signInManager != NULL );
    500 		signInManager->RemoveAllLocalUsers();
    501 		hasShownVoiceRestrictionDialog = false;
    502 		MoveToMainMenu();
    503 		session->FinishDisconnect();
    504 		SetState( STATE_PRESS_START );
    505 	}
    506 }
    507 
    508 /*
    509 ========================
    510 idSessionLocal::ShouldShowMigratingDialog
    511 ========================
    512 */
    513 bool idSessionLocal::ShouldShowMigratingDialog() const {
    514 	const idLobby * activeLobby = GetActivePlatformLobby();
    515 
    516 	if ( activeLobby == NULL ) {
    517 		return false;
    518 	}
    519 	
    520 	return activeLobby->ShouldShowMigratingDialog();
    521 }
    522 
    523 /*
    524 ========================
    525 idSessionLocal::IsCurrentLobbyMigrating
    526 ========================
    527 */
    528 bool idSessionLocal::IsCurrentLobbyMigrating() const {
    529 	const idLobby * activeLobby = GetActivePlatformLobby();
    530 
    531 	if ( activeLobby == NULL ) {
    532 		return false;
    533 	}
    534 
    535 	return activeLobby->IsMigrating();
    536 }
    537 
    538 /*
    539 ========================
    540 idSessionLocal::IsLosingConnectionToHost
    541 ========================
    542 */
    543 bool idSessionLocal::IsLosingConnectionToHost() const {
    544 	return GetActingGameStateLobby().IsLosingConnectionToHost();
    545 }
    546 
    547 /*
    548 ========================
    549 idSessionLocal::WasMigrationGame
    550 returns true if we are hosting a migrated game and we had valid migration data
    551 ========================
    552 */
    553 bool idSessionLocal::WasMigrationGame() const {
    554 	return GetGameLobby().IsMigratedStatsGame();
    555 }
    556 
    557 /*
    558 ========================
    559 idSessionLocal::ShouldRelaunchMigrationGame
    560 returns true if we are hosting a migrated game and we had valid migration data
    561 ========================
    562 */
    563 bool idSessionLocal::ShouldRelaunchMigrationGame() const { 
    564 	return GetGameLobby().ShouldRelaunchMigrationGame() && !IsCurrentLobbyMigrating();
    565 }
    566 
    567 /*
    568 ========================
    569 idSessionLocal::GetMigrationGameData
    570 ========================
    571 */
    572 bool idSessionLocal::GetMigrationGameData( idBitMsg & msg, bool reading ) {
    573 	return GetGameLobby().GetMigrationGameData( msg, reading );
    574 }
    575 
    576 /*
    577 ========================
    578 idSessionLocal::GetMigrationGameDataUser
    579 ========================
    580 */
    581 bool idSessionLocal::GetMigrationGameDataUser( lobbyUserID_t lobbyUserID, idBitMsg & msg, bool reading ) {
    582 	if ( GetGameStateLobby().IsHost() ) {
    583 		return false;
    584 	}
    585 
    586 	return GetGameLobby().GetMigrationGameDataUser( lobbyUserID, msg, reading );
    587 }
    588 
    589 
    590 /*
    591 ========================
    592 idSessionLocal::GetMatchParamUpdate
    593 ========================
    594 */
    595 bool idSessionLocal::GetMatchParamUpdate( int &peer, int &msg ){
    596 	if ( storedPeer != -1 && storedMsgType != -1 ) {
    597 		peer = storedPeer;
    598 		msg = storedMsgType;
    599 		storedPeer = -1;
    600 		storedMsgType = -1;
    601 		return true;
    602 	}
    603 	return false;
    604 }
    605 
    606 
    607 
    608 /*
    609 ========================
    610 idSessionLocal::UpdatePartyParms
    611 
    612 Updates the party parameters when in a party lobby OR a game lobby in order to keep them always in sync.
    613 ========================
    614 */
    615 void idSessionLocal::UpdatePartyParms( const idMatchParameters & p ) {
    616 	if ( ( GetState() != PARTY_LOBBY && GetState() != GAME_LOBBY ) || !GetPartyLobby().IsHost() ) {
    617 		return;
    618 	}
    619 
    620 	// NET_VERBOSE_PRINT( "NET: UpdatePartyParms\n" );
    621 
    622 	GetPartyLobby().UpdateMatchParms( p );
    623 }
    624 
    625 /*
    626 ========================
    627 idSessionLocal::UpdateMatchParms
    628 ========================
    629 */
    630 void idSessionLocal::UpdateMatchParms( const idMatchParameters & p ) {
    631 	if ( GetState() != GAME_LOBBY || !GetGameLobby().IsHost() ) {
    632 		return;
    633 	}
    634 
    635 	NET_VERBOSE_PRINT( "NET: UpdateMatchParms\n" );
    636 
    637 	GetGameLobby().UpdateMatchParms( p );
    638 }
    639 
    640 /*
    641 ========================
    642 idSessionLocal::StartSessions
    643 ========================
    644 */
    645 void idSessionLocal::StartSessions() {
    646 	if ( GetPartyLobby().lobbyBackend != NULL ) {
    647 		GetPartyLobby().lobbyBackend->StartSession();
    648 	}
    649 
    650 	if ( GetGameLobby().lobbyBackend != NULL ) {
    651 		GetGameLobby().lobbyBackend->StartSession();
    652 	}
    653 	
    654 	SetLobbiesAreJoinable( false );
    655 }
    656 
    657 /*
    658 ========================
    659 idSessionLocal::EndSessions
    660 ========================
    661 */
    662 void idSessionLocal::EndSessions() {
    663 	if ( GetPartyLobby().lobbyBackend != NULL ) {
    664 		GetPartyLobby().lobbyBackend->EndSession();
    665 	}
    666 
    667 	if ( GetGameLobby().lobbyBackend != NULL ) {
    668 		GetGameLobby().lobbyBackend->EndSession();
    669 	}
    670 
    671 	SetLobbiesAreJoinable( true );
    672 }
    673 
    674 /*
    675 ========================
    676 idSessionLocal::SetLobbiesAreJoinable
    677 ========================
    678 */
    679 void idSessionLocal::SetLobbiesAreJoinable( bool joinable ) {
    680 	// NOTE - We don't manipulate the joinable state when we are supporting join in progress
    681 	// Lobbies will naturally be non searchable when there are no free slots
    682 	if ( GetPartyLobby().lobbyBackend != NULL && !MatchTypeIsJoinInProgress( GetPartyLobby().parms.matchFlags ) ) {
    683 		NET_VERBOSE_PRINT( "Party lobbyBackend SetIsJoinable: %d\n", joinable );
    684 		GetPartyLobby().lobbyBackend->SetIsJoinable( joinable );
    685 	}
    686 	
    687 	if ( GetGameLobby().lobbyBackend != NULL && !MatchTypeIsJoinInProgress( GetGameLobby().parms.matchFlags ) ) {
    688 		GetGameLobby().lobbyBackend->SetIsJoinable( joinable );
    689 		NET_VERBOSE_PRINT( "Game lobbyBackend SetIsJoinable: %d\n", joinable );
    690 
    691 	}
    692 }
    693 
    694 /*
    695 ========================
    696 idSessionLocal::MoveToMainMenu
    697 ========================
    698 */
    699 void idSessionLocal::MoveToMainMenu() {
    700 	GetPartyLobby().Shutdown();
    701 	GetGameLobby().Shutdown();
    702 	GetGameStateLobby().Shutdown();
    703 	SetState( STATE_IDLE );
    704 }
    705 
    706 /*
    707 ========================
    708 idSessionLocal::HandleVoiceRestrictionDialog
    709 ========================
    710 */
    711 void idSessionLocal::HandleVoiceRestrictionDialog() {
    712 	// don't bother complaining about voice restrictions when in a splitscreen lobby
    713 	if ( MatchTypeIsLocal( GetActivePlatformLobby()->parms.matchFlags ) ) {
    714 		return;
    715 	}
    716 
    717 	// Pop a dialog up the first time we are in a lobby and have voice chat restrictions due to account privileges
    718 	if ( voiceChat != NULL && voiceChat->IsRestrictedByPrivleges() && !hasShownVoiceRestrictionDialog ) {
    719 		common->Dialog().AddDialog( GDM_VOICE_RESTRICTED, DIALOG_ACCEPT, NULL, NULL, false );
    720 		hasShownVoiceRestrictionDialog = true;
    721 	}
    722 }
    723 
    724 /*
    725 ========================
    726 idSessionLocal::WaitOnLobbyCreate
    727 
    728 Called from State_Create_And_Move_To_Party_Lobby and State_Create_And_Move_To_Game_Lobby and State_Create_And_Move_To_Game_State_Lobby.
    729 This function will create the lobby, then wait for it to either succeed or fail.
    730 ========================
    731 */
    732 bool idSessionLocal::WaitOnLobbyCreate( idLobby & lobby ) {
    733 
    734 	assert( localState == STATE_CREATE_AND_MOVE_TO_PARTY_LOBBY || localState == STATE_CREATE_AND_MOVE_TO_GAME_LOBBY || localState == STATE_CREATE_AND_MOVE_TO_GAME_STATE_LOBBY );
    735 	assert( connectType == CONNECT_FIND_OR_CREATE || connectType == CONNECT_NONE );
    736 
    737 	if ( lobby.GetState() == idLobby::STATE_FAILED ) {
    738 		NET_VERBOSE_PRINT( "NET: idSessionLocal::WaitOnLobbyCreate lobby.GetState() == idLobby::STATE_FAILED (%s)\n", lobby.GetLobbyName() );
    739 		// If we failed to create a lobby, assume connection to backend service was lost
    740 		MoveToMainMenu();
    741 		common->Dialog().ClearDialogs( true );
    742 		common->Dialog().AddDialog( GDM_CONNECTION_LOST, DIALOG_ACCEPT, NULL, NULL, true, "", 0, true );
    743 		return false;
    744 	}
    745 	
    746 	if ( DetectDisconnectFromService( true ) ) {
    747 		return false;
    748 	}
    749 
    750 	if ( lobby.GetState() != idLobby::STATE_IDLE ) {
    751 		return false;		// Valid but busy
    752 	}
    753 
    754 	NET_VERBOSE_PRINT( "NET: idSessionLocal::WaitOnLobbyCreate SUCCESS (%s)\n", lobby.GetLobbyName() );
    755 
    756 	return true;
    757 }
    758 
    759 /*
    760 ========================
    761 idSessionLocal::DetectDisconnectFromService
    762 Called from CreateMatch/CreatePartyLobby/FindOrCreateMatch state machines
    763 ========================
    764 */
    765 bool idSessionLocal::DetectDisconnectFromService( bool cancelAndShowMsg ) {
    766 	const int DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS = session->GetTitleStorageInt( "DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS", 30 );
    767 
    768 	// If we are taking too long, cancel the connection
    769 	if ( DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS > 0 ) {
    770 		if ( Sys_Milliseconds() - connectTime > 1000 * DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS ) {
    771 			NET_VERBOSE_PRINT( "NET: idSessionLocal::DetectDisconnectFromService timed out\n" );
    772 			if ( cancelAndShowMsg ) {
    773 				MoveToMainMenu();
    774 				common->Dialog().ClearDialogs( true );
    775 				common->Dialog().AddDialog( GDM_CONNECTION_LOST, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true );
    776 			}
    777 
    778 			return true;
    779 		}
    780 	}
    781 
    782 	return false;
    783 }
    784 
    785 /*
    786 ========================
    787 idSessionLocal::HandleConnectionFailed
    788 Called anytime a connection fails, and does the right thing.
    789 ========================
    790 */
    791 void idSessionLocal::HandleConnectionFailed( idLobby & lobby, bool wasFull ) {
    792 	assert( localState == STATE_CONNECT_AND_MOVE_TO_PARTY || localState == STATE_CONNECT_AND_MOVE_TO_GAME || localState == STATE_CONNECT_AND_MOVE_TO_GAME_STATE );
    793 	assert( connectType == CONNECT_FIND_OR_CREATE || connectType == CONNECT_DIRECT );
    794 	bool canPlayOnline = true;
    795 	
    796 	// Check for online status (this is only a problem on the PS3 at the moment. The 360 LIVE system handles this for us
    797 	if ( GetSignInManager().GetMasterLocalUser() != NULL ) {
    798 		canPlayOnline = GetSignInManager().GetMasterLocalUser()->CanPlayOnline();
    799 	}
    800 	
    801 	if ( connectType == CONNECT_FIND_OR_CREATE ) {
    802 		// Clear the "Lobby was Full" dialog in case it's up
    803 		// We only want to see this msg when doing a direct connect (CONNECT_DIRECT)
    804 		common->Dialog().ClearDialog( GDM_LOBBY_FULL );
    805 
    806 		assert( localState == STATE_CONNECT_AND_MOVE_TO_GAME || localState == STATE_CONNECT_AND_MOVE_TO_GAME_STATE );
    807 		assert( lobby.lobbyType == idLobby::TYPE_GAME );
    808 		if ( !lobby.ConnectToNextSearchResult() ) {
    809 			CreateMatch( GetGameLobby().parms );		// Assume any time we are connecting to a game lobby, it is from a FindOrCreateMatch call, so create a match
    810 		}
    811 	} else if ( connectType == CONNECT_DIRECT ) {
    812 		if ( localState == STATE_CONNECT_AND_MOVE_TO_GAME && GetPartyLobby().IsPeer() ) {
    813 			int flags = GetPartyLobby().parms.matchFlags;
    814 
    815 			if ( MatchTypeIsOnline( flags ) && ( flags & MATCH_REQUIRE_PARTY_LOBBY ) && ( ( flags & MATCH_PARTY_INVITE_PLACEHOLDER ) == 0 ) ) {
    816 				// We get here when our party host told us to connect to a game, but the game didn't exist.  
    817 				// Just drop back to the party lobby and wait for further orders.
    818 				SetState( STATE_PARTY_LOBBY_PEER );
    819 				return;
    820 			}
    821 		}
    822 
    823 		if ( wasFull ) {
    824 			common->Dialog().AddDialog( GDM_LOBBY_FULL, DIALOG_ACCEPT, NULL, NULL, false );
    825 		} else if ( !canPlayOnline ) {
    826 			common->Dialog().AddDialog( GDM_PLAY_ONLINE_NO_PROFILE, DIALOG_ACCEPT, NULL, NULL, false );
    827 		} else {
    828 			// TEMP HACK: We detect the steam lobby is full in idLobbyBackendWin, and then STATE_FAILED, which brings us here. Need to find a way to notify
    829 			// session local that the game was full so we don't do this check here
    830 			// eeubanks: Pollard, how do you think we should handle this?
    831 			if ( !common->Dialog().HasDialogMsg( GDM_LOBBY_FULL, NULL ) ) {
    832 				common->Dialog().AddDialog( GDM_INVALID_INVITE, DIALOG_ACCEPT, NULL, NULL, false );
    833 			}
    834 		}
    835 		MoveToMainMenu();
    836 	} else {
    837 		// Shouldn't be possible, but just in case
    838 		MoveToMainMenu();
    839 	}
    840 }
    841 
    842 /*
    843 ========================
    844 idSessionLocal::HandleConnectAndMoveToLobby
    845 Called from State_Connect_And_Move_To_Party/State_Connect_And_Move_To_Game
    846 ========================
    847 */
    848 bool idSessionLocal::HandleConnectAndMoveToLobby( idLobby & lobby ) {	
    849 	assert( localState == STATE_CONNECT_AND_MOVE_TO_PARTY || localState == STATE_CONNECT_AND_MOVE_TO_GAME || localState == STATE_CONNECT_AND_MOVE_TO_GAME_STATE );
    850 	assert( connectType == CONNECT_FIND_OR_CREATE || connectType == CONNECT_DIRECT );
    851 
    852 	if ( lobby.GetState() == idLobby::STATE_FAILED ) {
    853 		// If we get here, we were trying to connect to a lobby (from state State_Connect_And_Move_To_Party/State_Connect_And_Move_To_Game)
    854 		HandleConnectionFailed( lobby, false );
    855 		return true;
    856 	}
    857 
    858 	if ( lobby.GetState() != idLobby::STATE_IDLE ) {
    859 		return HandlePackets();	// Valid but busy
    860 	}
    861 
    862 	assert( !GetPartyLobby().waitForPartyOk );
    863 
    864 	//
    865 	// Past this point, we've connected to the lobby
    866 	//
    867 
    868 	// If we are connecting to a game lobby, see if we need to keep waiting as either a host or peer while we're confirming all party members made it
    869 	if ( lobby.lobbyType == idLobby::TYPE_GAME ) {
    870 		if ( GetPartyLobby().IsHost() ) {
    871 			// As a host, wait until all party members make it
    872 			assert( !GetGameLobby().waitForPartyOk );
    873 
    874 			const int timeoutMs = session->GetTitleStorageInt( "net_connectTimeoutInSeconds", net_connectTimeoutInSeconds.GetInteger() ) * 1000;
    875 
    876 			if ( timeoutMs != 0 && Sys_Milliseconds() - lobby.helloStartTime > timeoutMs ) {
    877 				// Took too long, move to next result, or create a game instead
    878 				HandleConnectionFailed( lobby, false );
    879 				return true;
    880 			}
    881 
    882 			int numUsersIn = 0;
    883 
    884 			for ( int i = 0; i < GetPartyLobby().GetNumLobbyUsers(); i++ ) {
    885 
    886 				if ( net_testPartyMemberConnectFail.GetInteger() == i ) {
    887 					continue;
    888 				}
    889 
    890 				bool foundUser = false;
    891 
    892 				lobbyUser_t * partyUser = GetPartyLobby().GetLobbyUser( i );
    893 
    894 				for ( int j = 0; j < GetGameLobby().GetNumLobbyUsers(); j++ ) {
    895 					lobbyUser_t * gameUser = GetGameLobby().GetLobbyUser( j );
    896 
    897 					if ( GetGameLobby().IsSessionUserLocal( gameUser ) || gameUser->address.Compare( partyUser->address, true ) ) {
    898 						numUsersIn++;
    899 						foundUser = true;
    900 						break;
    901 					}
    902 				}
    903 	
    904 				assert( !GetPartyLobby().IsSessionUserIndexLocal( i ) || foundUser );
    905 			}
    906 
    907 			if ( numUsersIn != GetPartyLobby().GetNumLobbyUsers() ) {
    908 				return HandlePackets();		// All users not in, keep waiting until all user make it, or we time out
    909 			} 
    910 		
    911 			NET_VERBOSE_PRINT( "NET: All party members made it into the game lobby.\n" );
    912 			
    913 			// Let all the party members know everyone made it, and it's ok to stay at this server
    914 			for ( int i = 0; i < GetPartyLobby().peers.Num(); i++ ) {
    915 				if ( GetPartyLobby().peers[ i ].IsConnected() ) {
    916 					GetPartyLobby().QueueReliableMessage( i, idLobby::RELIABLE_PARTY_CONNECT_OK );
    917 				}
    918 			}
    919 		} else {
    920 			if ( !verify ( lobby.host != -1 ) ) {
    921 				MoveToMainMenu();
    922 				connectType = CONNECT_NONE;
    923 				return false;
    924 			}
    925 
    926 			// As a peer, wait for server to tell us everyone made it
    927 			if ( GetGameLobby().waitForPartyOk ) {
    928 				const int timeoutMs = session->GetTitleStorageInt( "net_connectTimeoutInSeconds", net_connectTimeoutInSeconds.GetInteger() ) * 1000;
    929 
    930 				if ( timeoutMs != 0 && Sys_Milliseconds() - lobby.helloStartTime > timeoutMs ) {
    931 					GetGameLobby().waitForPartyOk = false;		// Just connect to this game lobby if we haven't heard from the party host for the entire timeout duration
    932 				}
    933 			}
    934 
    935 			if ( GetGameLobby().waitForPartyOk ) {
    936 				return HandlePackets();			// Waiting on party host to tell us everyone made it
    937 			}
    938 		}
    939 	}
    940 
    941 	// Success
    942 	switch ( lobby.lobbyType ) {
    943 		case idLobby::TYPE_PARTY:		
    944 			SetState( STATE_PARTY_LOBBY_PEER );
    945 			break;
    946 		case idLobby::TYPE_GAME:		
    947 			SetState( STATE_GAME_LOBBY_PEER );
    948 			break;
    949 		case idLobby::TYPE_GAME_STATE:	
    950 			waitingOnGameStateMembersToJoinTime = Sys_Milliseconds();
    951 			// As a host of the game lobby, it's our duty to notify our members to also join this game state lobby
    952 			GetGameLobby().SendMembersToLobby( GetGameStateLobby(), false );
    953 			SetState( STATE_GAME_STATE_LOBBY_PEER );
    954 			break;
    955 	}
    956 
    957 	connectType = CONNECT_NONE;
    958 
    959 	return false;
    960 }
    961 
    962 /*
    963 ========================
    964 idSessionLocal::State_Create_And_Move_To_Party_Lobby
    965 ========================
    966 */
    967 bool idSessionLocal::State_Create_And_Move_To_Party_Lobby() {
    968 	if ( WaitOnLobbyCreate( GetPartyLobby() ) ) {
    969 
    970 		if ( GetPartyLobby().parms.matchFlags & MATCH_PARTY_INVITE_PLACEHOLDER ) {
    971 			// If this party lobby was for a placeholder, continue on with either finding or creating a game lobby
    972 			if ( connectType == CONNECT_FIND_OR_CREATE ) {
    973 				FindOrCreateMatch( GetPartyLobby().parms );
    974 				return true;
    975 			} else if ( connectType == CONNECT_NONE ) {
    976 				CreateMatch( GetPartyLobby().parms );
    977 				return true;
    978 			}
    979 		}
    980 
    981 		// Success
    982 		SetState( STATE_PARTY_LOBBY_HOST );
    983 
    984 		return true;
    985 	}
    986 
    987 	return HandlePackets();		// Valid but busy
    988 }
    989 
    990 /*
    991 ========================
    992 idSessionLocal::State_Create_And_Move_To_Game_Lobby
    993 ========================
    994 */
    995 bool idSessionLocal::State_Create_And_Move_To_Game_Lobby() {
    996 
    997 	if ( WaitOnLobbyCreate( GetGameLobby() ) ) {
    998 		// Success
    999 		SetState( STATE_GAME_LOBBY_HOST );
   1000 
   1001 		// Now that we've created our game lobby, send our own party users to it
   1002 		// NOTE - We pass in false to wait on party members since we are the host, and we know they can connect to us
   1003 		GetPartyLobby().SendMembersToLobby( GetGameLobby(), false );
   1004 		return true;		
   1005 	}
   1006 
   1007 	return false;
   1008 }
   1009 
   1010 /*
   1011 ========================
   1012 idSessionLocal::State_Create_And_Move_To_Game_State_Lobby
   1013 ========================
   1014 */
   1015 bool idSessionLocal::State_Create_And_Move_To_Game_State_Lobby() {
   1016 
   1017 	if ( WaitOnLobbyCreate( GetGameStateLobby() ) ) {
   1018 		// Success
   1019 		SetState( STATE_GAME_STATE_LOBBY_HOST );
   1020 
   1021 		// Now that we've created our game state lobby, send our own game users to it
   1022 		// NOTE - We pass in false to wait on party members since we are the host, and we know they can connect to us
   1023 		GetGameLobby().SendMembersToLobby( GetGameStateLobby(), false );
   1024 
   1025 		// If we are the host of a game lobby, we know we are not using dedicated servers, so we want to start the match immediately
   1026 		// as soon as we detect all users have connected.
   1027 		if ( GetGameLobby().IsHost() ) {
   1028 			waitingOnGameStateMembersToJoinTime = Sys_Milliseconds();
   1029 		}
   1030 
   1031 		return true;		
   1032 	}
   1033 
   1034 	return false;
   1035 }
   1036 
   1037 /*
   1038 ========================
   1039 idSessionLocal::State_Find_Or_Create_Match
   1040 ========================
   1041 */
   1042 bool idSessionLocal::State_Find_Or_Create_Match() {
   1043 	assert( connectType == CONNECT_FIND_OR_CREATE );
   1044 
   1045 	if ( GetGameLobby().GetState() == idLobby::STATE_FAILED ) {
   1046 		// Failed to find any games.  Create one instead (we're assuming this always gets called from FindOrCreateMatch
   1047 		CreateMatch( GetGameLobby().parms );
   1048 		return true;
   1049 	}
   1050 
   1051 	if ( DetectDisconnectFromService( true ) ) {
   1052 		return false;
   1053 	}
   1054 
   1055 	if ( GetGameLobby().GetState() != idLobby::STATE_IDLE ) {
   1056 		return HandlePackets();		// Valid but busy
   1057 	}
   1058 
   1059 	// Done searching, connect to the first search result
   1060 	if ( !GetGameLobby().ConnectToNextSearchResult() ) {
   1061 		// Failed to find any games.  Create one instead (we're assuming this always gets called from FindOrCreateMatch
   1062 		CreateMatch( GetGameLobby().parms );
   1063 		return true;
   1064 	}
   1065 
   1066 	SetState( STATE_CONNECT_AND_MOVE_TO_GAME );
   1067 
   1068 	return true;
   1069 }
   1070 
   1071 /*
   1072 ========================
   1073 idSessionLocal::State_Connect_And_Move_To_Party
   1074 ========================
   1075 */
   1076 bool idSessionLocal::State_Connect_And_Move_To_Party() {
   1077 	return HandleConnectAndMoveToLobby( GetPartyLobby() );	
   1078 }
   1079 
   1080 /*
   1081 ========================
   1082 idSessionLocal::State_Connect_And_Move_To_Game
   1083 ========================
   1084 */
   1085 bool idSessionLocal::State_Connect_And_Move_To_Game() {
   1086 	return HandleConnectAndMoveToLobby( GetGameLobby() );	
   1087 }
   1088 
   1089 /*
   1090 ========================
   1091 idSessionLocal::State_Connect_And_Move_To_Game_State
   1092 ========================
   1093 */
   1094 bool idSessionLocal::State_Connect_And_Move_To_Game_State() {
   1095 	return HandleConnectAndMoveToLobby( GetGameStateLobby() );	
   1096 }
   1097 
   1098 /*
   1099 ========================
   1100 idSessionLocal::State_InGame
   1101 ========================
   1102 */
   1103 bool idSessionLocal::State_InGame() {
   1104 	return HandlePackets();
   1105 }
   1106 
   1107 /*
   1108 ========================
   1109 idSessionLocal::State_Loading
   1110 ========================
   1111 */
   1112 bool idSessionLocal::State_Loading() {
   1113 
   1114 	HandlePackets();
   1115 
   1116 	if ( !GetActingGameStateLobby().loaded ) {
   1117 		return false;
   1118 	}
   1119 
   1120 	SetVoiceGroupsToTeams();
   1121 
   1122 	if ( GetActingGameStateLobby().IsHost() ) {
   1123 		bool everyoneLoaded = true;
   1124 		for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) {
   1125 			idLobby::peer_t & peer = GetActingGameStateLobby().peers[p];
   1126 
   1127 			if ( !peer.IsConnected() ) {
   1128 				continue;		// We don't care about peers that aren't connected as a game session
   1129 			}
   1130 
   1131 			if ( !peer.loaded ) {
   1132 				everyoneLoaded = false;
   1133 				continue;		// Don't waste time sending resources to a peer who hasn't loaded the map yet
   1134 			}
   1135 
   1136 			if ( GetActingGameStateLobby().SendResources( p ) ) {
   1137 				everyoneLoaded = false;
   1138 
   1139 				// if client is taking a LONG time to load up - give them the boot: they're just holding up the lunch line. Useful for loose assets playtesting.
   1140 				int time = Sys_Milliseconds();
   1141 				int maxLoadTime = net_maxLoadResourcesTimeInSeconds.GetInteger();
   1142 				if ( maxLoadTime > 0 && peer.startResourceLoadTime + SEC2MS( maxLoadTime ) < time ) {
   1143 					NET_VERBOSERESOURCE_PRINT( "NET: dropping client %i - %s because they took too long to load resources.\n Check 'net_maxLoadResourcesTimeInSeconds' to adjust the time allowed.\n", p, GetPeerName( p ) );
   1144 					GetActingGameStateLobby().DisconnectPeerFromSession( p );
   1145 					continue;
   1146 				}
   1147 			}
   1148 		}
   1149 		if ( !everyoneLoaded ) {
   1150 			return false;
   1151 		}
   1152 	} else {
   1153 		// not sure how we got there, but we won't be receiving anything that could get us out of this state anymore
   1154 		// possible step towards fixing the join stalling/disconnect problems
   1155 		if ( GetActingGameStateLobby().peers.Num() == 0 ) {
   1156 			NET_VERBOSE_PRINT( "NET: no peers in idSessionLocal::State_Loading - giving up\n" );
   1157 			MoveToMainMenu();
   1158 		}
   1159 		// need at least a peer with a real connection
   1160 		bool haveOneGoodPeer = false;
   1161 		for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) {
   1162 			if ( GetActingGameStateLobby().peers[p].IsConnected() ) {
   1163 				haveOneGoodPeer = true;
   1164 				break;
   1165 			}
   1166 		}
   1167 		if ( !haveOneGoodPeer ) {
   1168 			NET_VERBOSE_PRINT( "NET: no good peers in idSessionLocal::State_Loading - giving up\n" );
   1169 			MoveToMainMenu();
   1170 		}
   1171 
   1172 		return false;
   1173 	}
   1174 
   1175 	GetActingGameStateLobby().ResetBandwidthStats();
   1176 
   1177 	// if we got here then we're the host and everyone indicated map load finished
   1178 	NET_VERBOSE_PRINT( "NET: (loading) Starting Game\n" );
   1179 	SetState( STATE_INGAME );		// NOTE - Only the host is in-game at this point, all peers will start becoming in-game when they receive their first full snap
   1180 	return true;
   1181 }
   1182 
   1183 /*
   1184 ========================
   1185 idSessionLocal::State_Busy
   1186 ========================
   1187 */
   1188 bool idSessionLocal::State_Busy() {
   1189 	idLobby * activeLobby = GetActivePlatformLobby();
   1190 	if ( activeLobby == NULL ) {
   1191 		idLib::Warning("No active session lobby when idSessionLocal::State_Busy called");
   1192 		return false;
   1193 	}
   1194 
   1195 	if ( activeLobby->bandwidthChallengeFinished ) {
   1196 		// Start loading
   1197 		NET_VERBOSE_PRINT( "NET: Bandwidth test finished - Start loading\n" );
   1198 		StartLoading();
   1199 	}
   1200 
   1201 	return HandlePackets();
   1202 }
   1203 
   1204 /*
   1205 ========================
   1206 idSessionLocal::VerifySnapshotInitialState
   1207 ========================
   1208 */
   1209 void idSessionLocal::VerifySnapshotInitialState() {
   1210 	// Verify that snapshot state is reset
   1211 	for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) {
   1212 		if ( !GetActingGameStateLobby().peers[p].IsConnected() ) {
   1213 			assert( GetActingGameStateLobby().peers[p].snapProc == NULL );
   1214 			continue;
   1215 		}
   1216 
   1217 		assert( GetActingGameStateLobby().peers[p].snapProc != NULL );
   1218 
   1219 		if ( !verify( GetActingGameStateLobby().peers[p].needToSubmitPendingSnap == false ) ) {
   1220 			idLib::Error( "Invalid needToSubmitPendingSnap state\n" );
   1221 		}
   1222 		if ( !verify( GetActingGameStateLobby().peers[p].snapProc->HasPendingSnap() == false ) ) {
   1223 			idLib::Error( "Invalid HasPendingSnap state\n" );
   1224 		}
   1225 		if ( !verify( GetActingGameStateLobby().peers[p].snapProc->GetSnapSequence() == idSnapshotProcessor::INITIAL_SNAP_SEQUENCE ) ) {
   1226 			idLib::Error( "Invalid INITIAL_SNAP_SEQUENCE state %d for peer %d \n", GetActingGameStateLobby().peers[p].snapProc->GetSnapSequence(), p  );
   1227 		}
   1228 		if ( !verify( GetActingGameStateLobby().peers[p].snapProc->GetBaseSequence() == -1 ) ) {
   1229 			idLib::Error( "Invalid GetBaseSequence state\n" );
   1230 		}
   1231 	}
   1232 }
   1233 
   1234 /*
   1235 ========================
   1236 idSessionLocal::State_Party_Lobby_Host
   1237 ========================
   1238 */
   1239 bool idSessionLocal::State_Party_Lobby_Host() {
   1240 	HandleVoiceRestrictionDialog();
   1241 	return HandlePackets();
   1242 }
   1243 
   1244 /*
   1245 ========================
   1246 idSessionLocal::State_Game_Lobby_Host
   1247 ========================
   1248 */
   1249 bool idSessionLocal::State_Game_Lobby_Host() {
   1250 	HandleVoiceRestrictionDialog();
   1251 	return HandlePackets();
   1252 }
   1253 
   1254 /*
   1255 ========================
   1256 idSessionLocal::State_Game_State_Lobby_Host
   1257 ========================
   1258 */
   1259 bool idSessionLocal::State_Game_State_Lobby_Host() {
   1260 	HandleVoiceRestrictionDialog();
   1261 
   1262 	if ( waitingOnGameStateMembersToLeaveTime != 0 ) {
   1263 
   1264 		const int MAX_LEAVE_WAIT_TIME_IN_SECONDS = 5;
   1265 
   1266 		const bool forceDisconnectMembers = ( Sys_Milliseconds() - waitingOnGameStateMembersToLeaveTime ) > MAX_LEAVE_WAIT_TIME_IN_SECONDS * 1000;
   1267 
   1268 		// Check to see if all peers have finally left
   1269 		if ( GetGameStateLobby().GetNumConnectedPeers() == 0 || forceDisconnectMembers ) {
   1270 
   1271 			//
   1272 			// All peers left, we can stop waiting
   1273 			//
   1274 
   1275 			waitingOnGameStateMembersToLeaveTime = 0;	
   1276 
   1277 			assert( !GetGameLobby().IsPeer() );
   1278 
   1279 			if ( GetGameLobby().IsHost() ) {		
   1280 				// If we aren't a dedicated game state host, then drop back to the game lobby as host
   1281 				GetGameStateLobby().Shutdown();
   1282 				SetState( STATE_GAME_LOBBY_HOST );
   1283 			} else {
   1284 				// A dedicated game state host will remain in State_Game_State_Lobby_Host mode while waiting for another set of users to join
   1285 				// DEDICATED_SERVER_FIXME: Notify master server we can server another game now
   1286 				GetGameStateLobby().DisconnectAllPeers();
   1287 			}
   1288 		}
   1289 	} else {
   1290 		// When all the players from the game lobby are in the game state lobby, StartLoading
   1291 		if ( GetGameLobby().IsHost() ) {
   1292 			if ( GetGameStateLobby().GetNumLobbyUsers() == GetGameLobby().GetNumLobbyUsers() ) {
   1293 				waitingOnGameStateMembersToJoinTime = 0;
   1294 				StartLoading();
   1295 			}
   1296 		} else {
   1297 			// The dedicated server host relies on the game host to say when all users are in
   1298 			if ( GetGameStateLobby().startLoadingFromHost ) {
   1299 				GetGameStateLobby().startLoadingFromHost = false;
   1300 				StartLoading();
   1301 			}
   1302 		}
   1303 	}
   1304 
   1305 	return HandlePackets();
   1306 }
   1307 
   1308 /*
   1309 ========================
   1310 idSessionLocal::State_Party_Lobby_Peer
   1311 ========================
   1312 */
   1313 bool idSessionLocal::State_Party_Lobby_Peer() {
   1314 	HandleVoiceRestrictionDialog();
   1315 	return HandlePackets();
   1316 }
   1317 
   1318 /*
   1319 ========================
   1320 idSessionLocal::State_Game_Lobby_Peer
   1321 ========================
   1322 */
   1323 bool idSessionLocal::State_Game_Lobby_Peer() {
   1324 	HandleVoiceRestrictionDialog();
   1325 	bool saving = false;
   1326 	idPlayerProfile * profile = GetProfileFromMasterLocalUser();
   1327 	if ( profile != NULL && ( profile->GetState() == idPlayerProfile::SAVING || profile->GetRequestedState() == idPlayerProfile::SAVE_REQUESTED ) ) {
   1328 		saving = true;
   1329 	}
   1330 
   1331 	if ( GetActingGameStateLobby().startLoadingFromHost && !saving ) { 
   1332 		common->Dialog().ClearDialog( GDM_HOST_RETURNED_TO_LOBBY );
   1333 		common->Dialog().ClearDialog( GDM_HOST_RETURNED_TO_LOBBY_STATS_DROPPED );
   1334 
   1335 		VerifySnapshotInitialState();
   1336 
   1337 		// Set loading flag back to false
   1338 		GetActingGameStateLobby().startLoadingFromHost = false;
   1339 
   1340 		// Set state to loading
   1341 		SetState( STATE_LOADING );
   1342 
   1343 		loadingID++;
   1344 
   1345 		return true;
   1346 	}
   1347 
   1348 	return HandlePackets();
   1349 }
   1350 
   1351 /*
   1352 ========================
   1353 idSessionLocal::State_Game_State_Lobby_Peer
   1354 ========================
   1355 */
   1356 bool idSessionLocal::State_Game_State_Lobby_Peer() {
   1357 	// We are in charge of telling the dedicated host that all our members are in
   1358 	if ( GetGameLobby().IsHost() && waitingOnGameStateMembersToJoinTime != 0 ) {		
   1359 		int foundMembers = 0;
   1360 
   1361 		for ( int i = 0; i < GetGameLobby().GetNumLobbyUsers(); i++ ) {
   1362 			if ( GetGameStateLobby().GetLobbyUserByID( GetGameLobby().GetLobbyUser( i )->lobbyUserID, true ) != NULL ) {
   1363 				foundMembers++;
   1364 			}
   1365 		}
   1366 
   1367 		// Give all of our game members 10 seconds to join, otherwise start without them
   1368 		const int MAX_JOIN_WAIT_TIME_IN_SECONDS = 10;
   1369 
   1370 		const bool forceStart = ( Sys_Milliseconds() - waitingOnGameStateMembersToJoinTime ) > MAX_JOIN_WAIT_TIME_IN_SECONDS * 1000;
   1371 
   1372 		if ( foundMembers == GetGameLobby().GetNumLobbyUsers() || forceStart ) {
   1373 			byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ];
   1374 
   1375 			idBitMsg msg( buffer, sizeof( buffer ) );
   1376 
   1377 			// Write match paramaters to the game state host, and tell him to start
   1378 			GetGameLobby().parms.Write( msg );
   1379 
   1380 			// Tell the game state lobby host we are ready
   1381 			GetGameStateLobby().QueueReliableMessage( GetGameStateLobby().host, idLobby::RELIABLE_START_MATCH_GAME_LOBBY_HOST, msg.GetReadData(), msg.GetSize() );
   1382 
   1383 			waitingOnGameStateMembersToJoinTime = 0;
   1384 		}
   1385 	}
   1386 
   1387 	return State_Game_Lobby_Peer();
   1388 }
   1389 
   1390 /*
   1391 ========================
   1392 idSessionLocal::~idSession
   1393 ========================
   1394 */
   1395 idSession::~idSession() {
   1396 	delete signInManager;
   1397 	signInManager = NULL;
   1398 	delete saveGameManager;
   1399 	saveGameManager = NULL;
   1400 	delete dedicatedServerSearch;
   1401 	dedicatedServerSearch = NULL;
   1402 }
   1403 
   1404 idCVar net_verbose( "net_verbose", "0", CVAR_BOOL, "Print a bunch of message about the network session" );
   1405 idCVar net_verboseResource( "net_verboseResource", "0", CVAR_BOOL, "Prints a bunch of message about network resources" );
   1406 idCVar net_verboseReliable( "net_verboseReliable", "0", CVAR_BOOL, "Prints the more spammy messages about reliable network msgs" );
   1407 idCVar si_splitscreen( "si_splitscreen", "0", CVAR_INTEGER, "force splitscreen" );
   1408 
   1409 idCVar net_forceLatency( "net_forceLatency", "0", CVAR_INTEGER, "Simulate network latency (milliseconds round trip time - applied equally on the receive and on the send)" );
   1410 idCVar net_forceDrop( "net_forceDrop", "0", CVAR_INTEGER, "Percentage chance of simulated network packet loss" );
   1411 idCVar net_forceUpstream( "net_forceUpstream", "0", CVAR_FLOAT, "Force a maximum upstream in kB/s (256kbps <-> 32kB/s)" ); // I would much rather deal in kbps but most of the code is written in bytes ..
   1412 idCVar net_forceUpstreamQueue( "net_forceUpstreamQueue", "64", CVAR_INTEGER, "How much data is queued when enforcing upstream (in kB)" );
   1413 idCVar net_verboseSimulatedTraffic( "net_verboseSimulatedTraffic", "0", CVAR_BOOL, "Print some stats about simulated traffic (net_force* cvars)" );
   1414 
   1415 /*
   1416 ========================
   1417 idSessionLocal::Initialize
   1418 ========================
   1419 */
   1420 void idSessionLocal::Initialize() {
   1421 }
   1422 
   1423 /*
   1424 ========================
   1425 idSessionLocal::Shutdown
   1426 ========================
   1427 */
   1428 void idSessionLocal::Shutdown() {
   1429 }
   1430 
   1431 /*
   1432 ========================
   1433 idSession interface semi-common between platforms (#ifdef's in sys_session_local.cpp)
   1434 ========================
   1435 */
   1436 
   1437 idCVar com_deviceZeroOverride( "com_deviceZeroOverride", "-1", CVAR_INTEGER, "change input routing for device 0 to poll a different device" );
   1438 idCVar mp_bot_input_override( "mp_bot_input_override", "-1", CVAR_INTEGER, "Override local input routing for bot control" );
   1439 
   1440 /*
   1441 ========================
   1442 idSessionLocal::GetInputRouting
   1443 This function sets up inputRouting to be a mapping from inputDevice index to session user index.
   1444 ========================
   1445 */
   1446 int idSessionLocal::GetInputRouting( int inputRouting[ MAX_INPUT_DEVICES ] ) {
   1447 
   1448 	int numLocalUsers = 0;
   1449 	for ( int i = 0; i < MAX_INPUT_DEVICES; i++ ) {
   1450 		inputRouting[i] = -1;
   1451 	}
   1452 	
   1453 	for ( int i = 0; i < GetActingGameStateLobby().GetNumLobbyUsers(); i++ ) {
   1454 		if ( GetActingGameStateLobby().IsSessionUserIndexLocal( i ) ) {
   1455 
   1456 			// Find the local user that this session user maps to
   1457 			const idLocalUser * localUser = GetActingGameStateLobby().GetLocalUserFromLobbyUserIndex( i );
   1458 			
   1459 			if ( localUser != NULL ) {
   1460 				int localDevice = localUser->GetInputDevice();
   1461 				if ( localDevice == 0 && com_deviceZeroOverride.GetInteger() > 0 ) {
   1462 					localDevice = com_deviceZeroOverride.GetInteger();
   1463 				}
   1464 				assert( localDevice < MAX_INPUT_DEVICES );
   1465 				// Route the input device that this local user is mapped to
   1466 				assert( inputRouting[localDevice] == -1 );	// Make sure to only initialize each entry once
   1467 				inputRouting[localDevice] = i;
   1468 
   1469 				if ( mp_bot_input_override.GetInteger() >= 0 ) {
   1470 					inputRouting[localDevice] = mp_bot_input_override.GetInteger();
   1471 				}
   1472 
   1473 				numLocalUsers++;
   1474 			}
   1475 		}
   1476 	}
   1477 
   1478 	// For testing swapping controllers
   1479 	if ( si_splitscreen.GetInteger() == 2 && numLocalUsers == 2 ) {
   1480 		SwapValues( inputRouting[0], inputRouting[1] );
   1481 	}
   1482 
   1483 	return numLocalUsers;
   1484 }
   1485 
   1486 /*
   1487 ========================
   1488 idSessionLocal::EndMatch
   1489 EndMatch is meant for the host to cleanly end a match and return to the lobby page
   1490 ========================
   1491 */
   1492 void idSessionLocal::EndMatch( bool premature /*=false*/ ) {
   1493 	if ( verify( GetActingGameStateLobby().IsHost() ) ) {
   1494 		// Host quits back to game lobby, and will notify peers internally to do the same
   1495 		EndMatchInternal( premature );
   1496 	}
   1497 }
   1498 
   1499 /*
   1500 ========================
   1501 idSessionLocal::EndMatch
   1502 this is for when the game is over before we go back to lobby. Need this incase the host leaves during this time
   1503 ========================
   1504 */
   1505 void idSessionLocal::MatchFinished( ) {
   1506 	if ( verify( GetActingGameStateLobby().IsHost() ) ) {
   1507 		// host is putting up end game stats make sure other peers know and clear migration data
   1508 		MatchFinishedInternal();
   1509 	}
   1510 }
   1511 
   1512 /*
   1513 ========================
   1514 idSessionLocal::QuitMatch
   1515 QuitMatch is considered a premature ending of a match, and does the right thing depending on whether the host or peer is quitting
   1516 ========================
   1517 */
   1518 void idSessionLocal::QuitMatch() {
   1519 	if ( GetActingGameStateLobby().IsHost() && !MatchTypeIsRanked( GetActingGameStateLobby().parms.matchFlags ) ) {
   1520 		EndMatch( true );		// When host quits private match, takes members back to game lobby
   1521 	} else {
   1522 		// Quitting a public match (or not being a host) before it ends takes you to an empty party lobby
   1523 		CreatePartyLobby( GetActingGameStateLobby().parms );
   1524 	}
   1525 }
   1526 
   1527 /*
   1528 ========================
   1529 idSessionLocal::QuitMatchToTitle
   1530 QuitMatchToTitle will forcefully quit the match and return to the title screen.
   1531 ========================
   1532 */
   1533 void idSessionLocal::QuitMatchToTitle() {
   1534 	MoveToMainMenu();
   1535 }
   1536 
   1537 /*
   1538 ========================
   1539 idSessionLocal::ClearMigrationState
   1540 ========================
   1541 */
   1542 void idSessionLocal::ClearMigrationState() {
   1543 	// We are ending the match without migration, so clear that state
   1544 	GetPartyLobby().ResetAllMigrationState();
   1545 	GetGameLobby().ResetAllMigrationState();
   1546 }
   1547 
   1548 /*
   1549 ========================
   1550 idSessionLocal::EndMatchInternal
   1551 ========================
   1552 */
   1553 void idSessionLocal::EndMatchInternal( bool premature/*=false*/ ) {
   1554 	assert( GetGameStateLobby().IsLobbyActive() == net_useGameStateLobby.GetBool() );
   1555 
   1556 	ClearVoiceGroups();
   1557 
   1558 	for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) {
   1559 		// If we are the host, increment the session ID.  The client will use a rolling check to catch it
   1560 		if ( GetActingGameStateLobby().IsHost() ) {
   1561 			if ( GetActingGameStateLobby().peers[p].IsConnected() ) {
   1562 				if ( GetActingGameStateLobby().peers[p].packetProc != NULL ) {
   1563 					GetActingGameStateLobby().peers[p].packetProc->VerifyEmptyReliableQueue( idLobby::RELIABLE_GAME_DATA, idLobby::RELIABLE_DUMMY_MSG );
   1564 				}
   1565 				GetActingGameStateLobby().peers[p].sessionID = GetActingGameStateLobby().IncrementSessionID( GetActingGameStateLobby().peers[p].sessionID );
   1566 			}
   1567 		}
   1568 		GetActingGameStateLobby().peers[p].ResetMatchData();
   1569 	}
   1570 
   1571 	GetActingGameStateLobby().snapDeltaAckQueue.Clear();
   1572 
   1573 	GetActingGameStateLobby().loaded	= false;
   1574 
   1575 	gameLobbyWasCoalesced	= false;		// Reset this back to false.  We use this so the lobby code doesn't randomly choose a map when we coalesce
   1576 	numFullSnapsReceived	= 0;
   1577 
   1578 	ClearMigrationState();
   1579 
   1580 	if ( GetActingGameStateLobby().IsLobbyActive() && ( GetActingGameStateLobby().GetMatchParms().matchFlags & MATCH_REQUIRE_PARTY_LOBBY ) ) {
   1581 		// All peers need to remove disconnected users to stay in sync
   1582 		GetActingGameStateLobby().CompactDisconnectedUsers();
   1583 
   1584 		// Go back to the game lobby
   1585 		if ( GetActingGameStateLobby().IsHost() ) {
   1586 			// We want the game state host to go back to STATE_GAME_STATE_LOBBY_HOST, so he can wait on all his game state peers to leave
   1587 			SetState( GetGameStateLobby().IsHost() ? STATE_GAME_STATE_LOBBY_HOST : STATE_GAME_LOBBY_HOST );		// We want the dedicated host to go back to STATE_GAME_STATE_LOBBY_HOST
   1588 		} else {
   1589 			SetState( STATE_GAME_LOBBY_PEER );
   1590 		}
   1591 	} else {
   1592 		SetState( STATE_IDLE );
   1593 	}
   1594 
   1595 	if ( GetActingGameStateLobby().IsHost() ) {
   1596 		// Send a reliable msg to all peers to also "EndMatch"
   1597 		for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) {
   1598 			GetActingGameStateLobby().QueueReliableMessage( p, premature ? idLobby::RELIABLE_ENDMATCH_PREMATURE : idLobby::RELIABLE_ENDMATCH );
   1599 		}
   1600 	} else if ( premature ) {
   1601 		// Notify client that host left early and thats why we are back in the lobby
   1602 		const bool stats = MatchTypeHasStats( GetActingGameStateLobby().GetMatchParms().matchFlags ) && ( GetFlushedStats() == false );
   1603 		common->Dialog().AddDialog( stats ? GDM_HOST_RETURNED_TO_LOBBY_STATS_DROPPED : GDM_HOST_RETURNED_TO_LOBBY, DIALOG_ACCEPT, NULL, NULL, false, __FUNCTION__, __LINE__, true );
   1604 	}
   1605 
   1606 	if ( GetGameStateLobby().IsLobbyActive() ) {
   1607 		if ( GetGameStateLobby().IsHost() ) {
   1608 			// As a game state host, keep the lobby around, so we can make sure we know when everyone leaves (which means they got the reliable msg to EndMatch)
   1609 			waitingOnGameStateMembersToLeaveTime = Sys_Milliseconds();
   1610 		} else if ( GetGameStateLobby().IsPeer() ) {
   1611 			// Game state lobby peers should disconnect now
   1612 			GetGameStateLobby().Shutdown();
   1613 		}
   1614 	}
   1615 }
   1616 
   1617 /*
   1618 ========================
   1619 idSessionLocal::MatchFinishedInternal
   1620 ========================
   1621 */
   1622 void idSessionLocal::MatchFinishedInternal() {
   1623 	ClearMigrationState();
   1624 
   1625 	if ( GetActingGameStateLobby().IsHost() ) {
   1626 		// Send a reliable msg to all peers to also "EndMatch"
   1627 		for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) {
   1628 			GetActingGameStateLobby().QueueReliableMessage( p, idLobby::RELIABLE_MATCHFINISHED );
   1629 		}
   1630 	}
   1631 }
   1632 
   1633 /*
   1634 ========================
   1635 idSessionLocal::EndMatchForMigration
   1636 ========================
   1637 */
   1638 void idSessionLocal::EndMatchForMigration() {
   1639 	ClearVoiceGroups();	
   1640 }
   1641 
   1642 /*
   1643 ========================
   1644 idSessionLocal::ShouldHavePartyLobby
   1645 ========================
   1646 */
   1647 bool idSessionLocal::ShouldHavePartyLobby() {
   1648 	if ( GetActivePlatformLobby() == NULL ) {
   1649 		return false;
   1650 	}
   1651 
   1652 	idMatchParameters & parms = GetActivePlatformLobby()->parms;
   1653 
   1654 	int flags = parms.matchFlags;
   1655 
   1656 	// Don't we always have a party lobby if we're online? At least in Doom 3?
   1657 	return MatchTypeIsOnline( flags ) && ( ( flags & MATCH_PARTY_INVITE_PLACEHOLDER ) == 0 );
   1658 }
   1659 
   1660 /*
   1661 ========================
   1662 idSessionLocal::ValidateLobbies
   1663 Determines if any of the session instances need to become the host
   1664 ========================
   1665 */
   1666 void idSessionLocal::ValidateLobbies() {
   1667 	if ( localState == STATE_PRESS_START || localState == STATE_IDLE ) {
   1668 		// At press start or main menu, don't do anything
   1669 		return;
   1670 	}
   1671 
   1672 	if ( GetActivePlatformLobby() == NULL ) {
   1673 		// If we're in between lobbies, don't do anything yet (the state transitioning code will handle error cases)
   1674 		return;
   1675 	}
   1676 
   1677 	// Validate lobbies that should be alive and active
   1678 	if ( ShouldHavePartyLobby() && GetState() >= idSession::PARTY_LOBBY ) {
   1679 		ValidateLobby( GetPartyLobby() );
   1680 	}
   1681 	if ( GetState() >= idSession::GAME_LOBBY && !net_headlessServer.GetBool() ) {
   1682 		ValidateLobby( GetGameLobby() );
   1683 	}
   1684 }
   1685 
   1686 /*
   1687 ========================
   1688 idSessionLocal::ValidateLobby
   1689 ========================
   1690 */
   1691 void idSessionLocal::ValidateLobby( idLobby & lobby ) {
   1692 	if ( lobby.lobbyBackend == NULL || lobby.lobbyBackend->GetState() == idLobbyBackend::STATE_FAILED || lobby.GetState() == idLobby::STATE_FAILED ) {
   1693 		NET_VERBOSE_PRINT( "NET: ValidateLobby: FAILED (lobbyType = %i, state = %s)\n", lobby.lobbyType, stateToString[ localState ] );
   1694 		if ( lobby.failedReason == idLobby::FAILED_MIGRATION_CONNECT_FAILED || lobby.failedReason == idLobby::FAILED_CONNECT_FAILED ) {
   1695 			MoveToMainMenu();
   1696 			common->Dialog().AddDialog( GDM_INVALID_INVITE, DIALOG_ACCEPT, NULL, NULL, false );		// The game session no longer exists
   1697 		} else {
   1698 			// If the lobbyBackend goes bad under our feet for no known reason, assume we lost connection to the back end service
   1699 			MoveToMainMenu();
   1700 			common->Dialog().ClearDialogs( true );
   1701 			common->Dialog().AddDialog( GDM_CONNECTION_LOST, DIALOG_ACCEPT, NULL, NULL, false );		// Lost connection to XBox LIVE
   1702 		}
   1703 	}	
   1704 }
   1705 
   1706 /*
   1707 ========================
   1708 idSessionLocal::Pump
   1709 ========================
   1710 */
   1711 void idSessionLocal::Pump() {
   1712 	SCOPED_PROFILE_EVENT( "Session::Pump" );
   1713 
   1714 	static int lastPumpTime = -1;
   1715 
   1716 	const int time					= Sys_Milliseconds();
   1717 	const int elapsedPumpSeconds	= ( time - lastPumpTime ) / 1000;
   1718 
   1719 	if ( lastPumpTime != -1 && elapsedPumpSeconds > 2 ) {
   1720 		idLib::Warning( "idSessionLocal::Pump was not called for %i seconds", elapsedPumpSeconds );
   1721 	}
   1722 
   1723 	lastPumpTime = time;
   1724 
   1725 	if ( net_migrateHost.GetInteger() >= 0 ) {
   1726 		if ( net_migrateHost.GetInteger() <= 2 ) {
   1727 			if ( net_migrateHost.GetInteger() == 0 ) {
   1728 				GetPartyLobby().PickNewHost( true, true );				
   1729 			} else {
   1730 				GetGameLobby().PickNewHost( true, true );				
   1731 			}
   1732 		} else {
   1733 			GetPartyLobby().PickNewHost( true, true );				
   1734 			GetGameLobby().PickNewHost( true, true );				
   1735 		}
   1736 		net_migrateHost.SetInteger( -1 );
   1737 	}
   1738 
   1739 	PlatformPump();
   1740 
   1741 	if ( HasAchievementSystem() ) {
   1742 		GetAchievementSystem().Pump();
   1743 	}
   1744 
   1745 	// Send any voice packets if it's time
   1746 	SendVoiceAudio();
   1747 
   1748 	bool shouldContinue = true;
   1749 
   1750 	while ( shouldContinue ) {
   1751 		// Each iteration, validate the session instances
   1752 		ValidateLobbies();
   1753 
   1754 		// Pump state
   1755 		shouldContinue = HandleState();
   1756 
   1757 		// Pump lobbies
   1758 		PumpLobbies();
   1759 	} 
   1760 
   1761 	if ( GetPartyLobby().lobbyBackend != NULL ) {
   1762 		// Make sure game properties aren't set on the lobbyBackend if we aren't in a game lobby.
   1763 		// This is so we show up properly in search results in Play with Friends option
   1764 		GetPartyLobby().lobbyBackend->SetInGame( GetGameLobby().IsLobbyActive() );
   1765 
   1766 		// Temp location
   1767 		UpdateMasterUserHeadsetState();
   1768 	}
   1769 
   1770 	// Do some last minute checks, make sure everything about the current state and lobbyBackend state is valid, otherwise, take action
   1771 	ValidateLobbies();
   1772 
   1773 	GetActingGameStateLobby().UpdateSnaps();
   1774 
   1775 	idLobby * activeLobby = GetActivePlatformLobby();
   1776 
   1777 	// Pump pings for the active lobby
   1778 	if ( activeLobby != NULL ) {
   1779 		activeLobby->PumpPings();
   1780 	}
   1781 
   1782 	// Pump packet processing for all lobbies
   1783 	GetPartyLobby().PumpPackets();
   1784 	GetGameLobby().PumpPackets();
   1785 	GetGameStateLobby().PumpPackets();
   1786 
   1787 	int currentTime = Sys_Milliseconds();
   1788 
   1789 	const int SHOW_MIGRATING_INFO_IN_SECONDS = 3;	// Show for at least this long once we start showing it
   1790 
   1791 	if ( ShouldShowMigratingDialog() ) {
   1792 		showMigratingInfoStartTime = currentTime;
   1793 	} else if ( showMigratingInfoStartTime > 0 && ( ( currentTime - showMigratingInfoStartTime ) > SHOW_MIGRATING_INFO_IN_SECONDS * 1000 ) ) {
   1794 		showMigratingInfoStartTime = 0;
   1795 	}
   1796 
   1797 	bool isShowingMigrate = common->Dialog().HasDialogMsg( GDM_MIGRATING, NULL );
   1798 
   1799 	if ( showMigratingInfoStartTime != 0 ) {
   1800 		if ( !isShowingMigrate ) {
   1801 			common->Dialog().AddDialog( GDM_MIGRATING, DIALOG_WAIT, NULL, NULL, false, "", 0, false, false, true );
   1802 		}
   1803 	} else if ( isShowingMigrate ) {
   1804 		common->Dialog().ClearDialog( GDM_MIGRATING );
   1805 	}
   1806 
   1807 	// Update possible pending invite
   1808 	UpdatePendingInvite();
   1809 
   1810 	// Check to see if we should coalesce the lobby
   1811 	if ( nextGameCoalesceTime != 0 ) {
   1812 
   1813 		if ( GetGameLobby().IsLobbyActive() &&
   1814 			 GetGameLobby().IsHost() &&
   1815 			 GetState() == idSession::GAME_LOBBY &&
   1816 			 GetPartyLobby().GetNumLobbyUsers() <= 1 &&
   1817 			 GetGameLobby().GetNumLobbyUsers() == 1 &&
   1818 			 MatchTypeIsRanked( GetGameLobby().parms.matchFlags ) &&
   1819 			 Sys_Milliseconds() > nextGameCoalesceTime ) {
   1820 			
   1821 			// If the player doesn't care about the mode or map,
   1822 			// make sure the search is broadened.
   1823 			idMatchParameters newGameParms = GetGameLobby().parms;
   1824 			newGameParms.gameMap = GAME_MAP_RANDOM;
   1825 
   1826 			// Assume that if the party lobby's mode is random,
   1827 			// the player chose "Quick Match" and doesn't care about the mode.
   1828 			// If the player chose "Find Match" and a specific mode,
   1829 			// the party lobby mode will be set to non-random.
   1830 			if ( GetPartyLobby().parms.gameMode == GAME_MODE_RANDOM ) {
   1831 				newGameParms.gameMode = GAME_MODE_RANDOM;
   1832 			}
   1833 
   1834 			FindOrCreateMatch( newGameParms );
   1835 
   1836 			gameLobbyWasCoalesced	= true;		// Remember that this round was coalesced.  We so this so main menu doesn't randomize the map, which looks odd
   1837 			nextGameCoalesceTime	= 0;
   1838 		}
   1839 	}
   1840 }
   1841 
   1842 /*
   1843 ========================
   1844 idSessionLocal::ProcessSnapAckQueue
   1845 ========================
   1846 */
   1847 void idSessionLocal::ProcessSnapAckQueue() {
   1848 	if ( GetActingGameStateLobby().IsLobbyActive() ) {
   1849 		GetActingGameStateLobby().ProcessSnapAckQueue();
   1850 	}
   1851 }
   1852 
   1853 /*
   1854 ========================
   1855 idSessionLocal::UpdatePendingInvite
   1856 ========================
   1857 */
   1858 void idSessionLocal::UpdatePendingInvite() {
   1859 	if ( pendingInviteMode == PENDING_INVITE_NONE ) {
   1860 		return;		// No pending invite
   1861 	}
   1862 
   1863 	idLocalUser * masterLocalUser = signInManager->GetMasterLocalUser();
   1864 
   1865 	if ( masterLocalUser == NULL && signInManager->IsDeviceBeingRegistered( pendingInviteDevice ) ) {
   1866 		idLib::Printf( "masterLocalUser == NULL\n" );
   1867 		return;		// Waiting on master to sign in to continue with invite
   1868 	}
   1869 
   1870 	const bool wasFromInvite = pendingInviteMode == PENDING_INVITE_WAITING;	// Remember if this was a real invite, or a self invitation (matters when lobby is invite only)
   1871 
   1872 	// At this point, the invitee should be ready
   1873 	pendingInviteMode = PENDING_INVITE_NONE;
   1874 
   1875 	if ( masterLocalUser == NULL || masterLocalUser->GetInputDevice() != pendingInviteDevice || !masterLocalUser->IsOnline() ) {
   1876 		idLib::Printf( "ignoring invite - master local user is not setup properly\n" );
   1877 		return; // If there is no master, if the invitee is not online, or different than the current master, then ignore invite
   1878 	}
   1879 
   1880 	// Clear any current dialogs, as we're going into a state which will be unstable for any current dialogs.
   1881 	// Do we want to throw an assert if a dialog is currently up?
   1882 	common->Dialog().ClearDialogs( true );
   1883 
   1884 	// Everything looks good, let's join the party
   1885 	ConnectAndMoveToLobby( GetPartyLobby(), pendingInviteConnectInfo, wasFromInvite );
   1886 }
   1887 
   1888 /*
   1889 ========================
   1890 idSessionLocal::HandleState
   1891 ========================
   1892 */
   1893 bool idSessionLocal::HandleState() {	
   1894 	// Handle individual lobby states
   1895 	GetPartyLobby().Pump();
   1896 	GetGameLobby().Pump();
   1897 	GetGameStateLobby().Pump();
   1898 
   1899 	// Let IsHost be authoritative on the qualification of peer/host state types
   1900 	if ( GetPartyLobby().IsHost() && localState == STATE_PARTY_LOBBY_PEER ) {
   1901 		SetState( STATE_PARTY_LOBBY_HOST );
   1902 	} else if ( GetPartyLobby().IsPeer() && localState == STATE_PARTY_LOBBY_HOST ) {
   1903 		SetState( STATE_PARTY_LOBBY_PEER );
   1904 	}
   1905 
   1906 	// Let IsHost be authoritative on the qualification of peer/host state types
   1907 	if ( GetGameLobby().IsHost() && localState == STATE_GAME_LOBBY_PEER ) {
   1908 		SetState( STATE_GAME_LOBBY_HOST );
   1909 	} else if ( GetGameLobby().IsPeer() && localState == STATE_GAME_LOBBY_HOST ) {
   1910 		SetState( STATE_GAME_LOBBY_PEER );
   1911 	}
   1912 
   1913 	switch ( localState ) {
   1914 		case STATE_PRESS_START:							return false;
   1915 		case STATE_IDLE:								HandlePackets(); return false;		// Call handle packets, since packets from old sessions could still be in flight, which need to be emptied
   1916 		case STATE_PARTY_LOBBY_HOST:					return State_Party_Lobby_Host();
   1917 		case STATE_PARTY_LOBBY_PEER:					return State_Party_Lobby_Peer();
   1918 		case STATE_GAME_LOBBY_HOST:						return State_Game_Lobby_Host();
   1919 		case STATE_GAME_LOBBY_PEER:						return State_Game_Lobby_Peer();
   1920 		case STATE_GAME_STATE_LOBBY_HOST:				return State_Game_State_Lobby_Host();
   1921 		case STATE_GAME_STATE_LOBBY_PEER:				return State_Game_State_Lobby_Peer();
   1922 		case STATE_LOADING:								return State_Loading();
   1923 		case STATE_INGAME:								return State_InGame();
   1924 		case STATE_CREATE_AND_MOVE_TO_PARTY_LOBBY:		return State_Create_And_Move_To_Party_Lobby();
   1925 		case STATE_CREATE_AND_MOVE_TO_GAME_LOBBY:		return State_Create_And_Move_To_Game_Lobby();
   1926 		case STATE_CREATE_AND_MOVE_TO_GAME_STATE_LOBBY:	return State_Create_And_Move_To_Game_State_Lobby();
   1927 		case STATE_FIND_OR_CREATE_MATCH:				return State_Find_Or_Create_Match();
   1928 		case STATE_CONNECT_AND_MOVE_TO_PARTY:			return State_Connect_And_Move_To_Party();
   1929 		case STATE_CONNECT_AND_MOVE_TO_GAME:			return State_Connect_And_Move_To_Game();
   1930 		case STATE_CONNECT_AND_MOVE_TO_GAME_STATE:		return State_Connect_And_Move_To_Game_State();
   1931 		case STATE_BUSY:								return State_Busy();
   1932 		default:
   1933 			idLib::Error( "HandleState:  Unknown state in idSessionLocal" );
   1934 	}
   1935 }
   1936 
   1937 /*
   1938 ========================
   1939 idSessionLocal::GetState
   1940 ========================
   1941 */
   1942 idSessionLocal::sessionState_t idSessionLocal::GetState() const {
   1943 	// Convert our internal state to one of the external states
   1944 	switch ( localState ) {
   1945 		case STATE_PRESS_START:							return PRESS_START;
   1946 		case STATE_IDLE:								return IDLE;
   1947 		case STATE_PARTY_LOBBY_HOST:					return PARTY_LOBBY;
   1948 		case STATE_PARTY_LOBBY_PEER:					return PARTY_LOBBY;
   1949 		case STATE_GAME_LOBBY_HOST:						return GAME_LOBBY;
   1950 		case STATE_GAME_LOBBY_PEER:						return GAME_LOBBY;
   1951 		case STATE_GAME_STATE_LOBBY_HOST:				return GAME_LOBBY;
   1952 		case STATE_GAME_STATE_LOBBY_PEER:				return GAME_LOBBY;
   1953 		case STATE_LOADING:								return LOADING;
   1954 		case STATE_INGAME:								return INGAME;
   1955 		case STATE_CREATE_AND_MOVE_TO_PARTY_LOBBY:		return CONNECTING;
   1956 		case STATE_CREATE_AND_MOVE_TO_GAME_LOBBY:		return CONNECTING;
   1957 		case STATE_CREATE_AND_MOVE_TO_GAME_STATE_LOBBY:	return CONNECTING;
   1958 		case STATE_FIND_OR_CREATE_MATCH:				return SEARCHING;
   1959 		case STATE_CONNECT_AND_MOVE_TO_PARTY:			return CONNECTING;
   1960 		case STATE_CONNECT_AND_MOVE_TO_GAME:			return CONNECTING;
   1961 		case STATE_CONNECT_AND_MOVE_TO_GAME_STATE:		return CONNECTING;
   1962 		case STATE_BUSY:								return BUSY;
   1963 		default: {
   1964 			idLib::Error( "GetState: Unknown state in idSessionLocal" );
   1965 		}	
   1966 	};
   1967 }
   1968 
   1969 const char * idSessionLocal::GetStateString() const {
   1970 	static const char * stateToString[] = {
   1971 		ASSERT_ENUM_STRING( STATE_PRESS_START, 0 ),
   1972 		ASSERT_ENUM_STRING( STATE_IDLE, 1 ),
   1973 		ASSERT_ENUM_STRING( STATE_PARTY_LOBBY_HOST, 2 ),
   1974 		ASSERT_ENUM_STRING( STATE_PARTY_LOBBY_PEER, 3 ),
   1975 		ASSERT_ENUM_STRING( STATE_GAME_LOBBY_HOST, 4 ),
   1976 		ASSERT_ENUM_STRING( STATE_GAME_LOBBY_PEER, 5 ),
   1977 		ASSERT_ENUM_STRING( STATE_GAME_STATE_LOBBY_HOST, 6 ),
   1978 		ASSERT_ENUM_STRING( STATE_GAME_STATE_LOBBY_PEER, 7 ),
   1979 		ASSERT_ENUM_STRING( STATE_CREATE_AND_MOVE_TO_PARTY_LOBBY, 8 ),
   1980 		ASSERT_ENUM_STRING( STATE_CREATE_AND_MOVE_TO_GAME_LOBBY, 9 ),
   1981 		ASSERT_ENUM_STRING( STATE_CREATE_AND_MOVE_TO_GAME_STATE_LOBBY, 10 ),
   1982 		ASSERT_ENUM_STRING( STATE_FIND_OR_CREATE_MATCH, 11 ),
   1983 		ASSERT_ENUM_STRING( STATE_CONNECT_AND_MOVE_TO_PARTY, 12 ),
   1984 		ASSERT_ENUM_STRING( STATE_CONNECT_AND_MOVE_TO_GAME, 13 ),
   1985 		ASSERT_ENUM_STRING( STATE_CONNECT_AND_MOVE_TO_GAME_STATE, 14 ),
   1986 		ASSERT_ENUM_STRING( STATE_BUSY, 15 ),
   1987 		ASSERT_ENUM_STRING( STATE_LOADING, 16 ),
   1988 		ASSERT_ENUM_STRING( STATE_INGAME, 17 )
   1989 	};
   1990 	return stateToString[ localState ];
   1991 }
   1992 
   1993 // idSession interface
   1994 
   1995 /*
   1996 ========================
   1997 idSessionLocal::LoadingFinished
   1998 
   1999 Only called by idCommonLocal::FinalizeMapChange
   2000 ========================
   2001 */
   2002 void idSessionLocal::LoadingFinished() {
   2003 	NET_VERBOSE_PRINT( "NET: Loading Finished\n" );
   2004 
   2005 	assert( GetState() == idSession::LOADING );
   2006 
   2007 	common->Dialog().ClearDialog( GDM_VOICE_RESTRICTED );
   2008 	GetActingGameStateLobby().loaded = true;
   2009 	
   2010 	if ( MatchTypeIsLocal( GetActingGameStateLobby().parms.matchFlags ) ) {
   2011 		SetState( STATE_INGAME );
   2012 	} else if ( !GetActingGameStateLobby().IsHost() ) {	// Tell game host we're done loading
   2013 		byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ];
   2014 		idBitMsg msg( buffer, sizeof( buffer ) );
   2015 		GetActingGameStateLobby().QueueReliableMessage( GetActingGameStateLobby().host, idLobby::RELIABLE_LOADING_DONE, msg.GetReadData(), msg.GetSize() );
   2016 	} else  {
   2017 		SetState( STATE_INGAME );
   2018 	}
   2019 
   2020 	SetFlushedStats( false );
   2021 }
   2022 
   2023 /*
   2024 ========================
   2025 idSessionLocal::SendUsercmds
   2026 ========================
   2027 */
   2028 void idSessionLocal::SendUsercmds( idBitMsg & msg ) {
   2029 	if ( localState != STATE_INGAME ) {
   2030 		return;
   2031 	}
   2032 	
   2033 	if ( GetActingGameStateLobby().IsPeer() ) {
   2034 		idLobby::peer_t & hostPeer = GetActingGameStateLobby().peers[GetActingGameStateLobby().host];
   2035 		
   2036 		// Don't send user cmds if we have unsent packet fragments
   2037 		//  (This can happen if we have packets to send, but SendAnotherFragment got throttled)
   2038 		if ( hostPeer.packetProc->HasMoreFragments() ) {
   2039 			idLib::Warning( "NET: Client called SendUsercmds while HasMoreFragments(). Skipping userCmds for this frame." );
   2040 			return;
   2041 		}
   2042 
   2043 		int sequence = hostPeer.snapProc->GetLastAppendedSequence();
   2044 
   2045 		// Add incoming BPS for QoS
   2046 		float incomingBPS = hostPeer.receivedBps;
   2047 		if ( hostPeer.receivedBpsIndex != sequence ) {
   2048 			incomingBPS = idMath::ClampFloat( 0.0f, static_cast<float>( idLobby::BANDWIDTH_REPORTING_MAX ), hostPeer.packetProc->GetIncomingRateBytes() );
   2049 			hostPeer.receivedBpsIndex = sequence;
   2050 			hostPeer.receivedBps = incomingBPS;
   2051 		}
   2052 		uint16 incomingBPS_quantized = idMath::Ftoi( incomingBPS * ( ( BIT( idLobby::BANDWIDTH_REPORTING_BITS ) - 1 )  / idLobby::BANDWIDTH_REPORTING_MAX ) );
   2053 
   2054 		byte buffer[idPacketProcessor::MAX_FINAL_PACKET_SIZE];
   2055 		lzwCompressionData_t lzwData;
   2056 		idLZWCompressor lzwCompressor( &lzwData );
   2057 		lzwCompressor.Start( buffer, sizeof( buffer ) );
   2058 		lzwCompressor.WriteAgnostic( sequence );
   2059 		lzwCompressor.WriteAgnostic( incomingBPS_quantized );
   2060 		lzwCompressor.Write( msg.GetReadData(), msg.GetSize() );
   2061 		lzwCompressor.End();
   2062 
   2063 		GetActingGameStateLobby().ProcessOutgoingMsg( GetActingGameStateLobby().host, buffer, lzwCompressor.Length(), false, 0 );
   2064 		
   2065 		if ( net_debugBaseStates.GetBool() && sequence < 50 ) {
   2066 			idLib::Printf( "NET: Acking snap %d \n", sequence );
   2067 		}
   2068 	}
   2069 }
   2070 
   2071 /*
   2072 ========================
   2073 idSessionLocal::SendSnapshot
   2074 ========================
   2075 */
   2076 void idSessionLocal::SendSnapshot( idSnapShot & ss ) {
   2077 	for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) {
   2078 		idLobby::peer_t & peer = GetActingGameStateLobby().peers[p];
   2079 	
   2080 		if ( !peer.IsConnected() ) {
   2081 			continue;
   2082 		}
   2083 		
   2084 		if ( !peer.loaded ) {
   2085 			continue;
   2086 		}
   2087 
   2088 		if ( peer.pauseSnapshots ) {
   2089 			continue;
   2090 		}
   2091 		
   2092 		GetActingGameStateLobby().SendSnapshotToPeer( ss, p );
   2093 	}
   2094 }
   2095 
   2096 /*
   2097 ========================
   2098 idSessionLocal::UpdateSignInManager
   2099 ========================
   2100 */
   2101 void idSessionLocal::UpdateSignInManager() {	
   2102 	if ( !HasSignInManager() ) {
   2103 		return;
   2104 	}
   2105 
   2106 	if ( net_headlessServer.GetBool() ) {
   2107 		return;
   2108 	}
   2109 			
   2110 	// FIXME: We need to ask the menu system for this info.  Just making a best guess for now
   2111 	// (assume we are allowed to join the party as a splitscreen user if we are in the party lobby)
   2112 	bool allowJoinParty	= ( localState == STATE_PARTY_LOBBY_HOST || localState == STATE_PARTY_LOBBY_PEER ) && GetPartyLobby().state == idLobby::STATE_IDLE;
   2113 	bool allowJoinGame	= ( localState == STATE_GAME_LOBBY_HOST || localState == STATE_GAME_LOBBY_PEER ) && GetGameLobby().state == idLobby::STATE_IDLE;
   2114 
   2115 	bool eitherLobbyRunning	= GetActivePlatformLobby() != NULL && ( GetPartyLobby().IsLobbyActive() || GetGameLobby().IsLobbyActive() );
   2116 	bool onlineMatch		= eitherLobbyRunning && MatchTypeIsOnline( GetActivePlatformLobby()->parms.matchFlags );
   2117 
   2118 	//=================================================================================
   2119 	// Get the number of desired signed in local users depending on what mode we're in.
   2120 	//=================================================================================
   2121 	int minDesiredUsers = 0;
   2122 	int maxDesiredUsers = Max( 1, signInManager->GetNumLocalUsers() );
   2123 	
   2124 	if ( si_splitscreen.GetInteger() != 0 ) {
   2125 		// For debugging, force 2 splitscreen players
   2126 		minDesiredUsers = 2;
   2127 		maxDesiredUsers = 2;
   2128 		allowJoinGame = true;
   2129 	} else if ( onlineMatch || ( eitherLobbyRunning == false ) ) {
   2130 		// If this an online game, then only 1 user can join locally.
   2131 		// Also, if no sessions are active, remove any extra players.
   2132 		maxDesiredUsers = 1;
   2133 	} else if ( allowJoinParty || allowJoinGame ) {
   2134 		// If we are in the party lobby, allow 2 splitscreen users to join
   2135 		maxDesiredUsers = 2;
   2136 	}
   2137 
   2138 	// Set the number of desired users
   2139 	signInManager->SetDesiredLocalUsers( minDesiredUsers, maxDesiredUsers );
   2140 	
   2141 	//=================================================================================
   2142 	// Update signin manager
   2143 	//=================================================================================
   2144 	
   2145 	// Update signin mgr.  This manager tracks signed in local users, which the session then uses
   2146 	// to determine who should be in the lobby.
   2147 	signInManager->Pump();
   2148 			
   2149 	// Get the master local user
   2150 	idLocalUser * masterUser = signInManager->GetMasterLocalUser();
   2151 
   2152 	if ( onlineMatch && masterUser != NULL && !masterUser->CanPlayOnline() && !masterUser->HasOwnerChanged() ) { 
   2153 		if ( localState > STATE_IDLE ) {
   2154 			// User is still valid, just no longer online
   2155 			if ( offlineTransitionTimerStart == 0 ) {
   2156 				offlineTransitionTimerStart = Sys_Milliseconds();
   2157 			}
   2158 
   2159 			if ( ( Sys_Milliseconds() - offlineTransitionTimerStart ) > net_offlineTransitionThreshold.GetInteger() ) {
   2160 				MoveToMainMenu();
   2161 				common->Dialog().ClearDialogs();
   2162 				common->Dialog().AddDialog( GDM_CONNECTION_LOST, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true );
   2163 			}
   2164 		}
   2165 		return;		// Bail out so signInManager->ValidateLocalUsers below doesn't prematurely remove the master user before we can detect loss of connection
   2166 	} else {
   2167 		offlineTransitionTimerStart = 0;
   2168 	}
   2169 
   2170 	// Remove local users (from the signin manager) who aren't allowed to be online if this is an online match.
   2171 	// Remove local user (from the signin manager) who are not properly signed into a profile.
   2172 	signInManager->ValidateLocalUsers( onlineMatch );
   2173 
   2174 	//=================================================================================
   2175 	// Check to see if we need to go to "Press Start"
   2176 	//=================================================================================
   2177 
   2178 	// Get the master local user (again, after ValidateOnlineLocalUsers, to make sure he is still valid)
   2179 	masterUser = signInManager->GetMasterLocalUser();
   2180 	
   2181 	if ( masterUser == NULL ) { 
   2182 		// If we don't have a master user at all, then we need to be at "Press Start"
   2183 		MoveToPressStart( GDM_SP_SIGNIN_CHANGE_POST );
   2184 		return;
   2185 	} else if ( localState == STATE_PRESS_START ) {
   2186 
   2187 
   2188 		// If we have a master user, and we are at press start, move to the menu area
   2189 		SetState( STATE_IDLE );
   2190 
   2191 	}
   2192 
   2193 	// See if the master user either isn't persistent (but needs to be), OR, if the owner changed
   2194 	// RequirePersistentMaster is poorly named, this really means RequireSignedInMaster
   2195 	if ( masterUser->HasOwnerChanged() || ( RequirePersistentMaster() && !masterUser->IsProfileReady() ) ) {
   2196 		MoveToPressStart( GDM_SP_SIGNIN_CHANGE_POST );
   2197 		return;
   2198 	}
   2199 
   2200 	//=================================================================================
   2201 	// Sync lobby users with the signed in users
   2202 	// The initial list of session users are normally determined at connect or create time.
   2203 	// These functions allow splitscreen users to join in, or check to see if existing
   2204 	// users (including the master) need to be removed.
   2205 	//=================================================================================
   2206 	GetPartyLobby().SyncLobbyUsersWithLocalUsers( allowJoinParty, onlineMatch );
   2207 	GetGameLobby().SyncLobbyUsersWithLocalUsers( allowJoinGame, onlineMatch );
   2208 	GetGameStateLobby().SyncLobbyUsersWithLocalUsers( allowJoinGame, onlineMatch );
   2209 }
   2210 
   2211 /*
   2212 ========================
   2213 idSessionLocal::GetProfileFromMasterLocalUser
   2214 ========================
   2215 */
   2216 idPlayerProfile * idSessionLocal::GetProfileFromMasterLocalUser() {
   2217 	idPlayerProfile * profile = NULL;
   2218 	idLocalUser * masterUser = signInManager->GetMasterLocalUser();
   2219 	
   2220 	if ( masterUser != NULL ) {
   2221 		profile = masterUser->GetProfile();
   2222 	}
   2223 	
   2224 	if ( profile == NULL ) {
   2225 		// Whoops
   2226 		profile = signInManager->GetDefaultProfile();
   2227 		//idLib::Warning( "Returning fake profile until the code is fixed to handle NULL profiles." );
   2228 	}
   2229 	
   2230 	return profile;
   2231 }
   2232 
   2233 /*
   2234 ========================
   2235 /*
   2236 ========================
   2237 idSessionLocal::MoveToPressStart
   2238 ========================
   2239 */
   2240 void idSessionLocal::MoveToPressStart( gameDialogMessages_t msg ) {	
   2241 	if ( localState != STATE_PRESS_START ) {
   2242 		MoveToPressStart();
   2243 		common->Dialog().ClearDialogs();
   2244 		common->Dialog().AddDialog( msg, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true );
   2245 	}
   2246 }
   2247 
   2248 /*
   2249 ========================
   2250 idSessionLocal::GetPeerName
   2251 ========================
   2252 */
   2253 const char * idSessionLocal::GetPeerName( int peerNum ) {
   2254 	return GetActingGameStateLobby().GetPeerName( peerNum );
   2255 }
   2256 
   2257 
   2258 /*
   2259 ========================
   2260 idSessionLocal::SetState
   2261 ========================
   2262 */
   2263 void idSessionLocal::SetState( state_t newState ) {	
   2264 
   2265 	assert( newState < NUM_STATES );
   2266 	assert( localState < NUM_STATES );
   2267 	verify_array_size( stateToString, NUM_STATES );
   2268 
   2269 	if ( newState == localState ) {
   2270 		NET_VERBOSE_PRINT( "NET: SetState: State SAME %s\n", stateToString[ newState ] );
   2271 		return;
   2272 	}
   2273 
   2274 	// Set the current state
   2275 	NET_VERBOSE_PRINT( "NET: SetState: State changing from %s to %s\n", stateToString[ localState ], stateToString[ newState ] );
   2276 
   2277 	if ( localState < STATE_LOADING && newState >= STATE_LOADING ) {
   2278 		// Tell lobby instances that the match has started
   2279 		StartSessions();
   2280 		// Clear certain dialog boxes we don't want to see in-game
   2281 		common->Dialog().ClearDialog( GDM_LOBBY_DISBANDED );	// The lobby you were previously in has disbanded
   2282 	} else if ( localState >= STATE_LOADING && newState < STATE_LOADING ) {
   2283 		// Tell lobby instances that the match has ended
   2284 		if ( !WasMigrationGame() ) { // Don't end the session if we are going right back into the game
   2285 			EndSessions();
   2286 		}
   2287 	}
   2288 
   2289 	if ( newState == STATE_GAME_LOBBY_HOST || newState == STATE_GAME_LOBBY_PEER ) {
   2290 		ComputeNextGameCoalesceTime();
   2291 	}
   2292 
   2293 	localState = newState;
   2294 }
   2295 
   2296 /*
   2297 ========================
   2298 idSessionLocal::HandlePackets
   2299 ========================
   2300 */
   2301 bool idSessionLocal::HandlePackets() {
   2302 	SCOPED_PROFILE_EVENT( "Session::HandlePackets" );
   2303 
   2304 	byte				packetBuffer[ idPacketProcessor::MAX_FINAL_PACKET_SIZE ];
   2305 	lobbyAddress_t		remoteAddress;
   2306 	int					recvSize = 0;
   2307 	bool				fromDedicated = false;
   2308 
   2309 	while ( ReadRawPacket( remoteAddress, packetBuffer, recvSize, fromDedicated, sizeof( packetBuffer ) ) && recvSize > 0 ) {
   2310 		
   2311 		// fragMsg will hold the raw packet
   2312 		idBitMsg fragMsg;
   2313 		fragMsg.InitRead( packetBuffer, recvSize );
   2314 
   2315 		// Peek at the session ID
   2316 		idPacketProcessor::sessionId_t sessionID = idPacketProcessor::GetSessionID( fragMsg );
   2317 
   2318 		// idLib::Printf( "NET: HandlePackets - session %d, size %d \n", sessionID, recvSize );
   2319 
   2320 		// Make sure it's valid
   2321 		if ( sessionID == idPacketProcessor::SESSION_ID_INVALID ) {
   2322 			idLib::Printf( "NET: Invalid sessionID %s.\n", remoteAddress.ToString() );
   2323 			continue;
   2324 		}
   2325 
   2326 		//
   2327 		// Distribute the packet to the proper lobby
   2328 		//
   2329 
   2330 		const int maskedType = sessionID & idPacketProcessor::LOBBY_TYPE_MASK;
   2331 
   2332 		if ( !verify( maskedType > 0 ) ) {
   2333 			continue;
   2334 		}
   2335 
   2336 		idLobby::lobbyType_t lobbyType = (idLobby::lobbyType_t)( maskedType - 1 );
   2337 
   2338 		switch ( lobbyType ) {
   2339 			case idLobby::TYPE_PARTY:		GetPartyLobby().HandlePacket( remoteAddress, fragMsg, sessionID );		break;
   2340 			case idLobby::TYPE_GAME:		GetGameLobby().HandlePacket( remoteAddress, fragMsg, sessionID );		break;
   2341 			case idLobby::TYPE_GAME_STATE:	GetGameStateLobby().HandlePacket( remoteAddress, fragMsg, sessionID );	break;
   2342 			default:						assert( 0 );
   2343 		}
   2344 	}
   2345 
   2346 	return false;
   2347 }
   2348 
   2349 /*
   2350 ========================
   2351 idSessionLocal::GetActivePlatformLobby
   2352 ========================
   2353 */
   2354 idLobby * idSessionLocal::GetActivePlatformLobby() {
   2355 	sessionState_t state = GetState();
   2356 
   2357 	if ( ( state == GAME_LOBBY ) || ( state == BUSY ) || ( state == INGAME ) || ( state == LOADING ) ) {
   2358 		return &GetGameLobby();
   2359 	} else if ( state == PARTY_LOBBY ) {
   2360 		return &GetPartyLobby();
   2361 	}
   2362 	
   2363 	return NULL;
   2364 }
   2365 
   2366 /*
   2367 ========================
   2368 idSessionLocal::GetActivePlatformLobby
   2369 ========================
   2370 */
   2371 const idLobby * idSessionLocal::GetActivePlatformLobby() const {
   2372 	sessionState_t state = GetState();
   2373 
   2374 	if ( ( state == GAME_LOBBY ) || ( state == BUSY ) || ( state == INGAME ) || ( state == LOADING ) ) {
   2375 		return &GetGameLobby();
   2376 	} else if ( state == PARTY_LOBBY ) {
   2377 		return &GetPartyLobby();
   2378 	}
   2379 	
   2380 	return NULL;
   2381 }
   2382 
   2383 /*
   2384 ========================
   2385 idSessionLocal::GetActingGameStateLobby
   2386 ========================
   2387 */
   2388 idLobby & idSessionLocal::GetActingGameStateLobby() {
   2389 	if ( net_useGameStateLobby.GetBool() ) {
   2390 		return GetGameStateLobby();
   2391 	}
   2392 
   2393 	return GetGameLobby();
   2394 }
   2395 
   2396 /*
   2397 ========================
   2398 idSessionLocal::GetActingGameStateLobby
   2399 ========================
   2400 */
   2401 const idLobby & idSessionLocal::GetActingGameStateLobby() const {
   2402 	if ( net_useGameStateLobby.GetBool() ) {
   2403 		return GetGameStateLobby();
   2404 	}
   2405 
   2406 	return GetGameLobby();
   2407 }
   2408 
   2409 /*
   2410 ========================
   2411 idSessionLocal::GetLobbyFromType
   2412 ========================
   2413 */
   2414 idLobby * idSessionLocal::GetLobbyFromType( idLobby::lobbyType_t lobbyType ) {
   2415 	switch ( lobbyType ) {
   2416 		case idLobby::TYPE_PARTY:		return &GetPartyLobby();
   2417 		case idLobby::TYPE_GAME:		return &GetGameLobby();
   2418 		case idLobby::TYPE_GAME_STATE:	return &GetGameStateLobby();
   2419 	}
   2420 
   2421 	return NULL;
   2422 }
   2423 
   2424 /*
   2425 ========================
   2426 idSessionLocal::GetActivePlatformLobbyBase
   2427 This returns the base version for the idSession version
   2428 ========================
   2429 */
   2430 idLobbyBase & idSessionLocal::GetActivePlatformLobbyBase() {
   2431 	idLobby * activeLobby = GetActivePlatformLobby();
   2432 
   2433 	if ( activeLobby != NULL ) {
   2434 		return *activeLobby;
   2435 	}
   2436 
   2437 	return stubLobby;		// So we can return at least something
   2438 }
   2439 
   2440 /*
   2441 ========================
   2442 idSessionLocal::GetLobbyFromLobbyUserID
   2443 ========================
   2444 */
   2445 idLobbyBase & idSessionLocal::GetLobbyFromLobbyUserID( lobbyUserID_t lobbyUserID ) {
   2446 	if ( !lobbyUserID.IsValid() ) {
   2447 		return stubLobby;	// So we can return at least something
   2448 	}
   2449 
   2450 	idLobby * lobby = GetLobbyFromType( (idLobby::lobbyType_t)lobbyUserID.GetLobbyType() );
   2451 
   2452 	if ( lobby != NULL ) {
   2453 		return *lobby;
   2454 	}
   2455 
   2456 	return stubLobby;		// So we can return at least something
   2457 }
   2458 
   2459 /*
   2460 ========================
   2461 idSessionLocal::TickSendQueue
   2462 ========================
   2463 */
   2464 void idSessionLocal::TickSendQueue() {
   2465 	assert( !sendQueue.IsEmpty() );
   2466 	int now = Sys_Milliseconds();
   2467 	idQueuePacket * packet = sendQueue.Peek();
   2468 	while ( packet != NULL ) {
   2469 		if ( now < packet->time ) {
   2470 			break;
   2471 		}
   2472 
   2473 		GetPort( packet->dedicated ).SendRawPacket( packet->address, packet->data, packet->size );
   2474 
   2475 		if ( net_forceUpstream.GetFloat() != 0.0f && net_forceUpstreamQueue.GetFloat() != 0.0f ) {
   2476 			// FIXME: no can do both
   2477 			assert( net_forceLatency.GetInteger() == 0 );
   2478 			// compute / update an added traffic due to the queuing
   2479 			// we can't piggyback on upstreamDropRate because of the way it's computed and clamped to zero
   2480 			int time = Sys_Milliseconds();
   2481 			if ( time > upstreamQueueRateTime ) {
   2482 				upstreamQueueRate -= upstreamQueueRate * ( float )( time - upstreamQueueRateTime ) / 1000.0f;
   2483 				if ( upstreamQueueRate < 0.0f ) {
   2484 					upstreamQueueRate = 0.0f;
   2485 				}
   2486 				upstreamQueueRateTime = time;
   2487 			}
   2488 			// update queued bytes
   2489 			queuedBytes -= packet->size;
   2490 			if ( net_verboseSimulatedTraffic.GetBool() ) {
   2491 				idLib::Printf( "send queued packet size %d to %s\n", packet->size, packet->address.ToString() );
   2492 			}
   2493 		}
   2494 
   2495 		sendQueue.RemoveFirst(); // we have it already, just push it off the queue before freeing
   2496 		packetAllocator.Free( packet );
   2497 		packet = sendQueue.Peek();
   2498 	}
   2499 }
   2500 
   2501 /*
   2502 ========================
   2503 idSessionLocal::QueuePacket
   2504 ========================
   2505 */
   2506 void idSessionLocal::QueuePacket( idQueue< idQueuePacket,&idQueuePacket::queueNode > & queue, int time, const lobbyAddress_t & to, const void * data, int size, bool dedicated ) {
   2507 	//mem.PushHeap();
   2508 
   2509 	idQueuePacket * packet = packetAllocator.Alloc();
   2510 
   2511 	packet->address		= to;
   2512 	packet->size		= size;
   2513 	packet->dedicated	= dedicated;
   2514 	packet->time		= time;
   2515 
   2516 	memcpy( packet->data, data, size );
   2517 
   2518 	queue.Add( packet );
   2519 
   2520 	//mem.PopHeap();
   2521 }
   2522 
   2523 /*
   2524 ========================
   2525 idSessionLocal::ReadRawPacketFromQueue
   2526 ========================
   2527 */
   2528 bool idSessionLocal::ReadRawPacketFromQueue( int time, lobbyAddress_t & from, void * data, int & size, bool & outDedicated, int maxSize ) {
   2529 	idQueuePacket * packet = recvQueue.Peek();
   2530 
   2531 	if ( packet == NULL || time < packet->time ) {
   2532 		return false;		// Either there are no packets, or no packet is ready
   2533 	}
   2534 
   2535 	//idLib::Printf( "NET: Packet recvd: %d ms\n", now );
   2536 
   2537 	from = packet->address;
   2538 	size = packet->size;
   2539 	assert( size <= maxSize );
   2540 	outDedicated = packet->dedicated;
   2541 	memcpy( data, packet->data, packet->size );
   2542 	recvQueue.RemoveFirst(); // we have it already, just push it off the queue before freeing
   2543 	packetAllocator.Free( packet );
   2544 
   2545 	return true;
   2546 }
   2547 
   2548 /*
   2549 ========================
   2550 idSessionLocal::SendRawPacket
   2551 ========================
   2552 */
   2553 void idSessionLocal::SendRawPacket( const lobbyAddress_t & to, const void * data, int size, bool dedicated ) {
   2554 	const int now = Sys_Milliseconds();
   2555 
   2556 	if ( net_forceUpstream.GetFloat() != 0 ) {
   2557 
   2558 		// the total bandwidth rate at which the networking systems are trying to push data through
   2559 		float totalOutgoingRate = (float)GetActingGameStateLobby().GetTotalOutgoingRate(); // B/s
   2560 
   2561 		// update the rate at which we have been taking data out by dropping it
   2562 		int time = Sys_Milliseconds();
   2563 		if ( time > upstreamDropRateTime ) {
   2564 			upstreamDropRate -= upstreamDropRate * ( float )( time - upstreamDropRateTime ) / 1000.0f;
   2565 			if ( upstreamDropRate < 0.0f ) {
   2566 				upstreamDropRate = 0.0f;
   2567 			}
   2568 			upstreamDropRateTime = time;
   2569 		}
   2570 
   2571 		if ( (float)( totalOutgoingRate - upstreamDropRate + upstreamQueueRate ) > net_forceUpstream.GetFloat() * 1024.0f ) { // net_forceUpstream is in kB/s, everything else in B/s
   2572 			if ( net_forceUpstreamQueue.GetFloat() == 0.0f ) {
   2573 				// just drop the packet - not representative, but simple
   2574 				if ( net_verboseSimulatedTraffic.GetBool() ) {
   2575 					idLib::Printf( "drop %d bytes to %s\n", size, to.ToString() );
   2576 				}
   2577 				// increase the instant drop rate with the data we just dropped
   2578 				upstreamDropRate += size;
   2579 				return;
   2580 			}
   2581 
   2582 			// simulate a network device with a send queue
   2583 			// do we have room in the queue?
   2584 			assert( net_forceUpstreamQueue.GetFloat() > 0.0f );
   2585 			if ( (float)( queuedBytes + size ) > net_forceUpstreamQueue.GetFloat() * 1024.0f ) { // net_forceUpstreamQueue is in kB/s
   2586 				// too much queued, this is still a drop
   2587 				// FIXME: factorize
   2588 				// just drop the packet - not representative, but simple
   2589 				if ( net_verboseSimulatedTraffic.GetBool() ) {
   2590 					idLib::Printf( "full queue: drop %d bytes to %s\n", size, to.ToString() );
   2591 				}
   2592 				// increase the instant drop rate with the data we just dropped
   2593 				upstreamDropRate += size;
   2594 				return;
   2595 			}
   2596 			// there is room to buffer up in the queue
   2597 			queuedBytes += size;
   2598 			// with queuedBytes and the current upstream, when should this packet be sent?
   2599 			int queuedPacketSendDelay = 1000.0f * ( (float)queuedBytes / ( net_forceUpstream.GetFloat() * 1024.0f ) ); // in ms
   2600 			// queue for sending
   2601 			if ( net_verboseSimulatedTraffic.GetBool() ) {
   2602 				idLib::Printf( "queuing packet: %d bytes delayed %d ms\n", size, queuedPacketSendDelay );
   2603 			}
   2604 
   2605 			QueuePacket( sendQueue, now + queuedPacketSendDelay, to, data, size, dedicated );
   2606 
   2607 			// will abuse the forced latency code below to take care of the sending
   2608 			// FIXME: right now, can't have both on
   2609 			assert( net_forceLatency.GetInteger() == 0 );
   2610 		}
   2611 	}
   2612 
   2613 	// short path
   2614 	// NOTE: network queuing: will go to tick the queue whenever sendQueue isn't empty, regardless of latency
   2615 	if ( net_forceLatency.GetInteger() == 0 && sendQueue.IsEmpty() ) {
   2616 		GetPort( dedicated ).SendRawPacket( to, data, size );
   2617 		return;
   2618 	}
   2619 
   2620 	if ( net_forceUpstream.GetFloat() != 0.0f && net_forceUpstreamQueue.GetFloat() != 0.0f ) {
   2621 		// FIXME: not doing both just yet
   2622 		assert( net_forceLatency.GetInteger() == 0 );
   2623 		TickSendQueue();
   2624 		return; // we done (at least for queue only path)
   2625 	}
   2626 
   2627 	// queue up
   2628 	assert( size != 0 && size <= idPacketProcessor::MAX_FINAL_PACKET_SIZE );
   2629 
   2630 	QueuePacket( sendQueue, now + net_forceLatency.GetInteger() / 2, to, data, size, dedicated );
   2631 
   2632 	TickSendQueue();
   2633 }
   2634 
   2635 /*
   2636 ========================
   2637 idSessionLocal::ReadRawPacket
   2638 ========================
   2639 */
   2640 bool idSessionLocal::ReadRawPacket( lobbyAddress_t & from, void * data, int & size, bool & outDedicated, int maxSize ) {
   2641 	SCOPED_PROFILE_EVENT( "Session::ReadRawPacket" );
   2642 
   2643 	assert( maxSize <= idPacketProcessor::MAX_FINAL_PACKET_SIZE );
   2644 
   2645 	if ( !sendQueue.IsEmpty() ) {
   2646 		TickSendQueue();
   2647 	}
   2648 
   2649 	const int now = Sys_Milliseconds();
   2650 
   2651 	// Make sure we give both ports equal time
   2652 	static bool currentDedicated = false;
   2653 	currentDedicated = !currentDedicated;
   2654 
   2655 	for ( int i = 0; i < 2; i++ ) {
   2656 		// BRIAN_FIXME: Dedicated servers fuck up running 2 instances on the same machine
   2657 		// outDedicated = ( i == 0 ) ? currentDedicated : !currentDedicated;
   2658 		outDedicated = false;
   2659 
   2660 		if ( GetPort( outDedicated ).ReadRawPacket( from, data, size, maxSize ) ) {
   2661 			if ( net_forceLatency.GetInteger() == 0 && recvQueue.IsEmpty() ) {
   2662 				// If we aren't forcing latency, and queue is empty, return result immediately
   2663 				return true;
   2664 			}
   2665 			
   2666 			// the cvar is meant to be a round trip latency so we're applying half on the send and half on the recv
   2667 			const int time = ( net_forceLatency.GetInteger() == 0 ) ? 0 : now + net_forceLatency.GetInteger() / 2;
   2668 
   2669 			// Otherwise, queue result
   2670 			QueuePacket( recvQueue, time, from, data, size, outDedicated );
   2671 		}
   2672 	}
   2673 
   2674 	// Return any queued results
   2675 	return ReadRawPacketFromQueue( now, from, data, size, outDedicated, maxSize );
   2676 }
   2677 
   2678 /*
   2679 ========================
   2680 idSessionLocal::ConnectAndMoveToLobby
   2681 ========================
   2682 */
   2683 void idSessionLocal::ConnectAndMoveToLobby( idLobby & lobby, const lobbyConnectInfo_t & connectInfo, bool fromInvite ) {
   2684 
   2685 	// Since we are connecting directly to a lobby, make sure no search results are left over from previous FindOrCreateMatch results
   2686 	// If we don't do this, we might think we should attempt to connect to an old search result, and we don't want to in this case
   2687 	lobby.searchResults.Clear();
   2688 
   2689 	// Attempt to connect to the lobby
   2690 	lobby.ConnectTo( connectInfo, fromInvite );
   2691 
   2692 	connectType = CONNECT_DIRECT;
   2693 
   2694 	// Wait for connection
   2695 	switch ( lobby.lobbyType ) {
   2696 		case idLobby::TYPE_PARTY:		SetState( STATE_CONNECT_AND_MOVE_TO_PARTY );		break;
   2697 		case idLobby::TYPE_GAME:		SetState( STATE_CONNECT_AND_MOVE_TO_GAME );			break;
   2698 		case idLobby::TYPE_GAME_STATE:	SetState( STATE_CONNECT_AND_MOVE_TO_GAME_STATE );	break;
   2699 	}
   2700 }
   2701 
   2702 /*
   2703 ========================
   2704 idSessionLocal::GoodbyeFromHost
   2705 ========================
   2706 */
   2707 void idSessionLocal::GoodbyeFromHost( idLobby & lobby, int peerNum, const lobbyAddress_t & remoteAddress, int msgType ) {
   2708 	if ( !verify( localState > STATE_IDLE ) ) {
   2709 		idLib::Printf( "NET: Got disconnected from host %s on session %s when we were not in a lobby or game.\n", remoteAddress.ToString(), lobby.GetLobbyName() );
   2710 		MoveToMainMenu();
   2711 		return;		// Ignore if we are not past the main menu
   2712 	}
   2713 
   2714 	// Goodbye from host.  See if we were connecting vs connected
   2715 	if ( ( localState == STATE_CONNECT_AND_MOVE_TO_PARTY || localState == STATE_CONNECT_AND_MOVE_TO_GAME ) && lobby.peers[peerNum].GetConnectionState() == idLobby::CONNECTION_CONNECTING ) {
   2716 		// We were denied a connection attempt
   2717 		idLib::Printf( "NET: Denied connection attempt from host %s on session %s. MsgType %i.\n", remoteAddress.ToString(), lobby.GetLobbyName(), msgType );
   2718 		// This will try to move to the next connection if one exists, otherwise will create a match
   2719 		HandleConnectionFailed( lobby, msgType == idLobby::OOB_GOODBYE_FULL );
   2720 	} else {
   2721 		// We were disconnected from a server we were previously connected to
   2722 		idLib::Printf( "NET: Disconnected from host %s on session %s. MsgType %i.\n", remoteAddress.ToString(), lobby.GetLobbyName(), msgType );
   2723 
   2724 		const bool leaveGameWithParty = ( msgType == idLobby::OOB_GOODBYE_W_PARTY );
   2725 
   2726 		if ( leaveGameWithParty && lobby.lobbyType == idLobby::TYPE_GAME && lobby.IsPeer() && GetState() == idSession::GAME_LOBBY && GetPartyLobby().host >= 0 &&
   2727 				lobby.peers[peerNum].address.Compare( GetPartyLobby().peers[GetPartyLobby().host].address, true ) ) {
   2728 			// If a host is telling us goodbye from a game lobby, and the game host is the same as our party host, 
   2729 			// and we aren't in a game, and the host wants us to leave with him, then do so now
   2730 			GetGameLobby().Shutdown();
   2731 			GetGameStateLobby().Shutdown();
   2732 			SetState( STATE_PARTY_LOBBY_PEER );
   2733 		} else {
   2734 			// Host left, so pick a new host (possibly even us) for this lobby
   2735 			lobby.PickNewHost();
   2736 		}
   2737 	}
   2738 }
   2739 
   2740 /*
   2741 ========================
   2742 idSessionLocal::WriteLeaderboardToMsg
   2743 ========================
   2744 */
   2745 void idSessionLocal::WriteLeaderboardToMsg( idBitMsg & msg, const leaderboardDefinition_t * leaderboard, const column_t * stats ) {
   2746 	assert( Sys_FindLeaderboardDef( leaderboard->id ) == leaderboard );
   2747 	
   2748 	msg.WriteLong( leaderboard->id );
   2749 
   2750 	for ( int i = 0; i < leaderboard->numColumns; ++i ) {
   2751 		uint64 value = stats[i].value;
   2752 
   2753 		//idLib::Printf( "value = %i\n", (int32)value );
   2754 
   2755 		for ( int j = 0; j < leaderboard->columnDefs[i].bits; j++ ) {
   2756 			msg.WriteBits( value & 1, 1 );
   2757 			value >>= 1;
   2758 		}
   2759 		//msg.WriteData( &stats[i].value, sizeof( stats[i].value ) );
   2760 	}
   2761 }
   2762 
   2763 /*
   2764 ========================
   2765 idSessionLocal::ReadLeaderboardFromMsg
   2766 ========================
   2767 */
   2768 const leaderboardDefinition_t * idSessionLocal::ReadLeaderboardFromMsg( idBitMsg & msg, column_t * stats ) {
   2769 	int id = msg.ReadLong();
   2770 	
   2771 	const leaderboardDefinition_t * leaderboard = Sys_FindLeaderboardDef( id );
   2772 	
   2773 	if ( leaderboard == NULL ) {
   2774 		idLib::Printf( "NET: Invalid leaderboard id: %i\n", id );
   2775 		return NULL;
   2776 	}
   2777 	
   2778 	for ( int i = 0; i < leaderboard->numColumns; ++i ) {
   2779 		uint64 value = 0;
   2780 
   2781 		for ( int j = 0; j < leaderboard->columnDefs[i].bits; j++ ) {
   2782 			value |= (uint64)( msg.ReadBits( 1 ) & 1 ) << j;
   2783 		}
   2784 
   2785 		stats[i].value = value;
   2786 
   2787 		//idLib::Printf( "value = %i\n", (int32)value );
   2788 		//msg.ReadData( &stats[i].value, sizeof( stats[i].value ) );
   2789 	}
   2790 	
   2791 	return leaderboard;
   2792 }
   2793 
   2794 /*
   2795 ========================
   2796 idSessionLocal::SendLeaderboardStatsToPlayer
   2797 ========================
   2798 */
   2799 void idSessionLocal::SendLeaderboardStatsToPlayer( lobbyUserID_t lobbyUserID, const leaderboardDefinition_t * leaderboard, const column_t * stats ) {
   2800 
   2801 	const int sessionUserIndex = GetActingGameStateLobby().GetLobbyUserIndexByID( lobbyUserID );
   2802 
   2803 	if ( GetActingGameStateLobby().IsLobbyUserDisconnected( sessionUserIndex ) ) {
   2804 		idLib::Warning( "Tried to tell disconnected user to report stats" );
   2805 		return;
   2806 	}
   2807 	
   2808 	const int peerIndex = GetActingGameStateLobby().PeerIndexFromLobbyUser( lobbyUserID );
   2809 	
   2810 	if ( peerIndex == -1 ) {
   2811 		idLib::Warning( "Tried to tell invalid peer index to report stats" );
   2812 		return;
   2813 	}
   2814 
   2815 	if ( !verify( GetActingGameStateLobby().IsHost() ) || 
   2816 		!verify( peerIndex < GetActingGameStateLobby().peers.Num() ) || 
   2817 		!verify( GetActingGameStateLobby().peers[ peerIndex ].IsConnected() ) ) {
   2818 		idLib::Warning( "Tried to tell invalid peer to report stats" );
   2819 		return;
   2820 	}
   2821 
   2822 	NET_VERBOSE_PRINT( "Telling sessionUserIndex %i (peer %i) to report stats\n", sessionUserIndex, peerIndex );
   2823 
   2824 	lobbyUser_t * gameUser = GetActingGameStateLobby().GetLobbyUser( sessionUserIndex );
   2825 
   2826 	if ( !verify( gameUser != NULL ) ) {
   2827 		return;
   2828 	}
   2829 
   2830 	byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ];
   2831 	idBitMsg msg( buffer, sizeof( buffer ) );
   2832 
   2833 	// Use the user ID
   2834 	gameUser->lobbyUserID.WriteToMsg( msg );
   2835 
   2836 	WriteLeaderboardToMsg( msg, leaderboard, stats );
   2837 	
   2838 	GetActingGameStateLobby().QueueReliableMessage( peerIndex, idLobby::RELIABLE_POST_STATS, msg.GetReadData(), msg.GetSize() );
   2839 }
   2840 
   2841 /*
   2842 ========================
   2843 idSessionLocal::RecvLeaderboardStatsForPlayer
   2844 ========================
   2845 */
   2846 void idSessionLocal::RecvLeaderboardStatsForPlayer( idBitMsg & msg ) {
   2847 	column_t stats[ MAX_LEADERBOARD_COLUMNS ];
   2848 
   2849 	lobbyUserID_t lobbyUserID;
   2850 	lobbyUserID.ReadFromMsg( msg );
   2851 
   2852 	const leaderboardDefinition_t * leaderboard = ReadLeaderboardFromMsg( msg, stats );
   2853 	
   2854 	if ( leaderboard == NULL ) {
   2855 		idLib::Printf( "RecvLeaderboardStatsForPlayer: Invalid lb.\n" );
   2856 		return;
   2857 	}
   2858 
   2859 	LeaderboardUpload( lobbyUserID, leaderboard, stats );
   2860 }
   2861 
   2862 /*
   2863 ========================
   2864 idSessionLocal::RequirePersistentMaster
   2865 ========================
   2866 */
   2867 bool idSessionLocal::RequirePersistentMaster() {
   2868 	return signInManager->RequirePersistentMaster();
   2869 }
   2870 
   2871 /*
   2872 ========================
   2873 CheckAndUpdateValue
   2874 ========================
   2875 */
   2876 template<typename T>
   2877 bool CheckAndUpdateValue( T & value, const T & newValue ) {
   2878 	if ( value == newValue ) {
   2879 		return false;
   2880 	}
   2881 	value = newValue;
   2882 	return true;
   2883 }
   2884 
   2885 /*
   2886 ========================
   2887 lobbyUser_t::UpdateClientMutableData
   2888 ========================
   2889 */
   2890 bool lobbyUser_t::UpdateClientMutableData( const idLocalUser * localUser ) {
   2891 	bool updated = false;
   2892 	const idPlayerProfile * profile = localUser->GetProfile();
   2893 	if ( profile != NULL ) {
   2894 		updated |= CheckAndUpdateValue( level, profile->GetLevel() );
   2895 	}
   2896 	updated |= CheckAndUpdateValue( selectedSkin, ui_skinIndex.GetInteger() );
   2897 	updated |= CheckAndUpdateValue( weaponAutoSwitch, ui_autoSwitch.GetBool() );
   2898 	updated |= CheckAndUpdateValue( weaponAutoReload, ui_autoReload.GetBool() );
   2899 	return updated;
   2900 }
   2901 
   2902 /*
   2903 ========================
   2904 idSessionLocal::ComputeNextGameCoalesceTime
   2905 ========================
   2906 */
   2907 void idSessionLocal::ComputeNextGameCoalesceTime() {
   2908 	const int coalesceTimeInSeconds			= session->GetTitleStorageInt( "net_LobbyCoalesceTimeInSeconds", net_LobbyCoalesceTimeInSeconds.GetInteger() );
   2909 	const int randomCoalesceTimeInSeconds	= session->GetTitleStorageInt( "net_LobbyRandomCoalesceTimeInSeconds", net_LobbyRandomCoalesceTimeInSeconds.GetInteger() );
   2910 
   2911 	if ( coalesceTimeInSeconds != 0 ) {
   2912 		static idRandom2 random( Sys_Milliseconds() );
   2913 
   2914 		nextGameCoalesceTime = Sys_Milliseconds() + ( coalesceTimeInSeconds + random.RandomInt( randomCoalesceTimeInSeconds ) ) * 1000;
   2915 	} else {
   2916 		nextGameCoalesceTime = 0;
   2917 	}
   2918 }
   2919 
   2920 /*
   2921 ========================
   2922 lobbyUser_t::Net_BandwidthChallenge
   2923 ========================
   2924 */
   2925 CONSOLE_COMMAND( Net_BandwidthChallenge, "Test network bandwidth", 0 ) {
   2926 	session->StartOrContinueBandwidthChallenge( true );
   2927 }
   2928 
   2929 /*
   2930 ========================
   2931 lobbyUser_t::Net_ThrottlePeer
   2932 ========================
   2933 */
   2934 CONSOLE_COMMAND( Net_ThrottlePeer, "Test network bandwidth", 0 ) {
   2935 	
   2936 	int peerNum = -1;
   2937 	int snapRate = 0;
   2938 
   2939 	if ( args.Argc() >= 3 ) {		
   2940 		peerNum = atoi( args.Argv(1) );
   2941 		snapRate = atoi( args.Argv(2) );		
   2942 	}
   2943 
   2944 	// Note DebugSetPeerSnaprate will handle peerNum=-1 by printing out list of peers
   2945 	session->DebugSetPeerSnaprate( peerNum, snapRate );
   2946 }
   2947 
   2948 
   2949 // FIXME: Move to sys_stats.cpp
   2950 idStaticList< leaderboardDefinition_t *, MAX_LEADERBOARDS > registeredLeaderboards;
   2951 
   2952 /*
   2953 ========================
   2954 Sys_FindLeaderboardDef
   2955 ========================
   2956 */
   2957 const leaderboardDefinition_t * Sys_FindLeaderboardDef( int id ) {
   2958 	for ( int i = 0; i < registeredLeaderboards.Num() ; i++ ) {
   2959 		if ( registeredLeaderboards[i] && registeredLeaderboards[i]->id == id ) {
   2960 			return registeredLeaderboards[i];
   2961 		}
   2962 	}
   2963 	
   2964 	return NULL;
   2965 }
   2966 
   2967 /*
   2968 ========================
   2969 Sys_CreateLeaderboardDef
   2970 ========================
   2971 */
   2972 leaderboardDefinition_t * Sys_CreateLeaderboardDef( int id_, int numColumns_, const columnDef_t * columnDefs_,
   2973 													rankOrder_t rankOrder_, bool supportsAttachments_, bool checkAgainstCurrent_ ) {
   2974 	
   2975 	leaderboardDefinition_t * newDef = new (TAG_NETWORKING) leaderboardDefinition_t(	id_, numColumns_, columnDefs_, rankOrder_, supportsAttachments_, checkAgainstCurrent_ );	
   2976 
   2977 	// try and reuse a free spot
   2978 	int leaderboardHandle = registeredLeaderboards.FindNull();
   2979 
   2980 	if ( leaderboardHandle == -1 ) {
   2981 		leaderboardHandle = registeredLeaderboards.Append( NULL );
   2982 	} 
   2983 
   2984 	registeredLeaderboards[ leaderboardHandle ]  = newDef;
   2985 
   2986 	return newDef;
   2987 }
   2988 
   2989 /*
   2990 ========================
   2991 Sys_CreateLeaderboardDef
   2992 ========================
   2993 */
   2994 void Sys_DestroyLeaderboardDefs() {
   2995 
   2996 	// delete and clear all the contents of the registeredLeaderboards static list. 
   2997 	registeredLeaderboards.DeleteContents( true );
   2998 }
   2999 
   3000 /*
   3001 ========================
   3002 idSessionLocal::StartOrContinueBandwidthChallenge
   3003 This will start a bandwidth test if one is not active
   3004 returns true if a test has completed
   3005 ========================
   3006 */
   3007 bool idSessionLocal::StartOrContinueBandwidthChallenge( bool forceStart ) {
   3008 	idLobby * activeLobby = GetActivePlatformLobby();
   3009 	if ( activeLobby == NULL ) {
   3010 		idLib::Warning("No active session lobby when idSessionLocal::StartBandwidthChallenge called");
   3011 		return true;
   3012 	}
   3013 
   3014 	if ( !forceStart && activeLobby->bandwidthChallengeFinished ) {
   3015 		activeLobby->bandwidthChallengeFinished = false;
   3016 		return true;
   3017 	}
   3018 
   3019 	if ( !activeLobby->BandwidthTestStarted() ) {
   3020 		activeLobby->BeginBandwidthTest();
   3021 	}
   3022 
   3023 	return false;
   3024 }
   3025 
   3026 /*
   3027 ========================
   3028 idSessionLocal::DebugSetPeerSnaprate
   3029 This is debug function for manually setting peer's snaprate in game
   3030 ========================
   3031 */
   3032 void idSessionLocal::DebugSetPeerSnaprate( int peerIndex, int snapRateMS ) {
   3033 	idLobby * activeLobby = GetActivePlatformLobby();
   3034 	if ( activeLobby == NULL ) {
   3035 		idLib::Warning("No active session lobby when idSessionLocal::StartBandwidthChallenge called");
   3036 		return;
   3037 	}
   3038 
   3039 	if ( peerIndex < 0 || peerIndex > activeLobby->peers.Num() ) {
   3040 		idLib::Printf("Invalid peer %d\n", peerIndex );
   3041 		for ( int i=0; i < activeLobby->peers.Num(); i++ ) {
   3042 			idLib::Printf( "Peer[%d] %s\n", i, activeLobby->GetPeerName(i) );
   3043 		}
   3044 		return;
   3045 	}
   3046 
   3047 	activeLobby->peers[peerIndex].throttledSnapRate = snapRateMS * 1000;
   3048 	activeLobby->peers[peerIndex].receivedThrottle = 0;
   3049 	idLib::Printf( "Set peer %s new snapRate: %d\n", activeLobby->GetPeerName(peerIndex), activeLobby->peers[peerIndex].throttledSnapRate );
   3050 }
   3051 
   3052 /*
   3053 ========================
   3054 idSessionLocal::DebugSetPeerSnaprate
   3055 This is debug function for manually setting peer's snaprate in game
   3056 ========================
   3057 */
   3058 float idSessionLocal::GetIncomingByteRate() {
   3059 	idLobby * activeLobby = GetActivePlatformLobby();
   3060 	if ( activeLobby == NULL ) {
   3061 		idLib::Warning("No active session lobby when idSessionLocal::GetIncomingByteRate called");
   3062 		return 0;
   3063 	}
   3064 
   3065 	float total = 0;
   3066 	for ( int p=0; p < activeLobby->peers.Num(); p++ ) {
   3067 		if ( activeLobby->peers[p].IsConnected() ) {
   3068 			total += activeLobby->peers[p].packetProc->GetIncomingRateBytes();
   3069 		}
   3070 	}
   3071 
   3072 	return total;
   3073 }
   3074 
   3075 /*
   3076 ========================
   3077 idSessionLocal::OnLocalUserSignin
   3078 ========================
   3079 */
   3080 void idSessionLocal::OnLocalUserSignin( idLocalUser * user ) {
   3081 	// Do stuff before calling OnMasterLocalUserSignin()
   3082 	session->GetAchievementSystem().RegisterLocalUser( user );
   3083 
   3084 	// We may not have a profile yet, need to call user's version...
   3085 	user->LoadProfileSettings();
   3086 
   3087 	// for all consoles except the PS3 we enumerate right away because they don't 
   3088 	// take such a long time as the PS3. PS3 enumeration is done in the 
   3089 	// background and kicked off when the profile callback is triggered
   3090 	if ( user == GetSignInManager().GetMasterLocalUser() ) {
   3091 		OnMasterLocalUserSignin();
   3092 	}
   3093 }
   3094 
   3095 /*
   3096 ========================
   3097 idSessionLocal::OnLocalUserSignout
   3098 ========================
   3099 */
   3100 void idSessionLocal::OnLocalUserSignout( idLocalUser * user ) {
   3101 	// Do stuff before calling OnMasterLocalUserSignout()
   3102 	session->GetAchievementSystem().RemoveLocalUser( user );
   3103 
   3104 	if ( GetSignInManager().GetMasterLocalUser() == NULL ) {
   3105 		OnMasterLocalUserSignout();
   3106 	}
   3107 }
   3108 
   3109 /*
   3110 ========================
   3111 idSessionLocal::OnMasterLocalUserSignout
   3112 ========================
   3113 */
   3114 void idSessionLocal::OnMasterLocalUserSignout() {
   3115 	CancelSaveGameWithHandle( enumerationHandle );
   3116 	enumerationHandle = 0;
   3117 	GetSaveGameManager().GetEnumeratedSavegamesNonConst().Clear();
   3118 }
   3119 
   3120 /*
   3121 ========================
   3122 idSessionLocal::OnMasterLocalUserSignin
   3123 ========================
   3124 */
   3125 void idSessionLocal::OnMasterLocalUserSignin() {
   3126 	enumerationHandle = EnumerateSaveGamesAsync();
   3127 }
   3128 
   3129 /*
   3130 ========================
   3131 idSessionLocal::OnLocalUserProfileLoaded
   3132 ========================
   3133 */
   3134 void idSessionLocal::OnLocalUserProfileLoaded( idLocalUser * user ) {
   3135 	user->RequestSyncAchievements();
   3136 }
   3137 
   3138 /*
   3139 ========================
   3140 idSessionLocal::SetVoiceGroupsToTeams
   3141 ========================
   3142 */
   3143 void idSessionLocal::SetVoiceGroupsToTeams() {
   3144 	// move voice chat to team 
   3145 	int myTeam = 0;
   3146 	for ( int i = 0; i < GetGameLobby().GetNumLobbyUsers(); ++i ) {
   3147 		const lobbyUser_t * gameUser = GetGameLobby().GetLobbyUser( i );
   3148 
   3149 		if ( !verify( gameUser != NULL ) ) {
   3150 			continue;
   3151 		}
   3152 
   3153 		if ( gameUser->IsDisconnected() ) {
   3154 			continue;
   3155 		}
   3156 
   3157 		int userTeam = gameUser->teamNumber;
   3158 
   3159 		voiceChat->SetTalkerGroup( gameUser, GetGameLobby().lobbyType, userTeam );
   3160 
   3161 		if ( GetGameLobby().IsSessionUserIndexLocal( i ) ) {
   3162 			myTeam = userTeam;
   3163 		} 
   3164 	}
   3165 
   3166 	SetActiveChatGroup( myTeam );
   3167 }
   3168 
   3169 /*
   3170 ========================
   3171 idSessionLocal::ClearVoiceGroups
   3172 ========================
   3173 */
   3174 void idSessionLocal::ClearVoiceGroups() {
   3175 	for ( int i = 0; i < GetGameLobby().GetNumLobbyUsers(); ++i ) {
   3176 		const lobbyUser_t * gameUser = GetGameLobby().GetLobbyUser( i );
   3177 
   3178 		if ( !verify( gameUser != NULL ) ) {
   3179 			continue;
   3180 		}
   3181 
   3182 		if ( gameUser->IsDisconnected() ) {
   3183 			continue;
   3184 		}
   3185 
   3186 		voiceChat->SetTalkerGroup( gameUser, GetGameLobby().lobbyType, 0 );
   3187 	}
   3188 
   3189 	SetActiveChatGroup( 0 );
   3190 }
   3191 
   3192 /*
   3193 ========================
   3194 idSessionLocal::SendVoiceAudio
   3195 ========================
   3196 */
   3197 void idSessionLocal::SendVoiceAudio() {
   3198 	if ( voiceChat == NULL ) {
   3199 		return;
   3200 	}
   3201 
   3202 	idLobby * activeLobby = GetActivePlatformLobby();
   3203 
   3204 	int activeSessionIndex = ( activeLobby != NULL ) ? activeLobby->lobbyType : -1;
   3205 
   3206 	voiceChat->SetActiveLobby( activeSessionIndex );
   3207 	voiceChat->Pump();
   3208 
   3209 	if ( activeLobby == NULL ) {
   3210 		return;
   3211 	}
   3212 
   3213 	int time = Sys_Milliseconds();
   3214 
   3215 	const int VOICE_THROTTLE_TIME_IN_MS	= session->GetTitleStorageInt( "VOICE_THROTTLE_TIME_IN_MS", 33) ;		// Don't allow faster than 30hz send rate
   3216 
   3217 	if ( time - lastVoiceSendtime < VOICE_THROTTLE_TIME_IN_MS ) {
   3218 		return;
   3219 	}
   3220 
   3221 	lastVoiceSendtime = time;
   3222 
   3223 	idStaticList< int, MAX_PLAYERS > localTalkers;
   3224 	
   3225 	voiceChat->GetActiveLocalTalkers( localTalkers );
   3226 	
   3227 	for ( int i = 0; i < localTalkers.Num(); i++ ) {
   3228 						
   3229 		// NOTE - For 360, we don't need more than XHV_MAX_VOICECHAT_PACKETS * XHV_VOICECHAT_MODE_PACKET_SIZE bytes
   3230 		const int MAX_VDP_DATA_SIZE = 1000;
   3231 
   3232 		byte buffer[MAX_VDP_DATA_SIZE];
   3233 		
   3234 		const int titleStorageDataSize = session->GetTitleStorageInt( "MAX_VDP_DATA_SIZE", 1000 );
   3235 		const int dataSizeAvailable = Min< int >( titleStorageDataSize, sizeof( buffer ) );
   3236 
   3237 		// in-out parameter
   3238 		int dataSize = dataSizeAvailable;
   3239 		if ( !voiceChat->GetLocalChatData( localTalkers[i], buffer, dataSize ) ) {
   3240 			continue;
   3241 		}
   3242 		assert( dataSize <= sizeof( buffer ) );
   3243 
   3244 		idStaticList< const lobbyAddress_t *, MAX_PLAYERS > recipients;
   3245 		
   3246 		voiceChat->GetRecipientsForTalker( localTalkers[i], recipients );
   3247 
   3248 		for ( int j = 0; j < recipients.Num(); j++ ) {
   3249 			activeLobby->SendConnectionLess( *recipients[j], idLobby::OOB_VOICE_AUDIO, buffer, dataSize );
   3250 		}		
   3251 	}
   3252 }
   3253 
   3254 /*
   3255 ========================
   3256 idSessionLocal::HandleOobVoiceAudio
   3257 ========================
   3258 */
   3259 void idSessionLocal::HandleOobVoiceAudio( const lobbyAddress_t & from, const idBitMsg & msg ) {
   3260 
   3261 	idLobby * activeLobby = GetActivePlatformLobby();
   3262 	
   3263 	if ( activeLobby == NULL ) {
   3264 		return;
   3265 	}
   3266 
   3267 	voiceChat->SetActiveLobby( activeLobby->lobbyType );
   3268 
   3269 	voiceChat->SubmitIncomingChatData( msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() );
   3270 }
   3271 
   3272 /*
   3273 ========================
   3274 idSessionLocal::SetActiveChatGroup
   3275 ========================
   3276 */
   3277 void idSessionLocal::SetActiveChatGroup( int groupIndex ) { 
   3278 	voiceChat->SetActiveChatGroup( groupIndex );
   3279 }
   3280 
   3281 /*
   3282 ========================
   3283 idSessionLocal::GetLobbyUserVoiceState
   3284 ========================
   3285 */
   3286 voiceState_t idSessionLocal::GetLobbyUserVoiceState( lobbyUserID_t lobbyUserID ) {
   3287 	idLobby * activeLobby = GetActivePlatformLobby();
   3288 
   3289 	if ( activeLobby == NULL ) {
   3290 		return VOICECHAT_STATE_NOT_TALKING;
   3291 	}
   3292 
   3293 	const lobbyUser_t * user = activeLobby->GetLobbyUserByID( lobbyUserID );
   3294 
   3295 	if ( !verify( user != NULL ) ) {
   3296 		return VOICECHAT_STATE_NOT_TALKING;
   3297 	}
   3298 
   3299 	return voiceChat->GetVoiceState( user );
   3300 }
   3301 
   3302 /*
   3303 ========================
   3304 idSessionLocal::GetDisplayStateFromVoiceState
   3305 ========================
   3306 */
   3307 voiceStateDisplay_t idSessionLocal::GetDisplayStateFromVoiceState( voiceState_t voiceState ) const {
   3308 	if ( ( GetState() == GAME_LOBBY && MatchTypeIsLocal( GetGameLobby().GetMatchParms().matchFlags ) )
   3309 			|| ( GetState() == PARTY_LOBBY && MatchTypeIsLocal( GetPartyLobby().GetMatchParms().matchFlags ) ) ) {
   3310 		return VOICECHAT_DISPLAY_NONE;	// never show voice stuff in splitscreen
   3311 	}
   3312 
   3313 	switch ( voiceState ) {
   3314 		case VOICECHAT_STATE_MUTED_REMOTE:
   3315 		case VOICECHAT_STATE_MUTED_LOCAL:
   3316 		case VOICECHAT_STATE_MUTED_ALL:
   3317 			return VOICECHAT_DISPLAY_MUTED;
   3318 		case VOICECHAT_STATE_NOT_TALKING:
   3319 			return VOICECHAT_DISPLAY_NOTTALKING;
   3320 		case VOICECHAT_STATE_TALKING:
   3321 			return VOICECHAT_DISPLAY_TALKING;
   3322 		case VOICECHAT_STATE_TALKING_GLOBAL:
   3323 			return VOICECHAT_DISPLAY_TALKING_GLOBAL;
   3324 		case VOICECHAT_STATE_NO_MIC:
   3325 		default:
   3326 			return VOICECHAT_DISPLAY_NOTTALKING;
   3327 	}
   3328 }
   3329 
   3330 /*
   3331 ========================
   3332 idSessionLocal::ToggleLobbyUserVoiceMute
   3333 ========================
   3334 */
   3335 void idSessionLocal::ToggleLobbyUserVoiceMute( lobbyUserID_t lobbyUserID ) {
   3336 	idLobby * activeLobby = GetActivePlatformLobby();
   3337 
   3338 	if ( activeLobby == NULL ) {
   3339 		return;
   3340 	}
   3341 
   3342 	// Get the master local user
   3343 	idLocalUser * masterUser = signInManager->GetMasterLocalUser();
   3344 
   3345 	if ( masterUser == NULL ) {
   3346 		return;
   3347 	}
   3348 
   3349 	const lobbyUser_t * srcUser = activeLobby->GetLobbyUser( activeLobby->GetLobbyUserIndexByLocalUserHandle( masterUser->GetLocalUserHandle() ) );
   3350 
   3351 	if ( srcUser == NULL ) {
   3352 		return;
   3353 	}
   3354 
   3355 	const lobbyUser_t * targetUser = activeLobby->GetLobbyUserByID( lobbyUserID );
   3356 
   3357 	if ( !verify( targetUser != NULL ) ) {
   3358 		return;
   3359 	}
   3360 
   3361 	if ( srcUser == targetUser ) {
   3362 		return;		// Can't toggle yourself
   3363 	}
   3364 
   3365 	voiceChat->ToggleMuteLocal( srcUser, targetUser );
   3366 }
   3367 
   3368 /*
   3369 ========================
   3370 idSessionLocal::UpdateMasterUserHeadsetState
   3371 ========================
   3372 */
   3373 void idSessionLocal::UpdateMasterUserHeadsetState()
   3374 {
   3375 	if ( GetState() != PARTY_LOBBY && GetState() != GAME_LOBBY && GetState() != INGAME ) {
   3376 		return;
   3377 	}
   3378 
   3379 	lobbyUser_t * user = GetActivePlatformLobby()->GetSessionUserFromLocalUser( signInManager->GetMasterLocalUser() );
   3380 
   3381 	// TODO: Is this possible?
   3382 	if ( user == NULL ) {
   3383 		return;
   3384 	}
   3385 
   3386 	int talkerIndex = voiceChat->FindTalkerByUserId( user->lobbyUserID, GetActivePlatformLobby()->lobbyType );
   3387 	bool voiceChanged = voiceChat->HasHeadsetStateChanged( talkerIndex );
   3388 
   3389 	if ( voiceChanged ) {
   3390 		byte buffer[ idPacketProcessor::MAX_MSG_SIZE ];
   3391 		idBitMsg msg( buffer, sizeof( buffer ) );
   3392 		msg.WriteLong( 1 );
   3393 		user->lobbyUserID.WriteToMsg( msg );
   3394 		msg.WriteBool( voiceChat->GetHeadsetState( talkerIndex ) );
   3395 
   3396 		idLib::Printf( "Sending voicestate %d for user %d %s\n", voiceChat->GetHeadsetState( talkerIndex ), talkerIndex, user->gamertag );
   3397 
   3398 		if ( GetActivePlatformLobby()->IsHost() ) {
   3399 			for ( int p = 0; p < GetActivePlatformLobby()->peers.Num(); p++ ) {
   3400 				if ( GetActivePlatformLobby()->peers[p].IsConnected() ) {
   3401 					GetActivePlatformLobby()->QueueReliableMessage( p, idLobby::RELIABLE_HEADSET_STATE, msg.GetReadData(), msg.GetSize() );
   3402 				}
   3403 			}
   3404 
   3405 		} else {
   3406 			GetActivePlatformLobby()->QueueReliableMessage( GetActivePlatformLobby()->host, idLobby::RELIABLE_HEADSET_STATE, msg.GetReadData(), msg.GetSize() );
   3407 		}
   3408 	}
   3409 	
   3410 }
   3411 
   3412 /*
   3413 ========================
   3414 idSessionLocal::GetNumContentPackages
   3415 ========================
   3416 */
   3417 int	idSessionLocal::GetNumContentPackages() const {
   3418 	return downloadedContent.Num();
   3419 }
   3420 
   3421 /*
   3422 ========================
   3423 idSessionLocal::GetContentPackageID
   3424 ========================
   3425 */
   3426 int idSessionLocal::GetContentPackageID( int contentIndex ) const {
   3427 	assert( contentIndex < MAX_CONTENT_PACKAGES );
   3428 
   3429 	if ( downloadedContent[ contentIndex ].isMounted ) {
   3430 		return downloadedContent[ contentIndex ].dlcID;
   3431 	}
   3432 
   3433 	return 0;
   3434 }
   3435 
   3436 /*
   3437 ========================
   3438 idSessionLocal::GetContentPackagePath
   3439 ========================
   3440 */
   3441 const char * idSessionLocal::GetContentPackagePath( int contentIndex ) const {
   3442 	assert( contentIndex < MAX_CONTENT_PACKAGES );
   3443 
   3444 	if ( downloadedContent[ contentIndex ].isMounted ) {
   3445 		return downloadedContent[ contentIndex ].rootPath.c_str();
   3446 	}
   3447 
   3448 	return NULL;
   3449 }
   3450 
   3451 /*
   3452 ========================
   3453 idSessionLocal::GetContentPackageIndexForID
   3454 ========================
   3455 */
   3456 int idSessionLocal::GetContentPackageIndexForID( int contentID ) const {
   3457 	int contentIndex = -1;
   3458 
   3459 	for ( int i = 0; i < downloadedContent.Num(); i++ ) {
   3460 		if ( downloadedContent[i].dlcID == contentID ) {
   3461 			contentIndex = i;
   3462 			break;
   3463 		}
   3464 	}
   3465 
   3466 	return contentIndex;
   3467 }
   3468 
   3469 /*
   3470 ========================
   3471 idSessionLocal::SetLobbyUserRelativeScore
   3472 ========================
   3473 */
   3474 void idSessionLocal::SetLobbyUserRelativeScore( lobbyUserID_t lobbyUserID, int relativeScore, int team ) {
   3475 	// All platforms but 360 stub this out
   3476 }
   3477 
   3478 /*
   3479 ========================
   3480 idSessionLocal::ReadTitleStorage
   3481 ========================
   3482 */
   3483 void idSessionLocal::ReadTitleStorage( void * buffer, int bufferLen ) {
   3484 	// https://ps3.scedev.net/projects/ps3_sdk_docs/docs/ps3-en,NP_Lookup-Reference,sceNpLookupTitleSmallStorageAsync/1
   3485 	// If the file is not on the server, this will be handled as though a file of 0 bytes were on the server.
   3486 	// This means that 0 will be set to contentLength and 0 (for normal termination) will return for the return value.
   3487 	// This situation can occur with problems in actual operation, so the application must be designed not to hang up even in such situations
   3488 	//bufferLen = 0;
   3489 
   3490 	idLib::Printf( "ReadTitleStorage: %i bytes\n", bufferLen );
   3491 
   3492 #if !defined( ID_RETAIL ) || defined( ID_RETAIL_INTERNAL )
   3493 	if ( net_ignoreTitleStorage.GetBool() ) {//&& idLib::GetProduction() < PROD_PRODUCTION ) {
   3494 		idLib::Printf( "ReadTitleStorage: *********************** IGNORING ********************\n" );
   3495 		return;
   3496 	}
   3497 #endif
   3498 
   3499 	//idScopedGlobalHeap	everythingHereGoesInTheGlobalHeap;
   3500 
   3501 	idParser parser( LEXFL_NOERRORS | LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT );
   3502 	parser.LoadMemory( ( const char* )buffer, bufferLen, "default.tss" );
   3503 
   3504 	bool valid = true;
   3505 
   3506 	while ( true ) {
   3507 		idToken token;
   3508 
   3509 		if ( !parser.ReadToken( &token ) ) {
   3510 			break;
   3511 		}
   3512 
   3513 		if ( token.Icmp( "netvars" ) == 0 ) {
   3514 			if ( !titleStorageVars.Parse( parser ) ) {
   3515 				valid = false;
   3516 				break;
   3517 			}
   3518 		} else {
   3519 			valid = false;
   3520 			break;
   3521 		}
   3522 	}
   3523 
   3524 	if ( valid ) {
   3525 		titleStorageLoaded = true;
   3526 		idLib::Printf( "ReadTitleStorage: SUCCESS\n" );
   3527 		titleStorageVars.Print();
   3528 	} else {
   3529 		titleStorageLoaded = false;
   3530 		idLib::Printf( "ReadTitleStorage: FAILED\n" );
   3531 		titleStorageVars.Clear();
   3532 	}
   3533 }
   3534 
   3535 /*
   3536 ========================
   3537 idSessionLocal::ReadDLCInfo
   3538 ========================
   3539 */
   3540 bool idSessionLocal::ReadDLCInfo( idDict & dlcInfo, void * buffer, int bufferLen ) {
   3541 	idParser parser( LEXFL_NOERRORS | LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT );
   3542 	parser.LoadMemory( ( const char* )buffer, bufferLen, "info.txt" );
   3543 
   3544 	bool valid = true;
   3545 
   3546 	while ( true ) {
   3547 		idToken token;
   3548 
   3549 		if ( !parser.ReadToken( &token ) ) {
   3550 			break;
   3551 		}
   3552 
   3553 		if ( token.Icmp( "dlcInfo" ) == 0 ) {
   3554 			if ( !dlcInfo.Parse( parser ) ) {
   3555 				valid = false;
   3556 				break;
   3557 			}
   3558 		} else {
   3559 			valid = false;
   3560 			break;
   3561 		}
   3562 	}
   3563 
   3564 	return valid;
   3565 }
   3566 
   3567 /*
   3568 ========================
   3569 idSessionLocal::IsPlatformPartyInLobby
   3570 ========================
   3571 */
   3572 bool idSessionLocal::IsPlatformPartyInLobby() {
   3573 	idLocalUser * user = session->GetSignInManager().GetMasterLocalUser();
   3574 	idLobby * lobby = GetActivePlatformLobby();
   3575 
   3576 	if ( user == NULL || lobby == NULL ) {
   3577 		return false;
   3578 	}
   3579 
   3580 	if ( user->GetPartyCount() > MAX_PLAYERS || user->GetPartyCount() < 2 ) {
   3581 		return false;
   3582 	}
   3583 
   3584 	// TODO: Implement PC
   3585 	return false;
   3586 }
   3587 
   3588 /*
   3589 ========================
   3590 idSessionLocal::PrePickNewHost
   3591 This is called when we have determined that we need to pick a new host.
   3592 Call PickNewHostInternal to continue on with the host picking process.
   3593 ========================
   3594 */
   3595 void idSessionLocal::PrePickNewHost( idLobby & lobby, bool forceMe, bool inviteOldHost ) {
   3596 	NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: (%s)\n", lobby.GetLobbyName() );
   3597 
   3598 	if ( GetActivePlatformLobby() == NULL ) {
   3599 		NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: GetActivePlatformLobby() == NULL (%s)\n", lobby.GetLobbyName() );
   3600 		return;
   3601 	}
   3602 
   3603 	// Check to see if we can migrate AT ALL
   3604 	// This is checking for coop, we should make this a specific option (MATCH_ALLOW_MIGRATION)
   3605 	if ( GetPartyLobby().parms.matchFlags & MATCH_PARTY_INVITE_PLACEHOLDER ) {
   3606 		NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: MATCH_PARTY_INVITE_PLACEHOLDER (%s)\n", lobby.GetLobbyName() );
   3607 
   3608 		// Can't migrate, shut both lobbies down, and create a new match using the original parms
   3609 		GetGameStateLobby().Shutdown();
   3610 		GetGameLobby().Shutdown();
   3611 		GetPartyLobby().Shutdown();
   3612 		
   3613 		// Throw up the appropriate dialog message so the player knows what happeend
   3614 		if ( localState >= idSessionLocal::STATE_LOADING ) {
   3615 			NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: localState >= idSessionLocal::STATE_LOADING (%s)\n", lobby.GetLobbyName() );
   3616 			common->Dialog().AddDialog( GDM_BECAME_HOST_GAME_STATS_DROPPED, DIALOG_ACCEPT, NULL, NULL, false, __FUNCTION__, __LINE__, true );
   3617 		} else {
   3618 			NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: localState < idSessionLocal::STATE_LOADING (%s)\n", lobby.GetLobbyName() );
   3619 			common->Dialog().AddDialog( GDM_LOBBY_BECAME_HOST_GAME, DIALOG_ACCEPT, NULL, NULL, false, __FUNCTION__, __LINE__, true  );
   3620 		}
   3621 
   3622 		CreateMatch( GetActivePlatformLobby()->parms );
   3623 
   3624 		return;
   3625 	}
   3626 
   3627 	// Check to see if the match is searchable
   3628 	if ( GetState() >= idSession::GAME_LOBBY && MatchTypeIsSearchable( GetGameLobby().parms.matchFlags ) ) {
   3629 		NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: MatchTypeIsSearchable (%s)\n", lobby.GetLobbyName() );
   3630 		// Searchable games migrate lobbies independently, and don't need to stay in sync
   3631 		lobby.PickNewHostInternal( forceMe, inviteOldHost );	
   3632 		return;
   3633 	}
   3634 
   3635 	//
   3636 	// Beyond this point, game lobbies must be sync'd with party lobbies as far as host status
   3637 	// So to enforce that, we pull you out of the game lobby if you are in one when migration occurs
   3638 	//
   3639 
   3640 	// Check to see if we should go back to a party lobby
   3641 	if ( GetBackState() >= idSessionLocal::PARTY_LOBBY || GetState() == idSession::PARTY_LOBBY ) {
   3642 		NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: GetBackState() >= idSessionLocal::PARTY_LOBBY || GetState() == idSession::PARTY_LOBBY (%s)\n", lobby.GetLobbyName() );
   3643 		// Force the party lobby to start picking a new host if we lost the game lobby host
   3644 		GetPartyLobby().PickNewHostInternal( forceMe, inviteOldHost );
   3645 
   3646 		// End the game lobby, and go back to party lobby
   3647 		GetGameStateLobby().Shutdown();
   3648 		GetGameLobby().Shutdown();
   3649 		SetState( GetPartyLobby().IsHost() ? idSessionLocal::STATE_PARTY_LOBBY_HOST : idSessionLocal::STATE_PARTY_LOBBY_PEER );
   3650 	} else {
   3651 		NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: GetBackState() < idSessionLocal::PARTY_LOBBY && GetState() != idSession::PARTY_LOBBY (%s)\n", lobby.GetLobbyName() );
   3652 		if ( localState >= idSessionLocal::STATE_LOADING ) {
   3653 			common->Dialog().AddDialog( GDM_HOST_QUIT, DIALOG_ACCEPT, NULL, NULL, false, __FUNCTION__, __LINE__, true  );		// The host has quit the session. Returning to the main menu.
   3654 		}
   3655 
   3656 		// Go back to main menu
   3657 		GetGameLobby().Shutdown();
   3658 		GetGameStateLobby().Shutdown();
   3659 		GetPartyLobby().Shutdown();
   3660 		SetState( idSessionLocal::STATE_IDLE );
   3661 	}
   3662 }
   3663 /*
   3664 ========================
   3665 idSessionLocal::PreMigrateInvite
   3666 This is called just before we get invited to a migrated session
   3667 If we return false, the invite will be ignored
   3668 ========================
   3669 */
   3670 bool idSessionLocal::PreMigrateInvite( idLobby & lobby )
   3671 {
   3672 	if ( GetActivePlatformLobby() == NULL ) {
   3673 		return false;
   3674 	}
   3675 
   3676 	// Check to see if we can migrate AT ALL
   3677 	// This is checking for coop, we should make this a specific option (MATCH_ALLOW_MIGRATION)
   3678 	if ( !verify( ( GetPartyLobby().parms.matchFlags & MATCH_PARTY_INVITE_PLACEHOLDER ) == 0 ) ) {
   3679 		return false;	// Shouldn't get invites for coop (we should make this a specific option (MATCH_ALLOW_MIGRATION))
   3680 	}
   3681 
   3682 	// Check to see if the match is searchable
   3683 	if ( MatchTypeIsSearchable( GetGameLobby().parms.matchFlags ) ) {
   3684 		// Searchable games migrate lobbies independently, and don't need to stay in sync
   3685 		return true;	
   3686 	}
   3687 
   3688 	//
   3689 	// Beyond this point, game lobbies must be sync'd with party lobbies as far as host status
   3690 	// So to enforce that, we pull you out of the game lobby if you are in one when migration occurs
   3691 	//
   3692 
   3693 	if ( lobby.lobbyType != idLobby::TYPE_PARTY ) {
   3694 		return false;		// We shouldn't be getting invites from non party lobbies when in a non searchable game
   3695 	}
   3696 
   3697 	// Non placeholder Party lobbies can always migrate
   3698 	if ( GetBackState() >= idSessionLocal::PARTY_LOBBY ) {
   3699 		// Non searchable games go back to the party lobby
   3700 		GetGameLobby().Shutdown();
   3701 		SetState( GetPartyLobby().IsHost() ? idSessionLocal::STATE_PARTY_LOBBY_HOST : idSessionLocal::STATE_PARTY_LOBBY_PEER );
   3702 	}
   3703 
   3704 	return true;	// Non placeholder Party lobby invites joinable
   3705 }
   3706 
   3707 /*
   3708 ================================================================================================
   3709 lobbyAddress_t
   3710 ================================================================================================
   3711 */
   3712 
   3713 /*
   3714 ========================
   3715 lobbyAddress_t::lobbyAddress_t
   3716 ========================
   3717 */
   3718 lobbyAddress_t::lobbyAddress_t() {
   3719 	memset( &netAddr, 0, sizeof( netAddr ) );
   3720 	netAddr.type = NA_BAD;
   3721 }
   3722 
   3723 /*
   3724 ========================
   3725 lobbyAddress_t::InitFromIPandPort
   3726 ========================
   3727 */
   3728 void lobbyAddress_t::InitFromIPandPort( const char * ip, int port ) {
   3729 	Sys_StringToNetAdr( ip, &netAddr, true );
   3730 	if ( !netAddr.port ) {
   3731 		netAddr.port = port;
   3732 	}
   3733 }
   3734 
   3735 
   3736 /*
   3737 ========================
   3738 lobbyAddress_t::InitFromNetadr
   3739 ========================
   3740 */
   3741 void lobbyAddress_t::InitFromNetadr( const netadr_t & netadr ) {
   3742 	assert( netadr.type != NA_BAD );
   3743 	netAddr = netadr;
   3744 }
   3745 
   3746 /*
   3747 ========================
   3748 lobbyAddress_t::ToString
   3749 ========================
   3750 */
   3751 const char * lobbyAddress_t::ToString() const {
   3752 	return Sys_NetAdrToString( netAddr );
   3753 }
   3754 
   3755 /*
   3756 ========================
   3757 lobbyAddress_t::UsingRelay
   3758 ========================
   3759 */
   3760 bool lobbyAddress_t::UsingRelay() const {
   3761 	return false;
   3762 }
   3763 
   3764 /*
   3765 ========================
   3766 lobbyAddress_t::Compare
   3767 ========================
   3768 */
   3769 bool lobbyAddress_t::Compare( const lobbyAddress_t & addr, bool ignoreSessionCheck ) const {
   3770 	return Sys_CompareNetAdrBase( netAddr, addr.netAddr );
   3771 }
   3772 
   3773 /*
   3774 ========================
   3775 lobbyAddress_t::WriteToMsg
   3776 ========================
   3777 */
   3778 void lobbyAddress_t::WriteToMsg( idBitMsg & msg ) const {
   3779 	msg.WriteData( &netAddr, sizeof( netAddr ) );
   3780 }
   3781 
   3782 /*
   3783 ========================
   3784 lobbyAddress_t::ReadFromMsg
   3785 ========================
   3786 */
   3787 void lobbyAddress_t::ReadFromMsg( idBitMsg & msg ) {
   3788 	msg.ReadData( &netAddr, sizeof( netAddr ) );
   3789 }
   3790 
   3791 /*
   3792 ================================================================================================
   3793 idNetSessionPort
   3794 ================================================================================================
   3795 */
   3796 
   3797 /*
   3798 ========================
   3799 idNetSessionPort::idNetSessionPort
   3800 ========================
   3801 */
   3802 idNetSessionPort::idNetSessionPort() :
   3803 	forcePacketDropPrev( 0.0f ),
   3804 	forcePacketDropCurr( 0.0f )
   3805 {
   3806 }
   3807 
   3808 /*
   3809 ========================
   3810 idNetSessionPort::InitPort
   3811 ========================
   3812 */
   3813 bool idNetSessionPort::InitPort( int portNumber, bool useBackend ) {
   3814 	return UDP.InitForPort( portNumber );
   3815 }
   3816 
   3817 /*
   3818 ========================
   3819 idNetSessionPort::ReadRawPacket
   3820 ========================
   3821 */
   3822 bool idNetSessionPort::ReadRawPacket( lobbyAddress_t & from, void * data, int & size, int maxSize  ) {
   3823 	bool result = UDP.GetPacket( from.netAddr, data, size, maxSize );
   3824 	
   3825 	static idRandom2 random( Sys_Milliseconds() );
   3826 	if ( net_forceDrop.GetInteger() != 0 ) {
   3827 		forcePacketDropCurr = random.RandomInt( 100 );		
   3828 		if ( net_forceDrop.GetInteger() >= forcePacketDropCurr ) {
   3829 			return false;
   3830 		}
   3831 	}
   3832 
   3833 	return result;
   3834 }
   3835 
   3836 /*
   3837 ========================
   3838 idNetSessionPort::SendRawPacket
   3839 ========================
   3840 */
   3841 void idNetSessionPort::SendRawPacket( const lobbyAddress_t & to, const void * data, int size ) {
   3842 	static idRandom2 random( Sys_Milliseconds() );
   3843 	if ( net_forceDrop.GetInteger() != 0 && net_forceDrop.GetInteger() >= random.RandomInt( 100 ) ) {
   3844 		return;
   3845 	}
   3846 	assert( size <= idPacketProcessor::MAX_FINAL_PACKET_SIZE );
   3847 	
   3848 	UDP.SendPacket( to.netAddr, data, size );
   3849 }
   3850 
   3851 /*
   3852 ========================
   3853 idNetSessionPort::IsOpen
   3854 ========================
   3855 */
   3856 bool idNetSessionPort::IsOpen() {
   3857 	return UDP.IsOpen();
   3858 }
   3859 
   3860 /*
   3861 ========================
   3862 idNetSessionPort::Close
   3863 ========================
   3864 */
   3865 void idNetSessionPort::Close() {
   3866 	UDP.Close();
   3867 }
   3868 
   3869 /*
   3870 ================================================================================================
   3871 Commands
   3872 ================================================================================================
   3873 */
   3874 
   3875 //====================================================================================
   3876 
   3877 CONSOLE_COMMAND( voicechat_mute, "TEMP", 0 ) {
   3878 	if ( args.Argc() != 2 ) {
   3879 		idLib::Printf( "Usage: voicechat_mute <user index>\n" );
   3880 		return;
   3881 	}
   3882 
   3883 	int i = atoi( args.Argv( 1 ) );
   3884 	session->ToggleLobbyUserVoiceMute( session->GetActivePlatformLobbyBase().GetLobbyUserIdByOrdinal( i ) );
   3885 }
   3886 
   3887 /*
   3888 ========================
   3889 force_disconnect_all
   3890 ========================
   3891 */
   3892 CONSOLE_COMMAND( force_disconnect_all, "force disconnect on all users", 0 ) {
   3893 	session->GetSignInManager().RemoveAllLocalUsers();
   3894 }
   3895 
   3896 /*
   3897 ========================
   3898 void Net_DebugOutputSignedInUsers_f
   3899 ========================
   3900 */
   3901 void Net_DebugOutputSignedInUsers_f( const idCmdArgs &args ) {
   3902 	session->GetSignInManager().DebugOutputLocalUserInfo();
   3903 }
   3904 idCommandLink Net_DebugOutputSignedInUsers( "net_debugOutputSignedInUsers", Net_DebugOutputSignedInUsers_f, "Outputs all the local users and other debugging information from the sign in manager" );
   3905 
   3906 /*
   3907 ========================
   3908 void Net_RemoveUserFromLobby_f
   3909 ========================
   3910 */
   3911 void Net_RemoveUserFromLobby_f( const idCmdArgs &args ) {
   3912 	if ( args.Argc() > 1 ) {
   3913 		int localUserNum = atoi( args.Argv( 1 ) );
   3914 		if ( localUserNum < session->GetSignInManager().GetNumLocalUsers() ) {
   3915 			session->GetSignInManager().RemoveLocalUserByIndex( localUserNum );
   3916 		} else {
   3917 			idLib::Printf( "This user is not in the lobby\n" );
   3918 		}
   3919 	} else {
   3920 		idLib::Printf( "Usage: net_RemoveUserFromLobby <localUserNum>\n" );
   3921 	}
   3922 }
   3923 
   3924 idCommandLink Net_RemoveUserFromLobby( "net_removeUserFromLobby", Net_RemoveUserFromLobby_f, "Removes the given user from the lobby" );
   3925 
   3926 /*
   3927 ========================
   3928 Net_dropClient
   3929 ========================
   3930 */
   3931 CONSOLE_COMMAND( Net_DropClient, "Drop a client", 0 ) {
   3932 	if ( args.Argc() < 3 ) {
   3933 		idLib::Printf( "usage: Net_DropClient <clientnum> [<session>] 0/default: drop from game, 1: drop from party, otherwise drop from both\n" );
   3934 		return;
   3935 	}
   3936 	int lobbyType = 0;
   3937 	if ( args.Argc() > 2 ) {
   3938 		lobbyType = atoi( args.Argv( 2 ) );
   3939 	}
   3940 	session->DropClient( atoi( args.Argv(1) ), lobbyType ); 
   3941 }
   3942 
   3943 /*
   3944 ========================
   3945 idSessionLocal::DropClient
   3946 ========================
   3947 */
   3948 void idSessionLocal::DropClient( int peerNum, int session ) {
   3949 	if ( session == 1 || session >= 2 ) {
   3950 		GetPartyLobby().DisconnectPeerFromSession( peerNum );
   3951 	}
   3952 	if ( session == 0 || session >= 2 ) {
   3953 		GetGameLobby().DisconnectPeerFromSession( peerNum );
   3954 	}
   3955 }
   3956 
   3957 /*
   3958 ========================
   3959 idSessionLocal::ListServersCommon
   3960 ========================
   3961 */
   3962 void idSessionLocal::ListServersCommon() { 
   3963 	netadr_t broadcast;
   3964 	memset( &broadcast, 0, sizeof( broadcast ) );
   3965 	broadcast.type = NA_BROADCAST;
   3966 	broadcast.port = net_port.GetInteger();
   3967 		
   3968 	lobbyAddress_t address;
   3969 	address.InitFromNetadr( broadcast );
   3970 
   3971 	byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ];
   3972 	idBitMsg msg( buffer, sizeof( buffer ) );
   3973 				
   3974 	// Add the current version info to the query
   3975 	const unsigned long localChecksum = NetGetVersionChecksum();
   3976 
   3977 	NET_VERBOSE_PRINT( "ListServers: Hash checksum: %i, broadcasting to: %s\n", localChecksum, address.ToString() );
   3978 
   3979 	msg.WriteLong( localChecksum );
   3980 
   3981 	GetPort();
   3982 	// Send the query as a broadcast
   3983 	GetPartyLobby().SendConnectionLess( address, idLobby::OOB_MATCH_QUERY, msg.GetReadData(), msg.GetSize() );	
   3984 }
   3985 
   3986 /*
   3987 ========================
   3988 idSessionLocal::HandleDedicatedServerQueryRequest
   3989 ========================
   3990 */
   3991 void idSessionLocal::HandleDedicatedServerQueryRequest( lobbyAddress_t & remoteAddr, idBitMsg & msg, int msgType ) {
   3992 	NET_VERBOSE_PRINT( "HandleDedicatedServerQueryRequest from %s\n", remoteAddr.ToString() );
   3993 	
   3994 	bool canJoin = true;
   3995 	
   3996 	const unsigned long localChecksum = NetGetVersionChecksum();
   3997 	const unsigned long remoteChecksum = msg.ReadLong();
   3998 
   3999 	if ( remoteChecksum != localChecksum ) {
   4000 		NET_VERBOSE_PRINT( "HandleServerQueryRequest: Invalid version from %s\n", remoteAddr.ToString() );
   4001 		canJoin = false;
   4002 	}
   4003 
   4004 	// Make sure we are the host of this party session
   4005 	if ( !GetPartyLobby().IsHost() ) {
   4006 		NET_VERBOSE_PRINT( "HandleServerQueryRequest: Not host of party\n" );
   4007 		canJoin = false;
   4008 	}
   4009 
   4010 	// Make sure there is a session active
   4011 	if ( GetActivePlatformLobby() == NULL ) {
   4012 		canJoin = false;
   4013 	}
   4014 
   4015 	// Make sure we have enough free slots
   4016 	if ( GetPartyLobby().NumFreeSlots() == 0 || GetGameLobby().NumFreeSlots() == 0 ) {
   4017 		NET_VERBOSE_PRINT( "No free slots\n" );
   4018 		canJoin = false;
   4019 	}
   4020 	
   4021 	if ( MatchTypeInviteOnly( GetPartyLobby().parms.matchFlags ) ) {
   4022 		canJoin = false;
   4023 	} 
   4024 
   4025 	// Buffer to hold reply msg
   4026 	byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ];
   4027 	idBitMsg retmsg( buffer, sizeof( buffer ) );
   4028 	
   4029 	idLocalUser * masterUser = GetSignInManager().GetMasterLocalUser();
   4030 
   4031 	if ( masterUser == NULL && !net_headlessServer.GetBool() ) {
   4032 		canJoin = false;
   4033 	}
   4034 	
   4035 	// Send the info about this game session to the caller
   4036 	retmsg.WriteBool( canJoin );
   4037 	
   4038 	if ( canJoin ) {
   4039 		serverInfo_t serverInfo;
   4040 		serverInfo.joinable = ( session->GetState() >= idSession::LOADING );
   4041 
   4042 		if ( !net_headlessServer.GetBool() ) {
   4043 			serverInfo.serverName = masterUser->GetGamerTag();
   4044 		}
   4045 		
   4046 		if ( GetGameLobby().IsLobbyActive() ) {
   4047 			serverInfo.gameMap = GetGameLobby().parms.gameMap;
   4048 			serverInfo.gameMode = GetGameLobby().parms.gameMode;
   4049 		} else {
   4050 			serverInfo.gameMode = -1;
   4051 		}
   4052 
   4053 		serverInfo.numPlayers = GetActivePlatformLobby()->GetNumLobbyUsers();
   4054 		serverInfo.maxPlayers = GetActivePlatformLobby()->parms.numSlots;
   4055 		serverInfo.Write( retmsg );
   4056 
   4057 		for ( int i = 0; i < GetActivePlatformLobby()->GetNumLobbyUsers(); i++ ) {
   4058 			retmsg.WriteString( GetActivePlatformLobby()->GetLobbyUserName( GetActivePlatformLobby()->GetLobbyUserIdByOrdinal( i ) ) );
   4059 		}
   4060 	}
   4061 
   4062 	// Send it
   4063 	GetPartyLobby().SendConnectionLess( remoteAddr, idLobby::OOB_MATCH_QUERY_ACK, retmsg.GetReadData(), retmsg.GetSize() );		
   4064 }
   4065 
   4066 /*
   4067 ========================
   4068 idSessionLocal::HandleDedicatedServerQueryAck
   4069 ========================
   4070 */
   4071 void idSessionLocal::HandleDedicatedServerQueryAck( lobbyAddress_t & remoteAddr, idBitMsg & msg ) {
   4072 	NET_VERBOSE_PRINT( "HandleDedicatedServerQueryAck from %s\n", remoteAddr.ToString() );
   4073 	dedicatedServerSearch->HandleQueryAck( remoteAddr, msg );
   4074 }
   4075 
   4076 /*
   4077 ========================
   4078 idSessionLocal::ServerPlayerList
   4079 ========================
   4080 */
   4081 const idList< idStr > * idSessionLocal::ServerPlayerList( int i ) {
   4082 	return NULL;
   4083 }
   4084 
   4085 /*
   4086 ========================
   4087 lobbyUserID_t::Serialize
   4088 ========================
   4089 */
   4090 void lobbyUserID_t::Serialize( idSerializer & ser ) {
   4091 	localUserHandle.Serialize( ser );
   4092 	ser.Serialize( lobbyType );
   4093 }