Team Fortress 2 Source Code as on 22/4/2020
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.

4706 lines
148 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. #include "cbase.h"
  3. #include "tf_gc_client.h"
  4. #include "gcsdk/gcsdk_auto.h"
  5. #include "tf_gcmessages.h"
  6. #include "kvpacker.h"
  7. #include "tf_party.h"
  8. // XXX(JohnS): Eventually, we want to send a smaller lobby object to clients. For now, they use the CTFGSLobby, which is
  9. // in shared code for that reason.
  10. #include "tf_lobby_server.h"
  11. #include "base_gcmessages.pb.h"
  12. #include "igameevents.h"
  13. #include "netadr.h"
  14. #include "protocol.h"
  15. #include "econ_item_inventory.h"
  16. #include "tf_item_inventory.h"
  17. #include "tf_hud_mann_vs_machine_status.h"
  18. #include "econ/confirm_dialog.h"
  19. #include "rtime.h"
  20. #include "ienginevgui.h"
  21. #include "clientmode_tf.h"
  22. #include "tf_match_description.h"
  23. #include "tf_xp_source.h"
  24. #include "tf_notification.h"
  25. #include "c_tf_notification.h"
  26. #include "tf_gc_shared.h"
  27. #include <google/protobuf/text_format.h>
  28. #include "tf_match_join_handlers.h"
  29. #include "tf_matchmaking_dashboard.h"
  30. #include "tf_ladder_data.h"
  31. #include "tf_rating_data.h"
  32. #include "econ_item_description.h"
  33. #include "tf_hud_disconnect_prompt.h"
  34. #include "util_shared.h"
  35. #include <steamnetworkingsockets/isteamnetworkingutils.h>
  36. #include <steamnetworkingsockets/isteamnetworkingsockets.h>
  37. #include "filesystem.h"
  38. // memdbgon must be the last include file in a .cpp file!!!
  39. #include "tier0/memdbgon.h"
  40. void SelectGroup( EMatchmakingGroupType eGroup, bool bSelected );
  41. #ifdef _DEBUG
  42. #define GCMATCHMAKING_DEBUG_LEVEL 4
  43. #else
  44. #define GCMATCHMAKING_DEBUG_LEVEL 1
  45. #endif
  46. #define GCMatchmakingDebugSpew( lvl, ...) do { if ( lvl <= GCMATCHMAKING_DEBUG_LEVEL ) { Msg( __VA_ARGS__); } } while(false)
  47. #define MM_REJOIN_WAIT_TIME 1.0f
  48. static const char* s_pszCasualCriteriaSaveFileName = "casual_criteria.vdf";
  49. // Ping stuff
  50. static ConVar tf_datacenter_ping_interval( "tf_datacenter_ping_interval", "180", FCVAR_DEVELOPMENTONLY );
  51. #ifdef TF_GC_PING_DEBUG
  52. #define CV_TF_DATACENTER_PING_DEBUG_DEFAULT "1"
  53. #else
  54. #define CV_TF_DATACENTER_PING_DEBUG_DEFAULT "0"
  55. #endif
  56. static ConVar tf_datacenter_ping_debug( "tf_datacenter_ping_debug", CV_TF_DATACENTER_PING_DEBUG_DEFAULT, FCVAR_INTERNAL_USE );
  57. static bool BPingDebug() { return tf_datacenter_ping_debug.GetBool(); }
  58. #define TFPingMsg(...) Msg("[SDR Ping] " __VA_ARGS__)
  59. #define TFPingDbg(...) if ( BPingDebug() ) { TFPingMsg( __VA_ARGS__ ); }
  60. // Allow disabling for staging. Will only send dummy values set by the overrides above
  61. #ifdef TF_GC_PING_DEBUG
  62. #include "tier0/icommandline.h"
  63. static bool BUseSteamDatagram() { return !CommandLine()->CheckParm("-nosteamdatagram" ); }
  64. #else
  65. static bool BUseSteamDatagram() { return true; }
  66. #endif
  67. using namespace GCSDK;
  68. // @FD We need this for TF?
  69. //DEFINE_LOGGING_CHANNEL_NO_TAGS( LOG_CONSOLE, "Console" );
  70. static CTFGCClientSystem s_TFGCClientSystem;
  71. CTFGCClientSystem *GTFGCClientSystem() { return &s_TFGCClientSystem; }
  72. // Dialog Prompt Asking users if they want to rejoin a MvM Game
  73. static CTFRejoinConfirmDialog *s_pRejoinLobbyDialog;
  74. //bool g_bClientReceivedGCWelcome = false;
  75. //bool CTFGCClientSystem::HasGCUserSessionBeenCreated() { return g_bClientReceivedGCWelcome; }
  76. //static ConVar tf_spectator_auto_spectate_games( "tf_spectator_auto_spectate_games", "0", 0, "Automatically spectate available games" );
  77. //static ConVar tf_auto_connect( "tf_auto_connect", "", 0, "Automatically connect to the specified server forever" );
  78. static ConVar tf_matchgroups( "tf_matchgroups", "0", FCVAR_ARCHIVE, "Bit masks of match groups to search in for matchmaking" );
  79. //static ConVar tf_auto_create_proxy( "tf_auto_create_proxy", "0", 0, "Automatically create a proxy" );
  80. //ConVar tf_debug_today_message_sorting( "tf_debug_today_message_sorting", "0", 0, "Print out unsorted and sorted today messages to the console" );
  81. #ifdef STAGING_ONLY
  82. CON_COMMAND( cl_check_process_count, "cl_check_process_count" )
  83. {
  84. int iProcessCount = engine->GetInstancesRunningCount();
  85. Msg( "cl_check_process_count - %d \n", iProcessCount );
  86. }
  87. #endif
  88. // This triggers a GC packet so isn't great to let clients misuse
  89. #if defined( STAGING_ONLY ) || defined( _DEBUG )
  90. CON_COMMAND( tf_datacenter_ping_refresh, "Force an immediate refresh of datacenter ping" )
  91. {
  92. GTFGCClientSystem()->InvalidatePingData();
  93. }
  94. #endif // defined( STAGING_ONLY ) || defined( _DEBUG )
  95. CON_COMMAND( tf_datacenter_ping_dump, "Dump current datacenter ping values to console" )
  96. {
  97. GTFGCClientSystem()->DumpPing();
  98. }
  99. static void OnRejoinMvMLobbyDialogCallBack( bool bConfirmed, void *pContext )
  100. {
  101. GTFGCClientSystem()->RejoinLobby( bConfirmed );
  102. s_pRejoinLobbyDialog = NULL;
  103. }
  104. void SubscribeToLocalPlayerSOCache( ISharedObjectListener* pListener )
  105. {
  106. if ( steamapicontext && steamapicontext->SteamUser() )
  107. {
  108. CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
  109. GCClientSystem()->GetGCClient()->AddSOCacheListener( steamID, pListener );
  110. }
  111. else
  112. {
  113. Assert( !"Failed to subscribe to local user's SOCache!" );
  114. }
  115. }
  116. // Helper to add or replace a ping entry in an update
  117. static void ApplyPingToMsg( CMsgGCDataCenterPing_Update &msg, const CMsgGCDataCenterPing_Update_PingEntry &entry )
  118. {
  119. // Existing?
  120. const char *pszName = entry.name().c_str();
  121. CMsgGCDataCenterPing_Update_PingEntry *pEntry = NULL;
  122. for ( int j = 0; j < msg.pingdata_size(); ++j )
  123. {
  124. CMsgGCDataCenterPing_Update_PingEntry& existingEntry = *msg.mutable_pingdata(j);
  125. if ( V_stricmp( existingEntry.name().c_str(), pszName ) == 0 )
  126. {
  127. pEntry = &existingEntry;
  128. break;
  129. }
  130. }
  131. // New?
  132. if ( !pEntry )
  133. {
  134. pEntry = msg.add_pingdata();
  135. }
  136. pEntry->CopyFrom( entry );
  137. }
  138. const char *CTFGCClientSystem::k_pszSteamLobbyKey_PartyID = "PartyID";
  139. //-----------------------------------------------------------------------------
  140. // Reliable messages
  141. //-----------------------------------------------------------------------------
  142. class ReliableMsgNotificationAcknowledge : public CJobReliableMessageBase < ReliableMsgNotificationAcknowledge,
  143. CMsgNotificationAcknowledge,
  144. k_EMsgGC_NotificationAcknowledge,
  145. CMsgNotificationAcknowledgeReply,
  146. k_EMsgGC_NotificationAcknowledgeReply >
  147. {
  148. public:
  149. const char *MsgName() { return "NotificationAcknowledge"; }
  150. void InitDebugString( CUtlString &dbgStr )
  151. {
  152. dbgStr.Format( "Account %u / Notification %016llx",
  153. Msg().Body().account_id(), Msg().Body().notification_id() );
  154. }
  155. };
  156. CTFGCClientSystem::CTFGCClientSystem()
  157. : m_pPendingCreateOrUpdatePartyMsg( NULL )
  158. , m_flSendPartyUpdateMessageTime( FLT_MAX )
  159. , m_nMostSearchedMapCount( 0 )
  160. , m_WorldStatus()
  161. , m_bRegisteredSharedObjects( false )
  162. , m_bInittedGC( false )
  163. , m_eAcceptInviteStep( eAcceptInviteStep_None )
  164. , m_eCreateLobbyStatus( k_EResultOK )
  165. , m_bWantToActivateInviteUI( false )
  166. , m_steamIDGCAssignedMatch()
  167. , m_bAssignedMatchEnded( false )
  168. , m_eAssignedMatchGroup( k_nMatchGroup_Invalid )
  169. , m_uAssignedMatchID( 0 )
  170. , m_bServerAssignmentChanged( false )
  171. , m_rtLastPingFix( 0 )
  172. , m_bPendingPingRefresh( false )
  173. , m_bSentInitialPingFix( false )
  174. , m_flCheckForRejoinTime( 0 )
  175. , m_pSOCache( NULL )
  176. , m_eConnectState( eConnectState_Disconnected )
  177. , m_bGCUserSessionCreated( false )
  178. , m_bUserWantsToBeInMatchmaking( false )
  179. , m_nPendingAutoJoinPartyID( 0 )
  180. , m_eLocalWizardStep( TF_Matchmaking_WizardStep_INVALID )
  181. , m_callbackSteamLobbyCreated( this, &CTFGCClientSystem::OnSteamLobbyCreated )
  182. , m_callbackSteamLobbyEnter( this, &CTFGCClientSystem::OnSteamLobbyEnter )
  183. , m_callbackSteamLobbyChatMsg( this, &CTFGCClientSystem::OnSteamLobbyChatMsg )
  184. , m_callbackSteamGameLobbyJoinRequested( this, &CTFGCClientSystem::OnSteamGameLobbyJoinRequested )
  185. , m_callbackSteamLobbyDataUpdate( this, &CTFGCClientSystem::OnSteamLobbyDataUpdate )
  186. , m_callbackSteamLobbyChatUpdate( this, &CTFGCClientSystem::OnSteamLobbyChatUpdate )
  187. {
  188. // replace base GCClientSystem
  189. SetGCClientSystem( this );
  190. s_pRejoinLobbyDialog = NULL;
  191. // if ( g_bClientReceivedGCWelcome )
  192. // {
  193. // Msg( "CTFGCClientSystem::CTFGCClientSystem firing event\n" );
  194. //
  195. // IGameEvent *pEvent = gameeventmanager->CreateEvent( "gc_user_session_created" );
  196. // if ( pEvent )
  197. // {
  198. // gameeventmanager->FireEventClientSide( pEvent );
  199. // }
  200. // }
  201. // else
  202. // {
  203. // Msg( "CTFGCClientSystem::CTFGCClientSystem user session not yet created\n" );
  204. // }
  205. }
  206. CTFGCClientSystem::~CTFGCClientSystem( void )
  207. {
  208. // Prevent other system from using this pointer after it's destroyed
  209. SetGCClientSystem( NULL );
  210. }
  211. ////-----------------------------------------------------------------------------
  212. //// Purpose: Asynchronous job for getting news
  213. ////-----------------------------------------------------------------------------
  214. //class CGCClientJobGetNews : public GCSDK::CGCClientJob
  215. //{
  216. //public:
  217. // CGCClientJobGetNews( GCSDK::CGCClient *pGCClient, int nAppID ) : GCSDK::CGCClientJob( pGCClient )
  218. // {
  219. // m_nAppID = nAppID;
  220. // }
  221. //
  222. // virtual bool BYieldingRunJob( void *pvStartParam )
  223. // {
  224. // CGCMsg<MsgGCGetNews_t> msgGetNews( k_EMsgGCGetNews );
  225. // msgGetNews.Body().m_unAppID = m_nAppID;
  226. //
  227. // GCSDK::CGCMsg<MsgGCNewsReponse_t> msgResponse( k_EMsgGCNewsResponse );
  228. // bool bRet = BYldSendMessageAndGetReply( msgGetNews, 150, &msgResponse, k_EMsgGCNewsResponse );
  229. // //Assert( bRet );
  230. //
  231. // if ( !bRet )
  232. // {
  233. // Warning( "CGCClientJobGetNews failed to get reply\n" );
  234. // GTFGCClientSystem()->SetGetNewsTime( Plat_FloatTime() + 5.0f );
  235. // return false;
  236. // }
  237. //
  238. // //deserialize KV
  239. // CUtlBuffer bufNews;
  240. // bufNews.Put( msgResponse.PubReadCur(), msgResponse.Body().m_cMsgLen );
  241. //
  242. // KVPacker packer;
  243. // KeyValues *pNewsKeys = GTFGCClientSystem()->GetNewsKeys();
  244. // pNewsKeys->Clear();
  245. // if ( !packer.ReadAsBinary( pNewsKeys, bufNews ) )
  246. // {
  247. // Warning( "Failed to deserialize key values from news request\n" );
  248. // return false;
  249. // }
  250. //
  251. // //KeyValuesDumpAsDevMsg( pNewsKeys );
  252. //
  253. // IGameEvent *pEvent = gameeventmanager->CreateEvent( "news_updated" );
  254. // if ( pEvent )
  255. // {
  256. // gameeventmanager->FireEventClientSide( pEvent );
  257. // }
  258. //
  259. // return true;
  260. // }
  261. //
  262. //private:
  263. // int m_nAppID;
  264. //};
  265. ////-----------------------------------------------------------------------------
  266. //// Purpose: Asynchronous job for pinging the GC with a hello until we get
  267. //// a welcome
  268. ////-----------------------------------------------------------------------------
  269. //class CGCClientJobHello : public GCSDK::CGCClientJob
  270. //{
  271. //public:
  272. // CGCClientJobHello( GCSDK::CGCClient *pGCClient )
  273. // : GCSDK::CGCClientJob( pGCClient )
  274. // {
  275. // }
  276. //
  277. // virtual bool BYieldingRunJob( void *pvStartParam )
  278. // {
  279. // CProtoBufMsg<CMsgClientHello> msg( k_EMsgGCClientHello );
  280. // msg.Body().set_version( engine->GetClientVersion() );
  281. //
  282. // while ( !g_bClientReceivedGCWelcome )
  283. // {
  284. // // Wait two seconds between messages
  285. // BYieldingWaitTime( 2 * k_nMillion );
  286. //
  287. // if ( !m_pGCClient->BSendMessage( msg ) )
  288. // return false;
  289. // }
  290. // return true;
  291. // }
  292. //};
  293. ////-----------------------------------------------------------------------------
  294. //class CGCClientJobFindSourceTVGamesAutoSpectate : public GCSDK::CGCClientJob
  295. //{
  296. //public:
  297. // CGCClientJobFindSourceTVGamesAutoSpectate( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient )
  298. // {
  299. // }
  300. //
  301. // virtual bool BYieldingRunJob( void *pvStartParam )
  302. // {
  303. // CProtoBufMsg<CMsgFindSourceTVGames> msg( k_EMsgGCFindSourceTVGames );
  304. // CProtoBufMsg<CMsgSourceTVGamesResponse> msgResponse( k_EMsgGCSourceTVGamesResponse );
  305. //
  306. // static ConVarRef sv_search_key("sv_search_key");
  307. // if ( sv_search_key.IsValid() && *sv_search_key.GetString() )
  308. // {
  309. // msg.Body().set_search_key( sv_search_key.GetString() );
  310. // }
  311. //
  312. // msg.Body().set_start( 0 );
  313. // msg.Body().set_num_games( 10 );
  314. //
  315. // bool bRet = BYldSendMessageAndGetReply( msg, 15, &msgResponse, k_EMsgGCSourceTVGamesResponse );
  316. //
  317. // GTFGCClientSystem()->SetAutoSpectateCheckTime( Plat_FloatTime() + 30.0f ); // try again in 30 seconds
  318. //
  319. // if ( !bRet )
  320. // {
  321. // Warning( "CGCClientJobFindSourceTVGamesDebug failed to get reply\n" );
  322. // return false;
  323. // }
  324. //
  325. // if ( GTFGCClientSystem()->GetSignonState() != SIGNONSTATE_NONE || !msgResponse.Body().games_size() )
  326. // {
  327. // return true; // already connected somewhere else
  328. // }
  329. //
  330. // const CSourceTVGame &game = msgResponse.Body().games( RandomInt( 0, msgResponse.Body().games_size() - 1 ));
  331. //
  332. // GTFGCClientSystem()->StartWatchingGame( game.server_steamid() );
  333. // return true;
  334. // }
  335. //
  336. //private:
  337. //};
  338. void CTFGCClientSystem::LoadCasualSearchCriteria()
  339. {
  340. // Read casual criteria if the file exists
  341. CUtlBuffer buffer;
  342. buffer.SetBufferType( true, true );
  343. if ( g_pFullFileSystem->ReadFile( s_pszCasualCriteriaSaveFileName, NULL, buffer ) &&
  344. buffer.TellPut() > buffer.TellGet() )
  345. {
  346. // Null terminate. Why is buffer this pseudo-text class but has AddNullTerminator private?
  347. const char zero = '\0';
  348. buffer.Put( &zero, sizeof( zero ) );
  349. std::string strIn( (const char *)buffer.PeekGet() );
  350. google::protobuf::TextFormat::ParseFromString( strIn, m_msgLocalSearchCriteria.mutable_casual_criteria() );
  351. // let the CCasualCriteriaHelper validate/cleanup the bits that we've just loaded
  352. CCasualCriteriaHelper casualHelper( m_msgLocalSearchCriteria.casual_criteria() );
  353. if ( GetParty() != NULL )
  354. {
  355. CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
  356. pMsg->mutable_search_criteria()->mutable_casual_criteria()->CopyFrom( casualHelper.GetCasualCriteria() );
  357. }
  358. m_msgLocalSearchCriteria.mutable_casual_criteria()->CopyFrom( casualHelper.GetCasualCriteria() );
  359. FireGameEventPartyUpdated();
  360. }
  361. else
  362. {
  363. // default to the Core maps
  364. SelectGroup( kMatchmakingType_Core, true );
  365. }
  366. }
  367. // Initialize steam client datagram lib if we haven't already
  368. static bool CheckInitSteamDatagramClientLib()
  369. {
  370. if ( !BUseSteamDatagram() )
  371. return false;
  372. if ( !steamapicontext || !steamapicontext->SteamHTTP() || !steamapicontext->SteamUtils() )
  373. {
  374. Assert( false );
  375. Warning( "Steam datagram not initialized - no Steam context\n" );
  376. return false;
  377. }
  378. static bool bInittedNetwork = false;
  379. if ( bInittedNetwork )
  380. return true;
  381. // Locate the first PLATFORM path
  382. char szAbsPlatform[MAX_PATH] = "";
  383. const char *pszConfigDir = "config";
  384. g_pFullFileSystem->GetSearchPath( "PLATFORM", false, szAbsPlatform, sizeof(szAbsPlatform) );
  385. char *semi = strchr( szAbsPlatform, ';' );
  386. if ( semi )
  387. *semi = '\0';
  388. char szAbsConfigDir[MAX_PATH];
  389. V_ComposeFileName( szAbsPlatform, pszConfigDir, szAbsConfigDir, sizeof(szAbsConfigDir) );
  390. SteamDatagramErrMsg errMsg;
  391. if ( !SteamDatagramClient_Init( szAbsConfigDir, k_ESteamDatagramPartner_Steam, (1<<k_ESteamDatagramPartner_Steam), errMsg ) )
  392. {
  393. Warning( "Failed to initialize steam datagram client. %s\n", errMsg );
  394. return false;
  395. }
  396. bInittedNetwork = true;
  397. return true;
  398. }
  399. bool CTFGCClientSystem::Init()
  400. {
  401. // Get this guy created
  402. GetMMDashboard();
  403. // Convars may have initialized before us
  404. UpdateCustomPingTolerance();
  405. ListenForGameEvent( "client_disconnect" );
  406. ListenForGameEvent( "client_beginconnect" );
  407. ListenForGameEvent( "server_spawn" );
  408. // init steamdatagram system ASAP so we're more likely to have initial ping data to the clusters ready by the time
  409. // we ask for it
  410. CheckInitSteamDatagramClientLib();
  411. if ( SteamNetworkingUtils() )
  412. SteamNetworkingUtils()->CheckPingDataUpToDate( 0.0f );
  413. // Just loading the library starts initial pinging
  414. m_bPendingPingRefresh = true;
  415. // m_GameVersion = GAME_VERSION_CURRENT;
  416. // Default search criteria
  417. //m_msgLocalSearchCriteria.set_matchgroups( 1 );
  418. m_msgLocalSearchCriteria.set_matchmaking_mode( TF_Matchmaking_LADDER );
  419. m_bLocalSquadSurplus = false;
  420. return true;
  421. }
  422. void CTFGCClientSystem::PostInit()
  423. {
  424. BaseClass::PostInit();
  425. }
  426. //-----------------------------------------------------------------------------
  427. // Purpose:
  428. //-----------------------------------------------------------------------------
  429. void CTFGCClientSystem::PreInitGC()
  430. {
  431. if ( !m_bRegisteredSharedObjects )
  432. {
  433. // REG_SHARED_OBJECT_SUBCLASS( CTFHeroStandings );
  434. // REG_SHARED_OBJECT_SUBCLASS( CTFGameAccountClient );
  435. REG_SHARED_OBJECT_SUBCLASS( CTFParty );
  436. REG_SHARED_OBJECT_SUBCLASS( CTFGSLobby );
  437. REG_SHARED_OBJECT_SUBCLASS( CPartyInvite );
  438. // REG_SHARED_OBJECT_SUBCLASS( CTFBetaParticipation );
  439. REG_SHARED_OBJECT_SUBCLASS( CTFPartyInvite );
  440. REG_SHARED_OBJECT_SUBCLASS( CTFRatingData );
  441. m_bRegisteredSharedObjects = true;
  442. }
  443. // if ( m_flGetNewsTime == 0.0f )
  444. // {
  445. // m_flGetNewsTime = Plat_FloatTime() + 2.0f;
  446. // }
  447. }
  448. //-----------------------------------------------------------------------------
  449. // Purpose:
  450. //-----------------------------------------------------------------------------
  451. void CTFGCClientSystem::PostInitGC()
  452. {
  453. GCMatchmakingDebugSpew( 1, "CTFGCClientSystem::PostInitGC\n" );
  454. if ( steamapicontext && steamapicontext->SteamUser() )
  455. {
  456. GCMatchmakingDebugSpew( 1, "CTFGCClientSystem - adding listener\n" );
  457. CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
  458. GCClientSystem()->FindOrAddSOCache( steamID )->AddListener( this );
  459. }
  460. else
  461. {
  462. Warning( "CTFGCClientSystem - couldn't add listener because Steam wasn't ready\n" );
  463. }
  464. // @FD We need this?
  465. // // Force a resend of our SO cache.
  466. // // This is only necessary because the Steam client doesn't detect a quick relaunch of the game, so the GC doesn't get a SessionStartPlaying call.
  467. // CProtoBufMsg<CMsgForceSOCacheResend> msg( k_EMsgForceSOCacheResend );
  468. // GCClientSystem()->BSendMessage( msg );
  469. // // Start hello job to ping the GC until we get a welcome
  470. // if ( !g_bClientReceivedGCWelcome )
  471. // {
  472. // CGCClientJobHello *pJob = new CGCClientJobHello( GCClientSystem()->GetGCClient() );
  473. // pJob->StartJob( NULL );
  474. // }
  475. }
  476. //-----------------------------------------------------------------------------
  477. // Purpose:
  478. //-----------------------------------------------------------------------------
  479. void CTFGCClientSystem::ReceivedClientWelcome( const CMsgClientWelcome &msg )
  480. {
  481. BaseClass::ReceivedClientWelcome( msg );
  482. // Send a client init message in response to welcome
  483. GCSDK::CProtoBufMsg<CMsgTFClientInit> initMsg( k_EMsgGC_TFClientInit );
  484. initMsg.Body().set_client_version( engine->GetClientVersion() );
  485. char uilanguage[ 64 ];
  486. engine->GetUILanguage( uilanguage, sizeof( uilanguage ) );
  487. initMsg.Body().set_language( PchLanguageToELanguage( uilanguage ) );
  488. this->BSendMessage( initMsg );
  489. // Send a ping fix if we know it (e.g. we re-connected to the GC, or got a fix before the GC was ready)
  490. if ( BHavePingData() )
  491. {
  492. GCSDK::CProtoBufMsg<CMsgGCDataCenterPing_Update> pingmsg( k_EMsgGCDataCenterPing_Update );
  493. pingmsg.Body().CopyFrom( GetPingData() );
  494. m_bSentInitialPingFix = this->BSendMessage( pingmsg );
  495. }
  496. else
  497. {
  498. // PingThink will send it next fix
  499. m_bSentInitialPingFix = false;
  500. }
  501. }
  502. class CSendCreateOrUpdatePartyMsgJob;
  503. static int s_nNumWizardStepChangesWaitingForReply = 0;
  504. class CSendCreateOrUpdatePartyMsgJob : public GCSDK::CGCClientJob
  505. {
  506. public:
  507. CSendCreateOrUpdatePartyMsgJob()
  508. : GCSDK::CGCClientJob( GCClientSystem()->GetGCClient() )
  509. , msg( k_EMsgGCCreateOrUpdateParty )
  510. {
  511. msg.Body().set_client_version( engine->GetClientVersion() );
  512. }
  513. CProtoBufMsg<CMsgCreateOrUpdateParty> msg;
  514. CProtoBufMsg<CMsgCreateOrUpdatePartyReply> msgReply;
  515. static void UpdateWizardStepFromParty()
  516. {
  517. // Make sure we have a party and the data changed
  518. CTFParty *pParty = GTFGCClientSystem()->GetParty();
  519. if ( pParty != NULL && GTFGCClientSystem()->m_eLocalWizardStep != pParty->Obj().wizard_step() )
  520. {
  521. GTFGCClientSystem()->m_eLocalWizardStep = pParty->Obj().wizard_step();
  522. GTFGCClientSystem()->FireGameEventPartyUpdated();
  523. }
  524. }
  525. virtual bool BYieldingRunJob( void *pvStartParam )
  526. {
  527. Assert( s_nNumWizardStepChangesWaitingForReply >= 0 );
  528. if ( msg.Body().has_wizard_step() )
  529. ++s_nNumWizardStepChangesWaitingForReply;
  530. bool bGotReply = BYldSendMessageAndGetReply( msg, 10, &msgReply, k_EMsgGCCreateOrUpdatePartyReply );
  531. if ( msg.Body().has_wizard_step() )
  532. --s_nNumWizardStepChangesWaitingForReply;
  533. Assert( s_nNumWizardStepChangesWaitingForReply >= 0 );
  534. if ( !bGotReply )
  535. {
  536. CTFParty *pParty = GTFGCClientSystem()->GetParty();
  537. if ( pParty )
  538. {
  539. pParty->SetOffline( true );
  540. }
  541. GTFGCClientSystem()->FireGameEventPartyUpdated();
  542. // !FIXME! Here we really should mark the GC Client as not
  543. // being connected to the GC
  544. Warning( "Timed out getting reply from GC to change party.\n" );
  545. return true;
  546. }
  547. // Any error message?
  548. EResult result = (EResult)msgReply.Body().result();
  549. const char *pszMsg = msgReply.Body().message().c_str();
  550. if ( *pszMsg != '\0' )
  551. {
  552. if ( result != k_EResultOK )
  553. {
  554. Warning( "%s\n", pszMsg );
  555. }
  556. else
  557. {
  558. Msg( "%s\n", pszMsg );
  559. }
  560. }
  561. // Check for error.
  562. switch ( result )
  563. {
  564. case k_EResultOK:
  565. break;
  566. case k_EResultInvalidProtocolVer:
  567. //if ( GTFGCClientSystem()->BIsPartyLeader() )
  568. //{
  569. //}
  570. GTFGCClientSystem()->EndMatchmaking();
  571. ShowMessageBox( "#TF_MM_NotCurrentVersionTitle", "#TF_MM_NotCurrentVersionMessage", "#GameUI_OK" );
  572. return true;
  573. default:
  574. Warning( "CreateOrUpdate returned error code %d\n", result );
  575. break;
  576. }
  577. // If no more messages pending that will change the wizard step, then
  578. // get in sync with the GC
  579. if ( s_nNumWizardStepChangesWaitingForReply <= 0 )
  580. {
  581. UpdateWizardStepFromParty();
  582. GTFGCClientSystem()->FireGameEventPartyUpdated();
  583. return true;
  584. }
  585. // Did we request a particular wizard step?
  586. if ( !msg.Body().has_wizard_step() )
  587. return true;
  588. // Do we have a party?
  589. CTFParty *pParty = GTFGCClientSystem()->GetParty();
  590. if ( pParty == NULL )
  591. return true;
  592. // We got a response. Definitely not offline anymore
  593. pParty->SetOffline( false );
  594. // We should be the party leader
  595. Assert( GTFGCClientSystem()->BIsPartyLeader() );
  596. // Party should have a known wizard step
  597. Assert( pParty->Obj().has_wizard_step() );
  598. if ( !pParty->Obj().has_wizard_step() )
  599. return true;
  600. // If GC did not like our request, or we are starting or stopping searching,
  601. // the force us to get on the same page as the GC
  602. if (
  603. msg.Body().wizard_step() != pParty->Obj().wizard_step() // after processing message, we are not in requested step
  604. || msg.Body().wizard_step() == TF_Matchmaking_WizardStep_SEARCHING // we requested to search
  605. || pParty->Obj().wizard_step() == TF_Matchmaking_WizardStep_SEARCHING // we are now searching
  606. || GTFGCClientSystem()->m_eLocalWizardStep == TF_Matchmaking_WizardStep_SEARCHING // we think we were searching previously
  607. )
  608. {
  609. UpdateWizardStepFromParty();
  610. }
  611. return true;
  612. }
  613. };
  614. CMsgCreateOrUpdateParty *CTFGCClientSystem::GetCreateOrUpdatePartyMsg()
  615. {
  616. // TODO We should only send updates if something changes, some callers might just be copying same-values back in :-/
  617. if ( m_pPendingCreateOrUpdatePartyMsg == NULL )
  618. {
  619. m_pPendingCreateOrUpdatePartyMsg = new CSendCreateOrUpdatePartyMsgJob;
  620. }
  621. if ( m_flSendPartyUpdateMessageTime == FLT_MAX )
  622. {
  623. if ( GetParty() && GetParty()->GetNumMembers() > 1 )
  624. {
  625. // If we're in a party, delay the sending of the message to queue up any rapid changes
  626. // that might occur from users clicking on criteria UI controls
  627. m_flSendPartyUpdateMessageTime = Plat_FloatTime() + 2.f;
  628. }
  629. else
  630. {
  631. m_flSendPartyUpdateMessageTime = 0.f;
  632. }
  633. }
  634. return &m_pPendingCreateOrUpdatePartyMsg->msg.Body();
  635. }
  636. //-----------------------------------------------------------------------------
  637. void CTFGCClientSystem::LevelShutdownPostEntity()
  638. {
  639. BaseClass::LevelShutdownPostEntity();
  640. // // clear caches, so the player will see his stats update after a game
  641. // if ( Dashboard() )
  642. // {
  643. // Dashboard()->ClearDashboardCaches();
  644. // }
  645. }
  646. //-----------------------------------------------------------------------------
  647. void CTFGCClientSystem::LevelInitPreEntity()
  648. {
  649. BaseClass::LevelInitPreEntity();
  650. }
  651. //-----------------------------------------------------------------------------
  652. void CTFGCClientSystem::Shutdown()
  653. {
  654. GCMatchmakingDebugSpew( 1, "CTFGCClientSystem::ShutdownGC\n" );
  655. if ( steamapicontext && steamapicontext->SteamUser() )
  656. {
  657. GCMatchmakingDebugSpew( 1, "CTFGCClientSystem - adding listener\n" );
  658. CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
  659. GCSDK::CGCClientSharedObjectCache *pSOCache = GCClientSystem()->GetSOCache( steamID );
  660. Assert( pSOCache ); // we installed ourselves as a listener, right, so it shouldn't have deleted the cache
  661. if ( pSOCache )
  662. {
  663. pSOCache->RemoveListener( this );
  664. }
  665. }
  666. else
  667. {
  668. Warning( "CTFGCClientSystem - couldn't add listener because Steam wasn't ready\n" );
  669. }
  670. BaseClass::Shutdown();
  671. SteamDatagramClient_Kill();
  672. }
  673. //-----------------------------------------------------------------------------
  674. void CTFGCClientSystem::FireGameEvent( IGameEvent *event )
  675. {
  676. const char *pEventName = event->GetName();
  677. // Disconnected from gameserver
  678. if ( !Q_stricmp( pEventName, "client_disconnect" ) )
  679. {
  680. m_steamIDCurrentServer.Clear();
  681. // Do not send end match making if we see the mvm end message
  682. if ( !Q_stricmp( event->GetString( "message", "" ), "#TF_PVE_Disconnect" ) )
  683. return;
  684. // Don't bail if GC has told us to expect to be put into a new party
  685. if ( m_nPendingAutoJoinPartyID != 0 )
  686. return;
  687. m_eConnectState = eConnectState_Disconnected; // clear variable first to avoid recursion
  688. // Ladder games
  689. //if ( !Q_stricmp( event->GetString( "message", "" ), "#TF_Competitive_Disconnect" ) ) // FIXME only disconnect if we were previously connected(ing), this fires spuriously from the main menu
  690. //{
  691. // engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby ladder" );
  692. // return;
  693. //}
  694. CTFParty *pParty = GetParty();
  695. // Return to party screen upon disconnect
  696. if ( m_bUserWantsToBeInMatchmaking && ( ( pParty && pParty->GetNumMembers() > 1 ) || m_eLocalWizardStep == TF_Matchmaking_WizardStep_SEARCHING ) )
  697. {
  698. switch( GTFGCClientSystem()->GetSearchMode() )
  699. {
  700. case TF_Matchmaking_LADDER:
  701. engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby ladder" );
  702. break;
  703. case TF_Matchmaking_CASUAL:
  704. engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby casual" );
  705. break;
  706. case TF_Matchmaking_MVM:
  707. engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby mvm" );
  708. break;
  709. default:
  710. engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby" );
  711. Assert( !"Unhandled enum value" );
  712. break;
  713. };
  714. }
  715. return;
  716. }
  717. // Started attempting connection to gameserver
  718. if ( !Q_stricmp( pEventName, "client_beginconnect" ) )
  719. {
  720. Assert( IsConnectStateDisconnected() );
  721. // TODO does the retry command set this source? It should go through ::ConnectToServer
  722. const char *pszSource = event->GetString( "source", "" );
  723. if ( FStrEq( pszSource, "matchmaking" ) )
  724. {
  725. // Assume we're doing the right thing until we hit server_spawn and figure otherwise.
  726. m_steamIDCurrentServer = m_steamIDGCAssignedMatch;
  727. m_eConnectState = eConnectState_ConnectingToMatchmade;
  728. }
  729. else
  730. {
  731. if ( !BAllowMatchMakingInGame() )
  732. {
  733. EndMatchmaking();
  734. }
  735. m_eConnectState = eConnectState_NonmatchmadeServer;
  736. }
  737. return;
  738. }
  739. // Successfully connected to a gameserver. For MM purposes, we stay in state connecting until server spawn as that
  740. // ensures there's no intermediate "loading into some server but we're not sure of its steamid yet" state.
  741. if ( !Q_strcmp( pEventName, "server_spawn" ) )
  742. {
  743. GCMatchmakingDebugSpew( 4, "Client reached server_spawn.\n" );
  744. switch ( m_eConnectState )
  745. {
  746. default:
  747. AssertMsg1( false, "Unknown connect state %d", m_eConnectState );
  748. // These two can happen when doing weird things with timedemo or listen servers
  749. case eConnectState_Disconnected:
  750. m_eConnectState = eConnectState_NonmatchmadeServer;
  751. GCMatchmakingDebugSpew( 4, "Client connected to non-matchmade.\n" );
  752. break;
  753. case eConnectState_ConnectingToMatchmade:
  754. m_eConnectState = eConnectState_ConnectedToMatchmade;
  755. GCMatchmakingDebugSpew( 4, "Client connected to matchmade.\n" );
  756. break;
  757. case eConnectState_ConnectedToMatchmade:
  758. break;
  759. case eConnectState_NonmatchmadeServer:
  760. break;
  761. }
  762. m_steamIDCurrentServer.Clear();
  763. if ( steamapicontext && steamapicontext->SteamUser() && steamapicontext->SteamUtils() )
  764. {
  765. m_steamIDCurrentServer.SetFromString( event->GetString( "steamid", "" ), GetUniverse() );
  766. GCMatchmakingDebugSpew( 4, "Recognizing MM server id %s\n", m_steamIDCurrentServer.Render() );
  767. }
  768. if ( m_eConnectState == eConnectState_ConnectedToMatchmade && !m_steamIDCurrentServer.IsValid() )
  769. {
  770. Warning( "Connected to MM server but no GS steamid is set.\n" );
  771. }
  772. return;
  773. }
  774. }
  775. //-----------------------------------------------------------------------------
  776. void CTFGCClientSystem::InvalidatePingData()
  777. {
  778. // Invalidate current refresh
  779. TFPingMsg("Forcing ping refresh\n" );
  780. m_bPendingPingRefresh = true;
  781. // Wipe all cached data.
  782. m_rtLastPingFix = 0; // 0 means never. Or time traveler. 50/50.
  783. m_msgCachedPingUpdate = CMsgGCDataCenterPing_Update();
  784. if ( BUseSteamDatagram() && SteamNetworkingUtils() )
  785. {
  786. SteamNetworkingUtils()->CheckPingDataUpToDate( 0.0f );
  787. }
  788. }
  789. //-----------------------------------------------------------------------------
  790. void CTFGCClientSystem::PingThink()
  791. {
  792. ISteamNetworkingUtils *pUtils = SteamNetworkingUtils();
  793. if ( !pUtils && BUseSteamDatagram() )
  794. {
  795. Assert( pUtils );
  796. return;
  797. }
  798. if ( !m_bPendingPingRefresh )
  799. {
  800. // No refresh in progress, start one if necessary
  801. RTime32 rtRefreshInterval = (RTime32)Clamp( tf_datacenter_ping_interval.GetInt(), 0, INT32_MAX );
  802. RTime32 rtLastRefreshAge = CRTime::RTime32TimeCur() - m_rtLastPingFix;
  803. if ( rtLastRefreshAge <= rtRefreshInterval )
  804. { return; }
  805. // Start a refresh
  806. m_bPendingPingRefresh = true;
  807. // Non-steam datagram will just succeed next heartbeat
  808. if ( BUseSteamDatagram() )
  809. { pUtils->CheckPingDataUpToDate(0.0f); }
  810. return;
  811. }
  812. //
  813. // Refresh pending, speculatively start building an update and bail out if we find missing data
  814. //
  815. CMsgGCDataCenterPing_Update newPingUpdate;
  816. // Check if our connection status is good enough for ping measurement
  817. // Without steamdatagram, we'll just always succeed immediately with empty data (plus overrides below)
  818. if ( BUseSteamDatagram() )
  819. {
  820. // Not ready yet?
  821. if ( pUtils->IsPingMeasurementInProgress() )
  822. return;
  823. // Get complete list of points of presence
  824. CUtlVector<SteamNetworkingPOPID> vecPoPs;
  825. vecPoPs.SetCount( pUtils->GetPOPCount() );
  826. vecPoPs.SetCountNonDestructively( pUtils->GetPOPList( vecPoPs.Base(), vecPoPs.Count() ) );
  827. // Waiting on a ping refresh to complete. Check if we have every datacenter and cache off if so.
  828. //
  829. // NOTE that we don't use SDR for actual-routing yet, so we are purposefully using the *router* ping values as
  830. // estimates for that DC -- since we will talk to the DC directly and not via the relay.
  831. for ( SteamNetworkingPOPID id: vecPoPs )
  832. {
  833. char szCode[ 8 ];
  834. GetSteamNetworkingLocationPOPStringFromID( id, szCode );
  835. CMsgGCDataCenterPing_Update_PingEntry *pMsgPingEntry = newPingUpdate.add_pingdata();
  836. pMsgPingEntry->set_name( szCode );
  837. int nPing = pUtils->GetDirectPingToPOP( id );
  838. if ( nPing >= 0 )
  839. {
  840. pMsgPingEntry->set_ping( nPing );
  841. }
  842. else
  843. {
  844. nPing = pUtils->GetPingToDataCenter( id, nullptr );
  845. if ( nPing >= 0 )
  846. {
  847. pMsgPingEntry->set_ping( nPing );
  848. pMsgPingEntry->set_ping_status( CMsgGCDataCenterPing_Update_Status_FallbackToDCPing );
  849. }
  850. }
  851. if ( !pMsgPingEntry->has_ping() )
  852. {
  853. pMsgPingEntry->set_ping_status( CMsgGCDataCenterPing_Update_Status_Unreachable );
  854. }
  855. }
  856. }
  857. else
  858. {
  859. // Otherwise we're fine
  860. TFPingMsg( "Not using steam datagram, proceeding with empty cluster ping data\n" );
  861. }
  862. // If we're in beta/dev, add the magic "beta" cluster. See tf_datacenter_info on GC.
  863. EUniverse eUniverse = GetUniverse();
  864. if ( eUniverse == k_EUniverseBeta || eUniverse == k_EUniverseDev )
  865. {
  866. CMsgGCDataCenterPing_Update_PingEntry newEntry;
  867. newEntry.set_name( "beta" );
  868. newEntry.set_ping( 5 );
  869. newEntry.set_ping_status( CMsgGCDataCenterPing_Update_Status_Normal );
  870. ApplyPingToMsg( newPingUpdate, newEntry );
  871. }
  872. #ifdef TF_GC_PING_DEBUG
  873. // Apply overrides
  874. for ( int j = 0; j < m_msgPingOverrides.pingdata_size(); ++j )
  875. {
  876. ApplyPingToMsg( newPingUpdate, m_msgPingOverrides.pingdata(j) );
  877. }
  878. #endif // def TF_GC_PING_DEBUG
  879. // We made it through all routers without bailing, can claim to have ping data now
  880. if ( BConnectedtoGC() )
  881. {
  882. GCSDK::CProtoBufMsg<CMsgGCDataCenterPing_Update> msg( k_EMsgGCDataCenterPing_Update );
  883. msg.Body().CopyFrom( newPingUpdate );
  884. if ( this->BSendMessage( msg ) )
  885. {
  886. TFPingDbg( "Initial ping fix sent\n" );
  887. m_bSentInitialPingFix = true;
  888. }
  889. }
  890. m_bPendingPingRefresh = false;
  891. m_rtLastPingFix = CRTime::RTime32TimeCur();
  892. m_msgCachedPingUpdate = newPingUpdate;
  893. IGameEvent *event = gameeventmanager->CreateEvent( "ping_updated" );
  894. if ( event )
  895. {
  896. gameeventmanager->FireEventClientSide( event );
  897. }
  898. if ( BPingDebug() )
  899. {
  900. DumpPing();
  901. }
  902. }
  903. #ifdef TF_GC_PING_DEBUG
  904. //-----------------------------------------------------------------------------
  905. void CTFGCClientSystem::SetPingOverride( const char *pszDataCenter, uint32 nPing, CMsgGCDataCenterPing_Update_Status eStatus )
  906. {
  907. CMsgGCDataCenterPing_Update_PingEntry newEntry;
  908. newEntry.set_name( pszDataCenter );
  909. newEntry.set_ping( nPing );
  910. newEntry.set_ping_status( eStatus );
  911. ApplyPingToMsg( m_msgPingOverrides, newEntry );
  912. InvalidatePingData();
  913. }
  914. //-----------------------------------------------------------------------------
  915. void CTFGCClientSystem::ClearPingOverrides()
  916. {
  917. m_msgPingOverrides.clear_pingdata();
  918. InvalidatePingData();
  919. }
  920. #endif // def TF_GC_PING_DEBUG
  921. //-----------------------------------------------------------------------------
  922. void CTFGCClientSystem::Update( float frametime )
  923. {
  924. BaseClass::Update( frametime );
  925. // if ( m_flGetNewsTime != 0.0f && Plat_FloatTime() > m_flGetNewsTime && steamapicontext && steamapicontext->SteamUtils() )
  926. // {
  927. // m_flGetNewsTime = 0.0f;
  928. //
  929. // // get the latest news
  930. // CGCClientJobGetNews *pJob = new CGCClientJobGetNews( GCClientSystem()->GetGCClient(), (int) engine->GetAppID() );
  931. // pJob->StartJob( NULL );
  932. // }
  933. PingThink();
  934. // Check if it's time to send a party update message
  935. if ( Plat_FloatTime() > m_flSendPartyUpdateMessageTime )
  936. {
  937. m_flSendPartyUpdateMessageTime = FLT_MAX;
  938. Assert( m_pPendingCreateOrUpdatePartyMsg );
  939. if ( m_pPendingCreateOrUpdatePartyMsg )
  940. {
  941. // Send the message
  942. m_pPendingCreateOrUpdatePartyMsg->StartJob( NULL );
  943. m_pPendingCreateOrUpdatePartyMsg = NULL;
  944. }
  945. }
  946. CTFParty *pParty = GetParty();
  947. CTFGSLobby *pLobby = GetLobby();
  948. // Are we in a active lobby?
  949. bool bInLiveMatch = BConnectedToMatchServer( true );
  950. // If we do, are we actively connected to said match?
  951. bool bHaveLiveMatch = BHaveLiveMatch();
  952. bool bNewServerAssignment = m_bServerAssignmentChanged;
  953. m_bServerAssignmentChanged = false;
  954. Assert( !bInLiveMatch || m_steamIDCurrentServer.IsValid() );
  955. if ( bInLiveMatch )
  956. {
  957. // We cannot assume cannot assume the gc will tell of us the match ending -- the GC connection is fallible, and
  958. // the gameserver is authoritative once we're assigned (as long as the GC doesn't revoke said assignment, see
  959. // SOChanged)
  960. CTFGameRules *pTFGameRules = TFGameRules();
  961. // If we're not loaded enough to look at gamerules, assume the match is live until we reach that state.
  962. //
  963. // - Because source engine, we can get a stale TFGameRules from our *last* game *after* starting a new
  964. // connection. Only even think about asking once our connect state hits connected (keyed to server_spawn)
  965. if ( m_eConnectState == eConnectState_ConnectedToMatchmade &&
  966. engine->IsInGame() &&
  967. pTFGameRules && pTFGameRules->RecievedBaseline() && pTFGameRules->IsManagedMatchEnded() )
  968. {
  969. // We no longer consider this our assigned match. Only the GC can change the GCAssignedMatch, this bool is
  970. // our "but we reject this". SOChanged will clear it if a new assignment overrides things.
  971. GCMatchmakingDebugSpew( 1, "GS marked assigned match as ended\n" );
  972. m_bAssignedMatchEnded = true;
  973. }
  974. }
  975. if ( !bHaveLiveMatch )
  976. {
  977. // Are we waiting to activate the lobby UI until a certain party appears?
  978. if ( m_nPendingAutoJoinPartyID != 0 && pParty != NULL && pParty->GetGroupID() == m_nPendingAutoJoinPartyID )
  979. {
  980. Msg( "New party was instanced that GC told us to expect. Entering matchmaking lobby UI\n" );
  981. engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby ladder" );
  982. BeginMatchmaking( pParty->GetMatchmakingMode() );
  983. }
  984. /// XXX(JohnS): Right now invites just wont work if you're in a match, needs better flow.
  985. // Do we have a pending invite we need to process?
  986. if ( m_eAcceptInviteStep == eAcceptInviteStep_ReadyToJoinSteamLobby )
  987. {
  988. // Wait for everything else to go away, if we have anything
  989. if ( !IsConnectStateDisconnected() )
  990. {
  991. //Msg( "Disconnecting from current server to accept invite\n" );
  992. engine->ClientCmd_Unrestricted( "disconnect" );
  993. }
  994. else if ( m_bUserWantsToBeInMatchmaking )
  995. {
  996. EndMatchmaking();
  997. }
  998. else if ( pLobby == NULL && GetParty() == NULL )
  999. {
  1000. Assert( m_steamIDLobbyInviteAccepted.IsValid() );
  1001. m_eAcceptInviteStep = eAcceptInviteStep_JoinSteamLobby;
  1002. // OK, start joining the lobby.
  1003. Msg( "Joining lobby %s\n", m_steamIDLobbyInviteAccepted.Render() );
  1004. steamapicontext->SteamMatchmaking()->JoinLobby( m_steamIDLobbyInviteAccepted );
  1005. m_steamIDLobbyInviteAccepted = CSteamID();
  1006. }
  1007. }
  1008. }
  1009. if ( bHaveLiveMatch )
  1010. {
  1011. if ( m_eConnectState != eConnectState_Disconnected && engine->IsInGame() )
  1012. {
  1013. // The dashboard will handle this automatically
  1014. }
  1015. else if ( m_bUserWantsToBeInMatchmaking && bNewServerAssignment )
  1016. {
  1017. // Use the autojoin if in the MM flow and this is a fresh match
  1018. m_AutoJoinHandler.MatchFound();
  1019. }
  1020. else
  1021. {
  1022. // Use the prompt
  1023. m_PromptJoinHandler.MatchFound();
  1024. }
  1025. }
  1026. FOR_EACH_VEC_BACK( m_vecDelayedLocalPlayerSOListenersToAdd, i )
  1027. {
  1028. SubscribeToLocalPlayerSOCache( m_vecDelayedLocalPlayerSOListenersToAdd[ i ] );
  1029. m_vecDelayedLocalPlayerSOListenersToAdd.Remove( i );
  1030. }
  1031. }
  1032. void CTFGCClientSystem::SOCacheSubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent )
  1033. {
  1034. if ( steamIDOwner == ClientSteamContext().GetLocalPlayerSteamID() )
  1035. {
  1036. // Assert( m_pSOCache == NULL ); // we *can* get two SOCacheSubscribed calls in a row.
  1037. m_pSOCache = GCClientSystem()->GetSOCache( steamIDOwner );
  1038. Assert( m_pSOCache != NULL );
  1039. if ( gameeventmanager )
  1040. {
  1041. // force a party/lobby update whenever our SO cache arrives
  1042. FireGameEventPartyUpdated();
  1043. FireGameEventLobbyUpdated();
  1044. }
  1045. }
  1046. }
  1047. void CTFGCClientSystem::FireGameEventPartyUpdated()
  1048. {
  1049. IGameEvent *event = gameeventmanager->CreateEvent( "party_updated" );
  1050. if ( event )
  1051. {
  1052. gameeventmanager->FireEventClientSide( event );
  1053. }
  1054. }
  1055. void CTFGCClientSystem::FireGameEventLobbyUpdated()
  1056. {
  1057. IGameEvent *event2 = gameeventmanager->CreateEvent( "lobby_updated" );
  1058. if ( event2 )
  1059. {
  1060. gameeventmanager->FireEventClientSide( event2 );
  1061. }
  1062. }
  1063. void CTFGCClientSystem::SOCreated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) { SOChanged( pObject, SOChanged_Create, eEvent ); }
  1064. void CTFGCClientSystem::SOUpdated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) { SOChanged( pObject, SOChanged_Update, eEvent ); }
  1065. void CTFGCClientSystem::SODestroyed( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) { SOChanged( pObject, SOChanged_Destroy, eEvent ); }
  1066. void CTFGCClientSystem::SOChanged( const GCSDK::CSharedObject *pObject, SOChangeType_t changeType, GCSDK::ESOCacheEvent eEvent )
  1067. {
  1068. // Broadcasts
  1069. if ( pObject->GetTypeID() == CTFParty::k_nTypeID )
  1070. {
  1071. #if GCMATCHMAKING_DEBUG_LEVEL > 0
  1072. switch ( changeType )
  1073. {
  1074. case SOChanged_Create: GCMatchmakingDebugSpew( 1, "Party created\n"); break;
  1075. case SOChanged_Update: GCMatchmakingDebugSpew( 2, "Party updated\n"); break;
  1076. case SOChanged_Destroy: GCMatchmakingDebugSpew( 1, "Party destroyed\n"); break;
  1077. default: AssertMsg1( false, "Bogus change type %d", changeType );
  1078. }
  1079. #endif
  1080. CTFParty *pParty = GetParty();
  1081. if ( changeType == SOChanged_Destroy )
  1082. {
  1083. Assert( pParty == NULL );
  1084. FireGameEventPartyUpdated();
  1085. }
  1086. else if ( pParty != NULL ) // FIXME we're restarting the game after a crash, rejoining a match, and have no wizard step (or other BeginMatchmaking()) setup, so when we return to UI it's state is running but fucked
  1087. {
  1088. if ( pParty->BOffline() )
  1089. {
  1090. pParty->SetOffline( false );
  1091. // The user says they dont want to be in matchmaking, but the party coming in says that its
  1092. // searching or hanging out in the UI, which is not what we're doing. This can happen in
  1093. // the following circumstances:
  1094. // 1) With the GC up, start searching for a match
  1095. // 2) Crash the GC
  1096. // 3) Go back to the main menu
  1097. // 4) Reboot the GC
  1098. if ( pParty->GetState() == CSOTFParty_State_UI || pParty->GetState() == CSOTFParty_State_FINDING_MATCH )
  1099. {
  1100. if ( !m_bUserWantsToBeInMatchmaking )
  1101. {
  1102. Msg( "Party was updated/created, but our party is marked offline, we don't want to be matchmaking, and the party is not in a match. Ending matchmaking\n" );
  1103. EndMatchmaking();
  1104. }
  1105. else
  1106. {
  1107. Msg( "Party was updated/created, and our party is marked offline, and the party is not in a match. Sending update to GC with our predicted changes\n" );
  1108. CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
  1109. pMsg->set_wizard_step( m_eLocalWizardStep );
  1110. }
  1111. }
  1112. }
  1113. else
  1114. {
  1115. bool bShouldGoToMMUI = false;
  1116. // We'll hit this when start the game back up after a crash
  1117. if ( !m_bUserWantsToBeInMatchmaking && ( eEvent == eSOCacheEvent_Subscribed || eEvent == eSOCacheEvent_ListenerAdded ) && m_eAcceptInviteStep != eAcceptInviteStep_JoinParty )
  1118. {
  1119. switch( pParty->GetState() )
  1120. {
  1121. case CSOTFParty_State_UI:
  1122. case CSOTFParty_State_FINDING_MATCH:
  1123. // They backed out of the MM UI somehow, and are getting party updates. We want out.
  1124. if ( pParty->GetNumMembers() <= 1 )
  1125. {
  1126. Msg( "Creating a party when we don't want to be in matchmaking, and we're the only ones in it. Possibly and old party from an old session. Ending matchmaking.\n" );
  1127. EndMatchmaking();
  1128. }
  1129. else
  1130. {
  1131. Msg( "Creating a party when we don't want to be in matchmaking, and it has other players in it. Possibly and old party from an old session. Going to MM UI.\n" );
  1132. bShouldGoToMMUI = true;
  1133. }
  1134. break;
  1135. case CSOTFParty_State_IN_MATCH:
  1136. case CSOTFParty_State_AWAITING_RESERVATION_CONFIRMATION:
  1137. // We don't have a match, but we're still in a party. Leave matchmaking.
  1138. // TODO: Once the lobby panels are no longer a nightmare, let this happen.
  1139. // We dont really want to destroy their party, but it's too much of
  1140. // a hassle to support now.
  1141. if ( !BHaveLiveMatch() )
  1142. {
  1143. Msg( "Creating a party when we don't want to be in matchmaking, and it has other players in it, and it's live. Leaving matchmaking\n" );
  1144. EndMatchmaking();
  1145. }
  1146. break;
  1147. default:
  1148. AssertMsg1( false, "Unhandled party state %d", pParty->GetState() );
  1149. }
  1150. }
  1151. if ( bShouldGoToMMUI )
  1152. {
  1153. if ( IsLadderGroup( pParty->GetMatchGroup() ) )
  1154. {
  1155. engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby ladder" );
  1156. }
  1157. else if ( IsCasualGroup( pParty->GetMatchGroup() ) )
  1158. {
  1159. engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby casual" );
  1160. }
  1161. else if ( IsMvMMatchGroup( pParty->GetMatchGroup() ) )
  1162. {
  1163. engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby mvm" );
  1164. }
  1165. BeginMatchmaking( pParty->GetMatchmakingMode() );
  1166. }
  1167. // Check if a party was instanced on us as a process of accepting an invite
  1168. if ( m_eAcceptInviteStep == eAcceptInviteStep_JoinParty )
  1169. {
  1170. m_eAcceptInviteStep = eAcceptInviteStep_None;
  1171. Msg( "Party was instanced as a result of accepting invite. Entering matchmaking lobby UI\n" );
  1172. engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby invited" );
  1173. BeginMatchmaking( pParty->GetMatchmakingMode() );
  1174. }
  1175. //m_msgLocalSearchCriteria.set_key( pParty->Obj().search_key() );
  1176. m_msgLocalSearchCriteria.set_late_join_ok( pParty->Obj().search_late_join_ok() );
  1177. //m_msgLocalSearchCriteria.set_matchgroups( pParty->Obj().matchgroups() );
  1178. m_msgLocalSearchCriteria.set_matchmaking_mode( pParty->GetMatchmakingMode() );
  1179. m_msgLocalSearchCriteria.set_quickplay_game_type( pParty->GetSearchQuickplayGameType() );
  1180. m_msgLocalSearchCriteria.clear_mvm_missions();
  1181. #ifdef USE_MVM_TOUR
  1182. m_msgLocalSearchCriteria.clear_mvm_mannup_tour();
  1183. #endif // USE_MVM_TOUR
  1184. if ( pParty->GetMatchmakingMode() == TF_Matchmaking_MVM )
  1185. {
  1186. m_msgLocalSearchCriteria.mutable_mvm_missions()->MergeFrom( pParty->Obj().search_mvm_missions() );
  1187. #ifdef USE_MVM_TOUR
  1188. if ( pParty->GetSearchPlayForBraggingRights() )
  1189. m_msgLocalSearchCriteria.set_mvm_mannup_tour( pParty->GetSearchMannUpTourName() );
  1190. #endif // USE_MVM_TOUR
  1191. }
  1192. else if ( pParty->GetMatchmakingMode() == TF_Matchmaking_LADDER )
  1193. {
  1194. m_msgLocalSearchCriteria.set_ladder_game_type( pParty->Obj().search_ladder_game_type() );
  1195. }
  1196. else if ( pParty->GetMatchmakingMode() == TF_Matchmaking_CASUAL )
  1197. {
  1198. m_msgLocalSearchCriteria.mutable_casual_criteria()->CopyFrom( pParty->Obj().search_casual() );
  1199. }
  1200. m_bLocalSquadSurplus = false;
  1201. int iLocalMemberIdx = pParty->GetMemberIndexBySteamID( steamapicontext->SteamUser()->GetSteamID() );
  1202. if ( iLocalMemberIdx >= 0 )
  1203. {
  1204. m_bLocalSquadSurplus = pParty->Obj().members( iLocalMemberIdx ).squad_surplus();
  1205. }
  1206. Assert( pParty->Obj().has_wizard_step() );
  1207. if ( pParty->Obj().has_wizard_step() )
  1208. {
  1209. // If entering or leaving the searching state, clear searching stats
  1210. if ( m_eLocalWizardStep != TF_Matchmaking_WizardStep_SEARCHING || pParty->Obj().wizard_step() != TF_Matchmaking_WizardStep_SEARCHING )
  1211. {
  1212. m_msgMatchmakingProgress.Clear();
  1213. }
  1214. // Get on the same page as the GC. But if we have a pending request to change the current step,
  1215. // then wait for that to finish. Otherwise the current step could flicker back and forth.
  1216. if ( s_nNumWizardStepChangesWaitingForReply == 0 )
  1217. {
  1218. m_eLocalWizardStep = pParty->Obj().wizard_step();
  1219. }
  1220. }
  1221. }
  1222. }
  1223. else
  1224. {
  1225. Assert( pParty != NULL );
  1226. }
  1227. FireGameEventPartyUpdated();
  1228. CheckAssociatePartyAndSteamLobby();
  1229. // Check if we're ready to active the Steam overlay to invite a user
  1230. CheckReadyToActivateInvite();
  1231. }
  1232. else if ( pObject->GetTypeID() == CTFGSLobby::k_nTypeID )
  1233. {
  1234. #if GCMATCHMAKING_DEBUG_LEVEL > 0
  1235. switch ( changeType )
  1236. {
  1237. case SOChanged_Create: GCMatchmakingDebugSpew( 1, "Lobby created\n"); break;
  1238. case SOChanged_Update: GCMatchmakingDebugSpew( 2, "Lobby updated\n"); break;
  1239. case SOChanged_Destroy: GCMatchmakingDebugSpew( 1, "Lobby destroyed\n"); break;
  1240. default: AssertMsg1( false, "Bogus change type %d", changeType );
  1241. }
  1242. #endif
  1243. CTFGSLobby *pLobby = GetLobby();
  1244. CSteamID currentServer;
  1245. if ( pLobby && pLobby->GetState() == CSOTFGameServerLobby_State_RUN )
  1246. {
  1247. currentServer = pLobby->GetServerID();
  1248. }
  1249. // We cannot take the Lobby being deleted as a server assignment change, since the GC could crash and fail to
  1250. // recover lobbies. Or, since lobbies are lazy-loaded from memcache, it may come back up and lazily put us back
  1251. // into our lobby.
  1252. //
  1253. // However, since we have no way to ask the gameserver without being connected to it, we'll treat
  1254. // lobby-destroyed as canon only if we're not connected to the match. Otherwise, we'll assume the gameserver's
  1255. // m_bAssignedMatchEnded flag is the authority.
  1256. //
  1257. // Thus, this line reads:
  1258. // - If we got a *new and differing* lobby, propagate it to the m_*Assigned* convars.
  1259. // - If our lobby *went away*, clear these convars IF:
  1260. // - We're not connected to a match server
  1261. // - OR the gameserver concurs that the match is over
  1262. bool bLobbyChanged = ( currentServer != m_steamIDGCAssignedMatch ) ||
  1263. ( pLobby && pLobby->GetMatchID() != m_uAssignedMatchID ) ;
  1264. if ( bLobbyChanged && ( !BConnectedToMatchServer( true ) || pLobby || m_bAssignedMatchEnded ) )
  1265. {
  1266. Msg( "Lobby received with a differing steamID. Lobby's: %s CurrentlyAssigned: %s ConnectedToMatchServer: %d HasLobby: %d AssignedMatchEnded: %d\n"
  1267. , currentServer.Render()
  1268. , m_steamIDGCAssignedMatch.Render()
  1269. , BConnectedToMatchServer( true )
  1270. , pLobby != NULL
  1271. , m_bAssignedMatchEnded );
  1272. m_bServerAssignmentChanged = true;
  1273. m_steamIDGCAssignedMatch = currentServer;
  1274. m_bAssignedMatchEnded = pLobby ? false : m_bAssignedMatchEnded; // If the lobby is still here, we know the match isn't over.
  1275. m_uAssignedMatchID = pLobby ? pLobby->GetMatchID() : 0;
  1276. m_eAssignedMatchGroup = pLobby ? pLobby->GetMatchGroup() : k_nMatchGroup_Invalid;
  1277. // Store match connection history for generic server browser/connection code to reason about which of our
  1278. // connections was match related.
  1279. netadr_t connectAdr; // but y is string
  1280. if ( pLobby && connectAdr.SetFromString( pLobby->GetConnect() ) )
  1281. {
  1282. m_vecMatchServerHistory.AddToTail( connectAdr );
  1283. }
  1284. }
  1285. //CTFParty *pParty = GetParty();
  1286. // Lobby is gone, but we're connected to our match server still.
  1287. /*if ( pParty && !pLobby && BConnectedToMatchServer( false ) )
  1288. {
  1289. const IMatchGroupDescription* pDesc = GetMatchGroupDescription( pParty->GetMatchGroup() );
  1290. if ( pDesc && pDesc->BShouldAutomaticallyRequeueOnMatchEnd() )
  1291. {
  1292. SendCreateOrUpdatePartyMsg( TF_Matchmaking_WizardStep_SEARCHING );
  1293. }
  1294. }*/
  1295. FireGameEventLobbyUpdated();
  1296. }
  1297. // Notifications. Sync/add/delete with what's in our notification drawer
  1298. else if ( pObject->GetTypeID() == CTFNotification::k_nTypeID )
  1299. {
  1300. const CTFNotification* pSONotification = ( const CTFNotification* )( pObject );
  1301. Msg( "Notification %llu %s: \"%s\"\n",
  1302. pSONotification->Obj().notification_id(),
  1303. changeType == SOChanged_Create ? "created" : changeType == SOChanged_Destroy ? "destroyed" : "updated",
  1304. pSONotification->Obj().notification_string().c_str() );
  1305. // Update existing notification if found
  1306. bool bFound = false;
  1307. for ( int i = NotificationQueue_GetNumNotifications() - 1; i >= 0; --i )
  1308. {
  1309. CClientNotification *pNotif = dynamic_cast<CClientNotification *>(NotificationQueue_GetByIndex( i ));
  1310. if ( pNotif && pNotif->NotificationID() == pSONotification->Obj().notification_id() )
  1311. {
  1312. Msg( "Notification %llu already displayed, updating\n",
  1313. pSONotification->Obj().notification_id() );
  1314. bFound = true;
  1315. if ( changeType == SOChanged_Destroy )
  1316. {
  1317. NotificationQueue_Remove( pNotif );
  1318. }
  1319. else
  1320. {
  1321. pNotif->Update( pSONotification );
  1322. }
  1323. }
  1324. }
  1325. // Add them to our notifications drawer if not
  1326. if ( !bFound && changeType != SOChanged_Destroy )
  1327. {
  1328. Msg( "New notification %llu arrived: \"%s\"\n",
  1329. pSONotification->Obj().notification_id(),
  1330. pSONotification->Obj().notification_string().c_str() );
  1331. CClientNotification *pClientNotification = new CClientNotification();
  1332. pClientNotification->Update( pSONotification );
  1333. NotificationQueue_Add( pClientNotification );
  1334. }
  1335. }
  1336. // // After here we only care about create or change events
  1337. // if ( changeType == SOChanged_Destroy )
  1338. // {
  1339. // return;
  1340. // }
  1341. //
  1342. // if( pObject->GetTypeID() == CTFGameAccountClient::k_nTypeID )
  1343. // {
  1344. // CTFGameAccountClient *pAccount = (CTFGameAccountClient *)pObject;
  1345. // m_unWinCount = pAccount->GetWins();
  1346. // m_unLossCount = pAccount->GetLosses();
  1347. // }
  1348. // else if ( pObject->GetTypeID() == CTFHeroStandings::k_nTypeID )
  1349. // {
  1350. // CTFHeroStandings *pHeroStandings = (CTFHeroStandings *)pObject;
  1351. // // see if we have an entry for this already
  1352. // int nFoundIndex = -1;
  1353. // for ( int i = 0; i < m_aHeroRecords.Count(); i++ )
  1354. // {
  1355. // if ( m_aHeroRecords[i].m_unHeroID == pHeroStandings->GetHeroID() )
  1356. // {
  1357. // nFoundIndex = i;
  1358. // break;
  1359. // }
  1360. // }
  1361. // if ( nFoundIndex == -1 )
  1362. // {
  1363. // GCHeroRecord_t newHeroStanding;
  1364. // nFoundIndex = m_aHeroRecords.InsertNoSort( newHeroStanding );
  1365. // }
  1366. //
  1367. // m_aHeroRecords[ nFoundIndex ].m_unHeroID = pHeroStandings->GetHeroID();
  1368. // m_aHeroRecords[ nFoundIndex ].m_unWinCount = pHeroStandings->GetWins();
  1369. // m_aHeroRecords[ nFoundIndex ].m_unLossCount = pHeroStandings->GetLosses();
  1370. //
  1371. // m_aHeroRecords.RedoSort();
  1372. // }
  1373. }
  1374. //KeyValues *CTFGCClientSystem::GetNewsStory( uint64 unNewsID )
  1375. //{
  1376. // if ( !m_pNewsKeys )
  1377. // return NULL;
  1378. //
  1379. // for ( KeyValues *pItems = m_pNewsKeys->GetFirstSubKey(); pItems; pItems = pItems->GetNextKey() )
  1380. // {
  1381. // if ( !Q_stricmp( pItems->GetName(), "newsitems" ) )
  1382. // {
  1383. // for ( KeyValues *pItem = pItems->GetFirstSubKey(); pItem; pItem = pItem->GetNextKey() )
  1384. // {
  1385. // if ( !Q_stricmp( pItem->GetName(), "newsitem" ) )
  1386. // {
  1387. // for ( KeyValues *pStory = pItem->GetFirstSubKey(); pStory; pStory = pStory->GetNextKey() )
  1388. // {
  1389. // if ( pStory->GetUint64( "gid" ) == unNewsID )
  1390. // {
  1391. // return pStory;
  1392. // }
  1393. // }
  1394. // }
  1395. // }
  1396. // }
  1397. // }
  1398. // return NULL;
  1399. //}
  1400. //
  1401. //KeyValues *CTFGCClientSystem::GetNewsStoryByIndex( int nNewsIndex )
  1402. //{
  1403. // if ( !m_pNewsKeys )
  1404. // return NULL;
  1405. //
  1406. // int nCount = 0;
  1407. // for ( KeyValues *pItems = m_pNewsKeys->GetFirstSubKey(); pItems; pItems = pItems->GetNextKey() )
  1408. // {
  1409. // if ( !Q_stricmp( pItems->GetName(), "newsitems" ) )
  1410. // {
  1411. // for ( KeyValues *pItem = pItems->GetFirstSubKey(); pItem; pItem = pItem->GetNextKey() )
  1412. // {
  1413. // if ( !Q_stricmp( pItem->GetName(), "newsitem" ) )
  1414. // {
  1415. // for ( KeyValues *pStory = pItem->GetFirstSubKey(); pStory; pStory = pStory->GetNextKey() )
  1416. // {
  1417. // if ( nCount >= nNewsIndex )
  1418. // {
  1419. // return pStory;
  1420. // }
  1421. // nCount++;
  1422. // }
  1423. // }
  1424. // }
  1425. // }
  1426. // }
  1427. // return NULL;
  1428. //}
  1429. void CTFGCClientSystem::DumpInvites()
  1430. {
  1431. if ( !m_pSOCache )
  1432. {
  1433. Msg( "No SO cache.\n" );
  1434. return;
  1435. }
  1436. CSharedObjectTypeCache *pTypeCache = m_pSOCache->FindBaseTypeCache( CTFPartyInvite::k_nTypeID );
  1437. if ( !pTypeCache )
  1438. {
  1439. Msg( "No invites typecache.\n" );
  1440. return;
  1441. }
  1442. Msg( "Listing invites in typecache:\n" );
  1443. for ( uint32 i = 0; i < pTypeCache->GetCount(); i++ )
  1444. {
  1445. CTFPartyInvite *pInvite = static_cast<CTFPartyInvite*>( pTypeCache->GetObject( i ) );
  1446. Msg( "[%u] PartyID = %llu Sender = %s %s\n", i, pInvite->GetGroupID(), pInvite->GetSenderID().Render(), pInvite->GetSenderName() );
  1447. }
  1448. }
  1449. void CTFGCClientSystem::DumpPing()
  1450. {
  1451. // RTime32 m_rtLastPingFix;
  1452. // bool m_bPendingPingRefresh;
  1453. // bool m_bSentInitialPingFix;
  1454. if ( !m_rtLastPingFix )
  1455. {
  1456. TFPingMsg( "No current ping data. Pending refresh: %i, Sent initial fix: %i\n",
  1457. m_bPendingPingRefresh, m_bSentInitialPingFix );
  1458. return;
  1459. }
  1460. char szLastFix[ k_RTimeRenderBufferSize ] = { 0 };
  1461. CRTime::Render( m_rtLastPingFix, szLastFix );
  1462. TFPingMsg( "Ping data is current as of %s. Pending refresh: %i, Sent initial fix: %i\n",
  1463. szLastFix, m_bPendingPingRefresh, m_bSentInitialPingFix );
  1464. for ( int i = 0; i < m_msgCachedPingUpdate.pingdata_size(); i++ )
  1465. {
  1466. Msg( " %5s: %dms, status %i\n",
  1467. m_msgCachedPingUpdate.pingdata( i ).name().c_str(),
  1468. m_msgCachedPingUpdate.pingdata( i ).ping(),
  1469. m_msgCachedPingUpdate.pingdata( i ).ping_status() );
  1470. }
  1471. }
  1472. //CTFGameAccountClient* CTFGCClientSystem::GetGameAccountClient()
  1473. //{
  1474. // if ( !m_pSOCache )
  1475. // return NULL;
  1476. //
  1477. // CSharedObjectTypeCache *pTypeCache = m_pSOCache->GetBaseTypeCache( CTFGameAccountClient::k_nTypeID );
  1478. // if ( pTypeCache && pTypeCache->GetCount() > 0 )
  1479. // {
  1480. // AssertMsg1( pTypeCache->GetCount() == 1, "Client has %d CTFGameAccountClient objects in his cache! He should only have 1.", pTypeCache->GetCount() );
  1481. // CTFGameAccountClient *pObject = dynamic_cast<CTFGameAccountClient*>( pTypeCache->GetObject( pTypeCache->GetCount() - 1 ) );
  1482. // return pObject;
  1483. // }
  1484. // return NULL;
  1485. //}
  1486. //
  1487. //void CTFGCClientSystem::DumpGameAccountClient()
  1488. //{
  1489. // CTFGameAccountClient *pObj = GetGameAccountClient();
  1490. // if ( !pObj )
  1491. // {
  1492. // Msg( "Failed to find CTFGameAccountClient shared object\n" );
  1493. // return;
  1494. // }
  1495. //
  1496. // Msg( "CTFGameAccountClient:\n" );
  1497. // pObj->Dump();
  1498. //}
  1499. //CTFBetaParticipation* CTFGCClientSystem::GetBetaParticipation()
  1500. //{
  1501. // if ( !m_pSOCache )
  1502. // return NULL;
  1503. //
  1504. // CSharedObjectTypeCache *pTypeCache = m_pSOCache->GetBaseTypeCache( CTFBetaParticipation::k_nTypeID );
  1505. // if ( pTypeCache && pTypeCache->GetCount() > 0 )
  1506. // {
  1507. // AssertMsg1( pTypeCache->GetCount() == 1, "Client has %d CTFBetaParticipation objects in his cache! He should only have 1.", pTypeCache->GetCount() );
  1508. // CTFBetaParticipation *pObject = dynamic_cast<CTFBetaParticipation*>( pTypeCache->GetObject( pTypeCache->GetCount() - 1 ) );
  1509. // return pObject;
  1510. // }
  1511. // return NULL;
  1512. //}
  1513. //
  1514. //void CTFGCClientSystem::DumpBetaParticipation()
  1515. //{
  1516. // CTFBetaParticipation *pObj = GetBetaParticipation();
  1517. // if ( !pObj )
  1518. // {
  1519. // Msg( "Failed to find beta participation shared object\n" );
  1520. // return;
  1521. // }
  1522. //
  1523. // Msg( "Beta participation:\n" );
  1524. // pObj->Dump();
  1525. //}
  1526. void CTFGCClientSystem::DumpParty()
  1527. {
  1528. CTFParty *pParty = GetParty();
  1529. if ( !pParty )
  1530. {
  1531. Msg( "Failed to find party shared object\n" );
  1532. return;
  1533. }
  1534. pParty->SpewDebug();
  1535. }
  1536. CTFParty* CTFGCClientSystem::GetParty()
  1537. {
  1538. if ( !m_pSOCache )
  1539. return NULL;
  1540. CSharedObjectTypeCache *pTypeCache = m_pSOCache->FindBaseTypeCache( CTFParty::k_nTypeID );
  1541. if ( pTypeCache && pTypeCache->GetCount() > 0 )
  1542. {
  1543. AssertMsg1( pTypeCache->GetCount() == 1, "Client has %d party objects in his cache! He should only have 1.", pTypeCache->GetCount() );
  1544. return static_cast<CTFParty*>( pTypeCache->GetObject( pTypeCache->GetCount() - 1 ) );
  1545. }
  1546. return NULL;
  1547. }
  1548. void CTFGCClientSystem::CreateNewParty()
  1549. {
  1550. Assert( GetParty() == NULL );
  1551. if ( GetParty() )
  1552. return;
  1553. switch( GetSearchMode() )
  1554. {
  1555. case TF_Matchmaking_LADDER:
  1556. RequestSelectWizardStep( TF_Matchmaking_WizardStep_LADDER );
  1557. break;
  1558. case TF_Matchmaking_CASUAL:
  1559. RequestSelectWizardStep( TF_Matchmaking_WizardStep_CASUAL );
  1560. break;
  1561. default:
  1562. // Unhandled for now.
  1563. // TODO: When GetSearchMode() goes away and we just deal with match groups
  1564. // fixup all these damn switches everywhere
  1565. Assert( false );
  1566. break;
  1567. };
  1568. // Get the party created. This will get our search criteria set. It will
  1569. // be the criteria of whatever our previous party was. I *think* this is the
  1570. // most intuitive thing to do, but we can instead use whatever the local guy's
  1571. // preferred criteria if this feels weird.
  1572. SendCreateOrUpdatePartyMsg( m_eLocalWizardStep );
  1573. }
  1574. CTFGSLobby* CTFGCClientSystem::GetLobby()
  1575. {
  1576. if ( !m_pSOCache )
  1577. return NULL;
  1578. CSharedObjectTypeCache *pTypeCache = m_pSOCache->FindBaseTypeCache( CTFGSLobby::k_nTypeID );
  1579. if ( pTypeCache && pTypeCache->GetCount() > 0 )
  1580. {
  1581. AssertMsg1( pTypeCache->GetCount() == 1, "Client has %d lobby objects in his cache! He should only have 1.", pTypeCache->GetCount() );
  1582. CTFGSLobby *pLobby = dynamic_cast<CTFGSLobby*>( pTypeCache->GetObject( pTypeCache->GetCount() - 1 ) );
  1583. return pLobby;
  1584. }
  1585. return NULL;
  1586. }
  1587. bool CTFGCClientSystem::BIsPartyLeader()
  1588. {
  1589. CTFParty *pParty = GetParty();
  1590. if ( pParty == NULL )
  1591. return true;
  1592. Assert( steamapicontext );
  1593. Assert( steamapicontext->SteamUser() );
  1594. if ( pParty->GetLeader() == steamapicontext->SteamUser()->GetSteamID() )
  1595. return true;
  1596. return false;
  1597. }
  1598. bool CTFGCClientSystem::BHasOutstandingMatchmakingPartyMessage() const
  1599. {
  1600. return m_pPendingCreateOrUpdatePartyMsg != NULL || s_nNumWizardStepChangesWaitingForReply > 0;
  1601. }
  1602. void CTFGCClientSystem::DumpLobby()
  1603. {
  1604. CTFGSLobby *pLobby = GetLobby();
  1605. if ( !pLobby )
  1606. {
  1607. Msg( "Failed to find lobby shared object\n" );
  1608. return;
  1609. }
  1610. pLobby->SpewDebug();
  1611. }
  1612. #ifdef _DEBUG
  1613. static ConVar mm_debug_ignore_connect( "mm_debug_ignore_connect", "0", FCVAR_ARCHIVE, "Debug command to discard matchmaking commands to connect to server" );
  1614. #endif
  1615. //-----------------------------------------------------------------------------
  1616. #ifdef STAGING_ONLY
  1617. static ConVar tf_competitive_convar_restrictions_disabled( "tf_competitive_convar_restrictions_disabled", "0", FCVAR_NONE, "If set, this will disable competitive convar restrictions." );
  1618. #endif // STAGING_ONLY
  1619. bool ForceCompetitiveConvars()
  1620. {
  1621. #ifdef STAGING_ONLY
  1622. if ( tf_competitive_convar_restrictions_disabled.GetBool() )
  1623. {
  1624. return true;
  1625. }
  1626. #endif // STAGING_ONLY
  1627. bool anyFailures = false;
  1628. Assert( ThreadInMainThread() );
  1629. for ( ConCommandBase *ccb = g_pCVar->GetCommands(); ccb; ccb = ccb->GetNext() )
  1630. {
  1631. if ( ccb->IsCommand() )
  1632. continue;
  1633. ConVar *pVar = ( ConVar * ) ccb;
  1634. if ( !pVar->IsCompetitiveRestricted() )
  1635. continue;
  1636. // Hack: This var is created by the dxconfig system, but it doesn't actually exist.
  1637. // Skip it so we have no vars change when running a clean config.
  1638. if ( V_stricmp( pVar->GetName(), "r_decal_cullsize" ) == 0 )
  1639. continue;
  1640. if ( !pVar->SetCompetitiveMode( true ) )
  1641. anyFailures = true;
  1642. }
  1643. return !anyFailures;
  1644. }
  1645. void CTFGCClientSystem::ConnectToServer( const char *connect )
  1646. {
  1647. CTFGSLobby *pLobby = GetLobby();
  1648. Assert( pLobby );
  1649. if ( !pLobby )
  1650. return;
  1651. // !TEST! Check convar to stub connection
  1652. #ifdef _DEBUG
  1653. if ( mm_debug_ignore_connect.GetBool() )
  1654. {
  1655. Warning(" IGNORING request to connect to %s as per mm_debug_ignore_connect\n", connect );
  1656. return;
  1657. }
  1658. #endif
  1659. Msg("Connecting to %s\n", connect );
  1660. CUtlString connectCmd;
  1661. connectCmd.Format( "connect %s matchmaking", connect );
  1662. if ( engine )
  1663. {
  1664. const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( m_eAssignedMatchGroup );
  1665. bool bAllowed = !( pMatchDesc && pMatchDesc->m_params.m_bForceClientSettings ) || ForceCompetitiveConvars();
  1666. if ( !bAllowed )
  1667. {
  1668. // ForceCompetitiveConvars() shouldn't fail
  1669. Assert( 0 );
  1670. }
  1671. engine->ClientCmd_Unrestricted( connectCmd.String() );
  1672. //vgui::surface()->PlaySound( "ui/ui_findmatch_join_01.wav" );
  1673. }
  1674. else
  1675. {
  1676. Warning( "Failed to reconnect to game server as engine wasn't ready\n" );
  1677. }
  1678. }
  1679. //void CTFGCClientSystem::StartWatchingGame( const CSteamID &gameServerSteamID )
  1680. //{
  1681. // CSteamID steamIDEmpty;
  1682. // StartWatchingGame( gameServerSteamID, steamIDEmpty );
  1683. //}
  1684. //
  1685. //void CTFGCClientSystem::StartWatchingGame( const CSteamID &gameServerSteamID, const CSteamID &watchServerSteamID )
  1686. //{
  1687. // CProtoBufMsg<CMsgWatchGame> msg( k_EMsgGCWatchGame );
  1688. // msg.Body().set_server_steamid( gameServerSteamID.ConvertToUint64() );
  1689. // msg.Body().set_watch_server_steamid( watchServerSteamID.ConvertToUint64() );
  1690. // msg.Body().set_client_version( engine->GetClientVersion() );
  1691. // GCClientSystem()->BSendMessage( msg );
  1692. // Msg( "StartWatchingGame request SteamID: %s, watching SteamID: %s\n", gameServerSteamID.Render(), watchServerSteamID.Render() );
  1693. //}
  1694. //
  1695. //void CTFGCClientSystem::CancelWatchGameRequest()
  1696. //{
  1697. // CProtoBufMsg< CMsgCancelWatchGame > msg( k_EMsgGCCancelWatchGame );
  1698. // GCClientSystem()->BSendMessage( msg );
  1699. //}
  1700. //
  1701. //void CTFGCClientSystem::StartWatchingGameResponse( const CMsgWatchGameResponse &response )
  1702. //{
  1703. // Msg( "Received CMsgWatchGameResponse result %d.\n", response.watch_game_result() );
  1704. //
  1705. // // Tell UI what is going on
  1706. // if ( g_pWatchGameStatus != NULL )
  1707. // {
  1708. // g_pWatchGameStatus->OnWatchGameResult( response.watch_game_result() );
  1709. // }
  1710. //
  1711. // if ( response.watch_game_result() != CMsgWatchGameResponse_WatchGameResult_READY )
  1712. // {
  1713. // return;
  1714. // }
  1715. //
  1716. // if ( tf_auto_create_proxy.GetBool() )
  1717. // {
  1718. // CreateSourceTVProxy( response.source_tv_public_addr(), response.source_tv_private_addr(), response.source_tv_port() );
  1719. // return;
  1720. // }
  1721. //
  1722. // RichPresence()->OnStartedWatchingGame( response.game_server_steamid(), response.watch_server_steamid() );
  1723. //
  1724. // netadr_t serverPublicIPAddr( response.source_tv_public_addr(), response.source_tv_port() );
  1725. // netadr_t serverPrivateIPAddr( response.source_tv_private_addr(), response.source_tv_port() );
  1726. //
  1727. // CUtlString connect;
  1728. //
  1729. // if ( serverPublicIPAddr.GetIP() != serverPrivateIPAddr.GetIP() )
  1730. // {
  1731. // connect.Format( "connect %s %s", serverPublicIPAddr.ToString(), serverPrivateIPAddr.ToString() );
  1732. // }
  1733. // else
  1734. // {
  1735. // connect.Format( "connect %s", serverPublicIPAddr.ToString() );
  1736. // }
  1737. //
  1738. // Msg( "StartWatchingGame: Sending console command: %s\n", connect.String() );
  1739. // engine->ClientCmd_Unrestricted( connect );
  1740. //}
  1741. //
  1742. void CTFGCClientSystem::RequestSelectWizardStep( TF_Matchmaking_WizardStep eWizardStep )
  1743. {
  1744. // We should only be calling this if we're the party leader
  1745. Assert( BIsPartyLeader() );
  1746. if ( BAllowMatchmakingSearch() )
  1747. {
  1748. // Make sure the wizard step makes sense for the search mode we are using
  1749. switch ( GetSearchMode() )
  1750. {
  1751. case TF_Matchmaking_MVM:
  1752. #ifdef USE_MVM_TOUR
  1753. Assert(
  1754. eWizardStep == TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS
  1755. || eWizardStep == TF_Matchmaking_WizardStep_MVM_TOUR_OF_DUTY
  1756. || eWizardStep == TF_Matchmaking_WizardStep_MVM_CHALLENGE
  1757. || eWizardStep == TF_Matchmaking_WizardStep_SEARCHING
  1758. );
  1759. #else // new mm
  1760. Assert(
  1761. eWizardStep == TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS
  1762. || eWizardStep == TF_Matchmaking_WizardStep_MVM_CHALLENGE
  1763. || eWizardStep == TF_Matchmaking_WizardStep_SEARCHING
  1764. );
  1765. #endif // USE_MVM_TOUR
  1766. break;
  1767. case TF_Matchmaking_LADDER:
  1768. Assert(
  1769. eWizardStep == TF_Matchmaking_WizardStep_LADDER
  1770. || eWizardStep == TF_Matchmaking_WizardStep_SEARCHING );
  1771. break;
  1772. case TF_Matchmaking_CASUAL:
  1773. Assert( eWizardStep == TF_Matchmaking_WizardStep_CASUAL
  1774. || eWizardStep == TF_Matchmaking_WizardStep_SEARCHING );
  1775. break;
  1776. default:
  1777. AssertMsg1( false, "Invalid matchmaking mode %d", (int)GetSearchMode() );
  1778. }
  1779. // If we already have a party, or we're asking to start searching, then
  1780. // ask the GC to set our state.
  1781. bool bApplyLocally = false;
  1782. CTFParty* pParty = GetParty();
  1783. if ( ( pParty != NULL ) || ( eWizardStep == TF_Matchmaking_WizardStep_SEARCHING ) )
  1784. {
  1785. SendCreateOrUpdatePartyMsg( eWizardStep );
  1786. bApplyLocally = ( eWizardStep != TF_Matchmaking_WizardStep_SEARCHING ) || ( pParty && pParty->BOffline() );
  1787. }
  1788. else
  1789. {
  1790. // We're just setting local options by ourself right now,
  1791. // nothing exists on the GC. We can apply this change immediately locally.
  1792. bApplyLocally = true;
  1793. }
  1794. // Can we apply this change immediately?
  1795. if ( bApplyLocally )
  1796. {
  1797. m_eLocalWizardStep = eWizardStep;
  1798. FireGameEventPartyUpdated();
  1799. }
  1800. }
  1801. }
  1802. EMatchmakingUIState CTFGCClientSystem::GetMatchmakingUIState()
  1803. {
  1804. // User shutdown?
  1805. if ( !m_bUserWantsToBeInMatchmaking )
  1806. {
  1807. return eMatchmakingUIState_Inactive;
  1808. }
  1809. // Check if we're connected / connecting to any game server
  1810. switch ( m_eConnectState )
  1811. {
  1812. default:
  1813. AssertMsg1( false, "Unknown connect state %d", m_eConnectState );
  1814. case eConnectState_Disconnected: // we should have gotten a beginconnect message first, right?
  1815. break;
  1816. case eConnectState_ConnectingToMatchmade:
  1817. return eMatchmakingUIState_Connecting;
  1818. case eConnectState_ConnectedToMatchmade:
  1819. return eMatchmakingUIState_InGame;
  1820. case eConnectState_NonmatchmadeServer:
  1821. {
  1822. if ( BAllowMatchMakingInGame() )
  1823. break;
  1824. // Eh??? How did we connect to this other server without
  1825. // exiting matchmaking alrady?
  1826. Assert( !"In eConnectState_NonmatchmadeServer state, but m_bUserWantsToBeInMatchmaking=true" );
  1827. EndMatchmaking();
  1828. return eMatchmakingUIState_Inactive;
  1829. }
  1830. }
  1831. // We're not connected to a server.
  1832. // So we should not be in a game right now, unless it's for ranked games
  1833. if ( !BAllowMatchMakingInGame() )
  1834. {
  1835. Assert( !engine->IsInGame() );
  1836. }
  1837. CTFParty *pParty = GetParty();
  1838. CTFGSLobby *pLobby = GetLobby();
  1839. if ( pLobby )
  1840. {
  1841. switch ( pLobby->GetState() )
  1842. {
  1843. case CSOTFGameServerLobby_State_UNKNOWN:
  1844. default:
  1845. AssertMsg1( false, "Unexpected lobby state %d", pLobby->GetState() );
  1846. case CSOTFGameServerLobby_State_RUN:
  1847. case CSOTFGameServerLobby_State_SERVERSETUP:
  1848. return eMatchmakingUIState_Connecting;
  1849. // case CSOTFGameServerLobby_State_NOTREADY:
  1850. // case CSOTFGameServerLobby_State_SERVERASSIGN:
  1851. // return eMatchmakingUIState_InQueue;
  1852. }
  1853. }
  1854. // Are we in a search party?
  1855. if ( pParty )
  1856. {
  1857. if ( pParty->GetState() == CSOTFParty_State_FINDING_MATCH )
  1858. {
  1859. return eMatchmakingUIState_InQueue;
  1860. }
  1861. }
  1862. return eMatchmakingUIState_Chat;
  1863. }
  1864. void CTFGCClientSystem::AssertMakesSenseToReadSearchCriteria()
  1865. {
  1866. // EMatchmakingUIState eState = GetMatchmakingUIState();
  1867. // switch ( eState )
  1868. // {
  1869. // case eMatchmakingUIState_Chat:
  1870. // case eMatchmakingUIState_InQueue:
  1871. // case eMatchmakingUIState_Connecting:
  1872. // // They might need to update the UI during this state
  1873. // break;
  1874. //
  1875. // case eMatchmakingUIState_Inactive:
  1876. // case eMatchmakingUIState_InGame:
  1877. // default:
  1878. // // Why do you want to know?
  1879. // AssertMsg1( false, "Invalid matchmaking UI state %d", eState );
  1880. // break;
  1881. // }
  1882. }
  1883. bool CTFGCClientSystem::BAllowMatchmakingSearch()
  1884. {
  1885. bool bLeavingIncursPenalty = ( GTFGCClientSystem()->GetAssignedMatchAbandonStatus() == k_EAbandonGameStatus_AbandonWithPenalty );
  1886. bool bAllowInGame = ( BAllowMatchMakingInGame() && !bLeavingIncursPenalty );
  1887. EMatchmakingUIState eState = GetMatchmakingUIState();
  1888. switch ( eState )
  1889. {
  1890. case eMatchmakingUIState_Chat:
  1891. case eMatchmakingUIState_InQueue:
  1892. // They might need to update the UI during this state
  1893. return true;
  1894. case eMatchmakingUIState_Inactive:
  1895. case eMatchmakingUIState_Connecting:
  1896. case eMatchmakingUIState_InGame:
  1897. if ( bAllowInGame )
  1898. return true;
  1899. return false;
  1900. default:
  1901. AssertMsg1( false, "Invalid matchmaking UI state %d", eState );
  1902. // Why do you want to know?
  1903. return false;
  1904. }
  1905. }
  1906. TF_MatchmakingMode CTFGCClientSystem::GetSearchMode()
  1907. {
  1908. return m_msgLocalSearchCriteria.matchmaking_mode();
  1909. }
  1910. void CTFGCClientSystem::GetSearchChallenges( CMvMMissionSet &challenges )
  1911. {
  1912. challenges.Clear();
  1913. // O(n^2) goodness...
  1914. for ( int i = 0 ; i < m_msgLocalSearchCriteria.mvm_missions_size() ; ++i )
  1915. {
  1916. int iChallengeIndex = GetItemSchema()->FindMvmMissionByName( m_msgLocalSearchCriteria.mvm_missions( i ).c_str() );
  1917. if ( iChallengeIndex >= 0 )
  1918. challenges.SetMissionBySchemaIndex( iChallengeIndex, true );
  1919. }
  1920. }
  1921. void CTFGCClientSystem::SetSearchChallenges( const CMvMMissionSet &challenges )
  1922. {
  1923. if ( BInternalSetSearchChallenges( challenges ) )
  1924. FireGameEventPartyUpdated();
  1925. }
  1926. bool CTFGCClientSystem::BInternalSetSearchChallenges( const CMvMMissionSet &challenges )
  1927. {
  1928. if ( !BAllowMatchmakingSearch() )
  1929. return false;
  1930. if ( !BIsPartyLeader() )
  1931. {
  1932. AssertMsg( false, "Not party leader" );
  1933. return false;
  1934. }
  1935. // No change?
  1936. CMvMMissionSet currentChallenges;
  1937. GetSearchChallenges( currentChallenges );
  1938. if ( currentChallenges == challenges )
  1939. {
  1940. return false;
  1941. }
  1942. // Apply the change locally
  1943. m_msgLocalSearchCriteria.clear_mvm_missions();
  1944. for ( int i = 0 ; i < GetItemSchema()->GetMvmMissions().Count() ; ++i )
  1945. {
  1946. if ( challenges.GetMissionBySchemaIndex( i ) )
  1947. {
  1948. m_msgLocalSearchCriteria.add_mvm_missions( GetItemSchema()->GetMvmMissionName( i ) );
  1949. }
  1950. }
  1951. if ( m_msgLocalSearchCriteria.mvm_missions_size() == 0 )
  1952. {
  1953. m_msgLocalSearchCriteria.add_mvm_missions( "invalid" );
  1954. }
  1955. // Check if we need to send a message
  1956. if ( GetParty() != NULL )
  1957. {
  1958. CMsgMatchSearchCriteria *pSearchCriteria = GetCreateOrUpdatePartyMsg()->mutable_search_criteria();
  1959. pSearchCriteria->clear_mvm_missions();
  1960. pSearchCriteria->mutable_mvm_missions()->MergeFrom( m_msgLocalSearchCriteria.mvm_missions() );
  1961. }
  1962. // Fire event
  1963. return true;
  1964. }
  1965. bool CTFGCClientSystem::GetSearchJoinLate()
  1966. {
  1967. CTFParty *pParty = GetParty();
  1968. // if ( pParty == NULL || m_msgLocalSearchCriteria.has_late_join_ok() )
  1969. if ( pParty == NULL )
  1970. {
  1971. return m_msgLocalSearchCriteria.late_join_ok();
  1972. }
  1973. return pParty->Obj().search_late_join_ok();
  1974. }
  1975. void CTFGCClientSystem::SetSearchJoinLate( bool bJoinLate )
  1976. {
  1977. if ( !BAllowMatchmakingSearch() )
  1978. return;
  1979. if ( !BIsPartyLeader() )
  1980. {
  1981. AssertMsg( false, "Not party leader" );
  1982. return;
  1983. }
  1984. if ( m_msgLocalSearchCriteria.late_join_ok() != bJoinLate )
  1985. {
  1986. if ( GetParty() == NULL )
  1987. {
  1988. m_msgLocalSearchCriteria.set_late_join_ok( bJoinLate );
  1989. FireGameEventPartyUpdated();
  1990. }
  1991. else
  1992. {
  1993. CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
  1994. pMsg->mutable_search_criteria()->set_late_join_ok( bJoinLate );
  1995. }
  1996. }
  1997. //CheckSendAdjustSearchCriteria();
  1998. }
  1999. EGameCategory CTFGCClientSystem::GetQuickplayGameType()
  2000. {
  2001. return (EGameCategory)m_msgLocalSearchCriteria.quickplay_game_type();
  2002. }
  2003. void CTFGCClientSystem::SetQuickplayGameType( EGameCategory type )
  2004. {
  2005. if ( !BAllowMatchmakingSearch() )
  2006. return;
  2007. if ( !BIsPartyLeader() )
  2008. {
  2009. AssertMsg( false, "Not party leader" );
  2010. return;
  2011. }
  2012. if ( (EGameCategory)m_msgLocalSearchCriteria.quickplay_game_type() != type )
  2013. {
  2014. if ( GetParty() == NULL )
  2015. {
  2016. m_msgLocalSearchCriteria.set_quickplay_game_type( type );
  2017. FireGameEventPartyUpdated();
  2018. }
  2019. else
  2020. {
  2021. CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
  2022. pMsg->mutable_search_criteria()->set_quickplay_game_type( type );
  2023. }
  2024. }
  2025. //CheckSendAdjustSearchCriteria();
  2026. }
  2027. void CTFGCClientSystem::UpdateCustomPingTolerance()
  2028. {
  2029. bool bEnabled = ConVarRef( "tf_custom_ping_enabled" ).GetBool();
  2030. uint32 unValue = bEnabled ? (uint32)Max( 0, ConVarRef( "tf_custom_ping" ).GetInt() ) : 0u;
  2031. // Don't queue unnecessary messages
  2032. if ( m_msgLocalSearchCriteria.custom_ping_tolerance() == unValue )
  2033. { return; }
  2034. if ( GetParty() && BIsPartyLeader() )
  2035. {
  2036. CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
  2037. auto *pCriteria = pMsg->mutable_search_criteria();
  2038. pCriteria->set_custom_ping_tolerance( unValue );
  2039. }
  2040. m_msgLocalSearchCriteria.set_custom_ping_tolerance( unValue );
  2041. }
  2042. void CTFGCClientSystem::SelectCasualMap( uint32 nMapDefIndex, bool bSelected )
  2043. {
  2044. CCasualCriteriaHelper casualHelper( m_msgLocalSearchCriteria.casual_criteria() );
  2045. casualHelper.SetMapSelected( nMapDefIndex, bSelected );
  2046. if ( casualHelper.IsValid() || !casualHelper.AnySelected() )
  2047. {
  2048. if ( GetParty() != NULL )
  2049. {
  2050. CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
  2051. pMsg->mutable_search_criteria()->mutable_casual_criteria()->CopyFrom( casualHelper.GetCasualCriteria() );
  2052. }
  2053. m_msgLocalSearchCriteria.mutable_casual_criteria()->CopyFrom( casualHelper.GetCasualCriteria() );
  2054. FireGameEventPartyUpdated();
  2055. }
  2056. }
  2057. void CTFGCClientSystem::ClearCasualSearchCriteria()
  2058. {
  2059. CCasualCriteriaHelper casualHelper( m_msgLocalSearchCriteria.casual_criteria() );
  2060. if ( casualHelper.AnySelected() )
  2061. {
  2062. casualHelper.Clear();
  2063. if ( GetParty() != NULL )
  2064. {
  2065. CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
  2066. pMsg->mutable_search_criteria()->mutable_casual_criteria()->CopyFrom( casualHelper.GetCasualCriteria() );
  2067. }
  2068. m_msgLocalSearchCriteria.mutable_casual_criteria()->CopyFrom( casualHelper.GetCasualCriteria() );
  2069. FireGameEventPartyUpdated();
  2070. }
  2071. }
  2072. bool CTFGCClientSystem::IsCasualMapSelected( uint32 nMapDefIndex ) const
  2073. {
  2074. CCasualCriteriaHelper casualHelper( m_msgLocalSearchCriteria.casual_criteria() );
  2075. return casualHelper.IsMapSelected( nMapDefIndex );
  2076. }
  2077. bool CTFGCClientSystem::GetLocalPlayerSquadSurplus()
  2078. {
  2079. return m_bLocalSquadSurplus;
  2080. }
  2081. void CTFGCClientSystem::SetLocalPlayerSquadSurplus( bool bSquadSurplus )
  2082. {
  2083. if ( m_bLocalSquadSurplus != bSquadSurplus )
  2084. {
  2085. if ( GetParty() == NULL )
  2086. {
  2087. m_bLocalSquadSurplus = bSquadSurplus;
  2088. FireGameEventPartyUpdated();
  2089. }
  2090. else
  2091. {
  2092. CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
  2093. pMsg->set_squad_surplus( bSquadSurplus );
  2094. }
  2095. }
  2096. //CheckSendAdjustSearchCriteria();
  2097. }
  2098. bool CTFGCClientSystem::BLocalPlayerInventoryHasMvmTicket( void )
  2099. {
  2100. CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
  2101. if ( pLocalInv == NULL )
  2102. return false;
  2103. static CSchemaItemDefHandle pItemDef_MvmTicket( CTFItemSchema::k_rchMvMTicketItemDefName );
  2104. if ( !pItemDef_MvmTicket )
  2105. return false;
  2106. for ( int i = 0 ; i < pLocalInv->GetItemCount() ; ++i )
  2107. {
  2108. CEconItemView *pItem = pLocalInv->GetItem( i );
  2109. Assert( pItem );
  2110. if ( pItem->GetItemDefinition() == pItemDef_MvmTicket )
  2111. return true;
  2112. }
  2113. return false;
  2114. }
  2115. int CTFGCClientSystem::GetLocalPlayerInventoryMvmTicketCount( void )
  2116. {
  2117. int nCount = 0;
  2118. CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
  2119. if ( pLocalInv )
  2120. {
  2121. static CSchemaItemDefHandle pItemDef_MvmTicket( CTFItemSchema::k_rchMvMTicketItemDefName );
  2122. if ( pItemDef_MvmTicket )
  2123. {
  2124. for ( int i = 0 ; i < pLocalInv->GetItemCount() ; ++i )
  2125. {
  2126. CEconItemView *pItem = pLocalInv->GetItem( i );
  2127. Assert( pItem );
  2128. if ( pItem->GetItemDefinition() == pItemDef_MvmTicket )
  2129. {
  2130. nCount++;
  2131. }
  2132. }
  2133. }
  2134. }
  2135. return nCount;
  2136. }
  2137. uint32 CTFGCClientSystem::GetLadderType()
  2138. {
  2139. return m_msgLocalSearchCriteria.has_ladder_game_type() ? m_msgLocalSearchCriteria.ladder_game_type() : k_nMatchGroup_Invalid;
  2140. }
  2141. void CTFGCClientSystem::SetLadderType( uint32 nType )
  2142. {
  2143. if ( !BIsPartyLeader() )
  2144. {
  2145. AssertMsg( false, "Not party leader" );
  2146. return;
  2147. }
  2148. if ( m_msgLocalSearchCriteria.ladder_game_type() != nType )
  2149. {
  2150. if ( !GetParty() )
  2151. {
  2152. m_msgLocalSearchCriteria.set_ladder_game_type( nType );
  2153. FireGameEventPartyUpdated();
  2154. }
  2155. else
  2156. {
  2157. CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
  2158. pMsg->mutable_search_criteria()->set_ladder_game_type( nType );
  2159. }
  2160. }
  2161. }
  2162. bool CTFGCClientSystem::BLocalPlayerInventoryHasSquadSurplusVoucher( void )
  2163. {
  2164. CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
  2165. if ( pLocalInv == NULL )
  2166. return false;
  2167. static CSchemaItemDefHandle k_rchMvMSquadSurplusVoucherItemDefName( CTFItemSchema::k_rchMvMSquadSurplusVoucherItemDefName );
  2168. if ( !k_rchMvMSquadSurplusVoucherItemDefName )
  2169. return false;
  2170. for ( int i = 0 ; i < pLocalInv->GetItemCount() ; ++i )
  2171. {
  2172. CEconItemView *pItem = pLocalInv->GetItem( i );
  2173. Assert( pItem );
  2174. if ( pItem->GetItemDefinition() == k_rchMvMSquadSurplusVoucherItemDefName )
  2175. return true;
  2176. }
  2177. return false;
  2178. }
  2179. int CTFGCClientSystem::GetLocalPlayerInventorySquadSurplusVoucherCount( void )
  2180. {
  2181. int nCount = 0;
  2182. CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
  2183. if ( pLocalInv )
  2184. {
  2185. static CSchemaItemDefHandle k_rchMvMSquadSurplusVoucherItemDefName( CTFItemSchema::k_rchMvMSquadSurplusVoucherItemDefName );
  2186. if ( k_rchMvMSquadSurplusVoucherItemDefName )
  2187. {
  2188. for ( int i = 0 ; i < pLocalInv->GetItemCount() ; ++i )
  2189. {
  2190. CEconItemView *pItem = pLocalInv->GetItem( i );
  2191. Assert( pItem );
  2192. if ( pItem->GetItemDefinition() == k_rchMvMSquadSurplusVoucherItemDefName )
  2193. {
  2194. nCount++;
  2195. }
  2196. }
  2197. }
  2198. }
  2199. return nCount;
  2200. }
  2201. #ifdef USE_MVM_TOUR
  2202. bool CTFGCClientSystem::BGetLocalPlayerBadgeInfoForTour( int iTourIndex, uint32 *pnBadgeLevel, uint32 *pnCompletedChallenges )
  2203. {
  2204. Assert( iTourIndex >= 0 );
  2205. Assert( iTourIndex < GetItemSchema()->GetMvmTours().Count() );
  2206. Assert( pnBadgeLevel );
  2207. Assert( pnCompletedChallenges );
  2208. *pnBadgeLevel = 0;
  2209. *pnCompletedChallenges = 0;
  2210. CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
  2211. if ( pLocalInv == NULL )
  2212. return false;
  2213. // We can't search for a badge without knowing which attribute to look for.
  2214. static CSchemaAttributeDefHandle pAttribDef_MvmChallengeCompleted( CTFItemSchema::k_rchMvMChallengeCompletedMaskAttribName );
  2215. Assert( pAttribDef_MvmChallengeCompleted );
  2216. if ( !pAttribDef_MvmChallengeCompleted )
  2217. return false;
  2218. if ( iTourIndex < 0 || iTourIndex >= GetItemSchema()->GetMvmTours().Count() )
  2219. {
  2220. AssertMsg1( false, "Invalid tour index %d", iTourIndex );
  2221. return false;
  2222. }
  2223. const CEconItemDefinition *pBadgeDef = GetItemSchema()->GetMvmTours()[iTourIndex].m_pBadgeItemDef;
  2224. if ( pBadgeDef == NULL )
  2225. {
  2226. Assert( pBadgeDef );
  2227. return false;
  2228. }
  2229. for ( int i = 0 ; i < pLocalInv->GetItemCount() ; ++i )
  2230. {
  2231. CEconItemView *pBadge = pLocalInv->GetItem( i );
  2232. Assert( pBadge );
  2233. if ( pBadge->GetItemDefinition() != pBadgeDef )
  2234. continue;
  2235. if ( !pBadge->FindAttribute( pAttribDef_MvmChallengeCompleted, pnCompletedChallenges ) )
  2236. {
  2237. AssertMsg( false, "Badge missing challenges completed attribute?" );
  2238. *pnCompletedChallenges = 0;
  2239. }
  2240. extern uint32 GetItemDescriptionDisplayLevel( const IEconItemInterface *pEconItem );
  2241. *pnBadgeLevel = GetItemDescriptionDisplayLevel( pBadge );
  2242. return true;
  2243. }
  2244. return false;
  2245. }
  2246. int CTFGCClientSystem::GetSearchMannUpTourIndex()
  2247. {
  2248. CTFParty *pParty = GetParty();
  2249. // if ( pParty == NULL || m_msgLocalSearchCriteria.has_late_join_ok() )
  2250. if ( pParty == NULL )
  2251. {
  2252. if ( !m_msgLocalSearchCriteria.play_for_bragging_rights() )
  2253. {
  2254. m_msgLocalSearchCriteria.clear_mvm_mannup_tour();
  2255. return k_iMvmTourIndex_NotMannedUp;
  2256. }
  2257. return GetItemSchema()->FindMvmTourByName( m_msgLocalSearchCriteria.mvm_mannup_tour().c_str() );
  2258. }
  2259. return pParty->GetSearchMannUpTourIndex();
  2260. }
  2261. void CTFGCClientSystem::SetSearchMannUpTourIndex( int idxTour )
  2262. {
  2263. Assert( GetSearchPlayForBraggingRights() );
  2264. if ( BInternalSetSearchMannUpTourIndex( idxTour ) )
  2265. FireGameEventPartyUpdated();
  2266. }
  2267. bool CTFGCClientSystem::BInternalSetSearchMannUpTourIndex( int idxTour )
  2268. {
  2269. if ( !BAllowMatchmakingSearch() )
  2270. return false;
  2271. if ( !BIsPartyLeader() )
  2272. {
  2273. AssertMsg( false, "Not party leader" );
  2274. return false;
  2275. }
  2276. // No change?
  2277. if ( GetSearchMannUpTourIndex() == idxTour )
  2278. return false;
  2279. const char *pszTourName = "";
  2280. if ( idxTour >= 0 )
  2281. {
  2282. pszTourName = GetItemSchema()->GetMvmTours()[ idxTour ].m_sTourInternalName.Get();
  2283. }
  2284. else
  2285. {
  2286. Assert( idxTour == k_iMvmTourIndex_Empty );
  2287. }
  2288. bool bResult = false;
  2289. if ( GetParty() == NULL )
  2290. {
  2291. m_msgLocalSearchCriteria.set_mvm_mannup_tour( pszTourName );
  2292. bResult = true;
  2293. }
  2294. else
  2295. {
  2296. CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
  2297. pMsg->mutable_search_criteria()->set_mvm_mannup_tour( pszTourName );
  2298. }
  2299. // Check if we need to deselect inappropriate challenges
  2300. if ( idxTour >= 0 )
  2301. {
  2302. CMvMMissionSet challenges;
  2303. GetSearchChallenges( challenges );
  2304. bool bChanged = false;
  2305. for ( int i = 0 ; i < GetItemSchema()->GetMvmMissions().Count() ; ++i )
  2306. {
  2307. if ( GetItemSchema()->FindMvmMissionInTour( idxTour, i ) < 0 )
  2308. {
  2309. if ( challenges.GetMissionBySchemaIndex( i ) )
  2310. {
  2311. challenges.SetMissionBySchemaIndex( i, false );
  2312. bChanged = true;
  2313. }
  2314. }
  2315. }
  2316. if ( bChanged )
  2317. {
  2318. if ( BInternalSetSearchChallenges( challenges ) )
  2319. bResult = true;
  2320. }
  2321. }
  2322. return bResult;
  2323. }
  2324. #endif // USE_MVM_TOUR
  2325. bool CTFGCClientSystem::GetSearchPlayForBraggingRights()
  2326. {
  2327. CTFParty *pParty = GetParty();
  2328. // if ( pParty == NULL || m_msgLocalSearchCriteria.has_late_join_ok() )
  2329. if ( pParty == NULL )
  2330. {
  2331. return m_msgLocalSearchCriteria.play_for_bragging_rights();
  2332. }
  2333. return pParty->GetSearchPlayForBraggingRights();
  2334. }
  2335. void CTFGCClientSystem::SetSearchPlayForBraggingRights( bool bPlayForBraggingRights )
  2336. {
  2337. if ( !BAllowMatchmakingSearch() )
  2338. return;
  2339. if ( !BIsPartyLeader() )
  2340. {
  2341. AssertMsg( false, "Not party leader" );
  2342. return;
  2343. }
  2344. // Do we need to fire local event?
  2345. bool bFirePartyUpdated = false;
  2346. // Any change?
  2347. #ifdef USE_MVM_TOUR
  2348. if ( GetSearchPlayForBraggingRights() != bPlayForBraggingRights )
  2349. {
  2350. if ( GetParty() == NULL )
  2351. {
  2352. if ( m_msgLocalSearchCriteria.play_for_bragging_rights() != bPlayForBraggingRights )
  2353. {
  2354. m_msgLocalSearchCriteria.set_play_for_bragging_rights( bPlayForBraggingRights );
  2355. bFirePartyUpdated = true;
  2356. }
  2357. }
  2358. else
  2359. {
  2360. CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
  2361. pMsg->mutable_search_criteria()->set_play_for_bragging_rights( bPlayForBraggingRights );
  2362. }
  2363. // Clear tour selection when first entering mann up
  2364. if ( bPlayForBraggingRights )
  2365. {
  2366. if ( BInternalSetSearchMannUpTourIndex( k_iMvmTourIndex_Empty ) )
  2367. bFirePartyUpdated = true;
  2368. }
  2369. }
  2370. // Check if we must deselect the non-Mann-UP challenges
  2371. if ( !bPlayForBraggingRights )
  2372. {
  2373. m_msgLocalSearchCriteria.clear_mvm_mannup_tour();
  2374. }
  2375. #else // new mm
  2376. if ( GetSearchPlayForBraggingRights() != bPlayForBraggingRights )
  2377. {
  2378. if ( GetParty() == NULL )
  2379. {
  2380. if ( m_msgLocalSearchCriteria.play_for_bragging_rights() != bPlayForBraggingRights )
  2381. {
  2382. m_msgLocalSearchCriteria.set_play_for_bragging_rights( bPlayForBraggingRights );
  2383. }
  2384. }
  2385. else
  2386. {
  2387. CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
  2388. pMsg->mutable_search_criteria()->set_play_for_bragging_rights( bPlayForBraggingRights );
  2389. }
  2390. bFirePartyUpdated = true;
  2391. }
  2392. #endif // USE_MVM_TOUR
  2393. if ( bFirePartyUpdated )
  2394. FireGameEventPartyUpdated();
  2395. }
  2396. //void CTFGCClientSystem::CheckSendAdjustSearchCriteria()
  2397. //{
  2398. // if ( !BMakesSenseToWriteSearchCriteria() )
  2399. // {
  2400. // AssertMsg1( false, "Invalid matchmaking UI state %d", GetMatchmakingUIState() );
  2401. // return;
  2402. // }
  2403. //
  2404. // // We only need to do this if we have a party!
  2405. // if ( GetParty() == NULL )
  2406. // {
  2407. // return;
  2408. // }
  2409. //
  2410. // if ( m_msgLocalSearchCriteria.has_map() ||
  2411. // m_msgLocalSearchCriteria.has_challenge() ||
  2412. // m_msgLocalSearchCriteria.has_late_join_ok() ||
  2413. // m_msgLocalSearchCriteria.has_matchgroups() )
  2414. // {
  2415. // }
  2416. //}
  2417. void CTFGCClientSystem::SendCreateOrUpdatePartyMsg( TF_Matchmaking_WizardStep eWizardStep )
  2418. {
  2419. CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
  2420. pMsg->set_wizard_step( eWizardStep );
  2421. // If we don't have a party yet, populate message with our search criteria
  2422. CTFParty *pParty = GetParty();
  2423. if ( pParty == NULL )
  2424. {
  2425. *pMsg->mutable_search_criteria() = m_msgLocalSearchCriteria;
  2426. pMsg->set_squad_surplus( m_bLocalSquadSurplus );
  2427. }
  2428. // Send the steam lobby, if we have one
  2429. if ( m_steamIDLobby.IsValid() )
  2430. {
  2431. if ( pParty == NULL || pParty->GetSteamLobbyID() != m_steamIDLobby )
  2432. {
  2433. pMsg->set_steam_lobby_id( m_steamIDLobby.ConvertToUint64() );
  2434. }
  2435. }
  2436. pMsg->set_wizard_step( eWizardStep );
  2437. // This is important! Send it now, even if we have a party.
  2438. m_flSendPartyUpdateMessageTime = 0.f;
  2439. // static ConVarRef sv_search_key("sv_search_key");
  2440. // if ( sv_search_key.IsValid() && *sv_search_key.GetString() )
  2441. // {
  2442. // msg.Body().set_key( sv_search_key.GetString() );
  2443. // }
  2444. // static ConVarRef dota_matchgroups("dota_matchgroups");
  2445. // if ( dota_matchgroups.IsValid() )
  2446. // {
  2447. // // abort if no matchgroups set
  2448. // if ( dota_matchgroups.GetInt() == 0 )
  2449. // {
  2450. // DOTA_SF_AddErrorMessage( "#DOTA_Matchmaking_NoRegion_Error" );
  2451. // return;
  2452. // }
  2453. //
  2454. // msg.Body().set_matchgroups( dota_matchgroups.GetInt() );
  2455. // }
  2456. }
  2457. void CTFGCClientSystem::SendExitMatchmaking( bool bExplicitAbandon )
  2458. {
  2459. Msg( "Sending request to exit matchmaking system [ abandon = %d ]\n", bExplicitAbandon );
  2460. CProtoBufMsg<CMsgExitMatchmaking> msg( k_EMsgGCExitMatchmaking );
  2461. msg.Body().set_explicit_abandon( bExplicitAbandon );
  2462. msg.Body().set_party_id( GetParty() ? GetParty()->GetGroupID() : 0 );
  2463. msg.Body().set_lobby_id( GetLobby() ? GetLobby()->GetGroupID() : 0 );
  2464. GCClientSystem()->BSendMessage( msg );
  2465. // We're done! No more messages!
  2466. if ( m_pPendingCreateOrUpdatePartyMsg )
  2467. {
  2468. delete m_pPendingCreateOrUpdatePartyMsg;
  2469. m_pPendingCreateOrUpdatePartyMsg = NULL;
  2470. m_flSendPartyUpdateMessageTime = FLT_MAX;
  2471. s_nNumWizardStepChangesWaitingForReply = 0;
  2472. }
  2473. if ( bExplicitAbandon && m_steamIDGCAssignedMatch.IsValid() && !m_bAssignedMatchEnded )
  2474. {
  2475. // Consider this match over on our end, since we're not waiting for the lobby to update (the GC may even be gone)
  2476. GCMatchmakingDebugSpew( 1, "Sending request to exit matchmaking, marking assigned match as ended\n" );
  2477. m_bAssignedMatchEnded = true;
  2478. }
  2479. }
  2480. void CTFGCClientSystem::SaveCasualSearchCriteriaToDisk()
  2481. {
  2482. std::string strOut;
  2483. google::protobuf::TextFormat::PrintToString( m_msgLocalSearchCriteria.casual_criteria(), &strOut );
  2484. CUtlBuffer bufOut;
  2485. bufOut.SetBufferType( true, true );
  2486. bufOut.PutString( strOut.c_str() );
  2487. g_pFullFileSystem->WriteFile( s_pszCasualCriteriaSaveFileName, NULL, bufOut );
  2488. }
  2489. void CTFGCClientSystem::RejoinActiveMatch( void )
  2490. {
  2491. // Dialog already exists, just quit
  2492. if ( s_pRejoinLobbyDialog )
  2493. return;
  2494. if ( enginevgui == NULL || GetClientModeTFNormal()->GameUI() == NULL )
  2495. return;
  2496. // Check if this player is in Abandon territory, if so warn them
  2497. EAbandonGameStatus eAbandonStatus = GTFGCClientSystem()->GetAssignedMatchAbandonStatus();
  2498. const char* pszTitle = "#TF_MM_Rejoin_Title";
  2499. const char* pszBody = NULL;
  2500. const char* pszConfirm = "#TF_MM_Rejoin_Confirm";
  2501. const char* pszCancel = NULL;
  2502. switch ( eAbandonStatus )
  2503. {
  2504. case k_EAbandonGameStatus_Safe:
  2505. pszBody = "#TF_MM_Rejoin_BaseText";
  2506. pszCancel = "#TF_MM_Rejoin_Leave";
  2507. break;
  2508. case k_EAbandonGameStatus_AbandonWithoutPenalty:
  2509. pszBody = "#TF_MM_Rejoin_AbandonText_NoPenalty";
  2510. pszCancel = "#TF_MM_Rejoin_Abandon";
  2511. break;
  2512. case k_EAbandonGameStatus_AbandonWithPenalty:
  2513. pszBody = "#TF_MM_Rejoin_AbandonText";
  2514. pszCancel = "#TF_MM_Rejoin_Abandon";
  2515. break;
  2516. }
  2517. s_pRejoinLobbyDialog = vgui::SETUP_PANEL( new CTFRejoinConfirmDialog(
  2518. pszTitle,
  2519. pszBody,
  2520. pszConfirm,
  2521. pszCancel,
  2522. &OnRejoinMvMLobbyDialogCallBack,
  2523. NULL
  2524. ));
  2525. if ( s_pRejoinLobbyDialog )
  2526. {
  2527. s_pRejoinLobbyDialog->Show();
  2528. // VGUI is being dumb so I need to manually calculate this windows position
  2529. int sW, sT, dW, dT;
  2530. vgui::surface()->GetScreenSize( sW, sT );
  2531. s_pRejoinLobbyDialog->GetSize( dW, dT );
  2532. s_pRejoinLobbyDialog->SetPos( (sW - dW) / 2, (sT - dT) / 2 );
  2533. }
  2534. }
  2535. void CTFGCClientSystem::BeginMatchmaking( TF_MatchmakingMode mode )
  2536. {
  2537. Assert( !m_bUserWantsToBeInMatchmaking );
  2538. m_bUserWantsToBeInMatchmaking = true;
  2539. m_msgMatchmakingProgress.Clear();
  2540. m_nPendingAutoJoinPartyID = 0;
  2541. // Disconnect from any server we're already in
  2542. if ( ( mode != TF_Matchmaking_LADDER ) && ( mode != TF_Matchmaking_CASUAL ) )
  2543. {
  2544. engine->ClientCmd_Unrestricted( "disconnect" );
  2545. }
  2546. TF_Matchmaking_WizardStep eWizardStep = TF_Matchmaking_WizardStep_INVALID;
  2547. switch ( mode )
  2548. {
  2549. case TF_Matchmaking_MVM:
  2550. eWizardStep = TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS;
  2551. break;
  2552. case TF_Matchmaking_LADDER:
  2553. eWizardStep = TF_Matchmaking_WizardStep_LADDER;
  2554. break;
  2555. case TF_Matchmaking_CASUAL:
  2556. eWizardStep = TF_Matchmaking_WizardStep_CASUAL;
  2557. break;
  2558. default:
  2559. AssertMsg1( false, "Unknown wizard step %d\n", (int)mode );
  2560. break;
  2561. }
  2562. // Check if we don't already have a party, then set some default search options
  2563. CTFParty *pParty = GetParty();
  2564. if ( pParty == NULL )
  2565. {
  2566. m_msgLocalSearchCriteria.set_matchmaking_mode( mode );
  2567. m_eLocalWizardStep = eWizardStep;
  2568. // Default late join option
  2569. m_msgLocalSearchCriteria.set_late_join_ok( false );
  2570. // Default Mann Up state based on whether they have a ticket
  2571. SetSearchPlayForBraggingRights( mode == TF_Matchmaking_MVM && BLocalPlayerInventoryHasMvmTicket() );
  2572. FireGameEventPartyUpdated();
  2573. }
  2574. else
  2575. {
  2576. // Hmmm. we already have a party. We really should already be in the correct mode.
  2577. if ( pParty->GetMatchmakingMode() != mode && BIsPartyLeader() )
  2578. {
  2579. CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
  2580. pMsg->mutable_search_criteria()->set_matchmaking_mode( mode );
  2581. pMsg->set_wizard_step( eWizardStep );
  2582. }
  2583. }
  2584. // Post an event so we'll know that we joined the lobby OK
  2585. IGameEvent *pEvent = gameeventmanager->CreateEvent( "mm_lobby_member_join" );
  2586. if ( !pEvent )
  2587. return;
  2588. pEvent->SetString( "steamid", CFmtStr("%llu", steamapicontext->SteamUser()->GetSteamID().ConvertToUint64() ) );
  2589. pEvent->SetInt( "solo", 1 ); // is this always true?
  2590. gameeventmanager->FireEventClientSide( pEvent );
  2591. }
  2592. bool CTFGCClientSystem::BAllowMatchMakingInGame( void ) const
  2593. {
  2594. return !BHaveLiveMatch();
  2595. }
  2596. void CTFGCClientSystem::EndMatchmaking( bool bSendAbandonLobby /* = false */)
  2597. {
  2598. // Set flag, so if GC sends us any further messages, we'll know to ignore them
  2599. m_bUserWantsToBeInMatchmaking = false;
  2600. m_bWantToActivateInviteUI = false;
  2601. m_msgMatchmakingProgress.Clear();
  2602. // If bSendAbandonLobby is false, this will only ask the GC to drop us from our party. If this message races with
  2603. // us finding a match, the GC will decline.
  2604. // ( If that happens, the rejoin game in progress dialog will pop up and resolve the race, so you can't accidentally
  2605. // abandon by canceling queue at the right millisecond )
  2606. SendExitMatchmaking( bSendAbandonLobby );
  2607. if ( BConnectedToMatchServer( true ) )
  2608. {
  2609. // If we were connected to a server we matchmade into, then disconnect
  2610. switch ( m_eConnectState )
  2611. {
  2612. default:
  2613. AssertMsg1( false, "Unknown connect state %d", m_eConnectState );
  2614. case eConnectState_NonmatchmadeServer:
  2615. case eConnectState_Disconnected:
  2616. break;
  2617. case eConnectState_ConnectingToMatchmade:
  2618. case eConnectState_ConnectedToMatchmade:
  2619. Msg( "Disconnecting from matchmade server\n" );
  2620. engine->ClientCmd_Unrestricted( "disconnect" );
  2621. break;
  2622. }
  2623. }
  2624. }
  2625. bool CTFGCClientSystem::BExitMatchmakingAfterDisconnect( void )
  2626. {
  2627. return BConnectedToMatchServer( true );
  2628. }
  2629. void CTFGCClientSystem::LeaveSteamLobby()
  2630. {
  2631. if ( m_steamIDLobby.IsValid() )
  2632. {
  2633. Assert( steamapicontext );
  2634. Assert( m_steamIDLobby.IsLobby() );
  2635. if ( steamapicontext )
  2636. {
  2637. Msg( "Leaving steam lobby %s\n", m_steamIDLobby.Render() );
  2638. steamapicontext->SteamMatchmaking()->LeaveLobby( m_steamIDLobby );
  2639. }
  2640. m_steamIDLobby = CSteamID();
  2641. }
  2642. }
  2643. int CTFGCClientSystem::CheckSteamLobbyCreated()
  2644. {
  2645. if ( !m_bUserWantsToBeInMatchmaking )
  2646. {
  2647. Assert( m_bUserWantsToBeInMatchmaking ); // why are you calling this?
  2648. LeaveSteamLobby();
  2649. return -1;
  2650. }
  2651. // Already in a lobby?
  2652. if ( m_steamIDLobby.IsValid() )
  2653. return 1;
  2654. // Do we have the interfaces we need?
  2655. if ( steamapicontext == NULL || steamapicontext->SteamMatchmaking() == NULL )
  2656. return -1;
  2657. // Is a creation request already in progress?
  2658. if ( m_eCreateLobbyStatus == -1 )
  2659. return 0;
  2660. Msg( "Creating Steam lobby\n" );
  2661. m_eCreateLobbyStatus = -1;
  2662. steamapicontext->SteamMatchmaking()->CreateLobby( k_ELobbyTypePrivate, MAX_PLAYERS );
  2663. return 0;
  2664. }
  2665. void CTFGCClientSystem::RequestActivateInvite()
  2666. {
  2667. // What state are we in?
  2668. switch ( GetMatchmakingUIState() )
  2669. {
  2670. case eMatchmakingUIState_Chat:
  2671. break;
  2672. case eMatchmakingUIState_InQueue:
  2673. Warning( "Leaving matchmaking queue due to request to active friend invite UI\n" );
  2674. break;
  2675. default:
  2676. Warning( "Can only invite friends to party when in the chat state, or the searching state\n" );
  2677. m_bWantToActivateInviteUI = false;
  2678. return;
  2679. }
  2680. // Set flag. We'll try to activate the UI at he earliest opportunity
  2681. m_bWantToActivateInviteUI = true;
  2682. // Create our party I we don't have one, and also
  2683. // get us out of the queue, if we're in it.
  2684. SendCreateOrUpdatePartyMsg( GetWizardStep() );
  2685. // Check if we're ready to activate the UI now
  2686. CheckReadyToActivateInvite();
  2687. }
  2688. //-----------------------------------------------------------------------------
  2689. // Purpose: Ask the GC for the latest global casual criteria stats
  2690. //-----------------------------------------------------------------------------
  2691. void CTFGCClientSystem::RequestMatchMakerStats() const
  2692. {
  2693. CProtoBufMsg<CMsgGCRequestMatchMakerStats> msg( k_EMsgGCRequestMatchMakerStats );
  2694. GCClientSystem()->BSendMessage( msg );
  2695. }
  2696. //-----------------------------------------------------------------------------
  2697. // Purpose: Set our cached global casual criteria stats and figure out the most
  2698. // popular map so we can do some health computations later.
  2699. //-----------------------------------------------------------------------------
  2700. void CTFGCClientSystem::SetMatchMakerStats( const CMsgGCMatchMakerStatsResponse newStats )
  2701. {
  2702. m_MatchMakerStats = newStats;
  2703. // Update m_nMostSearchedMapCount to be the largest in m_CasualCriteriaStats
  2704. m_nMostSearchedMapCount = 0;
  2705. for( int iMap=0; iMap < m_MatchMakerStats.map_count_size(); ++iMap )
  2706. {
  2707. m_nMostSearchedMapCount = Max( m_nMostSearchedMapCount, m_MatchMakerStats.map_count( iMap ) );
  2708. }
  2709. // put data_center_population in dict so we don't have to loop over and strcmp everytime we ask for it
  2710. COMPILE_TIME_ASSERT( ARRAYSIZE( m_dictDataCenterPopulationRatio ) == k_nMatchGroup_Count );
  2711. Assert( m_MatchMakerStats.matchgroup_data_center_population_size() == k_nMatchGroup_Count );
  2712. for ( int iMatchGroup=0; iMatchGroup<k_nMatchGroup_Count; ++iMatchGroup )
  2713. {
  2714. m_dictDataCenterPopulationRatio[ iMatchGroup ].Purge();
  2715. const auto& matchgroup_datacenter_population = m_MatchMakerStats.matchgroup_data_center_population( iMatchGroup );
  2716. for ( int iDataCenter=0; iDataCenter<matchgroup_datacenter_population.data_center_population_size(); ++iDataCenter )
  2717. {
  2718. auto dcp = matchgroup_datacenter_population.data_center_population( iDataCenter );
  2719. m_dictDataCenterPopulationRatio[ iMatchGroup ].Insert( dcp.name().c_str(), dcp.health_ratio() );
  2720. }
  2721. }
  2722. }
  2723. //-----------------------------------------------------------------------------
  2724. // Purpose: Given a health ratio, get the health data
  2725. //-----------------------------------------------------------------------------
  2726. CTFGCClientSystem::MatchMakerHealthData_t CTFGCClientSystem::GetHealthBracketForRatio( float flRatio ) const
  2727. {
  2728. CTFGCClientSystem::MatchMakerHealthData_t data;
  2729. data.m_flRatio = flRatio;
  2730. static const Color colorBad( 128, 128, 128, 60 );
  2731. static const Color colorOK( 188, 112, 0, 128 );
  2732. static const Color colorGood( 94, 150, 49, 255 );
  2733. // Walk through our brackets and fine where we fall and setup data accordingly
  2734. if ( flRatio < 0.3f )
  2735. {
  2736. data.m_colorBar = LerpColor( colorBad, colorOK, RemapValClamped( flRatio, 0.2f, 0.3f, 0.f, 1.f ) );
  2737. data.m_strLocToken = "TF_Casual_QueueEstimation_Bad";
  2738. }
  2739. else if ( flRatio < 0.7f )
  2740. {
  2741. data.m_colorBar = LerpColor( colorOK, colorGood, RemapValClamped( flRatio, 0.3f, 0.7f, 0.f, 1.f ) );
  2742. data.m_strLocToken = "TF_Casual_QueueEstimation_OK";
  2743. }
  2744. else
  2745. {
  2746. data.m_colorBar = colorGood;
  2747. data.m_strLocToken = "TF_Casual_QueueEstimation_Good";
  2748. }
  2749. return data;
  2750. }
  2751. #ifdef STAGING_ONLY
  2752. ConVar tf_fake_casual_map_stats( "tf_fake_casual_map_stats", "0" );
  2753. #endif
  2754. //-----------------------------------------------------------------------------
  2755. // Purpose: Really here just so we can shortcircuit some staging_only debug
  2756. //-----------------------------------------------------------------------------
  2757. inline uint32 GetCountForMap( const CMsgGCMatchMakerStatsResponse msg, int nIndex )
  2758. {
  2759. #ifdef STAGING_ONLY
  2760. // If we're faking, then fake some stats
  2761. if ( tf_fake_casual_map_stats.GetBool() )
  2762. {
  2763. CUniformRandomStream randomStream;
  2764. randomStream.SetSeed( nIndex + tf_fake_casual_map_stats.GetInt() );
  2765. return randomStream.RandomInt( 0, 100000 );
  2766. }
  2767. #endif
  2768. return msg.map_count( nIndex );
  2769. }
  2770. //-----------------------------------------------------------------------------
  2771. // Purpose: Get the overall health of the current local casual criteria.
  2772. // Currently just takes the best individual map health.
  2773. //-----------------------------------------------------------------------------
  2774. CTFGCClientSystem::MatchMakerHealthData_t CTFGCClientSystem::GetOverallHealthDataForLocalCriteria() const
  2775. {
  2776. uint32 nMostSearchedCount = m_nMostSearchedMapCount;
  2777. uint32 nLargestOfSelected = 0;
  2778. CCasualCriteriaHelper helper( m_msgLocalSearchCriteria.casual_criteria() );
  2779. #ifdef STAGING_ONLY
  2780. if ( tf_fake_casual_map_stats.GetBool() )
  2781. {
  2782. nMostSearchedCount = 100000;
  2783. // Force some fake stats if we dont have the baseline message yet
  2784. if ( m_MatchMakerStats.map_count_size() == 0 )
  2785. {
  2786. for( int i=0; i < GetItemSchema()->GetMasterMapsList().Count(); ++i )
  2787. {
  2788. if ( helper.IsMapSelected( i ) )
  2789. {
  2790. nLargestOfSelected = Max( nLargestOfSelected, GetCountForMap( m_MatchMakerStats, i ) );
  2791. }
  2792. }
  2793. }
  2794. }
  2795. #endif
  2796. // No data -- we assume bad
  2797. if ( nMostSearchedCount == 0 )
  2798. return GetHealthBracketForRatio( 0.f );
  2799. // Go through all the locallty selected maps and find the one with the best health.
  2800. // Use that to get our estimated overall criteria health.
  2801. for( int i=0; i < m_MatchMakerStats.map_count_size(); ++i )
  2802. {
  2803. if ( helper.IsMapSelected( i ) )
  2804. {
  2805. nLargestOfSelected = Max( nLargestOfSelected, GetCountForMap( m_MatchMakerStats, i ) );
  2806. }
  2807. }
  2808. return GetHealthBracketForRatio( (float)nLargestOfSelected / (float)nMostSearchedCount );
  2809. }
  2810. //-----------------------------------------------------------------------------
  2811. // Purpose: Gets the health of a given map
  2812. //-----------------------------------------------------------------------------
  2813. CTFGCClientSystem::MatchMakerHealthData_t CTFGCClientSystem::GetHealthDataForMap( uint32 nMapIndex ) const
  2814. {
  2815. uint32 nMostSearchedCount = m_nMostSearchedMapCount;
  2816. uint32 nLargestOfSelected = 0;
  2817. #ifdef STAGING_ONLY
  2818. if ( tf_fake_casual_map_stats.GetBool() )
  2819. {
  2820. nMostSearchedCount = 100000;
  2821. nLargestOfSelected = GetCountForMap( m_MatchMakerStats, nMapIndex );
  2822. }
  2823. #endif
  2824. // No data -- we assume bad
  2825. if ( nMostSearchedCount == 0 )
  2826. return GetHealthBracketForRatio( 0.f );
  2827. if ( (int)nMapIndex < m_MatchMakerStats.map_count_size() )
  2828. {
  2829. nLargestOfSelected = GetCountForMap( m_MatchMakerStats, nMapIndex );
  2830. }
  2831. return GetHealthBracketForRatio( (float)nLargestOfSelected / (float)nMostSearchedCount );
  2832. }
  2833. //CON_COMMAND( tf_resend_so_cache, "Resend SO cache" )
  2834. //{
  2835. // // Force a resend of our SO cache.
  2836. // CProtoBufMsg<CMsgForceSOCacheResend> msg( k_EMsgForceSOCacheResend );
  2837. // GCClientSystem()->BSendMessage( msg );
  2838. //}
  2839. //
  2840. //CON_COMMAND( tf_get_news, "Request game news from the GC" )
  2841. //{
  2842. // if ( args.ArgC() < 2 )
  2843. // return;
  2844. //
  2845. // CGCClientJobGetNews *pJob = new CGCClientJobGetNews( GCClientSystem()->GetGCClient(), atoi( args[1] ) );
  2846. // pJob->StartJob( NULL );
  2847. //}
  2848. //CON_COMMAND( tf_party_test, "Tests sending a party invite" )
  2849. //{
  2850. // CProtoBufMsg<CMsgInviteToParty> msg( k_EMsgGCInviteToParty );
  2851. // msg.Body().set_steam_id( steamapicontext->SteamUser()->GetSteamID().ConvertToUint64() );
  2852. //// msg.Body().set_client_version( engine->GetClientVersion() );
  2853. // GCClientSystem()->BSendMessage( msg );
  2854. //}
  2855. CON_COMMAND( tf_party_debug, "Prints local party objects" )
  2856. {
  2857. GTFGCClientSystem()->DumpParty();
  2858. }
  2859. CON_COMMAND( tf_invite_debug, "Prints local invite objects" )
  2860. {
  2861. GTFGCClientSystem()->DumpInvites();
  2862. }
  2863. CON_COMMAND( tf_lobby_debug, "Prints local lobby objects" )
  2864. {
  2865. GTFGCClientSystem()->DumpLobby();
  2866. }
  2867. //CON_COMMAND( tf_beta_debug, "Prints local dota beta participation object" )
  2868. //{
  2869. // GTFGCClientSystem()->DumpBetaParticipation();
  2870. //}
  2871. //CON_COMMAND( tf_game_account_debug, "Prints game account info" )
  2872. //{
  2873. // GTFGCClientSystem()->DumpGameAccountClient();
  2874. //}
  2875. //class CGCClientJobNestedTest : public GCSDK::CGCClientJob
  2876. //{
  2877. //public:
  2878. // CGCClientJobNestedTest( GCSDK::CGCClient *pGCClient, int nIndex ) : GCSDK::CGCClientJob( pGCClient ), m_nIndex( nIndex ) { }
  2879. //
  2880. // virtual bool BYieldingRunJob( void *pvStartParam )
  2881. // {
  2882. // Msg( "Nested job %d running!\n", m_nIndex );
  2883. // BYieldingWaitOneFrame();
  2884. // Msg( "Nested job %d done running!\n", m_nIndex );
  2885. // return true;
  2886. // }
  2887. // int m_nIndex;
  2888. //};
  2889. ////-----------------------------------------------------------------------------
  2890. //// Purpose: Job for being told when the user GC connection is established
  2891. ////-----------------------------------------------------------------------------
  2892. //class CGCClientJobClientWelcome : public GCSDK::CGCClientJob
  2893. //{
  2894. //public:
  2895. // CGCClientJobClientWelcome( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
  2896. //
  2897. // virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
  2898. // {
  2899. // GCSDK::CProtoBufMsg<CMsgClientWelcome> msg( pNetPacket );
  2900. //
  2901. // // Validate version
  2902. // int engineClientVersion = engine->GetClientVersion();
  2903. // int gcClientVersion = (int)msg.Body().version();
  2904. //
  2905. // // Version checking is enforced if both sides do not report zero as their version
  2906. // if ( engineClientVersion && gcClientVersion && engineClientVersion != gcClientVersion )
  2907. // {
  2908. // IGameEvent *pEvent = gameeventmanager->CreateEvent( "gc_mismatched_version" );
  2909. // if ( pEvent )
  2910. // {
  2911. // gameeventmanager->FireEventClientSide( pEvent );
  2912. // }
  2913. // }
  2914. //
  2915. // g_bClientReceivedGCWelcome = true;
  2916. //
  2917. // if ( GTFGCClientSystem() && gameeventmanager )
  2918. // {
  2919. // // when client has reconnected to Steam, wipe dashboard caches.
  2920. // if ( Dashboard() )
  2921. // {
  2922. // Dashboard()->ClearDashboardCaches();
  2923. // }
  2924. //
  2925. // Msg( "CGCClientJobUserSessionCreated::BYieldingRunJobFromMsg firing event\n" );
  2926. // IGameEvent *pEvent = gameeventmanager->CreateEvent( "gc_user_session_created" );
  2927. // if ( pEvent )
  2928. // {
  2929. // gameeventmanager->FireEventClientSide( pEvent );
  2930. // }
  2931. // }
  2932. // else
  2933. // {
  2934. // Msg( "CGCClientJobUserSessionCreated::BYieldingRunJobFromMsg not firing event\n" );
  2935. // }
  2936. //
  2937. // if ( DOTAChat() )
  2938. // {
  2939. // if ( !DOTAChat()->HasJoinedStartupChannels() )
  2940. // {
  2941. // DOTAChat()->JoinStartupChannels();
  2942. // }
  2943. // else
  2944. // {
  2945. // DOTAChat()->SetRejoinChannels();
  2946. // }
  2947. // }
  2948. // return true;
  2949. // }
  2950. //};
  2951. //GC_REG_JOB( GCSDK::CGCClient, CGCClientJobClientWelcome, "CGCClientJobClientWelcome", k_EMsgGCClientWelcome, k_EServerTypeGCClient );
  2952. ////-----------------------------------------------------------------------------
  2953. //// Purpose: Job for being told when the user's GC session is created
  2954. ////-----------------------------------------------------------------------------
  2955. //class CGCClientInvitationCreated : public GCSDK::CGCClientJob
  2956. //{
  2957. //public:
  2958. // CGCClientInvitationCreated( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
  2959. //
  2960. // virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
  2961. // {
  2962. // GCSDK::CProtoBufMsg<CMsgInvitationCreated> msg( pNetPacket );
  2963. //
  2964. // CUtlString commandline;
  2965. // commandline.Format( "+invite %llu", msg.Body().group_id() );
  2966. // steamapicontext->SteamFriends()->InviteUserToGame( msg.Body().steam_id(), commandline.String() );
  2967. //
  2968. // return true;
  2969. // }
  2970. //};
  2971. //GC_REG_JOB( GCSDK::CGCClient, CGCClientInvitationCreated, "CGCClientInvitationCreated", k_EMsgGCInvitationCreated, k_EServerTypeGCClient );
  2972. class CGCClientMatchmakingProgress : public GCSDK::CGCClientJob
  2973. {
  2974. public:
  2975. CGCClientMatchmakingProgress( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
  2976. virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
  2977. {
  2978. GCSDK::CProtoBufMsg<CMsgMatchmakingProgress> msg( pNetPacket );
  2979. GTFGCClientSystem()->m_msgMatchmakingProgress = msg.Body();
  2980. return true;
  2981. }
  2982. };
  2983. GC_REG_JOB( GCSDK::CGCClient, CGCClientMatchmakingProgress, "CGCClientMatchmakingProgress", k_EMsgGCMatchmakingProgress, k_EServerTypeGCClient );
  2984. class CGCClientMatchMakerStats : public GCSDK::CGCClientJob
  2985. {
  2986. public:
  2987. CGCClientMatchMakerStats( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
  2988. virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
  2989. {
  2990. GCSDK::CProtoBufMsg<CMsgGCMatchMakerStatsResponse> msg( pNetPacket );
  2991. GTFGCClientSystem()->SetMatchMakerStats( msg.Body() );
  2992. IGameEvent *event = gameeventmanager->CreateEvent( "matchmaker_stats_updated" );
  2993. if ( event )
  2994. {
  2995. gameeventmanager->FireEventClientSide( event );
  2996. }
  2997. return true;
  2998. }
  2999. };
  3000. GC_REG_JOB( GCSDK::CGCClient, CGCClientMatchMakerStats, "CGCClientMatchMakerStats", k_EMsgGCMatchMakerStatsResponse, k_EServerTypeGCClient );
  3001. class CGCClientSurveyRequest : public GCSDK::CGCClientJob
  3002. {
  3003. public:
  3004. CGCClientSurveyRequest( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
  3005. virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
  3006. {
  3007. GCSDK::CProtoBufMsg<CMsgGCSurveyRequest> msg( pNetPacket );
  3008. GTFGCClientSystem()->SetSurveyRequest( msg.Body() );
  3009. return true;
  3010. }
  3011. };
  3012. GC_REG_JOB( GCSDK::CGCClient, CGCClientSurveyRequest, "CGCClientSurveyRequest", k_EMsgGC_SurveyQuestionRequest, k_EServerTypeGCClient );
  3013. ////-----------------------------------------------------------------------------
  3014. //class CGCClientJobFindSourceTVGamesDebug : public GCSDK::CGCClientJob
  3015. //{
  3016. //public:
  3017. // CGCClientJobFindSourceTVGamesDebug( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient )
  3018. // {
  3019. //
  3020. // }
  3021. //
  3022. // virtual bool BYieldingRunJob( void *pvStartParam )
  3023. // {
  3024. // CProtoBufMsg<CMsgFindSourceTVGames> msg( k_EMsgGCFindSourceTVGames );
  3025. // CProtoBufMsg<CMsgSourceTVGamesResponse> msgResponse( k_EMsgGCSourceTVGamesResponse );
  3026. //
  3027. // static ConVarRef sv_search_key("sv_search_key");
  3028. // if ( sv_search_key.IsValid() && *sv_search_key.GetString() )
  3029. // {
  3030. // msg.Body().set_search_key( sv_search_key.GetString() );
  3031. // }
  3032. //
  3033. // bool bRet = BYldSendMessageAndGetReply( msg, 15, &msgResponse, k_EMsgGCSourceTVGamesResponse );
  3034. // if ( !bRet )
  3035. // {
  3036. // Warning( "CGCClientJobFindSourceTVGamesDebug failed to get reply\n" );
  3037. // return false;
  3038. // }
  3039. //
  3040. // Msg( "CGCClientJobFindSourceTVGamesDebug: %d\n", msgResponse.Body().games_size() );
  3041. // for ( int i = 0; i < msgResponse.Body().games_size(); i++ )
  3042. // {
  3043. // const CSourceTVGame &game = msgResponse.Body().games(i);
  3044. // Msg( " Game %d:\n", i );
  3045. //
  3046. // CUtlString sGoodPlayers;
  3047. // for ( int p = 0; p < game.good_players_size(); p++ )
  3048. // {
  3049. // if ( sGoodPlayers.Length() > 0 )
  3050. // {
  3051. // sGoodPlayers += ", ";
  3052. // }
  3053. // sGoodPlayers += game.good_players(p).name().c_str();
  3054. // }
  3055. //
  3056. // CUtlString sBadPlayers;
  3057. // for ( int p = 0; p < game.bad_players_size(); p++ )
  3058. // {
  3059. // if ( sBadPlayers.Length() > 0 )
  3060. // {
  3061. // sBadPlayers += ", ";
  3062. // }
  3063. // sBadPlayers += game.bad_players(p).name().c_str();
  3064. // }
  3065. //
  3066. // CUtlString sOtherPlayers;
  3067. // for ( int p = 0; p < game.other_players_size(); p++ )
  3068. // {
  3069. // if ( sOtherPlayers.Length() > 0 )
  3070. // {
  3071. // sOtherPlayers += ", ";
  3072. // }
  3073. // sOtherPlayers += game.other_players(p).name().c_str();
  3074. // }
  3075. //
  3076. // CSteamID steamIDServer( game.server_steamid() );
  3077. // Msg( " SteamID: %s\n Good: %s\n Bad: %s\n Other: %s\n", steamIDServer.Render(), sGoodPlayers.Get(), sBadPlayers.Get(), sOtherPlayers.Get() );
  3078. // }
  3079. //
  3080. // return true;
  3081. // }
  3082. //
  3083. //private:
  3084. //};
  3085. ////-----------------------------------------------------------------------------
  3086. //// Purpose: Receive a broadcast message for notification
  3087. ////-----------------------------------------------------------------------------
  3088. //class CGCClientJobDOTABroadcastNotificationClient : public GCSDK::CGCClientJob
  3089. //{
  3090. //public:
  3091. // CGCClientJobDOTABroadcastNotificationClient( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
  3092. //
  3093. // virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
  3094. // {
  3095. // CProtoBufMsg<CMsgDOTABroadcastNotification> msg( pNetPacket );
  3096. //
  3097. // wchar_t wszText[256];
  3098. // if ( g_pVGuiLocalize->ConvertANSIToUnicode( msg.Body().message().c_str(), wszText, sizeof( wszText ) ) <= 0 )
  3099. // return false;
  3100. //
  3101. // // create a console chat message
  3102. // KeyValues *pChatMsg = new KeyValues( "Command::Game::Chat" );
  3103. // KeyValues::AutoDelete autodelete( pChatMsg );
  3104. // pChatMsg->SetString( "run", "all" );
  3105. // pChatMsg->SetUint64( "xuid", 0 );
  3106. // pChatMsg->SetString( "name", "Console" );
  3107. // pChatMsg->SetWString( "chat", wszText );
  3108. //
  3109. // // make each channel receive the message
  3110. // for ( int i = 0; i < DOTAChat()->GetNumChannels(); ++i )
  3111. // {
  3112. // // receive message immediately
  3113. // DOTAChat()->GetChannel(i)->ReceiveMessage( pChatMsg );
  3114. // }
  3115. //
  3116. // return true;
  3117. // }
  3118. //};
  3119. //GC_REG_JOB( GCSDK::CGCClient, CGCClientJobDOTABroadcastNotificationClient, "CGCClientJobDOTABroadcastNotificationClient", k_EMsgGCBroadcastNotification, k_EServerTypeGCClient );
  3120. //-----------------------------------------------------------------------------
  3121. //CON_COMMAND( tf_find_source_tv_games, "Request game news from the GC" )
  3122. //{
  3123. // CGCClientJobFindSourceTVGamesDebug *pJob = new CGCClientJobFindSourceTVGamesDebug( GCClientSystem()->GetGCClient() );
  3124. // pJob->StartJob( NULL );
  3125. //}
  3126. //CON_COMMAND( tf_set_lobby_details, "Set game/team names" )
  3127. //{
  3128. // if ( args.ArgC() != 6 )
  3129. // {
  3130. // Msg( "Usage: tf_set_lobby_details <game name> <radiant team name> <radiant team logo> <dire team name> <dire team logo>\n" );
  3131. // return;
  3132. // }
  3133. //
  3134. // CTFGSLobby *pLobby = GTFGCClientSystem() ? GTFGCClientSystem()->GetLobby() : NULL;
  3135. // if ( !pLobby )
  3136. // {
  3137. // Msg( "No lobby found.\n" );
  3138. // return;
  3139. // }
  3140. //
  3141. // CProtoBufMsg<CMsgPracticeLobbySetDetails> msg( k_EMsgGCPracticeLobbySetDetails );
  3142. // msg.Body().set_lobby_id( pLobby->GetGroupID() );
  3143. // msg.Body().set_game_name( args[1] );
  3144. // msg.Body().add_team_details(); // radiant
  3145. // msg.Body().add_team_details(); // dire
  3146. // msg.Body().mutable_team_details( DOTA_GC_TEAM_GOOD_GUYS )->set_team_name( args[2] );
  3147. // msg.Body().mutable_team_details( DOTA_GC_TEAM_GOOD_GUYS )->set_team_logo( args[3] );
  3148. // msg.Body().mutable_team_details( DOTA_GC_TEAM_BAD_GUYS )->set_team_name( args[4] );
  3149. // msg.Body().mutable_team_details( DOTA_GC_TEAM_BAD_GUYS )->set_team_logo( args[5] );
  3150. // GCClientSystem()->BSendMessage( msg );
  3151. //}
  3152. //
  3153. //CON_COMMAND( request_today_messages, "Ask the GC for a list of today messages" )
  3154. //{
  3155. // Msg( "Requesting today messages...\n" );
  3156. // CProtoBufMsg<CMsgDOTARequestTodayMessages> msg( k_EMsgGCRequestTodayMessages );
  3157. // GCClientSystem()->BSendMessage( msg );
  3158. //}
  3159. //class CUtlSortVectorTodayMessageGreater
  3160. //{
  3161. //public:
  3162. // bool Less( const CMsgDOTATodayMessages_TodayMessage& lhs, const CMsgDOTATodayMessages_TodayMessage& rhs, void * )
  3163. // {
  3164. // return lhs.date() > rhs.date();
  3165. // }
  3166. //};
  3167. //
  3168. ////-----------------------------------------------------------------------------
  3169. //// Purpose: Receive a list of today messages
  3170. ////-----------------------------------------------------------------------------
  3171. //class CGCClientJobDOTATodayMessages : public GCSDK::CGCClientJob
  3172. //{
  3173. //public:
  3174. // CGCClientJobDOTATodayMessages( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
  3175. //
  3176. // virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
  3177. // {
  3178. // CProtoBufMsg<CMsgDOTATodayMessages> msg( pNetPacket );
  3179. //
  3180. // g_pFullFileSystem->CreateDirHierarchy( "resource/flash3/images/today/", "GAME" );
  3181. //
  3182. // CUtlSortVector< CMsgDOTATodayMessages_TodayMessage, CUtlSortVectorTodayMessageGreater > sortedMessageList;
  3183. //
  3184. // int nMessages = msg.Body().messages_size();
  3185. // for ( int i = 0; i < nMessages; i++ )
  3186. // {
  3187. // sortedMessageList.Insert( msg.Body().messages( i ) );
  3188. // }
  3189. //
  3190. // CMsgDOTATodayMessages sortedMessages;
  3191. // for ( int i = 0; i < sortedMessageList.Count(); i++ )
  3192. // {
  3193. // CMsgDOTATodayMessages_TodayMessage *pNewMessage = sortedMessages.add_messages();
  3194. // *pNewMessage = sortedMessageList[i];
  3195. // }
  3196. // if ( tf_debug_today_message_sorting.GetBool() )
  3197. // {
  3198. // Msg ( "\nUNSORTED ***\n = %s\n", sortedMessages.DebugString().c_str() );
  3199. // Msg ( "\nSORTED ***\n = %s\n", msg.Body().DebugString().c_str() );
  3200. // }
  3201. // GTFGCClientSystem()->SetTodayMessages( &sortedMessages );
  3202. //
  3203. // IGameEvent *pEvent = gameeventmanager->CreateEvent( "today_messages_updated" );
  3204. // if ( pEvent )
  3205. // {
  3206. // pEvent->SetInt( "num_messages", nMessages );
  3207. // gameeventmanager->FireEventClientSide( pEvent );
  3208. // }
  3209. //
  3210. // // make sure we have all the today images downloaded
  3211. // for ( int i = 0; i < nMessages; i++ )
  3212. // {
  3213. // const char *pszImageURL = msg.Body().messages( i ).image_url().c_str();
  3214. // if ( pszImageURL && pszImageURL[0] )
  3215. // {
  3216. // const char *pszFilename = V_UnqualifiedFileName( pszImageURL );
  3217. // if ( pszFilename && pszFilename[0] )
  3218. // {
  3219. // char szBuffer[MAX_PATH];
  3220. // szBuffer[0] = '\0';
  3221. // V_strcat( szBuffer, "resource/flash3/images/today/", sizeof( szBuffer ) );
  3222. // V_strcat( szBuffer, pszFilename, sizeof( szBuffer ) );
  3223. // GTFGCClientSystem()->DownloadFile( pszImageURL, szBuffer );
  3224. // }
  3225. // }
  3226. // }
  3227. //
  3228. // return true;
  3229. // }
  3230. //};
  3231. //GC_REG_JOB( GCSDK::CGCClient, CGCClientJobDOTATodayMessages, "CGCClientJobDOTATodayMessages", k_EMsgGCTodayMessages, k_EServerTypeGCClient );
  3232. //
  3233. ///*
  3234. //CON_COMMAND( reports_remaining, "Request number of reports remaining this week" )
  3235. //{
  3236. // CProtoBufMsg<CMsgDOTAReportsRemainingRequest> msg( k_EMsgGCReportsRemainingRequest );
  3237. // GCClientSystem()->BSendMessage( msg );
  3238. //
  3239. // Msg( "Report submitted\n" );
  3240. //}
  3241. //*/
  3242. //
  3243. ////-----------------------------------------------------------------------------
  3244. //// Purpose: Receive number of reports remaining
  3245. ////-----------------------------------------------------------------------------
  3246. //class CGCClientJobDOTAReportsRemaining : public GCSDK::CGCClientJob
  3247. //{
  3248. //public:
  3249. // CGCClientJobDOTAReportsRemaining( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
  3250. //
  3251. // virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
  3252. // {
  3253. // CProtoBufMsg<CMsgDOTAReportsRemainingResponse> msg( pNetPacket );
  3254. // //Msg( "You have %u positive and %u negative reports remaining\n", msg.Body().num_positive_reports_remaining(), msg.Body().num_negative_reports_remaining() );
  3255. // //Msg( "You have %u positive and %u negative reports total\n", msg.Body().num_positive_reports_total(), msg.Body().num_negative_reports_total() );
  3256. //
  3257. // IGameEvent *pEvent = gameeventmanager->CreateEvent( "player_report_counts_updated" );
  3258. // if ( pEvent )
  3259. // {
  3260. // pEvent->SetInt( "positive_remaining", msg.Body().num_positive_reports_remaining() );
  3261. // pEvent->SetInt( "negative_remaining", msg.Body().num_negative_reports_remaining() );
  3262. // pEvent->SetInt( "positive_total", msg.Body().num_positive_reports_total() );
  3263. // pEvent->SetInt( "negative_total", msg.Body().num_negative_reports_total() );
  3264. // gameeventmanager->FireEventClientSide( pEvent );
  3265. // }
  3266. //
  3267. // return true;
  3268. // }
  3269. //};
  3270. //GC_REG_JOB( GCSDK::CGCClient, CGCClientJobDOTAReportsRemaining, "CGCClientJobDOTAReportsRemaining", k_EMsgGCReportsRemainingResponse, k_EServerTypeGCClient );
  3271. //
  3272. ////-----------------------------------------------------------------------------
  3273. //// Purpose: Handle response to k_EMsgGCWatchGame (k_EMsgGCWatchGameResponse)
  3274. ////-----------------------------------------------------------------------------
  3275. //class CGCClientJobWatchGameResponse : public GCSDK::CGCClientJob
  3276. //{
  3277. //public:
  3278. // CGCClientJobWatchGameResponse( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
  3279. //
  3280. // virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
  3281. // {
  3282. // GCSDK::CProtoBufMsg<CMsgWatchGameResponse> msg( pNetPacket );
  3283. // GTFGCClientSystem()->StartWatchingGameResponse( msg.Body() );
  3284. // return true;
  3285. // }
  3286. //};
  3287. //GC_REG_JOB( GCSDK::CGCClient, CGCClientJobWatchGameResponse, "CGCClientJobWatchGameResponse", k_EMsgGCWatchGameResponse, k_EServerTypeGCClient );
  3288. //
  3289. //
  3290. //#ifdef _WIN32
  3291. //#undef DECLARE_HANDLE
  3292. //#undef INVALID_HANDLE_VALUE
  3293. //#include <windows.h>
  3294. //#include <shellapi.h>
  3295. //#undef GetCurrentDirectory
  3296. //#elif POSIX
  3297. //#include <sys/syscall.h>
  3298. //#include <sys/wait.h>
  3299. //#include <signal.h>
  3300. //#endif
  3301. //
  3302. //void CTFGCClientSystem::CreateSourceTVProxy( uint32 source_tv_public_addr, uint32 source_tv_private_addr, uint32 source_tv_port )
  3303. //{
  3304. // char szExecutablePath[MAX_PATH];
  3305. // if ( g_pFullFileSystem->RelativePathToFullPath( "..", "EXECUTABLE_PATH", szExecutablePath, sizeof( szExecutablePath ) ) )
  3306. // {
  3307. // Q_FixSlashes( szExecutablePath );
  3308. //
  3309. // char szSrcdsProxyBinary[MAX_PATH];
  3310. // Q_snprintf( szSrcdsProxyBinary, sizeof( szSrcdsProxyBinary ), "%s/srcds.exe", szExecutablePath );
  3311. // Q_FixSlashes( szSrcdsProxyBinary );
  3312. //
  3313. // netadr_t serverPublicIPAddr( source_tv_public_addr, source_tv_port );
  3314. // netadr_t serverPrivateIPAddr( source_tv_private_addr, source_tv_port );
  3315. //
  3316. // netadr_t *addr = NULL;
  3317. //
  3318. // if ( serverPublicIPAddr.GetIP() && !serverPublicIPAddr.IsLocalhost() )
  3319. // {
  3320. // addr = &serverPublicIPAddr;
  3321. // }
  3322. // else if ( serverPublicIPAddr.GetIP() != serverPrivateIPAddr.GetIP() && serverPrivateIPAddr.GetIP() && !serverPrivateIPAddr.IsLocalhost() )
  3323. // {
  3324. // addr = &serverPrivateIPAddr;
  3325. // }
  3326. //
  3327. // if (!addr)
  3328. // {
  3329. // Msg("unable to determine address for proxy at public:%s private:%s\n", serverPublicIPAddr.ToString(), serverPrivateIPAddr.ToString() );
  3330. // return;
  3331. // }
  3332. //
  3333. // CUtlString srcds_args;
  3334. //
  3335. // srcds_args.Format( "-console -game dota +tv_relay %s", addr->ToString() );
  3336. //
  3337. //#ifdef _WIN32
  3338. // int nRet = (int) ShellExecute( NULL, "open", szSrcdsProxyBinary, srcds_args.String(), szExecutablePath, 1 /*SW_SHOWNORMAL*/ );
  3339. //
  3340. // if ( nRet > 0 && nRet < 32 )
  3341. // {
  3342. // Warning( "Failed to execute srcds proxy for public:%s private:%s\n", serverPublicIPAddr.ToString(), serverPrivateIPAddr.ToString() );
  3343. // Warning( " szSrcdsProxyBinary = %s\n", szSrcdsProxyBinary );
  3344. // Warning( " szExecutablePath = %s\n", szExecutablePath );
  3345. // Warning( " ShellExecute result = %d\n", nRet );
  3346. // }
  3347. // else
  3348. // {
  3349. // Msg( "Executed srcds proxy: %s args: %s dir:%s\n", szSrcdsProxyBinary, srcds_args.String(), szExecutablePath );
  3350. // }
  3351. //#else
  3352. // /* */
  3353. //#endif
  3354. // }
  3355. //
  3356. //}
  3357. //-----------------------------------------------------------------------------
  3358. // Purpose: Sends an xp acknowledge via k_EMsgGC_AcknowledgeXP to the GC if
  3359. // we have any outstanding xp sources or a mismatch in our last
  3360. // acknowledged xp and our current one.
  3361. //-----------------------------------------------------------------------------
  3362. void CTFGCClientSystem::AcknowledgePendingXPSources( EMatchGroup eMatchGroup ) const
  3363. {
  3364. if ( !m_pSOCache )
  3365. return;
  3366. const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( eMatchGroup );
  3367. if ( !pMatchDesc )
  3368. return;
  3369. bool bHasProgressToAcknowledge = false;
  3370. // Check if we have any xp notifications to acknowledge
  3371. CSharedObjectTypeCache *pXPTypeCache = m_pSOCache->FindBaseTypeCache( CXPSource::k_nTypeID );
  3372. if ( pXPTypeCache && pXPTypeCache->GetCount() > 0 )
  3373. {
  3374. bHasProgressToAcknowledge = true;
  3375. }
  3376. if ( !steamapicontext || !steamapicontext->SteamUser() )
  3377. return;
  3378. // Check if the last acknowledged and the current xp are different.
  3379. auto nLastAckd = pMatchDesc->m_pProgressionDesc->GetLocalPlayerLastAckdExperience();
  3380. auto nCurrent = pMatchDesc->m_pProgressionDesc->GetPlayerExperienceBySteamID( steamapicontext->SteamUser()->GetSteamID() );
  3381. if ( nLastAckd != nCurrent )
  3382. {
  3383. bHasProgressToAcknowledge = true;
  3384. }
  3385. if ( bHasProgressToAcknowledge )
  3386. {
  3387. // Send a message acknowledging the XP
  3388. CProtoBufMsg<CMsgAcknowledgeXP> msg( k_EMsgGC_AcknowledgeXP );
  3389. msg.Body().set_match_group( eMatchGroup );
  3390. GCClientSystem()->BSendMessage( msg );
  3391. }
  3392. }
  3393. void CTFGCClientSystem::AcknowledgeNotification( uint32 nAccountID, uint64 ulNotificationID ) const
  3394. {
  3395. if ( !m_pSOCache )
  3396. return;
  3397. CSharedObjectTypeCache *pTypeCache = m_pSOCache->FindBaseTypeCache( CTFNotification::k_nTypeID );
  3398. if ( pTypeCache && pTypeCache->GetCount() > 0 )
  3399. {
  3400. // Send a message acknowledging our notifications
  3401. ReliableMsgNotificationAcknowledge *pReliable = new ReliableMsgNotificationAcknowledge;
  3402. auto &msg = pReliable->Msg().Body();
  3403. msg.set_account_id( nAccountID );
  3404. msg.set_notification_id( ulNotificationID );
  3405. pReliable->Enqueue();
  3406. }
  3407. }
  3408. //-----------------------------------------------------------------------------
  3409. // Purpose: Hang onto the survey request
  3410. //-----------------------------------------------------------------------------
  3411. void CTFGCClientSystem::SetSurveyRequest( const CMsgGCSurveyRequest& msgSurveyRequest )
  3412. {
  3413. m_msgSurveyRequest = msgSurveyRequest;
  3414. }
  3415. //-----------------------------------------------------------------------------
  3416. // Purpose: Send survey response and clear the stored survey request
  3417. //-----------------------------------------------------------------------------
  3418. void CTFGCClientSystem::SendSurveyResponse( int32 nResponse )
  3419. {
  3420. Assert( m_msgSurveyRequest.has_question_type() );
  3421. GCSDK::CProtoBufMsg< CMsgGCSurveyResponse > msgSurveyResponse( k_EMsgGC_SurveyQuestionResponse );
  3422. msgSurveyResponse.Body().set_match_id( m_msgSurveyRequest.match_id() );
  3423. msgSurveyResponse.Body().set_question_type( m_msgSurveyRequest.question_type() );
  3424. msgSurveyResponse.Body().set_response( nResponse );
  3425. if ( this->BSendMessage( msgSurveyResponse ) )
  3426. {
  3427. ClearSurveyRequest();
  3428. }
  3429. }
  3430. void CTFGCClientSystem::ClearSurveyRequest()
  3431. {
  3432. m_msgSurveyRequest.Clear();
  3433. }
  3434. void CTFGCClientSystem::CheckAssociatePartyAndSteamLobby()
  3435. {
  3436. if ( !m_bUserWantsToBeInMatchmaking )
  3437. {
  3438. LeaveSteamLobby();
  3439. return;
  3440. }
  3441. CTFParty *pParty = GetParty();
  3442. if ( pParty == NULL && m_eAcceptInviteStep == eAcceptInviteStep_None )
  3443. {
  3444. // Left party, leave lobby now. We don't do this when we request to leave the party, because the GC may deny to
  3445. // drop us if we raced with a match starting (See SendExitMatchmaking)
  3446. if ( m_steamIDLobby.IsValid() )
  3447. {
  3448. LeaveSteamLobby();
  3449. }
  3450. return;
  3451. }
  3452. if ( steamapicontext == NULL || steamapicontext->SteamUser() == NULL || steamapicontext->SteamMatchmaking() == NULL )
  3453. return; // WAT. How did we create our lobby?
  3454. // Let the leader of the party take charge
  3455. if ( BIsPartyLeader() )
  3456. {
  3457. // Make sure we have a Steam lobby. If we don't, initiate creation of one.
  3458. int r = CheckSteamLobbyCreated();
  3459. if ( r <= 0 )
  3460. return; // in progress, or failed
  3461. Assert( m_steamIDLobby.IsValid() );
  3462. // Do we need to update the party, linking it to the steam lobby?
  3463. if ( pParty->GetSteamLobbyID() != m_steamIDLobby )
  3464. {
  3465. Msg( "Sending GCUpdateParty to associate party %016llX with steam lobby %s\n", pParty->GetGroupID(), m_steamIDLobby.Render() );
  3466. CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
  3467. pMsg->set_steam_lobby_id( m_steamIDLobby.ConvertToUint64() );
  3468. }
  3469. // Do we need to update the steam lobby, linking it to the party?
  3470. uint64 nCurrentPartyID = 0;
  3471. const char *pszData = steamapicontext->SteamMatchmaking()->GetLobbyData( m_steamIDLobby, k_pszSteamLobbyKey_PartyID );
  3472. sscanf( pszData, "%llX", &nCurrentPartyID );
  3473. if ( nCurrentPartyID != pParty->GetGroupID() )
  3474. {
  3475. Msg( "Setting lobby %s data to associate it with party %016llX\n", m_steamIDLobby.Render(), pParty->GetGroupID() );
  3476. char rchValue[64];
  3477. V_sprintf_safe( rchValue, "%016llX", pParty->GetGroupID() );
  3478. steamapicontext->SteamMatchmaking()->SetLobbyData( m_steamIDLobby, k_pszSteamLobbyKey_PartyID, rchValue );
  3479. }
  3480. }
  3481. else
  3482. {
  3483. // Check if we're not in the lobby associated with this party.
  3484. if ( pParty->GetSteamLobbyID() != m_steamIDLobby )
  3485. {
  3486. // If we're already in a party, get out of it
  3487. if ( m_steamIDLobby.IsValid() )
  3488. {
  3489. Warning( "Leaving steam lobby %s, the party is associated with lobby %s\n", m_steamIDLobby.Render(), pParty->GetSteamLobbyID().Render() );
  3490. LeaveSteamLobby();
  3491. }
  3492. // If the party has a lobby, then join it
  3493. if ( pParty->GetSteamLobbyID().IsValid() )
  3494. {
  3495. // OK, start joining the lobby.
  3496. m_steamIDLobby = pParty->GetSteamLobbyID();
  3497. Msg( "Joining lobby %s\n", m_steamIDLobby.Render() );
  3498. steamapicontext->SteamMatchmaking()->JoinLobby( m_steamIDLobby );
  3499. }
  3500. }
  3501. }
  3502. }
  3503. void CTFGCClientSystem::OnSteamLobbyCreated( LobbyCreated_t *pInfo )
  3504. {
  3505. Assert( !m_steamIDLobby.IsValid() );
  3506. m_eCreateLobbyStatus = pInfo->m_eResult;
  3507. if ( m_eCreateLobbyStatus == k_EResultOK )
  3508. {
  3509. m_steamIDLobby.SetFromUint64( pInfo->m_ulSteamIDLobby );
  3510. Msg( "Steam lobby %s created OK\n", m_steamIDLobby.Render() );
  3511. // If they already bailed, then destroy the newly created lobby
  3512. if ( !m_bUserWantsToBeInMatchmaking )
  3513. {
  3514. LeaveSteamLobby();
  3515. m_eCreateLobbyStatus = k_EResultFail;
  3516. return;
  3517. }
  3518. // Check if we should let the GC know what lobby to associate
  3519. // with our party
  3520. CheckAssociatePartyAndSteamLobby();
  3521. // Check if now we have everything we need to active the UI
  3522. CheckReadyToActivateInvite();
  3523. }
  3524. else
  3525. {
  3526. Warning( "FAILED to create steam lobby, error code %d\n", m_eCreateLobbyStatus );
  3527. }
  3528. }
  3529. void CTFGCClientSystem::OnSteamGameLobbyJoinRequested( GameLobbyJoinRequested_t *pInfo )
  3530. {
  3531. Msg( "OnSteamGameLobbyJoinRequested(%s)\n", pInfo->m_steamIDLobby.Render() );
  3532. // Transform it into a console command and do it!
  3533. char rchCommand[256];
  3534. V_sprintf_safe( rchCommand, "connect_lobby %lld", pInfo->m_steamIDLobby.ConvertToUint64() );
  3535. engine->ClientCmd_Unrestricted( rchCommand );
  3536. }
  3537. void CTFGCClientSystem::AcceptFriendInviteToJoinLobby( const CSteamID &steamIDLobby )
  3538. {
  3539. Msg( "Disconnecting from current server to accept invite\n" );
  3540. // Whatever matchmaking we're doing right right now, get out of it.
  3541. EndMatchmaking();
  3542. // Just queue it to be joined at the earliest opportunity
  3543. Msg( "Ready to join steam lobby at next opportunity\n" );
  3544. m_steamIDLobbyInviteAccepted = steamIDLobby;
  3545. m_eAcceptInviteStep = eAcceptInviteStep_ReadyToJoinSteamLobby;
  3546. }
  3547. void CTFGCClientSystem::OnSteamLobbyEnter( LobbyEnter_t *pInfo )
  3548. {
  3549. CSteamID steamIDLobby( pInfo->m_ulSteamIDLobby );
  3550. // Are we expecting this?
  3551. if ( m_steamIDLobby == steamIDLobby )
  3552. {
  3553. return;
  3554. }
  3555. if ( m_eAcceptInviteStep != eAcceptInviteStep_JoinSteamLobby )
  3556. {
  3557. Assert( m_eAcceptInviteStep == eAcceptInviteStep_None );
  3558. Assert( BIsPartyLeader() );
  3559. return;
  3560. }
  3561. m_eAcceptInviteStep = eAcceptInviteStep_GetLobbyMetadata;
  3562. Assert( !m_steamIDLobby.IsValid() );
  3563. // Make sure it succeeded
  3564. if ( pInfo->m_EChatRoomEnterResponse != k_EChatRoomEnterResponseSuccess )
  3565. {
  3566. Warning(" Failed to join Steam lobby with error code %d. Cannot join party\n", pInfo->m_EChatRoomEnterResponse );
  3567. OnFailedToAcceptInvite();
  3568. return;
  3569. }
  3570. // We're in a lobby. Remember that, in case we need to bail for some other
  3571. // reason.
  3572. m_steamIDLobby.SetFromUint64( pInfo->m_ulSteamIDLobby );
  3573. Msg( "OnSteamLobbyEnter(%s)\n", m_steamIDLobby.Render() );
  3574. Assert( m_steamIDLobby.IsLobby() );
  3575. // Request the lobby data
  3576. steamapicontext->SteamMatchmaking()->RequestLobbyData( m_steamIDLobby );
  3577. }
  3578. void CTFGCClientSystem::OnFailedToAcceptInvite()
  3579. {
  3580. m_eAcceptInviteStep = eAcceptInviteStep_None;
  3581. EndMatchmaking();
  3582. // !KLUDGE! Well, this is a bit crappy us showing a dialog box in this part of the code...
  3583. // IGameEvent *pEvent = gameeventmanager->CreateEvent( "mm_accept_invite_fail" );
  3584. // if ( pEvent )
  3585. // {
  3586. // gameeventmanager->FireEventClientSide( pEvent );
  3587. // }
  3588. ShowMessageBox( "#TF_Matchmaking_AcceptInviteFailTitle", "#TF_Matchmaking_AcceptInviteFailMessage", "#TF_OK" );
  3589. }
  3590. void CTFGCClientSystem::AddLocalPlayerSOListener( ISharedObjectListener* pListener, bool bImmediately )
  3591. {
  3592. if ( bImmediately )
  3593. {
  3594. SubscribeToLocalPlayerSOCache( pListener );
  3595. }
  3596. else
  3597. {
  3598. m_vecDelayedLocalPlayerSOListenersToAdd.AddToTail( pListener );
  3599. }
  3600. }
  3601. void CTFGCClientSystem::RemoveLocalPlayerSOListener( ISharedObjectListener* pListener )
  3602. {
  3603. // Remove if it was a delayed add
  3604. auto idx = m_vecDelayedLocalPlayerSOListenersToAdd.Find( pListener );
  3605. if ( idx != m_vecDelayedLocalPlayerSOListenersToAdd.InvalidIndex() )
  3606. {
  3607. m_vecDelayedLocalPlayerSOListenersToAdd.Remove( idx );
  3608. }
  3609. if ( steamapicontext && steamapicontext->SteamUser() )
  3610. {
  3611. CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
  3612. GetGCClient()->RemoveSOCacheListener( steamID, pListener );
  3613. }
  3614. }
  3615. void CTFGCClientSystem::OnSteamLobbyDataUpdate( LobbyDataUpdate_t *pInfo )
  3616. {
  3617. CSteamID steamIDLobby( pInfo->m_ulSteamIDLobby );
  3618. Msg( "OnSteamLobbyDataUpdate(%s)\n", steamIDLobby.Render() );
  3619. // Check if we're in the process of accepting an invite
  3620. if ( steamIDLobby != m_steamIDLobby )
  3621. {
  3622. Assert( steamIDLobby == m_steamIDLobby );
  3623. return;
  3624. }
  3625. if ( m_eAcceptInviteStep != eAcceptInviteStep_GetLobbyMetadata )
  3626. {
  3627. return;
  3628. }
  3629. m_eAcceptInviteStep = eAcceptInviteStep_JoinParty;
  3630. // Fetch the party ID
  3631. uint64 nPartyToJoin = 0;
  3632. const char *pszData = steamapicontext->SteamMatchmaking()->GetLobbyData( m_steamIDLobby, k_pszSteamLobbyKey_PartyID );
  3633. sscanf( pszData, "%llX", &nPartyToJoin );
  3634. if ( nPartyToJoin == 0 )
  3635. {
  3636. Warning(" No %s metadata set in steam lobby, cannot join party\n", k_pszSteamLobbyKey_PartyID );
  3637. OnFailedToAcceptInvite();
  3638. return;
  3639. }
  3640. Msg( "Requesting GC add us to party %016llX\n", nPartyToJoin );
  3641. // Join the party.
  3642. CProtoBufMsg<CMsgAcceptInvite> msgAcceptInvite( k_EMsgGCAcceptInvite );
  3643. msgAcceptInvite.Body().set_party_id( nPartyToJoin );
  3644. msgAcceptInvite.Body().set_steamid_lobby( pInfo->m_ulSteamIDLobby );
  3645. msgAcceptInvite.Body().set_client_version( engine->GetClientVersion() );
  3646. GCClientSystem()->BSendMessage( msgAcceptInvite );
  3647. }
  3648. class CGCClientAcceptInviteResponse : public GCSDK::CGCClientJob
  3649. {
  3650. public:
  3651. CGCClientAcceptInviteResponse( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
  3652. virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
  3653. {
  3654. GCSDK::CProtoBufMsg<CMsgAcceptInviteResponse> msg( pNetPacket );
  3655. switch ( msg.Body().result_code() )
  3656. {
  3657. case k_EResultOK:
  3658. case k_EResultDuplicateRequest:
  3659. break;
  3660. case k_EResultInvalidProtocolVer:
  3661. GTFGCClientSystem()->EndMatchmaking();
  3662. ShowMessageBox( "#TF_MM_NotCurrentVersionTitle", "#TF_MM_NotCurrentVersionMessage", "#GameUI_OK" );
  3663. break;
  3664. default:
  3665. Warning( "Failed to accept invite, result code %d\n", msg.Body().result_code() );
  3666. GTFGCClientSystem()->OnFailedToAcceptInvite();
  3667. break;
  3668. }
  3669. return true;
  3670. }
  3671. };
  3672. GC_REG_JOB( GCSDK::CGCClient, CGCClientAcceptInviteResponse, "CGCClientAcceptInviteResponse", k_EMsgGCAcceptInviteResponse, k_EServerTypeGCClient );
  3673. void CTFGCClientSystem::OnSteamLobbyChatUpdate( LobbyChatUpdate_t *pInfo )
  3674. {
  3675. if ( m_steamIDLobby.ConvertToUint64() != pInfo->m_ulSteamIDLobby || !m_steamIDLobby.IsValid() )
  3676. return;
  3677. CSteamID steamIDWhoChanged( pInfo->m_ulSteamIDUserChanged );
  3678. if ( !steamIDWhoChanged.IsValid() || steamIDWhoChanged == steamapicontext->SteamUser()->GetSteamID() )
  3679. return;
  3680. if ( BChatMemberStateChangeRemoved( pInfo->m_rgfChatMemberStateChange ) )
  3681. {
  3682. IGameEvent *pEvent = gameeventmanager->CreateEvent( "mm_lobby_member_leave" );
  3683. if ( !pEvent )
  3684. return;
  3685. pEvent->SetString( "steamid", CFmtStr("%llu", steamIDWhoChanged.ConvertToUint64() ) );
  3686. pEvent->SetInt( "flags", pInfo->m_rgfChatMemberStateChange );
  3687. gameeventmanager->FireEventClientSide( pEvent );
  3688. }
  3689. else if ( pInfo->m_rgfChatMemberStateChange & k_EChatMemberStateChangeEntered )
  3690. {
  3691. IGameEvent *pEvent = gameeventmanager->CreateEvent( "mm_lobby_member_join" );
  3692. if ( !pEvent )
  3693. return;
  3694. pEvent->SetString( "steamid", CFmtStr("%llu", steamIDWhoChanged.ConvertToUint64() ) );
  3695. gameeventmanager->FireEventClientSide( pEvent );
  3696. }
  3697. }
  3698. void PostChatGameEvent( const CSteamID &steamIDPoster, CTFGCClientSystem::ELobbyMsgType eType, const char *pszText )
  3699. {
  3700. IGameEvent *pEvent = gameeventmanager->CreateEvent( "mm_lobby_chat" );
  3701. if ( !pEvent )
  3702. return;
  3703. pEvent->SetString( "steamid", CFmtStr("%llu", steamIDPoster.ConvertToUint64() ) );
  3704. pEvent->SetString( "text", pszText );
  3705. pEvent->SetInt( "type", eType );
  3706. gameeventmanager->FireEventClientSide( pEvent );
  3707. }
  3708. void CTFGCClientSystem::OnSteamLobbyChatMsg( LobbyChatMsg_t *pInfo )
  3709. {
  3710. if ( pInfo->m_eChatEntryType != k_EChatEntryTypeChatMsg )
  3711. return;
  3712. CSteamID steamIDPoster;
  3713. EChatEntryType eEntryType;
  3714. int nBufferSize = 2048;
  3715. char *pszText = (char *)calloc( nBufferSize + 4, 1 );
  3716. int nDataSize = steamapicontext->SteamMatchmaking()->GetLobbyChatEntry( pInfo->m_ulSteamIDLobby, pInfo->m_iChatID, &steamIDPoster, pszText, nBufferSize, &eEntryType );
  3717. if ( nDataSize > 0 )
  3718. {
  3719. PostChatGameEvent( steamIDPoster, ELobbyMsgType(pszText[0]), pszText+1 );
  3720. }
  3721. free( pszText );
  3722. }
  3723. void CTFGCClientSystem::SendSteamLobbyChat( ELobbyMsgType eType, const char *pszText )
  3724. {
  3725. if ( steamapicontext == NULL )
  3726. return;
  3727. // If we are going to send it to Steam, let's post the message as a result of the callback
  3728. // on our own message. That gives them some feedback if their message is not being sent.
  3729. if ( m_steamIDLobby.IsValid() )
  3730. {
  3731. int sz = V_strlen(pszText)+2;
  3732. char *temp = new char[sz];
  3733. temp[0] = eType;
  3734. memcpy( temp+1, pszText, sz-1 );
  3735. steamapicontext->SteamMatchmaking()->SendLobbyChatMsg( m_steamIDLobby, temp, sz );
  3736. delete[] temp;
  3737. }
  3738. else
  3739. {
  3740. // Not sending to Steam, just echo locally
  3741. PostChatGameEvent( steamapicontext->SteamUser()->GetSteamID(), eType, pszText );
  3742. }
  3743. }
  3744. void CTFGCClientSystem::CheckReadyToActivateInvite()
  3745. {
  3746. // Don't bother, if we don't have a pending action to popup the UI
  3747. if ( !m_bWantToActivateInviteUI )
  3748. return;
  3749. // What high-level state are we in?
  3750. switch ( GetMatchmakingUIState() )
  3751. {
  3752. case eMatchmakingUIState_Chat:
  3753. case eMatchmakingUIState_InQueue:
  3754. break;
  3755. default:
  3756. Warning( "We missed our opportunity to active the invite UI\n" );
  3757. m_bWantToActivateInviteUI = false;
  3758. return;
  3759. }
  3760. // Make sure we have a Steam lobby. If we don't, initiate creation of one.
  3761. int r = CheckSteamLobbyCreated();
  3762. if ( r == 0 )
  3763. return; // in progress
  3764. // Steam lobby creation finished. No matter what, let's make this the last time we
  3765. // do this polling work.
  3766. m_bWantToActivateInviteUI = false;
  3767. // Do we have a steam lobby?
  3768. if ( r < 0 )
  3769. {
  3770. Warning( "Cannot active invite UI. Failed to create Steam Lobby\n" );
  3771. }
  3772. else
  3773. {
  3774. Assert( m_steamIDLobby.IsValid() );
  3775. // Let's invite some of these muthas
  3776. Msg( "Activating Steam overlay to process invite to lobby %s\n", m_steamIDLobby.Render() );
  3777. steamapicontext->SteamFriends()->ActivateGameOverlayInviteDialog( m_steamIDLobby );
  3778. }
  3779. }
  3780. bool CTFGCClientSystem::BConnectedToMatchServer( bool bLiveMatch )
  3781. {
  3782. // A false bLiveMatch means we don't require the lobby and the match to be currently running.
  3783. // Meaning, you're connected to the match server, but not necessarily in the match (ie. post-match win podium)
  3784. if ( bLiveMatch && !BHaveLiveMatch() )
  3785. {
  3786. return false;
  3787. }
  3788. // Are we in the process of connecting, or connected to, our assigned server?
  3789. if ( m_eConnectState != eConnectState_ConnectedToMatchmade && m_eConnectState != eConnectState_ConnectingToMatchmade )
  3790. {
  3791. return false;
  3792. }
  3793. // If steamIDCurrentServer isn't set yet (it only happens late in the connect) default to assuming it's the right
  3794. // server if it came from matchmaking. We'll find out otherwise when we finish loading.
  3795. if ( !bLiveMatch || !m_steamIDCurrentServer.IsValid() || m_steamIDCurrentServer == m_steamIDGCAssignedMatch )
  3796. {
  3797. return true;
  3798. }
  3799. return false;
  3800. }
  3801. bool CTFGCClientSystem::BHaveLiveMatch() const
  3802. {
  3803. // NOTE That we don't check the lobby -- SOChanged() and Update() together decide to set or clear our assigned
  3804. // match, in a way that considers GC connections being fallible but the gameserver remaining authoritative.
  3805. // See comments there.
  3806. return m_steamIDGCAssignedMatch.IsValid() && !m_bAssignedMatchEnded;
  3807. }
  3808. EAbandonGameStatus CTFGCClientSystem::GetAssignedMatchAbandonStatus()
  3809. {
  3810. // Bootcamp is a magical cannot-trust-the-game-server land that never has abandons.
  3811. //
  3812. // TODO move this to match description as bNonTrustedMM or something.
  3813. if ( m_eAssignedMatchGroup == k_nMatchGroup_MvM_Practice )
  3814. {
  3815. // Can never abandon from bootcamp
  3816. return k_EAbandonGameStatus_Safe;
  3817. }
  3818. if ( BConnectedToMatchServer( true ) )
  3819. {
  3820. // Gameserver is authoritative if we're connected to this point.
  3821. C_TFPlayer *pTFPlayer = C_TFPlayer::GetLocalTFPlayer();
  3822. CTFGameRules *pTFGameRules = TFGameRules();
  3823. if ( pTFPlayer && pTFGameRules )
  3824. {
  3825. bool bLiveMatch = !pTFGameRules->IsManagedMatchEnded();
  3826. bool bPenalty = !pTFPlayer->GetMatchSafeToLeave();
  3827. if ( !bLiveMatch )
  3828. { return k_EAbandonGameStatus_Safe; }
  3829. else if ( bPenalty )
  3830. { return k_EAbandonGameStatus_AbandonWithPenalty; }
  3831. else
  3832. { return k_EAbandonGameStatus_AbandonWithoutPenalty; }
  3833. }
  3834. }
  3835. // Only fall back to looking at the lobby if we're not in the match, or not loaded enough to look at gamerules. The
  3836. // gameserver is the authority on MM matches, so this is just getting that data secondhand through the GC.
  3837. // TODO(JohnS): Right now, if we have a match via the GC, assume we're required to return. Ideally we'd pipe the
  3838. // MatchSafeToLeave flag through the lobby, but right now you'll be told you must return and can find
  3839. // out otherwise once you connect.
  3840. if ( BHaveLiveMatch() )
  3841. { return k_EAbandonGameStatus_AbandonWithPenalty; }
  3842. return k_EAbandonGameStatus_Safe;
  3843. }
  3844. EMatchGroup CTFGCClientSystem::GetLiveMatchGroup() const
  3845. {
  3846. if ( !m_bAssignedMatchEnded )
  3847. return m_eAssignedMatchGroup;
  3848. return k_nMatchGroup_Invalid;
  3849. }
  3850. void CTFGCClientSystem::RejoinLobby( bool bConfirmed )
  3851. {
  3852. // Ask to GC to Rejoin the game
  3853. // For now try to immediately join
  3854. // XXX(JohnS): Ideally we need to craft a BeginMatchmaking() call to put them back into the matchmaking system --
  3855. // right now, after a crash, you will lose your MM state and end up at the main menu after a
  3856. // crash-rejoin. We cannot just set m_bUserWantsToBeInMatchmaking because this skips most of the other
  3857. // BeginMatchmaking() setup like wizard step and results in broken UI
  3858. if ( bConfirmed )
  3859. {
  3860. CTFGSLobby *pLobby = GetLobby();
  3861. if ( pLobby )
  3862. {
  3863. ConnectToServer( pLobby->GetConnect() );
  3864. return;
  3865. }
  3866. // Lobby disappeared
  3867. ShowMessageBox( "#TF_MM_Rejoin_FailedTitle", "#TF_MM_Rejoin_FailedBody", "#GameUI_OK" );
  3868. Log( "Unable to Rejoin existing Lobby game since the Lobby no longer exists." );
  3869. }
  3870. // Canceled or no lobby for rejoin
  3871. EndMatchmaking( BHaveLiveMatch() ? true : false );
  3872. }
  3873. bool CTFGCClientSystem::JoinMMMatch()
  3874. {
  3875. CTFGSLobby *pLobby = GetLobby();
  3876. if ( pLobby )
  3877. {
  3878. ConnectToServer( pLobby->GetConnect() );
  3879. return true;
  3880. }
  3881. return false;
  3882. }
  3883. void CTFGCClientSystem::LeaveGameAndPrepareToJoinParty( GCSDK::PlayerGroupID_t nPartyID )
  3884. {
  3885. // Remember that we are expecting to join a party
  3886. m_nPendingAutoJoinPartyID = nPartyID;
  3887. m_bUserWantsToBeInMatchmaking = false;
  3888. // Clear any other action that might be in progress
  3889. m_bWantToActivateInviteUI = false;
  3890. m_msgMatchmakingProgress.Clear();
  3891. // Leave current server
  3892. engine->ClientCmd_Unrestricted( "disconnect" );
  3893. }
  3894. bool CTFGCClientSystem::BIsPhoneVerified( void )
  3895. {
  3896. CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
  3897. if ( !pLocalInv )
  3898. return false;
  3899. if ( !pLocalInv->GetSOC() )
  3900. return false;
  3901. CEconGameAccountClient *pGameAccountClient = pLocalInv->GetSOC()->GetSingleton< CEconGameAccountClient >();
  3902. return ( pGameAccountClient && pGameAccountClient->Obj().has_phone_verified() && pGameAccountClient->Obj().phone_verified() );
  3903. }
  3904. bool CTFGCClientSystem::BIsPhoneIdentifying( void )
  3905. {
  3906. CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
  3907. if ( !pLocalInv )
  3908. return false;
  3909. if ( !pLocalInv->GetSOC() )
  3910. return false;
  3911. CEconGameAccountClient *pGameAccountClient = pLocalInv->GetSOC()->GetSingleton< CEconGameAccountClient >();
  3912. return ( pGameAccountClient && pGameAccountClient->Obj().has_phone_identifying() && pGameAccountClient->Obj().phone_identifying() );
  3913. }
  3914. bool CTFGCClientSystem::BHasCompetitiveAccess( void )
  3915. {
  3916. CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
  3917. if ( !pLocalInv )
  3918. return false;
  3919. if ( !pLocalInv->GetSOC() )
  3920. return false;
  3921. CEconGameAccountClient *pGameAccountClient = pLocalInv->GetSOC()->GetSingleton< CEconGameAccountClient >();
  3922. return ( pGameAccountClient && pGameAccountClient->Obj().has_competitive_access() && pGameAccountClient->Obj().competitive_access() );
  3923. }
  3924. class CGCClientMvMVictoryInfo : public GCSDK::CGCClientJob
  3925. {
  3926. public:
  3927. CGCClientMvMVictoryInfo( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
  3928. virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
  3929. {
  3930. GCSDK::CProtoBufMsg<CMsgMvMVictoryInfo> msg( pNetPacket );
  3931. CTFHudMannVsMachineStatus *pMannVsMachineStatus = GET_HUDELEMENT( CTFHudMannVsMachineStatus );
  3932. if ( pMannVsMachineStatus )
  3933. {
  3934. pMannVsMachineStatus->MVMVictoryGCResponse( msg.Body() );
  3935. }
  3936. else
  3937. {
  3938. Warning( "Received CMsgMvMVictoryInfo but CTFHudMannVsMachineStatus does not exist \n" );
  3939. }
  3940. return true;
  3941. }
  3942. };
  3943. GC_REG_JOB( GCSDK::CGCClient, CGCClientMvMVictoryInfo, "CGCClientMvMVictoryInfo", k_EMsgGCMvMVictoryInfo, k_EServerTypeGCClient );
  3944. class CGCLeaveGameAndPrepareToJoinPartyJob : public GCSDK::CGCClientJob
  3945. {
  3946. public:
  3947. CGCLeaveGameAndPrepareToJoinPartyJob( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
  3948. virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
  3949. {
  3950. GCSDK::CProtoBufMsg<CMsgLeaveGameAndPrepareToJoinParty> msg( pNetPacket );
  3951. GTFGCClientSystem()->LeaveGameAndPrepareToJoinParty( msg.Body().party_id() );
  3952. return true;
  3953. }
  3954. };
  3955. GC_REG_JOB( GCSDK::CGCClient, CGCLeaveGameAndPrepareToJoinPartyJob, "CGCClientMvMVictoryInfo", k_EMsgGCLeaveGameAndPrepareToJoinParty, k_EServerTypeGCClient );
  3956. //-----------------------------------------------------------------------------
  3957. // Purpose: GC Msg handler to receive the periodic world status message
  3958. //-----------------------------------------------------------------------------
  3959. class CGCWorldStatusBroadcast : public GCSDK::CGCClientJob
  3960. {
  3961. public:
  3962. CGCWorldStatusBroadcast( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  3963. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  3964. {
  3965. GCSDK::CProtoBufMsg<CMsgTFWorldStatus> msg( pNetPacket );
  3966. GTFGCClientSystem()->SetWorldStatus( msg.Body() );
  3967. DevMsg( "TF world status heartbeat.\n Event: %s\n",
  3968. msg.Body().beta_stress_test_event_active() ? "Y" : "N" );
  3969. return true;
  3970. }
  3971. };
  3972. GC_REG_JOB( GCSDK::CGCClient, CGCWorldStatusBroadcast, "CGCWorldStatusBroadcast", k_EMsgGC_WorldStatusBroadcast, GCSDK::k_EServerTypeGCClient );
  3973. #if TF_ANTI_IDLEBOT_VERIFICATION
  3974. static void GenerateClientVerificationMD5ForItemList( MD5Context_t& out_md5Context, const CUtlVector<const CEconItemView *>& vecItems, bool bIsVerbose = false )
  3975. {
  3976. FOR_EACH_VEC( vecItems, i )
  3977. {
  3978. const CEconItemView *pEconItemView = vecItems[i];
  3979. Assert( pEconItemView );
  3980. CEconItemDescription desc;
  3981. desc.SetHashContext( &out_md5Context );
  3982. desc.SetVerbose( bIsVerbose );
  3983. IEconItemDescription::YieldingFillOutEconItemDescription( &desc, GLocalizationProvider(), pEconItemView );
  3984. }
  3985. }
  3986. #ifdef DEBUG
  3987. CON_COMMAND_F( tf_generate_client_verification, "<itemID0> ... <itemIDn>", FCVAR_CLIENTDLL )
  3988. {
  3989. CUtlVector<const CEconItemView *> vecItems;
  3990. for ( int i = 1; i < args.ArgC(); i++ )
  3991. {
  3992. #ifdef POSIX
  3993. const itemid_t unItemId = static_cast<itemid_t >( atoll( args[i] ) );
  3994. #else
  3995. const itemid_t unItemId = static_cast<itemid_t >( _atoi64( args[i] ) );
  3996. #endif // LINUX
  3997. const CEconItemView *pEconItemView = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( unItemId );
  3998. if ( !pEconItemView )
  3999. {
  4000. Msg( "Unable to find item id %llu.\n", unItemId );
  4001. return;
  4002. }
  4003. vecItems.AddToTail( pEconItemView );
  4004. }
  4005. MD5Context_t md5Context;
  4006. MD5Init( &md5Context );
  4007. GenerateClientVerificationMD5ForItemList( md5Context, vecItems );
  4008. }
  4009. #endif // DEBUG
  4010. class CGCClientHelloResponse : public GCSDK::CGCClientJob
  4011. {
  4012. public:
  4013. CGCClientHelloResponse( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
  4014. virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
  4015. {
  4016. GCSDK::CProtoBufMsg<CGCMsgTFHelloResponse> msg( pNetPacket );
  4017. // Don't send a response if we don't have our inventory from Steam yet. The GC will send down another challenge in
  4018. // a few seconds.
  4019. if ( !TFInventoryManager()->GetLocalTFInventory()->RetrievedInventoryFromSteam() )
  4020. return true;
  4021. #ifdef DBEUG
  4022. {
  4023. Msg( "Beginning response to client verification challenge:\n" );
  4024. }
  4025. #endif // DEBUG
  4026. CUtlVector<const CEconItemView *> vecItems;
  4027. for ( int i = 0; i < msg.Body().version_checksum_size(); i++ )
  4028. {
  4029. const itemid_t unItemId = msg.Body().version_checksum(i);
  4030. const CEconItemView *pEconItemView = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( unItemId );
  4031. if ( !pEconItemView )
  4032. {
  4033. // The GC thought we had this item but we don't agree. It should be impossible to mess with the session on the
  4034. // GC to add/remove items from the SO cache while we've got the lock we use to build the challenge, but we might
  4035. // have traded away an item *after* that. For this, we ignore the challenge and hope the GC sends us down another
  4036. // one with our new current inventory.
  4037. return true;
  4038. }
  4039. vecItems.AddToTail( pEconItemView );
  4040. }
  4041. bool bIsVerbose = false;
  4042. if ( msg.Body().has_version_verbose() )
  4043. {
  4044. bIsVerbose = msg.Body().version_verbose();
  4045. }
  4046. MD5Context_t md5Context;
  4047. MD5Init( &md5Context );
  4048. GenerateClientVerificationMD5ForItemList( md5Context, vecItems, bIsVerbose );
  4049. GCSDK::CProtoBufMsg<CGCMsgTFSync> msgResponse( k_EMsgGC_ClientVerificationChallengeResponse );
  4050. MD5Context_t md5ContextEx = md5Context;
  4051. MD5Value_t md5ResultEx;
  4052. MD5Final( &md5ResultEx.bits[0], &md5ContextEx );
  4053. msgResponse.Body().set_version_checksum_ex( &md5ResultEx.bits[0], MD5_DIGEST_LENGTH );
  4054. const unsigned int unRandomSeed = msg.Body().version_check();
  4055. int key;
  4056. if ( (*((bool *)g_pClientPurchaseInterface - 156) ) )
  4057. {
  4058. key = kTFDescriptionHash_TextmodeArbitraryKey;
  4059. }
  4060. else
  4061. {
  4062. int iInstanceCount = engine->GetInstancesRunningCount();
  4063. key = iInstanceCount > 1 ? kTFDescriptionHash_MultiRunArbitraryKey : kTFDescriptionHash_ValidArbitraryKey;
  4064. }
  4065. const unsigned int unMungedRandomSeed = unRandomSeed + key;
  4066. TFDescription_HashDataMunge( &md5Context, unMungedRandomSeed, bIsVerbose, VarArgs( "%d", unMungedRandomSeed ) );
  4067. MD5Value_t md5Result;
  4068. MD5Final( &md5Result.bits[0], &md5Context );
  4069. msgResponse.Body().set_version_checksum( &md5Result.bits[0], MD5_DIGEST_LENGTH );
  4070. // What language are we running in? We need to send this up so the GC will localize with the same strings we're using.
  4071. char uilanguage[ 64 ];
  4072. uilanguage[0] = 0;
  4073. engine->GetUILanguage( uilanguage, sizeof( uilanguage ) );
  4074. msgResponse.Body().set_version_check( PchLanguageToELanguage( uilanguage ) );
  4075. // Send back up the challenge identifier to maintain sync.
  4076. msgResponse.Body().set_version_check_ex( unRandomSeed ^ kTFDescriptionHash_ChallengeXorShenanigans );
  4077. GCClientSystem()->BSendMessage( msgResponse );
  4078. return true;
  4079. }
  4080. };
  4081. GC_REG_JOB( GCSDK::CGCClient, CGCClientHelloResponse, "CGCClientHelloResponse", k_EMsgGC_ClientVerificationChallenge, k_EServerTypeGCClient );
  4082. #endif // TF_ANTI_IDLEBOT_VERIFICATION
  4083. #ifdef TF_GC_PING_DEBUG
  4084. // Ping debug commands for spoofin'
  4085. CON_COMMAND( tf_datacenter_ping_override, "Override the ping data we'll report for a specific datacenter." )
  4086. {
  4087. if ( args.ArgC() != 4 )
  4088. {
  4089. ConMsg( "Usage: tf_datacenter_ping_override <datacenter> <ping> <status>\n" );
  4090. return;
  4091. }
  4092. const char *pszDC = args[1];
  4093. uint32 nPing = (uint32)Clamp( V_atoi( args[2] ), 0, INT_MAX );
  4094. CMsgGCDataCenterPing_Update_Status eStatus = (CMsgGCDataCenterPing_Update_Status)V_atoi( args[3] );
  4095. GTFGCClientSystem()->SetPingOverride( pszDC, nPing, eStatus );
  4096. ConMsg( "Started overriding datacenter \"%s\" to %ums ping with status %i (enum)\n"
  4097. "Forcing a ping refresh to submit new data with this override\n",
  4098. pszDC, nPing, eStatus );
  4099. }
  4100. CON_COMMAND( tf_datacenter_clear_ping_override, "Stop overriding ping data." )
  4101. {
  4102. if ( args.ArgC() != 1 )
  4103. {
  4104. ConMsg( "Usage: tf_datacenter_clear_ping_override\n" );
  4105. return;
  4106. }
  4107. GTFGCClientSystem()->ClearPingOverrides();
  4108. ConMsg( "Stopped overriding any datacenter ping data\n" );
  4109. }
  4110. #endif