|
|
//===== 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_ignored_sessions_forget_time( "mm_ignored_sessions_forget_time", "600", FCVAR_DEVELOPMENTONLY ); static ConVar mm_ignored_sessions_forget_pass( "mm_ignored_sessions_forget_pass", "5", FCVAR_DEVELOPMENTONLY );
class CIgnoredSessionsMgr { public: CIgnoredSessionsMgr();
public: void Reset(); void OnSearchStarted(); bool IsIgnored( XNKID xid ); void Ignore( XNKID xid );
protected: struct SessionSearchPass_t { double m_flTime; int m_nSearchCounter; };
static bool XNKID_LessFunc( const XNKID &lhs, const XNKID &rhs ) { return ( (uint64 const&) lhs ) < ( (uint64 const&) rhs ); }
CUtlMap< XNKID, SessionSearchPass_t > m_IgnoredSessionsAndTime; int m_nSearchCounter; };
static CIgnoredSessionsMgr g_IgnoredSessionsMgr; static CUtlMap< uint32, float > g_mapValidatedWhitelistCacheTimestamps( DefLessFunc( uint32 ) );
CON_COMMAND_F( mm_ignored_sessions_reset, "Reset ignored sessions", FCVAR_DEVELOPMENTONLY ) { g_IgnoredSessionsMgr.Reset(); DevMsg( "Reset ignored sessions" ); }
CIgnoredSessionsMgr::CIgnoredSessionsMgr() : m_IgnoredSessionsAndTime( XNKID_LessFunc ), m_nSearchCounter( 0 ) { }
void CIgnoredSessionsMgr::Reset() { m_nSearchCounter = 0; m_IgnoredSessionsAndTime.RemoveAll(); }
void CIgnoredSessionsMgr::OnSearchStarted() { ++ m_nSearchCounter;
double fNow = Plat_FloatTime(); double const fKeepIgnoredTime = mm_ignored_sessions_forget_time.GetFloat(); int const numIgnoredSearches = mm_ignored_sessions_forget_pass.GetInt();
// Keep sessions for only so long...
for ( int x = m_IgnoredSessionsAndTime.FirstInorder(); x != m_IgnoredSessionsAndTime.InvalidIndex(); ) { SessionSearchPass_t ssp = m_IgnoredSessionsAndTime.Element( x ); int xNext = m_IgnoredSessionsAndTime.NextInorder( x ); if ( fabs( fNow - ssp.m_flTime ) > fKeepIgnoredTime && m_nSearchCounter - ssp.m_nSearchCounter > numIgnoredSearches ) { m_IgnoredSessionsAndTime.RemoveAt( x ); } x = xNext; } }
bool CIgnoredSessionsMgr::IsIgnored( XNKID xid ) { return ( m_IgnoredSessionsAndTime.Find( xid ) != m_IgnoredSessionsAndTime.InvalidIndex() ); }
void CIgnoredSessionsMgr::Ignore( XNKID xid ) { if ( ( const uint64 & )xid == 0ull ) return;
SessionSearchPass_t ssp = { Plat_FloatTime(), m_nSearchCounter }; m_IgnoredSessionsAndTime.InsertOrReplace( xid, ssp ); }
//
// CMatchSessionOnlineSearch
//
// Implementation of an online session search (aka matchmaking)
//
CMatchSessionOnlineSearch::CMatchSessionOnlineSearch( KeyValues *pSettings ) : m_pSettings( pSettings->MakeCopy() ), m_autodelete_pSettings( m_pSettings ), m_eState( STATE_INIT ), m_pSysSession( NULL ), m_pMatchSearcher( NULL ), m_result( RESULT_UNDEFINED ), m_pSysSessionConTeam (NULL), #if !defined( NO_STEAM )
m_pServerListListener( NULL ), #endif
m_flInitializeTimestamp( 0.0f ) { DevMsg( "Created CMatchSessionOnlineSearch:\n" ); KeyValuesDumpAsDevMsg( m_pSettings, 1 ); }
CMatchSessionOnlineSearch::CMatchSessionOnlineSearch() : m_pSettings( NULL ), m_autodelete_pSettings( (KeyValues*)NULL ), m_eState( STATE_INIT ), m_pSysSession( NULL ), m_pMatchSearcher( NULL ), m_result( RESULT_UNDEFINED ), m_pSysSessionConTeam (NULL), m_flInitializeTimestamp( 0.0f ) { }
CMatchSessionOnlineSearch::~CMatchSessionOnlineSearch() { if ( m_pMatchSearcher ) m_pMatchSearcher->Destroy(); m_pMatchSearcher = NULL;
DevMsg( "Destroying CMatchSessionOnlineSearch:\n" ); KeyValuesDumpAsDevMsg( m_pSettings, 1 ); }
KeyValues * CMatchSessionOnlineSearch::GetSessionSettings() { return m_pSettings; }
void CMatchSessionOnlineSearch::UpdateSessionSettings( KeyValues *pSettings ) { Warning( "CMatchSessionOnlineSearch::UpdateSessionSettings is unavailable in state %d!\n", m_eState ); Assert( !"CMatchSessionOnlineSearch::UpdateSessionSettings is unavailable!\n" ); }
void CMatchSessionOnlineSearch::Command( KeyValues *pCommand ) { Warning( "CMatchSessionOnlineSearch::Command is unavailable!\n" ); Assert( !"CMatchSessionOnlineSearch::Command is unavailable!\n" ); }
uint64 CMatchSessionOnlineSearch::GetSessionID() { return 0; }
#if !defined( NO_STEAM )
extern volatile uint32 *g_hRankingSetupCallHandle; void CMatchSessionOnlineSearch::SetupSteamRankingConfiguration() { KeyValues *kvNotification = new KeyValues( "SetupSteamRankingConfiguration" ); kvNotification->SetPtr( "settingsptr", m_pSettings ); kvNotification->SetPtr( "callhandleptr", ( void * ) &g_hRankingSetupCallHandle );
g_pMatchEventsSubscription->BroadcastEvent( kvNotification ); }
bool CMatchSessionOnlineSearch::IsSteamRankingConfigured() const { return !g_hRankingSetupCallHandle || !*g_hRankingSetupCallHandle; } #endif
extern ConVar mm_session_sys_ranking_timeout; extern ConVar mm_session_search_qos_timeout;
void CMatchSessionOnlineSearch::Update() { switch ( m_eState ) { case STATE_INIT: if ( !m_flInitializeTimestamp ) { m_flInitializeTimestamp = Plat_FloatTime(); #if !defined( NO_STEAM )
SetupSteamRankingConfiguration(); #endif
} #if !defined( NO_STEAM )
if ( !IsSteamRankingConfigured() && ( Plat_FloatTime() < m_flInitializeTimestamp + mm_session_sys_ranking_timeout.GetFloat() ) ) break; #endif
m_eState = STATE_SEARCHING;
// Kick off the search
g_IgnoredSessionsMgr.OnSearchStarted(); m_pMatchSearcher = OnStartSearching(); // Update our settings with match searcher
m_pSettings->deleteThis(); m_pSettings = m_pMatchSearcher->GetSearchSettings()->MakeCopy(); m_autodelete_pSettings.Assign( m_pSettings );
// Run the first frame update on the searcher
m_pMatchSearcher->Update(); break;
case STATE_SEARCHING: // Waiting for session search to complete
m_pMatchSearcher->Update(); break;
case STATE_JOIN_NEXT: StartJoinNextFoundSession(); break;
#if !defined( NO_STEAM )
case STATE_VALIDATING_WHITELIST: if ( Plat_FloatTime() > m_flInitializeTimestamp + mm_session_search_qos_timeout.GetFloat() ) { DevWarning( "Steam whitelist validation timed out.\n" ); Steam_OnDedicatedServerListFetched(); } break; #endif
case STATE_JOINING: // Waiting for the join negotiation
if ( m_pSysSession ) { m_pSysSession->Update(); }
if (m_pSysSessionConTeam) { m_pSysSessionConTeam->Update();
switch ( m_pSysSessionConTeam->GetResult() ) { case CSysSessionConTeamHost::RESULT_SUCCESS: OnSearchCompletedSuccess( NULL, m_pSettings ); break;
case CSysSessionConTeamHost::RESULT_FAIL: m_pSysSessionConTeam->Destroy(); m_pSysSessionConTeam = NULL; // Try next session
m_eState = STATE_JOIN_NEXT; break; } } break; } }
void CMatchSessionOnlineSearch::Destroy() { // Stop the search
if ( m_pMatchSearcher ) { m_pMatchSearcher->Destroy(); m_pMatchSearcher = NULL; }
// If we are in the middle of connecting,
// abort
if ( m_pSysSession ) { m_pSysSession->Destroy(); m_pSysSession = NULL; }
if ( m_pSysSessionConTeam ) { m_pSysSessionConTeam->Destroy(); m_pSysSessionConTeam = NULL; }
#if !defined( NO_STEAM )
if ( m_pServerListListener ) { m_pServerListListener->Destroy(); m_pServerListListener = NULL; } #endif
delete this; }
void CMatchSessionOnlineSearch::DebugPrint() { DevMsg( "CMatchSessionOnlineSearch [ state=%d ]\n", m_eState ); DevMsg( "System data:\n" ); KeyValuesDumpAsDevMsg( GetSessionSystemData(), 1 );
DevMsg( "Settings data:\n" ); KeyValuesDumpAsDevMsg( GetSessionSettings(), 1 ); if ( m_pSysSession ) m_pSysSession->DebugPrint(); else DevMsg( "SysSession is NULL\n" );
DevMsg( "Search results outstanding: %d\n", m_arrSearchResults.Count() ); }
void CMatchSessionOnlineSearch::OnEvent( KeyValues *pEvent ) { char const *szEvent = pEvent->GetName();
if ( !Q_stricmp( "mmF->SysSessionUpdate", szEvent ) ) { if ( m_pSysSession && pEvent->GetPtr( "syssession", NULL ) == m_pSysSession ) { // This is our session
switch ( m_eState ) { case STATE_JOINING: // Session was creating
if ( char const *szError = pEvent->GetString( "error", NULL ) ) { // Destroy the session
m_pSysSession->Destroy(); m_pSysSession = NULL;
// Go ahead and join next available session
m_eState = STATE_JOIN_NEXT; } else { // We have received an entirely new "settings" data and copied that to our "settings" data
m_eState = STATE_CLOSING;
// Now we need to create a new client session
CSysSessionClient *pSysSession = m_pSysSession; KeyValues *pSettings = m_pSettings;
// Release ownership of the resources since new match session now owns them
m_pSysSession = NULL; m_pSettings = NULL; m_autodelete_pSettings.Assign( NULL );
OnSearchCompletedSuccess( pSysSession, pSettings ); return; } break; } } } }
void CMatchSessionOnlineSearch::OnSearchEvent( KeyValues *pNotify ) { g_pMatchEventsSubscription->BroadcastEvent( pNotify ); }
CSysSessionClient * CMatchSessionOnlineSearch::OnBeginJoiningSearchResult() { return new CSysSessionClient( m_pSettings ); }
void CMatchSessionOnlineSearch::OnSearchDoneNoResultsMatch() { m_eState = STATE_CLOSING;
// Reset ignored session tracker
g_IgnoredSessionsMgr.Reset();
// Just go ahead and create the session
KeyValues *pSettings = m_pSettings;
m_pSettings = NULL; m_autodelete_pSettings.Assign( NULL );
OnSearchCompletedEmpty( pSettings ); }
void CMatchSessionOnlineSearch::OnSearchCompletedSuccess( CSysSessionClient *pSysSession, KeyValues *pSettings ) { m_result = RESULT_SUCCESS;
// Note that m_pSysSessionConTeam will be NULL if this is an individual joining a
// match that was started with con team
KeyValues *teamMatch = pSettings->FindKey( "options/conteammatch" ); if ( teamMatch && m_pSysSessionConTeam ) { DevMsg( "OnlineSearch - ConTeam host reserved session\n" ); KeyValuesDumpAsDevMsg( pSettings ); int numPlayers, sides[10]; uint64 playerIds[10];
if ( !m_pSysSessionConTeam->GetPlayerSidesAssignment( &numPlayers, playerIds, sides ) ) { // Something went badly wrong, bail
m_result = RESULT_FAIL; } else { // Send out a "joinsession" event. This is picked up by the host and sent out
// to all machines machines in lobby.
KeyValues *joinSession = new KeyValues( "OnMatchSessionUpdate", "state", "joinconteamsession" ); #if defined (_X360)
uint64 sessionId = pSettings->GetUint64( "options/sessionid", 0 ); const char *sessionInfo = pSettings->GetString( "options/sessioninfo", "" ); joinSession->SetUint64( "sessionid", sessionId ); joinSession->SetString( "sessioninfo", sessionInfo );
// Unpack sessionHostData
KeyValues *pSessionHostData = (KeyValues*)pSettings->GetPtr( "options/sessionHostData" ); if ( pSessionHostData ) { KeyValues *pSessionHostDataUnpacked = joinSession->CreateNewKey(); pSessionHostDataUnpacked->SetName("sessionHostDataUnpacked"); pSessionHostData->CopySubkeys( pSessionHostDataUnpacked ); }
#else
joinSession->SetUint64( "sessionid", m_pSysSessionConTeam->GetSessionID() ); #endif
KeyValues *pTeamMembers = joinSession->CreateNewKey(); pTeamMembers->SetName( "teamMembers" ); pTeamMembers->SetInt( "numPlayers", numPlayers ); // Assign players to different teams
for ( int i = 0; i < numPlayers; i++ ) { KeyValues *pTeamPlayer = pTeamMembers->CreateNewKey(); pTeamPlayer->SetName( CFmtStr( "player%d", i ) ); pTeamPlayer->SetUint64( "xuid", playerIds[i] ); pTeamPlayer->SetInt( "team", sides[i] ); }
OnSearchEvent( joinSession ); } } else { // Destroy our instance and point at the new match interface
CMatchSessionOnlineClient *pNewSession = new CMatchSessionOnlineClient( pSysSession, pSettings ); g_pMMF->SetCurrentMatchSession( pNewSession ); this->Destroy();
DevMsg( "OnlineSearch - client fully connected to session, search finished.\n" ); pNewSession->OnClientFullyConnectedToSession(); } }
void CMatchSessionOnlineSearch::OnSearchCompletedEmpty( KeyValues *pSettings ) { KeyValues::AutoDelete autodelete_pSettings( pSettings );
m_result = RESULT_FAIL;
KeyValues *notify = new KeyValues( "OnMatchSessionUpdate", "state", "progress", "progress", "searchempty" ); notify->SetPtr( "settingsptr", pSettings );
OnSearchEvent( notify ); if ( !Q_stricmp( pSettings->GetString( "options/searchempty" ), "close" ) ) { g_pMatchFramework->CloseSession(); return; }
// If this is a team session then stop here and let the team host decide what
// to do next
KeyValues *teamMatch = pSettings->FindKey( "options/conteammatch" ); if ( teamMatch ) { return; }
// Preserve the "options/bypasslobby" key
bool bypassLobby = pSettings->GetBool( "options/bypasslobby", false );
// Preserve the "options/server" key
char serverType[64]; const char *prevServerType = pSettings->GetString( "options/server", NULL ); if ( prevServerType ) { Q_strncpy( serverType, prevServerType, sizeof( serverType ) ); }
// Remove "options" key
if ( KeyValues *kvOptions = pSettings->FindKey( "options" ) ) { pSettings->RemoveSubKey( kvOptions ); kvOptions->deleteThis(); } pSettings->SetString( "options/createreason", "searchempty" ); if ( bypassLobby ) { pSettings->SetBool( "options/bypasslobby", bypassLobby ); } if ( prevServerType ) { pSettings->SetString( "options/server", serverType ); }
DevMsg( "Search completed empty - creating a new session\n" ); KeyValuesDumpAsDevMsg( pSettings );
g_pMatchFramework->CreateSession( pSettings ); }
void CMatchSessionOnlineSearch::UpdateTeamProperties( KeyValues *pTeamProperties ) { }
void CMatchSessionOnlineSearch::StartJoinNextFoundSession() { if ( !m_arrSearchResults.Count() ) { OnSearchDoneNoResultsMatch(); return; }
// Session is joining
KeyValues *notify = new KeyValues( "OnMatchSessionUpdate", "state", "progress", "progress", "searchresult" ); notify->SetInt( "numResults", m_arrSearchResults.Count() ); OnSearchEvent( notify );
// Peek at the next search result
CMatchSearcher::SearchResult_t const &sr = *m_arrSearchResults.Head();
// Register it into the ignored session pool
g_IgnoredSessionsMgr.Ignore( sr.GetXNKID() );
// Make a validation query
ValidateSearchResultWhitelist(); }
static uint32 OfficialWhitelistClientCachedAddress( uint32 uiServerIP ) { /** Removed for partner depot **/ return 0; }
void CMatchSessionOnlineSearch::ValidateSearchResultWhitelist() { #if !defined( NO_STEAM )
// In case of official matchmaking we need to validate that the server
// that we are about to join actually is whitelisted official server
if ( !m_pSettings->GetInt( "game/hosted" ) ) { // Peek at the next search result
CMatchSearcher::SearchResult_t const &sr = *m_arrSearchResults.Head(); if ( ( g_mapValidatedWhitelistCacheTimestamps.Find( sr.m_svAdr.GetIPHostByteOrder() ) == g_mapValidatedWhitelistCacheTimestamps.InvalidIndex() ) && !OfficialWhitelistClientCachedAddress( sr.m_svAdr.GetIPHostByteOrder() ) ) { // This server needs to be validated
m_flInitializeTimestamp = Plat_FloatTime(); m_eState = STATE_VALIDATING_WHITELIST;
CUtlVector< MatchMakingKeyValuePair_t > filters; filters.EnsureCapacity( 10 ); filters.AddToTail( MatchMakingKeyValuePair_t( "gamedir", COM_GetModDirectory() ) ); filters.AddToTail( MatchMakingKeyValuePair_t( "addr", sr.m_svAdr.ToString() ) ); filters.AddToTail( MatchMakingKeyValuePair_t( "white", "1" ) );
m_pServerListListener = new CServerListListener( this, filters ); return; } } #endif
ConnectJoinLobbyNextFoundSession(); }
#if !defined( NO_STEAM )
CMatchSessionOnlineSearch::CServerListListener::CServerListListener( CMatchSessionOnlineSearch *pDsSearcher, CUtlVector< MatchMakingKeyValuePair_t > &filters ) : m_pOuter( pDsSearcher ), m_hRequest( NULL ) { MatchMakingKeyValuePair_t *pFilter = filters.Base(); DevMsg( 1, "Requesting dedicated whitelist validation...\n" ); for (int i = 0; i < filters.Count(); i++) { DevMsg("Filter %d: %s=%s\n", i, filters.Element(i).m_szKey, filters.Element(i).m_szValue); } m_hRequest = steamapicontext->SteamMatchmakingServers()->RequestInternetServerList( ( AppId_t ) g_pMatchFramework->GetMatchTitle()->GetTitleID(), &pFilter, filters.Count(), this ); }
void CMatchSessionOnlineSearch::CServerListListener::Destroy() { m_pOuter = NULL;
if ( m_hRequest ) steamapicontext->SteamMatchmakingServers()->ReleaseRequest( m_hRequest ); m_hRequest = NULL;
delete this; }
void CMatchSessionOnlineSearch::CServerListListener::HandleServerResponse( HServerListRequest hReq, int iServer, bool bResponded ) { // Register the result
if ( bResponded ) { gameserveritem_t *pServer = steamapicontext->SteamMatchmakingServers() ->GetServerDetails( hReq, iServer ); DevMsg( 1, "Successfully validated whitelist for %s...\n", pServer->m_NetAdr.GetConnectionAddressString() ); g_mapValidatedWhitelistCacheTimestamps.InsertOrReplace( pServer->m_NetAdr.GetIP(), Plat_FloatTime() ); } }
void CMatchSessionOnlineSearch::CServerListListener::RefreshComplete( HServerListRequest hReq, EMatchMakingServerResponse response ) { if ( m_pOuter ) { m_pOuter->Steam_OnDedicatedServerListFetched(); } }
void CMatchSessionOnlineSearch::Steam_OnDedicatedServerListFetched() { if ( m_pServerListListener ) { m_pServerListListener->Destroy(); m_pServerListListener = NULL; }
// Peek at the next search result
if ( m_arrSearchResults.Count() ) { CMatchSearcher::SearchResult_t const &sr = *m_arrSearchResults.Head(); if ( g_mapValidatedWhitelistCacheTimestamps.Find( sr.m_svAdr.GetIPHostByteOrder() ) == g_mapValidatedWhitelistCacheTimestamps.InvalidIndex() ) { DevWarning( 1, "Failed to validate whitelist for %s...\n", sr.m_svAdr.ToString( true ) ); FOR_EACH_VEC_BACK( m_arrSearchResults, itSR ) { if ( m_arrSearchResults[itSR]->m_svAdr.GetIPHostByteOrder() == sr.m_svAdr.GetIPHostByteOrder() ) { m_arrSearchResults.Remove( itSR ); } } } }
m_eState = STATE_JOIN_NEXT; }
#endif
void CMatchSessionOnlineSearch::ConnectJoinLobbyNextFoundSession() { // Pop the search result
CMatchSearcher::SearchResult_t const &sr = *m_arrSearchResults.Head(); m_arrSearchResults.RemoveMultipleFromHead( 1 );
// Set the settings to connect with
if ( KeyValues *kvOptions = m_pSettings->FindKey( "options", true ) ) { #ifdef _X360
kvOptions->SetUint64( "sessionid", ( const uint64 & ) sr.m_info.sessionID );
char chSessionInfo[ XSESSION_INFO_STRING_LENGTH ] = {0}; MMX360_SessionInfoToString( sr.m_info, chSessionInfo ); kvOptions->SetString( "sessioninfo", chSessionInfo );
kvOptions->SetPtr( "sessionHostData", sr.GetGameDetails() );
KeyValuesDumpAsDevMsg( sr.GetGameDetails(), 1, 2 ); #else
kvOptions->SetUint64( "sessionid", sr.m_uiLobbyId ); #endif
}
// Trigger client session creation
Msg( "[MM] Joining session %llx, %d search results remaining...\n", m_pSettings->GetUint64( "options/sessionid", 0ull ), m_arrSearchResults.Count() );
KeyValues *teamMatch = m_pSettings->FindKey( "options/conteammatch" ); if ( teamMatch ) { m_pSysSessionConTeam = new CSysSessionConTeamHost( m_pSettings ); } else { m_pSysSession = OnBeginJoiningSearchResult(); }
m_eState = STATE_JOINING; }
CMatchSearcher_OnlineSearch::CMatchSearcher_OnlineSearch( CMatchSessionOnlineSearch *pSession, KeyValues *pSettings ) : CMatchSearcher( pSettings ), m_pSession( pSession ) { }
void CMatchSearcher_OnlineSearch::OnSearchEvent( KeyValues *pNotify ) { m_pSession->OnSearchEvent( pNotify ); }
void CMatchSearcher_OnlineSearch::OnSearchDone() { // Let the base searcher finalize results
CMatchSearcher::OnSearchDone();
// Iterate over search results
for ( int k = 0, kNum = GetNumSearchResults(); k < kNum; ++ k ) { SearchResult_t const &sr = GetSearchResult( k ); if ( !g_IgnoredSessionsMgr.IsIgnored( sr.GetXNKID() ) ) m_pSession->m_arrSearchResults.AddToTail( &sr ); }
if ( !m_pSession->m_arrSearchResults.Count() ) { m_pSession->OnSearchDoneNoResultsMatch(); return; }
// Go ahead and start joining the results
DevMsg( "Establishing connection with %d search results.\n", m_pSession->m_arrSearchResults.Count() ); m_pSession->m_eState = m_pSession->STATE_JOIN_NEXT; }
CMatchSearcher * CMatchSessionOnlineSearch::OnStartSearching() { CMatchSearcher *pMS = new CMatchSearcher_OnlineSearch( this, m_pSettings->MakeCopy() );
// Let the title extend the game settings
g_pMMF->GetMatchTitleGameSettingsMgr()->InitializeGameSettings( pMS->GetSearchSettings(), "search_online" );
DevMsg( "CMatchSearcher_OnlineSearch title adjusted settings:\n" ); KeyValuesDumpAsDevMsg( pMS->GetSearchSettings(), 1 );
return pMS; }
|