//========= Copyright Valve Corporation, All rights reserved. ============// // //=======================================================================================// #include "replay/replay.h" #include "replay/iclientreplaycontext.h" #include "replay/ireplaymanager.h" #include "replay/replayutils.h" #include "replay/screenshot.h" #include "replay/shared_defs.h" #include "replay/ireplayscreenshotmanager.h" #include "replay/ireplayperformancemanager.h" #include "replay/performance.h" #include "KeyValues.h" #include "filesystem.h" #include "vgui/ILocalize.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" //---------------------------------------------------------------------------------------- extern IClientReplayContext *g_pClientReplayContext; extern vgui::ILocalize *g_pVGuiLocalize; //---------------------------------------------------------------------------------------- CReplay::CReplay() : m_pDownloadEventHandler( NULL ), m_pUserData( NULL ), m_bComplete( false ), m_bRequestedByUser( false ), m_bSaved( false ), m_bRendered( false ), m_bDirty( false ), m_bSavedDuringThisSession( true ), m_flLength( 0 ), m_nPlayerSlot( -1 ), m_nSpawnTick( -1 ), m_nDeathTick( -1 ), m_iMaxSessionBlockRequired( 0 ), m_nStatus( REPLAYSTATUS_INVALID ), m_hSession( REPLAY_HANDLE_INVALID ), m_pFileURL( NULL ), m_nPostDeathRecordTime( 0 ), m_flStartTime( 0.0f ), m_flNextUpdateTime( 0.0f ) { m_wszTitle[0] = L'\0'; m_szMapName[0] = 0; } bool CReplay::IsDownloaded() const { return m_nStatus == REPLAYSTATUS_READYTOCONVERT; } const CReplayPerformance *CReplay::GetPerformance( int i ) const { return const_cast< CReplay * >( this )->GetPerformance( i ); } CReplayPerformance *CReplay::GetPerformance( int i ) { if ( i < 0 || i >= m_vecPerformances.Count() ) return NULL; return m_vecPerformances[ i ]; } bool CReplay::FindPerformance( CReplayPerformance *pPerformance, int &iResult ) { const int it = m_vecPerformances.Find( pPerformance ); if ( it == m_vecPerformances.InvalidIndex() ) { iResult = -1; return false; } iResult = it; return true; } CReplayPerformance *CReplay::GetPerformanceWithTitle( const wchar_t *pTitle ) { FOR_EACH_VEC( m_vecPerformances, i ) { CReplayPerformance *pCurPerformance = m_vecPerformances[ i ]; if ( !V_wcscmp( pTitle, pCurPerformance->m_wszTitle ) ) { return pCurPerformance; } } return NULL; } CReplayPerformance *CReplay::AddNewPerformance( bool bGenTitle/*=true*/, bool bGenFilename/*=true*/ ) { // Create a performance IReplayPerformanceManager *pPerformanceManager = g_pClientReplayContext->GetPerformanceManager(); CReplayPerformance *pPerformance = pPerformanceManager->CreatePerformance( this ); if ( bGenTitle ) { // Give the performance a name pPerformance->AutoNameIfHasNoTitle( m_szMapName ); } if ( bGenFilename ) { // Generate a filename for the new performance pPerformance->SetFilename( pPerformanceManager->GeneratePerformanceFilename( this ) ); } // Cache m_vecPerformances.AddToTail( pPerformance ); return pPerformance; } void CReplay::AddPerformance( KeyValues *pIn ) { // Create a performance IReplayPerformanceManager *pPerformanceManager = g_pClientReplayContext->GetPerformanceManager(); CReplayPerformance *pPerformance = pPerformanceManager->CreatePerformance( this ); // Read pPerformance->Read( pIn ); // Cache m_vecPerformances.AddToTail( pPerformance ); } void CReplay::AddPerformance( CReplayPerformance *pPerformance ) { Assert( pPerformance ); m_vecPerformances.AddToTail( pPerformance ); } const CReplayTime &CReplay::GetItemDate() const { return m_RecordTime; } bool CReplay::IsItemRendered() const { return m_bRendered; } CReplay *CReplay::GetItemReplay() { return this; } ReplayHandle_t CReplay::GetItemReplayHandle() const { return GetHandle(); } QueryableReplayItemHandle_t CReplay::GetItemHandle() const { return GetHandle(); } const wchar_t *CReplay::GetItemTitle() const { return m_wszTitle; } void CReplay::SetItemTitle( const wchar_t *pTitle ) { V_wcsncpy( m_wszTitle, pTitle, sizeof( m_wszTitle ) ); } float CReplay::GetItemLength() const { return m_flLength; } void *CReplay::GetUserData() { return m_pUserData; } void CReplay::SetUserData( void* pUserData ) { m_pUserData = pUserData; } bool CReplay::IsItemAMovie() const { return false; } void CReplay::AddScreenshot( int nWidth, int nHeight, const char *pBaseFilename ) { m_vecScreenshots.AddToTail( new CReplayScreenshot( nWidth, nHeight, pBaseFilename ) ); } void CReplay::AutoNameTitleIfEmpty() { // Autoname it if ( !m_wszTitle[0] ) { Replay_GetAutoName( m_wszTitle, sizeof( m_wszTitle ), m_szMapName ); } } const char *CReplay::GetSubKeyTitle() const { return Replay_va( "replay_%i", GetHandle() ); } const char *CReplay::GetPath() const { return Replay_va( "%s%s%c", g_pClientReplayContext->GetBaseDir(), SUBDIR_REPLAYS, CORRECT_PATH_SEPARATOR ); } void CReplay::OnDelete() { BaseClass::OnDelete(); // Delete reconstructed replay if one exists if ( HasReconstructedReplay() ) { g_pFullFileSystem->RemoveFile( m_strReconstructedFilename.Get() ); } // Delete screenshots g_pClientReplayContext->GetScreenshotManager()->DeleteScreenshotsForReplay( this ); // TODO: Delete performance(s) } bool CReplay::Read( KeyValues *pIn ) { if ( !BaseClass::Read( pIn ) ) return false; m_hSession = (ReplayHandle_t)pIn->GetInt( "session", REPLAY_HANDLE_INVALID ); V_strcpy_safe( m_szMapName, pIn->GetString( "map", "" ) ); m_nSpawnTick = pIn->GetInt( "spawn_tick", -1 ); m_nDeathTick = pIn->GetInt( "death_tick", -1 ); m_nStatus = static_cast< CReplay::ReplayStatus_t >( pIn->GetInt( "status", (int)CReplay::REPLAYSTATUS_INVALID ) ); m_bComplete = pIn->GetInt( "complete" ) != 0; m_flLength = pIn->GetFloat( "length" ); m_nPostDeathRecordTime = pIn->GetInt( "postdeathrecordtime" ); m_bRendered = pIn->GetInt( "rendered" ) != 0; m_nPlayerSlot = pIn->GetInt( "player_slot", -1 ); m_iMaxSessionBlockRequired = pIn->GetInt( "max_block", 0 ); Assert( m_iMaxSessionBlockRequired >= 0 ); m_flStartTime = pIn->GetFloat( "start_time", -1.0f ); Assert( m_flStartTime >= 0.0f ); V_wcsncpy( m_wszTitle, pIn->GetWString( "title" ), sizeof( m_wszTitle ) ); // Read reconstructed filename and infer path const char *pReplaysDir = g_pClientReplayContext->GetReplayManager()->GetReplaysDir(); const char *pReconFilename = pIn->GetString( "recon_filename" ); if ( pReconFilename[0] != 0 ) { m_strReconstructedFilename = Replay_va( "%s%s", pReplaysDir, pReconFilename ); } // Read screenshots KeyValues *pScreenshots = pIn->FindKey( "screenshots" ); if ( pScreenshots ) { FOR_EACH_TRUE_SUBKEY( pScreenshots, pScreenshot ) { int nWidth = pScreenshot->GetInt( "width" ); int nHeight = pScreenshot->GetInt( "height" ); const char *pBaseFilename = pScreenshot->GetString( "base_filename" ); AddScreenshot( nWidth, nHeight, pBaseFilename ); } } // Read performances KeyValues *pPerformances = pIn->FindKey( "edits" ); if ( pPerformances ) { FOR_EACH_TRUE_SUBKEY( pPerformances, pPerformance ) { AddPerformance( pPerformance ); } } // Record time KeyValues *pRecordTimeSubKey = pIn->FindKey( "record_time" ); if ( pRecordTimeSubKey ) { m_RecordTime.Read( pRecordTimeSubKey ); } // Mark replay as saved, since it was just loaded from disk m_bSaved = true; return true; } void CReplay::Write( KeyValues *pOut ) { BaseClass::Write( pOut ); pOut->SetString( "map", m_szMapName ); pOut->SetInt( "session", m_hSession ); pOut->SetInt( "spawn_tick", m_nSpawnTick ); pOut->SetInt( "death_tick", m_nDeathTick ); pOut->SetInt( "status", static_cast< int >( m_nStatus ) ); pOut->SetInt( "complete", static_cast< int >( m_bComplete ) ); pOut->SetFloat( "length", m_flLength ); pOut->SetInt( "postdeathrecordtime", m_nPostDeathRecordTime ); pOut->SetInt( "rendered", m_bRendered ); pOut->SetInt( "player_slot", m_nPlayerSlot ); pOut->SetInt( "max_block", m_iMaxSessionBlockRequired ); pOut->SetFloat( "start_time", m_flStartTime ); pOut->SetWString( "title", m_wszTitle ); // Store only filename for reconstructed .dem if ( !m_strReconstructedFilename.IsEmpty() ) { const char *pReconFilename = V_UnqualifiedFileName( m_strReconstructedFilename.Get() ); if ( pReconFilename[0] ) { pOut->SetString( "recon_filename", pReconFilename ); } } // Write screenshots KeyValues *pScreenshots = new KeyValues( "screenshots" ); pOut->AddSubKey( pScreenshots ); for ( int i = 0; i < m_vecScreenshots.Count(); ++i ) { KeyValues *pScreenshotOut = new KeyValues( "screenshot" ); CReplayScreenshot *pScreenshot = m_vecScreenshots[ i ]; pScreenshotOut->SetInt( "width", pScreenshot->m_nWidth ); pScreenshotOut->SetInt( "height", pScreenshot->m_nHeight ); pScreenshotOut->SetString( "base_filename", pScreenshot->m_szBaseFilename ); pScreenshots->AddSubKey( pScreenshotOut ); } // Write performances KeyValues *pPerformances = new KeyValues( "edits" ); pOut->AddSubKey( pPerformances ); for ( int i = 0; i < m_vecPerformances.Count(); ++i ) { KeyValues *pPerfOut = new KeyValues( "edit" ); CReplayPerformance *pPerformance = m_vecPerformances[ i ]; pPerformance->Write( pPerfOut ); pPerformances->AddSubKey( pPerfOut ); } KeyValues *pRecordTime = new KeyValues( "record_time" ); pOut->AddSubKey( pRecordTime ); m_RecordTime.Write( pRecordTime ); // Mark as saved m_bSaved = true; } bool CReplay::HasReconstructedReplay() const { return !m_strReconstructedFilename.IsEmpty() && g_pFullFileSystem->FileExists( m_strReconstructedFilename.Get() ); } bool CReplay::IsSignificantBlock( int iBlockReconstruction ) const { return iBlockReconstruction <= m_iMaxSessionBlockRequired; } void CReplay::OnComplete() { AutoNameTitleIfEmpty(); } //----------------------------------------------------------------------------------------