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
999 lines
33 KiB
//======= Copyright (c) Valve Corporation, All rights reserved. ======
|
|
#include "cbase.h"
|
|
#include "hltvreplaysystem.h"
|
|
#include "hltvcamera.h"
|
|
#include "cs_gamerules.h"
|
|
#include "iviewrender.h"
|
|
#include "engine/IEngineSound.h"
|
|
#include "netmessages.h"
|
|
#include "cstrike15/c_cs_player.h"
|
|
#include "ihltv.h"
|
|
|
|
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" );
|
|
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" );
|
|
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" );
|
|
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" );
|
|
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
|
|
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" );
|
|
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" );
|
|
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" );
|
|
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" );
|
|
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." );
|
|
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
|
|
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
|
|
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." );
|
|
CHltvReplaySystem g_HltvReplaySystem;
|
|
extern void CS_FreezePanel_OnHltvReplayButtonStateChanged();
|
|
|
|
CHltvReplaySystem::CHltvReplaySystem()
|
|
{
|
|
m_nHltvReplayDelay = 0;
|
|
m_nHltvReplayStopAt = 0;
|
|
m_nHltvReplayStartAt = 0;
|
|
m_flHltvLastRequestRealTime = 0;
|
|
m_bWaitingForHltvReplayTick = false;
|
|
m_flStartedWaitingForHltvReplayTickRealTime = 0;
|
|
m_bDemoPlayback = false;
|
|
m_bListeningForGameEvents = false;
|
|
m_nDemoPlaybackStartAt = 0;
|
|
m_nDemoPlaybackStopAt = 0;
|
|
m_nCurrentPlaybackTick = 0;
|
|
m_flCurrentPlaybackTime = 0.0f;
|
|
m_nDemoPlaybackXuid = 0;
|
|
m_nSteamSelfAccountId = 0;
|
|
m_flReplayVideoFadeAmount = 0.0f;
|
|
m_flReplaySoundFadeAmount = 0.0f;
|
|
m_nExperimentalEvents = 0;
|
|
m_flFadeinStartRealTime = -1000;
|
|
m_flFadeoutEndTime = -1000;
|
|
m_DelayedReplay.Reset();
|
|
m_nHltvReplayBeginTick = -1;
|
|
m_nHltvReplayPrimaryTarget = 0;
|
|
m_bHltvReplayButtonTimedOut = false;
|
|
}
|
|
|
|
CHltvReplaySystem::~CHltvReplaySystem()
|
|
{
|
|
|
|
}
|
|
|
|
int CL_GetHltvReplayDelay() { return g_HltvReplaySystem.GetHltvReplayDelay(); }
|
|
|
|
void CHltvReplaySystem::EmitTimeJump()
|
|
{
|
|
GetHud().OnTimeJump();
|
|
#if defined( CSTRIKE15 )
|
|
C_BasePlayer::OnTimeJumpAllPlayers();
|
|
#endif
|
|
//GetHud().UpdateHud( true ); // may call SFUniqueAlerts::ShowHltvReplayAlertPanel
|
|
//GetHud().ProcessInput( true );
|
|
//GetHud().OnTimeJump();
|
|
}
|
|
|
|
void CHltvReplaySystem::StopHltvReplay()
|
|
{
|
|
if ( m_nHltvReplayDelay )
|
|
{
|
|
CSVCMsg_HltvReplay msg;
|
|
OnHltvReplay( msg );
|
|
}
|
|
}
|
|
|
|
|
|
void CHltvReplaySystem::OnHltvReplay( const CSVCMsg_HltvReplay &msg )
|
|
{
|
|
int nNewReplayDelay = msg.delay();
|
|
DevMsg( "%.2f OnHltvReplay %s\n", gpGlobals->curtime, nNewReplayDelay ? "START" : "END" );
|
|
|
|
extern bool g_bForceCLPredictOff;
|
|
g_bForceCLPredictOff = ( nNewReplayDelay != 0 );
|
|
|
|
static int s_nReplayLayerIndex = engine->GetMixLayerIndex( "ReplayLayer" );
|
|
|
|
if ( m_nHltvReplayDelay )
|
|
{
|
|
if ( !nNewReplayDelay )
|
|
{
|
|
// we're about to go out of HLTV replay. Current time is delayed replay time.
|
|
if ( C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer() )
|
|
{
|
|
C_BaseEntity::StopSound( -1, "Deathcam.Review_Start" );
|
|
if ( spec_replay_review_sound.GetBool() )
|
|
pPlayer->EmitSound( "Deathcam.Review_End" );
|
|
}
|
|
engine->SetMixLayerLevel( s_nReplayLayerIndex, 0.0f );
|
|
IGameEvent *pEvent = gameeventmanager->CreateEvent( "hide_freezepanel" );
|
|
if ( pEvent )
|
|
{
|
|
gameeventmanager->FireEventClientSide( pEvent );
|
|
}
|
|
if ( snd_deathcam_replay_mix.GetBool() )
|
|
{
|
|
ConVar *pSoundmixer = ( ConVar * )cvar->FindVar( "snd_soundmixer" );
|
|
pSoundmixer->Revert();
|
|
}
|
|
}
|
|
StopFadeout();// Msg( "%.2f Fadeout not stopped (%d,%d,%.2f)\n", gpGlobals->curtime, m_nHltvReplayDelay, nNewReplayDelay, m_flFadeoutEndTime );
|
|
}
|
|
else
|
|
{
|
|
if ( nNewReplayDelay )
|
|
{
|
|
m_DelayedReplay.Invalidate(); // we don't do delayed replay if we are about to do the replay
|
|
CacheRagdollBones();
|
|
// we're about to go into HLTV replay. Current time is real time.
|
|
HLTVCamera()->SetPrimaryTarget( m_nHltvReplayPrimaryTarget = msg.primary_target() );
|
|
HLTVCamera()->SetMode( OBS_MODE_IN_EYE );
|
|
GetHud().ResetHUD();
|
|
/*IGameEvent *pEvent = gameeventmanager->CreateEvent( "hide_freezepanel" );
|
|
if ( pEvent )
|
|
{
|
|
gameeventmanager->FireEventClientSide( pEvent );
|
|
}*/
|
|
if ( spec_replay_review_sound.GetBool() )
|
|
{
|
|
if ( C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer() )
|
|
{
|
|
EmitSound_t params;
|
|
params.m_pSoundName = "Deathcam.Review_Start";
|
|
params.m_bWarnOnDirectWaveReference = true;
|
|
params.m_hSoundScriptHash = soundemitterbase->HashSoundName( params.m_pSoundName );
|
|
CLocalPlayerFilter filter;
|
|
C_BaseEntity::EmitSound( filter, -1, params );
|
|
}
|
|
}
|
|
if ( snd_deathcam_replay_mix.GetBool() )
|
|
{
|
|
ConVar *pSoundmixer = ( ConVar * )cvar->FindVar( "snd_soundmixer" );
|
|
pSoundmixer->SetValue( "DeathCam_Replay_Mix" );
|
|
}
|
|
engine->SetMixLayerLevel( s_nReplayLayerIndex, 1.0f );
|
|
if ( int nSlowdownRatio = msg.replay_slowdown_rate() )
|
|
{
|
|
m_flHltvReplaySlowdownRate = 1.0f / nSlowdownRatio;
|
|
m_nHltvReplaySlowdownBegin = msg.replay_slowdown_begin();
|
|
m_nHltvReplaySlowdownEnd = msg.replay_slowdown_end();
|
|
}
|
|
else
|
|
{
|
|
m_nHltvReplaySlowdownBegin = 0;
|
|
m_nHltvReplaySlowdownEnd = 0;
|
|
m_flHltvReplaySlowdownRate = 1.0f;
|
|
}
|
|
|
|
m_nExperimentalEvents |= EE_REPLAY_STARTED;
|
|
// currently, Hltv Replay Active (m_nHltvReplayDelay!=0)
|
|
StartFadeout( msg.replay_stop_at() * gpGlobals->interval_per_tick, spec_replay_fadeout.GetFloat() );
|
|
}
|
|
else
|
|
{
|
|
// Hltv replay is NOT active. Requested NO Hltv replay . There's nothing to do.
|
|
StopFadeout(); //Msg( "%.2f Fadeout not stopped-2 (%d,%d,%.2f)\n", gpGlobals->curtime, m_nHltvReplayDelay, nNewReplayDelay, m_flFadeoutEndTime );
|
|
}
|
|
}
|
|
StartFadein( gpGlobals->realtime );
|
|
m_flHltvLastRequestRealTime = gpGlobals->realtime;
|
|
m_bWaitingForHltvReplayTick = true;
|
|
m_flStartedWaitingForHltvReplayTickRealTime = gpGlobals->realtime;
|
|
m_nHltvReplayBeginTick = gpGlobals->tickcount;
|
|
debugoverlay->ClearAllOverlays();
|
|
|
|
m_nHltvReplayDelay = nNewReplayDelay;
|
|
//enginesound->SetReplayMusicGain( nNewReplayDelay ? 0.0f : 1.0f );
|
|
|
|
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
|
|
m_nHltvReplayStartAt = msg.replay_start_at() - nNewReplayDelay;
|
|
// I'd like to call ParticleMgr()->RemoveAllEffects(); but there are still some entities referencing the particles in question
|
|
view->FreezeFrame( 0.0f );
|
|
Update();
|
|
|
|
EmitTimeJump();
|
|
}
|
|
|
|
|
|
void CHltvReplaySystem::OnHltvReplayTick()
|
|
{
|
|
Assert( m_bWaitingForHltvReplayTick );
|
|
m_bWaitingForHltvReplayTick = false;
|
|
m_nHltvReplayBeginTick = gpGlobals->tickcount;
|
|
Update();
|
|
CS_FreezePanel_OnHltvReplayButtonStateChanged();
|
|
}
|
|
|
|
|
|
float ReplayFade( float flCurTime, int nStartAt, int nStopAt, float flFadeIn, float flFadeOut )
|
|
{
|
|
if ( flFadeIn > 0.001f )
|
|
{
|
|
float flTimeIn = flCurTime - nStartAt * gpGlobals->interval_per_tick;
|
|
if ( flTimeIn < flFadeIn )
|
|
{
|
|
return 1.0f - Max( 0.0f, flTimeIn / flFadeIn );
|
|
}
|
|
}
|
|
|
|
if ( flFadeOut > 0.001f )
|
|
{
|
|
float flTimeOut = nStopAt * gpGlobals->interval_per_tick - flCurTime;
|
|
if ( flTimeOut < flFadeOut )
|
|
{
|
|
return 1.0f - Max( 0.0f, flTimeOut / flFadeOut );
|
|
}
|
|
}
|
|
return 0.0f;
|
|
}
|
|
|
|
void CHltvReplaySystem::Update()
|
|
{
|
|
float flCurTime = gpGlobals->curtime;
|
|
float flPrevReplaySoundFadeAmount = m_flReplaySoundFadeAmount;
|
|
//Assert( enginesound->GetReplaySoundFade() == m_flReplaySoundFadeAmount );
|
|
|
|
m_flReplayVideoFadeAmount = m_flReplaySoundFadeAmount = 0.0f; // there are no replay post effects going on, by default
|
|
|
|
if ( !m_nHltvReplayDelay && gpGlobals->tickcount > m_nHltvReplayBeginTick + 5 )
|
|
{
|
|
PurgeRagdollBoneCache();
|
|
}
|
|
|
|
if ( m_bWaitingForHltvReplayTick )
|
|
{
|
|
// 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
|
|
// 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
|
|
m_flReplayVideoFadeAmount = m_flReplaySoundFadeAmount = 1.0f; // fade everything out completely
|
|
|
|
if ( gpGlobals->realtime - m_flStartedWaitingForHltvReplayTickRealTime > 0.75f )
|
|
{
|
|
Msg( "Killer replay stuck waiting for tick message for %.2fs, requesting update\n", gpGlobals->realtime - m_flHltvLastRequestRealTime );
|
|
m_nExperimentalEvents |= EE_REPLAY_STUCK;
|
|
|
|
m_flStartedWaitingForHltvReplayTickRealTime = gpGlobals->realtime;
|
|
CCLCMsg_HltvReplay_t msgReplay;
|
|
msgReplay.set_request( REPLAY_EVENT_STUCK_NEED_FULL_UPDATE ); // request full update
|
|
engine->SendMessageToServer( &msgReplay );
|
|
}
|
|
}
|
|
else if ( m_bDemoPlayback )
|
|
{
|
|
if ( m_nDemoPlaybackStartAt < m_nDemoPlaybackStopAt )
|
|
{
|
|
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() );
|
|
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() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( m_flFadeinStartRealTime > 0.0f )
|
|
{
|
|
float flTimeIn = gpGlobals->realtime - m_flFadeinStartRealTime;
|
|
bool bFadeInStop = true;
|
|
|
|
if ( flTimeIn < m_flFadeinDuration )
|
|
{
|
|
m_flReplayVideoFadeAmount = Max( m_flReplayVideoFadeAmount, 1.0f - Max( 0.0f, flTimeIn / Max( m_flFadeinDuration, 0.01f ) ) );
|
|
bFadeInStop = false;
|
|
}
|
|
|
|
|
|
if ( flTimeIn < spec_replay_sound_fadein.GetFloat() )
|
|
{
|
|
m_flReplaySoundFadeAmount = Max( m_flReplaySoundFadeAmount, 1.0f - Max( 0.0f, flTimeIn / Max( spec_replay_sound_fadein.GetFloat(), 0.01f ) ) );
|
|
bFadeInStop = false;
|
|
}
|
|
|
|
if ( bFadeInStop )
|
|
m_flFadeinStartRealTime = -1000;
|
|
}
|
|
|
|
if ( m_flFadeoutEndTime > 0.0f )
|
|
{
|
|
float flTimeLeft = m_flFadeoutEndTime - gpGlobals->curtime;
|
|
if ( flTimeLeft < m_flFadeoutDuration )
|
|
{
|
|
m_flReplayVideoFadeAmount = Max( m_flReplayVideoFadeAmount, 1 - Max( 0.0f, flTimeLeft / Max( m_flFadeoutDuration, 0.01f ) ) );
|
|
// Msg( "%.2f +%.2f fade %.2f\n", gpGlobals->curtime, flTimeLeft, m_flReplayVideoFadeAmount ); // replayfade
|
|
}
|
|
if ( flTimeLeft < spec_replay_sound_fadeout.GetFloat() )
|
|
{
|
|
m_flReplaySoundFadeAmount = Max( m_flReplaySoundFadeAmount, 1 - Max( 0.0f, flTimeLeft / Max( spec_replay_sound_fadeout.GetFloat(), 0.01f ) ) );
|
|
}
|
|
|
|
if ( flTimeLeft < -1.0f )
|
|
{
|
|
// 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
|
|
DevMsg( "%.2f Aborting fadeout\n", gpGlobals->curtime );
|
|
m_flFadeoutEndTime = -1000;
|
|
m_flFadeinStartRealTime = gpGlobals->realtime;
|
|
m_flFadeinDuration = .3f; // fade in quickly
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( m_DelayedReplay.IsValid() )
|
|
{
|
|
if ( gpGlobals->curtime >= m_DelayedReplay.flTime )
|
|
{
|
|
if ( IsHltvReplayFeatureEnabled() ) // we checked the button enabled when we set up this delayed replay
|
|
RequestHltvReplay( m_DelayedReplay );
|
|
m_DelayedReplay.Invalidate();
|
|
}
|
|
}
|
|
|
|
bool bLocalPlayerChanged = m_LocalPlayer.Update();
|
|
bool bHltvReplayButtonTimeOutChanged = UpdateHltvReplayButtonTimeOutState();
|
|
if ( bLocalPlayerChanged || bHltvReplayButtonTimeOutChanged )
|
|
{
|
|
CS_FreezePanel_OnHltvReplayButtonStateChanged();
|
|
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
|
|
CancelDelayedHltvReplay(); // we can't have a replay if we see the player alive in real-time timeline
|
|
}
|
|
if ( m_nHltvReplayDelay )
|
|
{
|
|
// set the primary target
|
|
int nPrevTarget = HLTVCamera()->GetCurrentTargetEntindex(), nNewTarget = m_nHltvReplayPrimaryTarget;
|
|
if ( C_BasePlayer *pPlayer = UTIL_PlayerByIndex( nNewTarget ) )
|
|
{
|
|
if ( !pPlayer->IsAlive() )
|
|
{
|
|
if ( C_BaseEntity *pKillerSpectating = pPlayer->GetObserverTarget() )
|
|
{
|
|
nNewTarget = pKillerSpectating->entindex();
|
|
}
|
|
}
|
|
if ( nNewTarget != nPrevTarget )
|
|
{
|
|
HLTVCamera()->SetPrimaryTarget( nNewTarget );
|
|
}
|
|
}
|
|
|
|
if ( !m_bWaitingForHltvReplayTick && gpGlobals->tickcount > m_nHltvReplayStopAt + 32 )
|
|
{
|
|
// time to ask server to stop replay, again
|
|
RequestCancelHltvReplay( true );
|
|
// .. and ask the server again in 32 ticks, if this packet is lost, too
|
|
m_nHltvReplayStopAt = gpGlobals->tickcount;
|
|
}
|
|
}
|
|
|
|
if ( flPrevReplaySoundFadeAmount != m_flReplaySoundFadeAmount )
|
|
{
|
|
SetReplaySoundMixLayer( m_flReplaySoundFadeAmount );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void CHltvReplaySystem::PurgeRagdollBoneCache()
|
|
{
|
|
if ( int nCount = m_mapCachedRagdollBones.Count() )
|
|
{
|
|
// destroy all unclaimed bones
|
|
FOR_EACH_HASHTABLE( m_mapCachedRagdollBones, it )
|
|
{
|
|
FreeCachedRagdollBones( m_mapCachedRagdollBones[ it ] );
|
|
}
|
|
m_mapCachedRagdollBones.Purge();
|
|
DevMsg( "%d ragdoll states purged\n", nCount );
|
|
}
|
|
}
|
|
|
|
|
|
bool CHltvReplaySystem::IsFadeoutActive()
|
|
{
|
|
return m_flFadeoutEndTime > 0.0f && gpGlobals->curtime > m_flFadeoutEndTime - m_flFadeoutDuration; // fadeout is requested and has already started
|
|
}
|
|
|
|
bool CHltvReplaySystem::IsFadeoutFinished()
|
|
{
|
|
return gpGlobals->curtime >= m_flFadeoutEndTime;
|
|
}
|
|
|
|
|
|
void CHltvReplaySystem::OnLevelShutdown()
|
|
{
|
|
m_bWaitingForHltvReplayTick = false;
|
|
StopFades();
|
|
StopHltvReplay();
|
|
Update();
|
|
}
|
|
|
|
void CHltvReplaySystem::StopFadeout()
|
|
{
|
|
if ( m_flFadeoutEndTime > 0 )
|
|
DevMsg( "%.2f Stopping fadeout\n", gpGlobals->curtime );
|
|
|
|
SetReplaySoundMixLayer( 0.0f );
|
|
m_flFadeoutEndTime = -1000;
|
|
}
|
|
|
|
//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" );
|
|
|
|
void CHltvReplaySystem::SetReplaySoundMixLayer( float flFade )
|
|
{
|
|
static int s_nReplayBlinkLayerIndex = engine->GetMixLayerIndex( "ReplayBlinkLayer" );
|
|
engine->SetMixLayerLevel( s_nReplayBlinkLayerIndex, flFade );
|
|
//enginesound->SetReplaySoundFade( m_flReplaySoundFadeAmount );
|
|
}
|
|
|
|
|
|
void CHltvReplaySystem::StartFadeout( float flTimeEnd, float flDuration )
|
|
{
|
|
DevMsg( "%.2f Starting fadeout %.2f->%.2f\n", gpGlobals->curtime, flTimeEnd, flDuration );
|
|
m_flFadeoutEndTime = flTimeEnd;
|
|
m_flFadeoutDuration = flDuration;
|
|
}
|
|
|
|
void CHltvReplaySystem::StartFadein( float flStartRealTime )
|
|
{
|
|
m_flFadeinStartRealTime = flStartRealTime;
|
|
m_flFadeinDuration = spec_replay_fadein.GetFloat();
|
|
}
|
|
|
|
void CHltvReplaySystem::StopFades()
|
|
{
|
|
StopFadeout();
|
|
m_flFadeinStartRealTime = -1000;
|
|
}
|
|
|
|
void CHltvReplaySystem::OnLevelInit()
|
|
{
|
|
m_DelayedReplay.Reset();
|
|
m_bWaitingForHltvReplayTick = false;
|
|
m_flHltvLastRequestRealTime = 0;
|
|
m_flCurrentPlaybackTime = 0;
|
|
StopFades();
|
|
Assert( !m_nHltvReplayDelay ); // we MAY be in demo playback
|
|
if ( !m_bListeningForGameEvents )
|
|
{
|
|
ListenForGameEvent( "round_start" );
|
|
ListenForGameEvent( "bot_takeover" );
|
|
ListenForGameEvent( "player_death" );
|
|
m_bListeningForGameEvents = true; // now we're listening
|
|
}
|
|
}
|
|
|
|
void CHltvReplaySystem::FireGameEvent( IGameEvent *event )
|
|
{
|
|
const char *pEventName = event->GetName();
|
|
if ( !V_strcmp( pEventName, "round_start" ) )
|
|
{
|
|
m_nExperimentalEvents = 0;
|
|
CancelDelayedHltvReplay();
|
|
}
|
|
else if ( m_DelayedReplay.IsValid() && !V_strcmp( pEventName, "bot_takeover" ) )
|
|
{
|
|
C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
|
|
int nEventUserID = event->GetInt( "userid", -1 );
|
|
|
|
if ( pLocalPlayer->GetObserverMode() != OBS_MODE_NONE && pLocalPlayer->GetUserID() == nEventUserID )
|
|
{
|
|
int nBotId = event->GetInt( "botid" );
|
|
C_BasePlayer *pBot = UTIL_PlayerByUserId( nBotId );
|
|
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 );
|
|
CancelDelayedHltvReplay();
|
|
}
|
|
}
|
|
else if ( !V_strcmp( pEventName, "player_death" ) )
|
|
{
|
|
OnPlayerDeath( event );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
float CHltvReplaySystem::GetHltvReplayDistortAmount()const
|
|
{
|
|
return m_nHltvReplayDelay ? 1.0f : 0.0f; // no distortion in highlights or lowlights, just the fade in/out
|
|
}
|
|
|
|
void CHltvReplaySystem::OnDemoPlayback( bool bPlaying )
|
|
{
|
|
m_bDemoPlayback = bPlaying;
|
|
if ( !bPlaying )
|
|
{
|
|
if ( !m_nHltvReplayDelay )
|
|
{
|
|
StopFades();
|
|
}
|
|
m_nDemoPlaybackXuid = 0;
|
|
m_bDemoPlaybackLowLights = false;
|
|
}
|
|
// by default, we don't know if we need fade in demo playback
|
|
m_nDemoPlaybackStartAt = 0;
|
|
m_nDemoPlaybackStopAt = 0;
|
|
m_nCurrentPlaybackTick = 0;
|
|
m_flCurrentPlaybackTime = gpGlobals->curtime;
|
|
EmitTimeJump();
|
|
}
|
|
|
|
|
|
void CHltvReplaySystem::SetDemoPlaybackHighlightXuid( uint64 xuid, bool bLowlights )
|
|
{
|
|
m_nDemoPlaybackXuid = xuid;
|
|
m_bDemoPlaybackLowLights = bLowlights;
|
|
if ( !m_nSteamSelfAccountId && steamapicontext )
|
|
{
|
|
if ( ISteamUser* pSteamUser = steamapicontext->SteamUser() )
|
|
{
|
|
m_nSteamSelfAccountId = pSteamUser->GetSteamID().GetAccountID();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool CHltvReplaySystem::IsDemoPlaybackXuidOther()const
|
|
{
|
|
return m_nSteamSelfAccountId != uint32( m_nDemoPlaybackXuid );
|
|
}
|
|
|
|
|
|
C_BasePlayer *CHltvReplaySystem::GetDemoPlaybackPlayer()
|
|
{
|
|
if ( C_BasePlayer *pPlayer = m_DemoPlaybackPlayer.Get() )
|
|
{
|
|
CSteamID steamId;
|
|
if ( pPlayer->GetSteamID(&steamId) )
|
|
{
|
|
if ( steamId.GetAccountID() == uint32( m_nDemoPlaybackXuid ) )
|
|
return pPlayer;
|
|
}
|
|
}
|
|
m_DemoPlaybackPlayer.Term();
|
|
|
|
for ( int i = 1; i <= MAX_PLAYERS; ++i )
|
|
{
|
|
if ( C_BasePlayer *pPlayer = UTIL_PlayerByIndex( i ) )
|
|
{
|
|
CSteamID steamId;
|
|
if ( pPlayer->GetSteamID( &steamId ) )
|
|
{
|
|
if ( steamId.GetAccountID() == uint32( m_nDemoPlaybackXuid ) )
|
|
{
|
|
m_DemoPlaybackPlayer.Set( pPlayer );
|
|
return pPlayer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void CHltvReplaySystem::SetDemoPlaybackFadeBrackets( int nCurrentPlaybackTick, int nStartAt, int nStopAt )
|
|
{
|
|
Assert( m_bDemoPlayback );
|
|
if ( nCurrentPlaybackTick + 320 < nStartAt && nStartAt != m_nDemoPlaybackStartAt )
|
|
{
|
|
// 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
|
|
enginesound->StopAllSounds( true );
|
|
}
|
|
|
|
m_nDemoPlaybackStartAt = nStartAt;
|
|
m_nDemoPlaybackStopAt = nStopAt;
|
|
m_nCurrentPlaybackTick = nCurrentPlaybackTick;
|
|
m_flCurrentPlaybackTime = gpGlobals->curtime;
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CHltvReplaySystem::IsHltvReplayFeatureEnabled()
|
|
{
|
|
#if HLTV_REPLAY_ENABLED
|
|
if ( g_bEngineIsHLTV )
|
|
return false; // no replay during replay or GOTV
|
|
|
|
static ConVarRef spec_replay_enable( "spec_replay_enable" );
|
|
return spec_replay_enable.GetBool();
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool CHltvReplaySystem::IsHltvReplayButtonEnabled()
|
|
{
|
|
if ( !IsHltvReplayFeatureEnabled() )
|
|
return false;
|
|
|
|
static ConVarRef spec_replay_rate_limit( "spec_replay_rate_limit" );
|
|
|
|
if ( m_LocalPlayer.m_bLastSeenAlive ) // must be dead to allow replay
|
|
return false;
|
|
|
|
// Note: player death time may not be networked yet, so current pPlayer->GetDeathTime() may not be up to date
|
|
|
|
if ( m_bHltvReplayButtonTimedOut )
|
|
return false;
|
|
|
|
if ( gpGlobals->curtime > m_DelayedReplay.flEventTime + GetReplayMessageTime() )
|
|
return false;// did we die too long ago ? Then we can't replay
|
|
|
|
if ( gpGlobals->realtime < m_flHltvLastRequestRealTime + spec_replay_rate_limit.GetFloat() ) // rate-limit mashing the Replay button
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
void CHltvReplaySystem::CancelDelayedHltvReplay()
|
|
{
|
|
StopFadeout(); // fadeins are fine, leave them be
|
|
// abort the pending death replay, stop all fades because the user took over a bot
|
|
if ( m_DelayedReplay.IsValid() )
|
|
{
|
|
m_DelayedReplay.Stop();
|
|
CS_FreezePanel_OnHltvReplayButtonStateChanged();
|
|
}
|
|
}
|
|
|
|
|
|
void CHltvReplaySystem::RequestHltvReplayDeath()
|
|
{
|
|
ReplayParams_t rp;
|
|
rp.nRequest = REPLAY_EVENT_DEATH;
|
|
RequestHltvReplay( rp );
|
|
}
|
|
|
|
void CHltvReplaySystem::RequestHltvReplay( const ReplayParams_t &replay )
|
|
{
|
|
CCLCMsg_HltvReplay_t msgReplay;
|
|
msgReplay.set_request( replay.nRequest );
|
|
float flSlowdown = spec_replay_rate_slowdown.GetFloat();
|
|
if ( flSlowdown != 1.0f )
|
|
{
|
|
float flSlowdownLength = spec_replay_rate_slowdown_length.GetFloat();
|
|
if ( flSlowdownLength > 0.01f && flSlowdownLength < 5.0f )
|
|
{
|
|
msgReplay.set_slowdown_length( flSlowdownLength );
|
|
msgReplay.set_slowdown_rate( flSlowdown );
|
|
}
|
|
}
|
|
if ( replay.nRequest != REPLAY_EVENT_DEATH )
|
|
{
|
|
msgReplay.set_primary_target_ent_index( replay.nPrimaryTargetEntIndex );
|
|
msgReplay.set_event_time( replay.flEventTime );
|
|
}
|
|
|
|
// if we want to always see from victim's perspective, always set the primary target to victim
|
|
if ( spec_replay_victim_pov.GetBool() && replay.nPrimaryVictimEntIndex > 0 )
|
|
{
|
|
msgReplay.set_primary_target_ent_index( replay.nPrimaryVictimEntIndex );
|
|
}
|
|
|
|
engine->SendMessageToServer( &msgReplay );
|
|
|
|
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
|
|
StopFadeout();
|
|
|
|
m_flHltvLastRequestRealTime = gpGlobals->realtime;
|
|
m_nExperimentalEvents |= EE_REPLAY_REQUESTED;
|
|
}
|
|
|
|
void CHltvReplaySystem::RequestCancelHltvReplay( bool bSuppressFadeout )
|
|
{
|
|
if ( GetHltvReplayDelay() )
|
|
{
|
|
CCLCMsg_HltvReplay_t msgReplay;
|
|
msgReplay.set_request( 0 ); // cancel replay
|
|
engine->SendMessageToServer( &msgReplay );
|
|
if ( bSuppressFadeout )
|
|
{
|
|
StopFadeout();
|
|
}
|
|
else
|
|
{
|
|
if ( !IsFadeoutActive() )
|
|
{// if there's no other fadeout happening now, start fading out for cancel; pretend we're already .1 seconde into fadeout
|
|
StartFadeout( gpGlobals->curtime + m_flFadeoutDuration, 0.3f );// approximately how much it'll take for server to come back with the cancellation
|
|
}
|
|
}
|
|
m_nExperimentalEvents |= EE_REPLAY_CANCELLED;
|
|
DevMsg( "%.2f Replay Cancel requested (%d,%d..%d)\n", gpGlobals->curtime, gpGlobals->tickcount, m_nHltvReplayStartAt, m_nHltvReplayStopAt );
|
|
}
|
|
else
|
|
{
|
|
DevMsg( "%.2f Replay Cancel request failed, not in replay now\n", gpGlobals->curtime );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool IsPlayerTeamDead( int nPlayerEntIndex )
|
|
{
|
|
if ( C_BasePlayer* pPrincipalPlayer = UTIL_PlayerByIndex( nPlayerEntIndex ) )
|
|
{
|
|
int nTeam = pPrincipalPlayer->GetTeamNumber();
|
|
for ( int i = 1; i <= MAX_PLAYERS; ++i )
|
|
{
|
|
if ( i != nPlayerEntIndex )
|
|
{
|
|
if ( CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ) )
|
|
{
|
|
Assert( pPlayer != pPrincipalPlayer );
|
|
if ( pPlayer->GetTeamNumber() == nTeam )
|
|
{
|
|
if ( pPlayer->IsAlive() )
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
return false; // something's wrong - can't find a team of a non-existing player
|
|
}
|
|
|
|
|
|
void CHltvReplaySystem::OnPlayerDeath( IGameEvent *event )
|
|
{
|
|
if ( m_DelayedReplay.IsValid() || GetHltvReplayDelay() )
|
|
{
|
|
// we're already replaying or preparing to replay. Let's not react to new player deaths
|
|
return;
|
|
}
|
|
|
|
int iPlayerIndexVictim = engine->GetPlayerForUserID( event->GetInt( "userid" ) );
|
|
int iPlayerIndexKiller = engine->GetPlayerForUserID( event->GetInt( "attacker" ) );
|
|
C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
|
|
if ( !pLocalPlayer )
|
|
{
|
|
DevMsg( "OnPlayerDeath: Cannot replay without local player\n" );
|
|
return;
|
|
}
|
|
bool isLocalVictim = pLocalPlayer && iPlayerIndexVictim == pLocalPlayer->entindex(), isReplayAvailable = iPlayerIndexKiller && iPlayerIndexKiller != iPlayerIndexVictim && !event->GetBool( "noreplay", false );
|
|
|
|
UpdateHltvReplayButtonTimeOutState();
|
|
|
|
if ( !isReplayAvailable || m_bHltvReplayButtonTimedOut )
|
|
return; // no need to react to anything if replay isn't available for this death
|
|
|
|
if ( isLocalVictim )
|
|
{
|
|
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
|
|
if ( iPlayerIndexVictim != iPlayerIndexKiller ) // we don't replay suicides
|
|
{
|
|
m_DelayedReplay.bFinalKillOfRound = false;
|
|
if ( PrepareHltvReplayCountdown() )
|
|
{
|
|
m_DelayedReplay.nRequest = REPLAY_EVENT_DEATH;
|
|
}
|
|
}
|
|
}
|
|
else if (
|
|
iPlayerIndexKiller <= MAX_PLAYERS // the killer must be an actual player (not e.g. a bomb) for replay to make sense
|
|
&& !m_LocalPlayer.m_bLastSeenAlive && spec_replay_autostart.GetBool()
|
|
&& spec_replay_others_experimental.GetBool() && IsPlayerTeamDead( iPlayerIndexVictim )
|
|
)
|
|
{
|
|
int nLocalPlayerTeam = pLocalPlayer->GetTeamNumber();
|
|
if ( nLocalPlayerTeam == TEAM_CT || nLocalPlayerTeam == TEAM_TERRORIST )
|
|
{
|
|
if ( CBaseEntity *pTarget = pLocalPlayer->GetObserverTarget() )
|
|
{
|
|
int nObserverTarget = pTarget->entindex();
|
|
if ( nObserverTarget != iPlayerIndexKiller && nObserverTarget != iPlayerIndexVictim ) //
|
|
{
|
|
m_DelayedReplay.bFinalKillOfRound = true;
|
|
// EXPERIMENT: showing death of other players to people
|
|
if ( PrepareHltvReplayCountdown() )
|
|
{
|
|
// we're dead anyway... request a replay, it may be interesting
|
|
m_DelayedReplay.nRequest = REPLAY_EVENT_GENERIC;
|
|
extern void CS_FreezePanel_ResetDamageText( int iPlayerIndexKiller, int iPlayerIndexVictim );
|
|
CS_FreezePanel_ResetDamageText( iPlayerIndexKiller, iPlayerIndexVictim );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( isLocalVictim || m_DelayedReplay.nRequest >= 0 )
|
|
{// Some interesting event happened, and we are not replaying anything else. Store some stats that may or may not be useful for later replay
|
|
m_DelayedReplay.nPrimaryTargetEntIndex = iPlayerIndexKiller;
|
|
m_DelayedReplay.flEventTime = gpGlobals->curtime;
|
|
m_DelayedReplay.flTime = gpGlobals->curtime + spec_replay_autostart_delay.GetFloat();
|
|
m_DelayedReplay.nPrimaryVictimEntIndex = iPlayerIndexVictim;
|
|
m_DelayedReplay.bPrimaryVictimIsLocalPlayer = isLocalVictim;
|
|
}
|
|
}
|
|
|
|
void CHltvReplaySystem::OnLocalPlayerRespawning()
|
|
{
|
|
}
|
|
|
|
bool CHltvReplaySystem::PrepareHltvReplayCountdown()
|
|
{
|
|
bool bCountdownStarted = false;
|
|
if ( !GetHltvReplayDelay() )
|
|
{
|
|
m_nExperimentalEvents |= EE_REPLAY_OFFERED;
|
|
|
|
// if we have to show freezeframe before deathcam, this is the place to request it
|
|
if ( IsHltvReplayFeatureEnabled() && spec_replay_autostart.GetBool() )
|
|
{
|
|
bCountdownStarted = true;
|
|
m_nExperimentalEvents |= EE_REPLAY_AUTOMATIC;
|
|
|
|
float flDelay = spec_replay_autostart_delay.GetFloat();
|
|
m_DelayedReplay.flTime = gpGlobals->curtime + flDelay;
|
|
if ( !IsFadeoutActive() )
|
|
{
|
|
// use fadeout
|
|
StartFadeout( gpGlobals->curtime + flDelay, Min( flDelay, spec_replay_fadeout.GetFloat() ) );
|
|
}
|
|
// DevMsg( "%.2f Replay: starting to fade out\n", gpGlobals->curtime ); // replayfade
|
|
}
|
|
CS_FreezePanel_OnHltvReplayButtonStateChanged();
|
|
}
|
|
return bCountdownStarted;
|
|
}
|
|
|
|
|
|
|
|
|
|
ConVar r_replay_post_effect( "r_replay_post_effect", "-1", FCVAR_CHEAT );
|
|
|
|
bool CHltvReplaySystem::WantsReplayEffect() const
|
|
{
|
|
if ( r_replay_post_effect.GetInt() == -1 )
|
|
{
|
|
return ( g_HltvReplaySystem.GetHltvReplayDelay() != 0 );
|
|
}
|
|
else
|
|
return r_replay_post_effect.GetBool();
|
|
}
|
|
|
|
|
|
void CHltvReplaySystem::CacheRagdollBones()
|
|
{
|
|
PurgeRagdollBoneCache();
|
|
if ( !spec_replay_cache_ragdolls.GetBool() )
|
|
return;
|
|
C_BaseEntityIterator iterator;
|
|
C_BaseEntity *pEnt;
|
|
int nRagdollsCached = 0;
|
|
while ( ( pEnt = iterator.Next() ) != NULL )
|
|
{
|
|
if ( C_CSRagdoll * pRagdoll = dynamic_cast< C_CSRagdoll * >( pEnt ) )
|
|
{
|
|
if ( int nEntIndex = pRagdoll->entindex() )
|
|
{
|
|
if ( pRagdoll->m_pRagdoll )
|
|
{
|
|
nRagdollsCached++;
|
|
int nBones = pRagdoll->GetModelPtr()->numbones();
|
|
int nBodyParts = pRagdoll->m_pRagdoll->RagdollBoneCount();
|
|
CachedRagdollBones_t *pCachedRagdoll = ( CachedRagdollBones_t * )MemAlloc_AllocAligned( sizeof( CachedRagdollBones_t ) + sizeof( matrix3x4a_t ) * ( nBones + nBodyParts + 1 ), 16 );
|
|
pCachedRagdoll->bAllAsleep = true;
|
|
pCachedRagdoll->nBones = nBones;
|
|
pCachedRagdoll->nBodyParts = nBodyParts;
|
|
matrix3x4a_t *pBones = pCachedRagdoll->GetBones();
|
|
pBones[ nBones ].SetColumn( Vector( 11, 22, 33 ), X_AXIS );
|
|
pRagdoll->SetupBones( pBones, nBones, BONE_USED_BY_ANYTHING, gpGlobals->curtime );
|
|
matrix3x4a_t *pBodyParts = pCachedRagdoll->GetBodyParts();
|
|
for ( int i = 0; i < nBodyParts; i++ )
|
|
{
|
|
if ( IPhysicsObject* pObj = pRagdoll->m_pRagdoll->RagdollPhysicsObject( i ) )
|
|
{
|
|
pObj->GetPositionMatrix( &pBodyParts[ i ] );
|
|
if ( !pObj->IsAsleep() )
|
|
pCachedRagdoll->bAllAsleep = false;
|
|
}
|
|
else
|
|
{
|
|
pBodyParts[ i ].SetToIdentity();
|
|
}
|
|
}
|
|
|
|
Assert( pBones[ nBones ].GetColumn( X_AXIS ) == Vector( 11, 22, 33 ) );
|
|
Assert( m_mapCachedRagdollBones.Find( nEntIndex ) == m_mapCachedRagdollBones.InvalidHandle() );
|
|
m_mapCachedRagdollBones.Insert( nEntIndex, pCachedRagdoll );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Warning( "A ragdoll without entindex found @%p\n", pRagdoll );
|
|
}
|
|
}
|
|
}
|
|
if ( nRagdollsCached )
|
|
DevMsg( "%d ragdolls cached\n", nRagdollsCached );
|
|
}
|
|
|
|
|
|
CachedRagdollBones_t *CHltvReplaySystem::GetCachedRagdollBones( int nEntIndex, bool bTake )
|
|
{
|
|
if ( gpGlobals->tickcount > m_nHltvReplayBeginTick + 5 )
|
|
return NULL; // this cache isn't useful anymore, we need to create ragdoll dynamically - otherwise it'll just teleport to the final position instantaneously
|
|
|
|
UtlHashHandle_t hFind = m_mapCachedRagdollBones.Find( nEntIndex );
|
|
CachedRagdollBones_t *pTakenBones = NULL;
|
|
|
|
if ( hFind != m_mapCachedRagdollBones.InvalidHandle() )
|
|
{
|
|
pTakenBones = m_mapCachedRagdollBones[ hFind ];
|
|
if ( bTake )
|
|
m_mapCachedRagdollBones.RemoveByHandle( hFind );
|
|
}
|
|
|
|
return pTakenBones;
|
|
}
|
|
|
|
|
|
void CHltvReplaySystem::FreeCachedRagdollBones( CachedRagdollBones_t*pBones )
|
|
{
|
|
MemAlloc_FreeAligned( pBones );
|
|
}
|
|
|
|
|
|
CHltvReplaySystem::CLocalPlayerProps::CLocalPlayerProps()
|
|
{
|
|
m_bLastSeenAlive = false;
|
|
m_nLastTickUpdated = gpGlobals->tickcount;
|
|
}
|
|
|
|
bool CHltvReplaySystem::CLocalPlayerProps::Update()
|
|
{
|
|
if ( m_nLastTickUpdated != gpGlobals->tickcount )
|
|
{
|
|
m_nLastTickUpdated = gpGlobals->tickcount;
|
|
bool bLastLastSeenAlive = m_bLastSeenAlive;
|
|
if ( C_BasePlayer *pBasePlayer = C_BasePlayer::GetLocalPlayer() )
|
|
{
|
|
m_bLastSeenAlive = pBasePlayer->IsAlive();
|
|
}
|
|
else
|
|
{
|
|
m_bLastSeenAlive = false;
|
|
}
|
|
|
|
if ( bLastLastSeenAlive != m_bLastSeenAlive )
|
|
return true; // something changed
|
|
}
|
|
return false; // nothing changed
|
|
}
|
|
|
|
|
|
float CHltvReplaySystem::GetReplayMessageTime()
|
|
{
|
|
static ConVarRef spec_replay_message_time( "spec_replay_message_time" );
|
|
float flReplayMessageTime = spec_replay_message_time.GetFloat();
|
|
if ( CCSGameRules* pRules = CSGameRules() )
|
|
{
|
|
if ( pRules->m_iRoundWinStatus != WINNER_NONE )
|
|
{// the message time is cut short by the end of round
|
|
flReplayMessageTime = spec_replay_autostart_delay.GetFloat();
|
|
}
|
|
}
|
|
return flReplayMessageTime;
|
|
}
|
|
|
|
|
|
bool CHltvReplaySystem::UpdateHltvReplayButtonTimeOutState()
|
|
{
|
|
bool bTimedOut = false;
|
|
|
|
if ( CCSGameRules* pRules = CSGameRules() )
|
|
{
|
|
if ( pRules->IsWarmupPeriod() && !pRules->IsWarmupPeriodPaused() )
|
|
{
|
|
static ConVarRef spec_replay_leadup_time( "spec_replay_leadup_time" );
|
|
static ConVarRef spec_replay_winddown_time( "spec_replay_winddown_time" );
|
|
|
|
float flWarmupTimeLeft = pRules->GetWarmupPeriodEndTime() - gpGlobals->curtime;
|
|
float flReplayRoundtripTime = spec_replay_autostart_delay.GetFloat() + spec_replay_leadup_time.GetFloat() + spec_replay_winddown_time.GetFloat();
|
|
if ( flWarmupTimeLeft <= flReplayRoundtripTime )
|
|
{
|
|
bTimedOut = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bStateUpdated = ( m_bHltvReplayButtonTimedOut != bTimedOut );
|
|
m_bHltvReplayButtonTimedOut = bTimedOut;
|
|
return bStateUpdated;
|
|
}
|