Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

794 lines
23 KiB

  1. //===== Copyright � 1996-2009, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose:
  4. //
  5. //===========================================================================//
  6. #include "mm_framework.h"
  7. #include "vstdlib/random.h"
  8. #include "fmtstr.h"
  9. // memdbgon must be the last include file in a .cpp file!!!
  10. #include "tier0/memdbgon.h"
  11. static ConVar mm_session_search_num_results( "mm_session_search_num_results", "10", FCVAR_DEVELOPMENTONLY );
  12. static ConVar mm_session_search_distance( "mm_session_search_distance", "1", FCVAR_DEVELOPMENTONLY );
  13. ConVar mm_session_search_qos_timeout( "mm_session_search_qos_timeout", "15.0", FCVAR_RELEASE );
  14. // static ConVar mm_session_search_ping_limit( "mm_session_search_ping_limit", "200", FCVAR_RELEASE ); -- removed, using mm_dedicated_search_maxping instead
  15. static ConVar mm_session_search_ping_buckets( "mm_session_search_ping_buckets", "4", FCVAR_RELEASE );
  16. CMatchSearcher::CMatchSearcher( KeyValues *pSettings ) :
  17. m_pSettings( pSettings ), // takes ownership
  18. m_autodelete_pSettings( m_pSettings ),
  19. m_pSessionSearchTree( NULL ),
  20. m_autodelete_pSessionSearchTree( m_pSessionSearchTree ),
  21. m_pSearchPass( NULL ),
  22. m_eState( STATE_INIT )
  23. {
  24. #ifdef _X360
  25. ZeroMemory( &m_xOverlapped, sizeof( m_xOverlapped ) );
  26. m_pQosResults = NULL;
  27. m_pCancelOverlappedJob = NULL;
  28. #endif
  29. DevMsg( "Created CMatchSearcher:\n" );
  30. KeyValuesDumpAsDevMsg( m_pSettings, 1 );
  31. InitializeSettings();
  32. }
  33. CMatchSearcher::~CMatchSearcher()
  34. {
  35. DevMsg( "Destroying CMatchSearcher:\n" );
  36. KeyValuesDumpAsDevMsg( m_pSettings, 1 );
  37. // Free all retrieved game details
  38. CUtlVector< SearchResult_t > *arrResults[] = { &m_arrSearchResults, &m_arrSearchResultsAggregate };
  39. for ( int ia = 0; ia < ARRAYSIZE( arrResults ); ++ ia )
  40. {
  41. for ( int k = 0; k < arrResults[ia]->Count(); ++ k )
  42. {
  43. SearchResult_t &sr = arrResults[ia]->Element( k );
  44. if ( sr.m_pGameDetails )
  45. {
  46. sr.m_pGameDetails->deleteThis();
  47. sr.m_pGameDetails = NULL;
  48. }
  49. }
  50. }
  51. }
  52. void CMatchSearcher::InitializeSettings()
  53. {
  54. // Initialize only the settings required to connect...
  55. if ( KeyValues *kv = m_pSettings->FindKey( "system", true ) )
  56. {
  57. KeyValuesAddDefaultString( kv, "network", "LIVE" );
  58. KeyValuesAddDefaultString( kv, "access", "public" );
  59. }
  60. if ( KeyValues *pMembers = m_pSettings->FindKey( "members", true ) )
  61. {
  62. int numMachines = pMembers->GetInt( "numMachines", -1 );
  63. if ( numMachines == -1 )
  64. {
  65. numMachines = 1;
  66. pMembers->SetInt( "numMachines", numMachines );
  67. }
  68. int numPlayers = pMembers->GetInt( "numPlayers", -1 );
  69. if ( numPlayers == -1 )
  70. {
  71. numPlayers = 1;
  72. #ifdef _GAMECONSOLE
  73. numPlayers = XBX_GetNumGameUsers();
  74. #endif
  75. pMembers->SetInt( "numPlayers", numPlayers );
  76. }
  77. pMembers->SetInt( "numSlots", numPlayers );
  78. KeyValues *pMachine = pMembers->FindKey( "machine0");
  79. if ( !pMachine )
  80. {
  81. pMachine = pMembers->CreateNewKey();
  82. pMachine->SetName( "machine0" );
  83. XUID machineid = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() )->GetXUID();
  84. pMachine->SetUint64( "id", machineid );
  85. pMachine->SetUint64( "flags", MatchSession_GetMachineFlags() );
  86. pMachine->SetInt( "numPlayers", numPlayers );
  87. pMachine->SetUint64( "dlcmask", g_pMatchFramework->GetMatchSystem()->GetDlcManager()->GetDataInfo()->GetUint64( "@info/installed" ) );
  88. pMachine->SetString( "tuver", MatchSession_GetTuInstalledString() );
  89. pMachine->SetInt( "ping", 0 );
  90. for ( int k = 0; k < numPlayers; ++ k )
  91. {
  92. if ( KeyValues *pPlayer = pMachine->FindKey( CFmtStr( "player%d", k ), true ) )
  93. {
  94. int iController = 0;
  95. #ifdef _GAMECONSOLE
  96. iController = XBX_GetUserId( k );
  97. #endif
  98. IPlayerLocal *player = g_pPlayerManager->GetLocalPlayer( iController );
  99. pPlayer->SetUint64( "xuid", player->GetXUID() );
  100. pPlayer->SetString( "name", player->GetName() );
  101. }
  102. }
  103. }
  104. }
  105. DevMsg( "CMatchSearcher::InitializeGameSettings adjusted settings:\n" );
  106. KeyValuesDumpAsDevMsg( m_pSettings, 1 );
  107. }
  108. KeyValues * CMatchSearcher::GetSearchSettings()
  109. {
  110. return m_pSettings;
  111. }
  112. void CMatchSearcher::Destroy()
  113. {
  114. // Stop the search
  115. if ( m_eState == STATE_SEARCHING )
  116. {
  117. #ifdef _X360
  118. m_pCancelOverlappedJob = ThreadExecute( MMX360_CancelOverlapped, &m_xOverlapped ); // UpdateDormantOperations will clean the rest
  119. MMX360_RegisterDormant( this );
  120. return;
  121. #endif
  122. }
  123. #ifdef _X360
  124. if ( m_eState == STATE_CHECK_QOS )
  125. {
  126. g_pMatchExtensions->GetIXOnline()->XNetQosRelease( m_pQosResults );
  127. m_pQosResults = NULL;
  128. }
  129. #endif
  130. #if !defined( NO_STEAM )
  131. while ( m_arrOutstandingAsyncOperation.Count() > 0 )
  132. {
  133. IMatchAsyncOperation *pAsyncOperation = m_arrOutstandingAsyncOperation.Tail();
  134. m_arrOutstandingAsyncOperation.RemoveMultipleFromTail( 1 );
  135. if ( pAsyncOperation )
  136. pAsyncOperation->Release();
  137. }
  138. #endif
  139. delete this;
  140. }
  141. void CMatchSearcher::OnSearchEvent( KeyValues *pNotify )
  142. {
  143. g_pMatchEventsSubscription->BroadcastEvent( pNotify );
  144. }
  145. #ifdef _X360
  146. bool CMatchSearcher::UpdateDormantOperation()
  147. {
  148. if ( !m_pCancelOverlappedJob->IsFinished() )
  149. return true; // keep running dormant
  150. m_pCancelOverlappedJob->Release();
  151. m_pCancelOverlappedJob = NULL;
  152. delete this;
  153. return false; // destroyed object, remove from dormant list
  154. }
  155. #endif
  156. void CMatchSearcher::Update()
  157. {
  158. switch ( m_eState )
  159. {
  160. case STATE_INIT:
  161. m_eState = STATE_SEARCHING;
  162. // Session is searching
  163. OnSearchEvent( new KeyValues(
  164. "OnMatchSessionUpdate",
  165. "state", "progress",
  166. "progress", "searching"
  167. ) );
  168. // Kick off the search
  169. StartSearch();
  170. break;
  171. case STATE_SEARCHING:
  172. // Waiting for session search to complete
  173. #ifdef _X360
  174. if ( XHasOverlappedIoCompleted( &m_xOverlapped ) )
  175. Live_OnSessionSearchCompleted();
  176. #endif
  177. break;
  178. #ifdef _X360
  179. case STATE_CHECK_QOS:
  180. // Keep checking for results or until the wait time expires
  181. if ( Plat_FloatTime() > m_flQosTimeout ||
  182. !m_pQosResults->cxnqosPending )
  183. Live_OnQosCheckCompleted();
  184. break;
  185. #endif
  186. #if !defined (NO_STEAM)
  187. case STATE_WAITING_LOBBY_DATA_AND_PING:
  188. {
  189. bool bWaitLonger = ( ( Plat_MSTime() - m_uiQosTimeoutStartMS ) < ( mm_session_search_qos_timeout.GetFloat() * 1000.f ) );
  190. if ( bWaitLonger )
  191. {
  192. bool bHasPendingLobbyData = false;
  193. for ( int j = 0; j < m_arrSearchResults.Count(); ++ j )
  194. {
  195. SearchResult_t &sr = m_arrSearchResults[j];
  196. if ( !sr.m_numPlayers )
  197. { // didn't even receive lobby data from Steam yet
  198. bHasPendingLobbyData = true;
  199. }
  200. else if ( sr.m_svAdr.GetIPHostByteOrder() && ( sr.m_svPing <= 0 ) )
  201. { // received valid IP for lobby, still pinging
  202. bHasPendingLobbyData = true;
  203. }
  204. }
  205. if ( !bHasPendingLobbyData )
  206. bWaitLonger = false;
  207. }
  208. if ( !bWaitLonger )
  209. {
  210. m_CallbackOnLobbyDataReceived.Unregister();
  211. // Go ahead and start joining the results
  212. AggregateSearchPassResults();
  213. OnSearchPassDone( m_pSearchPass );
  214. }
  215. }
  216. break;
  217. #endif
  218. }
  219. }
  220. #if !defined (NO_STEAM)
  221. void CMatchSearcher::Steam_OnLobbyDataReceived( LobbyDataUpdate_t *pLobbyDataUpdate )
  222. {
  223. int iResultIndex = 0;
  224. for ( ; iResultIndex < m_arrSearchResults.Count(); ++ iResultIndex )
  225. {
  226. if ( m_arrSearchResults[ iResultIndex ].m_uiLobbyId == pLobbyDataUpdate->m_ulSteamIDLobby )
  227. break;
  228. }
  229. if ( !m_arrSearchResults.IsValidIndex( iResultIndex ) )
  230. return;
  231. SearchResult_t *pRes = &m_arrSearchResults[ iResultIndex ];
  232. if ( !pLobbyDataUpdate->m_bSuccess )
  233. {
  234. DevMsg( "[MM] Could not get lobby data for lobby %llu (%llx)\n",
  235. pRes->m_uiLobbyId, pRes->m_uiLobbyId );
  236. pRes->m_numPlayers = -1; // set numPlayers to negative number to indicate a failure here
  237. }
  238. else
  239. {
  240. // Get num players
  241. const char *numPlayers = steamapicontext->SteamMatchmaking()->GetLobbyData(
  242. pRes->m_uiLobbyId, "members:numPlayers" );
  243. if ( !numPlayers )
  244. {
  245. DevMsg( "[MM] Unable to get num players for lobby (%llx)\n",
  246. pRes->m_uiLobbyId );
  247. pRes->m_numPlayers = -1; // set numPlayers to negative number to indicate a failure here
  248. }
  249. else
  250. {
  251. pRes->m_numPlayers = V_atoi( numPlayers );
  252. if ( !pRes->m_numPlayers )
  253. pRes->m_numPlayers = -1; // set numPlayers to negative number to indicate a failure here
  254. }
  255. // Get the address of the server
  256. const char* pServerAdr = steamapicontext->SteamMatchmaking()->GetLobbyData(
  257. pRes->m_uiLobbyId, "server:adronline" );
  258. if ( !pServerAdr )
  259. {
  260. // DevMsg( "[MM] Unable to get server address from lobby (%llx)\n",
  261. // pRes->m_uiLobbyId );
  262. }
  263. else
  264. {
  265. pRes->m_svAdr.SetFromString( pServerAdr );
  266. DevMsg ( "[MM] Lobby %d: id %llu (%llx), num Players %d, sv ip %s, pinging dist %d\n",
  267. iResultIndex, pRes->m_uiLobbyId, pRes->m_uiLobbyId, pRes->m_numPlayers,
  268. pRes->m_svAdr.ToString(), pRes->m_svPing );
  269. // Ping server
  270. IMatchAsyncOperation *pAsyncOperationPing = NULL;
  271. g_pMatchExtensions->GetINetSupport()->ServerPing( pRes->m_svAdr, this, &pAsyncOperationPing );
  272. m_arrOutstandingAsyncOperation.AddToTail( pAsyncOperationPing );
  273. pRes->m_pAsyncOperationPingWeakRef = pAsyncOperationPing;
  274. }
  275. }
  276. }
  277. // Callback for server reservation check
  278. void CMatchSearcher::OnOperationFinished( IMatchAsyncOperation *pOperation )
  279. {
  280. if ( !pOperation )
  281. return;
  282. if ( m_eState != STATE_WAITING_LOBBY_DATA_AND_PING )
  283. return;
  284. for ( int i = 0; i < m_arrSearchResults.Count(); ++ i )
  285. {
  286. if ( m_arrSearchResults[i].m_pAsyncOperationPingWeakRef != pOperation )
  287. continue;
  288. int result = pOperation->GetResult();
  289. bool failed = ( pOperation->GetState() == AOS_FAILED );
  290. SearchResult_t *pRes = &m_arrSearchResults[i];
  291. pRes->m_svPing = ( failed ? -1 : ( result ? result : -1 ) );
  292. if ( pRes->m_svPing < 0 )
  293. {
  294. DevMsg( "[MM] Failed pinging server %s for lobby#%d %llu (%llx)\n",
  295. pRes->m_svAdr.ToString(), i, pRes->m_uiLobbyId, pRes->m_uiLobbyId );
  296. }
  297. else
  298. {
  299. DevMsg( "[MM] Successfully pinged server %s for lobby#%d %llu (%llx) = %d ms\n",
  300. pRes->m_svAdr.ToString(), i, pRes->m_uiLobbyId, pRes->m_uiLobbyId, pRes->m_svPing );
  301. }
  302. m_arrSearchResults[i].m_pAsyncOperationPingWeakRef = NULL;
  303. }
  304. }
  305. #endif
  306. void CMatchSearcher::OnSearchDone()
  307. {
  308. m_eState = STATE_DONE;
  309. }
  310. void CMatchSearcher::AggregateSearchPassResults()
  311. {
  312. #ifndef NO_STEAM
  313. extern ConVar mm_dedicated_search_maxping;
  314. int nPingLimitMax = mm_dedicated_search_maxping.GetInt();
  315. if ( nPingLimitMax <= 0 )
  316. nPingLimitMax = 5000;
  317. for ( int k = 0; k < mm_session_search_ping_buckets.GetInt(); ++ k )
  318. {
  319. int iPingLow = ( nPingLimitMax * k ) / mm_session_search_ping_buckets.GetInt();
  320. int iPingHigh = ( nPingLimitMax * ( k + 1 ) ) / mm_session_search_ping_buckets.GetInt();
  321. for ( int iResult = 0; iResult < m_arrSearchResults.Count(); ++ iResult )
  322. {
  323. SearchResult_t *pRes = &m_arrSearchResults[iResult];
  324. if ( ( pRes->m_svPing > iPingLow ) && ( pRes->m_svPing <= iPingHigh ) )
  325. {
  326. m_arrSearchResultsAggregate.AddToTail( *pRes );
  327. DevMsg ( "[MM] Search aggregated result%d / %d: id %llu (%llx), num Players %d, sv ip %s, dist %d\n",
  328. m_arrSearchResultsAggregate.Count(), iResult, pRes->m_uiLobbyId, pRes->m_uiLobbyId, pRes->m_numPlayers,
  329. pRes->m_svAdr.ToString(), pRes->m_svPing );
  330. }
  331. }
  332. }
  333. DevMsg ( "[MM] Search aggregated %d results\n", m_arrSearchResultsAggregate.Count() );
  334. #endif
  335. m_arrSearchResults.Purge();
  336. }
  337. void CMatchSearcher::OnSearchPassDone( KeyValues *pSearchPass )
  338. {
  339. // If we have enough results, then call it done
  340. if ( m_arrSearchResultsAggregate.Count() >= mm_session_search_num_results.GetInt() )
  341. {
  342. OnSearchDone();
  343. return;
  344. }
  345. // Evaluate if there is a nextpass condition
  346. char const *szNextPassKeyName = "nextpass";
  347. if ( KeyValues *pConditions = pSearchPass->FindKey( "nextpass?" ) )
  348. {
  349. // Inspect conditions and select which next pass will happen
  350. }
  351. if ( KeyValues *pNextPass = pSearchPass->FindKey( szNextPassKeyName ) )
  352. {
  353. StartSearchPass( pNextPass );
  354. }
  355. else
  356. {
  357. OnSearchDone();
  358. }
  359. }
  360. #ifdef _X360
  361. void CMatchSearcher::Live_OnSessionSearchCompleted()
  362. {
  363. DevMsg( "Received %d search results from Xbox LIVE.\n", GetXSearchResult()->dwSearchResults );
  364. for( unsigned int i = 0; i < GetXSearchResult()->dwSearchResults; ++ i )
  365. {
  366. XSESSION_SEARCHRESULT const &xsr = GetXSearchResult()->pResults[i];
  367. SearchResult_t sr = { xsr.info, NULL };
  368. m_arrSearchResults.AddToTail( sr );
  369. DevMsg( 2, "Result #%02d: %llx\n", i + 1, ( const uint64& ) xsr.info.sessionID );
  370. }
  371. if ( !m_arrSearchResults.Count() )
  372. {
  373. OnSearchPassDone( m_pSearchPass );
  374. }
  375. else
  376. {
  377. DevMsg( "Checking QOS with %d search results.\n", m_arrSearchResults.Count() );
  378. Live_CheckSearchResultsQos();
  379. }
  380. }
  381. void CMatchSearcher::Live_CheckSearchResultsQos()
  382. {
  383. m_eState = STATE_CHECK_QOS;
  384. int nResults = m_arrSearchResults.Count();
  385. CUtlVector< const void * > memQosData;
  386. memQosData.SetCount( 3 * nResults );
  387. const void ** bufQosData[3];
  388. for ( int k = 0; k < ARRAYSIZE( bufQosData ); ++ k )
  389. bufQosData[k] = &memQosData[ k * nResults ];
  390. for ( int k = 0; k < m_arrSearchResults.Count(); ++ k )
  391. {
  392. SearchResult_t const &sr = m_arrSearchResults[k];
  393. bufQosData[0][k] = &sr.m_info.hostAddress;
  394. bufQosData[1][k] = &sr.m_info.sessionID;
  395. bufQosData[2][k] = &sr.m_info.keyExchangeKey;
  396. }
  397. //
  398. // Note: XNetQosLookup requires only 2 successful probes to be received from the host.
  399. // This is much less than the recommended 8 probes because on a 10% data loss profile
  400. // it is impossible to find the host when requiring 8 probes to be received.
  401. m_flQosTimeout = Plat_FloatTime() + mm_session_search_qos_timeout.GetFloat();
  402. int res = g_pMatchExtensions->GetIXOnline()->XNetQosLookup(
  403. nResults,
  404. reinterpret_cast< XNADDR const ** >( bufQosData[0] ),
  405. reinterpret_cast< XNKID const ** >( bufQosData[1] ),
  406. reinterpret_cast< XNKEY const ** >( bufQosData[2] ),
  407. 0, // number of security gateways to probe
  408. NULL, // gateway ip addresses
  409. NULL, // gateway service ids
  410. 2, // number of probes
  411. 0, // upstream bandwith to use (0 = default)
  412. 0, // flags - not supported
  413. NULL, // signal event
  414. &m_pQosResults );// results
  415. if ( res != 0 )
  416. {
  417. DevWarning( "OnlineSearch::Live_CheckSearchResultsQos - XNetQosLookup failed (code = 0x%08X)!\n", res );
  418. m_arrSearchResults.Purge();
  419. OnSearchPassDone( m_pSearchPass );
  420. }
  421. }
  422. void CMatchSearcher::Live_OnQosCheckCompleted()
  423. {
  424. for ( uint k = m_pQosResults->cxnqos; k --> 0; )
  425. {
  426. XNQOSINFO &xqi = m_pQosResults->axnqosinfo[k];
  427. BYTE uNeedFlags = XNET_XNQOSINFO_TARGET_CONTACTED | XNET_XNQOSINFO_DATA_RECEIVED;
  428. if ( ( ( xqi.bFlags & uNeedFlags ) != uNeedFlags) ||
  429. ( xqi.bFlags & XNET_XNQOSINFO_TARGET_DISABLED ) )
  430. {
  431. m_arrSearchResults.Remove( k );
  432. continue;
  433. }
  434. extern ConVar mm_dedicated_search_maxping;
  435. if ( mm_dedicated_search_maxping.GetInt() > 0 &&
  436. xqi.wRttMedInMsecs > mm_dedicated_search_maxping.GetInt() )
  437. {
  438. m_arrSearchResults.Remove( k );
  439. continue;
  440. }
  441. if ( xqi.cbData && xqi.pbData )
  442. {
  443. MM_GameDetails_QOS_t gd = { xqi.pbData, xqi.cbData, xqi.wRttMedInMsecs };
  444. Assert( !m_arrSearchResults[k].m_pGameDetails );
  445. m_arrSearchResults[k].m_pGameDetails = g_pMatchFramework->GetMatchNetworkMsgController()->UnpackGameDetailsFromQOS( &gd );
  446. }
  447. }
  448. g_pMatchExtensions->GetIXOnline()->XNetQosRelease( m_pQosResults );
  449. m_pQosResults = NULL;
  450. // Go ahead and start joining the results
  451. DevMsg( "Qos completed with %d search results.\n", m_arrSearchResults.Count() );
  452. AggregateSearchPassResults();
  453. OnSearchPassDone( m_pSearchPass );
  454. }
  455. #elif !defined( NO_STEAM )
  456. void CMatchSearcher::Steam_OnLobbyMatchListReceived( LobbyMatchList_t *pLobbyMatchList, bool bError )
  457. {
  458. Msg( "[MM] Received %d search results.\n", bError ? 0 : pLobbyMatchList->m_nLobbiesMatching );
  459. m_CallbackOnLobbyDataReceived.Register( this, &CMatchSearcher::Steam_OnLobbyDataReceived );
  460. // Walk through search results and request lobby data
  461. for ( int iLobby = 0; iLobby < (int) ( bError ? 0 : pLobbyMatchList->m_nLobbiesMatching ); ++ iLobby )
  462. {
  463. uint64 uiLobbyId = steamapicontext->SteamMatchmaking()->GetLobbyByIndex( iLobby ).ConvertToUint64();
  464. SearchResult_t sr = { uiLobbyId };
  465. sr.m_pGameDetails = NULL;
  466. sr.m_svAdr.SetFromString( "0.0.0.0" );
  467. sr.m_svPing = 0;
  468. sr.m_numPlayers = 0;
  469. sr.m_pAsyncOperationPingWeakRef = NULL;
  470. m_arrSearchResults.AddToTail( sr );
  471. steamapicontext->SteamMatchmaking()->RequestLobbyData( sr.m_uiLobbyId );
  472. }
  473. m_eState = STATE_WAITING_LOBBY_DATA_AND_PING; // Waiting for lobby data
  474. m_uiQosPingLastMS = m_uiQosTimeoutStartMS = Plat_MSTime();
  475. }
  476. KeyValues * CMatchSearcher::SearchResult_t::GetGameDetails() const
  477. {
  478. if ( !m_pGameDetails )
  479. {
  480. m_pGameDetails = g_pMatchFramework->GetMatchNetworkMsgController()->UnpackGameDetailsFromSteamLobby( m_uiLobbyId );
  481. }
  482. return m_pGameDetails;
  483. }
  484. #endif
  485. void CMatchSearcher::StartSearch()
  486. {
  487. Assert( !m_pSessionSearchTree );
  488. m_pSessionSearchTree = g_pMMF->GetMatchTitleGameSettingsMgr()->DefineSessionSearchKeys( m_pSettings );
  489. m_autodelete_pSessionSearchTree.Assign( m_pSessionSearchTree );
  490. Assert( m_pSessionSearchTree );
  491. if ( !m_pSessionSearchTree )
  492. {
  493. DevWarning( "OnlineSearch::StartSearch failed to build filter list!\n" );
  494. OnSearchDone();
  495. }
  496. else
  497. {
  498. StartSearchPass( m_pSessionSearchTree );
  499. }
  500. }
  501. void CMatchSearcher::StartSearchPass( KeyValues *pSearchPass )
  502. {
  503. KeyValues *pSearchParams = pSearchPass;
  504. m_pSearchPass = pSearchPass;
  505. // Make sure we have fresh buffer for search results
  506. m_arrSearchResults.Purge();
  507. m_eState = STATE_SEARCHING;
  508. DevMsg( "OnlineSearch::StartSearchPass:\n" );
  509. KeyValuesDumpAsDevMsg( pSearchParams, 1 );
  510. #ifdef _X360
  511. DWORD dwSearchRule = pSearchParams->GetInt( "rule" );
  512. m_arrContexts.RemoveAll();
  513. if ( KeyValues *pContexts = pSearchParams->FindKey( "Contexts" ) )
  514. {
  515. for ( KeyValues *val = pContexts->GetFirstValue(); val; val = val->GetNextValue() )
  516. {
  517. XUSER_CONTEXT ctx = { 0 };
  518. ctx.dwContextId = atoi( val->GetName() );
  519. if ( val->GetDataType() == KeyValues::TYPE_INT )
  520. {
  521. ctx.dwValue = val->GetInt();
  522. m_arrContexts.AddToTail( ctx );
  523. }
  524. }
  525. }
  526. m_arrProperties.RemoveAll();
  527. if ( KeyValues *pContexts = pSearchParams->FindKey( "Properties" ) )
  528. {
  529. for ( KeyValues *val = pContexts->GetFirstValue(); val; val = val->GetNextValue() )
  530. {
  531. XUSER_PROPERTY prop = { 0 };
  532. prop.dwPropertyId = atoi( val->GetName() );
  533. if ( val->GetDataType() == KeyValues::TYPE_INT )
  534. {
  535. prop.value.type = XUSER_DATA_TYPE_INT32;
  536. prop.value.nData = val->GetInt();
  537. m_arrProperties.AddToTail( prop );
  538. }
  539. }
  540. }
  541. DWORD ret = ERROR_SUCCESS;
  542. DWORD numBytes = 0;
  543. DWORD dwNumSlotsRequired = pSearchParams->GetInt( "numPlayers" );
  544. //
  545. // Issue the asynchrounous session search request
  546. //
  547. ret = g_pMatchExtensions->GetIXOnline()->XSessionSearchEx(
  548. dwSearchRule, XBX_GetPrimaryUserId(), mm_session_search_num_results.GetInt(),
  549. dwNumSlotsRequired,
  550. m_arrProperties.Count(), m_arrContexts.Count(),
  551. m_arrProperties.Base(), m_arrContexts.Base(),
  552. &numBytes, NULL, NULL
  553. );
  554. // Log the search request to read X360 queries easier
  555. DevMsg( "XSessionSearchEx by rule %d for slots %d\n", dwSearchRule, dwNumSlotsRequired );
  556. for ( int k = 0; k < m_arrContexts.Count(); ++ k )
  557. DevMsg( " CTX %u/0x%08X = 0x%X/%u\n", m_arrContexts[k].dwContextId, m_arrContexts[k].dwContextId, m_arrContexts[k].dwValue, m_arrContexts[k].dwValue );
  558. for ( int k = 0; k < m_arrProperties.Count(); ++ k )
  559. DevMsg( " PRP %u/0x%08X = 0x%X/%u\n", m_arrProperties[k].dwPropertyId, m_arrProperties[k].dwPropertyId, m_arrProperties[k].value.nData, m_arrProperties[k].value.nData );
  560. DevMsg( "will use %u bytes buffer.\n", numBytes );
  561. if ( ERROR_INSUFFICIENT_BUFFER == ret && numBytes > 0 )
  562. {
  563. m_bufSearchResultHeader.EnsureCapacity( numBytes );
  564. ZeroMemory( GetXSearchResult(), numBytes );
  565. ZeroMemory( &m_xOverlapped, sizeof( m_xOverlapped ) );
  566. DevMsg( "Searching...\n" );
  567. ret = g_pMatchExtensions->GetIXOnline()->XSessionSearchEx(
  568. dwSearchRule, XBX_GetPrimaryUserId(), mm_session_search_num_results.GetInt(),
  569. dwNumSlotsRequired,
  570. m_arrProperties.Count(), m_arrContexts.Count(),
  571. m_arrProperties.Base(), m_arrContexts.Base(),
  572. &numBytes, GetXSearchResult(), &m_xOverlapped
  573. );
  574. if ( ret == ERROR_IO_PENDING )
  575. return;
  576. }
  577. // Otherwise search failed
  578. DevWarning( "XSessionSearchEx failed (code = 0x%08X)\n", ret );
  579. ZeroMemory( &m_xOverlapped, sizeof( m_xOverlapped ) );
  580. OnSearchPassDone( m_pSearchPass );
  581. #elif !defined( NO_STEAM )
  582. ISteamMatchmaking *mm = steamapicontext->SteamMatchmaking();
  583. // Configure filters
  584. DWORD dwNumSlotsRequired = pSearchParams->GetInt( "numPlayers" );
  585. mm->AddRequestLobbyListFilterSlotsAvailable( dwNumSlotsRequired );
  586. // Set filters
  587. char const * arrKeys[] = { "Filter<", "Filter<=", "Filter=", "Filter<>", "Filter>=", "Filter>" };
  588. ELobbyComparison nValueCmp[] = { k_ELobbyComparisonLessThan, k_ELobbyComparisonEqualToOrLessThan, k_ELobbyComparisonEqual, k_ELobbyComparisonNotEqual, k_ELobbyComparisonEqualToOrGreaterThan, k_ELobbyComparisonGreaterThan };
  589. for ( int k = 0; k < ARRAYSIZE( arrKeys ); ++ k )
  590. {
  591. if ( KeyValues *kv = pSearchParams->FindKey( arrKeys[k] ) )
  592. {
  593. for ( KeyValues *val = kv->GetFirstValue(); val; val = val->GetNextValue() )
  594. {
  595. if ( val->GetDataType() == KeyValues::TYPE_STRING )
  596. {
  597. mm->AddRequestLobbyListStringFilter( val->GetName(), val->GetString(), nValueCmp[k] );
  598. }
  599. else if ( val->GetDataType() == KeyValues::TYPE_INT )
  600. {
  601. mm->AddRequestLobbyListNumericalFilter( val->GetName(), val->GetInt(), nValueCmp[k] );
  602. }
  603. }
  604. }
  605. }
  606. // Set ordering near values
  607. if ( KeyValues *kv = pSearchParams->FindKey( "Near" ) )
  608. {
  609. for ( KeyValues *val = kv->GetFirstValue(); val; val = val->GetNextValue() )
  610. {
  611. if ( val->GetDataType() == KeyValues::TYPE_INT )
  612. {
  613. mm->AddRequestLobbyListNearValueFilter( val->GetName(), val->GetInt() );
  614. }
  615. else
  616. {
  617. // TODO: need to set near value filter for strings
  618. }
  619. }
  620. }
  621. ELobbyDistanceFilter eFilter = k_ELobbyDistanceFilterDefault;
  622. switch ( mm_session_search_distance.GetInt() )
  623. {
  624. case k_ELobbyDistanceFilterClose:
  625. case k_ELobbyDistanceFilterDefault:
  626. case k_ELobbyDistanceFilterFar:
  627. case k_ELobbyDistanceFilterWorldwide:
  628. eFilter = ( ELobbyDistanceFilter ) mm_session_search_distance.GetInt();
  629. break;
  630. }
  631. // Set distance filter to Near
  632. mm->AddRequestLobbyListDistanceFilter( eFilter );
  633. // Set dependent lobby
  634. if ( uint64 uiDependentLobbyId = pSearchParams->GetUint64( "DependentLobby", 0ull ) )
  635. {
  636. mm->AddRequestLobbyListCompatibleMembersFilter( uiDependentLobbyId );
  637. }
  638. // RequestLobbyList - will get called back at Steam_OnLobbyMatchListReceived
  639. DevMsg( "Searching...\n" );
  640. SteamAPICall_t hCall = mm->RequestLobbyList();
  641. m_CallbackOnLobbyMatchListReceived.Set( hCall, this, &CMatchSearcher::Steam_OnLobbyMatchListReceived );
  642. #endif
  643. }
  644. //
  645. // Results retrieval overrides
  646. //
  647. bool CMatchSearcher::IsSearchFinished() const
  648. {
  649. return m_eState == STATE_DONE;
  650. }
  651. int CMatchSearcher::GetNumSearchResults() const
  652. {
  653. return IsSearchFinished() ? m_arrSearchResultsAggregate.Count() : 0;
  654. }
  655. CMatchSearcher::SearchResult_t const & CMatchSearcher::GetSearchResult( int idx ) const
  656. {
  657. if ( !IsSearchFinished() || !m_arrSearchResultsAggregate.IsValidIndex( idx ) )
  658. {
  659. Assert( false );
  660. static SearchResult_t s_empty;
  661. return s_empty;
  662. }
  663. else
  664. {
  665. return m_arrSearchResultsAggregate[ idx ];
  666. }
  667. }