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
794 lines
23 KiB
//===== Copyright � 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 ];
|
|
}
|
|
}
|