Team Fortress 2 Source Code as on 22/4/2020
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.

343 lines
9.3 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "dod_playerstats.h"
  9. #include "hud_macros.h"
  10. const char *g_pszShortTeamNames[2] =
  11. {
  12. "US",
  13. "Ger"
  14. };
  15. const char *g_pszClassNames[NUM_DOD_PLAYERCLASSES] =
  16. {
  17. "Rifleman",
  18. "Assault",
  19. "Support",
  20. "Sniper",
  21. "MG",
  22. "Rocket"
  23. };
  24. const char *g_pszStatNames[DODSTAT_MAX] =
  25. {
  26. "iPlayTime",
  27. "iRoundsWon",
  28. "iRoundsLost",
  29. "iKills",
  30. "iDeaths",
  31. "iCaptures",
  32. "iBlocks",
  33. "iBombsPlanted",
  34. "iBombsDefused",
  35. "iDominations",
  36. "iRevenges",
  37. "iShotsHit",
  38. "iShotsFired",
  39. "iHeadshots"
  40. };
  41. int iValidWeaponStatBitMask = ( (1<<DODSTAT_KILLS ) | (1<<DODSTAT_SHOTS_HIT) | (1<<DODSTAT_SHOTS_FIRED) | (1<<DODSTAT_HEADSHOTS) );
  42. int iValidPlayerStatBitMask = 0xFFFF & ~( (1<<DODSTAT_SHOTS_HIT) | (1<<DODSTAT_SHOTS_FIRED) | (1<<DODSTAT_HEADSHOTS) );
  43. //-----------------------------------------------------------------------------
  44. // Purpose:
  45. // Input : &msg -
  46. //-----------------------------------------------------------------------------
  47. void __MsgFunc_DODPlayerStatsUpdate( bf_read &msg )
  48. {
  49. g_DODPlayerStats.MsgFunc_DODPlayerStatsUpdate( msg );
  50. }
  51. //-----------------------------------------------------------------------------
  52. // Purpose: Constructor
  53. //-----------------------------------------------------------------------------
  54. CDODPlayerStats::CDODPlayerStats()
  55. {
  56. m_flTimeNextForceUpload = 0;
  57. }
  58. //-----------------------------------------------------------------------------
  59. // Purpose: called at init time after all systems are init'd. We have to
  60. // do this in PostInit because the Steam app ID is not available earlier
  61. //-----------------------------------------------------------------------------
  62. void CDODPlayerStats::PostInit()
  63. {
  64. SetNextForceUploadTime();
  65. ListenForGameEvent( "player_stats_updated" );
  66. ListenForGameEvent( "user_data_downloaded" );
  67. HOOK_MESSAGE( DODPlayerStatsUpdate );
  68. }
  69. //-----------------------------------------------------------------------------
  70. // Purpose: called at level shutdown
  71. //-----------------------------------------------------------------------------
  72. void CDODPlayerStats::LevelShutdownPreEntity()
  73. {
  74. // upload user stats to Steam on every map change or when we quit
  75. UploadStats();
  76. }
  77. //-----------------------------------------------------------------------------
  78. // Purpose: called when the stats have changed in-game
  79. //-----------------------------------------------------------------------------
  80. void CDODPlayerStats::FireGameEvent( IGameEvent *event )
  81. {
  82. const char *pEventName = event->GetName();
  83. if ( FStrEq( pEventName, "user_data_downloaded" ) )
  84. {
  85. // Store our stat data
  86. Assert( steamapicontext->SteamUserStats() );
  87. if ( !steamapicontext->SteamUserStats() )
  88. return;
  89. CGameID gameID( engine->GetAppID() );
  90. // reset everything
  91. Q_memset( &m_PlayerStats, 0, sizeof(m_PlayerStats) );
  92. Q_memset( &m_WeaponStats, 0, sizeof(m_WeaponStats) );
  93. // read playerclass stats
  94. for ( int iTeam=0;iTeam<2;iTeam++ )
  95. {
  96. for ( int iClass=0;iClass<NUM_DOD_PLAYERCLASSES;iClass++ )
  97. {
  98. for ( int iStat=0;iStat<DODSTAT_MAX;iStat++ )
  99. {
  100. if ( iValidPlayerStatBitMask & (1<<iStat) )
  101. {
  102. char szStatName[256];
  103. int iData;
  104. Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.%s.%s", g_pszShortTeamNames[iTeam], g_pszClassNames[iClass], g_pszStatNames[iStat] );
  105. if ( steamapicontext->SteamUserStats()->GetStat( szStatName, &iData ) )
  106. {
  107. // use Steam's value
  108. m_PlayerStats[iTeam][iClass].m_iStat[iStat] = iData;
  109. }
  110. }
  111. }
  112. }
  113. }
  114. // read weapon stats
  115. for ( int iWeapon=WEAPON_NONE+1;iWeapon<WEAPON_MAX;iWeapon++ )
  116. {
  117. for ( int iStat=0;iStat<DODSTAT_MAX;iStat++ )
  118. {
  119. if ( iValidWeaponStatBitMask & (1<<iStat) )
  120. {
  121. char szStatName[256];
  122. int iData;
  123. Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.%s", s_WeaponAliasInfo[iWeapon], g_pszStatNames[iStat] );
  124. if ( steamapicontext->SteamUserStats()->GetStat( szStatName, &iData ) )
  125. {
  126. // use Steam's value
  127. m_WeaponStats[iWeapon].m_iStat[iStat] = iData;
  128. }
  129. }
  130. }
  131. }
  132. IGameEvent * event = gameeventmanager->CreateEvent( "player_stats_updated" );
  133. if ( event )
  134. {
  135. event->SetBool( "forceupload", false );
  136. gameeventmanager->FireEventClientSide( event );
  137. }
  138. }
  139. }
  140. void CDODPlayerStats::MsgFunc_DODPlayerStatsUpdate( bf_read &msg )
  141. {
  142. dod_stat_accumulator_t playerStats;
  143. Q_memset( &playerStats, 0, sizeof(playerStats) );
  144. // get the fixed-size information
  145. int iClass = msg.ReadByte();
  146. int iTeam = msg.ReadByte();
  147. int iPlayerStatBits = msg.ReadLong();
  148. Assert( iClass >= 0 && iClass <= 5 );
  149. if ( iClass < 0 || iClass > 5 )
  150. return;
  151. // the bitfield indicates which stats are contained in the message. Set the stats appropriately.
  152. int iStat = DODSTAT_FIRST;
  153. while ( iPlayerStatBits > 0 )
  154. {
  155. if ( iPlayerStatBits & 1 )
  156. {
  157. playerStats.m_iStat[iStat] = msg.ReadLong();
  158. }
  159. iPlayerStatBits >>= 1;
  160. iStat++;
  161. }
  162. int iNumWeapons = msg.ReadByte();
  163. int i;
  164. CUtlVector<weapon_stat_t> vecWeaponStats;
  165. for ( i=0;i<iNumWeapons;i++ )
  166. {
  167. weapon_stat_t weaponStat;
  168. Q_memset( &weaponStat, 0, sizeof(weaponStat) );
  169. weaponStat.iWeaponID = msg.ReadByte();
  170. vecWeaponStats.AddToTail( weaponStat );
  171. }
  172. for ( i=0;i<vecWeaponStats.Count();i++ )
  173. {
  174. int iWeaponStatMask = msg.ReadLong();
  175. int iStat = DODSTAT_FIRST;
  176. while ( iWeaponStatMask > 0 )
  177. {
  178. if ( iWeaponStatMask & 1 )
  179. {
  180. vecWeaponStats[i].stats.m_iStat[iStat] = msg.ReadLong();
  181. }
  182. iWeaponStatMask >>= 1;
  183. iStat++;
  184. }
  185. }
  186. // sanity check: the message should contain exactly the # of bytes we expect based on the bit field
  187. Assert( !msg.IsOverflowed() );
  188. Assert( 0 == msg.GetNumBytesLeft() );
  189. // if byte count isn't correct, bail out and don't use this data, rather than risk polluting player stats with garbage
  190. if ( msg.IsOverflowed() || ( 0 != msg.GetNumBytesLeft() ) )
  191. return;
  192. UpdateStats( iClass, iTeam, &playerStats, &vecWeaponStats );
  193. }
  194. //-----------------------------------------------------------------------------
  195. // Purpose: Uploads stats for current Steam user to Steam
  196. //-----------------------------------------------------------------------------
  197. void CDODPlayerStats::UploadStats()
  198. {
  199. // only upload if Steam is running
  200. if ( !steamapicontext->SteamUserStats() )
  201. return;
  202. CGameID gameID( engine->GetAppID() );
  203. char szStatName[256];
  204. // write playerclass stats
  205. for ( int iTeam=0;iTeam<2;iTeam++ )
  206. {
  207. for ( int iClass=0;iClass<NUM_DOD_PLAYERCLASSES;iClass++ )
  208. {
  209. for ( int iStat=0;iStat<DODSTAT_MAX;iStat++ )
  210. {
  211. if ( iValidPlayerStatBitMask & (1<<iStat) )
  212. {
  213. if ( m_PlayerStats[iTeam][iClass].m_bDirty[iStat] )
  214. {
  215. Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.%s.%s", g_pszShortTeamNames[iTeam], g_pszClassNames[iClass], g_pszStatNames[iStat] );
  216. steamapicontext->SteamUserStats()->SetStat( szStatName, m_PlayerStats[iTeam][iClass].m_iStat[iStat] );
  217. }
  218. }
  219. }
  220. }
  221. }
  222. // write weapon stats
  223. for ( int iWeapon=WEAPON_NONE+1;iWeapon<WEAPON_MAX;iWeapon++ )
  224. {
  225. for ( int iStat=0;iStat<DODSTAT_MAX;iStat++ )
  226. {
  227. if ( m_WeaponStats[iWeapon].m_bDirty[iStat] )
  228. {
  229. if ( iValidWeaponStatBitMask & (1<<iStat) )
  230. {
  231. Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.%s", s_WeaponAliasInfo[iWeapon], g_pszStatNames[iStat] );
  232. steamapicontext->SteamUserStats()->SetStat( szStatName, m_WeaponStats[iWeapon].m_iStat[iStat] );
  233. }
  234. }
  235. }
  236. }
  237. SetNextForceUploadTime();
  238. }
  239. void CDODPlayerStats::UpdateStats( int iPlayerClass, int iTeam, dod_stat_accumulator_t *playerStats, CUtlVector<weapon_stat_t> *vecWeaponStats )
  240. {
  241. Assert( iPlayerClass >= 0 && iPlayerClass < NUM_DOD_PLAYERCLASSES );
  242. if ( iPlayerClass < 0 || iPlayerClass >= NUM_DOD_PLAYERCLASSES )
  243. return;
  244. // translate the team index into [0,1]
  245. int iTeamIndex = ( iTeam == TEAM_ALLIES ) ? 0 : 1;
  246. // upload this class' data
  247. // add this stat update to our stored stats
  248. for ( int iStat=0;iStat<DODSTAT_MAX;iStat++ )
  249. {
  250. if ( iValidPlayerStatBitMask & (1<<iStat) )
  251. {
  252. if ( playerStats->m_iStat[iStat] > 0 )
  253. {
  254. m_PlayerStats[iTeamIndex][iPlayerClass].m_iStat[iStat] += playerStats->m_iStat[iStat];
  255. m_PlayerStats[iTeamIndex][iPlayerClass].m_bDirty[iStat] = true;
  256. }
  257. }
  258. }
  259. int iWeaponStatCount = vecWeaponStats->Count();
  260. for ( int i=0;i<iWeaponStatCount;i++ )
  261. {
  262. int iWeaponID = vecWeaponStats->Element(i).iWeaponID;
  263. for ( int iStat=0;iStat<DODSTAT_MAX;iStat++ )
  264. {
  265. if ( iValidWeaponStatBitMask & (1<<iStat) )
  266. {
  267. int iValue = vecWeaponStats->Element(i).stats.m_iStat[iStat];
  268. if ( iValue > 0 )
  269. {
  270. m_WeaponStats[iWeaponID].m_iStat[iStat] += iValue;
  271. m_WeaponStats[iWeaponID].m_bDirty[iStat] = true;
  272. }
  273. }
  274. }
  275. }
  276. // if we haven't uploaded stats in a long time, upload them
  277. if ( ( gpGlobals->curtime >= m_flTimeNextForceUpload ) )
  278. {
  279. UploadStats();
  280. }
  281. }
  282. //-----------------------------------------------------------------------------
  283. // Purpose: sets the next time to force a stats upload at
  284. //-----------------------------------------------------------------------------
  285. void CDODPlayerStats::SetNextForceUploadTime()
  286. {
  287. // pick a time a while from now (an hour +/- 15 mins) to upload stats if we haven't gotten a map change by then
  288. m_flTimeNextForceUpload = gpGlobals->curtime + ( 60 * RandomInt( 45, 75 ) );
  289. }
  290. CDODPlayerStats g_DODPlayerStats;