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.
421 lines
12 KiB
421 lines
12 KiB
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
//=======================================================================================//
|
|
|
|
#include "cl_replaycontext.h"
|
|
#include "replaysystem.h"
|
|
#include "replay/iclientreplay.h"
|
|
#include "replay/ireplaymovierenderer.h"
|
|
#include "replay/shared_defs.h"
|
|
#include "cl_replaymanager.h"
|
|
#include "replay_dbg.h"
|
|
#include "baserecordingsessionmanager.h"
|
|
#include "baserecordingsessionblockmanager.h"
|
|
#include "cl_replaymoviemanager.h"
|
|
#include "cl_screenshotmanager.h"
|
|
#include "cl_performancemanager.h"
|
|
#include "cl_sessionblockdownloader.h"
|
|
#include "cl_downloader.h"
|
|
#include "cl_recordingsession.h"
|
|
#include "cl_recordingsessionblock.h"
|
|
#include "cl_renderqueue.h"
|
|
#include "replay_reconstructor.h"
|
|
#include "globalvars_base.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
|
|
CClientReplayContext::CClientReplayContext()
|
|
: m_pReplayManager( NULL ),
|
|
m_pScreenshotManager( NULL ),
|
|
m_pMovieRenderer( NULL ),
|
|
m_pMovieManager( NULL ),
|
|
m_pPerformanceManager( NULL ),
|
|
m_pPerformanceController( NULL ),
|
|
m_pTestDownloader( NULL ),
|
|
m_pRenderQueue( NULL ),
|
|
m_bClientSideReplayDisabled( false )
|
|
{
|
|
}
|
|
|
|
CClientReplayContext::~CClientReplayContext()
|
|
{
|
|
delete m_pSessionBlockDownloader;
|
|
delete m_pReplayManager;
|
|
delete m_pScreenshotManager;
|
|
delete m_pMovieManager;
|
|
delete m_pPerformanceManager;
|
|
delete m_pShared;
|
|
delete m_pTestDownloader;
|
|
}
|
|
|
|
bool CClientReplayContext::Init( CreateInterfaceFn fnFactory )
|
|
{
|
|
m_pShared = new CSharedReplayContext( this );
|
|
m_pShared->m_strSubDir = SUBDIR_CLIENT;
|
|
m_pShared->m_pRecordingSessionManager = new CClientRecordingSessionManager( this );
|
|
m_pShared->m_pRecordingSessionBlockManager = new CClientRecordingSessionBlockManager( this );
|
|
m_pShared->m_pErrorSystem = new CErrorSystem( this );
|
|
|
|
if ( !m_pShared->Init( fnFactory ) )
|
|
return false;
|
|
|
|
m_pPerformanceManager = new CReplayPerformanceManager();
|
|
m_pPerformanceManager->Init();
|
|
|
|
m_pReplayManager = new CReplayManager();
|
|
m_pReplayManager->Init( fnFactory );
|
|
|
|
m_pScreenshotManager = new CScreenshotManager();
|
|
m_pScreenshotManager->Init();
|
|
|
|
m_pMovieManager = new CReplayMovieManager();
|
|
m_pMovieManager->Init();
|
|
|
|
m_pRenderQueue = new CRenderQueue();
|
|
if ( !m_pRenderQueue )
|
|
return false;
|
|
|
|
m_pSessionBlockDownloader = new CSessionBlockDownloader();
|
|
if ( !m_pSessionBlockDownloader )
|
|
return false;
|
|
|
|
m_pPerformanceController = new CPerformanceController();
|
|
|
|
// Cleanup any unneeded block data from disk - cleanup is done on the fly, but this will clean up
|
|
// blocks from the olden days, when block data was not cleaned up properly.
|
|
CleanupUnneededBlocks();
|
|
|
|
return true;
|
|
}
|
|
|
|
void CClientReplayContext::Shutdown()
|
|
{
|
|
// NOTE: Must come first, as any existing downloads are aborted and may cause status
|
|
// changes in replays, etc, which will need to be saved in CReplayManager::Shutdown(), etc.
|
|
m_pSessionBlockDownloader->Shutdown();
|
|
|
|
m_pShared->Shutdown();
|
|
m_pReplayManager->Shutdown();
|
|
m_pMovieManager->Shutdown();
|
|
}
|
|
|
|
void CClientReplayContext::DebugThink()
|
|
{
|
|
if ( !replay_debug.GetBool() )
|
|
return;
|
|
|
|
int iLine = 15;
|
|
|
|
// Recording session in progress
|
|
CClientRecordingSession *pRecordingSession = CL_GetRecordingSessionInProgress();
|
|
if ( pRecordingSession )
|
|
{
|
|
g_pEngineClient->Con_NPrintf( iLine++, "SESSION IN PROGRESS:" );
|
|
g_pEngineClient->Con_NPrintf( iLine++, " BLOCKS: %i", pRecordingSession->GetNumBlocks() );
|
|
g_pEngineClient->Con_NPrintf( iLine++, " NAME: %s", pRecordingSession->m_strName.Get() );
|
|
g_pEngineClient->Con_NPrintf( iLine++, " URL: %s", pRecordingSession->m_strBaseDownloadURL.Get() );
|
|
g_pEngineClient->Con_NPrintf( iLine++, " LAST CONSECUTIVE BLOCK DOWNLOADED: %i", pRecordingSession->GetGreatestConsecutiveBlockDownloaded() );
|
|
g_pEngineClient->Con_NPrintf( iLine++, " LAST BLOCK TO DOWNLOAD: %i", pRecordingSession->GetLastBlockToDownload() );
|
|
}
|
|
else
|
|
{
|
|
g_pEngineClient->Con_NPrintf( iLine++, "NO SESSION IN PROGRESS" );
|
|
}
|
|
|
|
iLine++;
|
|
|
|
// Server state
|
|
CClientRecordingSessionManager::ServerRecordingState_t *pServerState = &CL_GetRecordingSessionManager()->m_ServerRecordingState;
|
|
g_pEngineClient->Con_NPrintf( iLine++, "SERVER STATE:" );
|
|
g_pEngineClient->Con_NPrintf( iLine++, " NAME: %s\n", pServerState->m_strSessionName.Get() );
|
|
g_pEngineClient->Con_NPrintf( iLine++, " DUMP INTERVAL: %i\n", pServerState->m_nDumpInterval );
|
|
g_pEngineClient->Con_NPrintf( iLine++, " CURRENT BLOCK: %i\n", pServerState->m_nCurrentBlock );
|
|
}
|
|
|
|
void CClientReplayContext::Think()
|
|
{
|
|
DebugThink();
|
|
|
|
if ( m_pTestDownloader )
|
|
{
|
|
m_pTestDownloader->Think();
|
|
if ( m_pTestDownloader->IsDone() )
|
|
{
|
|
delete m_pTestDownloader;
|
|
m_pTestDownloader = NULL;
|
|
}
|
|
}
|
|
|
|
if ( !g_pReplay->IsReplayEnabled() )
|
|
return;
|
|
|
|
m_pShared->Think();
|
|
}
|
|
|
|
CReplay *CClientReplayContext::GetReplay( ReplayHandle_t hReplay )
|
|
{
|
|
return m_pReplayManager->GetReplay( hReplay );
|
|
}
|
|
|
|
IReplayManager *CClientReplayContext::GetReplayManager()
|
|
{
|
|
return m_pReplayManager;
|
|
}
|
|
|
|
IReplayScreenshotManager *CClientReplayContext::GetScreenshotManager()
|
|
{
|
|
return m_pScreenshotManager;
|
|
}
|
|
|
|
IReplayPerformanceManager *CClientReplayContext::GetPerformanceManager()
|
|
{
|
|
return m_pPerformanceManager;
|
|
}
|
|
|
|
IReplayPerformanceController *CClientReplayContext::GetPerformanceController()
|
|
{
|
|
return m_pPerformanceController;
|
|
}
|
|
|
|
IReplayRenderQueue *CClientReplayContext::GetRenderQueue()
|
|
{
|
|
return m_pRenderQueue;
|
|
}
|
|
|
|
void CClientReplayContext::SetMovieRenderer( IReplayMovieRenderer *pMovieRenderer )
|
|
{
|
|
m_pMovieRenderer = pMovieRenderer;
|
|
}
|
|
|
|
IReplayMovieRenderer *CClientReplayContext::GetMovieRenderer()
|
|
{
|
|
return m_pMovieRenderer;
|
|
}
|
|
|
|
IReplayMovieManager *CClientReplayContext::GetMovieManager()
|
|
{
|
|
return m_pMovieManager;
|
|
}
|
|
|
|
void CClientReplayContext::TestDownloader( const char *pURL )
|
|
{
|
|
// Don't overwrite existing test
|
|
if ( m_pTestDownloader )
|
|
return;
|
|
|
|
// Download the file
|
|
m_pTestDownloader = new CHttpDownloader();
|
|
m_pTestDownloader->BeginDownload( pURL, NULL );
|
|
}
|
|
|
|
void CClientReplayContext::OnSignonStateFull()
|
|
{
|
|
// Notify the demo player that we've reached full signon state
|
|
if ( g_pEngineClient->IsPlayingReplayDemo() )
|
|
{
|
|
g_pReplayDemoPlayer->OnSignonStateFull();
|
|
}
|
|
|
|
// Play a performance? This will play a performance from the beginning, if we're loading
|
|
// one (ie the 'watch' button in the details panel of the replay browser), or will continue
|
|
// playback if the user rewound while watching or editing a performance.
|
|
CL_GetPerformanceController()->OnSignonStateFull();
|
|
|
|
// If we're rendering, display the viewport
|
|
if ( CL_GetMovieManager()->IsRendering() )
|
|
{
|
|
extern IClientReplay *g_pClient;
|
|
g_pClient->OnRenderStart();
|
|
|
|
// Prepare audio system for recording.
|
|
g_pEngineClient->InitSoundRecord();
|
|
|
|
// Init renderer
|
|
IReplayMovie *pMovie = CL_GetMovieManager()->GetPendingMovie();
|
|
if ( !m_pMovieRenderer->SetupRenderer( CL_GetMovieManager()->GetRenderMovieSettings(), pMovie ) )
|
|
{
|
|
Warning( "Render failed!\n" );
|
|
CL_GetMovieManager()->CancelRender();
|
|
}
|
|
}
|
|
|
|
// If we're not rendering and are playing back a replay, initialize the performance editor -
|
|
// won't actually show until the user presses space, if they do at all.
|
|
else if ( g_pEngineClient->IsPlayingReplayDemo() )
|
|
{
|
|
const CReplay *pReplay = g_pReplayDemoPlayer->GetCurrentReplay();
|
|
if ( pReplay )
|
|
{
|
|
g_pClient->InitPerformanceEditor( pReplay->GetHandle() );
|
|
}
|
|
else
|
|
{
|
|
AssertMsg( 0, "Replay should exist here!" );
|
|
Warning( "No current replay in demo player!\n" );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CClientReplayContext::OnClientSideDisconnect()
|
|
{
|
|
if ( !g_pEngine->IsSupportedModAndPlatform() )
|
|
return;
|
|
|
|
// Reset replay_recording or we'll continue to think we're recording
|
|
extern ConVar replay_recording;
|
|
replay_recording.SetValue( 0 );
|
|
|
|
if ( !g_pEngineClient->IsPlayingReplayDemo() )
|
|
{
|
|
// Saves dangling replay, if there is one, clears out everything
|
|
// NOTE: We need to let the replay manager deal before we end the session, otherwise the
|
|
// state of the session will be cleared.
|
|
m_pReplayManager->OnClientSideDisconnect();
|
|
|
|
// Mark the session as no longer recording.
|
|
CClientRecordingSession *pSession = CL_GetRecordingSessionInProgress();
|
|
if ( pSession )
|
|
{
|
|
pSession->OnStopRecording();
|
|
}
|
|
|
|
// Sets recording flag to false in session in progress, clears session in progress,
|
|
// and clears server state
|
|
CL_GetRecordingSessionManager()->OnSessionEnd();
|
|
}
|
|
}
|
|
|
|
void CClientReplayContext::PlayReplay( ReplayHandle_t hReplay, int iPerformance, bool bPlaySound )
|
|
{
|
|
CReplay *pReplay = m_pReplayManager->GetReplay( hReplay );
|
|
if ( !pReplay )
|
|
return;
|
|
|
|
if ( !ReconstructReplayIfNecessary( pReplay ) )
|
|
{
|
|
Replay_MsgBox( iPerformance < 0 ? "#Replay_Err_User_FailedToPlayReplay" : "#Replay_Err_User_FailedToPlayTake" );
|
|
return;
|
|
}
|
|
|
|
// Play a sound?
|
|
if ( bPlaySound )
|
|
{
|
|
g_pClient->PlaySound( iPerformance >= 0 ? "replay\\playperformance.wav" : "replay\\playoriginalreplay.wav" );
|
|
}
|
|
|
|
// Play the replay!
|
|
g_pReplayDemoPlayer->PlayReplay( hReplay, iPerformance );
|
|
}
|
|
|
|
bool CClientReplayContext::ReconstructReplayIfNecessary( CReplay *pReplay )
|
|
{
|
|
// If reconstruction hasn't happened yet, try to reconstruct
|
|
extern ConVar replay_forcereconstruct;
|
|
if ( !pReplay->HasReconstructedReplay() || replay_forcereconstruct.GetBool() )
|
|
{
|
|
if ( !Replay_Reconstruct( pReplay ) )
|
|
{
|
|
CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Reconstruction_Fail" );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CClientReplayContext::OnPlayerSpawn()
|
|
{
|
|
DBG( "OnPlayerSpawn()\n" );
|
|
m_pReplayManager->AttemptToSetupNewReplay();
|
|
}
|
|
|
|
void CClientReplayContext::OnPlayerClassChanged()
|
|
{
|
|
DBG( "OnPlayerClassChanged()\n" );
|
|
m_pReplayManager->CompletePendingReplay();
|
|
}
|
|
|
|
void CClientReplayContext::GetPlaybackTimes( float &flOutTime, float &flOutLength, const CReplay *pReplay, const CReplayPerformance *pPerformance )
|
|
{
|
|
flOutTime = 0.0f;
|
|
flOutLength = 0.0f;
|
|
|
|
// Get server start record tick
|
|
const int nServerRecordStartTick = CL_GetRecordingSessionManager()->GetServerStartTickForSession( pReplay->m_hSession );
|
|
|
|
// Don't let it be -1. Take performance in tick into account.
|
|
int nStartTick = MAX( 0, pReplay->m_nSpawnTick );
|
|
if ( pPerformance && pPerformance->m_nTickIn >= 0 )
|
|
{
|
|
nStartTick = pPerformance->m_nTickIn;
|
|
}
|
|
|
|
// Calculate length
|
|
const int nReplayEndTick = pReplay->m_nSpawnTick + g_pEngine->TimeToTicks( pReplay->m_flLength );
|
|
const int nEndTick = ( pPerformance && pPerformance->m_nTickOut > 0 ) ? pPerformance->m_nTickOut : nReplayEndTick;
|
|
flOutLength = pPerformance ? g_pEngine->TicksToTime( nEndTick - nStartTick ) : pReplay->m_flLength;
|
|
|
|
// Calculate current time
|
|
const int nCurTick = MAX( g_pEngineClient->GetClientGlobalVars()->tickcount - nStartTick - nServerRecordStartTick, 0 );
|
|
flOutTime = MIN( g_pEngine->TicksToTime( nCurTick ), flOutLength );
|
|
}
|
|
|
|
uint64 CClientReplayContext::GetServerSessionId( ReplayHandle_t hReplay )
|
|
{
|
|
CReplay *pReplay = GetReplay( hReplay );
|
|
if ( !pReplay )
|
|
return 0;
|
|
|
|
CClientRecordingSession *pSession = CL_CastSession( CL_GetRecordingSessionManager()->FindSession( pReplay->m_hSession ) );
|
|
if ( !pSession )
|
|
return 0;
|
|
|
|
return pSession->GetServerSessionID();
|
|
}
|
|
|
|
void CClientReplayContext::CleanupUnneededBlocks()
|
|
{
|
|
CL_GetRecordingSessionManager()->CleanupUnneededBlocks();
|
|
}
|
|
|
|
void CClientReplayContext::ReportErrorsToUser( wchar_t *pErrorText )
|
|
{
|
|
// Display a message now
|
|
// Replay_MsgBox( pErrorText );
|
|
|
|
if ( !pErrorText || pErrorText[0] == L'\0' )
|
|
return;
|
|
|
|
const int nErrorLen = wcslen( pErrorText );
|
|
static char s_szError[1024];
|
|
wcstombs( s_szError, pErrorText, MIN( 1024, nErrorLen ) );
|
|
Warning( "Replay error system: %s\n", s_szError );
|
|
}
|
|
|
|
void CClientReplayContext::DisableReplayOnClient( bool bDisable )
|
|
{
|
|
if ( m_bClientSideReplayDisabled == bDisable )
|
|
return;
|
|
|
|
m_bClientSideReplayDisabled = bDisable;
|
|
|
|
// Display a message to the user
|
|
Replay_HudMsg( bDisable ? "#Replay_ClientSideDisabled" : "#Replay_ClientSideEnabled", NULL, true );
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
|
|
CClientRecordingSessionManager *CL_GetRecordingSessionManager()
|
|
{
|
|
return static_cast< CClientRecordingSessionManager * >( g_pClientReplayContextInternal->GetRecordingSessionManager() );
|
|
}
|
|
|
|
CClientRecordingSession *CL_GetRecordingSessionInProgress()
|
|
{
|
|
return CL_CastSession( CL_GetRecordingSessionManager()->GetRecordingSessionInProgress() );
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|