|
|
//===== 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
|