//========= 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() ); } //----------------------------------------------------------------------------------------