csgo-2018-source/matchmaking/leaderboards.cpp
2021-07-24 21:11:47 -07:00

541 lines
14 KiB
C++

//========= Copyright © 1996-2009, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#include "mm_framework.h"
#include "leaderboards.h"
#include "fmtstr.h"
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
//
// Definition of leaderboard request queue class
//
class CLeaderboardRequestQueue : public ILeaderboardRequestQueue
{
public:
CLeaderboardRequestQueue();
~CLeaderboardRequestQueue();
// ILeaderboardRequestQueue
public:
virtual void Request( KeyValues *pRequest );
virtual void Update();
public:
KeyValues * GetFinishedRequest();
protected:
CUtlVector< KeyValues * > m_arrRequests;
bool m_bQueryRunning;
KeyValues *m_pFinishedRequest;
void OnStartNewQuery();
void OnSubmitQuery();
void OnQueryFinished();
void Cleanup();
protected:
#ifdef _X360
// Leaderboard query data
CUtlVector< XUID > m_arrXuids;
CUtlVector< XUSER_STATS_SPEC > m_arrSpecs;
CUtlBuffer m_bufResults;
XOVERLAPPED m_xOverlapped;
void ProcessResults( XUSER_STATS_READ_RESULTS const *pResults );
// Leaderboard description data
CUtlVector< KeyValues * > m_arrViewDescriptions;
KeyValues * FindViewDescription( DWORD dwViewId );
#elif !defined( NO_STEAM )
KeyValues *m_pViewDescription;
CCallResult< CLeaderboardRequestQueue, LeaderboardFindResult_t > m_CallbackOnLeaderboardFindResult;
void Steam_OnLeaderboardFindResult( LeaderboardFindResult_t *p, bool bError );
CCallResult< CLeaderboardRequestQueue, LeaderboardScoresDownloaded_t > m_CallbackOnLeaderboardScoresDownloaded;
void Steam_OnLeaderboardScoresDownloaded( LeaderboardScoresDownloaded_t *p, bool bError );
void ProcessResults( LeaderboardEntry_t const &lbe );
#endif
};
CLeaderboardRequestQueue g_LeaderboardRequestQueue;
ILeaderboardRequestQueue *g_pLeaderboardRequestQueue = &g_LeaderboardRequestQueue;
//
// Implementation of leaderboard request queue class
//
CLeaderboardRequestQueue::CLeaderboardRequestQueue() :
m_bQueryRunning( false ),
#if !defined( _X360 ) && !defined( NO_STEAM )
m_pViewDescription( NULL ),
#endif
m_pFinishedRequest( NULL )
{
}
CLeaderboardRequestQueue::~CLeaderboardRequestQueue()
{
Cleanup();
}
void CLeaderboardRequestQueue::Request( KeyValues *pRequest )
{
if ( !pRequest )
return;
DevMsg( "CLeaderboardRequestQueue::Request\n" );
KeyValuesDumpAsDevMsg( pRequest, 1 );
m_arrRequests.AddToTail( pRequest->MakeCopy() );
}
void CLeaderboardRequestQueue::Update()
{
if ( m_bQueryRunning )
{
#ifdef _X360
if ( XHasOverlappedIoCompleted( &m_xOverlapped ) )
{
OnQueryFinished();
}
#endif
}
else if ( m_arrRequests.Count() )
{
OnStartNewQuery();
}
else if ( m_arrRequests.NumAllocated() )
{
Cleanup();
}
}
KeyValues * CLeaderboardRequestQueue::GetFinishedRequest()
{
return m_pFinishedRequest;
}
void CLeaderboardRequestQueue::OnQueryFinished()
{
#ifdef _X360
// Submit results for processing
XUSER_STATS_READ_RESULTS const *pResults = ( XUSER_STATS_READ_RESULTS const * ) m_bufResults.Base();
if ( pResults && pResults->dwNumViews > 0 && pResults->pViews )
ProcessResults( pResults );
#endif
// Query is no longer running
m_bQueryRunning = false;
DevMsg( "CLeaderboardRequestQueue::OnQueryFinished\n" );
KeyValuesDumpAsDevMsg( m_pFinishedRequest, 1 );
// Stuff the data into the players
#ifdef _X360
for ( int iCtrlr = 0; iCtrlr < XUSER_MAX_COUNT; ++ iCtrlr )
{
XUID xuid = 0;
XUSER_SIGNIN_INFO xsi;
if ( XUserGetSigninState( iCtrlr ) != eXUserSigninState_NotSignedIn &&
ERROR_SUCCESS == XUserGetSigninInfo( iCtrlr, XUSER_GET_SIGNIN_INFO_ONLINE_XUID_ONLY, &xsi ) &&
!(xsi.dwInfoFlags & XUSER_INFO_FLAG_GUEST) )
xuid = xsi.xuid;
#else
int iCtrlr = XBX_GetPrimaryUserId();
{
XUID xuid = g_pPlayerManager->GetLocalPlayer( iCtrlr )->GetXUID();
#endif
KeyValues *pUserViews = NULL;
if ( xuid )
pUserViews = m_pFinishedRequest->FindKey( CFmtStr( "%llx", xuid ) );
if ( pUserViews )
{
IPlayerLocal *pPlayer = g_pPlayerManager->GetLocalPlayer( iCtrlr );
if ( pPlayer )
(( PlayerLocal * ) pPlayer)->OnLeaderboardRequestFinished( pUserViews );
}
}
}
void CLeaderboardRequestQueue::Cleanup()
{
#ifdef _X360
// Clear view descriptions
while ( m_arrViewDescriptions.Count() )
{
m_arrViewDescriptions.Head()->deleteThis();
m_arrViewDescriptions.FastRemove( 0 );
}
m_arrXuids.Purge();
m_arrSpecs.Purge();
m_bufResults.Purge();
m_arrViewDescriptions.Purge();
#elif !defined( NO_STEAM )
if ( m_pViewDescription )
m_pViewDescription->deleteThis();
m_pViewDescription = NULL;
#endif
// Clear requests
while ( m_arrRequests.Count() )
{
m_arrRequests.Head()->deleteThis();
m_arrRequests.FastRemove( 0 );
}
m_arrRequests.Purge();
// Clear finished result
if ( m_pFinishedRequest )
m_pFinishedRequest->deleteThis();
m_pFinishedRequest = NULL;
}
void CLeaderboardRequestQueue::OnStartNewQuery()
{
// When we are starting a new query we need to get rid of the old finished result
if ( m_pFinishedRequest )
m_pFinishedRequest->deleteThis();
m_pFinishedRequest = NULL;
#if !defined( NO_STEAM ) && !defined( SWDS )
extern CInterlockedInt g_numSteamLeaderboardWriters;
if ( g_numSteamLeaderboardWriters )
return; // yield to writers that can alter the leaderboard
#endif
DevMsg( "CLeaderboardRequestQueue::OnStartNewQuery preparing request...\n" );
#ifdef _X360
// Prepare the XUIDs first
m_arrXuids.RemoveAll();
for ( DWORD k = 0; k < XBX_GetNumGameUsers(); ++ k )
{
int iCtrlr = XBX_GetUserId( k );
XUSER_SIGNIN_INFO xsi;
if ( XUserGetSigninState( iCtrlr ) != eXUserSigninState_NotSignedIn &&
ERROR_SUCCESS == XUserGetSigninInfo( iCtrlr, XUSER_GET_SIGNIN_INFO_ONLINE_XUID_ONLY, &xsi ) &&
!(xsi.dwInfoFlags & XUSER_INFO_FLAG_GUEST) )
{
m_arrXuids.AddToTail( xsi.xuid );
DevMsg( " XUID: %s (%llx)\n", xsi.szUserName, xsi.xuid );
}
}
// Clear view descriptions
while ( m_arrViewDescriptions.Count() )
{
m_arrViewDescriptions.Head()->deleteThis();
m_arrViewDescriptions.FastRemove( 0 );
}
// Prepare the spec
m_arrSpecs.RemoveAll();
for ( int q = 0; q < m_arrRequests.Count(); ++ q )
{
KeyValues *pRequest = m_arrRequests[q];
KeyValues::AutoDelete autodelete_pRequest( pRequest );
m_arrRequests.Remove( q -- );
char const *szViewName = pRequest->GetName();
KeyValues *pDescription = g_pMMF->GetMatchTitle()->DescribeTitleLeaderboard( szViewName );
if ( !pDescription )
{
DevWarning( " View %s failed to allocate description!\n", szViewName );
}
KeyValues::AutoDelete autodelete_pDescription( pDescription );
// See if we already have a request for this view
DWORD dwViewId = pDescription->GetInt( ":id" );
for ( int k = 0; k < m_arrSpecs.Count(); ++ k )
{
if ( m_arrSpecs[k].dwViewId == dwViewId )
{
dwViewId = 0;
break;
}
}
// If we already have a request for this view, then continue
if ( !dwViewId )
continue;
// Otherwise add this view to the spec
XUSER_STATS_SPEC xss = {0};
xss.dwViewId = dwViewId;
Assert( !xss.dwNumColumnIds );
m_arrSpecs.AddToTail( xss );
pDescription->SetString( ":name", szViewName );
m_arrViewDescriptions.AddToTail( pDescription );
autodelete_pDescription.Assign( NULL ); // don't autodelete now
DevMsg( " View: %s (%d)\n", szViewName, dwViewId );
}
#elif !defined( NO_STEAM )
// Clear view descriptions
if ( m_pViewDescription )
m_pViewDescription->deleteThis();
m_pViewDescription = NULL;
for ( int q = 0; q < m_arrRequests.Count(); ++ q )
{
KeyValues *pRequest = m_arrRequests[q];
KeyValues::AutoDelete autodelete_pRequest( pRequest );
m_arrRequests.Remove( q -- );
char const *szViewName = pRequest->GetName();
m_pViewDescription = g_pMMF->GetMatchTitle()->DescribeTitleLeaderboard( szViewName );
if ( !m_pViewDescription )
{
DevWarning( " View %s failed to allocate description!\n", szViewName );
continue;
}
m_pViewDescription->SetString( ":name", szViewName );
SteamAPICall_t hCall = steamapicontext->SteamUserStats()->FindLeaderboard( szViewName );
m_CallbackOnLeaderboardFindResult.Set( hCall, this, &CLeaderboardRequestQueue::Steam_OnLeaderboardFindResult );
m_bQueryRunning = true;
break;
}
#endif
// Clean up all the requests in the queue
DevMsg( "CLeaderboardRequestQueue::OnStartNewQuery - request prepared.\n" );
// Run the query
OnSubmitQuery();
}
void CLeaderboardRequestQueue::OnSubmitQuery()
{
#ifdef _X360
if ( m_arrXuids.Count() && m_arrSpecs.Count() )
{
DWORD dwBytes = 0;
DWORD ret = XUserReadStats( 0,
m_arrXuids.Count(), m_arrXuids.Base(),
m_arrSpecs.Count(), m_arrSpecs.Base(),
&dwBytes, NULL, NULL );
if ( ret == ERROR_INSUFFICIENT_BUFFER )
{
ZeroMemory( &m_xOverlapped, sizeof( m_xOverlapped ) );
m_bufResults.EnsureCapacity( dwBytes );
ret = XUserReadStats( 0,
m_arrXuids.Count(), m_arrXuids.Base(),
m_arrSpecs.Count(), m_arrSpecs.Base(),
&dwBytes, ( PXUSER_STATS_READ_RESULTS ) m_bufResults.Base(),
&m_xOverlapped );
}
if ( ret == ERROR_IO_PENDING )
{
DevMsg( "CLeaderboardRequestQueue::OnSubmitQuery - query submitted...\n" );
m_bQueryRunning = true;
}
else
{
DevWarning( "CLeaderboardRequestQueue::OnSubmitQuery - failed [code = %d, bytes = %d]\n", ret, dwBytes );
}
}
#endif
}
#ifdef _X360
void CLeaderboardRequestQueue::ProcessResults( XUSER_STATS_READ_RESULTS const *pResults )
{
/*
901D41D61DC61 // XUID %llx
{
survival_c5m2_park
{
:rank = 923 // uint64
:rows = 999 // uint64
:rating = 600 // uint64
besttime = 600 // uint64
}
... more views ...
}
... more users ...
*/
DevMsg( "LeaderboardRequestQueue: ProcessResults ( %d views )\n", pResults->dwNumViews );
for ( DWORD k = 0; k < pResults->dwNumViews; ++ k )
{
XUSER_STATS_VIEW const *pView = &pResults->pViews[k];
Assert( pView );
KeyValues *pViewDesc = FindViewDescription( pView->dwViewId );
Assert( pViewDesc );
if ( !pViewDesc )
{
Warning( "LeaderboardRequestQueue: ProcessResults has no view description for view %d!\n", pView->dwViewId );
continue;
}
char const *szViewName = pViewDesc->GetString( ":name" );
DevMsg( " Processing view %d ( %s ), total rows = %d\n", pView->dwViewId, szViewName, pView->dwTotalViewRows );
for ( DWORD r = 0; r < pView->dwNumRows; ++ r )
{
XUSER_STATS_ROW const *pRow = &pView->pRows[r];
if ( !pRow->dwRank && !pRow->i64Rating )
{
DevMsg( " Gamer %s (%llx) not in view\n", pRow->szGamertag, pRow->xuid );
continue; // gamer is not present in the leaderboard
}
DevMsg( " Gamer %s (%llx) data loaded: rank=%d, rating=%lld\n",
pRow->szGamertag, pRow->xuid, pRow->dwRank, pRow->i64Rating );
// Gamer is present in the leaderboard and should be included in the results
if ( !m_pFinishedRequest )
m_pFinishedRequest = new KeyValues( "Leaderboard" );
// Find or create the view for this gamer
KeyValues *pUserInfo = m_pFinishedRequest->FindKey( CFmtStr( "%llx/%s", pRow->xuid, szViewName ), true );
// Set his rank and rating
pUserInfo->SetUint64( ":rank", pRow->dwRank );
pUserInfo->SetUint64( ":rows", pView->dwTotalViewRows );
pUserInfo->SetUint64( ":rating", pRow->i64Rating );
if ( char const *szName = pViewDesc->GetString( ":rating/name", NULL ) )
{
if ( szName[0] )
pUserInfo->SetUint64( szName, pRow->i64Rating );
}
// Process additional columns that were retrieved
Assert( !pRow->dwNumColumns );
// for ( DWORD c = 0; c < pRow->dwNumColumns; ++ c )
// {
// XUSER_STATS_COLUMN const *pCol = pRow->pColumns[ c ];
// KeyValues *pColDesc = FindViewColumnDesc( pViewDesc, pCol->wColumnId )
// }
}
}
DevMsg( "LeaderboardRequestQueue: ProcessResults finished.\n" );
}
#elif !defined( NO_STEAM )
void CLeaderboardRequestQueue::ProcessResults( LeaderboardEntry_t const &lbe )
{
/*
901D41D61DC61 // XUID %llx
{
survival_c5m2_park
{
:rank = 923 // uint64
:rows = 999 // uint64
:rating = 600 // uint64
besttime = 600 // uint64
}
... more views ...
}
... more users ...
*/
KeyValues *pViewDesc = m_pViewDescription;
Assert( pViewDesc );
if ( !pViewDesc )
{
Warning( "LeaderboardRequestQueue: ProcessResults has no view description for view!\n" );
return;
}
char const *szViewName = pViewDesc->GetString( ":name" );
DevMsg( " Processing view %s\n", szViewName );
DevMsg( " Gamer data loaded: rank=%d, score=%d\n",
lbe.m_nGlobalRank, lbe.m_nScore );
// Gamer is present in the leaderboard and should be included in the results
if ( !m_pFinishedRequest )
m_pFinishedRequest = new KeyValues( "Leaderboard" );
// Find or create the view for this gamer
KeyValues *pUserInfo = m_pFinishedRequest->FindKey( CFmtStr( "%llx/%s",
g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() )->GetXUID(), szViewName ), true );
// Set user score
pUserInfo->SetUint64( pViewDesc->GetString( ":score" ), lbe.m_nScore );
DevMsg( "LeaderboardRequestQueue: ProcessResults finished.\n" );
}
#endif
#ifdef _X360
KeyValues * CLeaderboardRequestQueue::FindViewDescription( DWORD dwViewId )
{
if ( !dwViewId )
return NULL;
for ( int k = 0; k < m_arrViewDescriptions.Count(); ++ k )
{
KeyValues *pDesc = m_arrViewDescriptions[k];
if ( pDesc->GetInt( ":id" ) == ( int ) dwViewId )
return pDesc;
}
return NULL;
}
#elif !defined( NO_STEAM )
void CLeaderboardRequestQueue::Steam_OnLeaderboardFindResult( LeaderboardFindResult_t *p, bool bError )
{
if ( bError || !p->m_bLeaderboardFound )
{
DevMsg( "Steam leaderboard was not found.\n" );
OnQueryFinished();
return;
}
// Download the data
SteamAPICall_t hCall = steamapicontext->SteamUserStats()->DownloadLeaderboardEntries( p->m_hSteamLeaderboard,
k_ELeaderboardDataRequestGlobalAroundUser, 0, 0 );
m_CallbackOnLeaderboardScoresDownloaded.Set( hCall, this, &CLeaderboardRequestQueue::Steam_OnLeaderboardScoresDownloaded );
}
void CLeaderboardRequestQueue::Steam_OnLeaderboardScoresDownloaded( LeaderboardScoresDownloaded_t *p, bool bError )
{
// Fetch the data if found and no error
LeaderboardEntry_t lbe;
if ( !bError &&
p->m_cEntryCount == 1 &&
steamapicontext->SteamUserStats()->GetDownloadedLeaderboardEntry( p->m_hSteamLeaderboardEntries, 0, &lbe, NULL, 0 ) )
{
ProcessResults( lbe );
}
OnQueryFinished();
}
#endif