Counter Strike : Global Offensive Source Code
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

  1. //===== Copyright � 1996-2009, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose:
  4. //
  5. //===========================================================================//
  6. #include "mm_framework.h"
  7. #include "fmtstr.h"
  8. // memdbgon must be the last include file in a .cpp file!!!
  9. #include "tier0/memdbgon.h"
  10. #if !defined( NO_STEAM ) && !defined( SWDS )
  11. CInterlockedInt g_numSteamLeaderboardWriters;
  12. class CSteamLeaderboardWriter
  13. {
  14. public:
  15. CSteamLeaderboardWriter( KeyValues *pViewDescription, KeyValues *pViewData );
  16. ~CSteamLeaderboardWriter();
  17. protected:
  18. void UploadScore(SteamLeaderboard_t leaderboardHandle);
  19. protected:
  20. CCallResult< CSteamLeaderboardWriter, LeaderboardFindResult_t > m_CallbackOnLeaderboardFindResult;
  21. void Steam_OnLeaderboardFindResult( LeaderboardFindResult_t *p, bool bError );
  22. CCallResult< CSteamLeaderboardWriter, LeaderboardScoreUploaded_t > m_CallbackOnLeaderboardScoreUploaded;
  23. void Steam_OnLeaderboardScoreUploaded( LeaderboardScoreUploaded_t *p, bool bError );
  24. CCallResult< CSteamLeaderboardWriter, LeaderboardScoresDownloaded_t > m_CallbackOnLeaderboardScoresDownloaded;
  25. void Steam_OnLeaderboardScoresDownloaded( LeaderboardScoresDownloaded_t *p, bool bError );
  26. protected:
  27. KeyValues *m_pViewDescription;
  28. KeyValues *m_pViewData;
  29. int m_nViewDescriptionPayloadFormatSize;
  30. };
  31. CSteamLeaderboardWriter::CSteamLeaderboardWriter( KeyValues *pViewDescription, KeyValues *pViewData ) :
  32. m_pViewDescription( pViewDescription->MakeCopy() ),
  33. m_pViewData( pViewData->MakeCopy() ),
  34. m_nViewDescriptionPayloadFormatSize( 0 )
  35. {
  36. SteamAPICall_t hCall;
  37. if ( m_pViewDescription->GetBool( ":nocreate" ) )
  38. hCall = steamapicontext->SteamUserStats()->FindLeaderboard( m_pViewData->GetName() );
  39. else
  40. hCall = steamapicontext->SteamUserStats()->FindOrCreateLeaderboard(
  41. m_pViewData->GetName(),
  42. ( ELeaderboardSortMethod ) m_pViewDescription->GetInt( ":sort" ),
  43. ( ELeaderboardDisplayType ) m_pViewDescription->GetInt( ":format" ) );
  44. m_CallbackOnLeaderboardFindResult.Set( hCall, this, &CSteamLeaderboardWriter::Steam_OnLeaderboardFindResult );
  45. ++ g_numSteamLeaderboardWriters;
  46. }
  47. void CSteamLeaderboardWriter::Steam_OnLeaderboardFindResult( LeaderboardFindResult_t *p, bool bError )
  48. {
  49. if ( bError )
  50. {
  51. Warning( "Failed to contact leaderboard server for '%s'\n", m_pViewData->GetName() );
  52. delete this;
  53. return;
  54. }
  55. if ( !p->m_bLeaderboardFound )
  56. {
  57. DevWarning( "Leaderboard '%s' was not found on server\n", m_pViewData->GetName() );
  58. delete this;
  59. return;
  60. }
  61. // If the view description contains the keyvalue ":sumscore 1" or anything in the ":payloadformat"
  62. // section containes ":upload sum" then we want to treat the score in the view data as a delta of
  63. // the data that is currently uploaded to the scoreboard. So first we have to read the data, then
  64. // aggregate the values. Also compute the payload size.
  65. bool bHasSum = m_pViewDescription->GetBool( ":scoresum" );
  66. bHasSum = bHasSum || m_pViewDescription->GetString( ":scoreformula", NULL );
  67. int nPayloadSize = 0;
  68. KeyValues *pPayloadFormat = m_pViewDescription->FindKey( ":payloadformat" );
  69. if ( pPayloadFormat )
  70. {
  71. // Iterate over each payload entry to see if any of them need to be summed.
  72. for ( int payloadIndex=0; ; ++payloadIndex )
  73. {
  74. KeyValues *pPayload = pPayloadFormat->FindKey( CFmtStr( "payload%d", payloadIndex ) );
  75. if ( !pPayload )
  76. {
  77. // No more payload entries specified.
  78. break;
  79. }
  80. const char* pszFormat = pPayload->GetString( ":format", NULL );
  81. if ( V_stricmp( pszFormat, "int" ) == 0 )
  82. {
  83. nPayloadSize += sizeof( int );
  84. }
  85. else if ( V_stricmp( pszFormat, "uint64" ) == 0 )
  86. {
  87. nPayloadSize += sizeof( uint64 );
  88. }
  89. else
  90. {
  91. Warning( "Leaderboard description '%s' contains an invalid payload :format '%s'", m_pViewData->GetName(), pPayload->GetName() );
  92. delete this;
  93. return;
  94. }
  95. const char* pszUpload = pPayload->GetString( ":upload", NULL );
  96. if ( !pszUpload || V_stricmp( pszUpload, "last" ) == 0 )
  97. {
  98. // just overwrite the leaderboard value with our current value
  99. }
  100. else if ( V_stricmp( pszUpload, "sum" ) == 0 )
  101. {
  102. bHasSum = true;
  103. }
  104. else
  105. {
  106. Warning( "Leaderboard description '%s' contains an invalid payload :format '%s'", m_pViewData->GetName(), pPayload->GetName() );
  107. delete this;
  108. return;
  109. }
  110. }
  111. m_nViewDescriptionPayloadFormatSize = nPayloadSize;
  112. }
  113. if ( bHasSum )
  114. {
  115. CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
  116. // We need to download this user's current leaderboard data first.
  117. DevMsg( "Downloading score for leaderboard '%s', steam id '%llu'...\n", m_pViewData->GetName(), steamID.ConvertToUint64() );
  118. SteamAPICall_t hCall = steamapicontext->SteamUserStats()->DownloadLeaderboardEntriesForUsers( p->m_hSteamLeaderboard, &steamID, 1 );
  119. m_CallbackOnLeaderboardScoresDownloaded.Set( hCall, this, &CSteamLeaderboardWriter::Steam_OnLeaderboardScoresDownloaded );
  120. }
  121. else
  122. {
  123. UploadScore( p->m_hSteamLeaderboard );
  124. }
  125. }
  126. void CSteamLeaderboardWriter::Steam_OnLeaderboardScoreUploaded( LeaderboardScoreUploaded_t *p, bool bError )
  127. {
  128. if ( bError )
  129. {
  130. Warning( "Failed to upload leaderboard score for '%s'\n", m_pViewData->GetName() );
  131. }
  132. else if ( !p->m_bSuccess )
  133. {
  134. Warning( "Failed to update leaderboard score for '%s'\n", m_pViewData->GetName() );
  135. }
  136. else if ( !p->m_bScoreChanged )
  137. {
  138. DevMsg( "Leaderboard score uploaded, but not changed for '%s'\n", m_pViewData->GetName() );
  139. }
  140. else
  141. {
  142. DevMsg( "Leaderboard score uploaded for '%s', new rank %d, old rank %d\n", m_pViewData->GetName(), p->m_nGlobalRankNew, p->m_nGlobalRankPrevious );
  143. }
  144. delete this;
  145. }
  146. void CSteamLeaderboardWriter::Steam_OnLeaderboardScoresDownloaded( LeaderboardScoresDownloaded_t *p, bool bError )
  147. {
  148. if ( bError )
  149. {
  150. Warning( "Failed to download leaderboard score for '%s'\n", m_pViewData->GetName() );
  151. delete this;
  152. return;
  153. }
  154. // Now that we have the downloaded leaderboard data, we need to extract it, and sum the appropriate values
  155. // then write the data back out.
  156. if ( p->m_cEntryCount == 1 )
  157. {
  158. DevMsg( "Parsing downloaded scores for '%s'\n", m_pViewData->GetName() );
  159. // We have the one entry we were looking for, so extract the current data from it.
  160. LeaderboardEntry_t leaderboardEntry;
  161. int32 *pPayloadData = new int32[m_nViewDescriptionPayloadFormatSize];
  162. if ( steamapicontext->SteamUserStats()->GetDownloadedLeaderboardEntry( p->m_hSteamLeaderboardEntries, 0, &leaderboardEntry, pPayloadData, m_nViewDescriptionPayloadFormatSize ) )
  163. {
  164. unsigned char *pCurrentPayload = (unsigned char*)pPayloadData;
  165. // If the ranked score should be summed with the current ranked score from the leaderboard entry,
  166. // do that now.
  167. if ( m_pViewDescription->GetBool( ":scoresum" ) )
  168. {
  169. const char *pScore = m_pViewDescription->GetString( ":score", NULL );
  170. m_pViewData->SetInt( pScore, leaderboardEntry.m_nScore + m_pViewData->GetInt( pScore ) );
  171. }
  172. // Iterate over payload item and perform the appropriate aggregation method, then write the result
  173. // to the view data.
  174. // Iterate over all of the payload format information and extract the corresponding data from the view data.
  175. for ( int payloadIndex=0; ; ++payloadIndex )
  176. {
  177. KeyValues *pPayload = m_pViewDescription->FindKey( CFmtStr( ":payloadformat/payload%d", payloadIndex ) );
  178. if ( !pPayload )
  179. {
  180. // No more payload entries specified.
  181. break;
  182. }
  183. // Get payload format, aggregate it if necessary, then advance the current payload pointer.
  184. const char *pFormat = pPayload->GetString( ":format", NULL );
  185. const char *pScore = pPayload->GetString( ":score", NULL );
  186. const char *pUpload = pPayload->GetString( ":upload", NULL );
  187. if ( V_stricmp( pFormat, "int" ) == 0 )
  188. {
  189. if ( pUpload && V_stricmp( pUpload, "sum" ) == 0 )
  190. {
  191. int score = *(int*)pCurrentPayload;
  192. m_pViewData->SetInt( pScore, score + m_pViewData->GetInt( pScore ) );
  193. }
  194. pCurrentPayload += sizeof( int );
  195. }
  196. else if ( V_stricmp( pFormat, "uint64" ) == 0 )
  197. {
  198. if ( pUpload && V_stricmp( pUpload, "sum" ) == 0 )
  199. {
  200. uint64 score = *(uint64*)pCurrentPayload;
  201. m_pViewData->SetUint64( pScore, score + m_pViewData->GetUint64( pScore ) );
  202. }
  203. pCurrentPayload += sizeof( uint64 );
  204. }
  205. else
  206. {
  207. Warning( "Leaderboard description '%s' contains an invalid payload :format '%s'", m_pViewData->GetName(), pPayload->GetName() );
  208. delete [] pPayloadData;
  209. delete this;
  210. return;
  211. }
  212. }
  213. // Now that we've aggregated all of the data, we need to check to see if the ranking data
  214. // (the data associated with ":score") needs to have a forumla applied to it.
  215. const char* pszScoreFormula = m_pViewDescription->GetString( ":scoreformula", NULL );
  216. if ( pszScoreFormula )
  217. {
  218. // Create an expression from the string.
  219. CExpressionCalculator calc( pszScoreFormula );
  220. // Set variables that correspond to each payload.
  221. for ( int payloadIndex=0; ; ++payloadIndex )
  222. {
  223. CFmtStr payloadName = CFmtStr( "payload%d", payloadIndex );
  224. KeyValues *pPayload = m_pViewDescription->FindKey( CFmtStr( ":payloadformat/%s", payloadName.Access() ) );
  225. if ( !pPayload )
  226. {
  227. // No more payload entries specified.
  228. break;
  229. }
  230. // Get payload format and score.
  231. const char *pFormat = pPayload->GetString( ":format", NULL );
  232. const char *pScore = pPayload->GetString( ":score", NULL );
  233. if ( V_stricmp( pFormat, "int" ) == 0 )
  234. {
  235. int value = m_pViewData->GetInt( pScore );
  236. calc.SetVariable( payloadName, value );
  237. }
  238. else if ( V_stricmp( pFormat, "uint64" ) == 0 )
  239. {
  240. uint64 value = m_pViewData->GetUint64( pScore );
  241. calc.SetVariable( payloadName, value );
  242. }
  243. else
  244. {
  245. Warning( "Leaderboard description '%s' contains an invalid payload :format '%s'", m_pViewData->GetName(), pPayload->GetName() );
  246. delete [] pPayloadData;
  247. delete this;
  248. return;
  249. }
  250. }
  251. // Evaluate the expression and apply it to our view data.
  252. float value = 0.0f;
  253. if ( calc.Evaluate( value ) )
  254. {
  255. m_pViewData->SetInt( m_pViewDescription->GetString( ":score", NULL ), (int)value );
  256. }
  257. else
  258. {
  259. Warning( "Failed to evaluate leaderboard expression.\n"
  260. "\tLeaderboard = %s\n"
  261. "\tExpression: %s\n",
  262. m_pViewData->GetName(),
  263. pszScoreFormula
  264. );
  265. delete [] pPayloadData;
  266. delete this;
  267. return;
  268. }
  269. }
  270. delete [] pPayloadData;
  271. }
  272. else
  273. {
  274. Warning( "Failed to download leaderboard score for '%s'\n", m_pViewData->GetName() );
  275. delete [] pPayloadData;
  276. delete this;
  277. return;
  278. }
  279. }
  280. UploadScore( p->m_hSteamLeaderboard );
  281. }
  282. void CSteamLeaderboardWriter::UploadScore(SteamLeaderboard_t leaderboardHandle)
  283. {
  284. unsigned char *pvPayloadPtr = (unsigned char *)m_pViewData->GetPtr( ":payloadptr" );
  285. int nPayloadSize = m_pViewData->GetInt( ":payloadsize" );
  286. // If the view description contains ":payloadformat", then we need to construct the payload pointer
  287. // based on that description and the view data.
  288. KeyValues *pPayloadFormat = m_pViewDescription->FindKey( ":payloadformat" );
  289. if ( pPayloadFormat )
  290. {
  291. if ( pvPayloadPtr )
  292. {
  293. Warning( "Leaderboard data and description '%s' contain both :payloadptr and :payloadformat.\n", m_pViewData->GetName() );
  294. delete this;
  295. return;
  296. }
  297. // Allocate a buffer for the payload.
  298. if ( m_nViewDescriptionPayloadFormatSize > 0 )
  299. {
  300. nPayloadSize = m_nViewDescriptionPayloadFormatSize;
  301. pvPayloadPtr = new unsigned char[nPayloadSize];
  302. unsigned char *pvCurPayloadPtr = (unsigned char*)pvPayloadPtr;
  303. memset( pvCurPayloadPtr, 0, nPayloadSize );
  304. // Iterate over all of the payload format information and extract the corresponding data from the view data.
  305. for ( int payloadIndex=0; ; ++payloadIndex )
  306. {
  307. KeyValues *pPayload = pPayloadFormat->FindKey( CFmtStr( "payload%d", payloadIndex ) );
  308. if ( !pPayload )
  309. {
  310. // No more payload entries specified.
  311. break;
  312. }
  313. // Add assert to make sure payload pointer is still in bounds.
  314. // Get the appropriate entry from the view data and write it to our payload buffer.
  315. const char* pszFormat = pPayload->GetString( ":format", NULL );
  316. const char* pszScore = pPayload->GetString( ":score", NULL );
  317. if ( V_stricmp( pszFormat, "int" ) == 0 )
  318. {
  319. int score = m_pViewData->GetInt( pszScore );
  320. #if defined( PLAT_BIG_ENDIAN )
  321. // On big-endian platforms, byteswap our scores so we always write the online data as little endian
  322. *(int*)pvCurPayloadPtr = DWordSwap( score );
  323. #else
  324. *(int*)pvCurPayloadPtr = score;
  325. #endif
  326. pvCurPayloadPtr += sizeof( int );
  327. }
  328. else if ( V_stricmp( pszFormat, "uint64" ) == 0 )
  329. {
  330. uint64 score = m_pViewData->GetUint64( pszScore );
  331. #if defined( PLAT_BIG_ENDIAN )
  332. // On big-endian platforms, byteswap our scores so we always write the online data as little endian
  333. *(int*)pvCurPayloadPtr = QWordSwap( score );
  334. #else
  335. *(uint64*)pvCurPayloadPtr = score;
  336. #endif
  337. pvCurPayloadPtr += sizeof( uint64 );
  338. }
  339. else
  340. {
  341. Warning( "Leaderboard description '%s' contains an invalid payload :format '%s'", m_pViewData->GetName(), pPayload->GetName() );
  342. delete [] pvPayloadPtr;
  343. delete this;
  344. return;
  345. }
  346. }
  347. // Add our constructed payload and the size to the view data.
  348. m_pViewData->SetPtr( ":payloadptr", pvPayloadPtr );
  349. m_pViewData->SetInt( ":payloadsize", nPayloadSize );
  350. }
  351. }
  352. //
  353. // Upload score
  354. //
  355. int32 nScore = 0;
  356. nScore = ( uint32 ) m_pViewData->GetUint64( m_pViewDescription->GetString( ":score" ) );
  357. DevMsg( "Uploading score for leaderboard '%s'...\n", m_pViewData->GetName() );
  358. KeyValuesDumpAsDevMsg( m_pViewData, 1 );
  359. SteamAPICall_t hCall = steamapicontext->SteamUserStats()->UploadLeaderboardScore(
  360. leaderboardHandle,
  361. ( ELeaderboardUploadScoreMethod ) m_pViewDescription->GetInt( ":upload" ),
  362. nScore,
  363. ( int32 const *) pvPayloadPtr,
  364. ( nPayloadSize + sizeof( int32 ) - 1 ) / sizeof( int32 ) );
  365. m_CallbackOnLeaderboardScoreUploaded.Set( hCall, this, &CSteamLeaderboardWriter::Steam_OnLeaderboardScoreUploaded );
  366. }
  367. CSteamLeaderboardWriter::~CSteamLeaderboardWriter()
  368. {
  369. // We need to delete leaderboard payload allocated by caller
  370. delete [] ( char * ) m_pViewData->GetPtr( ":payloadptr" );
  371. if ( m_pViewDescription )
  372. m_pViewDescription->deleteThis();
  373. if ( m_pViewData )
  374. m_pViewData->deleteThis();
  375. -- g_numSteamLeaderboardWriters;
  376. }
  377. void Steam_WriteLeaderboardData( KeyValues *pViewDescription, KeyValues *pViewData )
  378. {
  379. MEM_ALLOC_CREDIT();
  380. // CSteamLeaderboardWriter is driven by Steam callbacks and will
  381. // delete itself when finished
  382. new CSteamLeaderboardWriter( pViewDescription, pViewData );
  383. }
  384. #endif