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.

425 lines
12 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. //=======================================================================================//
  4. #include "cbase.h"
  5. #if defined( REPLAY_ENABLED )
  6. #include "genericclassbased_replay.h"
  7. #include "clientmode_shared.h"
  8. #include "replay/ireplaymoviemanager.h"
  9. #include "replay/ireplayfactory.h"
  10. #include "replay/ireplayscreenshotmanager.h"
  11. #include "replay/screenshot.h"
  12. #include "replay/gamedefs.h"
  13. #include <time.h>
  14. //----------------------------------------------------------------------------------------
  15. extern IReplayScreenshotManager *g_pReplayScreenshotManager;
  16. //----------------------------------------------------------------------------------------
  17. CGenericClassBasedReplay::CGenericClassBasedReplay()
  18. : m_nPlayerTeam( 0 )
  19. {
  20. m_szKillerName[ 0 ] = 0;
  21. m_nPlayerClass = REPLAY_CLASS_UNDEFINED;
  22. m_nKillerClass = REPLAY_CLASS_UNDEFINED;
  23. }
  24. CGenericClassBasedReplay::~CGenericClassBasedReplay()
  25. {
  26. OnEndRecording();
  27. m_vecKills.PurgeAndDeleteElements();
  28. m_vecDominations.PurgeAndDeleteElements();
  29. m_vecAssisterDominations.PurgeAndDeleteElements();
  30. m_vecRevenges.PurgeAndDeleteElements();
  31. m_vecAssisterRevenges.PurgeAndDeleteElements();
  32. }
  33. void CGenericClassBasedReplay::OnBeginRecording()
  34. {
  35. BaseClass::OnBeginRecording();
  36. Assert( gameeventmanager );
  37. ListenForGameEvent( "player_death" );
  38. }
  39. void CGenericClassBasedReplay::OnEndRecording()
  40. {
  41. if ( gameeventmanager )
  42. {
  43. gameeventmanager->RemoveListener( this );
  44. }
  45. BaseClass::OnEndRecording();
  46. }
  47. void CGenericClassBasedReplay::OnComplete()
  48. {
  49. BaseClass::OnComplete();
  50. }
  51. bool CGenericClassBasedReplay::ShouldAllowDelete() const
  52. {
  53. return g_pClientReplayContext->GetMovieManager()->GetNumMoviesDependentOnReplay( this ) == 0;
  54. }
  55. void CGenericClassBasedReplay::OnDelete()
  56. {
  57. BaseClass::OnDelete();
  58. }
  59. void CGenericClassBasedReplay::FireGameEvent( IGameEvent *pEvent )
  60. {
  61. }
  62. void CGenericClassBasedReplay::Update()
  63. {
  64. BaseClass::Update();
  65. // Record any new stats
  66. RecordUpdatedStats();
  67. // Setup next update
  68. m_flNextUpdateTime = engine->Time() + .1f;
  69. }
  70. float CGenericClassBasedReplay::GetKillScreenshotDelay()
  71. {
  72. ConVarRef replay_screenshotkilldelay( "replay_screenshotkilldelay" );
  73. return replay_screenshotkilldelay.IsValid() ? replay_screenshotkilldelay.GetFloat() : 0.5f;
  74. }
  75. void CGenericClassBasedReplay::RecordUpdatedStats()
  76. {
  77. // Get current stats
  78. static RoundStats_t s_curStats;
  79. if ( !GetCurrentStats( s_curStats ) )
  80. return;
  81. // Go through each stat and see if it's changed
  82. for ( int i = 0; i < REPLAY_GAMESTATS_MAX; ++i )
  83. {
  84. const int nCurStat = s_curStats.Get( i );
  85. const int nRefStat = m_refStats.Get( i );
  86. if ( nCurStat != nRefStat )
  87. {
  88. // Calculate new stat based on reference
  89. const int nLifeStat = nCurStat - nRefStat;
  90. if ( nLifeStat != m_lifeStats.Get( i ) )
  91. {
  92. ConVarRef replay_debug( "replay_debug" );
  93. if ( replay_debug.IsValid() && replay_debug.GetBool() )
  94. {
  95. Msg( "REPLAY: Player stat \"%s\" changed from %i to %i.\n", GetStatString( i ), nRefStat, nCurStat );
  96. }
  97. // Set the new stat
  98. m_lifeStats.Set( i, nLifeStat );
  99. }
  100. }
  101. }
  102. }
  103. bool CGenericClassBasedReplay::Read( KeyValues *pIn )
  104. {
  105. if ( !BaseClass::Read( pIn ) )
  106. return false;
  107. // Read player class
  108. m_nPlayerClass = pIn->GetInt( "player_class" ); Assert( IsValidClass( m_nPlayerClass ) );
  109. // Read player team
  110. m_nPlayerTeam = pIn->GetInt( "player_team" ); Assert( IsValidTeam( m_nPlayerTeam ) );
  111. // Read killer info
  112. m_nKillerClass = pIn->GetInt( "killer_class" );
  113. V_strcpy_safe( m_szKillerName, pIn->GetString( "killer_name" ) );
  114. // Make sure vector is clear
  115. Assert( GetKillCount() == 0 );
  116. // Read all kill data and add the kills vector
  117. KeyValues *pKills = pIn->FindKey( "kills" );
  118. if ( pKills )
  119. {
  120. FOR_EACH_TRUE_SUBKEY( pKills, pKill )
  121. {
  122. // Create the kill data
  123. AddKill(
  124. pKill->GetString( "victim_name" ),
  125. pKill->GetInt( "victim_class" )
  126. );
  127. }
  128. }
  129. AddKillStats( m_vecDominations , pIn, "dominations", REPLAY_GAMESTATS_DOMINATIONS );
  130. AddKillStats( m_vecAssisterDominations, pIn, "assister_dominations", REPLAY_GAMESTATS_UNDEFINED );
  131. AddKillStats( m_vecRevenges , pIn, "revenges", REPLAY_GAMESTATS_REVENGE );
  132. AddKillStats( m_vecAssisterRevenges , pIn, "assister_revenges", REPLAY_GAMESTATS_UNDEFINED );
  133. // Read stats by index
  134. KeyValues *pStats = pIn->FindKey( "stats" );
  135. if ( pStats )
  136. {
  137. for ( int i = 0; i < REPLAY_GAMESTATS_MAX; ++i )
  138. {
  139. char szStatKey[ 16 ];
  140. V_snprintf( szStatKey, sizeof( szStatKey ), "%i", i );
  141. m_lifeStats.Set( i, pStats->GetInt( szStatKey ) );
  142. }
  143. }
  144. return true;
  145. }
  146. void CGenericClassBasedReplay::AddKillStats( CUtlVector< GenericStatInfo_t * > &vecKillStats, KeyValues *pIn, const char *pSubKeyName, int iStatIndex )
  147. {
  148. Assert( vecKillStats.Count() == 0 );
  149. KeyValues *pSubKey = pIn->FindKey( pSubKeyName );
  150. if ( pSubKey )
  151. {
  152. FOR_EACH_TRUE_SUBKEY( pSubKey, pCurKillStat )
  153. {
  154. GenericStatInfo_t *pNewKillStat = new GenericStatInfo_t;
  155. pNewKillStat->m_nVictimFriendId = pCurKillStat->GetInt( "victim_friend_id" );
  156. pNewKillStat->m_nAssisterFriendId = pCurKillStat->GetInt( "assister_friend_id" );
  157. vecKillStats.AddToTail( pNewKillStat );
  158. }
  159. }
  160. // Duplicate the data in the life stats
  161. if ( iStatIndex > m_nStatUndefined )
  162. {
  163. m_lifeStats.Set( iStatIndex, vecKillStats.Count() );
  164. }
  165. }
  166. void CGenericClassBasedReplay::Write( KeyValues *pOut )
  167. {
  168. BaseClass::Write( pOut );
  169. // Write player class
  170. pOut->SetInt( "player_class", m_nPlayerClass );
  171. // Write player team
  172. pOut->SetInt( "player_team", m_nPlayerTeam );
  173. // Write killer info
  174. pOut->SetInt( "killer_class", m_nKillerClass );
  175. pOut->SetString( "killer_name", m_szKillerName );
  176. // Write kills
  177. KeyValues *pKills = new KeyValues( "kills" );
  178. pOut->AddSubKey( pKills );
  179. for ( int i = 0; i < GetKillCount(); ++i )
  180. {
  181. KillData_t *pCurKill = m_vecKills[ i ];
  182. KeyValues *pKillOut = new KeyValues( "kill" );
  183. pKills->AddSubKey( pKillOut );
  184. // Write kill data
  185. pKillOut->SetString( "victim_name", pCurKill->m_szPlayerName );
  186. pKillOut->SetInt( "victim_class", pCurKill->m_nPlayerClass );
  187. }
  188. WriteKillStatVector( m_vecDominations , "dominations" , "domination" , pOut, 1 );
  189. WriteKillStatVector( m_vecAssisterDominations, "assister_dominations", "assister_domination", pOut, 2 );
  190. WriteKillStatVector( m_vecRevenges , "revenges" , "revenge" , pOut, 1 );
  191. WriteKillStatVector( m_vecAssisterRevenges , "assister_revenges" , "assister_revenge" , pOut, 2 );
  192. // Write non-zero stats by index
  193. KeyValues *pStats = new KeyValues( "stats" );
  194. pOut->AddSubKey( pStats );
  195. for ( int i = 0; i < REPLAY_GAMESTATS_MAX; ++i )
  196. {
  197. const int nCurStat = m_lifeStats.Get( i );
  198. if ( nCurStat )
  199. {
  200. char szStatKey[ 16 ];
  201. V_snprintf( szStatKey, sizeof( szStatKey ), "%i", i );
  202. pStats->SetInt( szStatKey, nCurStat );
  203. }
  204. }
  205. }
  206. void CGenericClassBasedReplay::WriteKillStatVector( CUtlVector< CGenericClassBasedReplay::GenericStatInfo_t * > const &vec, const char *pSubKeyName,
  207. const char *pElementKeyName, KeyValues *pRootKey, int nNumMembersToWrite ) const
  208. {
  209. Assert( nNumMembersToWrite >= 1 );
  210. // Write dominations
  211. KeyValues *pSubKey = new KeyValues( pSubKeyName );
  212. pRootKey->AddSubKey( pSubKey );
  213. for ( int i = 0; i < vec.Count(); ++i )
  214. {
  215. GenericStatInfo_t *pSrcData = vec[ i ];
  216. KeyValues *pCurSubKey = new KeyValues( pElementKeyName );
  217. pSubKey->AddSubKey( pCurSubKey );
  218. // Always write
  219. pCurSubKey->SetInt( "victim_friend_id", pSrcData->m_nVictimFriendId );
  220. if ( nNumMembersToWrite > 1 )
  221. {
  222. pCurSubKey->SetInt( "assister_friend_id", pSrcData->m_nAssisterFriendId );
  223. }
  224. }
  225. }
  226. void CGenericClassBasedReplay::AddKill( const char *pPlayerName, int nPlayerClass )
  227. {
  228. KillData_t *pNewKillData = new KillData_t;
  229. V_strcpy_safe( pNewKillData->m_szPlayerName , pPlayerName );
  230. pNewKillData->m_nPlayerClass = nPlayerClass;
  231. ConVarRef replay_debug( "replay_debug" );
  232. if ( replay_debug.IsValid() && replay_debug.GetBool() )
  233. {
  234. DevMsg( "\n\nRecorded kill: name=%s, class=%s (this=%i)\n\n", pPlayerName, GetPlayerClass( nPlayerClass ), (int)this );
  235. }
  236. m_vecKills.AddToTail( pNewKillData );
  237. }
  238. const char *CGenericClassBasedReplay::GetPlayerClass() const
  239. {
  240. return GetPlayerClass( m_nPlayerClass );
  241. }
  242. void CGenericClassBasedReplay::AddDomination( int nVictimID )
  243. {
  244. AddKillStatFromUserIds( m_vecDominations, nVictimID );
  245. }
  246. void CGenericClassBasedReplay::AddAssisterDomination( int nVictimID, int nAssiterID )
  247. {
  248. AddKillStatFromUserIds( m_vecAssisterDominations, nVictimID, nAssiterID );
  249. }
  250. void CGenericClassBasedReplay::AddRevenge( int nVictimID )
  251. {
  252. AddKillStatFromUserIds( m_vecRevenges, nVictimID );
  253. }
  254. void CGenericClassBasedReplay::AddAssisterRevenge( int nVictimID, int nAssiterID )
  255. {
  256. AddKillStatFromUserIds( m_vecAssisterRevenges, nVictimID, nAssiterID );
  257. }
  258. void CGenericClassBasedReplay::AddKillStatFromUserIds( CUtlVector< GenericStatInfo_t * > &vec, int nVictimId, int nAssisterId/*=0*/ )
  259. {
  260. uint32 nVictimFriendId;
  261. if ( !GetFriendIdFromUserId( engine->GetPlayerForUserID( nVictimId ), nVictimFriendId ) )
  262. return;
  263. uint32 nAssisterFriendId = 0;
  264. if ( nAssisterId && !GetFriendIdFromUserId( engine->GetPlayerForUserID( nAssisterId ), nAssisterFriendId ) )
  265. return;
  266. AddKillStatFromFriendIds( vec, nVictimFriendId, nAssisterFriendId );
  267. }
  268. void CGenericClassBasedReplay::AddKillStatFromFriendIds( CUtlVector< GenericStatInfo_t * > &vec, uint32 nVictimFriendId, uint32 nAssisterFriendId/*=0*/ )
  269. {
  270. GenericStatInfo_t *pNewKillStat = new GenericStatInfo_t;
  271. pNewKillStat->m_nVictimFriendId = nVictimFriendId;
  272. pNewKillStat->m_nAssisterFriendId = nAssisterFriendId;
  273. vec.AddToTail( pNewKillStat );
  274. }
  275. bool CGenericClassBasedReplay::GetFriendIdFromUserId( int nPlayerIndex, uint32 &nFriendIdOut ) const
  276. {
  277. player_info_t pi;
  278. if ( !steamapicontext->SteamFriends() ||
  279. !steamapicontext->SteamUtils() ||
  280. !engine->GetPlayerInfo( nPlayerIndex, &pi ) )
  281. {
  282. AssertMsg( 0, "REPLAY: Failed to add domination" );
  283. nFriendIdOut = 0;
  284. return false;
  285. }
  286. nFriendIdOut = pi.friendsID;
  287. return true;
  288. }
  289. const char *CGenericClassBasedReplay::GetMaterialFriendlyPlayerClass() const
  290. {
  291. return GetPlayerClass();
  292. }
  293. const char *CGenericClassBasedReplay::GetKillerName() const
  294. {
  295. Assert( WasKilled() );
  296. return m_szKillerName;
  297. }
  298. const char *CGenericClassBasedReplay::GetKillerClass() const
  299. {
  300. Assert( WasKilled() );
  301. return GetPlayerClass( m_nKillerClass );
  302. }
  303. void CGenericClassBasedReplay::DumpGameSpecificData() const
  304. {
  305. DevMsg( " class: %s\n", GetPlayerClass() );
  306. // Print kills
  307. DevMsg( " %i kills:\n", GetKillCount() );
  308. for ( int i = 0; i < GetKillCount(); ++i )
  309. {
  310. KillData_t *pCurKill = m_vecKills[ i ];
  311. Msg( " kill %i: name=%s class=%s\n", i, pCurKill->m_szPlayerName, GetPlayerClass( pCurKill->m_nPlayerClass ) );
  312. }
  313. if ( !WasKilled() )
  314. {
  315. Msg( " No killer.\n" );
  316. return;
  317. }
  318. // Print killer info
  319. Msg( " killer: name=%s class=%s\n", m_szKillerName, GetPlayerClass( m_nKillerClass ) );
  320. }
  321. void CGenericClassBasedReplay::SetPlayerClass( int nPlayerClass )
  322. {
  323. //Assert( IsValidClass( nPlayerClass ) );
  324. m_nPlayerClass = nPlayerClass;
  325. // Setup reference stats if this is a valid class
  326. if ( IsValidClass( nPlayerClass ) )
  327. {
  328. GetCurrentStats( m_refStats );
  329. }
  330. }
  331. void CGenericClassBasedReplay::SetPlayerTeam( int nPlayerTeam )
  332. {
  333. m_nPlayerTeam = nPlayerTeam;
  334. }
  335. void CGenericClassBasedReplay::RecordPlayerDeath( const char *pKillerName, int nKillerClass )
  336. {
  337. V_strcpy_safe( m_szKillerName, pKillerName );
  338. m_nKillerClass = nKillerClass;
  339. }
  340. //----------------------------------------------------------------------------------------
  341. #endif