541 lines
14 KiB
C++
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
|
|
|
|
|