csgo-2018-source/matchmaking/match_searcher.cpp
2021-07-24 21:11:47 -07:00

795 lines
23 KiB
C++
Raw Blame History

//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_framework.h"
#include "vstdlib/random.h"
#include "fmtstr.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
static ConVar mm_session_search_num_results( "mm_session_search_num_results", "10", FCVAR_DEVELOPMENTONLY );
static ConVar mm_session_search_distance( "mm_session_search_distance", "1", FCVAR_DEVELOPMENTONLY );
ConVar mm_session_search_qos_timeout( "mm_session_search_qos_timeout", "15.0", FCVAR_RELEASE );
// static ConVar mm_session_search_ping_limit( "mm_session_search_ping_limit", "200", FCVAR_RELEASE ); -- removed, using mm_dedicated_search_maxping instead
static ConVar mm_session_search_ping_buckets( "mm_session_search_ping_buckets", "4", FCVAR_RELEASE );
CMatchSearcher::CMatchSearcher( KeyValues *pSettings ) :
m_pSettings( pSettings ), // takes ownership
m_autodelete_pSettings( m_pSettings ),
m_pSessionSearchTree( NULL ),
m_autodelete_pSessionSearchTree( m_pSessionSearchTree ),
m_pSearchPass( NULL ),
m_eState( STATE_INIT )
{
#ifdef _X360
ZeroMemory( &m_xOverlapped, sizeof( m_xOverlapped ) );
m_pQosResults = NULL;
m_pCancelOverlappedJob = NULL;
#endif
DevMsg( "Created CMatchSearcher:\n" );
KeyValuesDumpAsDevMsg( m_pSettings, 1 );
InitializeSettings();
}
CMatchSearcher::~CMatchSearcher()
{
DevMsg( "Destroying CMatchSearcher:\n" );
KeyValuesDumpAsDevMsg( m_pSettings, 1 );
// Free all retrieved game details
CUtlVector< SearchResult_t > *arrResults[] = { &m_arrSearchResults, &m_arrSearchResultsAggregate };
for ( int ia = 0; ia < ARRAYSIZE( arrResults ); ++ ia )
{
for ( int k = 0; k < arrResults[ia]->Count(); ++ k )
{
SearchResult_t &sr = arrResults[ia]->Element( k );
if ( sr.m_pGameDetails )
{
sr.m_pGameDetails->deleteThis();
sr.m_pGameDetails = NULL;
}
}
}
}
void CMatchSearcher::InitializeSettings()
{
// Initialize only the settings required to connect...
if ( KeyValues *kv = m_pSettings->FindKey( "system", true ) )
{
KeyValuesAddDefaultString( kv, "network", "LIVE" );
KeyValuesAddDefaultString( kv, "access", "public" );
}
if ( KeyValues *pMembers = m_pSettings->FindKey( "members", true ) )
{
int numMachines = pMembers->GetInt( "numMachines", -1 );
if ( numMachines == -1 )
{
numMachines = 1;
pMembers->SetInt( "numMachines", numMachines );
}
int numPlayers = pMembers->GetInt( "numPlayers", -1 );
if ( numPlayers == -1 )
{
numPlayers = 1;
#ifdef _GAMECONSOLE
numPlayers = XBX_GetNumGameUsers();
#endif
pMembers->SetInt( "numPlayers", numPlayers );
}
pMembers->SetInt( "numSlots", numPlayers );
KeyValues *pMachine = pMembers->FindKey( "machine0");
if ( !pMachine )
{
pMachine = pMembers->CreateNewKey();
pMachine->SetName( "machine0" );
XUID machineid = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() )->GetXUID();
pMachine->SetUint64( "id", machineid );
pMachine->SetUint64( "flags", MatchSession_GetMachineFlags() );
pMachine->SetInt( "numPlayers", numPlayers );
pMachine->SetUint64( "dlcmask", g_pMatchFramework->GetMatchSystem()->GetDlcManager()->GetDataInfo()->GetUint64( "@info/installed" ) );
pMachine->SetString( "tuver", MatchSession_GetTuInstalledString() );
pMachine->SetInt( "ping", 0 );
for ( int k = 0; k < numPlayers; ++ k )
{
if ( KeyValues *pPlayer = pMachine->FindKey( CFmtStr( "player%d", k ), true ) )
{
int iController = 0;
#ifdef _GAMECONSOLE
iController = XBX_GetUserId( k );
#endif
IPlayerLocal *player = g_pPlayerManager->GetLocalPlayer( iController );
pPlayer->SetUint64( "xuid", player->GetXUID() );
pPlayer->SetString( "name", player->GetName() );
}
}
}
}
DevMsg( "CMatchSearcher::InitializeGameSettings adjusted settings:\n" );
KeyValuesDumpAsDevMsg( m_pSettings, 1 );
}
KeyValues * CMatchSearcher::GetSearchSettings()
{
return m_pSettings;
}
void CMatchSearcher::Destroy()
{
// Stop the search
if ( m_eState == STATE_SEARCHING )
{
#ifdef _X360
m_pCancelOverlappedJob = ThreadExecute( MMX360_CancelOverlapped, &m_xOverlapped ); // UpdateDormantOperations will clean the rest
MMX360_RegisterDormant( this );
return;
#endif
}
#ifdef _X360
if ( m_eState == STATE_CHECK_QOS )
{
g_pMatchExtensions->GetIXOnline()->XNetQosRelease( m_pQosResults );
m_pQosResults = NULL;
}
#endif
#if !defined( NO_STEAM )
while ( m_arrOutstandingAsyncOperation.Count() > 0 )
{
IMatchAsyncOperation *pAsyncOperation = m_arrOutstandingAsyncOperation.Tail();
m_arrOutstandingAsyncOperation.RemoveMultipleFromTail( 1 );
if ( pAsyncOperation )
pAsyncOperation->Release();
}
#endif
delete this;
}
void CMatchSearcher::OnSearchEvent( KeyValues *pNotify )
{
g_pMatchEventsSubscription->BroadcastEvent( pNotify );
}
#ifdef _X360
bool CMatchSearcher::UpdateDormantOperation()
{
if ( !m_pCancelOverlappedJob->IsFinished() )
return true; // keep running dormant
m_pCancelOverlappedJob->Release();
m_pCancelOverlappedJob = NULL;
delete this;
return false; // destroyed object, remove from dormant list
}
#endif
void CMatchSearcher::Update()
{
switch ( m_eState )
{
case STATE_INIT:
m_eState = STATE_SEARCHING;
// Session is searching
OnSearchEvent( new KeyValues(
"OnMatchSessionUpdate",
"state", "progress",
"progress", "searching"
) );
// Kick off the search
StartSearch();
break;
case STATE_SEARCHING:
// Waiting for session search to complete
#ifdef _X360
if ( XHasOverlappedIoCompleted( &m_xOverlapped ) )
Live_OnSessionSearchCompleted();
#endif
break;
#ifdef _X360
case STATE_CHECK_QOS:
// Keep checking for results or until the wait time expires
if ( Plat_FloatTime() > m_flQosTimeout ||
!m_pQosResults->cxnqosPending )
Live_OnQosCheckCompleted();
break;
#endif
#if !defined (NO_STEAM)
case STATE_WAITING_LOBBY_DATA_AND_PING:
{
bool bWaitLonger = ( ( Plat_MSTime() - m_uiQosTimeoutStartMS ) < ( mm_session_search_qos_timeout.GetFloat() * 1000.f ) );
if ( bWaitLonger )
{
bool bHasPendingLobbyData = false;
for ( int j = 0; j < m_arrSearchResults.Count(); ++ j )
{
SearchResult_t &sr = m_arrSearchResults[j];
if ( !sr.m_numPlayers )
{ // didn't even receive lobby data from Steam yet
bHasPendingLobbyData = true;
}
else if ( sr.m_svAdr.GetIPHostByteOrder() && ( sr.m_svPing <= 0 ) )
{ // received valid IP for lobby, still pinging
bHasPendingLobbyData = true;
}
}
if ( !bHasPendingLobbyData )
bWaitLonger = false;
}
if ( !bWaitLonger )
{
m_CallbackOnLobbyDataReceived.Unregister();
// Go ahead and start joining the results
AggregateSearchPassResults();
OnSearchPassDone( m_pSearchPass );
}
}
break;
#endif
}
}
#if !defined (NO_STEAM)
void CMatchSearcher::Steam_OnLobbyDataReceived( LobbyDataUpdate_t *pLobbyDataUpdate )
{
int iResultIndex = 0;
for ( ; iResultIndex < m_arrSearchResults.Count(); ++ iResultIndex )
{
if ( m_arrSearchResults[ iResultIndex ].m_uiLobbyId == pLobbyDataUpdate->m_ulSteamIDLobby )
break;
}
if ( !m_arrSearchResults.IsValidIndex( iResultIndex ) )
return;
SearchResult_t *pRes = &m_arrSearchResults[ iResultIndex ];
if ( !pLobbyDataUpdate->m_bSuccess )
{
DevMsg( "[MM] Could not get lobby data for lobby %llu (%llx)\n",
pRes->m_uiLobbyId, pRes->m_uiLobbyId );
pRes->m_numPlayers = -1; // set numPlayers to negative number to indicate a failure here
}
else
{
// Get num players
const char *numPlayers = steamapicontext->SteamMatchmaking()->GetLobbyData(
pRes->m_uiLobbyId, "members:numPlayers" );
if ( !numPlayers )
{
DevMsg( "[MM] Unable to get num players for lobby (%llx)\n",
pRes->m_uiLobbyId );
pRes->m_numPlayers = -1; // set numPlayers to negative number to indicate a failure here
}
else
{
pRes->m_numPlayers = V_atoi( numPlayers );
if ( !pRes->m_numPlayers )
pRes->m_numPlayers = -1; // set numPlayers to negative number to indicate a failure here
}
// Get the address of the server
const char* pServerAdr = steamapicontext->SteamMatchmaking()->GetLobbyData(
pRes->m_uiLobbyId, "server:adronline" );
if ( !pServerAdr )
{
// DevMsg( "[MM] Unable to get server address from lobby (%llx)\n",
// pRes->m_uiLobbyId );
}
else
{
pRes->m_svAdr.SetFromString( pServerAdr );
DevMsg ( "[MM] Lobby %d: id %llu (%llx), num Players %d, sv ip %s, pinging dist %d\n",
iResultIndex, pRes->m_uiLobbyId, pRes->m_uiLobbyId, pRes->m_numPlayers,
pRes->m_svAdr.ToString(), pRes->m_svPing );
// Ping server
IMatchAsyncOperation *pAsyncOperationPing = NULL;
g_pMatchExtensions->GetINetSupport()->ServerPing( pRes->m_svAdr, this, &pAsyncOperationPing );
m_arrOutstandingAsyncOperation.AddToTail( pAsyncOperationPing );
pRes->m_pAsyncOperationPingWeakRef = pAsyncOperationPing;
}
}
}
// Callback for server reservation check
void CMatchSearcher::OnOperationFinished( IMatchAsyncOperation *pOperation )
{
if ( !pOperation )
return;
if ( m_eState != STATE_WAITING_LOBBY_DATA_AND_PING )
return;
for ( int i = 0; i < m_arrSearchResults.Count(); ++ i )
{
if ( m_arrSearchResults[i].m_pAsyncOperationPingWeakRef != pOperation )
continue;
int result = pOperation->GetResult();
bool failed = ( pOperation->GetState() == AOS_FAILED );
SearchResult_t *pRes = &m_arrSearchResults[i];
pRes->m_svPing = ( failed ? -1 : ( result ? result : -1 ) );
if ( pRes->m_svPing < 0 )
{
DevMsg( "[MM] Failed pinging server %s for lobby#%d %llu (%llx)\n",
pRes->m_svAdr.ToString(), i, pRes->m_uiLobbyId, pRes->m_uiLobbyId );
}
else
{
DevMsg( "[MM] Successfully pinged server %s for lobby#%d %llu (%llx) = %d ms\n",
pRes->m_svAdr.ToString(), i, pRes->m_uiLobbyId, pRes->m_uiLobbyId, pRes->m_svPing );
}
m_arrSearchResults[i].m_pAsyncOperationPingWeakRef = NULL;
}
}
#endif
void CMatchSearcher::OnSearchDone()
{
m_eState = STATE_DONE;
}
void CMatchSearcher::AggregateSearchPassResults()
{
#ifndef NO_STEAM
extern ConVar mm_dedicated_search_maxping;
int nPingLimitMax = mm_dedicated_search_maxping.GetInt();
if ( nPingLimitMax <= 0 )
nPingLimitMax = 5000;
for ( int k = 0; k < mm_session_search_ping_buckets.GetInt(); ++ k )
{
int iPingLow = ( nPingLimitMax * k ) / mm_session_search_ping_buckets.GetInt();
int iPingHigh = ( nPingLimitMax * ( k + 1 ) ) / mm_session_search_ping_buckets.GetInt();
for ( int iResult = 0; iResult < m_arrSearchResults.Count(); ++ iResult )
{
SearchResult_t *pRes = &m_arrSearchResults[iResult];
if ( ( pRes->m_svPing > iPingLow ) && ( pRes->m_svPing <= iPingHigh ) )
{
m_arrSearchResultsAggregate.AddToTail( *pRes );
DevMsg ( "[MM] Search aggregated result%d / %d: id %llu (%llx), num Players %d, sv ip %s, dist %d\n",
m_arrSearchResultsAggregate.Count(), iResult, pRes->m_uiLobbyId, pRes->m_uiLobbyId, pRes->m_numPlayers,
pRes->m_svAdr.ToString(), pRes->m_svPing );
}
}
}
DevMsg ( "[MM] Search aggregated %d results\n", m_arrSearchResultsAggregate.Count() );
#endif
m_arrSearchResults.Purge();
}
void CMatchSearcher::OnSearchPassDone( KeyValues *pSearchPass )
{
// If we have enough results, then call it done
if ( m_arrSearchResultsAggregate.Count() >= mm_session_search_num_results.GetInt() )
{
OnSearchDone();
return;
}
// Evaluate if there is a nextpass condition
char const *szNextPassKeyName = "nextpass";
if ( KeyValues *pConditions = pSearchPass->FindKey( "nextpass?" ) )
{
// Inspect conditions and select which next pass will happen
}
if ( KeyValues *pNextPass = pSearchPass->FindKey( szNextPassKeyName ) )
{
StartSearchPass( pNextPass );
}
else
{
OnSearchDone();
}
}
#ifdef _X360
void CMatchSearcher::Live_OnSessionSearchCompleted()
{
DevMsg( "Received %d search results from Xbox LIVE.\n", GetXSearchResult()->dwSearchResults );
for( unsigned int i = 0; i < GetXSearchResult()->dwSearchResults; ++ i )
{
XSESSION_SEARCHRESULT const &xsr = GetXSearchResult()->pResults[i];
SearchResult_t sr = { xsr.info, NULL };
m_arrSearchResults.AddToTail( sr );
DevMsg( 2, "Result #%02d: %llx\n", i + 1, ( const uint64& ) xsr.info.sessionID );
}
if ( !m_arrSearchResults.Count() )
{
OnSearchPassDone( m_pSearchPass );
}
else
{
DevMsg( "Checking QOS with %d search results.\n", m_arrSearchResults.Count() );
Live_CheckSearchResultsQos();
}
}
void CMatchSearcher::Live_CheckSearchResultsQos()
{
m_eState = STATE_CHECK_QOS;
int nResults = m_arrSearchResults.Count();
CUtlVector< const void * > memQosData;
memQosData.SetCount( 3 * nResults );
const void ** bufQosData[3];
for ( int k = 0; k < ARRAYSIZE( bufQosData ); ++ k )
bufQosData[k] = &memQosData[ k * nResults ];
for ( int k = 0; k < m_arrSearchResults.Count(); ++ k )
{
SearchResult_t const &sr = m_arrSearchResults[k];
bufQosData[0][k] = &sr.m_info.hostAddress;
bufQosData[1][k] = &sr.m_info.sessionID;
bufQosData[2][k] = &sr.m_info.keyExchangeKey;
}
//
// Note: XNetQosLookup requires only 2 successful probes to be received from the host.
// This is much less than the recommended 8 probes because on a 10% data loss profile
// it is impossible to find the host when requiring 8 probes to be received.
m_flQosTimeout = Plat_FloatTime() + mm_session_search_qos_timeout.GetFloat();
int res = g_pMatchExtensions->GetIXOnline()->XNetQosLookup(
nResults,
reinterpret_cast< XNADDR const ** >( bufQosData[0] ),
reinterpret_cast< XNKID const ** >( bufQosData[1] ),
reinterpret_cast< XNKEY const ** >( bufQosData[2] ),
0, // number of security gateways to probe
NULL, // gateway ip addresses
NULL, // gateway service ids
2, // number of probes
0, // upstream bandwith to use (0 = default)
0, // flags - not supported
NULL, // signal event
&m_pQosResults );// results
if ( res != 0 )
{
DevWarning( "OnlineSearch::Live_CheckSearchResultsQos - XNetQosLookup failed (code = 0x%08X)!\n", res );
m_arrSearchResults.Purge();
OnSearchPassDone( m_pSearchPass );
}
}
void CMatchSearcher::Live_OnQosCheckCompleted()
{
for ( uint k = m_pQosResults->cxnqos; k --> 0; )
{
XNQOSINFO &xqi = m_pQosResults->axnqosinfo[k];
BYTE uNeedFlags = XNET_XNQOSINFO_TARGET_CONTACTED | XNET_XNQOSINFO_DATA_RECEIVED;
if ( ( ( xqi.bFlags & uNeedFlags ) != uNeedFlags) ||
( xqi.bFlags & XNET_XNQOSINFO_TARGET_DISABLED ) )
{
m_arrSearchResults.Remove( k );
continue;
}
extern ConVar mm_dedicated_search_maxping;
if ( mm_dedicated_search_maxping.GetInt() > 0 &&
xqi.wRttMedInMsecs > mm_dedicated_search_maxping.GetInt() )
{
m_arrSearchResults.Remove( k );
continue;
}
if ( xqi.cbData && xqi.pbData )
{
MM_GameDetails_QOS_t gd = { xqi.pbData, xqi.cbData, xqi.wRttMedInMsecs };
Assert( !m_arrSearchResults[k].m_pGameDetails );
m_arrSearchResults[k].m_pGameDetails = g_pMatchFramework->GetMatchNetworkMsgController()->UnpackGameDetailsFromQOS( &gd );
}
}
g_pMatchExtensions->GetIXOnline()->XNetQosRelease( m_pQosResults );
m_pQosResults = NULL;
// Go ahead and start joining the results
DevMsg( "Qos completed with %d search results.\n", m_arrSearchResults.Count() );
AggregateSearchPassResults();
OnSearchPassDone( m_pSearchPass );
}
#elif !defined( NO_STEAM )
void CMatchSearcher::Steam_OnLobbyMatchListReceived( LobbyMatchList_t *pLobbyMatchList, bool bError )
{
Msg( "[MM] Received %d search results.\n", bError ? 0 : pLobbyMatchList->m_nLobbiesMatching );
m_CallbackOnLobbyDataReceived.Register( this, &CMatchSearcher::Steam_OnLobbyDataReceived );
// Walk through search results and request lobby data
for ( int iLobby = 0; iLobby < (int) ( bError ? 0 : pLobbyMatchList->m_nLobbiesMatching ); ++ iLobby )
{
uint64 uiLobbyId = steamapicontext->SteamMatchmaking()->GetLobbyByIndex( iLobby ).ConvertToUint64();
SearchResult_t sr = { uiLobbyId };
sr.m_pGameDetails = NULL;
sr.m_svAdr.SetFromString( "0.0.0.0" );
sr.m_svPing = 0;
sr.m_numPlayers = 0;
sr.m_pAsyncOperationPingWeakRef = NULL;
m_arrSearchResults.AddToTail( sr );
steamapicontext->SteamMatchmaking()->RequestLobbyData( sr.m_uiLobbyId );
}
m_eState = STATE_WAITING_LOBBY_DATA_AND_PING; // Waiting for lobby data
m_uiQosPingLastMS = m_uiQosTimeoutStartMS = Plat_MSTime();
}
KeyValues * CMatchSearcher::SearchResult_t::GetGameDetails() const
{
if ( !m_pGameDetails )
{
m_pGameDetails = g_pMatchFramework->GetMatchNetworkMsgController()->UnpackGameDetailsFromSteamLobby( m_uiLobbyId );
}
return m_pGameDetails;
}
#endif
void CMatchSearcher::StartSearch()
{
Assert( !m_pSessionSearchTree );
m_pSessionSearchTree = g_pMMF->GetMatchTitleGameSettingsMgr()->DefineSessionSearchKeys( m_pSettings );
m_autodelete_pSessionSearchTree.Assign( m_pSessionSearchTree );
Assert( m_pSessionSearchTree );
if ( !m_pSessionSearchTree )
{
DevWarning( "OnlineSearch::StartSearch failed to build filter list!\n" );
OnSearchDone();
}
else
{
StartSearchPass( m_pSessionSearchTree );
}
}
void CMatchSearcher::StartSearchPass( KeyValues *pSearchPass )
{
KeyValues *pSearchParams = pSearchPass;
m_pSearchPass = pSearchPass;
// Make sure we have fresh buffer for search results
m_arrSearchResults.Purge();
m_eState = STATE_SEARCHING;
DevMsg( "OnlineSearch::StartSearchPass:\n" );
KeyValuesDumpAsDevMsg( pSearchParams, 1 );
#ifdef _X360
DWORD dwSearchRule = pSearchParams->GetInt( "rule" );
m_arrContexts.RemoveAll();
if ( KeyValues *pContexts = pSearchParams->FindKey( "Contexts" ) )
{
for ( KeyValues *val = pContexts->GetFirstValue(); val; val = val->GetNextValue() )
{
XUSER_CONTEXT ctx = { 0 };
ctx.dwContextId = atoi( val->GetName() );
if ( val->GetDataType() == KeyValues::TYPE_INT )
{
ctx.dwValue = val->GetInt();
m_arrContexts.AddToTail( ctx );
}
}
}
m_arrProperties.RemoveAll();
if ( KeyValues *pContexts = pSearchParams->FindKey( "Properties" ) )
{
for ( KeyValues *val = pContexts->GetFirstValue(); val; val = val->GetNextValue() )
{
XUSER_PROPERTY prop = { 0 };
prop.dwPropertyId = atoi( val->GetName() );
if ( val->GetDataType() == KeyValues::TYPE_INT )
{
prop.value.type = XUSER_DATA_TYPE_INT32;
prop.value.nData = val->GetInt();
m_arrProperties.AddToTail( prop );
}
}
}
DWORD ret = ERROR_SUCCESS;
DWORD numBytes = 0;
DWORD dwNumSlotsRequired = pSearchParams->GetInt( "numPlayers" );
//
// Issue the asynchrounous session search request
//
ret = g_pMatchExtensions->GetIXOnline()->XSessionSearchEx(
dwSearchRule, XBX_GetPrimaryUserId(), mm_session_search_num_results.GetInt(),
dwNumSlotsRequired,
m_arrProperties.Count(), m_arrContexts.Count(),
m_arrProperties.Base(), m_arrContexts.Base(),
&numBytes, NULL, NULL
);
// Log the search request to read X360 queries easier
DevMsg( "XSessionSearchEx by rule %d for slots %d\n", dwSearchRule, dwNumSlotsRequired );
for ( int k = 0; k < m_arrContexts.Count(); ++ k )
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 );
for ( int k = 0; k < m_arrProperties.Count(); ++ k )
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 );
DevMsg( "will use %u bytes buffer.\n", numBytes );
if ( ERROR_INSUFFICIENT_BUFFER == ret && numBytes > 0 )
{
m_bufSearchResultHeader.EnsureCapacity( numBytes );
ZeroMemory( GetXSearchResult(), numBytes );
ZeroMemory( &m_xOverlapped, sizeof( m_xOverlapped ) );
DevMsg( "Searching...\n" );
ret = g_pMatchExtensions->GetIXOnline()->XSessionSearchEx(
dwSearchRule, XBX_GetPrimaryUserId(), mm_session_search_num_results.GetInt(),
dwNumSlotsRequired,
m_arrProperties.Count(), m_arrContexts.Count(),
m_arrProperties.Base(), m_arrContexts.Base(),
&numBytes, GetXSearchResult(), &m_xOverlapped
);
if ( ret == ERROR_IO_PENDING )
return;
}
// Otherwise search failed
DevWarning( "XSessionSearchEx failed (code = 0x%08X)\n", ret );
ZeroMemory( &m_xOverlapped, sizeof( m_xOverlapped ) );
OnSearchPassDone( m_pSearchPass );
#elif !defined( NO_STEAM )
ISteamMatchmaking *mm = steamapicontext->SteamMatchmaking();
// Configure filters
DWORD dwNumSlotsRequired = pSearchParams->GetInt( "numPlayers" );
mm->AddRequestLobbyListFilterSlotsAvailable( dwNumSlotsRequired );
// Set filters
char const * arrKeys[] = { "Filter<", "Filter<=", "Filter=", "Filter<>", "Filter>=", "Filter>" };
ELobbyComparison nValueCmp[] = { k_ELobbyComparisonLessThan, k_ELobbyComparisonEqualToOrLessThan, k_ELobbyComparisonEqual, k_ELobbyComparisonNotEqual, k_ELobbyComparisonEqualToOrGreaterThan, k_ELobbyComparisonGreaterThan };
for ( int k = 0; k < ARRAYSIZE( arrKeys ); ++ k )
{
if ( KeyValues *kv = pSearchParams->FindKey( arrKeys[k] ) )
{
for ( KeyValues *val = kv->GetFirstValue(); val; val = val->GetNextValue() )
{
if ( val->GetDataType() == KeyValues::TYPE_STRING )
{
mm->AddRequestLobbyListStringFilter( val->GetName(), val->GetString(), nValueCmp[k] );
}
else if ( val->GetDataType() == KeyValues::TYPE_INT )
{
mm->AddRequestLobbyListNumericalFilter( val->GetName(), val->GetInt(), nValueCmp[k] );
}
}
}
}
// Set ordering near values
if ( KeyValues *kv = pSearchParams->FindKey( "Near" ) )
{
for ( KeyValues *val = kv->GetFirstValue(); val; val = val->GetNextValue() )
{
if ( val->GetDataType() == KeyValues::TYPE_INT )
{
mm->AddRequestLobbyListNearValueFilter( val->GetName(), val->GetInt() );
}
else
{
// TODO: need to set near value filter for strings
}
}
}
ELobbyDistanceFilter eFilter = k_ELobbyDistanceFilterDefault;
switch ( mm_session_search_distance.GetInt() )
{
case k_ELobbyDistanceFilterClose:
case k_ELobbyDistanceFilterDefault:
case k_ELobbyDistanceFilterFar:
case k_ELobbyDistanceFilterWorldwide:
eFilter = ( ELobbyDistanceFilter ) mm_session_search_distance.GetInt();
break;
}
// Set distance filter to Near
mm->AddRequestLobbyListDistanceFilter( eFilter );
// Set dependent lobby
if ( uint64 uiDependentLobbyId = pSearchParams->GetUint64( "DependentLobby", 0ull ) )
{
mm->AddRequestLobbyListCompatibleMembersFilter( uiDependentLobbyId );
}
// RequestLobbyList - will get called back at Steam_OnLobbyMatchListReceived
DevMsg( "Searching...\n" );
SteamAPICall_t hCall = mm->RequestLobbyList();
m_CallbackOnLobbyMatchListReceived.Set( hCall, this, &CMatchSearcher::Steam_OnLobbyMatchListReceived );
#endif
}
//
// Results retrieval overrides
//
bool CMatchSearcher::IsSearchFinished() const
{
return m_eState == STATE_DONE;
}
int CMatchSearcher::GetNumSearchResults() const
{
return IsSearchFinished() ? m_arrSearchResultsAggregate.Count() : 0;
}
CMatchSearcher::SearchResult_t const & CMatchSearcher::GetSearchResult( int idx ) const
{
if ( !IsSearchFinished() || !m_arrSearchResultsAggregate.IsValidIndex( idx ) )
{
Assert( false );
static SearchResult_t s_empty;
return s_empty;
}
else
{
return m_arrSearchResultsAggregate[ idx ];
}
}