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