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.

398 lines
13 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. //=======================================================================================//
  4. #include "cbase.h"
  5. #if defined( REPLAY_ENABLED )
  6. #include "tf_replay.h"
  7. #include "tf/tf_shareddefs.h"
  8. #include "tf/c_tf_player.h"
  9. #include "tf/c_tf_playerresource.h"
  10. #include "tf/c_tf_gamestats.h"
  11. #include "tf/tf_gamestats_shared.h"
  12. #include "tf/tf_hud_statpanel.h"
  13. #include "tf/c_obj_sentrygun.h"
  14. #include "clientmode_shared.h"
  15. #include "replay/ireplaymoviemanager.h"
  16. #include "replay/ireplayfactory.h"
  17. #include "replay/ireplayscreenshotmanager.h"
  18. #include "replay/screenshot.h"
  19. #include <time.h>
  20. //----------------------------------------------------------------------------------------
  21. extern IReplayScreenshotManager *g_pReplayScreenshotManager;
  22. //----------------------------------------------------------------------------------------
  23. CTFReplay::CTFReplay()
  24. : m_flNextMedicUpdateTime( 0.0f )
  25. {
  26. }
  27. CTFReplay::~CTFReplay()
  28. {
  29. }
  30. void CTFReplay::OnBeginRecording()
  31. {
  32. BaseClass::OnBeginRecording();
  33. // Setup the newly created replay
  34. C_TFPlayer* pPlayer = C_TFPlayer::GetLocalTFPlayer();
  35. if ( pPlayer )
  36. {
  37. if ( pPlayer->GetPlayerClass() )
  38. {
  39. SetPlayerClass( pPlayer->GetPlayerClass()->GetClassIndex() );
  40. }
  41. SetPlayerTeam( pPlayer->GetTeamNumber() );
  42. }
  43. }
  44. void CTFReplay::OnEndRecording()
  45. {
  46. if ( gameeventmanager )
  47. {
  48. gameeventmanager->RemoveListener( this );
  49. }
  50. BaseClass::OnEndRecording();
  51. }
  52. void CTFReplay::OnComplete()
  53. {
  54. BaseClass::OnComplete();
  55. }
  56. void CTFReplay::Update()
  57. {
  58. // If local player is medic and invuln'd someone, take a screenshot
  59. MedicUpdate();
  60. BaseClass::Update();
  61. }
  62. void CTFReplay::MedicUpdate()
  63. {
  64. // Not ready for update?
  65. if ( gpGlobals->curtime < m_flNextMedicUpdateTime )
  66. return;
  67. // Local player doesn't exist?
  68. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  69. if ( !pLocalPlayer )
  70. {
  71. Assert( 0 ); // Shouldn't happen
  72. return;
  73. }
  74. if ( pLocalPlayer->GetPlayerClass()->GetClassIndex() != TF_CLASS_MEDIC )
  75. return;
  76. // Releasing charge?
  77. if ( pLocalPlayer->MedicIsReleasingCharge() )
  78. {
  79. // Take a sick screenshot
  80. CaptureScreenshotParams_t params;
  81. V_memset( &params, 0, sizeof( params ) );
  82. params.m_flDelay = 0.25f;
  83. g_pReplayScreenshotManager->CaptureScreenshot( params );
  84. // Set next update to minimum time it would be until next recharge
  85. extern ConVar weapon_medigun_chargerelease_rate;
  86. m_flNextMedicUpdateTime = gpGlobals->curtime + weapon_medigun_chargerelease_rate.GetFloat();
  87. }
  88. else
  89. {
  90. // Check again in a second
  91. m_flNextMedicUpdateTime = gpGlobals->curtime + 1.0f;
  92. }
  93. }
  94. float CTFReplay::GetSentryKillScreenshotDelay()
  95. {
  96. ConVarRef replay_screenshotsentrykilldelay( "replay_screenshotsentrykilldelay" );
  97. return replay_screenshotsentrykilldelay.IsValid() ? replay_screenshotsentrykilldelay.GetFloat() : 0.5f;
  98. }
  99. void CTFReplay::FireGameEvent( IGameEvent *pEvent )
  100. {
  101. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  102. if ( !pLocalPlayer )
  103. return;
  104. CaptureScreenshotParams_t params;
  105. V_memset( &params, 0, sizeof( params ) );
  106. if ( FStrEq( pEvent->GetName(), "player_death" ) )
  107. {
  108. ConVarRef replay_debug( "replay_debug" );
  109. if ( replay_debug.IsValid() && replay_debug.GetBool() )
  110. {
  111. DevMsg( "%i: CTFReplay::FireGameEvent(): player_death\n", gpGlobals->tickcount );
  112. }
  113. int nKillerID = pEvent->GetInt( "attacker" );
  114. int nVictimID = pEvent->GetInt( "userid" );
  115. int nDeathFlags = pEvent->GetInt( "death_flags" );
  116. int nAssisterID = pEvent->GetInt( "assister" );
  117. const char *pWeaponName = pEvent->GetString( "weapon" );
  118. // Suicide?
  119. bool bSuicide = nKillerID == nVictimID;
  120. // Try to get killer
  121. C_TFPlayer *pKiller = ToTFPlayer( USERID2PLAYER( nKillerID ) );
  122. // Try to get victim
  123. C_TFPlayer *pVictim = ToTFPlayer( USERID2PLAYER( nVictimID ) );
  124. // Is local player healing the killer?
  125. bool bKillerLastHealerIsLocalPlayer = pKiller && pKiller->GetWasHealedByLocalPlayer();
  126. // Inflictor was a sentry gun?
  127. bool bSentry = V_strnicmp( pWeaponName, "obj_sentrygun", 13 ) == 0;
  128. int nInflictorEntIndex = pEvent->GetInt( "inflictor_entindex" );
  129. C_BaseEntity *pInflictor = ClientEntityList().GetEnt( nInflictorEntIndex );
  130. C_ObjectSentrygun *pSentry = dynamic_cast< C_ObjectSentrygun * >( pInflictor );
  131. bool bFeignDeath = pEvent->GetInt( "death_flags" ) & TF_DEATH_FEIGN_DEATH;
  132. // Is the killer the local player?
  133. if ( nKillerID == pLocalPlayer->GetUserID() &&
  134. !bSuicide &&
  135. !bSentry )
  136. {
  137. // Domination?
  138. if ( nDeathFlags & TF_DEATH_DOMINATION )
  139. {
  140. AddDomination( nVictimID );
  141. }
  142. // Assister domination?
  143. if ( ( nDeathFlags & TF_DEATH_ASSISTER_DOMINATION ) && ( nAssisterID > 0 ) )
  144. {
  145. AddAssisterDomination( nVictimID, nAssisterID );
  146. }
  147. // Revenge?
  148. if ( pEvent->GetInt( "death_flags" ) & TF_DEATH_REVENGE )
  149. {
  150. AddRevenge( nVictimID );
  151. }
  152. // Assister revenge?
  153. if ( pEvent->GetInt( "death_flags" ) & TF_DEATH_ASSISTER_REVENGE && ( nAssisterID > 0 ) )
  154. {
  155. AddAssisterRevenge( nVictimID, nAssisterID );
  156. }
  157. // Add victim info to kill list
  158. if ( pVictim )
  159. {
  160. AddKill( pVictim->GetPlayerName(), pVictim->GetPlayerClass()->GetClassIndex() );
  161. }
  162. // Take a quick screenshot with some delay
  163. ConVarRef replay_screenshotkilldelay( "replay_screenshotkilldelay" );
  164. if ( replay_screenshotkilldelay.IsValid() )
  165. {
  166. params.m_flDelay = GetKillScreenshotDelay();
  167. g_pReplayScreenshotManager->CaptureScreenshot( params );
  168. }
  169. }
  170. // Player death?
  171. else if ( pKiller &&
  172. nVictimID == pLocalPlayer->GetUserID() )
  173. {
  174. // Record who killed the player if not a suicide
  175. if ( !bSuicide && !bFeignDeath )
  176. {
  177. RecordPlayerDeath( pKiller->GetPlayerName(), pKiller->GetPlayerClass()->GetClassIndex() );
  178. }
  179. // Take screenshot - taking a screenshot during feign death is cool, too.
  180. ConVarRef replay_deathcammaxverticaloffset( "replay_deathcammaxverticaloffset" );
  181. ConVarRef replay_playerdeathscreenshotdelay( "replay_playerdeathscreenshotdelay" );
  182. params.m_flDelay = replay_playerdeathscreenshotdelay.IsValid() ? replay_playerdeathscreenshotdelay.GetFloat() : 0.0f;
  183. params.m_nEntity = pLocalPlayer->entindex();
  184. params.m_posCamera.Init( 0,0, replay_deathcammaxverticaloffset.IsValid() ? replay_deathcammaxverticaloffset.GetFloat() : 150 );
  185. params.m_angCamera.Init( 90, 0, 0 ); // Look straight down
  186. params.m_bUseCameraAngles = true;
  187. params.m_bIgnoreMinTimeBetweenScreenshots = true;
  188. g_pReplayScreenshotManager->CaptureScreenshot( params );
  189. }
  190. // Killer is invuln/crit boosted and healer is local player?
  191. else if ( bKillerLastHealerIsLocalPlayer &&
  192. ( pKiller->m_Shared.IsCritBoosted() ||
  193. pKiller->m_Shared.InCond( TF_COND_INVULNERABLE ) ||
  194. pKiller->m_Shared.InCond( TF_COND_INVULNERABLE_WEARINGOFF ) ) )
  195. {
  196. // Take a quick screenshot with some delay
  197. params.m_flDelay = GetKillScreenshotDelay();
  198. g_pReplayScreenshotManager->CaptureScreenshot( params );
  199. }
  200. // Is the inflictor a sentry belonging to the local player?
  201. else if ( pLocalPlayer->IsAlive() &&
  202. bSentry &&
  203. pSentry &&
  204. pSentry->GetOwner() == pLocalPlayer &&
  205. pVictim )
  206. {
  207. ConVarRef replay_sentrycammaxverticaloffset( "replay_sentrycammaxverticaloffset" );
  208. ConVarRef replay_sentrycamoffset_frontback( "replay_sentrycamoffset_frontback" );
  209. ConVarRef replay_sentrycamoffset_leftright( "replay_sentrycamoffset_leftright" );
  210. ConVarRef replay_sentrycamoffset_updown( "replay_sentrycamoffset_updown" );
  211. // Setup screenshot params
  212. params.m_flDelay = GetSentryKillScreenshotDelay();
  213. params.m_nEntity = pSentry->entindex();
  214. params.m_bUseCameraAngles = true;
  215. // Calculate camera eye position
  216. static float s_aSentryEyeLevels[3] = { SENTRYGUN_EYE_OFFSET_LEVEL_1[2], SENTRYGUN_EYE_OFFSET_LEVEL_2[2], SENTRYGUN_EYE_OFFSET_LEVEL_3[2] };
  217. int iSentryUpgrade = clamp( pSentry->GetUpgradeLevel() - 1, 0, 2 );
  218. Vector vecSentryEyeOffset = Vector( 0, 0, s_aSentryEyeLevels[ iSentryUpgrade ] );
  219. Vector vecSentryAimDir; // Since it seems the sentry's *actual* aim direction is only available on the server, use the victim's location to calculate a general idea of one
  220. Vector vecVictimUp = pVictim->WorldSpaceCenter() - pVictim->GetAbsOrigin(); // WorldSpaceCenter() seems to return player's eye level
  221. Vector vecVictimCenter = pVictim->GetAbsOrigin() + 0.5f * vecVictimUp;
  222. vecSentryAimDir = vecVictimCenter - pSentry->GetAbsOrigin() + vecSentryEyeOffset;
  223. VectorNormalizeFast( vecSentryAimDir );
  224. Vector vecX, vecY, vecZ; // Construct a matrix to transform the eye point
  225. vecX = vecSentryAimDir;
  226. vecY = CrossProduct( Vector(0,0,1), vecSentryAimDir );
  227. vecZ = CrossProduct( vecX, vecY );
  228. matrix3x4_t m;
  229. m.Init( vecX, vecY, vecZ, vec3_origin );
  230. Vector out; // Transform the point relative to the sentry's eye
  231. Vector vecOffset;
  232. if ( replay_sentrycamoffset_frontback.IsValid() &&
  233. replay_sentrycamoffset_leftright.IsValid() &&
  234. replay_sentrycamoffset_updown.IsValid() )
  235. {
  236. vecOffset.Init( replay_sentrycamoffset_frontback.GetFloat(), -replay_sentrycamoffset_leftright.GetFloat(), replay_sentrycamoffset_updown.GetFloat() );
  237. }
  238. else
  239. {
  240. vecOffset.Init( 0, 0, 0 );
  241. }
  242. VectorTransform( vecOffset, m, out );
  243. out += Vector( 0,0, s_aSentryEyeLevels[ iSentryUpgrade ] + ( replay_sentrycammaxverticaloffset.IsValid() ? replay_sentrycammaxverticaloffset.GetFloat() : 5 ) );
  244. params.m_posCamera = out;
  245. // Use the aim matrix we constructed as the camera's orientation
  246. MatrixAngles( m, params.m_angCamera );
  247. // Take the screenshot from the sentry's point of view
  248. g_pReplayScreenshotManager->CaptureScreenshot( params );
  249. }
  250. }
  251. }
  252. bool CTFReplay::IsValidClass( int nClass ) const
  253. {
  254. return IsValidTFPlayerClass( nClass );
  255. }
  256. bool CTFReplay::IsValidTeam( int iTeam ) const
  257. {
  258. return IsValidTFTeam( iTeam );
  259. }
  260. bool CTFReplay::GetCurrentStats( RoundStats_t &out )
  261. {
  262. if ( !g_TF_PR )
  263. return false;
  264. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  265. if ( !pLocalPlayer )
  266. return false;
  267. int iLocalPlayerIndex = GetLocalPlayerIndex();
  268. out.m_iStat[ TFSTAT_POINTSSCORED ] = g_TF_PR->GetPlayerScore( iLocalPlayerIndex );
  269. out.m_iStat[ TFSTAT_DEATHS ] = g_TF_PR->GetDeaths( iLocalPlayerIndex );
  270. out.m_iStat[ TFSTAT_CAPTURES ] = pLocalPlayer->m_Shared.GetCaptures( iLocalPlayerIndex );
  271. out.m_iStat[ TFSTAT_DEFENSES ] = pLocalPlayer->m_Shared.GetDefenses( iLocalPlayerIndex );
  272. out.m_iStat[ TFSTAT_DOMINATIONS ] = pLocalPlayer->m_Shared.GetDominations( iLocalPlayerIndex );
  273. out.m_iStat[ TFSTAT_REVENGE ] = pLocalPlayer->m_Shared.GetRevenge( iLocalPlayerIndex );
  274. out.m_iStat[ TFSTAT_BUILDINGSDESTROYED ] = pLocalPlayer->m_Shared.GetBuildingsDestroyed( iLocalPlayerIndex );
  275. out.m_iStat[ TFSTAT_HEADSHOTS ] = pLocalPlayer->m_Shared.GetHeadshots( iLocalPlayerIndex );
  276. out.m_iStat[ TFSTAT_BACKSTABS ] = pLocalPlayer->m_Shared.GetBackstabs( iLocalPlayerIndex );
  277. out.m_iStat[ TFSTAT_HEALING ] = pLocalPlayer->m_Shared.GetHealPoints( iLocalPlayerIndex );
  278. out.m_iStat[ TFSTAT_INVULNS ] = pLocalPlayer->m_Shared.GetInvulns( iLocalPlayerIndex );
  279. out.m_iStat[ TFSTAT_TELEPORTS ] = pLocalPlayer->m_Shared.GetTeleports( iLocalPlayerIndex );
  280. out.m_iStat[ TFSTAT_KILLASSISTS ] = pLocalPlayer->m_Shared.GetKillAssists( iLocalPlayerIndex );
  281. out.m_iStat[ TFSTAT_BONUS_POINTS ] = pLocalPlayer->m_Shared.GetBonusPoints( iLocalPlayerIndex );
  282. return true;
  283. }
  284. const char *CTFReplay::GetStatString( int iStat ) const
  285. {
  286. COMPILE_TIME_ASSERT( TFSTAT_TOTAL == ARRAYSIZE( s_pStatStrings ) );
  287. Assert( iStat >= TFSTAT_UNDEFINED && iStat < TFSTAT_TOTAL );
  288. return ClampedArrayElement( s_pStatStrings, iStat );
  289. }
  290. const char *CTFReplay::GetPlayerClass( int iClass ) const
  291. {
  292. COMPILE_TIME_ASSERT( TF_CLASS_MENU_BUTTONS == ARRAYSIZE( g_aPlayerClassNames_NonLocalized ) );
  293. Assert( iClass >= TF_CLASS_UNDEFINED && iClass < TF_CLASS_COUNT );
  294. return ClampedArrayElement( g_aPlayerClassNames_NonLocalized, iClass );
  295. }
  296. bool CTFReplay::Read( KeyValues *pIn )
  297. {
  298. return BaseClass::Read( pIn );
  299. }
  300. void CTFReplay::Write( KeyValues *pOut )
  301. {
  302. BaseClass::Write( pOut );
  303. }
  304. const char *CTFReplay::GetMaterialFriendlyPlayerClass() const
  305. {
  306. const char *pPlayerClass = BaseClass::GetMaterialFriendlyPlayerClass();
  307. if ( !V_stricmp( pPlayerClass, "heavyweapons" ) )
  308. return "heavy";
  309. else if ( !V_stricmp( pPlayerClass, "demoman" ) )
  310. return "demo";
  311. return pPlayerClass;
  312. }
  313. void CTFReplay::DumpGameSpecificData() const
  314. {
  315. BaseClass::DumpGameSpecificData();
  316. }
  317. //----------------------------------------------------------------------------------------
  318. class CTFReplayFactory : public IReplayFactory
  319. {
  320. public:
  321. virtual CReplay *Create()
  322. {
  323. return new CTFReplay();
  324. }
  325. };
  326. static CTFReplayFactory s_ReplayManager;
  327. IReplayFactory *g_pReplayFactory = &s_ReplayManager;
  328. EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CTFReplayFactory, IReplayFactory, INTERFACE_VERSION_REPLAY_FACTORY, s_ReplayManager );
  329. #endif