csgo-2018-source/matchmaking/steam_lobbyapi.cpp

449 lines
14 KiB
C++
Raw Normal View History

2021-07-25 12:11:47 +08:00
//===== Copyright <20> 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