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.
448 lines
14 KiB
448 lines
14 KiB
//===== Copyright © 1996-2009, Valve Corporation, All rights reserved. ======//
|
|
//
|
|
// Purpose:
|
|
//
|
|
//===========================================================================//
|
|
|
|
#include "mm_framework.h"
|
|
|
|
#include "fmtstr.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
#if !defined( NO_STEAM ) && !defined( SWDS )
|
|
|
|
CInterlockedInt g_numSteamLeaderboardWriters;
|
|
|
|
|
|
class CSteamLeaderboardWriter
|
|
{
|
|
public:
|
|
CSteamLeaderboardWriter( KeyValues *pViewDescription, KeyValues *pViewData );
|
|
~CSteamLeaderboardWriter();
|
|
|
|
protected:
|
|
void UploadScore(SteamLeaderboard_t leaderboardHandle);
|
|
|
|
protected:
|
|
CCallResult< CSteamLeaderboardWriter, LeaderboardFindResult_t > m_CallbackOnLeaderboardFindResult;
|
|
void Steam_OnLeaderboardFindResult( LeaderboardFindResult_t *p, bool bError );
|
|
|
|
CCallResult< CSteamLeaderboardWriter, LeaderboardScoreUploaded_t > m_CallbackOnLeaderboardScoreUploaded;
|
|
void Steam_OnLeaderboardScoreUploaded( LeaderboardScoreUploaded_t *p, bool bError );
|
|
|
|
CCallResult< CSteamLeaderboardWriter, LeaderboardScoresDownloaded_t > m_CallbackOnLeaderboardScoresDownloaded;
|
|
void Steam_OnLeaderboardScoresDownloaded( LeaderboardScoresDownloaded_t *p, bool bError );
|
|
|
|
protected:
|
|
KeyValues *m_pViewDescription;
|
|
KeyValues *m_pViewData;
|
|
|
|
int m_nViewDescriptionPayloadFormatSize;
|
|
};
|
|
|
|
CSteamLeaderboardWriter::CSteamLeaderboardWriter( KeyValues *pViewDescription, KeyValues *pViewData ) :
|
|
m_pViewDescription( pViewDescription->MakeCopy() ),
|
|
m_pViewData( pViewData->MakeCopy() ),
|
|
m_nViewDescriptionPayloadFormatSize( 0 )
|
|
{
|
|
SteamAPICall_t hCall;
|
|
|
|
if ( m_pViewDescription->GetBool( ":nocreate" ) )
|
|
hCall = steamapicontext->SteamUserStats()->FindLeaderboard( m_pViewData->GetName() );
|
|
else
|
|
hCall = steamapicontext->SteamUserStats()->FindOrCreateLeaderboard(
|
|
m_pViewData->GetName(),
|
|
( ELeaderboardSortMethod ) m_pViewDescription->GetInt( ":sort" ),
|
|
( ELeaderboardDisplayType ) m_pViewDescription->GetInt( ":format" ) );
|
|
|
|
m_CallbackOnLeaderboardFindResult.Set( hCall, this, &CSteamLeaderboardWriter::Steam_OnLeaderboardFindResult );
|
|
|
|
++ g_numSteamLeaderboardWriters;
|
|
}
|
|
|
|
void CSteamLeaderboardWriter::Steam_OnLeaderboardFindResult( LeaderboardFindResult_t *p, bool bError )
|
|
{
|
|
if ( bError )
|
|
{
|
|
Warning( "Failed to contact leaderboard server for '%s'\n", m_pViewData->GetName() );
|
|
delete this;
|
|
return;
|
|
}
|
|
|
|
if ( !p->m_bLeaderboardFound )
|
|
{
|
|
DevWarning( "Leaderboard '%s' was not found on server\n", m_pViewData->GetName() );
|
|
delete this;
|
|
return;
|
|
}
|
|
|
|
// If the view description contains the keyvalue ":sumscore 1" or anything in the ":payloadformat"
|
|
// section containes ":upload sum" then we want to treat the score in the view data as a delta of
|
|
// the data that is currently uploaded to the scoreboard. So first we have to read the data, then
|
|
// aggregate the values. Also compute the payload size.
|
|
bool bHasSum = m_pViewDescription->GetBool( ":scoresum" );
|
|
bHasSum = bHasSum || m_pViewDescription->GetString( ":scoreformula", NULL );
|
|
int nPayloadSize = 0;
|
|
|
|
KeyValues *pPayloadFormat = m_pViewDescription->FindKey( ":payloadformat" );
|
|
|
|
if ( pPayloadFormat )
|
|
{
|
|
// Iterate over each payload entry to see if any of them need to be summed.
|
|
for ( int payloadIndex=0; ; ++payloadIndex )
|
|
{
|
|
KeyValues *pPayload = pPayloadFormat->FindKey( CFmtStr( "payload%d", payloadIndex ) );
|
|
if ( !pPayload )
|
|
{
|
|
// No more payload entries specified.
|
|
break;
|
|
}
|
|
|
|
const char* pszFormat = pPayload->GetString( ":format", NULL );
|
|
if ( V_stricmp( pszFormat, "int" ) == 0 )
|
|
{
|
|
nPayloadSize += sizeof( int );
|
|
}
|
|
else if ( V_stricmp( pszFormat, "uint64" ) == 0 )
|
|
{
|
|
nPayloadSize += sizeof( uint64 );
|
|
}
|
|
else
|
|
{
|
|
Warning( "Leaderboard description '%s' contains an invalid payload :format '%s'", m_pViewData->GetName(), pPayload->GetName() );
|
|
delete this;
|
|
return;
|
|
}
|
|
|
|
const char* pszUpload = pPayload->GetString( ":upload", NULL );
|
|
if ( !pszUpload || V_stricmp( pszUpload, "last" ) == 0 )
|
|
{
|
|
// just overwrite the leaderboard value with our current value
|
|
}
|
|
else if ( V_stricmp( pszUpload, "sum" ) == 0 )
|
|
{
|
|
bHasSum = true;
|
|
}
|
|
else
|
|
{
|
|
Warning( "Leaderboard description '%s' contains an invalid payload :format '%s'", m_pViewData->GetName(), pPayload->GetName() );
|
|
delete this;
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_nViewDescriptionPayloadFormatSize = nPayloadSize;
|
|
}
|
|
|
|
if ( bHasSum )
|
|
{
|
|
CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
|
|
|
|
// We need to download this user's current leaderboard data first.
|
|
DevMsg( "Downloading score for leaderboard '%s', steam id '%llu'...\n", m_pViewData->GetName(), steamID.ConvertToUint64() );
|
|
SteamAPICall_t hCall = steamapicontext->SteamUserStats()->DownloadLeaderboardEntriesForUsers( p->m_hSteamLeaderboard, &steamID, 1 );
|
|
m_CallbackOnLeaderboardScoresDownloaded.Set( hCall, this, &CSteamLeaderboardWriter::Steam_OnLeaderboardScoresDownloaded );
|
|
}
|
|
else
|
|
{
|
|
UploadScore( p->m_hSteamLeaderboard );
|
|
}
|
|
}
|
|
|
|
void CSteamLeaderboardWriter::Steam_OnLeaderboardScoreUploaded( LeaderboardScoreUploaded_t *p, bool bError )
|
|
{
|
|
if ( bError )
|
|
{
|
|
Warning( "Failed to upload leaderboard score for '%s'\n", m_pViewData->GetName() );
|
|
}
|
|
else if ( !p->m_bSuccess )
|
|
{
|
|
Warning( "Failed to update leaderboard score for '%s'\n", m_pViewData->GetName() );
|
|
}
|
|
else if ( !p->m_bScoreChanged )
|
|
{
|
|
DevMsg( "Leaderboard score uploaded, but not changed for '%s'\n", m_pViewData->GetName() );
|
|
}
|
|
else
|
|
{
|
|
DevMsg( "Leaderboard score uploaded for '%s', new rank %d, old rank %d\n", m_pViewData->GetName(), p->m_nGlobalRankNew, p->m_nGlobalRankPrevious );
|
|
}
|
|
|
|
delete this;
|
|
}
|
|
|
|
void CSteamLeaderboardWriter::Steam_OnLeaderboardScoresDownloaded( LeaderboardScoresDownloaded_t *p, bool bError )
|
|
{
|
|
if ( bError )
|
|
{
|
|
Warning( "Failed to download leaderboard score for '%s'\n", m_pViewData->GetName() );
|
|
delete this;
|
|
return;
|
|
}
|
|
|
|
// Now that we have the downloaded leaderboard data, we need to extract it, and sum the appropriate values
|
|
// then write the data back out.
|
|
if ( p->m_cEntryCount == 1 )
|
|
{
|
|
DevMsg( "Parsing downloaded scores for '%s'\n", m_pViewData->GetName() );
|
|
|
|
// We have the one entry we were looking for, so extract the current data from it.
|
|
LeaderboardEntry_t leaderboardEntry;
|
|
int32 *pPayloadData = new int32[m_nViewDescriptionPayloadFormatSize];
|
|
if ( steamapicontext->SteamUserStats()->GetDownloadedLeaderboardEntry( p->m_hSteamLeaderboardEntries, 0, &leaderboardEntry, pPayloadData, m_nViewDescriptionPayloadFormatSize ) )
|
|
{
|
|
unsigned char *pCurrentPayload = (unsigned char*)pPayloadData;
|
|
|
|
// If the ranked score should be summed with the current ranked score from the leaderboard entry,
|
|
// do that now.
|
|
if ( m_pViewDescription->GetBool( ":scoresum" ) )
|
|
{
|
|
const char *pScore = m_pViewDescription->GetString( ":score", NULL );
|
|
m_pViewData->SetInt( pScore, leaderboardEntry.m_nScore + m_pViewData->GetInt( pScore ) );
|
|
}
|
|
|
|
// Iterate over payload item and perform the appropriate aggregation method, then write the result
|
|
// to the view data.
|
|
// Iterate over all of the payload format information and extract the corresponding data from the view data.
|
|
for ( int payloadIndex=0; ; ++payloadIndex )
|
|
{
|
|
KeyValues *pPayload = m_pViewDescription->FindKey( CFmtStr( ":payloadformat/payload%d", payloadIndex ) );
|
|
if ( !pPayload )
|
|
{
|
|
// No more payload entries specified.
|
|
break;
|
|
}
|
|
|
|
// Get payload format, aggregate it if necessary, then advance the current payload pointer.
|
|
const char *pFormat = pPayload->GetString( ":format", NULL );
|
|
const char *pScore = pPayload->GetString( ":score", NULL );
|
|
const char *pUpload = pPayload->GetString( ":upload", NULL );
|
|
|
|
if ( V_stricmp( pFormat, "int" ) == 0 )
|
|
{
|
|
if ( pUpload && V_stricmp( pUpload, "sum" ) == 0 )
|
|
{
|
|
int score = *(int*)pCurrentPayload;
|
|
m_pViewData->SetInt( pScore, score + m_pViewData->GetInt( pScore ) );
|
|
}
|
|
|
|
pCurrentPayload += sizeof( int );
|
|
}
|
|
else if ( V_stricmp( pFormat, "uint64" ) == 0 )
|
|
{
|
|
if ( pUpload && V_stricmp( pUpload, "sum" ) == 0 )
|
|
{
|
|
uint64 score = *(uint64*)pCurrentPayload;
|
|
m_pViewData->SetUint64( pScore, score + m_pViewData->GetUint64( pScore ) );
|
|
}
|
|
|
|
pCurrentPayload += sizeof( uint64 );
|
|
}
|
|
else
|
|
{
|
|
Warning( "Leaderboard description '%s' contains an invalid payload :format '%s'", m_pViewData->GetName(), pPayload->GetName() );
|
|
delete [] pPayloadData;
|
|
delete this;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Now that we've aggregated all of the data, we need to check to see if the ranking data
|
|
// (the data associated with ":score") needs to have a forumla applied to it.
|
|
const char* pszScoreFormula = m_pViewDescription->GetString( ":scoreformula", NULL );
|
|
if ( pszScoreFormula )
|
|
{
|
|
// Create an expression from the string.
|
|
CExpressionCalculator calc( pszScoreFormula );
|
|
|
|
// Set variables that correspond to each payload.
|
|
for ( int payloadIndex=0; ; ++payloadIndex )
|
|
{
|
|
CFmtStr payloadName = CFmtStr( "payload%d", payloadIndex );
|
|
KeyValues *pPayload = m_pViewDescription->FindKey( CFmtStr( ":payloadformat/%s", payloadName.Access() ) );
|
|
if ( !pPayload )
|
|
{
|
|
// No more payload entries specified.
|
|
break;
|
|
}
|
|
|
|
// Get payload format and score.
|
|
const char *pFormat = pPayload->GetString( ":format", NULL );
|
|
const char *pScore = pPayload->GetString( ":score", NULL );
|
|
|
|
if ( V_stricmp( pFormat, "int" ) == 0 )
|
|
{
|
|
int value = m_pViewData->GetInt( pScore );
|
|
calc.SetVariable( payloadName, value );
|
|
}
|
|
else if ( V_stricmp( pFormat, "uint64" ) == 0 )
|
|
{
|
|
uint64 value = m_pViewData->GetUint64( pScore );
|
|
calc.SetVariable( payloadName, value );
|
|
}
|
|
else
|
|
{
|
|
Warning( "Leaderboard description '%s' contains an invalid payload :format '%s'", m_pViewData->GetName(), pPayload->GetName() );
|
|
delete [] pPayloadData;
|
|
delete this;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Evaluate the expression and apply it to our view data.
|
|
float value = 0.0f;
|
|
if ( calc.Evaluate( value ) )
|
|
{
|
|
m_pViewData->SetInt( m_pViewDescription->GetString( ":score", NULL ), (int)value );
|
|
}
|
|
else
|
|
{
|
|
Warning( "Failed to evaluate leaderboard expression.\n"
|
|
"\tLeaderboard = %s\n"
|
|
"\tExpression: %s\n",
|
|
m_pViewData->GetName(),
|
|
pszScoreFormula
|
|
);
|
|
delete [] pPayloadData;
|
|
delete this;
|
|
return;
|
|
}
|
|
}
|
|
|
|
delete [] pPayloadData;
|
|
}
|
|
else
|
|
{
|
|
Warning( "Failed to download leaderboard score for '%s'\n", m_pViewData->GetName() );
|
|
delete [] pPayloadData;
|
|
delete this;
|
|
return;
|
|
}
|
|
}
|
|
|
|
UploadScore( p->m_hSteamLeaderboard );
|
|
}
|
|
|
|
void CSteamLeaderboardWriter::UploadScore(SteamLeaderboard_t leaderboardHandle)
|
|
{
|
|
unsigned char *pvPayloadPtr = (unsigned char *)m_pViewData->GetPtr( ":payloadptr" );
|
|
int nPayloadSize = m_pViewData->GetInt( ":payloadsize" );
|
|
|
|
// If the view description contains ":payloadformat", then we need to construct the payload pointer
|
|
// based on that description and the view data.
|
|
KeyValues *pPayloadFormat = m_pViewDescription->FindKey( ":payloadformat" );
|
|
if ( pPayloadFormat )
|
|
{
|
|
if ( pvPayloadPtr )
|
|
{
|
|
Warning( "Leaderboard data and description '%s' contain both :payloadptr and :payloadformat.\n", m_pViewData->GetName() );
|
|
delete this;
|
|
return;
|
|
}
|
|
|
|
// Allocate a buffer for the payload.
|
|
if ( m_nViewDescriptionPayloadFormatSize > 0 )
|
|
{
|
|
nPayloadSize = m_nViewDescriptionPayloadFormatSize;
|
|
pvPayloadPtr = new unsigned char[nPayloadSize];
|
|
|
|
unsigned char *pvCurPayloadPtr = (unsigned char*)pvPayloadPtr;
|
|
memset( pvCurPayloadPtr, 0, nPayloadSize );
|
|
|
|
// Iterate over all of the payload format information and extract the corresponding data from the view data.
|
|
for ( int payloadIndex=0; ; ++payloadIndex )
|
|
{
|
|
KeyValues *pPayload = pPayloadFormat->FindKey( CFmtStr( "payload%d", payloadIndex ) );
|
|
if ( !pPayload )
|
|
{
|
|
// No more payload entries specified.
|
|
break;
|
|
}
|
|
|
|
// Add assert to make sure payload pointer is still in bounds.
|
|
|
|
// Get the appropriate entry from the view data and write it to our payload buffer.
|
|
const char* pszFormat = pPayload->GetString( ":format", NULL );
|
|
const char* pszScore = pPayload->GetString( ":score", NULL );
|
|
if ( V_stricmp( pszFormat, "int" ) == 0 )
|
|
{
|
|
int score = m_pViewData->GetInt( pszScore );
|
|
#if defined( PLAT_BIG_ENDIAN )
|
|
// On big-endian platforms, byteswap our scores so we always write the online data as little endian
|
|
*(int*)pvCurPayloadPtr = DWordSwap( score );
|
|
#else
|
|
*(int*)pvCurPayloadPtr = score;
|
|
#endif
|
|
pvCurPayloadPtr += sizeof( int );
|
|
}
|
|
else if ( V_stricmp( pszFormat, "uint64" ) == 0 )
|
|
{
|
|
uint64 score = m_pViewData->GetUint64( pszScore );
|
|
#if defined( PLAT_BIG_ENDIAN )
|
|
// On big-endian platforms, byteswap our scores so we always write the online data as little endian
|
|
*(int*)pvCurPayloadPtr = QWordSwap( score );
|
|
#else
|
|
*(uint64*)pvCurPayloadPtr = score;
|
|
#endif
|
|
pvCurPayloadPtr += sizeof( uint64 );
|
|
}
|
|
else
|
|
{
|
|
Warning( "Leaderboard description '%s' contains an invalid payload :format '%s'", m_pViewData->GetName(), pPayload->GetName() );
|
|
delete [] pvPayloadPtr;
|
|
delete this;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Add our constructed payload and the size to the view data.
|
|
m_pViewData->SetPtr( ":payloadptr", pvPayloadPtr );
|
|
m_pViewData->SetInt( ":payloadsize", nPayloadSize );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Upload score
|
|
//
|
|
int32 nScore = 0;
|
|
nScore = ( uint32 ) m_pViewData->GetUint64( m_pViewDescription->GetString( ":score" ) );
|
|
|
|
DevMsg( "Uploading score for leaderboard '%s'...\n", m_pViewData->GetName() );
|
|
KeyValuesDumpAsDevMsg( m_pViewData, 1 );
|
|
|
|
SteamAPICall_t hCall = steamapicontext->SteamUserStats()->UploadLeaderboardScore(
|
|
leaderboardHandle,
|
|
( ELeaderboardUploadScoreMethod ) m_pViewDescription->GetInt( ":upload" ),
|
|
nScore,
|
|
( int32 const *) pvPayloadPtr,
|
|
( nPayloadSize + sizeof( int32 ) - 1 ) / sizeof( int32 ) );
|
|
m_CallbackOnLeaderboardScoreUploaded.Set( hCall, this, &CSteamLeaderboardWriter::Steam_OnLeaderboardScoreUploaded );
|
|
}
|
|
|
|
CSteamLeaderboardWriter::~CSteamLeaderboardWriter()
|
|
{
|
|
// We need to delete leaderboard payload allocated by caller
|
|
delete [] ( char * ) m_pViewData->GetPtr( ":payloadptr" );
|
|
|
|
if ( m_pViewDescription )
|
|
m_pViewDescription->deleteThis();
|
|
|
|
if ( m_pViewData )
|
|
m_pViewData->deleteThis();
|
|
|
|
-- g_numSteamLeaderboardWriters;
|
|
}
|
|
|
|
void Steam_WriteLeaderboardData( KeyValues *pViewDescription, KeyValues *pViewData )
|
|
{
|
|
MEM_ALLOC_CREDIT();
|
|
|
|
// CSteamLeaderboardWriter is driven by Steam callbacks and will
|
|
// delete itself when finished
|
|
new CSteamLeaderboardWriter( pViewDescription, pViewData );
|
|
}
|
|
|
|
|
|
#endif
|