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

2992 lines
89 KiB

  1. //========= Copyright � 1996-2009, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=====================================================================================//
  6. #include "mm_framework.h"
  7. #ifndef _X360
  8. #include "xbox/xboxstubs.h"
  9. #endif
  10. #include "smartptr.h"
  11. #include "utlvector.h"
  12. #include "x360_lobbyapi.h"
  13. #include "leaderboards.h"
  14. #include "igameevents.h"
  15. #include "GameUI/IGameUI.h"
  16. #include "filesystem.h"
  17. // NOTE: This has to be the last file included!
  18. #include "tier0/memdbgon.h"
  19. #define STEAM_PACK_BITFIELDS
  20. ConVar cl_names_debug( "cl_names_debug", "0", FCVAR_DEVELOPMENTONLY );
  21. #define PLAYER_DEBUG_NAME "WWWWWWWWWWWWWWW"
  22. #ifndef NO_STEAM
  23. static float s_flSteamStatsRequestTime = 0;
  24. static bool s_bSteamStatsRequestFailed = false;
  25. bool g_bSteamStatsReceived = false;
  26. #endif
  27. static DWORD GetTitleSpecificDataId( int idx )
  28. {
  29. static DWORD arrTSDI[3] = {
  30. XPROFILE_TITLE_SPECIFIC1,
  31. XPROFILE_TITLE_SPECIFIC2,
  32. XPROFILE_TITLE_SPECIFIC3
  33. };
  34. if ( idx >= 0 && idx < ARRAYSIZE( arrTSDI ) )
  35. return arrTSDI[idx];
  36. return DWORD(-1);
  37. }
  38. static int GetTitleSpecificDataIndex( DWORD TSDataId )
  39. {
  40. switch( TSDataId )
  41. {
  42. case XPROFILE_TITLE_SPECIFIC1: return 0;
  43. case XPROFILE_TITLE_SPECIFIC2: return 1;
  44. case XPROFILE_TITLE_SPECIFIC3: return 2;
  45. default: return -1;
  46. }
  47. }
  48. static MM_XWriteOpportunity s_arrXWO[ XUSER_MAX_COUNT ]; // rely on static memory being zero'd
  49. #ifdef _X360
  50. CUtlVector< PlayerLocal::XPendingAsyncAward_t * > PlayerLocal::s_arrPendingAsyncAwards;
  51. #endif
  52. void SignalXWriteOpportunity( MM_XWriteOpportunity eXWO )
  53. {
  54. if ( !eXWO )
  55. {
  56. Warning( "SignalXWriteOpportunity called with MMXWO_NONE!\n" );
  57. return;
  58. }
  59. else
  60. {
  61. Msg( "SignalXWriteOpportunity(%d)\n", eXWO );
  62. }
  63. // In case session has just started we bump every player's last write time
  64. // to the current time since player shouldn't write within the first 5 mins
  65. // from when the session started (TCR)
  66. if ( eXWO == MMXWO_SESSION_STARTED )
  67. {
  68. for ( int k = 0; k < XUSER_MAX_COUNT; ++ k )
  69. {
  70. IPlayerLocal *pLocalPlayer = g_pPlayerManager->GetLocalPlayer( k );
  71. if ( PlayerLocal *pPlayer = dynamic_cast< PlayerLocal * >( pLocalPlayer ) )
  72. {
  73. pPlayer->SetTitleDataWriteTime( Plat_FloatTime() );
  74. }
  75. }
  76. return;
  77. }
  78. for ( int k = 0; k < ARRAYSIZE( s_arrXWO ); ++ k )
  79. {
  80. // Only elevate write opportunity:
  81. // this way any other code can signal CHECKPOINT after SESSION_FINISHED
  82. // and the write will happen as SESSION_FINISHED
  83. if ( s_arrXWO[k] < eXWO )
  84. s_arrXWO[k] = eXWO;
  85. }
  86. }
  87. MM_XWriteOpportunity GetXWriteOpportunity( int iCtrlr )
  88. {
  89. if ( iCtrlr >= 0 && iCtrlr < ARRAYSIZE( s_arrXWO ) )
  90. {
  91. MM_XWriteOpportunity result = s_arrXWO[ iCtrlr ];
  92. s_arrXWO[ iCtrlr ] = MMXWO_NONE; // reset
  93. return result;
  94. }
  95. else
  96. return MMXWO_NONE;
  97. }
  98. static CUtlVector< XUID > s_arrSessionSearchesQueue;
  99. static int s_numSearchesOutstanding = 0;
  100. ConVar mm_player_search_count( "mm_player_search_count", "5", FCVAR_DEVELOPMENTONLY );
  101. void PumpSessionSearchQueue()
  102. {
  103. while ( s_arrSessionSearchesQueue.Count() > 0 &&
  104. s_numSearchesOutstanding < mm_player_search_count.GetInt() )
  105. {
  106. XUID xid = s_arrSessionSearchesQueue[0];
  107. s_arrSessionSearchesQueue.Remove( 0 );
  108. if ( PlayerFriend *player = g_pPlayerManager->FindPlayerFriend( xid ) )
  109. {
  110. player->StartSearchForSessionInfoImpl();
  111. }
  112. }
  113. }
  114. //
  115. // PlayerFriend implementation
  116. //
  117. PlayerFriend::PlayerFriend( XUID xuid, FriendInfo_t const *pFriendInfo /* = NULL */ ) :
  118. m_uFriendMark( 0 ),
  119. m_bIsStale( false ),
  120. m_eSearchState( SEARCH_NONE ),
  121. m_pDetails( NULL ),
  122. m_pPublishedPresence( NULL )
  123. {
  124. memset( m_wszRichPresence, 0, sizeof( m_wszRichPresence ) );
  125. memset( &m_xSessionID, 0, sizeof( m_xSessionID ) );
  126. memset( &m_GameSessionInfo, 0, sizeof( m_GameSessionInfo ) );
  127. m_uiTitleID = 0;
  128. m_uiGameServerIP = 0;
  129. #ifdef _X360
  130. memset( &m_xsiSearchState, 0, sizeof( m_xsiSearchState ) );
  131. m_pQOS_xnaddr = NULL;
  132. m_pQOS_xnkid = NULL;
  133. m_pQOS_xnkey = NULL;
  134. m_XNQOS = NULL;
  135. memset( &m_SessionSearchOverlapped, 0, sizeof( m_SessionSearchOverlapped ) );
  136. #endif
  137. m_xuid = xuid;
  138. m_eOnlineState = STATE_ONLINE;
  139. UpdateFriendInfo( pFriendInfo );
  140. }
  141. wchar_t const * PlayerFriend::GetRichPresence()
  142. {
  143. return m_wszRichPresence;
  144. }
  145. KeyValues * PlayerFriend::GetGameDetails()
  146. {
  147. return m_pDetails;
  148. }
  149. KeyValues * PlayerFriend::GetPublishedPresence()
  150. {
  151. return m_pPublishedPresence;
  152. }
  153. bool PlayerFriend::IsJoinable()
  154. {
  155. if ( m_pPublishedPresence && m_pPublishedPresence->GetString( "connect" )[0] )
  156. return true; // joining via connect string
  157. if ( !( const uint64 & ) m_xSessionID )
  158. return false;
  159. if ( m_pDetails->GetInt( "members/numSlots" ) <= m_pDetails->GetInt( "members/numPlayers" ) )
  160. return false;
  161. if ( *m_pDetails->GetString( "system/lock" ) )
  162. return false;
  163. if ( !Q_stricmp( "private", m_pDetails->GetString( "system/access" ) ) )
  164. return false;
  165. return true; // joining via lobby
  166. }
  167. uint64 PlayerFriend::GetTitleID()
  168. {
  169. return m_uiTitleID;
  170. }
  171. uint32 PlayerFriend::GetGameServerIP()
  172. {
  173. return m_uiGameServerIP;
  174. }
  175. void PlayerFriend::Join()
  176. {
  177. // Requesting to join this player
  178. KeyValues *pSettings = KeyValues::FromString(
  179. "settings",
  180. " system { "
  181. " network LIVE "
  182. " } "
  183. " options { "
  184. " action joinsession "
  185. " } "
  186. );
  187. if ( m_eSearchState == SEARCH_NONE )
  188. {
  189. pSettings->SetString( "system/network", m_pDetails->GetString( "system/network", "LIVE" ) );
  190. }
  191. pSettings->SetUint64( "options/sessionid", ( const uint64 & ) m_xSessionID );
  192. pSettings->SetUint64( "options/friendxuid", m_xuid );
  193. #ifdef _X360
  194. char chSessionInfoBuffer[ XSESSION_INFO_STRING_LENGTH ] = {0};
  195. MMX360_SessionInfoToString( m_GameSessionInfo, chSessionInfoBuffer );
  196. pSettings->SetString( "options/sessioninfo", chSessionInfoBuffer );
  197. #endif
  198. KeyValues::AutoDelete autodelete( pSettings );
  199. g_pMatchFramework->MatchSession( pSettings );
  200. }
  201. void PlayerFriend::Update()
  202. {
  203. if ( !m_xuid )
  204. return;
  205. #ifdef _X360
  206. if( m_eSearchState == SEARCH_XNKID )
  207. {
  208. Live_Update_SearchXNKID();
  209. }
  210. if ( m_eSearchState == SEARCH_QOS )
  211. {
  212. Live_Update_Search_QOS();
  213. }
  214. #endif
  215. if ( m_eSearchState == SEARCH_COMPLETED )
  216. {
  217. m_eSearchState = SEARCH_NONE;
  218. -- s_numSearchesOutstanding;
  219. PumpSessionSearchQueue();
  220. if( V_memcmp( &( m_GameSessionInfo.sessionID ), &m_xSessionID, sizeof( m_xSessionID ) ) != 0)
  221. {
  222. // Re-discover everything again since session ID changed
  223. StartSearchForSessionInfo();
  224. }
  225. // Signal that we are finished with a search
  226. KeyValues *kvEvent = new KeyValues( "OnMatchPlayerMgrUpdate", "update", "friend" );
  227. kvEvent->SetUint64( "xuid", GetXUID() );
  228. g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( kvEvent );
  229. }
  230. }
  231. #ifdef _X360
  232. #ifdef _DEBUG
  233. static ConVar mm_player_delay_xnkid( "mm_player_delay_xnkid", "0", FCVAR_DEVELOPMENTONLY );
  234. static ConVar mm_player_delay_qos( "mm_player_delay_qos", "0", FCVAR_DEVELOPMENTONLY );
  235. static bool ShouldDelayBasedOnTimeThrottling( float &flStaticTimekeeper, float flDelay )
  236. {
  237. if ( flDelay <= 0.0f )
  238. {
  239. flStaticTimekeeper = 0.0f;
  240. return false;
  241. }
  242. else if ( flStaticTimekeeper <= 0.0f )
  243. {
  244. flStaticTimekeeper = Plat_FloatTime();
  245. return true;
  246. }
  247. else if ( flStaticTimekeeper + flDelay < Plat_FloatTime() )
  248. {
  249. flStaticTimekeeper = 0.0f;
  250. return false;
  251. }
  252. else
  253. {
  254. return true;
  255. }
  256. }
  257. static bool ShouldDelayPlayerXnkid()
  258. {
  259. static float s_flTime = 0.0f;
  260. return ShouldDelayBasedOnTimeThrottling( s_flTime, mm_player_delay_xnkid.GetFloat() );
  261. }
  262. static bool ShouldDelayPlayerQos()
  263. {
  264. static float s_flTime = 0.0f;
  265. return ShouldDelayBasedOnTimeThrottling( s_flTime, mm_player_delay_qos.GetFloat() );
  266. }
  267. #else
  268. inline static bool ShouldDelayPlayerXnkid() { return false; }
  269. inline static bool ShouldDelayPlayerQos() { return false; }
  270. #endif
  271. void PlayerFriend::Live_Update_SearchXNKID()
  272. {
  273. if( !XHasOverlappedIoCompleted( & m_SessionSearchOverlapped ) )
  274. return;
  275. if ( ShouldDelayPlayerXnkid() )
  276. return;
  277. DWORD result = 0;
  278. if( XGetOverlappedResult( &m_SessionSearchOverlapped, &result, false ) == ERROR_SUCCESS )
  279. {
  280. //result should be 1
  281. if( GetXSearchResults()->dwSearchResults >= 1)
  282. {
  283. V_memcpy( &m_GameSessionInfo, &( GetXSearchResults()->pResults[0].info ), sizeof( m_GameSessionInfo ) );
  284. }
  285. else
  286. {
  287. memset( &m_xSessionID, 0, sizeof( m_xSessionID ) );
  288. memset( &m_GameSessionInfo, 0, sizeof( m_GameSessionInfo ) );
  289. if ( m_pDetails )
  290. m_pDetails->deleteThis();
  291. m_pDetails = NULL;
  292. }
  293. }
  294. else
  295. {
  296. memset( &m_xSessionID, 0, sizeof( m_xSessionID ) );
  297. memset( &m_GameSessionInfo, 0, sizeof( m_GameSessionInfo ) );
  298. if ( m_pDetails )
  299. m_pDetails->deleteThis();
  300. m_pDetails = NULL;
  301. }
  302. m_eSearchState = SEARCH_COMPLETED;
  303. if ( ( const uint64 & ) m_GameSessionInfo.sessionID )
  304. {
  305. // Issue the QOS query
  306. m_xsiSearchState = m_GameSessionInfo;
  307. m_pQOS_xnaddr = &m_xsiSearchState.hostAddress;
  308. m_pQOS_xnkid = &m_xsiSearchState.sessionID;
  309. m_pQOS_xnkey = &m_xsiSearchState.keyExchangeKey;
  310. int err = g_pMatchExtensions->GetIXOnline()->XNetQosLookup( 1,
  311. &m_pQOS_xnaddr, &m_pQOS_xnkid, &m_pQOS_xnkey,
  312. 0, NULL, NULL, 2, 0, 0, NULL, &m_XNQOS );
  313. if ( err == ERROR_SUCCESS )
  314. m_eSearchState = SEARCH_QOS;
  315. }
  316. }
  317. void PlayerFriend::Live_Update_Search_QOS()
  318. {
  319. if( m_XNQOS->cxnqosPending != 0 )
  320. return;
  321. if ( ShouldDelayPlayerQos() )
  322. return;
  323. if ( m_pDetails )
  324. m_pDetails->deleteThis();
  325. m_pDetails = NULL;
  326. XNQOSINFO *pQOS = &m_XNQOS->axnqosinfo[0];
  327. if( pQOS->bFlags & XNET_XNQOSINFO_COMPLETE &&
  328. pQOS->bFlags & XNET_XNQOSINFO_DATA_RECEIVED &&
  329. pQOS->cbData && pQOS->pbData )
  330. {
  331. MM_GameDetails_QOS_t gd = { pQOS->pbData, pQOS->cbData, pQOS->wRttMedInMsecs };
  332. m_pDetails = g_pMatchFramework->GetMatchNetworkMsgController()->UnpackGameDetailsFromQOS( &gd );
  333. }
  334. g_pMatchExtensions->GetIXOnline()->XNetQosRelease( m_XNQOS );
  335. m_XNQOS = NULL;
  336. if ( m_pDetails )
  337. {
  338. // Set AUX fields like sessioninfo
  339. if ( KeyValues *kvOptions = m_pDetails->FindKey( "options", true ) )
  340. {
  341. kvOptions->SetUint64( "sessionid", ( const uint64 & ) m_xSessionID );
  342. char chSessionInfoBuffer[ XSESSION_INFO_STRING_LENGTH ] = {0};
  343. MMX360_SessionInfoToString( m_GameSessionInfo, chSessionInfoBuffer );
  344. kvOptions->SetString( "sessioninfo", chSessionInfoBuffer );
  345. }
  346. // Set the "player" key
  347. if ( KeyValues *kvPlayer = m_pDetails->FindKey( "player", true ) )
  348. {
  349. kvPlayer->SetUint64( "xuid", GetXUID() );
  350. kvPlayer->SetUint64( "xuidOnline", GetXUID() );
  351. kvPlayer->SetString( "name", GetName() );
  352. kvPlayer->SetWString( "richpresence", GetRichPresence() );
  353. }
  354. }
  355. m_eSearchState = SEARCH_COMPLETED;
  356. }
  357. #elif !defined( NO_STEAM )
  358. void PlayerFriend::Steam_OnLobbyDataUpdate( LobbyDataUpdate_t *pParam )
  359. {
  360. // Only callbacks about lobby itself
  361. if ( pParam->m_ulSteamIDLobby != pParam->m_ulSteamIDMember )
  362. return;
  363. // Listening for only callbacks related to current player
  364. if ( pParam->m_ulSteamIDLobby != ( const uint64 & ) m_xSessionID )
  365. return;
  366. // Unregister the callback
  367. m_CallbackOnLobbyDataUpdate.Unregister();
  368. // Set session info
  369. memset( &m_GameSessionInfo, 0, sizeof( m_GameSessionInfo ) );
  370. m_GameSessionInfo.sessionID = m_xSessionID;
  371. // Describe the lobby
  372. if ( m_pDetails )
  373. m_pDetails->deleteThis();
  374. m_pDetails = NULL;
  375. m_pDetails = g_pMatchFramework->GetMatchNetworkMsgController()->UnpackGameDetailsFromSteamLobby( pParam->m_ulSteamIDLobby );
  376. if ( m_pDetails )
  377. {
  378. // Set AUX fields like session id
  379. if ( KeyValues *kvOptions = m_pDetails->FindKey( "options", true ) )
  380. {
  381. kvOptions->SetUint64( "sessionid", pParam->m_ulSteamIDLobby );
  382. }
  383. // Set the "player" key
  384. if ( KeyValues *kvPlayer = m_pDetails->FindKey( "player", true ) )
  385. {
  386. kvPlayer->SetUint64( "xuid", GetXUID() );
  387. kvPlayer->SetUint64( "xuidOnline", GetXUID() );
  388. kvPlayer->SetString( "name", GetName() );
  389. kvPlayer->SetWString( "richpresence", GetRichPresence() );
  390. }
  391. }
  392. m_eSearchState = SEARCH_COMPLETED;
  393. }
  394. #endif
  395. void PlayerFriend::Destroy()
  396. {
  397. AbortSearch();
  398. if ( m_pPublishedPresence )
  399. m_pPublishedPresence->deleteThis();
  400. m_pPublishedPresence = NULL;
  401. delete this;
  402. }
  403. void PlayerFriend::AbortSearch()
  404. {
  405. #ifdef _X360
  406. #elif !defined( NO_STEAM )
  407. m_CallbackOnLobbyDataUpdate.Unregister();
  408. #endif
  409. // Clean up the queue
  410. while ( s_arrSessionSearchesQueue.FindAndRemove( m_xuid ) )
  411. continue;
  412. bool bAbortedSearch = false;
  413. switch ( m_eSearchState )
  414. {
  415. #ifdef _X360
  416. case SEARCH_XNKID:
  417. MMX360_CancelOverlapped( &m_SessionSearchOverlapped );
  418. bAbortedSearch = true;
  419. break;
  420. case SEARCH_QOS:
  421. // We should gracefully abort the QOS operation outstanding
  422. g_pMatchExtensions->GetIXOnline()->XNetQosRelease( m_XNQOS );
  423. m_XNQOS = NULL;
  424. bAbortedSearch = true;
  425. break;
  426. #elif !defined( NO_STEAM )
  427. case SEARCH_WAIT_LOBBY_DATA:
  428. bAbortedSearch = true;
  429. break;
  430. #endif
  431. case SEARCH_COMPLETED:
  432. bAbortedSearch = true;
  433. break;
  434. }
  435. if ( bAbortedSearch )
  436. {
  437. -- s_numSearchesOutstanding;
  438. PumpSessionSearchQueue();
  439. }
  440. m_eSearchState = SEARCH_NONE;
  441. if ( m_pDetails )
  442. m_pDetails->deleteThis();
  443. m_pDetails = NULL;
  444. }
  445. void PlayerFriend::SetFriendMark( unsigned maskSetting )
  446. {
  447. m_uFriendMark = maskSetting;
  448. }
  449. unsigned PlayerFriend::GetFriendMark()
  450. {
  451. return m_uFriendMark;
  452. }
  453. void PlayerFriend::SetIsStale( bool bStale )
  454. {
  455. m_bIsStale = bStale;
  456. }
  457. bool PlayerFriend::GetIsStale()
  458. {
  459. return m_bIsStale;
  460. }
  461. void PlayerFriend::UpdateFriendInfo( FriendInfo_t const *pFriendInfo )
  462. {
  463. if ( !pFriendInfo )
  464. return;
  465. if ( pFriendInfo->m_szName )
  466. Q_strncpy( m_szName, pFriendInfo->m_szName, ARRAYSIZE( m_szName ) );
  467. if ( pFriendInfo->m_wszRichPresence )
  468. Q_wcsncpy( m_wszRichPresence, pFriendInfo->m_wszRichPresence, ARRAYSIZE( m_wszRichPresence ) );
  469. m_uiTitleID = pFriendInfo->m_uiTitleID;
  470. if ( pFriendInfo->m_uiGameServerIP != ~0 )
  471. m_uiGameServerIP = pFriendInfo->m_uiGameServerIP;
  472. if ( cl_names_debug.GetBool() )
  473. {
  474. Q_strncpy( m_szName, PLAYER_DEBUG_NAME, ARRAYSIZE( m_szName ) );
  475. }
  476. if ( pFriendInfo->m_pGameDetails )
  477. {
  478. AbortSearch();
  479. if ( m_pDetails )
  480. m_pDetails->deleteThis();
  481. m_pDetails = pFriendInfo->m_pGameDetails->MakeCopy();
  482. #ifdef _X360
  483. char const *szSessionInfo = m_pDetails->GetString( "options/sessioninfo" );
  484. MMX360_SessionInfoFromString( m_GameSessionInfo, szSessionInfo );
  485. m_xSessionID = m_GameSessionInfo.sessionID;
  486. #elif !defined( NO_STEAM )
  487. uint64 uiSessionId = m_pDetails->GetUint64( "options/sessionid" );
  488. m_xSessionID = ( XNKID & ) uiSessionId;
  489. #endif
  490. // Signal that we are finished with a search
  491. KeyValues *kvEvent = new KeyValues( "OnMatchPlayerMgrUpdate", "update", "friend" );
  492. kvEvent->SetUint64( "xuid", GetXUID() );
  493. g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( kvEvent );
  494. }
  495. else if ( pFriendInfo->m_uiTitleID &&
  496. pFriendInfo->m_uiTitleID != g_pMatchFramework->GetMatchTitle()->GetTitleID() )
  497. {
  498. if ( m_pDetails )
  499. m_pDetails->deleteThis();
  500. m_pDetails = new KeyValues( "TitleSettings" );
  501. m_pDetails->SetUint64( "titleid", pFriendInfo->m_uiTitleID );
  502. }
  503. else
  504. {
  505. m_xSessionID = pFriendInfo->m_xSessionID;
  506. if( m_eSearchState == SEARCH_NONE )
  507. {
  508. StartSearchForSessionInfo();
  509. }
  510. }
  511. #if !defined( NO_STEAM )
  512. // Update published presence for the friend too
  513. if ( m_pPublishedPresence )
  514. {
  515. m_pPublishedPresence->deleteThis();
  516. m_pPublishedPresence = NULL;
  517. }
  518. ISteamFriends *pf = steamapicontext->SteamFriends();
  519. if ( pf && ( g_pMatchFramework->GetMatchTitle()->GetTitleID() == m_uiTitleID ) )
  520. {
  521. pf->RequestFriendRichPresence( GetXUID() ); // refresh friend's rich presence
  522. int numRichPresenceKeys = pf->GetFriendRichPresenceKeyCount( GetXUID() );
  523. for ( int j = 0; j < numRichPresenceKeys; ++ j )
  524. {
  525. const char *pszKey = pf->GetFriendRichPresenceKeyByIndex( GetXUID(), j );
  526. if ( pszKey && *pszKey )
  527. {
  528. char const *pszValue = pf->GetFriendRichPresence( GetXUID(), pszKey );
  529. if ( pszValue && *pszValue )
  530. {
  531. if ( !m_pPublishedPresence )
  532. m_pPublishedPresence = new KeyValues( "RP" );
  533. CFmtStr fmtNewKey( "%s", pszKey );
  534. while ( char *szFixChar = strchr( fmtNewKey.Access(), ':' ) )
  535. *szFixChar = '/';
  536. m_pPublishedPresence->SetString( fmtNewKey, pszValue );
  537. }
  538. }
  539. }
  540. }
  541. #endif
  542. }
  543. bool PlayerFriend::IsUpdatingInfo()
  544. {
  545. return m_eSearchState != SEARCH_NONE;
  546. }
  547. void PlayerFriend::StartSearchForSessionInfo()
  548. {
  549. if ( !m_xuid )
  550. return;
  551. if ( m_eSearchState != SEARCH_NONE )
  552. return;
  553. m_eSearchState = SEARCH_QUEUED;
  554. // Check if we are not already in the queue
  555. if ( s_arrSessionSearchesQueue.Find( m_xuid ) == s_arrSessionSearchesQueue.InvalidIndex() )
  556. {
  557. s_arrSessionSearchesQueue.AddToTail( m_xuid );
  558. }
  559. PumpSessionSearchQueue();
  560. }
  561. void PlayerFriend::StartSearchForSessionInfoImpl()
  562. {
  563. #ifdef _X360
  564. if ( !XBX_GetNumGameUsers() || XBX_GetPrimaryUserIsGuest() )
  565. {
  566. memset( &m_xSessionID, 0, sizeof( m_xSessionID ) );
  567. memset( &m_GameSessionInfo, 0, sizeof( m_GameSessionInfo ) );
  568. if ( m_pDetails )
  569. m_pDetails->deleteThis();
  570. m_pDetails = NULL;
  571. m_eSearchState = SEARCH_NONE;
  572. return;
  573. }
  574. #endif
  575. if( m_eSearchState == SEARCH_NONE ||
  576. m_eSearchState == SEARCH_QUEUED )
  577. {
  578. #ifdef _X360
  579. if( ( const uint64 & ) m_xSessionID )
  580. {
  581. int iCtrlr = XBX_GetPrimaryUserId();
  582. DWORD numBytesResult = 0;
  583. DWORD dwError = g_pMatchExtensions->GetIXOnline()->XSessionSearchByID( m_xSessionID, iCtrlr, &numBytesResult, NULL, NULL );
  584. if( dwError != ERROR_INSUFFICIENT_BUFFER )
  585. {
  586. memset( &m_xSessionID, 0, sizeof( m_xSessionID ) );
  587. memset( &m_GameSessionInfo, 0, sizeof( m_GameSessionInfo ) );
  588. if ( m_pDetails )
  589. m_pDetails->deleteThis();
  590. m_pDetails = NULL;
  591. m_eSearchState = SEARCH_NONE;
  592. return;
  593. }
  594. m_bufSessionSearchResults.EnsureCapacity( numBytesResult );
  595. ZeroMemory( GetXSearchResults(), numBytesResult );
  596. dwError = g_pMatchExtensions->GetIXOnline()->XSessionSearchByID( m_xSessionID, iCtrlr, &numBytesResult, GetXSearchResults(), &m_SessionSearchOverlapped );
  597. if( dwError != ERROR_IO_PENDING )
  598. {
  599. memset( &m_xSessionID, 0, sizeof( m_xSessionID ) );
  600. memset( &m_GameSessionInfo, 0, sizeof( m_GameSessionInfo ) );
  601. if ( m_pDetails )
  602. m_pDetails->deleteThis();
  603. m_pDetails = NULL;
  604. m_eSearchState = SEARCH_NONE;
  605. return;
  606. }
  607. m_eSearchState = SEARCH_XNKID;
  608. #elif !defined( NO_STEAM )
  609. if ( steamapicontext->SteamMatchmaking() &&
  610. ( const uint64 & ) m_xSessionID &&
  611. steamapicontext->SteamMatchmaking()->RequestLobbyData( ( const uint64 & ) m_xSessionID ) )
  612. {
  613. // Enable the callback
  614. m_CallbackOnLobbyDataUpdate.Register( this, &PlayerFriend::Steam_OnLobbyDataUpdate );
  615. m_eSearchState = SEARCH_WAIT_LOBBY_DATA;
  616. #else
  617. if ( 0 )
  618. {
  619. #endif
  620. ++ s_numSearchesOutstanding;
  621. }
  622. else
  623. {
  624. memset( &m_xSessionID, 0, sizeof( m_xSessionID ) );
  625. memset( &m_GameSessionInfo, 0, sizeof( m_GameSessionInfo ) );
  626. if ( m_pDetails )
  627. m_pDetails->deleteThis();
  628. m_pDetails = NULL;
  629. m_eSearchState = SEARCH_NONE;
  630. }
  631. }
  632. }
  633. //
  634. // PlayerLocal implementation
  635. //
  636. PlayerLocal::PlayerLocal( int iController ) :
  637. m_eLoadedTitleData( eXUserSigninState_NotSignedIn ),
  638. m_flLastSave( 0.0f ),
  639. m_uiPlayerFlags( 0 ),
  640. m_pLeaderboardData( new KeyValues( "Leaderboard" ) ),
  641. m_autodelete_pLeaderboardData( m_pLeaderboardData )
  642. {
  643. Assert( iController >= 0 && iController < XUSER_MAX_COUNT );
  644. memset( &m_ProfileData, 0, sizeof( m_ProfileData ) );
  645. memset( m_bufTitleData, 0, sizeof( m_bufTitleData ) );
  646. memset( m_bSaveTitleData, 0, sizeof( m_bSaveTitleData ) );
  647. m_iController = iController;
  648. GetXWriteOpportunity( iController ); // reset
  649. #ifdef _X360
  650. m_bIsTitleDataValid = false;
  651. for ( int i=0; i<TITLE_DATA_COUNT; ++i )
  652. {
  653. m_bIsTitleDataBlockValid[ i ] = false;
  654. }
  655. m_bIsFreshPlayerProfile = false;
  656. if ( !XBX_GetPrimaryUserIsGuest() )
  657. {
  658. XUserGetXUID( iController, &m_xuid );
  659. }
  660. DetectOnlineState();
  661. #elif !defined( NO_STEAM )
  662. CSteamID steamIDPlayer;
  663. if ( steamapicontext->SteamUser() )
  664. {
  665. m_eOnlineState = steamapicontext->SteamUser()->BLoggedOn() ? IPlayer::STATE_ONLINE : IPlayer::STATE_OFFLINE;
  666. steamIDPlayer = steamapicontext->SteamUser()->GetSteamID();
  667. m_xuid = steamIDPlayer.IsValid() ? steamIDPlayer.ConvertToUint64() : 0;
  668. }
  669. else
  670. {
  671. m_xuid = 0;
  672. }
  673. #else
  674. m_xuid = 1ull;
  675. m_eOnlineState = IPlayer::STATE_OFFLINE;
  676. #endif
  677. #ifdef _X360
  678. if( m_xuid )
  679. {
  680. XUserGetName( m_iController, m_szName, ARRAYSIZE( m_szName ) );
  681. LoadPlayerProfileData();
  682. }
  683. else if ( char const *szGuestName = g_pMatchFramework->GetMatchTitle()->GetGuestPlayerName( m_iController ) )
  684. {
  685. Q_strncpy( m_szName, szGuestName, ARRAYSIZE( m_szName ) );
  686. }
  687. else
  688. {
  689. m_szName[0] = 0;
  690. }
  691. #elif defined ( _PS3 )
  692. ConVarRef cl_name( "name" );
  693. const char* pPlayerName = cl_name.GetString();
  694. Q_strncpy( m_szName, pPlayerName, ARRAYSIZE( m_szName ) );
  695. #elif !defined( NO_STEAM )
  696. // Get user name from Steam
  697. if ( steamIDPlayer.IsValid() && steamapicontext->SteamUser() && steamapicontext->SteamFriends() )
  698. {
  699. const char *pszName = steamapicontext->SteamFriends()->GetFriendPersonaName( steamIDPlayer );
  700. if ( pszName )
  701. {
  702. Q_strncpy( m_szName, pszName, ARRAYSIZE( m_szName ) );
  703. }
  704. }
  705. m_CallbackOnPersonaStateChange.Register( this, &PlayerLocal::Steam_OnPersonaStateChange );
  706. m_CallbackOnServersConnected.Register( this, &PlayerLocal::Steam_OnServersConnected );
  707. m_CallbackOnServersDisconnected.Register( this, &PlayerLocal::Steam_OnServersDisconnected );
  708. LoadPlayerProfileData();
  709. #else
  710. if ( char const *szGuestName = g_pMatchFramework->GetMatchTitle()->GetGuestPlayerName( m_iController ) )
  711. {
  712. Q_strncpy( m_szName, szGuestName, ARRAYSIZE( m_szName ) );
  713. }
  714. else
  715. {
  716. m_szName[0] = 0;
  717. }
  718. #endif
  719. if ( cl_names_debug.GetBool() )
  720. {
  721. Q_strncpy( m_szName, PLAYER_DEBUG_NAME, ARRAYSIZE( m_szName ) );
  722. }
  723. }
  724. PlayerLocal::~PlayerLocal()
  725. {
  726. #ifdef _X360
  727. for ( int k = 0; k < s_arrPendingAsyncAwards.Count(); ++ k )
  728. {
  729. // Detach pending achievement awards from currently destructed player
  730. if ( s_arrPendingAsyncAwards[k]->m_pLocalPlayer == this )
  731. s_arrPendingAsyncAwards[k]->m_pLocalPlayer = NULL;
  732. }
  733. #endif
  734. }
  735. void PlayerLocal::LoadTitleData()
  736. {
  737. if ( m_eLoadedTitleData == eXUserSigninState_SignedInToLive )
  738. // already processed
  739. return;
  740. if ( m_eLoadedTitleData >= GetAssumedSigninState() )
  741. // already processed
  742. return;
  743. #ifdef _X360
  744. m_bIsTitleDataValid = false;
  745. for ( int i=0; i<TITLE_DATA_COUNT; ++i )
  746. {
  747. m_bIsTitleDataBlockValid[ i ] = false;
  748. }
  749. float flTimeStart;
  750. flTimeStart = Plat_FloatTime();
  751. Msg( "Player %d : LoadTitleData...\n", m_iController );
  752. //
  753. // Enumerate the state of all achievements
  754. //
  755. {
  756. DWORD numAchievements = 0;
  757. HANDLE hEnumerator = NULL;
  758. DWORD dwBytes;
  759. DWORD ret = XUserCreateAchievementEnumerator( 0, m_iController, INVALID_XUID, XACHIEVEMENT_DETAILS_TFC, 0, 80, &dwBytes, &hEnumerator );
  760. if ( ret == ERROR_SUCCESS )
  761. {
  762. CUtlVector< char > vBuffer;
  763. vBuffer.SetCount( dwBytes );
  764. ret = XEnumerate( hEnumerator, vBuffer.Base(), dwBytes, &numAchievements, NULL );
  765. CloseHandle( hEnumerator );
  766. hEnumerator = NULL;
  767. if ( ret == ERROR_SUCCESS )
  768. {
  769. XACHIEVEMENT_DETAILS const *pXboxAchievements = ( XACHIEVEMENT_DETAILS const * ) vBuffer.Base();
  770. for ( DWORD i = 0; i < numAchievements; ++i )
  771. {
  772. if ( AchievementEarned( pXboxAchievements[i].dwFlags ) )
  773. {
  774. m_arrAchievementsEarned.FindAndFastRemove( pXboxAchievements[i].dwId );
  775. m_arrAchievementsEarned.AddToTail( pXboxAchievements[i].dwId );
  776. }
  777. }
  778. }
  779. }
  780. }
  781. //
  782. // Load actual title data blocks
  783. //
  784. DWORD dwNumDataIds = TITLE_DATA_COUNT_X360;
  785. CArrayAutoPtr< DWORD > pdwTitleDataIds( new DWORD[ dwNumDataIds ] );
  786. for ( DWORD k = 0; k < dwNumDataIds; ++ k )
  787. pdwTitleDataIds[k] = GetTitleSpecificDataId( k );
  788. m_eLoadedTitleData = GetAssumedSigninState();
  789. DWORD resultsSize = 0;
  790. DWORD ret = ERROR_FILE_NOT_FOUND;
  791. if ( m_eLoadedTitleData == eXUserSigninState_SignedInLocally )
  792. {
  793. ret = g_pMatchExtensions->GetIXOnline()->XUserReadProfileSettings(
  794. g_pMatchFramework->GetMatchTitle()->GetTitleID(), m_iController,
  795. dwNumDataIds, pdwTitleDataIds.Get(),
  796. &resultsSize, NULL,
  797. NULL );
  798. }
  799. else if ( m_eLoadedTitleData == eXUserSigninState_SignedInToLive )
  800. {
  801. ret = g_pMatchExtensions->GetIXOnline()->XUserReadProfileSettingsByXuid(
  802. g_pMatchFramework->GetMatchTitle()->GetTitleID(), m_iController,
  803. 1, &m_xuid,
  804. dwNumDataIds, pdwTitleDataIds.Get(),
  805. &resultsSize, NULL,
  806. NULL );
  807. }
  808. if ( ret != ERROR_INSUFFICIENT_BUFFER )
  809. {
  810. Warning( "Player %d : LoadTitleData failed to get size (err=0x%08X)!\n", m_iController, ret );
  811. // Failed
  812. OnProfileTitleDataLoaded( ret );
  813. return;
  814. }
  815. CArrayAutoPtr< char > spResultBuffer( new char[ resultsSize ] );
  816. XUSER_READ_PROFILE_SETTING_RESULT *pResult = (XUSER_READ_PROFILE_SETTING_RESULT *) spResultBuffer.Get();
  817. if ( m_eLoadedTitleData == eXUserSigninState_SignedInLocally )
  818. {
  819. ret = g_pMatchExtensions->GetIXOnline()->XUserReadProfileSettings(
  820. g_pMatchFramework->GetMatchTitle()->GetTitleID(), m_iController,
  821. dwNumDataIds, pdwTitleDataIds.Get(),
  822. &resultsSize, pResult,
  823. NULL );
  824. }
  825. else if ( m_eLoadedTitleData == eXUserSigninState_SignedInToLive )
  826. {
  827. ret = g_pMatchExtensions->GetIXOnline()->XUserReadProfileSettingsByXuid(
  828. g_pMatchFramework->GetMatchTitle()->GetTitleID(), m_iController,
  829. 1, &m_xuid,
  830. dwNumDataIds, pdwTitleDataIds.Get(),
  831. &resultsSize, pResult,
  832. NULL );
  833. }
  834. if ( ret != ERROR_SUCCESS )
  835. {
  836. Warning( "Player %d : LoadTitleData failed to read data (err=0x%08X)!\n", m_iController, ret );
  837. // Failed
  838. OnProfileTitleDataLoaded( ret );
  839. return;
  840. }
  841. m_bIsTitleDataValid = true;
  842. m_bIsFreshPlayerProfile = true;
  843. for ( DWORD iSetting = 0; iSetting < pResult->dwSettingsLen; ++ iSetting )
  844. {
  845. XUSER_PROFILE_SETTING const &xps = pResult->pSettings[ iSetting ];
  846. if ( xps.data.type != XUSER_DATA_TYPE_BINARY )
  847. {
  848. m_bIsTitleDataValid = false;
  849. m_bIsFreshPlayerProfile = false;
  850. continue;
  851. }
  852. if ( xps.data.binary.cbData != XPROFILE_SETTING_MAX_SIZE )
  853. {
  854. if ( xps.data.binary.cbData != 0 )
  855. {
  856. m_bIsTitleDataValid = false;
  857. }
  858. continue;
  859. }
  860. m_bIsFreshPlayerProfile = false;
  861. m_bIsTitleDataBlockValid[ iSetting ] = true;
  862. int iDataIndex = GetTitleSpecificDataIndex( xps.dwSettingId );
  863. if ( iDataIndex >= 0 )
  864. {
  865. Msg( "Player %d : LoadTitleData succeeded with Data%d\n",
  866. m_iController, iDataIndex );
  867. V_memcpy( m_bufTitleData[ iDataIndex ], xps.data.binary.pbData, XPROFILE_SETTING_MAX_SIZE );
  868. }
  869. }
  870. // Clear the dirty flag after dirty
  871. V_memset( m_bSaveTitleData, 0, sizeof( m_bSaveTitleData[0] ) * TITLE_DATA_COUNT_X360 );
  872. // After we loaded some title data, see if we need to retrospectively award achievements
  873. EvaluateAwardsStateBasedOnStats();
  874. Msg( "Player %d : LoadTitleData finished (%.3f sec).\n",
  875. m_iController, Plat_FloatTime() - flTimeStart );
  876. if ( m_bIsTitleDataValid )
  877. OnProfileTitleDataLoaded( 0 );
  878. else
  879. OnProfileTitleDataLoaded( 1 );
  880. #elif !defined ( NO_STEAM )
  881. // Always request user stats from Steam
  882. if ( steamapicontext->SteamUserStats() )
  883. {
  884. m_eLoadedTitleData = GetAssumedSigninState();
  885. m_CallbackOnUserStatsReceived.Register( this, &PlayerLocal::Steam_OnUserStatsReceived );
  886. steamapicontext->SteamUserStats()->RequestCurrentStats();
  887. s_flSteamStatsRequestTime = Plat_FloatTime();
  888. if ( !s_bSteamStatsRequestFailed )
  889. {
  890. DevMsg( "Requesting Steam stats... (%2.2f)\n", Plat_FloatTime() );
  891. }
  892. }
  893. #else
  894. m_eLoadedTitleData = GetAssumedSigninState();
  895. // Since we don't have Steam, we reset all configuration values and stats.
  896. IGameEvent *event = g_pMatchExtensions->GetIGameEventManager2()->CreateEvent( "reset_game_titledata" );
  897. if ( event )
  898. {
  899. event->SetInt( "controllerId", m_iController );
  900. g_pMatchExtensions->GetIGameEventManager2()->FireEventClientSide( event );
  901. }
  902. g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "ResetConfiguration", "iController", m_iController ) );
  903. #endif
  904. }
  905. #if !defined( _X360 ) && !defined ( NO_STEAM )
  906. ConVar mm_cfgoverride_file( "mm_cfgoverride_file", "", FCVAR_DEVELOPMENTONLY );
  907. ConVar mm_cfgoverride_commit( "mm_cfgoverride_commit", "", FCVAR_DEVELOPMENTONLY );
  908. ConVar mm_cfgdebug_mode( "mm_cfgdebug_mode", "0", FCVAR_DEVELOPMENTONLY );
  909. static bool SetSteamStatWithPotentialOverride( char const *szField, int32 iValue )
  910. {
  911. char const *szStatFieldSteamDB = szField;
  912. return steamapicontext->SteamUserStats() ? steamapicontext->SteamUserStats()->SetStat( szStatFieldSteamDB, iValue ) : false;
  913. }
  914. static bool SetSteamStatWithPotentialOverride( char const *szField, float fValue )
  915. {
  916. char const *szStatFieldSteamDB = szField;
  917. return steamapicontext->SteamUserStats() ? steamapicontext->SteamUserStats()->SetStat( szStatFieldSteamDB, fValue ) : false;
  918. }
  919. template < typename T >
  920. static inline bool ApplySteamStatPotentialOverride( char const *szField, T *pValue, bool bResult, T (KeyValues::*pfn)( char const *, T ) )
  921. {
  922. #ifdef _CERT
  923. return bResult;
  924. #else
  925. if ( mm_cfgdebug_mode.GetInt() > 0 )
  926. {
  927. DevMsg( "[PlayerStats] '%s' = %d (0x%08X)\n", szField, (int32)*pValue, *(int32*)pValue );
  928. }
  929. char const *szFile = mm_cfgoverride_file.GetString();
  930. if ( !szFile || !*szFile )
  931. return bResult;
  932. KeyValues *kvOverride = new KeyValues( "cfgoverride.kv" );
  933. KeyValues::AutoDelete autodelete( kvOverride );
  934. if ( !kvOverride->LoadFromFile( g_pFullFileSystem, szFile ) )
  935. return bResult;
  936. if ( KeyValues *kvItemOverride = kvOverride->FindKey( "items" )->FindKey( szField ) )
  937. {
  938. *pValue = ( kvItemOverride->*pfn )( "", 0 );
  939. DevMsg( "[PlayerStats] '%s' overrides '%s' = '%s'\n", szFile, szField, kvItemOverride->GetString( "", "" ) );
  940. if ( mm_cfgoverride_commit.GetBool() )
  941. SetSteamStatWithPotentialOverride( szField, *pValue );
  942. return true;
  943. }
  944. // Match by wildcard
  945. for ( KeyValues *kvWildcard = kvOverride->FindKey( "wildcards" )->GetFirstValue(); kvWildcard; kvWildcard = kvWildcard->GetNextValue() )
  946. {
  947. char const *szWildcard = kvWildcard->GetName();
  948. int nLen = Q_strlen( szWildcard );
  949. if ( !nLen || ( szWildcard[nLen-1] != '*' ) )
  950. continue;
  951. if ( (nLen <= 1) || !Q_strnicmp( szWildcard, szField, nLen - 1 ) )
  952. {
  953. *pValue = ( kvWildcard->*pfn )( "", 0 );
  954. DevMsg( "[PlayerStats] '%s' overrides '%s' = '%s' [wildcard match '%s']\n", szFile, szField, kvWildcard->GetString( "", "" ), szWildcard );
  955. if ( mm_cfgoverride_commit.GetBool() )
  956. SetSteamStatWithPotentialOverride( szField, *pValue );
  957. return true;
  958. }
  959. }
  960. return bResult;
  961. #endif
  962. }
  963. static bool GetSteamStatWithPotentialOverride( char const *szField, int32 *pValue )
  964. {
  965. char const *szStatFieldSteamDB = szField;
  966. bool bResult = steamapicontext->SteamUserStats()->GetStat( szStatFieldSteamDB, pValue );
  967. return ApplySteamStatPotentialOverride<int32>( szField, pValue, bResult, &KeyValues::GetInt );
  968. }
  969. static bool GetSteamStatWithPotentialOverride( char const *szField, float *pValue )
  970. {
  971. char const *szStatFieldSteamDB = szField;
  972. bool bResult = steamapicontext->SteamUserStats()->GetStat( szStatFieldSteamDB, pValue );
  973. return ApplySteamStatPotentialOverride<float>( szField, pValue, bResult, &KeyValues::GetFloat );
  974. }
  975. void PlayerLocal::Steam_OnUserStatsReceived( UserStatsReceived_t *pParam )
  976. {
  977. #ifndef NO_STEAM
  978. if ( !s_bSteamStatsRequestFailed || ( pParam->m_eResult == k_EResultOK ) )
  979. {
  980. DevMsg( "PlayerLocal::Steam_OnUserStatsReceived... (%2.2f sec since request)\n", s_flSteamStatsRequestTime ? ( Plat_FloatTime() - s_flSteamStatsRequestTime ) : 0.0f );
  981. }
  982. s_flSteamStatsRequestTime = 0;
  983. #endif
  984. // If failed, we'll request one more time
  985. if ( pParam->m_eResult != k_EResultOK )
  986. {
  987. if ( !s_bSteamStatsRequestFailed )
  988. {
  989. DevWarning( "PlayerLocal::Steam_OnUserStatsReceived (failed with error %d)\n", pParam->m_eResult );
  990. s_bSteamStatsRequestFailed = true;
  991. }
  992. m_eLoadedTitleData = eXUserSigninState_NotSignedIn;
  993. return;
  994. }
  995. s_bSteamStatsRequestFailed = false;
  996. g_bSteamStatsReceived = true;
  997. //
  998. // Achievements state
  999. //
  1000. for ( TitleAchievementsDescription_t const *pAchievement = g_pMatchFramework->GetMatchTitle()->DescribeTitleAchievements();
  1001. pAchievement && pAchievement->m_szAchievementName; ++ pAchievement )
  1002. {
  1003. bool bAchieved;
  1004. if ( steamapicontext->SteamUserStats()->GetAchievement( pAchievement->m_szAchievementName, &bAchieved ) && bAchieved )
  1005. {
  1006. m_arrAchievementsEarned.FindAndFastRemove( pAchievement->m_idAchievement );
  1007. m_arrAchievementsEarned.AddToTail( pAchievement->m_idAchievement );
  1008. }
  1009. }
  1010. //
  1011. // Load all our stats data
  1012. //
  1013. TitleDataFieldsDescription_t const *pTitleDataTable = g_pMatchFramework->GetMatchTitle()->DescribeTitleDataStorage();
  1014. for ( ; pTitleDataTable && pTitleDataTable->m_szFieldName; ++ pTitleDataTable )
  1015. {
  1016. switch( pTitleDataTable->m_eDataType )
  1017. {
  1018. case TitleDataFieldsDescription_t::DT_uint8:
  1019. case TitleDataFieldsDescription_t::DT_uint16:
  1020. case TitleDataFieldsDescription_t::DT_uint32:
  1021. {
  1022. uint32 i32field[3] = {0};
  1023. if ( GetSteamStatWithPotentialOverride( pTitleDataTable->m_szFieldName, ( int32 * ) &i32field[0] ) )
  1024. {
  1025. *( uint16 * )( &i32field[1] ) = uint16( i32field[0] );
  1026. *( uint8 * )( &i32field[2] ) = uint8 ( i32field[0] );
  1027. memcpy( &m_bufTitleData[ pTitleDataTable->m_iTitleDataBlock ][ pTitleDataTable->m_numBytesOffset ],
  1028. &i32field[ 2 - ( pTitleDataTable->m_eDataType / 16 ) ], pTitleDataTable->m_eDataType / 8 );
  1029. }
  1030. }
  1031. break;
  1032. case TitleDataFieldsDescription_t::DT_float:
  1033. {
  1034. float flField = 0.0f;
  1035. if ( GetSteamStatWithPotentialOverride( pTitleDataTable->m_szFieldName, &flField ) )
  1036. {
  1037. memcpy( &m_bufTitleData[ pTitleDataTable->m_iTitleDataBlock ][ pTitleDataTable->m_numBytesOffset ],
  1038. &flField, pTitleDataTable->m_eDataType / 8 );
  1039. }
  1040. }
  1041. break;
  1042. case TitleDataFieldsDescription_t::DT_uint64:
  1043. {
  1044. uint32 i32field[2] = { 0 };
  1045. char chBuffer[ 256 ] = {0};
  1046. for ( int k = 0; k < ARRAYSIZE( i32field ); ++ k )
  1047. {
  1048. Q_snprintf( chBuffer, ARRAYSIZE( chBuffer ), "%s.%d", pTitleDataTable->m_szFieldName, k );
  1049. if ( !GetSteamStatWithPotentialOverride( chBuffer, ( int32 * ) &i32field[k] ) )
  1050. i32field[k] = 0;
  1051. }
  1052. memcpy( &m_bufTitleData[ pTitleDataTable->m_iTitleDataBlock ][ pTitleDataTable->m_numBytesOffset ],
  1053. &i32field[0], pTitleDataTable->m_eDataType / 8 );
  1054. }
  1055. break;
  1056. case TitleDataFieldsDescription_t::DT_BITFIELD:
  1057. {
  1058. #ifdef STEAM_PACK_BITFIELDS
  1059. char chStatField[64] = {0};
  1060. uint32 uiOffsetInTermsOfUINT32 = pTitleDataTable->m_numBytesOffset/32;
  1061. V_snprintf( chStatField, sizeof( chStatField ), "bitfield_%02u_%03X", pTitleDataTable->m_iTitleDataBlock + 1, uiOffsetInTermsOfUINT32*4 );
  1062. int32 iCombinedBitValue = 0;
  1063. if ( GetSteamStatWithPotentialOverride( chStatField, &iCombinedBitValue ) )
  1064. {
  1065. ( reinterpret_cast< uint32 * >( &m_bufTitleData[pTitleDataTable->m_iTitleDataBlock][0] ) )[ uiOffsetInTermsOfUINT32 ] = iCombinedBitValue;
  1066. }
  1067. #else
  1068. int i32field = 0;
  1069. if ( GetSteamStatWithPotentialOverride( pTitleDataTable->m_szFieldName, &i32field ) )
  1070. {
  1071. char &rByte = m_bufTitleData[ pTitleDataTable->m_iTitleDataBlock ][ pTitleDataTable->m_numBytesOffset/8 ];
  1072. char iMask = ( 1 << ( pTitleDataTable->m_numBytesOffset % 8 ) );
  1073. if ( i32field )
  1074. rByte |= iMask;
  1075. else
  1076. rByte &=~iMask;
  1077. }
  1078. #endif
  1079. }
  1080. break;
  1081. }
  1082. }
  1083. #if defined ( _PS3 )
  1084. // We just loaded all our stats and settings from Steam.
  1085. TitleDataFieldsDescription_t const *fields = g_pMatchFramework->GetMatchTitle()->DescribeTitleDataStorage();
  1086. Assert( fields );
  1087. TitleDataFieldsDescription_t const *versionField = TitleDataFieldsDescriptionFindByString( fields, TITLE_DATA_PREFIX "CFG.sys.version" );
  1088. Assert( versionField );
  1089. int versionNumber = TitleDataFieldsDescriptionGetValue<int32>( versionField, this );
  1090. ConVarRef cl_configversion("cl_configversion");
  1091. // Check the version number to see if this is a new save profile.
  1092. // In that case, we need to reset everything to the defaults.
  1093. if ( versionNumber != cl_configversion.GetInt() )
  1094. {
  1095. // This will wipe out all achievement and stats. This is called in Host_ResetConfiguration for xbox, but we don't
  1096. // want to call it for anything that uses steam unless we're okay with clearning all stats.
  1097. IGameEvent *event = g_pMatchExtensions->GetIGameEventManager2()->CreateEvent( "reset_game_titledata" );
  1098. if ( event )
  1099. {
  1100. event->SetInt( "controllerId", m_iController );
  1101. g_pMatchExtensions->GetIGameEventManager2()->FireEventClientSide( event );
  1102. }
  1103. // ResetConfiguration will set all the settings to the defaults.
  1104. g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "ResetConfiguration", "iController", m_iController ) );
  1105. }
  1106. #endif
  1107. #if !defined ( _X360 )
  1108. // send an event to anyone else who needs Steam user stat data
  1109. IGameEvent *event = g_pMatchExtensions->GetIGameEventManager2()->CreateEvent( "user_data_downloaded" );
  1110. if ( event )
  1111. {
  1112. #ifdef GAME_DLL
  1113. g_pMatchExtensions->GetIGameEventManager2()->FireEvent( event );
  1114. #else
  1115. // not sure this event is caught anywhere but we brought it over from orange box just in case
  1116. g_pMatchExtensions->GetIGameEventManager2()->FireEventClientSide( event );
  1117. #endif
  1118. }
  1119. #endif
  1120. // After we loaded some title data, see if we need to retrospectively award achievements
  1121. EvaluateAwardsStateBasedOnStats();
  1122. // Flush stats if we are clearing for debugging
  1123. if ( mm_cfgoverride_commit.GetBool() )
  1124. steamapicontext->SteamUserStats()->StoreStats();
  1125. //
  1126. // Finished reading stats
  1127. //
  1128. DevMsg( "User%d stats retrieved.\n", m_iController );
  1129. OnProfileTitleDataLoaded( 0 );
  1130. #ifdef _DEBUG
  1131. static bool debugDumpStats = true;
  1132. if ( debugDumpStats )
  1133. {
  1134. debugDumpStats = false;
  1135. // Debug code.
  1136. // Dump the stats once loaded so we can see what they are.
  1137. g_pMatchExtensions->GetIVEngineClient()->ExecuteClientCmd( "ms_player_dump_properties" );
  1138. }
  1139. #endif
  1140. }
  1141. void PlayerLocal::Steam_OnPersonaStateChange( PersonaStateChange_t *pParam )
  1142. {
  1143. if ( !steamapicontext ||
  1144. !steamapicontext->SteamUtils() ||
  1145. !steamapicontext->SteamFriends() ||
  1146. !steamapicontext->SteamUser() ||
  1147. !pParam ||
  1148. !m_xuid )
  1149. return;
  1150. // Check that something changed about local user
  1151. if ( m_xuid == pParam->m_ulSteamID )
  1152. {
  1153. if ( pParam->m_nChangeFlags & k_EPersonaChangeName )
  1154. {
  1155. CSteamID steamID;
  1156. steamID.SetFromUint64( m_xuid );
  1157. if ( char const *szName = steamapicontext->SteamFriends()->GetFriendPersonaName( steamID ) )
  1158. {
  1159. Q_strncpy( m_szName, szName, ARRAYSIZE( m_szName ) );
  1160. }
  1161. if ( cl_names_debug.GetBool() )
  1162. {
  1163. Q_strncpy( m_szName, PLAYER_DEBUG_NAME, ARRAYSIZE( m_szName ) );
  1164. }
  1165. }
  1166. }
  1167. }
  1168. void PlayerLocal::UpdatePlayersSteamLogon()
  1169. {
  1170. if ( !steamapicontext->SteamUser() )
  1171. return;
  1172. IPlayer::OnlineState_t eState = steamapicontext->SteamUser()->BLoggedOn() ? IPlayer::STATE_ONLINE : IPlayer::STATE_OFFLINE;
  1173. m_eOnlineState = eState;
  1174. // Update XUID on PS3:
  1175. CSteamID cSteamId = steamapicontext->SteamUser()->GetSteamID();
  1176. if ( !m_xuid && cSteamId.IsValid() )
  1177. {
  1178. m_xuid = steamapicontext->SteamUser()->GetSteamID().ConvertToUint64();
  1179. }
  1180. }
  1181. void PlayerLocal::Steam_OnServersConnected( SteamServersConnected_t *pParam )
  1182. {
  1183. DevMsg( "Steam_OnServersConnected\n" );
  1184. UpdatePlayersSteamLogon();
  1185. }
  1186. void PlayerLocal::Steam_OnServersDisconnected( SteamServersDisconnected_t *pParam )
  1187. {
  1188. DevWarning( "Steam_OnServersDisconnected\n" );
  1189. UpdatePlayersSteamLogon();
  1190. }
  1191. #endif
  1192. void PlayerLocal::SetTitleDataWriteTime( float flTime )
  1193. {
  1194. m_flLastSave = flTime;
  1195. }
  1196. #if defined ( _X360 )
  1197. bool PlayerLocal::IsTitleDataBlockValid( int blockId )
  1198. {
  1199. if ( blockId < 0 || blockId >= TITLE_DATA_COUNT )
  1200. return false;
  1201. return m_bIsTitleDataBlockValid[ blockId ];
  1202. }
  1203. void PlayerLocal::ClearBufTitleData( void )
  1204. {
  1205. memset( m_bufTitleData, 0, sizeof( m_bufTitleData ) );
  1206. }
  1207. #endif
  1208. // Test if we can still read from profile; used when storage device is removed and we
  1209. // want to verify the profile still has a storage connection
  1210. bool PlayerLocal::IsTitleDataStorageConnected( void )
  1211. {
  1212. #if defined( _X360 )
  1213. // try to write out storage block 3 to see if there is a storage unit associated with this profile
  1214. CUtlVector< XUSER_PROFILE_SETTING > pXPS;
  1215. DWORD dwNumDataBufferBytes = XPROFILE_SETTING_MAX_SIZE;
  1216. CArrayAutoPtr< char > spDataBuffer( new char[ dwNumDataBufferBytes ] );
  1217. V_memset( spDataBuffer.Get(), 0, dwNumDataBufferBytes );
  1218. int titleStorageBlock3Index = 2;
  1219. XUSER_PROFILE_SETTING xps;
  1220. V_memset( &xps, 0, sizeof( xps ) );
  1221. xps.dwSettingId = GetTitleSpecificDataId( titleStorageBlock3Index );
  1222. xps.data.type = XUSER_DATA_TYPE_BINARY;
  1223. xps.data.binary.cbData = XPROFILE_SETTING_MAX_SIZE;
  1224. xps.data.binary.pbData = (PBYTE) spDataBuffer.Get();
  1225. V_memcpy( xps.data.binary.pbData, m_bufTitleData[ titleStorageBlock3Index ], XPROFILE_SETTING_MAX_SIZE );
  1226. pXPS.AddToTail( xps );
  1227. //
  1228. // Issue the XWrite operation
  1229. //
  1230. DWORD ret;
  1231. ret = g_pMatchExtensions->GetIXOnline()->XUserWriteProfileSettings( m_iController, pXPS.Count(), pXPS.Base(), NULL );
  1232. if ( ret != ERROR_SUCCESS )
  1233. {
  1234. return false;
  1235. }
  1236. #endif
  1237. return true;
  1238. }
  1239. void PlayerLocal::WriteTitleData()
  1240. {
  1241. #if defined( _DEMO ) && defined( _X360 )
  1242. // Demo versions are not allowed to write profile data
  1243. return;
  1244. #endif
  1245. #ifdef _X360
  1246. if ( !m_xuid )
  1247. return;
  1248. if ( GetAssumedSigninState() == eXUserSigninState_NotSignedIn )
  1249. return;
  1250. #if defined( CSTRIKE15 )
  1251. // Code to handle TCR 047
  1252. // Calling GetXWriteOpportunity clears the MM_XWriteOppurtinty to MMXWO_NONE
  1253. // but we don't want that if we are trying to write within 3 seconds of the last
  1254. // write. Rather we want the write to succeed after the 3 seconds expire; so
  1255. // we just bail until the 3 seconds is up and then allow the write to happen
  1256. // We do not have to queue up writes since the data we store on 360 is
  1257. // live data and is always the most current version of the data
  1258. const float cMinWriteDelay = 3.0f;
  1259. if ( Plat_FloatTime() - m_flLastSave < cMinWriteDelay )
  1260. {
  1261. return;
  1262. }
  1263. #endif
  1264. // NOTE: Need to call this here, because this has side effects.
  1265. // Getting the opportunity will clear the opportunity. This is used
  1266. // to only allow writes to happen at times out of game where we
  1267. // can be sure we don't hitch. If we don't do it here, then we can get into
  1268. // a state where some previous opportunity to write was set (say,
  1269. // leaving a cooperative game) with no stats to be saved. If that happens
  1270. // and we don't reset the state, then the next time we enter a new level
  1271. // it'll save at a bad time.
  1272. MM_XWriteOpportunity eXWO = GetXWriteOpportunity( m_iController );
  1273. #endif
  1274. //
  1275. // Determine if XWrite is required first
  1276. //
  1277. DWORD numXWritesRequired = 0;
  1278. for ( int iData = 0; iData < ( IsX360() ? TITLE_DATA_COUNT_X360 : TITLE_DATA_COUNT ); ++ iData )
  1279. {
  1280. if ( !m_bSaveTitleData[iData] )
  1281. continue;
  1282. ++ numXWritesRequired;
  1283. }
  1284. if ( !numXWritesRequired )
  1285. // early out if nothing to do here
  1286. return;
  1287. #ifdef _X360
  1288. if ( m_eLoadedTitleData < GetAssumedSigninState() )
  1289. {
  1290. // haven't loaded data for the state
  1291. return;
  1292. }
  1293. if ( !IsTitleDataValid() )
  1294. return;
  1295. bool bCanXWrite = true;
  1296. #if !defined (CSTRIKE15 )
  1297. //
  1298. // Check if we can actually XWrite (TCR 136)
  1299. //
  1300. static const float s_flXWritesFreq = 5 * 60 + 1; // 5 minutes
  1301. if ( Plat_FloatTime() - m_flLastSave < s_flXWritesFreq )
  1302. bCanXWrite = false;
  1303. switch ( eXWO )
  1304. {
  1305. default:
  1306. case MMXWO_NONE:
  1307. bCanXWrite = false;
  1308. break;
  1309. case MMXWO_CHECKPOINT:
  1310. break;
  1311. case MMXWO_SETTINGS:
  1312. case MMXWO_SESSION_FINISHED:
  1313. bCanXWrite = true;
  1314. break;
  1315. }
  1316. #else
  1317. // Cstrike only writes to user profile; writes are <500ms; earlier code ensures we only
  1318. // write to profile at most every 3 seconds so we are TCR compliant
  1319. // Cstrike needs a waiver for every 5 minute writes since we save stats at end of round
  1320. // so we can ignore 5 minute timer check;
  1321. // If we get to this code for any WriteOpportunity we are ok to write
  1322. if ( eXWO == MMXWO_NONE )
  1323. {
  1324. bCanXWrite = false;
  1325. }
  1326. #endif
  1327. if ( !bCanXWrite )
  1328. {
  1329. // have to wait longer
  1330. return;
  1331. }
  1332. //
  1333. // Prepare the XWrite batch
  1334. //
  1335. float flTimeStart;
  1336. flTimeStart = Plat_FloatTime();
  1337. Msg( "Player %d : WriteTitleData initiated...\n", m_iController );
  1338. CUtlVector< XUSER_PROFILE_SETTING > pXPS;
  1339. DWORD dwNumDataBufferBytes = TITLE_DATA_COUNT_X360 * XPROFILE_SETTING_MAX_SIZE;
  1340. CArrayAutoPtr< char > spDataBuffer( new char[ dwNumDataBufferBytes ] );
  1341. V_memset( spDataBuffer.Get(), 0, dwNumDataBufferBytes );
  1342. for ( int iData = 0; iData < TITLE_DATA_COUNT_X360; ++ iData )
  1343. {
  1344. if ( !m_bSaveTitleData[iData] )
  1345. continue;
  1346. Msg( "Player %d : WriteTitleData preparing TitleData%d...\n", m_iController, iData + 1 );
  1347. XUSER_PROFILE_SETTING xps;
  1348. V_memset( &xps, 0, sizeof( xps ) );
  1349. xps.dwSettingId = GetTitleSpecificDataId( iData );
  1350. xps.data.type = XUSER_DATA_TYPE_BINARY;
  1351. xps.data.binary.cbData = XPROFILE_SETTING_MAX_SIZE;
  1352. xps.data.binary.pbData = (PBYTE) spDataBuffer.Get() + iData * XPROFILE_SETTING_MAX_SIZE;
  1353. V_memcpy( xps.data.binary.pbData, m_bufTitleData[ iData ], XPROFILE_SETTING_MAX_SIZE );
  1354. pXPS.AddToTail( xps );
  1355. }
  1356. // Clear dirty state
  1357. V_memset( m_bSaveTitleData, 0, sizeof( m_bSaveTitleData[0] ) * TITLE_DATA_COUNT_X360 );
  1358. //
  1359. // Issue the XWrite operation
  1360. //
  1361. DWORD ret;
  1362. m_flLastSave = Plat_FloatTime();
  1363. ret = g_pMatchExtensions->GetIXOnline()->XUserWriteProfileSettings( m_iController, pXPS.Count(), pXPS.Base(), NULL );
  1364. if ( ret != ERROR_SUCCESS )
  1365. {
  1366. Warning( "Player %d : WriteTitleData failed (%.3f sec), err=0x%08X\n",
  1367. m_iController, Plat_FloatTime() - flTimeStart, ret );
  1368. g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnProfileDataWriteFailed", "iController", m_iController ) );
  1369. g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnProfileUnavailable", "iController", m_iController ) );
  1370. }
  1371. else
  1372. {
  1373. Msg( "Player %d : WriteTitleData finished (%.3f sec).\n",
  1374. m_iController, Plat_FloatTime() - flTimeStart );
  1375. }
  1376. #elif !defined( NO_STEAM )
  1377. //
  1378. // Steam stats have been written earlier
  1379. // Clear dirty state
  1380. //
  1381. V_memset( m_bSaveTitleData, 0, sizeof( m_bSaveTitleData ) );
  1382. g_pPlayerManager->RequestStoreStats();
  1383. GetXWriteOpportunity( m_iController ); // clear XWrite opportunity
  1384. DevMsg( "User stats written.\n" );
  1385. #endif
  1386. g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnProfileDataSaved", "iController", m_iController ) );
  1387. }
  1388. void PlayerLocal::Update()
  1389. {
  1390. #ifdef _X360
  1391. // When we are playing as guest, no updates
  1392. if ( !m_xuid )
  1393. return;
  1394. UpdatePendingAwardsState();
  1395. #endif
  1396. // Load title data if not loaded yet
  1397. LoadTitleData();
  1398. // Save title data if time has come to do so
  1399. WriteTitleData();
  1400. #ifndef NO_STEAM
  1401. // Re-request only if we got our callback with an error code.
  1402. if ( s_flSteamStatsRequestTime && ( Plat_FloatTime() - s_flSteamStatsRequestTime > 10.0 ) && s_bSteamStatsRequestFailed )
  1403. {
  1404. DevWarning( "=========== Failed to retrieve Steam stats (%2.2f sec) =================\n", Plat_FloatTime() - s_flSteamStatsRequestTime );
  1405. steamapicontext->SteamUserStats()->RequestCurrentStats();
  1406. s_flSteamStatsRequestTime = Plat_FloatTime();
  1407. }
  1408. #endif
  1409. }
  1410. void PlayerLocal::Destroy()
  1411. {
  1412. m_iController = XUSER_INDEX_NONE;
  1413. m_xuid = 0;
  1414. m_eOnlineState = STATE_OFFLINE;
  1415. #ifdef _X360
  1416. #elif !defined( NO_STEAM )
  1417. m_CallbackOnUserStatsReceived.Unregister();
  1418. m_CallbackOnPersonaStateChange.Unregister();
  1419. #endif
  1420. delete this;
  1421. }
  1422. void PlayerLocal::RecomputeXUID( char const *szNetwork )
  1423. {
  1424. if ( !m_xuid )
  1425. return;
  1426. #ifdef _X360
  1427. DWORD dwFlagSignin = XUSER_GET_SIGNIN_INFO_OFFLINE_XUID_ONLY;
  1428. if ( !Q_stricmp( "LIVE", szNetwork ) )
  1429. dwFlagSignin = XUSER_GET_SIGNIN_INFO_ONLINE_XUID_ONLY;
  1430. XUSER_SIGNIN_INFO xsi;
  1431. if ( ERROR_SUCCESS != XUserGetSigninInfo( m_iController, dwFlagSignin, &xsi ) ||
  1432. !xsi.xuid )
  1433. {
  1434. if ( ERROR_SUCCESS != XUserGetXUID( m_iController, &xsi.xuid ) )
  1435. {
  1436. DevWarning( "Player::RecomputeXUID failed! Leaving ctrlr%d as %llx.\n", m_iController, m_xuid );
  1437. }
  1438. else
  1439. {
  1440. m_xuid = xsi.xuid;
  1441. }
  1442. }
  1443. else
  1444. {
  1445. m_xuid = xsi.xuid;
  1446. }
  1447. #endif
  1448. }
  1449. void PlayerLocal::DetectOnlineState()
  1450. {
  1451. #ifdef _X360
  1452. OnlineState_t eOnlineState = IPlayer::STATE_OFFLINE;
  1453. if ( !XBX_GetPrimaryUserIsGuest() )
  1454. {
  1455. if ( XUserGetSigninState( m_iController ) == eXUserSigninState_SignedInToLive )
  1456. {
  1457. eOnlineState = IPlayer::STATE_NO_MULTIPLAYER;
  1458. BOOL bValue = false;
  1459. if ( ERROR_SUCCESS == XUserCheckPrivilege( m_iController, XPRIVILEGE_MULTIPLAYER_SESSIONS, &bValue ) && bValue )
  1460. eOnlineState = IPlayer::STATE_ONLINE;
  1461. }
  1462. }
  1463. m_eOnlineState = eOnlineState;
  1464. #endif
  1465. }
  1466. const UserProfileData & PlayerLocal::GetPlayerProfileData()
  1467. {
  1468. return m_ProfileData;
  1469. }
  1470. void PlayerLocal::LoadPlayerProfileData()
  1471. {
  1472. #ifdef _X360
  1473. // These are the values we're interested in having returned (must match the indices above)
  1474. const DWORD dwSettingIds[] =
  1475. {
  1476. XPROFILE_GAMERCARD_REP,
  1477. XPROFILE_GAMER_DIFFICULTY,
  1478. XPROFILE_GAMER_CONTROL_SENSITIVITY,
  1479. XPROFILE_GAMER_YAXIS_INVERSION,
  1480. XPROFILE_OPTION_CONTROLLER_VIBRATION,
  1481. XPROFILE_GAMER_PREFERRED_COLOR_FIRST,
  1482. XPROFILE_GAMER_PREFERRED_COLOR_SECOND,
  1483. XPROFILE_GAMER_ACTION_AUTO_AIM,
  1484. XPROFILE_GAMER_ACTION_AUTO_CENTER,
  1485. XPROFILE_GAMER_ACTION_MOVEMENT_CONTROL,
  1486. XPROFILE_GAMERCARD_REGION,
  1487. XPROFILE_GAMERCARD_ACHIEVEMENTS_EARNED ,
  1488. XPROFILE_GAMERCARD_CRED,
  1489. XPROFILE_GAMERCARD_ZONE,
  1490. XPROFILE_GAMERCARD_TITLES_PLAYED,
  1491. XPROFILE_GAMERCARD_TITLE_ACHIEVEMENTS_EARNED,
  1492. XPROFILE_GAMERCARD_TITLE_CRED_EARNED,
  1493. // [jason] For debugging voice settings only
  1494. XPROFILE_OPTION_VOICE_MUTED,
  1495. XPROFILE_OPTION_VOICE_THRU_SPEAKERS,
  1496. XPROFILE_OPTION_VOICE_VOLUME
  1497. };
  1498. enum { NUM_PROFILE_SETTINGS = ARRAYSIZE( dwSettingIds ) };
  1499. // First, we call with a NULL pointer and zero size to retrieve the buffer size we'll get back
  1500. DWORD dwResultSize = 0; // Must be zero to get the correct size back
  1501. XUSER_READ_PROFILE_SETTING_RESULT *pResults = NULL;
  1502. DWORD dwError = g_pMatchExtensions->GetIXOnline()->XUserReadProfileSettings( 0, // Family ID (current title)
  1503. m_iController,
  1504. NUM_PROFILE_SETTINGS,
  1505. dwSettingIds,
  1506. &dwResultSize,
  1507. pResults,
  1508. NULL );
  1509. // We need this to inform us that it's given us a size back for the buffer
  1510. if ( dwError != ERROR_INSUFFICIENT_BUFFER )
  1511. {
  1512. Warning( "Player %d : LoadPlayerProfileData failed to get size (err=0x%08X)!\n", m_iController, dwError );
  1513. return;
  1514. }
  1515. // Now we allocate that buffer and supply it to the call
  1516. BYTE *pData = (BYTE *) stackalloc( dwResultSize );
  1517. ZeroMemory( pData, dwResultSize );
  1518. pResults = (XUSER_READ_PROFILE_SETTING_RESULT *) pData;
  1519. dwError = g_pMatchExtensions->GetIXOnline()->XUserReadProfileSettings( 0, // Family ID (current title)
  1520. m_iController,
  1521. NUM_PROFILE_SETTINGS,
  1522. dwSettingIds,
  1523. &dwResultSize,
  1524. pResults,
  1525. NULL ); // Not overlapped, must be synchronous
  1526. // We now have a raw buffer of results
  1527. if ( dwError != ERROR_SUCCESS )
  1528. {
  1529. Warning( "Player %d : LoadTitleData failed to get data (err=0x%08X)!\n", m_iController, dwError );
  1530. return;
  1531. }
  1532. for ( DWORD k = 0; k < pResults->dwSettingsLen; ++ k )
  1533. {
  1534. XUSER_PROFILE_SETTING const &xps = pResults->pSettings[k];
  1535. switch ( xps.dwSettingId )
  1536. {
  1537. case XPROFILE_GAMERCARD_REP:
  1538. m_ProfileData.reputation = xps.data.fData;
  1539. break;
  1540. case XPROFILE_GAMER_DIFFICULTY:
  1541. m_ProfileData.difficulty = xps.data.nData;
  1542. break;
  1543. case XPROFILE_GAMER_CONTROL_SENSITIVITY:
  1544. m_ProfileData.sensitivity = xps.data.nData;
  1545. break;
  1546. case XPROFILE_GAMER_YAXIS_INVERSION:
  1547. m_ProfileData.yaxis = xps.data.nData;
  1548. break;
  1549. case XPROFILE_OPTION_CONTROLLER_VIBRATION:
  1550. m_ProfileData.vibration = xps.data.nData;
  1551. break;
  1552. case XPROFILE_GAMER_PREFERRED_COLOR_FIRST:
  1553. m_ProfileData.color1 = xps.data.nData;
  1554. break;
  1555. case XPROFILE_GAMER_PREFERRED_COLOR_SECOND:
  1556. m_ProfileData.color2 = xps.data.nData;
  1557. break;
  1558. case XPROFILE_GAMER_ACTION_AUTO_AIM:
  1559. m_ProfileData.action_autoaim = xps.data.nData;
  1560. break;
  1561. case XPROFILE_GAMER_ACTION_AUTO_CENTER:
  1562. m_ProfileData.action_autocenter = xps.data.nData;
  1563. break;
  1564. case XPROFILE_GAMER_ACTION_MOVEMENT_CONTROL:
  1565. m_ProfileData.action_movementcontrol = xps.data.nData;
  1566. break;
  1567. case XPROFILE_GAMERCARD_REGION:
  1568. m_ProfileData.region = xps.data.nData;
  1569. break;
  1570. case XPROFILE_GAMERCARD_ACHIEVEMENTS_EARNED:
  1571. m_ProfileData.achearned = xps.data.nData;
  1572. break;
  1573. case XPROFILE_GAMERCARD_CRED:
  1574. m_ProfileData.cred = xps.data.nData;
  1575. break;
  1576. case XPROFILE_GAMERCARD_ZONE:
  1577. m_ProfileData.zone = xps.data.nData;
  1578. break;
  1579. case XPROFILE_GAMERCARD_TITLES_PLAYED:
  1580. m_ProfileData.titlesplayed = xps.data.nData;
  1581. break;
  1582. case XPROFILE_GAMERCARD_TITLE_ACHIEVEMENTS_EARNED:
  1583. m_ProfileData.titleachearned = xps.data.nData;
  1584. break;
  1585. case XPROFILE_GAMERCARD_TITLE_CRED_EARNED:
  1586. m_ProfileData.titlecred = xps.data.nData;
  1587. break;
  1588. // [jason] For debugging voice settings only:
  1589. case XPROFILE_OPTION_VOICE_MUTED:
  1590. DevMsg( "Player %d : XPROFILE_OPTION_VOICE_MUTED setting: %d\n", m_iController, xps.data.nData );
  1591. break;
  1592. case XPROFILE_OPTION_VOICE_THRU_SPEAKERS:
  1593. DevMsg( "Player %d : XPROFILE_OPTION_VOICE_THRU_SPEAKERS setting: %d\n", m_iController, xps.data.nData );
  1594. break;
  1595. case XPROFILE_OPTION_VOICE_VOLUME:
  1596. DevMsg( "Player %d : XPROFILE_OPTION_VOICE_VOLUME setting: %d\n", m_iController, xps.data.nData );
  1597. break;
  1598. }
  1599. }
  1600. #endif
  1601. #ifdef _PS3
  1602. m_ProfileData.vibration = 3; // vibration enabled on PS3
  1603. #endif
  1604. DevMsg( "Player %d : LoadPlayerProfileData finished\n", m_iController );
  1605. }
  1606. MatchmakingData* PlayerLocal::GetPlayerMatchmakingData( void )
  1607. {
  1608. return &m_MatchmakingData;
  1609. }
  1610. void PlayerLocal::UpdatePlayerMatchmakingData( int mmDataType )
  1611. {
  1612. #if defined ( _X360 )
  1613. if ( mmDataType < 0 || mmDataType >= MMDATA_TYPE_COUNT )
  1614. {
  1615. DevMsg( "Invalid matchmaking data type passed to UpdatePlayerMatchmakingData ( %d )", mmDataType );
  1616. return;
  1617. }
  1618. DevMsg( "Player::UpdatePlayerMatchmakingData( ctrlr%d; mmDataType%d )\n", GetPlayerIndex(), mmDataType );
  1619. // Now obtain the avg calculator for the difficulty
  1620. // Calculator operates on "avgValue" and "newValue"
  1621. CExpressionCalculator calc( g_pMMF->GetMatchTitleGameSettingsMgr()->GetFormulaAverage( mmDataType ) );
  1622. char const *szAvg = "avgValue";
  1623. char const *szNew = "newValue";
  1624. float flResult;
  1625. //
  1626. // Average up our data
  1627. //
  1628. #define CALC_AVG( field ) \
  1629. calc.SetVariable( szAvg, m_MatchmakingData.field[mmDataType][MMDATA_SCOPE_LIFETIME] ); \
  1630. calc.SetVariable( szNew, m_MatchmakingData.field[mmDataType][MMDATA_SCOPE_ROUND] * MM_AVG_CONST ); \
  1631. if ( calc.Evaluate( flResult ) ) \
  1632. m_MatchmakingData.field[mmDataType][MMDATA_SCOPE_LIFETIME] = flResult;
  1633. CALC_AVG( mContribution );
  1634. CALC_AVG( mMVPs );
  1635. CALC_AVG( mKills );
  1636. CALC_AVG( mDeaths );
  1637. CALC_AVG( mHeadShots );
  1638. CALC_AVG( mDamage );
  1639. CALC_AVG( mShotsFired );
  1640. CALC_AVG( mShotsHit );
  1641. CALC_AVG( mDominations );
  1642. // Average rounds played makes no sense since the average is a per round average; increment both the average and running totals
  1643. m_MatchmakingData.mRoundsPlayed[mmDataType][MMDATA_SCOPE_LIFETIME] += 1;
  1644. m_MatchmakingData.mRoundsPlayed[mmDataType][MMDATA_SCOPE_ROUND] += 1;
  1645. #undef CALC_AVG
  1646. #endif // #if defined ( _X360 )
  1647. }
  1648. void PlayerLocal::ResetPlayerMatchmakingData( int mmDataScope )
  1649. {
  1650. #if defined ( _X360 )
  1651. if ( mmDataScope < 0 || mmDataScope >= MMDATA_SCOPE_COUNT )
  1652. {
  1653. DevMsg( "Invalid matchmaking data scope passed to ResetPlayerMatchmakingData ( %d )", mmDataScope );
  1654. return;
  1655. }
  1656. DevMsg( "Player::ResetPlayerMatchmakingData( ctrlr%d; mmDataScope%d )\n", GetPlayerIndex(), mmDataScope );
  1657. ConVarRef score_default( "score_default" );
  1658. for ( int i=0; i<MMDATA_TYPE_COUNT; ++i )
  1659. {
  1660. m_MatchmakingData.mContribution[i][mmDataScope] = score_default.GetInt();
  1661. m_MatchmakingData.mMVPs[i][mmDataScope] = 0;
  1662. m_MatchmakingData.mKills[i][mmDataScope] = 0;
  1663. m_MatchmakingData.mDeaths[i][mmDataScope] = 0;
  1664. m_MatchmakingData.mHeadShots[i][mmDataScope] = 0;
  1665. m_MatchmakingData.mDamage[i][mmDataScope] = 0;
  1666. m_MatchmakingData.mShotsFired[i][mmDataScope] = 0;
  1667. m_MatchmakingData.mShotsHit[i][mmDataScope] = 0;
  1668. m_MatchmakingData.mDominations[i][mmDataScope] = 0;
  1669. m_MatchmakingData.mRoundsPlayed[i][mmDataScope] = 0;
  1670. }
  1671. #endif // #if defined ( _X360 )
  1672. }
  1673. const void * PlayerLocal::GetPlayerTitleData( int iTitleDataIndex )
  1674. {
  1675. if ( iTitleDataIndex >= 0 && iTitleDataIndex < TITLE_DATA_COUNT )
  1676. {
  1677. return m_bufTitleData[ iTitleDataIndex ];
  1678. }
  1679. else
  1680. {
  1681. return NULL;
  1682. }
  1683. }
  1684. void PlayerLocal::UpdatePlayerTitleData( TitleDataFieldsDescription_t const *fdKey, const void *pvNewTitleData, int numNewBytes )
  1685. {
  1686. if ( !fdKey || (fdKey->m_eDataType == fdKey->DT_0) || !pvNewTitleData || (numNewBytes <= 0) || !fdKey->m_szFieldName )
  1687. return;
  1688. Assert( TITLE_DATA_COUNT == TitleDataFieldsDescription_t::DB_TD_COUNT );
  1689. if ( fdKey->m_iTitleDataBlock < 0 || fdKey->m_iTitleDataBlock >= TitleDataFieldsDescription_t::DB_TD_COUNT )
  1690. return;
  1691. #if !defined( NO_STEAM ) && !defined( _CERT )
  1692. if ( steamapicontext->SteamUtils() && steamapicontext->SteamUtils()->GetConnectedUniverse() == k_EUniverseBeta )
  1693. #endif
  1694. {
  1695. #if ( defined( _GAMECONSOLE ) && !defined( _CERT ) )
  1696. // Validate that the caller is not forging the field description
  1697. bool bKeyForged = true;
  1698. for ( TitleDataFieldsDescription_t const *fdCheck = g_pMatchFramework->GetMatchTitle()->DescribeTitleDataStorage();
  1699. fdCheck && fdCheck->m_szFieldName; ++ fdCheck )
  1700. {
  1701. if ( fdCheck == fdKey )
  1702. {
  1703. bKeyForged = false;
  1704. break;
  1705. }
  1706. }
  1707. if ( bKeyForged )
  1708. {
  1709. DevWarning( "PlayerLocal::UpdatePlayerTitleData( %s ) with invalid key!\n", fdKey->m_szFieldName );
  1710. Assert( 0 );
  1711. return;
  1712. }
  1713. #endif
  1714. }
  1715. // Validate data size
  1716. if ( fdKey->m_eDataType/8 != numNewBytes )
  1717. {
  1718. DevWarning( "PlayerLocal::UpdatePlayerTitleData( %s ) new data size %d != description size %d!\n", fdKey->m_szFieldName, numNewBytes, fdKey->m_eDataType/8 );
  1719. Assert( 0 );
  1720. return;
  1721. }
  1722. // Debug output for the write attempt
  1723. switch ( fdKey->m_eDataType )
  1724. {
  1725. case TitleDataFieldsDescription_t::DT_uint8:
  1726. case TitleDataFieldsDescription_t::DT_BITFIELD:
  1727. // DevMsg( "PlayerLocal(%s)::UpdatePlayerTitleData: %s = %d (0x%02X)\n", GetName(), fdKey->m_szFieldName, *( uint8 const * ) pvNewTitleData, *( uint8 const * ) pvNewTitleData );
  1728. break;
  1729. case TitleDataFieldsDescription_t::DT_uint16:
  1730. // DevMsg( "PlayerLocal(%s)::UpdatePlayerTitleData: %s = %d (0x%04X)\n", GetName(), fdKey->m_szFieldName, *( uint16 const * ) pvNewTitleData, *( uint16 const * ) pvNewTitleData );
  1731. break;
  1732. case TitleDataFieldsDescription_t::DT_uint32:
  1733. // DevMsg( "PlayerLocal(%s)::UpdatePlayerTitleData: %s = %d (0x%08X)\n", GetName(), fdKey->m_szFieldName, *( uint32 const * ) pvNewTitleData, *( uint32 const * ) pvNewTitleData );
  1734. break;
  1735. case TitleDataFieldsDescription_t::DT_uint64:
  1736. // DevMsg( "PlayerLocal(%s)::UpdatePlayerTitleData: %s = %llu (0x%llX)\n", GetName(), fdKey->m_szFieldName, *( uint64 const * ) pvNewTitleData, *( uint64 const * ) pvNewTitleData );
  1737. break;
  1738. case TitleDataFieldsDescription_t::DT_float:
  1739. // DevMsg( "PlayerLocal(%s)::UpdatePlayerTitleData: %s = %f (%0.2f)\n", GetName(), fdKey->m_szFieldName, *( float const * ) pvNewTitleData, *( float const * ) pvNewTitleData );
  1740. break;
  1741. }
  1742. // Check if the title allows the requested stat update
  1743. KeyValues *kvTitleRequest = new KeyValues( "stat" );
  1744. KeyValues::AutoDelete autodelete_kvTitleRequest( kvTitleRequest );
  1745. kvTitleRequest->SetString( "name", fdKey->m_szFieldName );
  1746. if ( !g_pMMF->GetMatchTitleGameSettingsMgr()->AllowClientProfileUpdate( kvTitleRequest ) )
  1747. return;
  1748. //
  1749. // Copy off the old data and check that the new data is different
  1750. // At the same time update internal buffers with new data
  1751. //
  1752. uint8 *pvData = ( uint8 * ) m_bufTitleData[ fdKey->m_iTitleDataBlock ];
  1753. switch ( fdKey->m_eDataType )
  1754. {
  1755. case TitleDataFieldsDescription_t::DT_BITFIELD:
  1756. {
  1757. uint8 *pvOldData = ( uint8 * ) stackalloc( numNewBytes );
  1758. uint8 bBitValue = !!*( ( uint8 const * ) pvNewTitleData );
  1759. uint8 bBitMask = ( 1 << (fdKey->m_numBytesOffset%8) );
  1760. memcpy( pvOldData, pvData + fdKey->m_numBytesOffset/8, numNewBytes );
  1761. if ( !!( bBitMask & pvOldData[0] ) == bBitValue )
  1762. return;
  1763. if ( bBitValue )
  1764. pvOldData[0] |= bBitMask;
  1765. else
  1766. pvOldData[0] &=~bBitMask;
  1767. memcpy( pvData + fdKey->m_numBytesOffset/8, pvOldData, numNewBytes );
  1768. }
  1769. break;
  1770. default:
  1771. if ( !memcmp( pvData + fdKey->m_numBytesOffset, pvNewTitleData, numNewBytes ) )
  1772. return;
  1773. memcpy( pvData + fdKey->m_numBytesOffset, pvNewTitleData, numNewBytes );
  1774. break;
  1775. }
  1776. // Check our "guest" status
  1777. #ifdef _GAMECONSOLE
  1778. bool bRegisteredPlayer = false;
  1779. for ( int k = 0; k < XBX_GetNumGameUsers(); ++ k )
  1780. {
  1781. if ( XBX_GetUserId( k ) == m_iController )
  1782. {
  1783. if ( XBX_GetUserIsGuest( k ) )
  1784. {
  1785. DevMsg( "pPlayerLocal(%s)->UpdatePlayerTitleData not saving for guests.\n", GetName() );
  1786. return;
  1787. }
  1788. bRegisteredPlayer = true;
  1789. break;
  1790. }
  1791. }
  1792. if ( !bRegisteredPlayer )
  1793. {
  1794. DevMsg( "pPlayerLocal(%s)->UpdatePlayerTitleData not saving for not participating gamers.\n", GetName() );
  1795. Assert( 0 ); // title code shouldn't be calling UpdateAwardsData for players not in active gameplay, title bug?
  1796. return;
  1797. }
  1798. #endif
  1799. // Mark stats to be stored at next available opportunity
  1800. m_bSaveTitleData[ fdKey->m_iTitleDataBlock ] = true;
  1801. #ifndef NO_STEAM
  1802. // On Steam we can freely upload stats rights now
  1803. //
  1804. // Prepare the data
  1805. //
  1806. switch( fdKey->m_eDataType )
  1807. {
  1808. case TitleDataFieldsDescription_t::DT_uint8:
  1809. case TitleDataFieldsDescription_t::DT_uint16:
  1810. case TitleDataFieldsDescription_t::DT_uint32:
  1811. {
  1812. uint32 i32field[4] = {0};
  1813. memcpy( &i32field[3],
  1814. &m_bufTitleData[ fdKey->m_iTitleDataBlock ][ fdKey->m_numBytesOffset ],
  1815. fdKey->m_eDataType / 8 );
  1816. i32field[0] = *( uint32 * )( &i32field[3] );
  1817. i32field[1] = *( uint16 * )( &i32field[3] );
  1818. i32field[2] = *( uint8 * )( &i32field[3] );
  1819. SetSteamStatWithPotentialOverride( fdKey->m_szFieldName, ( int32 ) i32field[ 2 - ( fdKey->m_eDataType / 16 ) ] );
  1820. }
  1821. break;
  1822. case TitleDataFieldsDescription_t::DT_float:
  1823. {
  1824. float flField = 0.0f;
  1825. memcpy( &flField,
  1826. &m_bufTitleData[ fdKey->m_iTitleDataBlock ][ fdKey->m_numBytesOffset ],
  1827. fdKey->m_eDataType / 8 );
  1828. SetSteamStatWithPotentialOverride( fdKey->m_szFieldName, flField );
  1829. }
  1830. break;
  1831. case TitleDataFieldsDescription_t::DT_uint64:
  1832. {
  1833. uint32 i32field[2] = { 0 };
  1834. memcpy( &i32field[0],
  1835. &m_bufTitleData[ fdKey->m_iTitleDataBlock ][ fdKey->m_numBytesOffset ],
  1836. fdKey->m_eDataType / 8 );
  1837. char chBuffer[ 256 ] = {0};
  1838. for ( int k = 0; k < ARRAYSIZE( i32field ); ++ k )
  1839. {
  1840. Q_snprintf( chBuffer, ARRAYSIZE( chBuffer ), "%s.%d", fdKey->m_szFieldName, k );
  1841. SetSteamStatWithPotentialOverride( chBuffer, ( int32 ) i32field[k] );
  1842. }
  1843. }
  1844. break;
  1845. case TitleDataFieldsDescription_t::DT_BITFIELD:
  1846. {
  1847. #ifdef STEAM_PACK_BITFIELDS
  1848. char chStatField[64] = {0};
  1849. uint32 uiOffsetInTermsOfUINT32 = fdKey->m_numBytesOffset/32;
  1850. V_snprintf( chStatField, sizeof( chStatField ), "bitfield_%02u_%03X", fdKey->m_iTitleDataBlock + 1, uiOffsetInTermsOfUINT32*4 );
  1851. int32 iCombinedBitValue = ( reinterpret_cast< uint32 * >( &m_bufTitleData[fdKey->m_iTitleDataBlock][0] ) )[ uiOffsetInTermsOfUINT32 ];
  1852. SetSteamStatWithPotentialOverride( chStatField, iCombinedBitValue );
  1853. #else
  1854. int32 i32field = !!(
  1855. m_bufTitleData[ fdKey->m_iTitleDataBlock ][ fdKey->m_numBytesOffset/8 ]
  1856. & ( 1 << ( fdKey->m_numBytesOffset % 8 ) ) );
  1857. SetSteamStatWithPotentialOverride( fdKey->m_szFieldName, i32field );
  1858. #endif
  1859. }
  1860. break;
  1861. }
  1862. #endif // NO_STEAM
  1863. // If a component achievement was affected, evaluate its state
  1864. int numFieldNameChars = Q_strlen( fdKey->m_szFieldName );
  1865. if ( ( numFieldNameChars > 0 ) && ( fdKey->m_szFieldName[numFieldNameChars - 1] == ']' ) )
  1866. {
  1867. EvaluateAwardsStateBasedOnStats();
  1868. }
  1869. }
  1870. void PlayerLocal::GetLeaderboardData( KeyValues *pLeaderboardInfo )
  1871. {
  1872. // Iterate over all views specified
  1873. for ( KeyValues *pView = pLeaderboardInfo->GetFirstTrueSubKey(); pView; pView = pView->GetNextTrueSubKey() )
  1874. {
  1875. char const *szViewName = pView->GetName();
  1876. KeyValues *pCurrentData = m_pLeaderboardData->FindKey( szViewName );
  1877. // If no such data yet or refresh was requested, then queue this request
  1878. if ( pView->GetInt( ":refresh" ) || !pCurrentData )
  1879. {
  1880. if ( g_pLeaderboardRequestQueue )
  1881. g_pLeaderboardRequestQueue->Request( pView );
  1882. }
  1883. // If we have data, then fill in the values
  1884. pView->MergeFrom( pCurrentData, KeyValues::MERGE_KV_BORROW );
  1885. }
  1886. }
  1887. void PlayerLocal::UpdateLeaderboardData( KeyValues *pLeaderboardInfo )
  1888. {
  1889. DevMsg( "PlayerLocal::UpdateLeaderboardData for %s ...\n", GetName() );
  1890. #ifdef _X360
  1891. IX360LeaderboardBatchWriter *pLbWriter = MMX360_CreateLeaderboardBatchWriter( m_xuid );
  1892. #endif
  1893. // Iterate over all views specified
  1894. for ( KeyValues *pView = pLeaderboardInfo->GetFirstTrueSubKey(); pView; pView = pView->GetNextTrueSubKey() )
  1895. {
  1896. char const *szViewName = pView->GetName();
  1897. // Check if the title allows the requested leaderboard update
  1898. KeyValues *kvTitleRequest = new KeyValues( "leaderboard" );
  1899. KeyValues::AutoDelete autodelete_kvTitleRequest( kvTitleRequest );
  1900. kvTitleRequest->SetString( "name", szViewName );
  1901. if ( !g_pMMF->GetMatchTitleGameSettingsMgr()->AllowClientProfileUpdate( kvTitleRequest ) )
  1902. continue;
  1903. // Find leaderboard description
  1904. KeyValues *pDescription = g_pMMF->GetMatchTitle()->DescribeTitleLeaderboard( szViewName );
  1905. if ( !pDescription )
  1906. {
  1907. DevWarning( " View %s failed to allocate description!\n", szViewName );
  1908. }
  1909. KeyValues::AutoDelete autodelete_pDescription( pDescription );
  1910. KeyValues *pCurrentData = m_pLeaderboardData->FindKey( szViewName );
  1911. if ( !pDescription->GetBool( ":nocache" ) && !pCurrentData )
  1912. {
  1913. pCurrentData = new KeyValues( szViewName );
  1914. m_pLeaderboardData->AddSubKey( pCurrentData );
  1915. }
  1916. // On PC we just issue a write per each request, no batching
  1917. if ( !IsX360() )
  1918. {
  1919. if ( pDescription )
  1920. {
  1921. #if !defined( _X360 ) && !defined( NO_STEAM )
  1922. Steam_WriteLeaderboardData( pDescription, pView );
  1923. #endif
  1924. }
  1925. continue;
  1926. }
  1927. // Check if the rating field passes rule requirement
  1928. if ( KeyValues *pValDesc = pDescription->FindKey( ":rating" ) )
  1929. {
  1930. char const *szValue = pDescription->GetString( ":rating/name" );
  1931. KeyValues *val = pView->FindKey( szValue );
  1932. if ( !val || !*szValue )
  1933. {
  1934. DevWarning( " View %s is rated, but no :rating field '%s' in update!\n", szViewName, szValue );
  1935. continue;
  1936. }
  1937. char const *szRule = pValDesc->GetString( "rule" );
  1938. IPropertyRule *pRuleObj = GetRuleByName( szRule );
  1939. if ( !pRuleObj )
  1940. {
  1941. DevWarning( " View %s is rated, but invalid rule '%s' specified!\n", szViewName, szRule );
  1942. continue;
  1943. }
  1944. uint64 uiValue = val->GetUint64();
  1945. uint64 uiCurrent = pCurrentData->GetUint64( szValue );
  1946. if ( !pRuleObj->ApplyRuleUint64( uiCurrent, uiValue ) )
  1947. {
  1948. DevMsg( " View %s is rated, rejected by rule '%s' ( old %lld, new %lld )!\n", szViewName, szRule, uiCurrent, val->GetUint64() );
  1949. continue;
  1950. }
  1951. DevMsg( " View %s is rated, rule '%s' passed ( old %lld, new %lld )!\n", szViewName, szRule, uiCurrent, uiValue );
  1952. pCurrentData->SetUint64( ":rating", uiValue );
  1953. // ... and proceed setting other contexts and properties
  1954. }
  1955. // Iterate over all values
  1956. for ( KeyValues *val = pView->GetFirstValue(); val; val = val->GetNextValue() )
  1957. {
  1958. char const *szValue = val->GetName();
  1959. if ( szValue[0] == ':' )
  1960. continue; // no update for system fields
  1961. if ( KeyValues *pValDesc = pDescription->FindKey( szValue ) )
  1962. {
  1963. char const *szRule = pValDesc->GetString( "rule" );
  1964. IPropertyRule *pRuleObj = GetRuleByName( szRule );
  1965. if ( !pRuleObj )
  1966. {
  1967. DevWarning( " View %s/%s has invalid rule '%s' specified!\n", szViewName, szValue, szRule );
  1968. continue;
  1969. }
  1970. char const *szType = pValDesc->GetString( "type" );
  1971. if ( !Q_stricmp( "uint64", szType ) )
  1972. {
  1973. uint64 uiValue = val->GetUint64();
  1974. uint64 uiCurrent = pCurrentData->GetUint64( szValue );
  1975. // Check if new value passes the rule
  1976. if ( !pRuleObj->ApplyRuleUint64( uiCurrent, uiValue ) )
  1977. {
  1978. DevMsg( " View %s/%s rejected by rule '%s' ( old %lld, new %lld )!\n",
  1979. szViewName, szValue, szRule, uiCurrent, val->GetUint64() );
  1980. continue;
  1981. }
  1982. DevMsg( " View %s/%s updated by rule '%s' ( old %lld, new %lld )!\n",
  1983. szViewName, szValue, szRule, uiCurrent, uiValue );
  1984. pCurrentData->SetUint64( szValue, uiValue );
  1985. #ifdef _X360
  1986. if ( pLbWriter )
  1987. {
  1988. XUSER_PROPERTY xProp = {0};
  1989. xProp.dwPropertyId = pValDesc->GetInt( "prop" );
  1990. xProp.value.type = XUSER_DATA_TYPE_INT64;
  1991. xProp.value.i64Data = uiValue;
  1992. pLbWriter->AddProperty( pDescription->GetInt( ":id" ), xProp );
  1993. }
  1994. #endif
  1995. }
  1996. else if ( !Q_stricmp( "float", szType ) )
  1997. {
  1998. float flValue = val->GetFloat();
  1999. float flCurrent = pCurrentData->GetFloat( szValue );
  2000. // Check if new value passes the rule
  2001. if ( !pRuleObj->ApplyRuleFloat( flCurrent, flValue ) )
  2002. {
  2003. DevMsg( " View %s/%s rejected by rule '%s' ( old %.4f, new %.4f )!\n",
  2004. szViewName, szValue, szRule, flCurrent, val->GetFloat() );
  2005. continue;
  2006. }
  2007. DevMsg( " View %s/%s updated by rule '%s' ( old %.4f, new %.4f )!\n",
  2008. szViewName, szValue, szRule, flCurrent, flValue );
  2009. pCurrentData->SetFloat( szValue, flValue );
  2010. #ifdef _X360
  2011. if ( pLbWriter )
  2012. {
  2013. XUSER_PROPERTY xProp = {0};
  2014. xProp.dwPropertyId = pValDesc->GetInt( "prop" );
  2015. xProp.value.type = XUSER_DATA_TYPE_FLOAT;
  2016. xProp.value.i64Data = flValue;
  2017. pLbWriter->AddProperty( pDescription->GetInt( ":id" ), xProp );
  2018. }
  2019. #endif
  2020. }
  2021. else
  2022. {
  2023. DevWarning( " View %s/%s has invalid type '%s' specified!\n", szViewName, szValue, szType );
  2024. }
  2025. }
  2026. else
  2027. {
  2028. DevWarning( " View %s/%s is missing description!\n", szViewName, szValue );
  2029. }
  2030. }
  2031. }
  2032. //
  2033. // Now submit all accumulated leaderboard writes
  2034. //
  2035. #ifdef _X360
  2036. if ( pLbWriter )
  2037. {
  2038. pLbWriter->WriteBatchAndDestroy();
  2039. pLbWriter = NULL;
  2040. }
  2041. else
  2042. {
  2043. Warning( "PlayerLocal::UpdateLeaderboardData failed to save leaderboard data to writer!\n" );
  2044. Assert( 0 );
  2045. }
  2046. #endif
  2047. DevMsg( "PlayerLocal::UpdateLeaderboardData for %s finished.\n", GetName() );
  2048. }
  2049. void PlayerLocal::OnLeaderboardRequestFinished( KeyValues *pLeaderboardData )
  2050. {
  2051. m_pLeaderboardData->MergeFrom( pLeaderboardData, KeyValues::MERGE_KV_UPDATE );
  2052. KeyValues *kvEvent = new KeyValues( "OnProfileLeaderboardData" );
  2053. kvEvent->SetInt( "iController", m_iController );
  2054. kvEvent->MergeFrom( pLeaderboardData, KeyValues::MERGE_KV_UPDATE );
  2055. g_pMatchEventsSubscription->BroadcastEvent( kvEvent );
  2056. }
  2057. void PlayerLocal::GetAwardsData( KeyValues *pAwardsData )
  2058. {
  2059. for ( KeyValues *kvValue = pAwardsData->GetFirstValue(); kvValue; kvValue = kvValue->GetNextValue() )
  2060. {
  2061. char const *szName = kvValue->GetName();
  2062. // If title is requesting all achievement IDs
  2063. if ( !Q_stricmp( "@achievements", szName ) )
  2064. {
  2065. for ( int k = 0; k < m_arrAchievementsEarned.Count(); ++ k )
  2066. {
  2067. KeyValues *kvAchValue = new KeyValues( "" );
  2068. kvAchValue->SetInt( NULL, m_arrAchievementsEarned[k] );
  2069. kvValue->AddSubKey( kvAchValue );
  2070. }
  2071. continue;
  2072. }
  2073. // If title is requesting all awards IDs
  2074. if ( !Q_stricmp( "@awards", szName ) )
  2075. {
  2076. for ( int k = 0; k < m_arrAvatarAwardsEarned.Count(); ++ k )
  2077. {
  2078. KeyValues *kvAchValue = new KeyValues( "" );
  2079. kvAchValue->SetInt( NULL, m_arrAvatarAwardsEarned[k] );
  2080. kvValue->AddSubKey( kvAchValue );
  2081. }
  2082. continue;
  2083. }
  2084. // Try to find the achievement in the achievement map
  2085. for ( TitleAchievementsDescription_t const *pAchievement = g_pMatchFramework->GetMatchTitle()->DescribeTitleAchievements();
  2086. pAchievement && pAchievement->m_szAchievementName; ++ pAchievement )
  2087. {
  2088. if ( !Q_stricmp( szName, pAchievement->m_szAchievementName ) )
  2089. {
  2090. kvValue->SetInt( "", ( m_arrAchievementsEarned.Find( pAchievement->m_idAchievement ) != m_arrAchievementsEarned.InvalidIndex() ) ? 1 : 0 );
  2091. szName = NULL;
  2092. break;
  2093. }
  2094. }
  2095. if ( !szName )
  2096. continue;
  2097. // Try to find the avatar award in the map
  2098. for ( TitleAvatarAwardsDescription_t const *pAvAward = g_pMatchFramework->GetMatchTitle()->DescribeTitleAvatarAwards();
  2099. pAvAward && pAvAward->m_szAvatarAwardName; ++ pAvAward )
  2100. {
  2101. if ( !Q_stricmp( szName, pAvAward->m_szAvatarAwardName ) )
  2102. {
  2103. kvValue->SetInt( "", ( m_arrAvatarAwardsEarned.Find( pAvAward->m_idAvatarAward ) != m_arrAvatarAwardsEarned.InvalidIndex() ) ? 1 : 0 );
  2104. szName = NULL;
  2105. break;
  2106. }
  2107. }
  2108. if ( !szName )
  2109. continue;
  2110. DevWarning( "pPlayerLocal(%s)->GetAwardsData(%s) UNKNOWN NAME!\n", GetName(), szName );
  2111. }
  2112. }
  2113. void PlayerLocal::UpdateAwardsData( KeyValues *pAwardsData )
  2114. {
  2115. if ( !pAwardsData )
  2116. return;
  2117. #ifdef _X360
  2118. if ( !m_xuid )
  2119. return;
  2120. if ( GetAssumedSigninState() == eXUserSigninState_NotSignedIn )
  2121. return;
  2122. #endif
  2123. // Check our "guest" status
  2124. #ifdef _GAMECONSOLE
  2125. bool bRegisteredPlayer = false;
  2126. for ( int k = 0; k < XBX_GetNumGameUsers(); ++ k )
  2127. {
  2128. if ( XBX_GetUserId( k ) == m_iController )
  2129. {
  2130. if ( XBX_GetUserIsGuest( k ) )
  2131. {
  2132. DevMsg( "pPlayerLocal(%s)->UpdateAwardsData is unavailable for guests.\n", GetName() );
  2133. return;
  2134. }
  2135. bRegisteredPlayer = true;
  2136. break;
  2137. }
  2138. }
  2139. if ( !bRegisteredPlayer )
  2140. {
  2141. DevMsg( "pPlayerLocal(%s)->UpdateAwardsData is unavailable for not participating gamers.\n", GetName() );
  2142. Assert( 0 ); // title code shouldn't be calling UpdateAwardsData for players not in active gameplay, title bug?
  2143. return;
  2144. }
  2145. #endif
  2146. for ( KeyValues *kvValue = pAwardsData->GetFirstValue(); kvValue; kvValue = kvValue->GetNextValue() )
  2147. {
  2148. char const *szName = kvValue->GetName();
  2149. if ( !kvValue->GetInt( "" ) )
  2150. continue;
  2151. // Check if the title allows the requested award
  2152. KeyValues *kvTitleRequest = new KeyValues( "award" );
  2153. KeyValues::AutoDelete autodelete_kvTitleRequest( kvTitleRequest );
  2154. kvTitleRequest->SetString( "name", szName );
  2155. if ( !g_pMMF->GetMatchTitleGameSettingsMgr()->AllowClientProfileUpdate( kvTitleRequest ) )
  2156. continue;
  2157. // Try to find the achievement in the achievement map
  2158. for ( TitleAchievementsDescription_t const *pAchievement = g_pMatchFramework->GetMatchTitle()->DescribeTitleAchievements();
  2159. pAchievement && pAchievement->m_szAchievementName; ++ pAchievement )
  2160. {
  2161. if ( !Q_stricmp( szName, pAchievement->m_szAchievementName ) )
  2162. {
  2163. // Found the achievement to award
  2164. if ( m_arrAchievementsEarned.Find( pAchievement->m_idAchievement ) == m_arrAchievementsEarned.InvalidIndex() )
  2165. {
  2166. #ifdef _X360
  2167. XPendingAsyncAward_t *pAsync = new XPendingAsyncAward_t;
  2168. Q_memset( pAsync, 0, sizeof( XPendingAsyncAward_t ) );
  2169. pAsync->m_flStartTimestamp = Plat_FloatTime();
  2170. pAsync->m_pLocalPlayer = this;
  2171. pAsync->m_eType = XPendingAsyncAward_t::TYPE_ACHIEVEMENT;
  2172. pAsync->m_pAchievementDesc = pAchievement;
  2173. pAsync->m_xAchievement.dwUserIndex = m_iController;
  2174. pAsync->m_xAchievement.dwAchievementId = pAchievement->m_idAchievement;
  2175. DWORD dwErrCode = XUserWriteAchievements( 1, &pAsync->m_xAchievement, &pAsync->m_xOverlapped );
  2176. if ( dwErrCode == ERROR_IO_PENDING )
  2177. {
  2178. DevMsg( "pPlayerLocal(%s)->UpdateAwardsData(%s) initiated async award.\n", GetName(), pAchievement->m_szAchievementName );
  2179. s_arrPendingAsyncAwards.AddToTail( pAsync );
  2180. m_arrAchievementsEarned.AddToTail( pAchievement->m_idAchievement );
  2181. }
  2182. else if ( ( dwErrCode == 1 ) || ( dwErrCode == 0 ) )
  2183. {
  2184. delete pAsync;
  2185. DevMsg( "pPlayerLocal(%s)->UpdateAwardsData(%s) already awarded by system.\n", GetName(), pAchievement->m_szAchievementName );
  2186. m_arrAchievementsEarned.AddToTail( pAchievement->m_idAchievement );
  2187. }
  2188. else
  2189. {
  2190. delete pAsync;
  2191. DevWarning( "pPlayerLocal(%s)->UpdateAwardsData(%s) failed to initiate async award.\n", GetName(), pAchievement->m_szAchievementName );
  2192. g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues(
  2193. "OnProfileUnavailable", "iController", m_iController ) );
  2194. }
  2195. #elif !defined( NO_STEAM )
  2196. bool bSteamResult = steamapicontext->SteamUserStats()->SetAchievement( pAchievement->m_szAchievementName );
  2197. if ( bSteamResult )
  2198. {
  2199. m_bSaveTitleData[0] = true; // signal that stats must be stored
  2200. m_arrAchievementsEarned.AddToTail( pAchievement->m_idAchievement );
  2201. DevMsg( "pPlayerLocal(%s)->UpdateAwardsData(%s) set achievement and stored stats.\n", GetName(), pAchievement->m_szAchievementName );
  2202. KeyValues *kvAwardedEvent = new KeyValues( "OnPlayerAward" );
  2203. kvAwardedEvent->SetInt( "iController", m_iController );
  2204. kvAwardedEvent->SetString( "award", pAchievement->m_szAchievementName );
  2205. g_pMatchEventsSubscription->BroadcastEvent( kvAwardedEvent );
  2206. }
  2207. else
  2208. {
  2209. DevWarning( "pPlayerLocal(%s)->UpdateAwardsData(%s) failed to set in Steam.\n", GetName(), pAchievement->m_szAchievementName );
  2210. }
  2211. #else
  2212. DevMsg( "pPlayerLocal(%s)->UpdateAwardsData(%s) skipped.\n", GetName(), pAchievement->m_szAchievementName );
  2213. #endif
  2214. }
  2215. else
  2216. {
  2217. DevMsg( "pPlayerLocal(%s)->UpdateAwardsData(%s) already earned.\n", GetName(), pAchievement->m_szAchievementName );
  2218. }
  2219. szName = NULL;
  2220. break;
  2221. }
  2222. }
  2223. if ( !szName )
  2224. continue;
  2225. // Try to find the avatar award in the map
  2226. for ( TitleAvatarAwardsDescription_t const *pAvAward = g_pMatchFramework->GetMatchTitle()->DescribeTitleAvatarAwards();
  2227. pAvAward && pAvAward->m_szAvatarAwardName; ++ pAvAward )
  2228. {
  2229. if ( !Q_stricmp( szName, pAvAward->m_szAvatarAwardName ) )
  2230. {
  2231. // Found the avaward to award
  2232. if ( m_arrAvatarAwardsEarned.Find( pAvAward->m_idAvatarAward ) == m_arrAvatarAwardsEarned.InvalidIndex() )
  2233. {
  2234. #ifdef _X360
  2235. XPendingAsyncAward_t *pAsync = new XPendingAsyncAward_t;
  2236. Q_memset( pAsync, 0, sizeof( XPendingAsyncAward_t ) );
  2237. pAsync->m_flStartTimestamp = Plat_FloatTime();
  2238. pAsync->m_pLocalPlayer = this;
  2239. pAsync->m_eType = XPendingAsyncAward_t::TYPE_AVATAR_AWARD;
  2240. pAsync->m_pAvatarAwardDesc = pAvAward;
  2241. pAsync->m_xAvatarAsset.dwUserIndex = m_iController;
  2242. pAsync->m_xAvatarAsset.dwAwardId = pAvAward->m_idAvatarAward;
  2243. DWORD dwErrCode = XUserAwardAvatarAssets( 1, &pAsync->m_xAvatarAsset, &pAsync->m_xOverlapped );
  2244. if ( dwErrCode == ERROR_IO_PENDING )
  2245. {
  2246. DevMsg( "pPlayerLocal(%s)->UpdateAwardsData(%s) initiated async award.\n", GetName(), pAvAward->m_szAvatarAwardName );
  2247. s_arrPendingAsyncAwards.AddToTail( pAsync );
  2248. m_arrAvatarAwardsEarned.AddToTail( pAvAward->m_idAvatarAward );
  2249. }
  2250. else if ( ( dwErrCode == 1 ) || ( dwErrCode == 0 ) )
  2251. {
  2252. delete pAsync;
  2253. DevMsg( "pPlayerLocal(%s)->UpdateAwardsData(%s) already awarded by system.\n", GetName(), pAvAward->m_szAvatarAwardName );
  2254. m_arrAvatarAwardsEarned.AddToTail( pAvAward->m_idAvatarAward );
  2255. if ( TitleDataFieldsDescription_t const *fdBitfield = TitleDataFieldsDescriptionFindByString( g_pMatchFramework->GetMatchTitle()->DescribeTitleDataStorage(), pAvAward->m_szTitleDataBitfieldStatName ) )
  2256. {
  2257. TitleDataFieldsDescriptionSetBit( fdBitfield, this, true );
  2258. }
  2259. }
  2260. else
  2261. {
  2262. delete pAsync;
  2263. DevWarning( "pPlayerLocal(%s)->UpdateAwardsData(%s) failed to initiate async award.\n", GetName(), pAvAward->m_szAvatarAwardName );
  2264. g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues(
  2265. "OnProfileUnavailable", "iController", m_iController ) );
  2266. }
  2267. #else
  2268. DevMsg( "pPlayerLocal(%s)->UpdateAwardsData(%s) skipped.\n", GetName(), pAvAward->m_szAvatarAwardName );
  2269. #endif
  2270. }
  2271. else
  2272. {
  2273. DevMsg( "pPlayerLocal(%s)->UpdateAwardsData(%s) already earned.\n", GetName(), pAvAward->m_szAvatarAwardName );
  2274. }
  2275. szName = NULL;
  2276. break;
  2277. }
  2278. }
  2279. if ( !szName )
  2280. continue;
  2281. DevWarning( "pPlayerLocal(%s)->write_awards(%s) UNKNOWN NAME!\n", GetName(), szName );
  2282. }
  2283. }
  2284. #ifdef _X360
  2285. void PlayerLocal::UpdatePendingAwardsState()
  2286. {
  2287. bool bWarningFired = false;
  2288. for ( int k = 0; k < s_arrPendingAsyncAwards.Count(); ++ k )
  2289. {
  2290. XPendingAsyncAward_t *pPendingAsyncAward = s_arrPendingAsyncAwards[k];
  2291. if ( pPendingAsyncAward->m_pLocalPlayer && ( pPendingAsyncAward->m_pLocalPlayer != this ) )
  2292. continue;
  2293. if ( !XHasOverlappedIoCompleted( &pPendingAsyncAward->m_xOverlapped ) )
  2294. continue;
  2295. DWORD result = 0;
  2296. DWORD dwXresult = XGetOverlappedResult( &pPendingAsyncAward->m_xOverlapped, &result, false );
  2297. bool bSuccessfullyEarned = ( dwXresult == ERROR_SUCCESS );
  2298. if ( this == pPendingAsyncAward->m_pLocalPlayer )
  2299. {
  2300. if ( !bSuccessfullyEarned )
  2301. {
  2302. switch ( pPendingAsyncAward->m_eType )
  2303. {
  2304. case XPendingAsyncAward_t::TYPE_ACHIEVEMENT:
  2305. m_arrAchievementsEarned.FindAndFastRemove( pPendingAsyncAward->m_pAchievementDesc->m_idAchievement );
  2306. break;
  2307. case XPendingAsyncAward_t::TYPE_AVATAR_AWARD:
  2308. m_arrAchievementsEarned.FindAndFastRemove( pPendingAsyncAward->m_pAvatarAwardDesc->m_idAvatarAward );
  2309. break;
  2310. }
  2311. if ( !bWarningFired )
  2312. {
  2313. g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues(
  2314. "OnProfileUnavailable", "iController", m_iController ) );
  2315. bWarningFired = true;
  2316. }
  2317. }
  2318. else
  2319. {
  2320. if ( pPendingAsyncAward->m_eType == XPendingAsyncAward_t::TYPE_AVATAR_AWARD )
  2321. {
  2322. if ( TitleDataFieldsDescription_t const *fdBitfield = TitleDataFieldsDescriptionFindByString( g_pMatchFramework->GetMatchTitle()->DescribeTitleDataStorage(),
  2323. pPendingAsyncAward->m_pAvatarAwardDesc->m_szTitleDataBitfieldStatName ) )
  2324. {
  2325. TitleDataFieldsDescriptionSetBit( fdBitfield, this, true );
  2326. }
  2327. }
  2328. KeyValues *kvAwardedEvent = new KeyValues( "OnPlayerAward" );
  2329. kvAwardedEvent->SetInt( "iController", m_iController );
  2330. if ( pPendingAsyncAward->m_eType == XPendingAsyncAward_t::TYPE_AVATAR_AWARD )
  2331. kvAwardedEvent->SetString( "award", pPendingAsyncAward->m_pAvatarAwardDesc->m_szAvatarAwardName );
  2332. if ( pPendingAsyncAward->m_eType == XPendingAsyncAward_t::TYPE_ACHIEVEMENT )
  2333. kvAwardedEvent->SetString( "award", pPendingAsyncAward->m_pAchievementDesc->m_szAchievementName );
  2334. g_pMatchEventsSubscription->BroadcastEvent( kvAwardedEvent );
  2335. }
  2336. }
  2337. // Remove the pending structure
  2338. s_arrPendingAsyncAwards.Remove( k -- );
  2339. delete pPendingAsyncAward;
  2340. }
  2341. }
  2342. #endif
  2343. void PlayerLocal::EvaluateAwardsStateBasedOnStats()
  2344. {
  2345. TitleDataFieldsDescription_t const *pTitleDataStorage = g_pMatchFramework->GetMatchTitle()->DescribeTitleDataStorage();
  2346. KeyValues *kvAwards = NULL;
  2347. KeyValues::AutoDelete autodelete_kvAwards( kvAwards );
  2348. //
  2349. // Evaluate the state of component achievements
  2350. //
  2351. for ( TitleAchievementsDescription_t const *pAchievement = g_pMatchFramework->GetMatchTitle()->DescribeTitleAchievements();
  2352. pAchievement && pAchievement->m_szAchievementName; ++ pAchievement )
  2353. {
  2354. if ( pAchievement->m_numComponents <= 1 )
  2355. continue;
  2356. if ( m_arrAchievementsEarned.Find( pAchievement->m_idAchievement ) != m_arrAchievementsEarned.InvalidIndex() )
  2357. continue;
  2358. TitleDataFieldsDescription_t const *fdBitfield = TitleDataFieldsDescriptionFindByString( pTitleDataStorage,
  2359. CFmtStr( "%s[1]", pAchievement->m_szAchievementName ) );
  2360. int numComponentsSet = 0;
  2361. for ( ; numComponentsSet < pAchievement->m_numComponents; ++ numComponentsSet, ++ fdBitfield )
  2362. {
  2363. if ( !fdBitfield || !fdBitfield->m_szFieldName || (fdBitfield->m_eDataType != fdBitfield->DT_BITFIELD) )
  2364. {
  2365. DevWarning( "EvaluateAwardsStateBasedOnStats for achievement [%s] error: invalid component configuration (comp#%d)!\n", pAchievement->m_szAchievementName, numComponentsSet + 1 );
  2366. break;
  2367. }
  2368. #ifdef _DEBUG
  2369. // In debug make sure bitfields names match
  2370. if ( Q_stricmp( fdBitfield->m_szFieldName, CFmtStr( "%s[%d]", pAchievement->m_szAchievementName, numComponentsSet + 1 ) ) )
  2371. {
  2372. Assert( 0 );
  2373. }
  2374. #endif
  2375. if ( !TitleDataFieldsDescriptionGetBit( fdBitfield, this ) )
  2376. break;
  2377. }
  2378. if ( numComponentsSet == pAchievement->m_numComponents )
  2379. {
  2380. // Achievement should be earned based on components
  2381. if ( !kvAwards )
  2382. {
  2383. kvAwards = new KeyValues( "write_award" );
  2384. autodelete_kvAwards.Assign( kvAwards );
  2385. }
  2386. DevMsg( "PlayerLocal(%s)::EvaluateAwardsStateBasedOnStats is awarding %s\n", GetName(), pAchievement->m_szAchievementName );
  2387. kvAwards->SetInt( pAchievement->m_szAchievementName, 1 );
  2388. }
  2389. }
  2390. //
  2391. // Evaluate the state of all avatar awards
  2392. //
  2393. for ( TitleAvatarAwardsDescription_t const *pAvatarAward = g_pMatchFramework->GetMatchTitle()->DescribeTitleAvatarAwards();
  2394. pAvatarAward && pAvatarAward->m_szAvatarAwardName; ++ pAvatarAward )
  2395. {
  2396. if ( TitleDataFieldsDescription_t const *fdBitfield = TitleDataFieldsDescriptionFindByString( pTitleDataStorage, pAvatarAward->m_szTitleDataBitfieldStatName ) )
  2397. {
  2398. if ( TitleDataFieldsDescriptionGetBit( fdBitfield, this ) )
  2399. {
  2400. m_arrAvatarAwardsEarned.FindAndFastRemove( pAvatarAward->m_idAvatarAward );
  2401. m_arrAvatarAwardsEarned.AddToTail( pAvatarAward->m_idAvatarAward );
  2402. }
  2403. }
  2404. }
  2405. //
  2406. // Award all accumulated awards
  2407. //
  2408. UpdateAwardsData( kvAwards );
  2409. }
  2410. void PlayerLocal::LoadGuestsTitleData()
  2411. {
  2412. #ifdef _GAMECONSOLE
  2413. for ( int k = 0; k < XBX_GetNumGameUsers(); ++ k )
  2414. {
  2415. int iCtrlr = XBX_GetUserId( k );
  2416. if ( iCtrlr == m_iController )
  2417. continue;
  2418. if ( !XBX_GetUserIsGuest( k ) )
  2419. continue;
  2420. DevMsg( "User%d stats inheriting from user%d.\n", iCtrlr, m_iController );
  2421. PlayerLocal *pGuest = ( PlayerLocal * ) g_pPlayerManager->GetLocalPlayer( iCtrlr );
  2422. Q_memcpy( pGuest->m_bufTitleData, m_bufTitleData, sizeof( m_bufTitleData ) );
  2423. }
  2424. #endif
  2425. }
  2426. void PlayerLocal::OnProfileTitleDataLoaded( int iErrorCode )
  2427. {
  2428. if ( iErrorCode )
  2429. {
  2430. g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnProfileDataLoadFailed", "iController", m_iController, "error", iErrorCode ) );
  2431. }
  2432. else
  2433. {
  2434. LoadGuestsTitleData();
  2435. g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnProfileDataLoaded", "iController", m_iController ) );
  2436. }
  2437. // Invite awaiting our title data
  2438. if ( m_uiPlayerFlags & PLAYER_INVITE_AWAITING_TITLEDATA )
  2439. {
  2440. m_uiPlayerFlags &=~PLAYER_INVITE_AWAITING_TITLEDATA;
  2441. g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues(
  2442. "OnInvite", "action", "join" ) );
  2443. }
  2444. }
  2445. XUSER_SIGNIN_STATE PlayerLocal::GetAssumedSigninState()
  2446. {
  2447. #ifdef _X360
  2448. return ( GetOnlineState() != STATE_OFFLINE ) ? ( eXUserSigninState_SignedInToLive ) : ( eXUserSigninState_SignedInLocally );
  2449. #elif !defined( NO_STEAM )
  2450. if ( steamapicontext->SteamUser() && steamapicontext->SteamUser()->BLoggedOn() )
  2451. return eXUserSigninState_SignedInToLive;
  2452. else
  2453. return eXUserSigninState_SignedInLocally;
  2454. #else // No steam.
  2455. #if defined( _PS3 )
  2456. return eXUserSigninState_SignedInLocally;
  2457. #else
  2458. return eXUserSigninState_NotSignedIn;
  2459. #endif
  2460. #endif
  2461. }
  2462. void PlayerLocal::SetNeedsSave()
  2463. {
  2464. for ( int ii=0; ii<TITLE_DATA_COUNT; ++ii )
  2465. {
  2466. m_bSaveTitleData[ii] = true;
  2467. }
  2468. }
  2469. CON_COMMAND_F( ms_player_dump_properties, "Prints a dump the current players property data", FCVAR_CHEAT )
  2470. {
  2471. Msg( "[DMM] ms_player_dump_properties...\n" );
  2472. Msg( " Num game users: %d\n", XBX_GetNumGameUsers() );
  2473. for ( unsigned int iUserSlot = 0; iUserSlot < XBX_GetNumGameUsers(); ++ iUserSlot )
  2474. {
  2475. int iCtrlr = iUserSlot;
  2476. bool bGuest = false;
  2477. #ifdef _GAMECONSOLE
  2478. iCtrlr = XBX_GetUserId( iUserSlot );
  2479. bGuest = !!XBX_GetUserIsGuest( iUserSlot );
  2480. #endif
  2481. Msg( "Slot%d ctrlr%d: %s\n", iUserSlot, iCtrlr, bGuest ? "guest" : "profile" );
  2482. IPlayerLocal *pPlayerLocal = g_pPlayerManager->GetLocalPlayer( iCtrlr );
  2483. if ( !pPlayerLocal )
  2484. continue;
  2485. Msg( " Name = %s\n", pPlayerLocal->GetName() );
  2486. TitleDataFieldsDescription_t const *fields = g_pMatchFramework->GetMatchTitle()->DescribeTitleDataStorage();
  2487. for ( ; fields && fields->m_szFieldName; ++ fields )
  2488. {
  2489. switch ( fields->m_eDataType )
  2490. {
  2491. case TitleDataFieldsDescription_t::DT_BITFIELD:
  2492. Msg( "BITFIELD %s = %u\n", fields->m_szFieldName, TitleDataFieldsDescriptionGetBit( fields, pPlayerLocal ) ? 1 : 0 );
  2493. break;
  2494. case TitleDataFieldsDescription_t::DT_uint8:
  2495. Msg( "UINT8 %s = %u\n", fields->m_szFieldName, TitleDataFieldsDescriptionGetValue<uint8>( fields, pPlayerLocal ) );
  2496. break;
  2497. case TitleDataFieldsDescription_t::DT_uint16:
  2498. Msg( "UINT16 %s = %u\n", fields->m_szFieldName, TitleDataFieldsDescriptionGetValue<uint16>( fields, pPlayerLocal ) );
  2499. break;
  2500. case TitleDataFieldsDescription_t::DT_uint32:
  2501. Msg( "UINT32 %s = %u\n", fields->m_szFieldName, TitleDataFieldsDescriptionGetValue<uint32>( fields, pPlayerLocal ) );
  2502. break;
  2503. case TitleDataFieldsDescription_t::DT_float:
  2504. Msg( "FLOAT %s = %.3f\n", fields->m_szFieldName, TitleDataFieldsDescriptionGetValue<float>( fields, pPlayerLocal ) );
  2505. break;
  2506. case TitleDataFieldsDescription_t::DT_uint64:
  2507. Msg( "UINT64 %s = 0x%llX\n", fields->m_szFieldName, TitleDataFieldsDescriptionGetValue<uint64>( fields, pPlayerLocal ) );
  2508. break;
  2509. }
  2510. }
  2511. }
  2512. Msg( " ms_player_dump_properties finished.\n" );
  2513. }
  2514. #ifdef _DEBUG
  2515. CON_COMMAND_F( ms_player_award, "Awards the current player an award", FCVAR_CHEAT )
  2516. {
  2517. int iCtrlr = args.FindArgInt( "ctrlr", XBX_GetPrimaryUserId() );
  2518. IPlayerLocal *pPlayer = g_pPlayerManager->GetLocalPlayer( iCtrlr );
  2519. if ( !pPlayer )
  2520. {
  2521. DevWarning( "ERROR: Controller %d is not registered!\n", iCtrlr );
  2522. return;
  2523. }
  2524. KeyValues *kvAwards = new KeyValues( "write_awards", args.FindArg( "award" ), 1 );
  2525. KeyValues::AutoDelete autodelete( kvAwards );
  2526. (( PlayerLocal * )pPlayer)->UpdateAwardsData( kvAwards );
  2527. }
  2528. #endif
  2529. #if !defined(NO_STEAM) && !defined(_CERT)
  2530. CON_COMMAND_F( ms_player_unaward, "UnAwards the current player an award", FCVAR_DEVELOPMENTONLY )
  2531. {
  2532. if ( !CommandLine()->FindParm( "+ms_player_unaward" ) )
  2533. {
  2534. Warning( "Error: You must pass +ms_player_unaward from command line!\n" );
  2535. return;
  2536. }
  2537. if ( !args.FindArg( "unaward" ) )
  2538. {
  2539. Warning( "Syntax: +ms_player_unaward unaward ACHIEVEMENT|everything\n" );
  2540. return;
  2541. }
  2542. if ( !V_stricmp( "everything", args.FindArg( "unaward" ) ) )
  2543. {
  2544. steamapicontext->SteamUserStats()->ResetAllStats( false );
  2545. steamapicontext->SteamUserStats()->ResetAllStats( true );
  2546. while ( steamapicontext->SteamRemoteStorage()->GetFileCount() > 0 )
  2547. {
  2548. int nUnused;
  2549. steamapicontext->SteamRemoteStorage()->FileDelete( steamapicontext->SteamRemoteStorage()->GetFileNameAndSize( 0, &nUnused ) );
  2550. }
  2551. steamapicontext->SteamUserStats()->StoreStats();
  2552. Msg( "Everything was reset!\n" );
  2553. if ( !IsGameConsole() )
  2554. {
  2555. g_pMatchExtensions->GetIVEngineClient()->ExecuteClientCmd( "exec config_default.cfg; exec joy_preset_1.cfg; host_writeconfig;" );
  2556. }
  2557. return;
  2558. }
  2559. if ( steamapicontext->SteamUserStats()->ClearAchievement( args.FindArg( "unaward" ) ) )
  2560. {
  2561. steamapicontext->SteamUserStats()->StoreStats();
  2562. Msg( "%s unawarded!\n", args.FindArg( "unaward" ) );
  2563. }
  2564. else
  2565. {
  2566. Warning( "%s failed\n", args.FindArg( "unaward" ) );
  2567. }
  2568. }
  2569. #endif