Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

563 lines
15 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Displays a leaderboard
  4. //
  5. //=============================================================================//
  6. #include "leaderboarddialog.h"
  7. #include "vgui_controls/Label.h"
  8. #include "vgui/ILocalize.h"
  9. #include "hl2orange.spa.h"
  10. // memdbgon must be the last include file in a .cpp file!!!
  11. #include "tier0/memdbgon.h"
  12. #define NUM_ROWS_PER_QUERY 100
  13. CLeaderboardDialog *g_pLeaderboardDialog;
  14. //----------------------------------------------------------
  15. // CLeaderboardDialog
  16. //----------------------------------------------------------
  17. CLeaderboardDialog::CLeaderboardDialog( vgui::Panel *pParent ) : BaseClass( pParent, "LeaderboardDialog" )
  18. {
  19. g_pLeaderboardDialog = this;
  20. m_iBaseRank = 0;
  21. m_iActiveRank = 0;
  22. m_iMaxRank = 0;
  23. m_cColumns = 0;
  24. m_iRangeBase = 0;
  25. #if defined( _X360 )
  26. m_pStats = NULL;
  27. #endif
  28. m_pProgressBg = new vgui::Panel( this, "ProgressBg" );
  29. m_pProgressBar = new vgui::Panel( this, "ProgressBar" );
  30. m_pProgressPercent = new vgui::Label( this, "ProgressPercent", "" );
  31. m_pNumbering = new vgui::Label( this, "Numbering", "" );
  32. m_pUpArrow = new vgui::Label( this, "UpArrow", "" );
  33. m_pDownArrow = new vgui::Label( this, "DownArrow", "" );
  34. m_pBestMoments = new vgui::Label( this, "BestMoments", "" );
  35. }
  36. CLeaderboardDialog::~CLeaderboardDialog()
  37. {
  38. CleanupStats();
  39. delete m_pProgressBg;
  40. delete m_pProgressBar;
  41. delete m_pProgressPercent;
  42. delete m_pNumbering;
  43. delete m_pUpArrow;
  44. delete m_pDownArrow;
  45. }
  46. //----------------------------------------------------------
  47. // Clean up the stats array
  48. //----------------------------------------------------------
  49. void CLeaderboardDialog::CleanupStats()
  50. {
  51. #if defined( _X360 )
  52. if ( m_pStats )
  53. {
  54. delete [] m_pStats;
  55. m_pStats = NULL;
  56. }
  57. #endif
  58. }
  59. //----------------------------------------------------------
  60. // Position the dialogs elements
  61. //----------------------------------------------------------
  62. void CLeaderboardDialog::PerformLayout( void )
  63. {
  64. BaseClass::PerformLayout();
  65. if ( m_cColumns )
  66. {
  67. int x, y, wide, tall;
  68. m_pProgressBg->GetBounds( x, y, wide, tall );
  69. int columnWide = wide / m_cColumns;
  70. int lockedColumns = m_Menu.GetFirstUnlockedColumnIndex();
  71. int visibleColumns = m_Menu.GetVisibleColumnCount() - lockedColumns;
  72. int iColumn = m_Menu.GetActiveColumnIndex() - lockedColumns;
  73. if ( iColumn < 0 )
  74. {
  75. iColumn = 0;
  76. }
  77. else if ( iColumn < m_iRangeBase )
  78. {
  79. m_iRangeBase = iColumn;
  80. }
  81. else if ( iColumn >= m_iRangeBase + visibleColumns )
  82. {
  83. m_iRangeBase = iColumn - visibleColumns + 1;
  84. }
  85. m_pProgressBg->SetBounds( x, y, columnWide * m_cColumns, tall );
  86. m_pProgressBar->SetBounds( x + columnWide * m_iRangeBase, y, columnWide * visibleColumns, tall );
  87. }
  88. else
  89. {
  90. m_pProgressBg->SetVisible( false );
  91. m_pProgressBar->SetVisible( false );
  92. }
  93. int menux, menuy;
  94. m_Menu.GetPos( menux, menuy );
  95. // Do a perform layout on the menu so we get the correct height now
  96. m_Menu.InvalidateLayout( true, false );
  97. m_pNumbering->SizeToContents();
  98. wchar_t wszNumbering[64];
  99. wchar_t *wzNumberingFmt = g_pVGuiLocalize->Find( "#GameUI_Achievement_Menu_Range" );
  100. wchar_t wzActiveItem[8];
  101. wchar_t wzTotal[8];
  102. int iActive = m_iBaseRank + m_Menu.GetActiveItemIndex();
  103. if ( iActive < 0 )
  104. {
  105. iActive = 0;
  106. }
  107. V_snwprintf( wzActiveItem, ARRAYSIZE( wzActiveItem ), L"%d", iActive );
  108. V_snwprintf( wzTotal, ARRAYSIZE( wzTotal ), L"%d", m_iMaxRank );
  109. g_pVGuiLocalize->ConstructString( wszNumbering, sizeof( wszNumbering ), wzNumberingFmt, 2, wzActiveItem, wzTotal );
  110. m_pNumbering->SetText( wszNumbering );
  111. m_pNumbering->SetWide( GetWide() );
  112. MoveToCenterOfScreen();
  113. }
  114. //----------------------------------------------------------
  115. //
  116. //----------------------------------------------------------
  117. void CLeaderboardDialog::ApplySettings( KeyValues *pResourceData )
  118. {
  119. BaseClass::ApplySettings( pResourceData );
  120. m_KeyRepeat.SetKeyRepeatTime( KEY_XBUTTON_DOWN, 0.05f );
  121. m_KeyRepeat.SetKeyRepeatTime( KEY_XSTICK1_DOWN, 0.05f );
  122. m_KeyRepeat.SetKeyRepeatTime( KEY_XBUTTON_UP, 0.05f );
  123. m_KeyRepeat.SetKeyRepeatTime( KEY_XSTICK1_UP, 0.05f );
  124. }
  125. //----------------------------------------------------------
  126. //
  127. //----------------------------------------------------------
  128. void CLeaderboardDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
  129. {
  130. BaseClass::ApplySchemeSettings( pScheme );
  131. m_pProgressBg->SetBgColor( Color( 200, 184, 151, 255 ) );
  132. m_pProgressBar->SetBgColor( Color( 179, 82, 22, 255 ) );
  133. m_pNumbering->SetFgColor( pScheme->GetColor( "MatchmakingMenuItemDescriptionColor", Color( 64, 64, 64, 255 ) ) );
  134. m_pBestMoments->SetFgColor( pScheme->GetColor( "MatchmakingMenuItemDescriptionColor", Color( 64, 64, 64, 255 ) ) );
  135. }
  136. //----------------------------------------------------------
  137. //
  138. //----------------------------------------------------------
  139. void CLeaderboardDialog::OnCommand( const char *pCommand )
  140. {
  141. if ( !Q_stricmp( pCommand, "CenterOnPlayer" ) )
  142. {
  143. if ( GetPlayerStats( -1 ) == 0 )
  144. {
  145. // Player isn't on the board, just start at rank 1
  146. GetPlayerStats( 1 );
  147. }
  148. }
  149. else if ( !Q_stricmp( pCommand, "Friends" ) )
  150. {
  151. GetPlayerStats( -1, true );
  152. }
  153. BaseClass::OnCommand( pCommand );
  154. }
  155. //----------------------------------------------------------
  156. //
  157. //----------------------------------------------------------
  158. void CLeaderboardDialog::AddLeaderboardEntry( const char **ppEntries, int ct )
  159. {
  160. m_Menu.AddSectionedItem( ppEntries, ct );
  161. }
  162. //----------------------------------------------------------
  163. // Get some portion of the leaderboard. This should ideally live
  164. // in the client, since it's very mod-specific
  165. //----------------------------------------------------------
  166. bool CLeaderboardDialog::GetPlayerStats( int rank, bool bFriends )
  167. {
  168. #if defined _X360
  169. HANDLE handle;
  170. // Retrieve the necessary buffer size
  171. DWORD cbResults = 0;
  172. bool bRanked = false;
  173. const char *pName = GetName();
  174. if ( !Q_stricmp( pName, "LeaderboardDialog_Ranked" ) )
  175. {
  176. bRanked = true;
  177. }
  178. XUSER_STATS_SPEC spec;
  179. if ( !bRanked )
  180. {
  181. spec.dwViewId = STATS_VIEW_PLAYER_MAX_UNRANKED;
  182. spec.dwNumColumnIds = 15;
  183. spec.rgwColumnIds[0] = STATS_COLUMN_PLAYER_MAX_UNRANKED_POINTS_SCORED;
  184. spec.rgwColumnIds[1] = STATS_COLUMN_PLAYER_MAX_UNRANKED_KILLS;
  185. spec.rgwColumnIds[2] = STATS_COLUMN_PLAYER_MAX_UNRANKED_POINTS_CAPPED;
  186. spec.rgwColumnIds[3] = STATS_COLUMN_PLAYER_MAX_UNRANKED_POINT_DEFENSES;
  187. spec.rgwColumnIds[4] = STATS_COLUMN_PLAYER_MAX_UNRANKED_DOMINATIONS;
  188. spec.rgwColumnIds[5] = STATS_COLUMN_PLAYER_MAX_UNRANKED_REVENGE;
  189. spec.rgwColumnIds[6] = STATS_COLUMN_PLAYER_MAX_UNRANKED_BUILDINGS_DESTROYED;
  190. spec.rgwColumnIds[7] = STATS_COLUMN_PLAYER_MAX_UNRANKED_HEADSHOTS;
  191. spec.rgwColumnIds[8] = STATS_COLUMN_PLAYER_MAX_UNRANKED_HEALTH_POINTS_HEALED;
  192. spec.rgwColumnIds[9] = STATS_COLUMN_PLAYER_MAX_UNRANKED_INVULNS;
  193. spec.rgwColumnIds[10] = STATS_COLUMN_PLAYER_MAX_UNRANKED_KILL_ASSISTS;
  194. spec.rgwColumnIds[11] = STATS_COLUMN_PLAYER_MAX_UNRANKED_BACKSTABS;
  195. spec.rgwColumnIds[12] = STATS_COLUMN_PLAYER_MAX_UNRANKED_HEALTH_POINTS_LEACHED;
  196. spec.rgwColumnIds[13] = STATS_COLUMN_PLAYER_MAX_UNRANKED_SENTRY_KILLS;
  197. spec.rgwColumnIds[14] = STATS_COLUMN_PLAYER_MAX_UNRANKED_TELEPORTS;
  198. m_cColumns = 15;
  199. }
  200. else
  201. {
  202. spec.dwViewId = STATS_VIEW_PLAYER_MAX_RANKED;
  203. spec.dwNumColumnIds = 1;
  204. spec.rgwColumnIds[ 0 ] = STATS_COLUMN_PLAYER_MAX_RANKED_POINTS_SCORED;
  205. // set to zero to hide the progress bar
  206. m_cColumns = 0;
  207. }
  208. DWORD ret;
  209. XUID xuid = 0u;
  210. XUID xuidFriends[NUM_ROWS_PER_QUERY];
  211. int xuidCount = 1;
  212. if ( !bFriends )
  213. {
  214. if ( rank == -1 )
  215. {
  216. // Center on the player's xuid
  217. XUserGetXUID( XBX_GetPrimaryUserId(), &xuid );
  218. ret = XUserCreateStatsEnumeratorByXuid(
  219. 0,
  220. xuid,
  221. NUM_ROWS_PER_QUERY,
  222. 1,
  223. &spec,
  224. &cbResults,
  225. &handle );
  226. }
  227. else
  228. {
  229. // Start at the requested rank
  230. ret = XUserCreateStatsEnumeratorByRank(
  231. 0,
  232. rank,
  233. NUM_ROWS_PER_QUERY,
  234. 1,
  235. &spec,
  236. &cbResults,
  237. &handle );
  238. }
  239. if( ret != ERROR_SUCCESS )
  240. {
  241. Warning( "Error getting stats\n" );
  242. return false;
  243. }
  244. // Allocate the buffer
  245. CleanupStats();
  246. m_pStats = ( XUSER_STATS_READ_RESULTS* ) new char[cbResults];
  247. DWORD cpReturned;
  248. ret = XEnumerate( handle, m_pStats, cbResults, &cpReturned, NULL );
  249. }
  250. else
  251. {
  252. // Get Friends leaderboard
  253. int id = XBX_GetPrimaryUserId();
  254. ret = XFriendsCreateEnumerator( id, 0, 5, &cbResults, &handle );
  255. if ( ret != ERROR_SUCCESS )
  256. {
  257. Warning( "Error getting friends list\n" );
  258. return false;
  259. }
  260. // Allocate the buffer
  261. XONLINE_FRIEND *pFriends = ( XONLINE_FRIEND* ) new char[cbResults];
  262. DWORD cpReturned;
  263. ret = XEnumerate( handle, pFriends, cbResults, &cpReturned, NULL );
  264. if( ret != ERROR_SUCCESS )
  265. {
  266. delete pFriends;
  267. return false;
  268. }
  269. for ( uint i = 0; i < cpReturned; ++i )
  270. {
  271. xuidFriends[i] = pFriends[i].xuid;
  272. }
  273. // Allocate the buffer
  274. CleanupStats();
  275. m_pStats = ( XUSER_STATS_READ_RESULTS* ) new char[cbResults];
  276. ret = XUserReadStats( 0, xuidCount, xuidFriends, 1, &spec, &cbResults, m_pStats, NULL );
  277. }
  278. if( ret == ERROR_SUCCESS )
  279. {
  280. const char *pEntries[32];
  281. char pRowBuffer[MAX_PATH];
  282. char pBuffers[32][MAX_PATH];
  283. m_Menu.ClearItems();
  284. m_iMaxRank = m_pStats->pViews[0].dwTotalViewRows;
  285. // Did this search return any rows?
  286. if ( m_pStats->pViews[0].dwNumRows == 0 )
  287. return false;
  288. for ( uint i = 0; i < m_pStats->pViews[0].dwNumRows; ++i )
  289. {
  290. XUSER_STATS_ROW &row = m_pStats->pViews[0].pRows[i];
  291. // Save off the first rank in this set of entries
  292. if ( i == 0 && m_iBaseRank == 0 )
  293. {
  294. m_iBaseRank = row.dwRank;
  295. }
  296. pEntries[0] = itoa( row.dwRank, pRowBuffer, 10 );
  297. pEntries[1] = row.szGamertag;
  298. for ( uint j = 0; j < row.dwNumColumns; ++j )
  299. {
  300. XUSER_STATS_COLUMN &col = m_pStats->pViews[0].pRows[i].pColumns[j];
  301. pEntries[j+2] = itoa( col.Value.nData, pBuffers[j], 10 );
  302. }
  303. AddLeaderboardEntry( pEntries, row.dwNumColumns + 2 );
  304. if ( rank == -1 && row.xuid == xuid )
  305. {
  306. m_Menu.SetFocus( i );
  307. m_iActiveRank = row.dwRank;
  308. }
  309. }
  310. }
  311. else
  312. {
  313. Warning( "Error getting leaderboard stats\n" );
  314. return false;
  315. }
  316. CloseHandle( handle );
  317. return true;
  318. #endif
  319. return false;
  320. }
  321. //----------------------------------------------------------
  322. // Determine if a new set of stats needs to be downloaded
  323. // Return true if the update has been handled, false otherwise
  324. //----------------------------------------------------------
  325. void CLeaderboardDialog::UpdateLeaderboard( int iNewRank )
  326. {
  327. // Clamp the input
  328. if ( iNewRank < 1 )
  329. {
  330. iNewRank = 1;
  331. }
  332. else if ( iNewRank > m_iMaxRank )
  333. {
  334. iNewRank = m_iMaxRank;
  335. }
  336. // No action necessary?
  337. if ( iNewRank == m_iActiveRank )
  338. return;
  339. int nInterval = iNewRank - m_iActiveRank;
  340. int iNewActiveItemIndex = m_Menu.GetActiveItemIndex() + nInterval;
  341. // Set these "new" values to the current values - they will be conditionally updated.
  342. int iNewBaseRank = m_iBaseRank;
  343. int iNewBaseItemIndex = m_Menu.GetBaseRowIndex();
  344. int nVisibleItems = m_Menu.GetVisibleItemCount();
  345. int nHiddenItems = NUM_ROWS_PER_QUERY - nVisibleItems;
  346. // Are we outside the visible range of the menu?
  347. if ( iNewActiveItemIndex < iNewBaseItemIndex )
  348. {
  349. // Do we need to grab another set of columns?
  350. if ( iNewRank < m_iBaseRank )
  351. {
  352. iNewBaseRank = iNewRank - nHiddenItems;
  353. if ( iNewBaseRank < 1 )
  354. {
  355. iNewBaseRank = 1;
  356. }
  357. if ( !GetPlayerStats( iNewBaseRank ) )
  358. {
  359. // Failed to load player stats, don't change the current index
  360. return;
  361. }
  362. m_iBaseRank = iNewBaseRank;
  363. }
  364. int nBaseToActiveInterval = iNewRank - m_iBaseRank;
  365. // Since we shifted the menu down, both base and active item are at the first visible menu item
  366. iNewActiveItemIndex = nBaseToActiveInterval;
  367. iNewBaseItemIndex = nBaseToActiveInterval;
  368. }
  369. else if ( iNewActiveItemIndex >= m_Menu.GetBaseRowIndex() + nVisibleItems )
  370. {
  371. int nHiddenItems = NUM_ROWS_PER_QUERY - nVisibleItems;
  372. int iTopRank = iNewRank + nHiddenItems;
  373. if ( iTopRank > m_iMaxRank )
  374. {
  375. iTopRank = m_iMaxRank;
  376. }
  377. // Do we need to grab another set of columns?
  378. if ( iNewRank >= m_iBaseRank + NUM_ROWS_PER_QUERY )
  379. {
  380. iNewBaseRank = iTopRank - NUM_ROWS_PER_QUERY + 1;
  381. if ( !GetPlayerStats( iNewBaseRank ) )
  382. {
  383. // Failed to load player stats, don't change the current index
  384. return;
  385. }
  386. m_iBaseRank = iNewBaseRank;
  387. }
  388. int nBaseToActiveInterval = iNewRank - m_iBaseRank;
  389. iNewActiveItemIndex = nBaseToActiveInterval;
  390. iNewBaseItemIndex = iNewActiveItemIndex - nVisibleItems + 1;
  391. }
  392. // Set all the new variables - must set base index before active index.
  393. m_iActiveRank = iNewRank;
  394. m_Menu.SetBaseRowIndex( iNewBaseItemIndex );
  395. m_Menu.SetFocus( iNewActiveItemIndex );
  396. InvalidateLayout();
  397. }
  398. //-----------------------------------------------------------------------------
  399. // Purpose:
  400. //-----------------------------------------------------------------------------
  401. void CLeaderboardDialog::HandleKeyRepeated( vgui::KeyCode code )
  402. {
  403. OnKeyCodePressed( code );
  404. }
  405. //-----------------------------------------------------------------
  406. // Purpose: Send key presses to the dialog's menu
  407. //-----------------------------------------------------------------
  408. void CLeaderboardDialog::OnKeyCodePressed( vgui::KeyCode code )
  409. {
  410. switch( code )
  411. {
  412. case KEY_XBUTTON_A:
  413. case STEAMCONTROLLER_A:
  414. #ifdef _X360
  415. {
  416. int idx = m_Menu.GetActiveItemIndex();
  417. if ( m_pStats && idx < (int)m_pStats->pViews[0].dwNumRows )
  418. {
  419. XUSER_STATS_ROW &row = m_pStats->pViews[0].pRows[idx];
  420. XShowGamerCardUI( XBX_GetPrimaryUserId(), row.xuid );
  421. }
  422. }
  423. #endif
  424. break;
  425. case KEY_XBUTTON_Y:
  426. case STEAMCONTROLLER_Y:
  427. break;
  428. case KEY_XSTICK1_DOWN:
  429. case KEY_XBUTTON_DOWN:
  430. case STEAMCONTROLLER_DPAD_DOWN:
  431. m_KeyRepeat.KeyDown( code );
  432. UpdateLeaderboard( m_iActiveRank + 1 );
  433. break;
  434. case KEY_XSTICK1_UP:
  435. case KEY_XBUTTON_UP:
  436. case STEAMCONTROLLER_DPAD_UP:
  437. m_KeyRepeat.KeyDown( code );
  438. UpdateLeaderboard( m_iActiveRank - 1 );
  439. break;
  440. case KEY_XBUTTON_LEFT_SHOULDER:
  441. UpdateLeaderboard( 1 );
  442. break;
  443. case KEY_XBUTTON_RIGHT_SHOULDER:
  444. OnCommand( "CenterOnPlayer" );
  445. break;
  446. // Disabled until friends enumeration works
  447. // case KEY_XBUTTON_RIGHT_SHOULDER:
  448. // OnCommand( "Friends" );
  449. // break;
  450. default:
  451. m_KeyRepeat.KeyDown( code );
  452. BaseClass::OnKeyCodePressed( code );
  453. break;
  454. }
  455. // Invalidate layout when scrolling through columns
  456. switch( code )
  457. {
  458. case KEY_XSTICK1_LEFT:
  459. case KEY_XBUTTON_LEFT:
  460. case STEAMCONTROLLER_DPAD_LEFT:
  461. case KEY_XSTICK1_RIGHT:
  462. case KEY_XBUTTON_RIGHT:
  463. case STEAMCONTROLLER_DPAD_RIGHT:
  464. InvalidateLayout();
  465. break;
  466. }
  467. }
  468. CON_COMMAND( mm_add_item, "Add a stats item" )
  469. {
  470. if ( args.ArgC() > 1 )
  471. {
  472. int ct = atoi( args[1] );
  473. const char *pEntries[32];
  474. for ( int i = 0; i < ct; ++i )
  475. {
  476. pEntries[i] = args[i+2];
  477. }
  478. g_pLeaderboardDialog->AddLeaderboardEntry( pEntries, ct );
  479. }
  480. }