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.

999 lines
33 KiB

  1. //======= Copyright (c) Valve Corporation, All rights reserved. ======
  2. #include "cbase.h"
  3. #include "hltvreplaysystem.h"
  4. #include "hltvcamera.h"
  5. #include "cs_gamerules.h"
  6. #include "iviewrender.h"
  7. #include "engine/IEngineSound.h"
  8. #include "netmessages.h"
  9. #include "cstrike15/c_cs_player.h"
  10. #include "ihltv.h"
  11. ConVar snd_deathcam_replay_mix( "snd_deathcam_replay_mix", "0", 0, "When set to non-0, client switches to DeathCam_Replay_Mix mixgroup during deathcam replay" );
  12. ConVar spec_replay_review_sound( "spec_replay_review_sound", "1", FCVAR_CLIENTDLL, "When set to non-0, a sound effect is played during Killer Replay" );
  13. ConVar spec_replay_rate_slowdown( "spec_replay_rate_slowdown", "1", FCVAR_CLIENTDLL, "The part of Killer Replay right before death is played at this rate" );
  14. ConVar spec_replay_rate_slowdown_length( "spec_replay_rate_slowdown_length", "0.5", FCVAR_CLIENTDLL, "The part of Killer Replay right before death is played at this rate" );
  15. ConVar spec_replay_fadein( "spec_replay_fadein", "0.75", FCVAR_CLIENTDLL, "Amount of time in seconds it takes to visually fade into replay, or into real-time after replay" ); // Original tuning: 1.0; MattWood 12/3/15: 0.75
  16. ConVar spec_replay_fadeout( "spec_replay_fadeout", "0.5", FCVAR_CLIENTDLL, "Amount of time in seconds it takes to visually fade out of replay, or out of real-time before replay" );
  17. ConVar spec_replay_sound_fadein( "spec_replay_sound_fadein", "1", FCVAR_CLIENTDLL, "Amount of time in seconds it takes to fade in the audio before or after replay" );
  18. ConVar spec_replay_sound_fadeout( "spec_replay_sound_fadeout", "1.5", FCVAR_CLIENTDLL, "Amount of time in seconds it takes to fade out the audio before or after replay" );
  19. ConVar spec_replay_cache_ragdolls( "spec_replay_cache_ragdolls", "1", FCVAR_CLIENTDLL, "when set to 0, ragdolls will settle dynamically before and after Killer Replay" );
  20. ConVar spec_replay_others_experimental( "spec_replay_others_experimental", "0", FCVAR_CLIENTDLL, "Replay the last death of the round, if possible. Disabled on official servers by default. Experimental." );
  21. ConVar spec_replay_autostart( "spec_replay_autostart", "1", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Auto-start Killer Replay when available" ); // in game/csgo/scripts/game_options.txt
  22. ConVar spec_replay_autostart_delay( "spec_replay_autostart_delay", "1.5", FCVAR_CLIENTDLL, "Time in freeze panel before switching to Killer Replay automatically" ); // original tuning: 2.4; MattWood 12/3/15: 1.5
  23. ConVar spec_replay_victim_pov( "spec_replay_victim_pov", "0", FCVAR_CLIENTDLL, "Killer Replay - replay from victim's point of view (1); the default is killer's (0). Experimental." );
  24. CHltvReplaySystem g_HltvReplaySystem;
  25. extern void CS_FreezePanel_OnHltvReplayButtonStateChanged();
  26. CHltvReplaySystem::CHltvReplaySystem()
  27. {
  28. m_nHltvReplayDelay = 0;
  29. m_nHltvReplayStopAt = 0;
  30. m_nHltvReplayStartAt = 0;
  31. m_flHltvLastRequestRealTime = 0;
  32. m_bWaitingForHltvReplayTick = false;
  33. m_flStartedWaitingForHltvReplayTickRealTime = 0;
  34. m_bDemoPlayback = false;
  35. m_bListeningForGameEvents = false;
  36. m_nDemoPlaybackStartAt = 0;
  37. m_nDemoPlaybackStopAt = 0;
  38. m_nCurrentPlaybackTick = 0;
  39. m_flCurrentPlaybackTime = 0.0f;
  40. m_nDemoPlaybackXuid = 0;
  41. m_nSteamSelfAccountId = 0;
  42. m_flReplayVideoFadeAmount = 0.0f;
  43. m_flReplaySoundFadeAmount = 0.0f;
  44. m_nExperimentalEvents = 0;
  45. m_flFadeinStartRealTime = -1000;
  46. m_flFadeoutEndTime = -1000;
  47. m_DelayedReplay.Reset();
  48. m_nHltvReplayBeginTick = -1;
  49. m_nHltvReplayPrimaryTarget = 0;
  50. m_bHltvReplayButtonTimedOut = false;
  51. }
  52. CHltvReplaySystem::~CHltvReplaySystem()
  53. {
  54. }
  55. int CL_GetHltvReplayDelay() { return g_HltvReplaySystem.GetHltvReplayDelay(); }
  56. void CHltvReplaySystem::EmitTimeJump()
  57. {
  58. GetHud().OnTimeJump();
  59. #if defined( CSTRIKE15 )
  60. C_BasePlayer::OnTimeJumpAllPlayers();
  61. #endif
  62. //GetHud().UpdateHud( true ); // may call SFUniqueAlerts::ShowHltvReplayAlertPanel
  63. //GetHud().ProcessInput( true );
  64. //GetHud().OnTimeJump();
  65. }
  66. void CHltvReplaySystem::StopHltvReplay()
  67. {
  68. if ( m_nHltvReplayDelay )
  69. {
  70. CSVCMsg_HltvReplay msg;
  71. OnHltvReplay( msg );
  72. }
  73. }
  74. void CHltvReplaySystem::OnHltvReplay( const CSVCMsg_HltvReplay &msg )
  75. {
  76. int nNewReplayDelay = msg.delay();
  77. DevMsg( "%.2f OnHltvReplay %s\n", gpGlobals->curtime, nNewReplayDelay ? "START" : "END" );
  78. extern bool g_bForceCLPredictOff;
  79. g_bForceCLPredictOff = ( nNewReplayDelay != 0 );
  80. static int s_nReplayLayerIndex = engine->GetMixLayerIndex( "ReplayLayer" );
  81. if ( m_nHltvReplayDelay )
  82. {
  83. if ( !nNewReplayDelay )
  84. {
  85. // we're about to go out of HLTV replay. Current time is delayed replay time.
  86. if ( C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer() )
  87. {
  88. C_BaseEntity::StopSound( -1, "Deathcam.Review_Start" );
  89. if ( spec_replay_review_sound.GetBool() )
  90. pPlayer->EmitSound( "Deathcam.Review_End" );
  91. }
  92. engine->SetMixLayerLevel( s_nReplayLayerIndex, 0.0f );
  93. IGameEvent *pEvent = gameeventmanager->CreateEvent( "hide_freezepanel" );
  94. if ( pEvent )
  95. {
  96. gameeventmanager->FireEventClientSide( pEvent );
  97. }
  98. if ( snd_deathcam_replay_mix.GetBool() )
  99. {
  100. ConVar *pSoundmixer = ( ConVar * )cvar->FindVar( "snd_soundmixer" );
  101. pSoundmixer->Revert();
  102. }
  103. }
  104. StopFadeout();// Msg( "%.2f Fadeout not stopped (%d,%d,%.2f)\n", gpGlobals->curtime, m_nHltvReplayDelay, nNewReplayDelay, m_flFadeoutEndTime );
  105. }
  106. else
  107. {
  108. if ( nNewReplayDelay )
  109. {
  110. m_DelayedReplay.Invalidate(); // we don't do delayed replay if we are about to do the replay
  111. CacheRagdollBones();
  112. // we're about to go into HLTV replay. Current time is real time.
  113. HLTVCamera()->SetPrimaryTarget( m_nHltvReplayPrimaryTarget = msg.primary_target() );
  114. HLTVCamera()->SetMode( OBS_MODE_IN_EYE );
  115. GetHud().ResetHUD();
  116. /*IGameEvent *pEvent = gameeventmanager->CreateEvent( "hide_freezepanel" );
  117. if ( pEvent )
  118. {
  119. gameeventmanager->FireEventClientSide( pEvent );
  120. }*/
  121. if ( spec_replay_review_sound.GetBool() )
  122. {
  123. if ( C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer() )
  124. {
  125. EmitSound_t params;
  126. params.m_pSoundName = "Deathcam.Review_Start";
  127. params.m_bWarnOnDirectWaveReference = true;
  128. params.m_hSoundScriptHash = soundemitterbase->HashSoundName( params.m_pSoundName );
  129. CLocalPlayerFilter filter;
  130. C_BaseEntity::EmitSound( filter, -1, params );
  131. }
  132. }
  133. if ( snd_deathcam_replay_mix.GetBool() )
  134. {
  135. ConVar *pSoundmixer = ( ConVar * )cvar->FindVar( "snd_soundmixer" );
  136. pSoundmixer->SetValue( "DeathCam_Replay_Mix" );
  137. }
  138. engine->SetMixLayerLevel( s_nReplayLayerIndex, 1.0f );
  139. if ( int nSlowdownRatio = msg.replay_slowdown_rate() )
  140. {
  141. m_flHltvReplaySlowdownRate = 1.0f / nSlowdownRatio;
  142. m_nHltvReplaySlowdownBegin = msg.replay_slowdown_begin();
  143. m_nHltvReplaySlowdownEnd = msg.replay_slowdown_end();
  144. }
  145. else
  146. {
  147. m_nHltvReplaySlowdownBegin = 0;
  148. m_nHltvReplaySlowdownEnd = 0;
  149. m_flHltvReplaySlowdownRate = 1.0f;
  150. }
  151. m_nExperimentalEvents |= EE_REPLAY_STARTED;
  152. // currently, Hltv Replay Active (m_nHltvReplayDelay!=0)
  153. StartFadeout( msg.replay_stop_at() * gpGlobals->interval_per_tick, spec_replay_fadeout.GetFloat() );
  154. }
  155. else
  156. {
  157. // Hltv replay is NOT active. Requested NO Hltv replay . There's nothing to do.
  158. StopFadeout(); //Msg( "%.2f Fadeout not stopped-2 (%d,%d,%.2f)\n", gpGlobals->curtime, m_nHltvReplayDelay, nNewReplayDelay, m_flFadeoutEndTime );
  159. }
  160. }
  161. StartFadein( gpGlobals->realtime );
  162. m_flHltvLastRequestRealTime = gpGlobals->realtime;
  163. m_bWaitingForHltvReplayTick = true;
  164. m_flStartedWaitingForHltvReplayTickRealTime = gpGlobals->realtime;
  165. m_nHltvReplayBeginTick = gpGlobals->tickcount;
  166. debugoverlay->ClearAllOverlays();
  167. m_nHltvReplayDelay = nNewReplayDelay;
  168. //enginesound->SetReplayMusicGain( nNewReplayDelay ? 0.0f : 1.0f );
  169. m_nHltvReplayStopAt = msg.replay_stop_at(); // we'll stop at this current tick, more or less; we'll jump back in time and stop here
  170. m_nHltvReplayStartAt = msg.replay_start_at() - nNewReplayDelay;
  171. // I'd like to call ParticleMgr()->RemoveAllEffects(); but there are still some entities referencing the particles in question
  172. view->FreezeFrame( 0.0f );
  173. Update();
  174. EmitTimeJump();
  175. }
  176. void CHltvReplaySystem::OnHltvReplayTick()
  177. {
  178. Assert( m_bWaitingForHltvReplayTick );
  179. m_bWaitingForHltvReplayTick = false;
  180. m_nHltvReplayBeginTick = gpGlobals->tickcount;
  181. Update();
  182. CS_FreezePanel_OnHltvReplayButtonStateChanged();
  183. }
  184. float ReplayFade( float flCurTime, int nStartAt, int nStopAt, float flFadeIn, float flFadeOut )
  185. {
  186. if ( flFadeIn > 0.001f )
  187. {
  188. float flTimeIn = flCurTime - nStartAt * gpGlobals->interval_per_tick;
  189. if ( flTimeIn < flFadeIn )
  190. {
  191. return 1.0f - Max( 0.0f, flTimeIn / flFadeIn );
  192. }
  193. }
  194. if ( flFadeOut > 0.001f )
  195. {
  196. float flTimeOut = nStopAt * gpGlobals->interval_per_tick - flCurTime;
  197. if ( flTimeOut < flFadeOut )
  198. {
  199. return 1.0f - Max( 0.0f, flTimeOut / flFadeOut );
  200. }
  201. }
  202. return 0.0f;
  203. }
  204. void CHltvReplaySystem::Update()
  205. {
  206. float flCurTime = gpGlobals->curtime;
  207. float flPrevReplaySoundFadeAmount = m_flReplaySoundFadeAmount;
  208. //Assert( enginesound->GetReplaySoundFade() == m_flReplaySoundFadeAmount );
  209. m_flReplayVideoFadeAmount = m_flReplaySoundFadeAmount = 0.0f; // there are no replay post effects going on, by default
  210. if ( !m_nHltvReplayDelay && gpGlobals->tickcount > m_nHltvReplayBeginTick + 5 )
  211. {
  212. PurgeRagdollBoneCache();
  213. }
  214. if ( m_bWaitingForHltvReplayTick )
  215. {
  216. // we're in this state when server let us know that Hltv replay is either about to stop or start, but we haven't received the frame update yet and the new timeline hasn't started yet
  217. // whether we're going in or out of replay, let's darken the screen. It's possible that we'll miss that message, though. If we disconnect or reconnect, we'll want to reset this flag
  218. m_flReplayVideoFadeAmount = m_flReplaySoundFadeAmount = 1.0f; // fade everything out completely
  219. if ( gpGlobals->realtime - m_flStartedWaitingForHltvReplayTickRealTime > 0.75f )
  220. {
  221. Msg( "Killer replay stuck waiting for tick message for %.2fs, requesting update\n", gpGlobals->realtime - m_flHltvLastRequestRealTime );
  222. m_nExperimentalEvents |= EE_REPLAY_STUCK;
  223. m_flStartedWaitingForHltvReplayTickRealTime = gpGlobals->realtime;
  224. CCLCMsg_HltvReplay_t msgReplay;
  225. msgReplay.set_request( REPLAY_EVENT_STUCK_NEED_FULL_UPDATE ); // request full update
  226. engine->SendMessageToServer( &msgReplay );
  227. }
  228. }
  229. else if ( m_bDemoPlayback )
  230. {
  231. if ( m_nDemoPlaybackStartAt < m_nDemoPlaybackStopAt )
  232. {
  233. m_flReplayVideoFadeAmount = ReplayFade( m_nCurrentPlaybackTick * gpGlobals->interval_per_tick + flCurTime - m_flCurrentPlaybackTime, m_nDemoPlaybackStartAt, m_nDemoPlaybackStopAt - 1, spec_replay_fadein.GetFloat(), spec_replay_fadeout.GetFloat() );
  234. m_flReplaySoundFadeAmount = ReplayFade( m_nCurrentPlaybackTick * gpGlobals->interval_per_tick + flCurTime - m_flCurrentPlaybackTime, m_nDemoPlaybackStartAt, m_nDemoPlaybackStopAt - 1, spec_replay_sound_fadein.GetFloat(), spec_replay_sound_fadeout.GetFloat() );
  235. }
  236. }
  237. else
  238. {
  239. if ( m_flFadeinStartRealTime > 0.0f )
  240. {
  241. float flTimeIn = gpGlobals->realtime - m_flFadeinStartRealTime;
  242. bool bFadeInStop = true;
  243. if ( flTimeIn < m_flFadeinDuration )
  244. {
  245. m_flReplayVideoFadeAmount = Max( m_flReplayVideoFadeAmount, 1.0f - Max( 0.0f, flTimeIn / Max( m_flFadeinDuration, 0.01f ) ) );
  246. bFadeInStop = false;
  247. }
  248. if ( flTimeIn < spec_replay_sound_fadein.GetFloat() )
  249. {
  250. m_flReplaySoundFadeAmount = Max( m_flReplaySoundFadeAmount, 1.0f - Max( 0.0f, flTimeIn / Max( spec_replay_sound_fadein.GetFloat(), 0.01f ) ) );
  251. bFadeInStop = false;
  252. }
  253. if ( bFadeInStop )
  254. m_flFadeinStartRealTime = -1000;
  255. }
  256. if ( m_flFadeoutEndTime > 0.0f )
  257. {
  258. float flTimeLeft = m_flFadeoutEndTime - gpGlobals->curtime;
  259. if ( flTimeLeft < m_flFadeoutDuration )
  260. {
  261. m_flReplayVideoFadeAmount = Max( m_flReplayVideoFadeAmount, 1 - Max( 0.0f, flTimeLeft / Max( m_flFadeoutDuration, 0.01f ) ) );
  262. // Msg( "%.2f +%.2f fade %.2f\n", gpGlobals->curtime, flTimeLeft, m_flReplayVideoFadeAmount ); // replayfade
  263. }
  264. if ( flTimeLeft < spec_replay_sound_fadeout.GetFloat() )
  265. {
  266. m_flReplaySoundFadeAmount = Max( m_flReplaySoundFadeAmount, 1 - Max( 0.0f, flTimeLeft / Max( spec_replay_sound_fadeout.GetFloat(), 0.01f ) ) );
  267. }
  268. if ( flTimeLeft < -1.0f )
  269. {
  270. // we're in full fade-out for over 1 second. All fadeouts must be very short to avoid disorienting the player. This may only happen when something went wrong: disconnect, dropped connection, server failed to reply to a request... Just stop it
  271. DevMsg( "%.2f Aborting fadeout\n", gpGlobals->curtime );
  272. m_flFadeoutEndTime = -1000;
  273. m_flFadeinStartRealTime = gpGlobals->realtime;
  274. m_flFadeinDuration = .3f; // fade in quickly
  275. }
  276. }
  277. }
  278. if ( m_DelayedReplay.IsValid() )
  279. {
  280. if ( gpGlobals->curtime >= m_DelayedReplay.flTime )
  281. {
  282. if ( IsHltvReplayFeatureEnabled() ) // we checked the button enabled when we set up this delayed replay
  283. RequestHltvReplay( m_DelayedReplay );
  284. m_DelayedReplay.Invalidate();
  285. }
  286. }
  287. bool bLocalPlayerChanged = m_LocalPlayer.Update();
  288. bool bHltvReplayButtonTimeOutChanged = UpdateHltvReplayButtonTimeOutState();
  289. if ( bLocalPlayerChanged || bHltvReplayButtonTimeOutChanged )
  290. {
  291. CS_FreezePanel_OnHltvReplayButtonStateChanged();
  292. if ( m_LocalPlayer.m_bLastSeenAlive && !m_nHltvReplayDelay ) // || m_bHltvReplayButtonTimedOut would cancel pending replay start, but we already don't allow replays right before time-out events
  293. CancelDelayedHltvReplay(); // we can't have a replay if we see the player alive in real-time timeline
  294. }
  295. if ( m_nHltvReplayDelay )
  296. {
  297. // set the primary target
  298. int nPrevTarget = HLTVCamera()->GetCurrentTargetEntindex(), nNewTarget = m_nHltvReplayPrimaryTarget;
  299. if ( C_BasePlayer *pPlayer = UTIL_PlayerByIndex( nNewTarget ) )
  300. {
  301. if ( !pPlayer->IsAlive() )
  302. {
  303. if ( C_BaseEntity *pKillerSpectating = pPlayer->GetObserverTarget() )
  304. {
  305. nNewTarget = pKillerSpectating->entindex();
  306. }
  307. }
  308. if ( nNewTarget != nPrevTarget )
  309. {
  310. HLTVCamera()->SetPrimaryTarget( nNewTarget );
  311. }
  312. }
  313. if ( !m_bWaitingForHltvReplayTick && gpGlobals->tickcount > m_nHltvReplayStopAt + 32 )
  314. {
  315. // time to ask server to stop replay, again
  316. RequestCancelHltvReplay( true );
  317. // .. and ask the server again in 32 ticks, if this packet is lost, too
  318. m_nHltvReplayStopAt = gpGlobals->tickcount;
  319. }
  320. }
  321. if ( flPrevReplaySoundFadeAmount != m_flReplaySoundFadeAmount )
  322. {
  323. SetReplaySoundMixLayer( m_flReplaySoundFadeAmount );
  324. }
  325. }
  326. void CHltvReplaySystem::PurgeRagdollBoneCache()
  327. {
  328. if ( int nCount = m_mapCachedRagdollBones.Count() )
  329. {
  330. // destroy all unclaimed bones
  331. FOR_EACH_HASHTABLE( m_mapCachedRagdollBones, it )
  332. {
  333. FreeCachedRagdollBones( m_mapCachedRagdollBones[ it ] );
  334. }
  335. m_mapCachedRagdollBones.Purge();
  336. DevMsg( "%d ragdoll states purged\n", nCount );
  337. }
  338. }
  339. bool CHltvReplaySystem::IsFadeoutActive()
  340. {
  341. return m_flFadeoutEndTime > 0.0f && gpGlobals->curtime > m_flFadeoutEndTime - m_flFadeoutDuration; // fadeout is requested and has already started
  342. }
  343. bool CHltvReplaySystem::IsFadeoutFinished()
  344. {
  345. return gpGlobals->curtime >= m_flFadeoutEndTime;
  346. }
  347. void CHltvReplaySystem::OnLevelShutdown()
  348. {
  349. m_bWaitingForHltvReplayTick = false;
  350. StopFades();
  351. StopHltvReplay();
  352. Update();
  353. }
  354. void CHltvReplaySystem::StopFadeout()
  355. {
  356. if ( m_flFadeoutEndTime > 0 )
  357. DevMsg( "%.2f Stopping fadeout\n", gpGlobals->curtime );
  358. SetReplaySoundMixLayer( 0.0f );
  359. m_flFadeoutEndTime = -1000;
  360. }
  361. //ConVar spec_replay_sound_fade( "spec_replay_sound_fade", "2", /*FCVAR_CLIENTDLL | FCVAR_RELEASE*/ 0, "0 : fade everything out at replay transition; 1 : use ReplayBlinkLayer for the replay sound transition; 2: use both ReplayLayer and ReplayBlinkLayer for replay sound transition" );
  362. void CHltvReplaySystem::SetReplaySoundMixLayer( float flFade )
  363. {
  364. static int s_nReplayBlinkLayerIndex = engine->GetMixLayerIndex( "ReplayBlinkLayer" );
  365. engine->SetMixLayerLevel( s_nReplayBlinkLayerIndex, flFade );
  366. //enginesound->SetReplaySoundFade( m_flReplaySoundFadeAmount );
  367. }
  368. void CHltvReplaySystem::StartFadeout( float flTimeEnd, float flDuration )
  369. {
  370. DevMsg( "%.2f Starting fadeout %.2f->%.2f\n", gpGlobals->curtime, flTimeEnd, flDuration );
  371. m_flFadeoutEndTime = flTimeEnd;
  372. m_flFadeoutDuration = flDuration;
  373. }
  374. void CHltvReplaySystem::StartFadein( float flStartRealTime )
  375. {
  376. m_flFadeinStartRealTime = flStartRealTime;
  377. m_flFadeinDuration = spec_replay_fadein.GetFloat();
  378. }
  379. void CHltvReplaySystem::StopFades()
  380. {
  381. StopFadeout();
  382. m_flFadeinStartRealTime = -1000;
  383. }
  384. void CHltvReplaySystem::OnLevelInit()
  385. {
  386. m_DelayedReplay.Reset();
  387. m_bWaitingForHltvReplayTick = false;
  388. m_flHltvLastRequestRealTime = 0;
  389. m_flCurrentPlaybackTime = 0;
  390. StopFades();
  391. Assert( !m_nHltvReplayDelay ); // we MAY be in demo playback
  392. if ( !m_bListeningForGameEvents )
  393. {
  394. ListenForGameEvent( "round_start" );
  395. ListenForGameEvent( "bot_takeover" );
  396. ListenForGameEvent( "player_death" );
  397. m_bListeningForGameEvents = true; // now we're listening
  398. }
  399. }
  400. void CHltvReplaySystem::FireGameEvent( IGameEvent *event )
  401. {
  402. const char *pEventName = event->GetName();
  403. if ( !V_strcmp( pEventName, "round_start" ) )
  404. {
  405. m_nExperimentalEvents = 0;
  406. CancelDelayedHltvReplay();
  407. }
  408. else if ( m_DelayedReplay.IsValid() && !V_strcmp( pEventName, "bot_takeover" ) )
  409. {
  410. C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
  411. int nEventUserID = event->GetInt( "userid", -1 );
  412. if ( pLocalPlayer->GetObserverMode() != OBS_MODE_NONE && pLocalPlayer->GetUserID() == nEventUserID )
  413. {
  414. int nBotId = event->GetInt( "botid" );
  415. C_BasePlayer *pBot = UTIL_PlayerByUserId( nBotId );
  416. DevMsg( "%.2f Aborting hltv replay scheduled @%.2f because %s (%d) took over bot %s (%d)\n", gpGlobals->curtime, m_DelayedReplay.flTime, pLocalPlayer->GetPlayerName(), nEventUserID, pBot ? pBot->GetPlayerName() : "(null)", nBotId );
  417. CancelDelayedHltvReplay();
  418. }
  419. }
  420. else if ( !V_strcmp( pEventName, "player_death" ) )
  421. {
  422. OnPlayerDeath( event );
  423. }
  424. }
  425. float CHltvReplaySystem::GetHltvReplayDistortAmount()const
  426. {
  427. return m_nHltvReplayDelay ? 1.0f : 0.0f; // no distortion in highlights or lowlights, just the fade in/out
  428. }
  429. void CHltvReplaySystem::OnDemoPlayback( bool bPlaying )
  430. {
  431. m_bDemoPlayback = bPlaying;
  432. if ( !bPlaying )
  433. {
  434. if ( !m_nHltvReplayDelay )
  435. {
  436. StopFades();
  437. }
  438. m_nDemoPlaybackXuid = 0;
  439. m_bDemoPlaybackLowLights = false;
  440. }
  441. // by default, we don't know if we need fade in demo playback
  442. m_nDemoPlaybackStartAt = 0;
  443. m_nDemoPlaybackStopAt = 0;
  444. m_nCurrentPlaybackTick = 0;
  445. m_flCurrentPlaybackTime = gpGlobals->curtime;
  446. EmitTimeJump();
  447. }
  448. void CHltvReplaySystem::SetDemoPlaybackHighlightXuid( uint64 xuid, bool bLowlights )
  449. {
  450. m_nDemoPlaybackXuid = xuid;
  451. m_bDemoPlaybackLowLights = bLowlights;
  452. if ( !m_nSteamSelfAccountId && steamapicontext )
  453. {
  454. if ( ISteamUser* pSteamUser = steamapicontext->SteamUser() )
  455. {
  456. m_nSteamSelfAccountId = pSteamUser->GetSteamID().GetAccountID();
  457. }
  458. }
  459. }
  460. bool CHltvReplaySystem::IsDemoPlaybackXuidOther()const
  461. {
  462. return m_nSteamSelfAccountId != uint32( m_nDemoPlaybackXuid );
  463. }
  464. C_BasePlayer *CHltvReplaySystem::GetDemoPlaybackPlayer()
  465. {
  466. if ( C_BasePlayer *pPlayer = m_DemoPlaybackPlayer.Get() )
  467. {
  468. CSteamID steamId;
  469. if ( pPlayer->GetSteamID(&steamId) )
  470. {
  471. if ( steamId.GetAccountID() == uint32( m_nDemoPlaybackXuid ) )
  472. return pPlayer;
  473. }
  474. }
  475. m_DemoPlaybackPlayer.Term();
  476. for ( int i = 1; i <= MAX_PLAYERS; ++i )
  477. {
  478. if ( C_BasePlayer *pPlayer = UTIL_PlayerByIndex( i ) )
  479. {
  480. CSteamID steamId;
  481. if ( pPlayer->GetSteamID( &steamId ) )
  482. {
  483. if ( steamId.GetAccountID() == uint32( m_nDemoPlaybackXuid ) )
  484. {
  485. m_DemoPlaybackPlayer.Set( pPlayer );
  486. return pPlayer;
  487. }
  488. }
  489. }
  490. }
  491. return NULL;
  492. }
  493. void CHltvReplaySystem::SetDemoPlaybackFadeBrackets( int nCurrentPlaybackTick, int nStartAt, int nStopAt )
  494. {
  495. Assert( m_bDemoPlayback );
  496. if ( nCurrentPlaybackTick + 320 < nStartAt && nStartAt != m_nDemoPlaybackStartAt )
  497. {
  498. // we're starting a jump forward in time. The jump is long (over 320 ticks), so it may take some time, and we don't want the sounds currently in the queue to randomly play
  499. enginesound->StopAllSounds( true );
  500. }
  501. m_nDemoPlaybackStartAt = nStartAt;
  502. m_nDemoPlaybackStopAt = nStopAt;
  503. m_nCurrentPlaybackTick = nCurrentPlaybackTick;
  504. m_flCurrentPlaybackTime = gpGlobals->curtime;
  505. }
  506. bool CHltvReplaySystem::IsHltvReplayFeatureEnabled()
  507. {
  508. #if HLTV_REPLAY_ENABLED
  509. if ( g_bEngineIsHLTV )
  510. return false; // no replay during replay or GOTV
  511. static ConVarRef spec_replay_enable( "spec_replay_enable" );
  512. return spec_replay_enable.GetBool();
  513. #else
  514. return false;
  515. #endif
  516. }
  517. bool CHltvReplaySystem::IsHltvReplayButtonEnabled()
  518. {
  519. if ( !IsHltvReplayFeatureEnabled() )
  520. return false;
  521. static ConVarRef spec_replay_rate_limit( "spec_replay_rate_limit" );
  522. if ( m_LocalPlayer.m_bLastSeenAlive ) // must be dead to allow replay
  523. return false;
  524. // Note: player death time may not be networked yet, so current pPlayer->GetDeathTime() may not be up to date
  525. if ( m_bHltvReplayButtonTimedOut )
  526. return false;
  527. if ( gpGlobals->curtime > m_DelayedReplay.flEventTime + GetReplayMessageTime() )
  528. return false;// did we die too long ago ? Then we can't replay
  529. if ( gpGlobals->realtime < m_flHltvLastRequestRealTime + spec_replay_rate_limit.GetFloat() ) // rate-limit mashing the Replay button
  530. return false;
  531. return true;
  532. }
  533. void CHltvReplaySystem::CancelDelayedHltvReplay()
  534. {
  535. StopFadeout(); // fadeins are fine, leave them be
  536. // abort the pending death replay, stop all fades because the user took over a bot
  537. if ( m_DelayedReplay.IsValid() )
  538. {
  539. m_DelayedReplay.Stop();
  540. CS_FreezePanel_OnHltvReplayButtonStateChanged();
  541. }
  542. }
  543. void CHltvReplaySystem::RequestHltvReplayDeath()
  544. {
  545. ReplayParams_t rp;
  546. rp.nRequest = REPLAY_EVENT_DEATH;
  547. RequestHltvReplay( rp );
  548. }
  549. void CHltvReplaySystem::RequestHltvReplay( const ReplayParams_t &replay )
  550. {
  551. CCLCMsg_HltvReplay_t msgReplay;
  552. msgReplay.set_request( replay.nRequest );
  553. float flSlowdown = spec_replay_rate_slowdown.GetFloat();
  554. if ( flSlowdown != 1.0f )
  555. {
  556. float flSlowdownLength = spec_replay_rate_slowdown_length.GetFloat();
  557. if ( flSlowdownLength > 0.01f && flSlowdownLength < 5.0f )
  558. {
  559. msgReplay.set_slowdown_length( flSlowdownLength );
  560. msgReplay.set_slowdown_rate( flSlowdown );
  561. }
  562. }
  563. if ( replay.nRequest != REPLAY_EVENT_DEATH )
  564. {
  565. msgReplay.set_primary_target_ent_index( replay.nPrimaryTargetEntIndex );
  566. msgReplay.set_event_time( replay.flEventTime );
  567. }
  568. // if we want to always see from victim's perspective, always set the primary target to victim
  569. if ( spec_replay_victim_pov.GetBool() && replay.nPrimaryVictimEntIndex > 0 )
  570. {
  571. msgReplay.set_primary_target_ent_index( replay.nPrimaryVictimEntIndex );
  572. }
  573. engine->SendMessageToServer( &msgReplay );
  574. StartFadein( gpGlobals->realtime + 1.0f ); // if we don't get hltv replay message in 1 second, it means we couldn't start replay normally, and it's time to fade out
  575. StopFadeout();
  576. m_flHltvLastRequestRealTime = gpGlobals->realtime;
  577. m_nExperimentalEvents |= EE_REPLAY_REQUESTED;
  578. }
  579. void CHltvReplaySystem::RequestCancelHltvReplay( bool bSuppressFadeout )
  580. {
  581. if ( GetHltvReplayDelay() )
  582. {
  583. CCLCMsg_HltvReplay_t msgReplay;
  584. msgReplay.set_request( 0 ); // cancel replay
  585. engine->SendMessageToServer( &msgReplay );
  586. if ( bSuppressFadeout )
  587. {
  588. StopFadeout();
  589. }
  590. else
  591. {
  592. if ( !IsFadeoutActive() )
  593. {// if there's no other fadeout happening now, start fading out for cancel; pretend we're already .1 seconde into fadeout
  594. StartFadeout( gpGlobals->curtime + m_flFadeoutDuration, 0.3f );// approximately how much it'll take for server to come back with the cancellation
  595. }
  596. }
  597. m_nExperimentalEvents |= EE_REPLAY_CANCELLED;
  598. DevMsg( "%.2f Replay Cancel requested (%d,%d..%d)\n", gpGlobals->curtime, gpGlobals->tickcount, m_nHltvReplayStartAt, m_nHltvReplayStopAt );
  599. }
  600. else
  601. {
  602. DevMsg( "%.2f Replay Cancel request failed, not in replay now\n", gpGlobals->curtime );
  603. }
  604. }
  605. bool IsPlayerTeamDead( int nPlayerEntIndex )
  606. {
  607. if ( C_BasePlayer* pPrincipalPlayer = UTIL_PlayerByIndex( nPlayerEntIndex ) )
  608. {
  609. int nTeam = pPrincipalPlayer->GetTeamNumber();
  610. for ( int i = 1; i <= MAX_PLAYERS; ++i )
  611. {
  612. if ( i != nPlayerEntIndex )
  613. {
  614. if ( CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ) )
  615. {
  616. Assert( pPlayer != pPrincipalPlayer );
  617. if ( pPlayer->GetTeamNumber() == nTeam )
  618. {
  619. if ( pPlayer->IsAlive() )
  620. return false;
  621. }
  622. }
  623. }
  624. }
  625. return true;
  626. }
  627. else
  628. return false; // something's wrong - can't find a team of a non-existing player
  629. }
  630. void CHltvReplaySystem::OnPlayerDeath( IGameEvent *event )
  631. {
  632. if ( m_DelayedReplay.IsValid() || GetHltvReplayDelay() )
  633. {
  634. // we're already replaying or preparing to replay. Let's not react to new player deaths
  635. return;
  636. }
  637. int iPlayerIndexVictim = engine->GetPlayerForUserID( event->GetInt( "userid" ) );
  638. int iPlayerIndexKiller = engine->GetPlayerForUserID( event->GetInt( "attacker" ) );
  639. C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
  640. if ( !pLocalPlayer )
  641. {
  642. DevMsg( "OnPlayerDeath: Cannot replay without local player\n" );
  643. return;
  644. }
  645. bool isLocalVictim = pLocalPlayer && iPlayerIndexVictim == pLocalPlayer->entindex(), isReplayAvailable = iPlayerIndexKiller && iPlayerIndexKiller != iPlayerIndexVictim && !event->GetBool( "noreplay", false );
  646. UpdateHltvReplayButtonTimeOutState();
  647. if ( !isReplayAvailable || m_bHltvReplayButtonTimedOut )
  648. return; // no need to react to anything if replay isn't available for this death
  649. if ( isLocalVictim )
  650. {
  651. m_LocalPlayer.m_bLastSeenAlive = false; // we can assume the local player is dead and unlock replays. The next Update may turn it back on for a few frames, but then it'll go back to dead
  652. if ( iPlayerIndexVictim != iPlayerIndexKiller ) // we don't replay suicides
  653. {
  654. m_DelayedReplay.bFinalKillOfRound = false;
  655. if ( PrepareHltvReplayCountdown() )
  656. {
  657. m_DelayedReplay.nRequest = REPLAY_EVENT_DEATH;
  658. }
  659. }
  660. }
  661. else if (
  662. iPlayerIndexKiller <= MAX_PLAYERS // the killer must be an actual player (not e.g. a bomb) for replay to make sense
  663. && !m_LocalPlayer.m_bLastSeenAlive && spec_replay_autostart.GetBool()
  664. && spec_replay_others_experimental.GetBool() && IsPlayerTeamDead( iPlayerIndexVictim )
  665. )
  666. {
  667. int nLocalPlayerTeam = pLocalPlayer->GetTeamNumber();
  668. if ( nLocalPlayerTeam == TEAM_CT || nLocalPlayerTeam == TEAM_TERRORIST )
  669. {
  670. if ( CBaseEntity *pTarget = pLocalPlayer->GetObserverTarget() )
  671. {
  672. int nObserverTarget = pTarget->entindex();
  673. if ( nObserverTarget != iPlayerIndexKiller && nObserverTarget != iPlayerIndexVictim ) //
  674. {
  675. m_DelayedReplay.bFinalKillOfRound = true;
  676. // EXPERIMENT: showing death of other players to people
  677. if ( PrepareHltvReplayCountdown() )
  678. {
  679. // we're dead anyway... request a replay, it may be interesting
  680. m_DelayedReplay.nRequest = REPLAY_EVENT_GENERIC;
  681. extern void CS_FreezePanel_ResetDamageText( int iPlayerIndexKiller, int iPlayerIndexVictim );
  682. CS_FreezePanel_ResetDamageText( iPlayerIndexKiller, iPlayerIndexVictim );
  683. }
  684. }
  685. }
  686. }
  687. }
  688. if ( isLocalVictim || m_DelayedReplay.nRequest >= 0 )
  689. {// Some interesting event happened, and we are not replaying anything else. Store some stats that may or may not be useful for later replay
  690. m_DelayedReplay.nPrimaryTargetEntIndex = iPlayerIndexKiller;
  691. m_DelayedReplay.flEventTime = gpGlobals->curtime;
  692. m_DelayedReplay.flTime = gpGlobals->curtime + spec_replay_autostart_delay.GetFloat();
  693. m_DelayedReplay.nPrimaryVictimEntIndex = iPlayerIndexVictim;
  694. m_DelayedReplay.bPrimaryVictimIsLocalPlayer = isLocalVictim;
  695. }
  696. }
  697. void CHltvReplaySystem::OnLocalPlayerRespawning()
  698. {
  699. }
  700. bool CHltvReplaySystem::PrepareHltvReplayCountdown()
  701. {
  702. bool bCountdownStarted = false;
  703. if ( !GetHltvReplayDelay() )
  704. {
  705. m_nExperimentalEvents |= EE_REPLAY_OFFERED;
  706. // if we have to show freezeframe before deathcam, this is the place to request it
  707. if ( IsHltvReplayFeatureEnabled() && spec_replay_autostart.GetBool() )
  708. {
  709. bCountdownStarted = true;
  710. m_nExperimentalEvents |= EE_REPLAY_AUTOMATIC;
  711. float flDelay = spec_replay_autostart_delay.GetFloat();
  712. m_DelayedReplay.flTime = gpGlobals->curtime + flDelay;
  713. if ( !IsFadeoutActive() )
  714. {
  715. // use fadeout
  716. StartFadeout( gpGlobals->curtime + flDelay, Min( flDelay, spec_replay_fadeout.GetFloat() ) );
  717. }
  718. // DevMsg( "%.2f Replay: starting to fade out\n", gpGlobals->curtime ); // replayfade
  719. }
  720. CS_FreezePanel_OnHltvReplayButtonStateChanged();
  721. }
  722. return bCountdownStarted;
  723. }
  724. ConVar r_replay_post_effect( "r_replay_post_effect", "-1", FCVAR_CHEAT );
  725. bool CHltvReplaySystem::WantsReplayEffect() const
  726. {
  727. if ( r_replay_post_effect.GetInt() == -1 )
  728. {
  729. return ( g_HltvReplaySystem.GetHltvReplayDelay() != 0 );
  730. }
  731. else
  732. return r_replay_post_effect.GetBool();
  733. }
  734. void CHltvReplaySystem::CacheRagdollBones()
  735. {
  736. PurgeRagdollBoneCache();
  737. if ( !spec_replay_cache_ragdolls.GetBool() )
  738. return;
  739. C_BaseEntityIterator iterator;
  740. C_BaseEntity *pEnt;
  741. int nRagdollsCached = 0;
  742. while ( ( pEnt = iterator.Next() ) != NULL )
  743. {
  744. if ( C_CSRagdoll * pRagdoll = dynamic_cast< C_CSRagdoll * >( pEnt ) )
  745. {
  746. if ( int nEntIndex = pRagdoll->entindex() )
  747. {
  748. if ( pRagdoll->m_pRagdoll )
  749. {
  750. nRagdollsCached++;
  751. int nBones = pRagdoll->GetModelPtr()->numbones();
  752. int nBodyParts = pRagdoll->m_pRagdoll->RagdollBoneCount();
  753. CachedRagdollBones_t *pCachedRagdoll = ( CachedRagdollBones_t * )MemAlloc_AllocAligned( sizeof( CachedRagdollBones_t ) + sizeof( matrix3x4a_t ) * ( nBones + nBodyParts + 1 ), 16 );
  754. pCachedRagdoll->bAllAsleep = true;
  755. pCachedRagdoll->nBones = nBones;
  756. pCachedRagdoll->nBodyParts = nBodyParts;
  757. matrix3x4a_t *pBones = pCachedRagdoll->GetBones();
  758. pBones[ nBones ].SetColumn( Vector( 11, 22, 33 ), X_AXIS );
  759. pRagdoll->SetupBones( pBones, nBones, BONE_USED_BY_ANYTHING, gpGlobals->curtime );
  760. matrix3x4a_t *pBodyParts = pCachedRagdoll->GetBodyParts();
  761. for ( int i = 0; i < nBodyParts; i++ )
  762. {
  763. if ( IPhysicsObject* pObj = pRagdoll->m_pRagdoll->RagdollPhysicsObject( i ) )
  764. {
  765. pObj->GetPositionMatrix( &pBodyParts[ i ] );
  766. if ( !pObj->IsAsleep() )
  767. pCachedRagdoll->bAllAsleep = false;
  768. }
  769. else
  770. {
  771. pBodyParts[ i ].SetToIdentity();
  772. }
  773. }
  774. Assert( pBones[ nBones ].GetColumn( X_AXIS ) == Vector( 11, 22, 33 ) );
  775. Assert( m_mapCachedRagdollBones.Find( nEntIndex ) == m_mapCachedRagdollBones.InvalidHandle() );
  776. m_mapCachedRagdollBones.Insert( nEntIndex, pCachedRagdoll );
  777. }
  778. }
  779. else
  780. {
  781. Warning( "A ragdoll without entindex found @%p\n", pRagdoll );
  782. }
  783. }
  784. }
  785. if ( nRagdollsCached )
  786. DevMsg( "%d ragdolls cached\n", nRagdollsCached );
  787. }
  788. CachedRagdollBones_t *CHltvReplaySystem::GetCachedRagdollBones( int nEntIndex, bool bTake )
  789. {
  790. if ( gpGlobals->tickcount > m_nHltvReplayBeginTick + 5 )
  791. return NULL; // this cache isn't useful anymore, we need to create ragdoll dynamically - otherwise it'll just teleport to the final position instantaneously
  792. UtlHashHandle_t hFind = m_mapCachedRagdollBones.Find( nEntIndex );
  793. CachedRagdollBones_t *pTakenBones = NULL;
  794. if ( hFind != m_mapCachedRagdollBones.InvalidHandle() )
  795. {
  796. pTakenBones = m_mapCachedRagdollBones[ hFind ];
  797. if ( bTake )
  798. m_mapCachedRagdollBones.RemoveByHandle( hFind );
  799. }
  800. return pTakenBones;
  801. }
  802. void CHltvReplaySystem::FreeCachedRagdollBones( CachedRagdollBones_t*pBones )
  803. {
  804. MemAlloc_FreeAligned( pBones );
  805. }
  806. CHltvReplaySystem::CLocalPlayerProps::CLocalPlayerProps()
  807. {
  808. m_bLastSeenAlive = false;
  809. m_nLastTickUpdated = gpGlobals->tickcount;
  810. }
  811. bool CHltvReplaySystem::CLocalPlayerProps::Update()
  812. {
  813. if ( m_nLastTickUpdated != gpGlobals->tickcount )
  814. {
  815. m_nLastTickUpdated = gpGlobals->tickcount;
  816. bool bLastLastSeenAlive = m_bLastSeenAlive;
  817. if ( C_BasePlayer *pBasePlayer = C_BasePlayer::GetLocalPlayer() )
  818. {
  819. m_bLastSeenAlive = pBasePlayer->IsAlive();
  820. }
  821. else
  822. {
  823. m_bLastSeenAlive = false;
  824. }
  825. if ( bLastLastSeenAlive != m_bLastSeenAlive )
  826. return true; // something changed
  827. }
  828. return false; // nothing changed
  829. }
  830. float CHltvReplaySystem::GetReplayMessageTime()
  831. {
  832. static ConVarRef spec_replay_message_time( "spec_replay_message_time" );
  833. float flReplayMessageTime = spec_replay_message_time.GetFloat();
  834. if ( CCSGameRules* pRules = CSGameRules() )
  835. {
  836. if ( pRules->m_iRoundWinStatus != WINNER_NONE )
  837. {// the message time is cut short by the end of round
  838. flReplayMessageTime = spec_replay_autostart_delay.GetFloat();
  839. }
  840. }
  841. return flReplayMessageTime;
  842. }
  843. bool CHltvReplaySystem::UpdateHltvReplayButtonTimeOutState()
  844. {
  845. bool bTimedOut = false;
  846. if ( CCSGameRules* pRules = CSGameRules() )
  847. {
  848. if ( pRules->IsWarmupPeriod() && !pRules->IsWarmupPeriodPaused() )
  849. {
  850. static ConVarRef spec_replay_leadup_time( "spec_replay_leadup_time" );
  851. static ConVarRef spec_replay_winddown_time( "spec_replay_winddown_time" );
  852. float flWarmupTimeLeft = pRules->GetWarmupPeriodEndTime() - gpGlobals->curtime;
  853. float flReplayRoundtripTime = spec_replay_autostart_delay.GetFloat() + spec_replay_leadup_time.GetFloat() + spec_replay_winddown_time.GetFloat();
  854. if ( flWarmupTimeLeft <= flReplayRoundtripTime )
  855. {
  856. bTimedOut = true;
  857. }
  858. }
  859. }
  860. bool bStateUpdated = ( m_bHltvReplayButtonTimedOut != bTimedOut );
  861. m_bHltvReplayButtonTimedOut = bTimedOut;
  862. return bStateUpdated;
  863. }