//===== Copyright © 1996-2009, Valve Corporation, All rights reserved. ======// // // Purpose: // //===========================================================================// #include "mm_framework.h" #include "vstdlib/random.h" #include "fmtstr.h" #include "steam_datacenterjobs.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" ConVar mm_teamsearch_errortime( "mm_teamsearch_errortime", "3.0", FCVAR_DEVELOPMENTONLY, "Time team search is in error state until it self-cancels" ); ConVar mm_teamsearch_nostart( "mm_teamsearch_nostart", "0", FCVAR_DEVELOPMENTONLY, "Team search will fake cancel before searching for server" ); #ifndef _GAMECONSOLE ConVar sv_search_team_key( "sv_search_team_key", "public", FCVAR_RELEASE, "When initiating team search, set this key to match with known opponents team" ); #endif // // // Specialized implementation of SysSessions for team search // // template < typename TBaseSession > class CSysSessionStubForTeamSearch : public TBaseSession { public: explicit CSysSessionStubForTeamSearch( KeyValues *pSettings, CMatchSessionOnlineTeamSearch *pMatchSession ) : TBaseSession( pSettings ), m_pMatchSession( pMatchSession ) { } protected: virtual void OnSessionEvent( KeyValues *notify ) { if ( !notify ) return; if ( m_pMatchSession ) m_pMatchSession->OnSessionEvent( notify ); notify->deleteThis(); } protected: // No voice virtual void Voice_ProcessTalkers( KeyValues *pMachine, bool bAdd ) {} virtual void Voice_CaptureAndTransmitLocalVoiceData() {} virtual void Voice_Playback( KeyValues *msg, XUID xuidSrc ) OVERRIDE {} virtual void Voice_UpdateLocalHeadsetsStatus() {} virtual void Voice_UpdateMutelist() {} protected: // No p2p connections, just two hosts talking virtual void XP2P_Interconnect() {} protected: CMatchSessionOnlineTeamSearch *m_pMatchSession; }; typedef CSysSessionStubForTeamSearch< CSysSessionClient > CSysTeamSearchClient; typedef CSysSessionStubForTeamSearch< CSysSessionHost > CSysTeamSearchHost; // // // CMatchSessionOnlineTeamSearch implementation // // CMatchSessionOnlineTeamSearch::CMatchSessionOnlineTeamSearch( KeyValues *pSettings, CMatchSessionOnlineHost *pHost ) : m_pHostSession( pHost ), m_eState( STATE_SEARCHING ), m_pSysSessionHost( NULL ), m_pSysSessionClient( NULL ), m_pDsSearcher( NULL ), m_flActionTime( 0.0f ), m_pUpdateHostSessionPacket( NULL ), m_autodelete_pUpdateHostSessionPacket( m_pUpdateHostSessionPacket ), m_iLinkState( 0 ), m_xuidLinkPeer( 0ull ), #ifdef _X360 m_pXlspConnection( NULL ), m_pXlspCommandBatch( NULL ), #endif m_flCreationTime( Plat_FloatTime() ) { m_pSettings = pSettings->MakeCopy(); m_autodelete_pSettings.Assign( m_pSettings ); #ifndef _GAMECONSOLE m_pSettings->SetString( "options/searchteamkey", sv_search_team_key.GetString() ); #endif if ( IsPC() ) { // On PC limit server selection for team games to official and listen if ( Q_stricmp( m_pSettings->GetString( "options/server" ), "listen" ) ) m_pSettings->SetString( "options/server", "official" ); } DevMsg( "Created CMatchSessionOnlineTeamSearch:\n" ); KeyValuesDumpAsDevMsg( m_pSettings, 1 ); } CMatchSessionOnlineTeamSearch::~CMatchSessionOnlineTeamSearch() { DevMsg( "Destroying CMatchSessionOnlineTeamSearch.\n" ); } CMatchSearcher * CMatchSessionOnlineTeamSearch::OnStartSearching() { return new CMatchSearcher_OnlineTeamSearch( this, m_pSettings->MakeCopy() ); } CMatchSearcher_OnlineTeamSearch::CMatchSearcher_OnlineTeamSearch( CMatchSessionOnlineTeamSearch *pSession, KeyValues *pSettings ) : CMatchSearcher_OnlineSearch( pSession, pSettings ) { // Search settings cannot be locked as it will interfere with syssessions m_pSettings->SetString( "system/lock", "" ); // Team versus searchable lobbies are public m_pSettings->SetString( "system/access", "public" ); // If we happen to host the session it will be a special teamlink session m_pSettings->SetString( "system/netflag", "teamlink" ); if ( IMatchSession *pMainSession = g_pMatchFramework->GetMatchSession() ) { KeyValues *kvSystemData = pMainSession->GetSessionSystemData(); m_pSettings->SetUint64( "System/dependentlobby", kvSystemData->GetUint64( "xuidReserve" ) ); } // Double the number of slots since we assume two teams playing m_pSettings->SetInt( "Members/numSlots", g_pMatchFramework->GetMatchTitle()->GetTotalNumPlayersSupported() ); // Let the title extend the game settings g_pMMF->GetMatchTitleGameSettingsMgr()->InitializeGameSettings( m_pSettings, "search_onlineteam" ); DevMsg( "CMatchSearcher_OnlineTeamSearch title adjusted settings:\n" ); KeyValuesDumpAsDevMsg( m_pSettings, 1 ); } void CMatchSearcher_OnlineTeamSearch::StartSearchPass( KeyValues *pSearchPass ) { #ifndef _GAMECONSOLE pSearchPass->SetString( "Filter=/options:searchteamkey", sv_search_team_key.GetString() ); #endif CMatchSearcher_OnlineSearch::StartSearchPass( pSearchPass ); } void CMatchSessionOnlineTeamSearch::OnSearchEvent( KeyValues *pNotify ) { if ( !pNotify ) return; if ( m_pHostSession ) m_pHostSession->OnEvent( pNotify ); pNotify->deleteThis(); } void CMatchSessionOnlineTeamSearch::OnSessionEvent( KeyValues *pEvent ) { OnEvent( pEvent ); // Searching state is handled entirely by the base class if ( m_eState == STATE_SEARCHING ) return; char const *szEvent = pEvent->GetName(); if ( !Q_stricmp( "mmF->SysSessionUpdate", szEvent ) ) { if ( m_pSysSessionHost && pEvent->GetPtr( "syssession", NULL ) == m_pSysSessionHost ) { // We had a session error if ( char const *szError = pEvent->GetString( "error", NULL ) ) { // Destroy the session m_pSysSessionHost->Destroy(); m_pSysSessionHost = NULL; // Handle error m_eState = STATE_ERROR; m_flActionTime = Plat_FloatTime() + mm_teamsearch_errortime.GetFloat(); OnSearchEvent( new KeyValues( "OnMatchSessionUpdate", "state", "progress", "progress", "searcherror" ) ); return; } // This is our session switch ( m_eState ) { case STATE_CREATING: // Session created successfully and we are awaiting peer m_eState = STATE_AWAITING_PEER; OnSearchEvent( new KeyValues( "OnMatchSessionUpdate", "state", "progress", "progress", "searchawaitingpeer" ) ); break; } } } else if ( !Q_stricmp( "mmF->SysSessionCommand", szEvent ) ) { if ( m_pSysSessionHost && pEvent->GetPtr( "syssession", NULL ) == m_pSysSessionHost ) { KeyValues *pCommand = pEvent->GetFirstTrueSubKey(); if ( pCommand ) { OnRunSessionCommand( pCommand ); } } if ( m_pSysSessionClient && pEvent->GetPtr( "syssession", NULL ) == m_pSysSessionClient ) { KeyValues *pCommand = pEvent->GetFirstTrueSubKey(); if ( pCommand ) { OnRunSessionCommand( pCommand ); } } } else if ( !Q_stricmp( "OnPlayerMachinesConnected", szEvent ) ) { // Another team is challenging us m_eState = STATE_LINK_HOST; m_xuidLinkPeer = pEvent->GetUint64( "id" ); OnSearchEvent( new KeyValues( "OnMatchSessionUpdate", "state", "progress", "progress", "searchlinked" ) ); // Lock the session to prevent other teams challenges m_pSettings->SetString( "system/lock", "linked" ); m_pSysSessionHost->OnUpdateSessionSettings( KeyValues::AutoDeleteInline( KeyValues::FromString( "update", " update { " " system { " " lock linked " " } " " } " ) ) ); LinkHost().LinkInit(); } else if ( !Q_stricmp( "OnPlayerRemoved", szEvent ) ) { // Assert( m_xuidLinkPeer == pEvent->GetUint64( "xuid" ) ); // the peer gave up before we finished negotiation, start the process all over ResetAndRestartTeamSearch(); } } void CMatchSessionOnlineTeamSearch::ResetAndRestartTeamSearch() { m_eState = STATE_ERROR; m_flActionTime = 0.0f; OnSearchEvent( new KeyValues( "OnMatchSessionUpdate", "state", "progress", "progress", "restart" ) ); } void CMatchSessionOnlineTeamSearch::RememberHostSessionUpdatePacket( KeyValues *pPacket ) { if ( m_pUpdateHostSessionPacket ) m_pUpdateHostSessionPacket->deleteThis(); m_pUpdateHostSessionPacket = pPacket; m_autodelete_pUpdateHostSessionPacket.Assign( m_pUpdateHostSessionPacket ); } void CMatchSessionOnlineTeamSearch::ApplyHostSessionUpdatePacket() { if ( m_pUpdateHostSessionPacket ) { KeyValues *pUpdatePkt = m_pUpdateHostSessionPacket; m_pUpdateHostSessionPacket = NULL; m_autodelete_pUpdateHostSessionPacket.Assign( NULL ); m_pHostSession->UpdateSessionSettings( pUpdatePkt ); pUpdatePkt->deleteThis(); } } void CMatchSessionOnlineTeamSearch::OnRunSessionCommand( KeyValues *pCommand ) { switch ( m_eState ) { case STATE_LINK_HOST: LinkHost().LinkCommand( pCommand ); break; case STATE_LINK_CLIENT: LinkClient().LinkCommand( pCommand ); break; } } CMatchSessionOnlineTeamSearchLinkHost & CMatchSessionOnlineTeamSearch::LinkHost() { return static_cast< CMatchSessionOnlineTeamSearchLinkHost & >( *this ); } CMatchSessionOnlineTeamSearchLinkClient & CMatchSessionOnlineTeamSearch::LinkClient() { return static_cast< CMatchSessionOnlineTeamSearchLinkClient & >( *this ); } CSysSessionBase * CMatchSessionOnlineTeamSearch::LinkSysSession() { switch ( m_eState ) { case STATE_LINK_HOST: return m_pSysSessionHost; case STATE_LINK_CLIENT: return m_pSysSessionClient; default: Assert( !m_pSysSessionClient || !m_pSysSessionHost ); if ( m_pSysSessionHost ) return m_pSysSessionHost; else if ( m_pSysSessionClient ) return m_pSysSessionClient; else return NULL; } } CSysSessionClient * CMatchSessionOnlineTeamSearch::OnBeginJoiningSearchResult() { return new CSysTeamSearchClient( m_pSettings, this ); } void CMatchSessionOnlineTeamSearch::OnSearchCompletedSuccess( CSysSessionClient *pSysSession, KeyValues *pSettings ) { // Push the settings back into searcher since we are going to be using them m_pSettings = pSettings; m_autodelete_pSettings.Assign( m_pSettings ); m_pSysSessionClient = pSysSession; // Established connection with another team m_eState = STATE_LINK_CLIENT; m_xuidLinkPeer = pSysSession->GetHostXuid(); OnSearchEvent( new KeyValues( "OnMatchSessionUpdate", "state", "progress", "progress", "searchlinked" ) ); LinkClient().LinkInit(); } void CMatchSessionOnlineTeamSearch::OnSearchCompletedEmpty( KeyValues *pSettings ) { // Push the settings back into searcher since we are going to be searching again m_pSettings = pSettings; m_autodelete_pSettings.Assign( m_pSettings ); // Idle out for some time m_eState = STATE_CREATING; // Notify everybody that our search idled out OnSearchEvent( new KeyValues( "OnMatchSessionUpdate", "state", "progress", "progress", "searchidle" ) ); // Allocate the hosting object to accept matches m_pSysSessionHost = new CSysTeamSearchHost( m_pSettings, this ); } void CMatchSessionOnlineTeamSearch::DebugPrint() { DevMsg( "CMatchSessionOnlineTeamSearch::CMatchSessionOnlineSearch\n" ); CMatchSessionOnlineSearch::DebugPrint(); DevMsg( "CMatchSessionOnlineTeamSearch [ state=%d ]\n", m_eState ); DevMsg( " linkstate: %d\n", m_iLinkState ); DevMsg( " linkpeer: %llx\n", m_xuidLinkPeer ); DevMsg( " actiontime:%.3f\n", m_flActionTime ? m_flActionTime - Plat_FloatTime() : 0.0f ); if ( m_pDsSearcher ) DevMsg( "TeamSearch: Dedicated search in progress\n" ); else DevMsg( "TeamSearch: Dedicated search not active\n" ); DevMsg( "TeamSearch: SysSession host state:\n" ); if ( m_pSysSessionHost ) m_pSysSessionHost->DebugPrint(); else DevMsg( "SysSession is NULL\n" ); DevMsg( "TeamSearch: SysSession client state:\n" ); if ( m_pSysSessionClient ) m_pSysSessionClient->DebugPrint(); else DevMsg( "SysSession is NULL\n" ); } void CMatchSessionOnlineTeamSearch::OnEvent( KeyValues *pEvent ) { CMatchSessionOnlineSearch::OnEvent( pEvent ); // Let the dedicated search handle the responses too if ( m_pDsSearcher ) m_pDsSearcher->OnEvent( pEvent ); } void CMatchSessionOnlineTeamSearch::Update() { switch ( m_eState ) { case STATE_ERROR: if ( m_flActionTime && Plat_FloatTime() > m_flActionTime ) { m_flActionTime = 0.0f; if ( m_pHostSession ) m_pHostSession->Command( KeyValues::AutoDeleteInline( new KeyValues( "Stop" ) ) ); } return; case STATE_AWAITING_PEER: break; case STATE_LINK_CLIENT: LinkClient().LinkUpdate(); break; case STATE_LINK_HOST: LinkHost().LinkUpdate(); break; } CMatchSessionOnlineSearch::Update(); if ( CSysSessionBase *pLinkSysSession = LinkSysSession() ) pLinkSysSession->Update(); } void CMatchSessionOnlineTeamSearch::Destroy() { if ( m_pSysSessionHost ) { m_pSysSessionHost->Destroy(); m_pSysSessionHost = NULL; } if ( m_pSysSessionClient ) { m_pSysSessionClient->Destroy(); m_pSysSessionClient = NULL; } if ( m_pDsSearcher ) { m_pDsSearcher->Destroy(); m_pDsSearcher = NULL; } #ifdef _X360 if ( m_pXlspCommandBatch ) { m_pXlspCommandBatch->Destroy(); m_pXlspCommandBatch = NULL; } if ( m_pXlspConnection ) { m_pXlspConnection->Destroy(); m_pXlspConnection = NULL; } #endif CMatchSessionOnlineSearch::Destroy(); } ////////////////////////////////////////////////////////////////////////// // // Link base implementation // // void CMatchSessionOnlineTeamSearchLinkBase::LinkUpdate() { switch ( m_iLinkState ) { case STATE_SEARCHING_DEDICATED: Assert( m_pDsSearcher ); if ( m_pDsSearcher ) { m_pDsSearcher->Update(); if ( m_pDsSearcher->IsFinished() ) { OnDedicatedSearchFinished(); return; } } break; case STATE_HOSTING_LISTEN_SERVER: if ( KeyValues *pServer = m_pHostSession->GetSessionSettings()->FindKey( "server" ) ) { // Listen server has started and we should notify the link peer if ( KeyValues *pPeerCommand = new KeyValues( "TeamSearchLink::ListenClient" ) ) { KeyValues::AutoDelete autodelete( pPeerCommand ); pPeerCommand->SetString( "run", "xuid" ); pPeerCommand->SetUint64( "runxuid", m_xuidLinkPeer ); pPeerCommand->AddSubKey( pServer->MakeCopy() ); pPeerCommand->SetString( "server/server", "externalpeer" ); // for other team it is not a listen server, but externalpeer pPeerCommand->SetInt( "server/team", 2 ); // other team is nominated team 2 LinkSysSession()->Command( pPeerCommand ); } m_iLinkState = STATE_LINK_FINISHED; } break; } } void CMatchSessionOnlineTeamSearchLinkBase::LinkCommand( KeyValues *pCommand ) { char const *szCommand = pCommand->GetName(); if ( !Q_stricmp( "TeamSearchLink::Dedicated", szCommand ) || !Q_stricmp( "TeamSearchLink::ListenClient", szCommand ) ) { Assert( m_iLinkState == STATE_WAITING_FOR_PEER_SERVER ); KeyValues *pServerInfo = pCommand->FindKey( "server", false ); if ( !pServerInfo ) { ResetAndRestartTeamSearch(); return; } // Apply host session update packet ApplyHostSessionUpdatePacket(); // Notify our team about the server if ( KeyValues *pOurTeamNotify = new KeyValues( CFmtStr( "TeamSearchResult::%s", szCommand + strlen( "TeamSearchLink::" ) ) ) ) { pOurTeamNotify->AddSubKey( pServerInfo->MakeCopy() ); OnSearchEvent( pOurTeamNotify ); } return; } } void CMatchSessionOnlineTeamSearchLinkBase::StartHostingListenServer() { if ( mm_teamsearch_nostart.GetBool() ) { // Fake-cancel the session for debugging m_pHostSession->Command( KeyValues::AutoDeleteInline( new KeyValues( "Cancel" ) ) ); return; } m_iLinkState = STATE_HOSTING_LISTEN_SERVER; // Update host session settings packet ApplyHostSessionUpdatePacket(); OnSearchEvent( new KeyValues( "TeamSearchResult::ListenHost" ) ); } void CMatchSessionOnlineTeamSearchLinkBase::StartDedicatedServerSearch() { if ( mm_teamsearch_nostart.GetBool() ) { // Fake-cancel the session for debugging m_pHostSession->Command( KeyValues::AutoDeleteInline( new KeyValues( "Cancel" ) ) ); return; } m_iLinkState = STATE_SEARCHING_DEDICATED; // Notify everybody that we are searching for dedicated server now OnSearchEvent( new KeyValues( "OnMatchSessionUpdate", "state", "progress", "progress", "dedicated" ) ); m_pDsSearcher = new CDsSearcher( m_pSettings, m_pHostSession->GetSessionSystemData()->GetUint64( "xuidReserve" ), NULL ); } void CMatchSessionOnlineTeamSearchLinkBase::OnDedicatedSearchFinished() { Assert( m_pDsSearcher ); CDsSearcher::DsResult_t dsResult = m_pDsSearcher->GetResult(); m_pDsSearcher->Destroy(); m_pDsSearcher = NULL; if ( !dsResult.m_bDedicated ) { StartHostingListenServer(); return; } // // Serialize the information about the dedicated server // KeyValues *pServerInfo = new KeyValues( "server" ); dsResult.CopyToServerKey( pServerInfo ); pServerInfo->SetUint64( "reservationid", m_pHostSession->GetSessionSystemData()->GetUint64( "xuidReserve" ) ); // Apply host session update packet ApplyHostSessionUpdatePacket(); // // Notify the peer team about the server // if ( KeyValues *pPeerCommand = new KeyValues( "TeamSearchLink::Dedicated" ) ) { KeyValues::AutoDelete autodelete( pPeerCommand ); pPeerCommand->SetString( "run", "xuid" ); pPeerCommand->SetUint64( "runxuid", m_xuidLinkPeer ); pServerInfo->SetInt( "team", 2 ); pPeerCommand->AddSubKey( pServerInfo->MakeCopy() ); LinkSysSession()->Command( pPeerCommand ); } // Notify our team about the server if ( KeyValues *pOurTeamNotify = new KeyValues( "TeamSearchResult::Dedicated" ) ) { pOurTeamNotify->SetPtr( "dsresult", &dsResult ); pServerInfo->SetInt( "team", 1 ); pOurTeamNotify->AddSubKey( pServerInfo ); OnSearchEvent( pOurTeamNotify ); } m_iLinkState = STATE_LINK_FINISHED; } void CMatchSessionOnlineTeamSearchLinkBase::StartWaitingForPeerServer() { m_iLinkState = STATE_WAITING_FOR_PEER_SERVER; // Notify everybody that peer is searching for game server OnSearchEvent( new KeyValues( "OnMatchSessionUpdate", "state", "progress", "progress", "peerserver" ) ); } ////////////////////////////////////////////////////////////////////////// // // Link host implementation // // void CMatchSessionOnlineTeamSearchLinkHost::LinkInit() { m_iLinkState = STATE_SUBMIT_STATS; } void CMatchSessionOnlineTeamSearchLinkHost::LinkUpdate() { switch ( m_iLinkState ) { case STATE_SUBMIT_STATS: { #ifdef _X360 m_pXlspConnection = new CXlspConnection( false ); CUtlVector< KeyValues * > arrCommands; #endif if ( KeyValues *pCmd = new KeyValues( "stat_agg" ) ) { int numSeconds = int( 1 + Plat_FloatTime() - m_flCreationTime ); numSeconds = ClampArrayBounds( numSeconds, 30 * 60 ); // 30 minutes pCmd->SetInt( "search_team_time", numSeconds ); #ifdef _X360 arrCommands.AddToTail( pCmd ); #elif !defined( NO_STEAM ) && !defined( NO_STEAM_GAMECOORDINATOR ) CGCClientJobUpdateStats *pJob = new CGCClientJobUpdateStats( pCmd ); pJob->StartJob( NULL ); #else pCmd->deleteThis(); #endif } #ifdef _X360 m_pXlspCommandBatch = new CXlspConnectionCmdBatch( m_pXlspConnection, arrCommands ); #endif } m_iLinkState = STATE_REPORTING_STATS; break; case STATE_REPORTING_STATS: #ifdef _X360 m_pXlspCommandBatch->Update(); if ( !m_pXlspCommandBatch->IsFinished() ) return; m_pXlspCommandBatch->Destroy(); m_pXlspCommandBatch = NULL; m_pXlspConnection->Destroy(); m_pXlspConnection = NULL; #endif m_iLinkState = STATE_CONFIRM_JOIN; break; case STATE_CONFIRM_JOIN: if ( KeyValues *pPeerCommand = new KeyValues( "TeamSearchLink::HostConfirmJoinReady" ) ) { KeyValues::AutoDelete autodelete( pPeerCommand ); pPeerCommand->SetString( "run", "xuid" ); pPeerCommand->SetUint64( "runxuid", m_xuidLinkPeer ); pPeerCommand->AddSubKey( m_pHostSession->GetSessionSettings()->MakeCopy() ); // Submit a copy of our session settings as well m_pSysSessionHost->Command( pPeerCommand ); } m_iLinkState = STATE_CONFIRM_JOIN_WAIT; break; case STATE_CONFIRM_JOIN_WAIT: // Waiting for client to tell us how to select server break; } CMatchSessionOnlineTeamSearchLinkBase::LinkUpdate(); } void CMatchSessionOnlineTeamSearchLinkHost::LinkCommand( KeyValues *pCommand ) { char const *szCommand = pCommand->GetName(); if ( !Q_stricmp( "TeamSearchLink::TeamLinkSessionUpdate", szCommand ) ) { if ( KeyValues *pUpdatePkt = pCommand->GetFirstTrueSubKey() ) { m_pSettings->MergeFrom( pUpdatePkt ); RememberHostSessionUpdatePacket( pUpdatePkt->MakeCopy() ); } } else if ( !Q_stricmp( "TeamSearchLink::HostHosting", szCommand ) ) { // We are going to host char const *szLinkHostServer = m_pSettings->GetString( "options/server", "" ); if ( !Q_stricmp( szLinkHostServer, "listen" ) ) StartHostingListenServer(); else StartDedicatedServerSearch(); return; } else if ( !Q_stricmp( "TeamSearchLink::ClientHosting", szCommand ) ) { // Our peer is going to host StartWaitingForPeerServer(); return; } CMatchSessionOnlineTeamSearchLinkBase::LinkCommand( pCommand ); } ////////////////////////////////////////////////////////////////////////// // // Link client implementation // // void CMatchSessionOnlineTeamSearchLinkClient::LinkInit() { m_iLinkState = STATE_WAITING_FOR_HOST_READY; } void CMatchSessionOnlineTeamSearchLinkClient::LinkUpdate() { switch ( m_iLinkState ) { case STATE_WAITING_FOR_HOST_READY: // We are waiting for host to report statistics and get the merged teamlink lobby ready return; case STATE_CONFIRM_JOIN: // m_pSettings has been set to the aggregate of host settings // and our members, so we can inspect host's preference of the Server to // decide who is going to search for the game server { char const *szLinkHostServer = m_pSettings->GetString( "options/server", "" ); char const *szOurServer = m_pHostSession->GetSessionSettings()->GetString( "options/server", "" ); if ( Q_stricmp( szLinkHostServer, "listen" ) && !Q_stricmp( szOurServer, "listen" ) ) { // Host doesn't care, but we need listen server m_pSysSessionClient->Command( KeyValues::AutoDeleteInline( new KeyValues( "TeamSearchLink::ClientHosting", "run", "host" ) ) ); StartHostingListenServer(); } else { // Let host find the server m_pSysSessionClient->Command( KeyValues::AutoDeleteInline( new KeyValues( "TeamSearchLink::HostHosting", "run", "host" ) ) ); StartWaitingForPeerServer(); } } return; } CMatchSessionOnlineTeamSearchLinkBase::LinkUpdate(); } void CMatchSessionOnlineTeamSearchLinkClient::LinkCommand( KeyValues *pCommand ) { char const *szCommand = pCommand->GetName(); if ( !Q_stricmp( "TeamSearchLink::HostConfirmJoinReady", szCommand ) ) { // Host has sent us full settings of the host session, // let's try to reconcile and summarize the settings and prepare an update package if needed KeyValues *pRemoteSettings = pCommand->GetFirstTrueSubKey(); KeyValues *pLocalSettings = m_pHostSession->GetSessionSettings(); if ( KeyValues *pUpdatePkt = g_pMMF->GetMatchTitleGameSettingsMgr()->PrepareTeamLinkForGame( pLocalSettings, pRemoteSettings ) ) { if ( KeyValues *pHostCmd = new KeyValues( "TeamSearchLink::TeamLinkSessionUpdate", "run", "host" ) ) { pHostCmd->AddSubKey( pUpdatePkt->MakeCopy() ); m_pSysSessionClient->Command( KeyValues::AutoDeleteInline( pHostCmd ) ); } m_pSettings->MergeFrom( pUpdatePkt ); RememberHostSessionUpdatePacket( pUpdatePkt ); } // Host is now ready if ( m_iLinkState == STATE_WAITING_FOR_HOST_READY ) m_iLinkState = STATE_CONFIRM_JOIN; return; } CMatchSessionOnlineTeamSearchLinkBase::LinkCommand( pCommand ); }