summaryrefslogtreecommitdiff
path: root/engine/replaydemoplayer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engine/replaydemoplayer.cpp')
-rw-r--r--engine/replaydemoplayer.cpp511
1 files changed, 511 insertions, 0 deletions
diff --git a/engine/replaydemoplayer.cpp b/engine/replaydemoplayer.cpp
new file mode 100644
index 0000000..5a28c81
--- /dev/null
+++ b/engine/replaydemoplayer.cpp
@@ -0,0 +1,511 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#if defined( REPLAY_ENABLED )
+
+#include "replaydemoplayer.h"
+#include "replay/ireplaymoviemanager.h"
+#include "replay/ireplayperformancemanager.h"
+#include "replay/ireplaymovierenderer.h"
+#include "replay/ireplayperformancecontroller.h"
+#include "replay/ireplaymanager.h"
+#include "replay/replay.h"
+#include "replay/replayutils.h"
+#include "replay/shared_defs.h"
+#include "replay/iclientreplay.h"
+#include "replay/performance.h"
+#include "replay_internal.h"
+#include "cmd.h"
+#include "KeyValues.h"
+#include "cdll_engine_int.h"
+#include "host.h"
+#include "fmtstr.h"
+#include "vgui_baseui_interface.h"
+
+#ifndef DEDICATED
+#include "screen.h"
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//----------------------------------------------------------------------------------------
+
+CReplayDemoPlayer s_ReplayDemoPlayer;
+IDemoPlayer *g_pReplayDemoPlayer = &s_ReplayDemoPlayer;
+
+//----------------------------------------------------------------------------------------
+
+ConVar replay_ignorereplayticks( "replay_ignorereplayticks", "0" );
+
+//----------------------------------------------------------------------------------------
+
+EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CReplayDemoPlayer, IReplayDemoPlayer, INTERFACEVERSION_REPLAYDEMOPLAYER, s_ReplayDemoPlayer );
+
+//----------------------------------------------------------------------------------------
+
+CReplayDemoPlayer::CReplayDemoPlayer()
+: m_pMovie( NULL ),
+ m_nCurReplayIndex( 0 ),
+ m_flStartRenderTime( 0.0f ),
+ m_bInStartPlayback( false ),
+ m_bStopCommandEncountered( false ),
+ m_bFullSignonStateReached( false )
+{
+}
+
+void CReplayDemoPlayer::ClearReplayList()
+{
+ m_vecReplaysToPlay.PurgeAndDeleteElements();
+}
+
+void CReplayDemoPlayer::AddReplayToList( ReplayHandle_t hReplay, int iPerformance )
+{
+ // Make sure the replay handle's OK
+ CReplay *pReplay = g_pReplayManager->GetReplay( hReplay );
+ if ( !pReplay )
+ return;
+
+ // Create new info
+ PlaybackInfo_t *pNewPlaybackInfo = new PlaybackInfo_t();
+
+ // Cache handle & performance
+ pNewPlaybackInfo->m_hReplay = hReplay;
+ pNewPlaybackInfo->m_iPerformance = iPerformance;
+
+ // Figure out replay spawn/spawn+length ticks
+ pNewPlaybackInfo->m_nStartTick = pReplay->m_nSpawnTick;
+ pNewPlaybackInfo->m_nEndTick = -1;
+
+ const int nLengthInTicks = TIME_TO_TICKS( pReplay->m_flLength );
+ if ( nLengthInTicks > 0 )
+ {
+ pNewPlaybackInfo->m_nEndTick = pReplay->m_nSpawnTick + nLengthInTicks;
+ }
+
+ // If a performance was specified, override ticks as appropriate
+ if ( iPerformance >= 0 )
+ {
+ // Get the performance from the replay
+ const CReplayPerformance *pPerformance = pReplay->GetPerformance( iPerformance );
+ if ( pPerformance->m_nTickIn >= 0 )
+ {
+ pNewPlaybackInfo->m_nStartTick = pPerformance->m_nTickIn;
+ }
+ if ( pPerformance->m_nTickOut >= 0 )
+ {
+ pNewPlaybackInfo->m_nEndTick = pPerformance->m_nTickOut;
+ }
+ }
+
+ // Cache
+ m_vecReplaysToPlay.AddToTail( pNewPlaybackInfo );
+}
+
+CReplay *CReplayDemoPlayer::GetCurrentReplay()
+{
+ PlaybackInfo_t *pCurrentPlaybackInfo = GetCurrentPlaybackInfo();
+ if ( !pCurrentPlaybackInfo )
+ return NULL;
+
+ return g_pReplayManager->GetReplay( pCurrentPlaybackInfo->m_hReplay );
+}
+
+const CReplay *CReplayDemoPlayer::GetCurrentReplay() const
+{
+ return const_cast< CReplayDemoPlayer * >( this )->GetCurrentReplay();
+}
+
+CReplayPerformance *CReplayDemoPlayer::GetCurrentPerformance()
+{
+ const PlaybackInfo_t *pCurrentPlaybackInfo = GetCurrentPlaybackInfo();
+ if ( !pCurrentPlaybackInfo )
+ return NULL;
+
+ CReplay *pReplay = GetCurrentReplay();
+ if ( !pReplay )
+ return NULL;
+
+ if ( pCurrentPlaybackInfo->m_iPerformance < 0 )
+ return NULL;
+
+ return pReplay->GetPerformance( pCurrentPlaybackInfo->m_iPerformance );
+}
+
+CReplayDemoPlayer::PlaybackInfo_t *CReplayDemoPlayer::GetCurrentPlaybackInfo()
+{
+ if ( m_vecReplaysToPlay.Count() == 0 )
+ return NULL;
+
+ return m_vecReplaysToPlay[ m_nCurReplayIndex ];
+}
+
+const CReplayDemoPlayer::PlaybackInfo_t *CReplayDemoPlayer::GetCurrentPlaybackInfo() const
+{
+ return const_cast< CReplayDemoPlayer * >( this )->GetCurrentPlaybackInfo();
+}
+
+void CReplayDemoPlayer::PauseReplay()
+{
+ PausePlayback( -1.0f );
+}
+
+bool CReplayDemoPlayer::IsReplayPaused()
+{
+ return IsPlaybackPaused();
+}
+
+void CReplayDemoPlayer::ResumeReplay()
+{
+ ResumePlayback();
+}
+
+void CReplayDemoPlayer::OnSignonStateFull()
+{
+ m_bFullSignonStateReached = true;
+}
+
+netpacket_t *CReplayDemoPlayer::ReadPacket( void )
+{
+ if ( !m_bStopCommandEncountered )
+ {
+ return BaseClass::ReadPacket();
+ }
+
+ return NULL;
+}
+
+float CReplayDemoPlayer::GetPlaybackTimeScale()
+{
+ if ( g_pReplayPerformanceController )
+ {
+ return g_pReplayPerformanceController->GetPlaybackTimeScale();
+ }
+
+ return 1.0f;
+}
+
+void CReplayDemoPlayer::OnStopCommand()
+{
+ if ( m_bStopCommandEncountered )
+ return;
+
+ m_bStopCommandEncountered = true;
+
+ if ( !g_pClientReplay->OnEndOfReplayReached() )
+ {
+ BaseClass::OnStopCommand();
+ }
+}
+
+bool CReplayDemoPlayer::StartPlayback( const char *pFilename, bool bAsTimeDemo )
+{
+ if ( !Replay_IsSupportedModAndPlatform() )
+ return false;
+
+ CInStartPlaybackGuard InStartPlaybackGuard( m_bInStartPlayback );
+
+ const PlaybackInfo_t *pPlaybackInfo = GetCurrentPlaybackInfo();
+ if ( !pPlaybackInfo )
+ return false;
+
+ // always display progress bar to ensure load screen background is redraw
+ EngineVGui()->EnabledProgressBarForNextLoad();
+
+ if ( !BaseClass::StartPlayback( pFilename, bAsTimeDemo ) )
+ {
+ DisplayFailedToPlayMsg( pPlaybackInfo->m_iPerformance );
+ return false;
+ }
+
+ CReplay *pReplay = GetCurrentReplay();
+ if ( !pReplay )
+ return false;
+
+ // Set this flag so we can detect whether the replay made it all the way to full signon state,
+ // otherwise we'll display a message StopPlayback() is called.
+ m_bFullSignonStateReached = false;
+
+ // Reset so when we see a dem_stop we will handle it
+ m_bStopCommandEncountered = false;
+
+ // Reset skip to tick now
+ m_nSkipToTick = -1;
+
+ // Reset the rendering cancelled flag now
+ g_pReplayMovieManager->ClearRenderCancelledFlag();
+
+ // Setup playback timeframe
+ if ( pPlaybackInfo->m_nStartTick >= 0 && !replay_ignorereplayticks.GetBool() )
+ {
+ SkipToTick( pPlaybackInfo->m_nStartTick, false, false );
+ }
+ if ( pPlaybackInfo->m_nEndTick >= 0 && !replay_ignorereplayticks.GetBool() )
+ {
+ SetEndTick( pPlaybackInfo->m_nEndTick );
+ }
+
+ if ( g_pReplayMovieManager->IsRendering() )
+ {
+#ifdef USE_WEBM_FOR_REPLAY
+ const char *pExtension = ".webm";
+#else
+ const char *pExtension = ".mov";
+#endif
+
+ // Start recording the movie
+ char szIdealFilename[ MAX_OSPATH ];
+ V_FileBase( pFilename, szIdealFilename, sizeof( szIdealFilename ) );
+ V_strcat( szIdealFilename, va( "_%i", pReplay->m_nSpawnTick ), sizeof( szIdealFilename ) );
+ V_SetExtension( szIdealFilename, pExtension, sizeof( szIdealFilename ) );
+
+ char szRenderPath[ MAX_OSPATH ];
+ V_snprintf( szRenderPath, sizeof( szRenderPath ), "%s%c%s%c%s%c%s",
+ com_gamedir, CORRECT_PATH_SEPARATOR, SUBDIR_REPLAY, CORRECT_PATH_SEPARATOR,
+ SUBDIR_CLIENT, CORRECT_PATH_SEPARATOR, SUBDIR_RENDERED
+ );
+
+ char szActualFilename[ MAX_OSPATH ];
+ Replay_GetFirstAvailableFilename( szActualFilename, sizeof( szActualFilename ), szIdealFilename,
+ pExtension, szRenderPath, 0 );
+
+ // Create an entry in the movie manager & save to disk
+ m_pMovie = g_pReplayMovieManager->CreateAndAddMovie( pReplay->GetHandle() );
+ m_pMovie->SetMovieFilename( szActualFilename );
+ wchar_t wszMovieTitle[MAX_REPLAY_TITLE_LENGTH] = L"";
+ if ( pPlaybackInfo->m_iPerformance < 0 )
+ {
+ g_pReplayMovieManager->GetCachedMovieTitleAndClear( wszMovieTitle, MAX_REPLAY_TITLE_LENGTH );
+ }
+ else
+ {
+ const CReplayPerformance *pPerformance = pReplay->GetPerformance( pPlaybackInfo->m_iPerformance ); AssertMsg( pPerformance, "Performance should always be valid!" );
+ if ( pPerformance )
+ {
+ V_wcsncpy( wszMovieTitle, pPerformance->m_wszTitle, sizeof( wszMovieTitle ) );
+ }
+ }
+ m_pMovie->SetMovieTitle( wszMovieTitle );
+ g_pReplayMovieManager->SetPendingMovie( m_pMovie );
+ g_pReplayMovieManager->FlagMovieForFlush( m_pMovie, true );
+
+ // Setup the start render time
+ m_flStartRenderTime = realtime;
+ }
+ else
+ {
+ m_pMovie = NULL;
+ }
+
+ return true;
+}
+
+void CReplayDemoPlayer::PlayNextReplay()
+{
+ const PlaybackInfo_t *pPlaybackInfo = GetCurrentPlaybackInfo();
+ if ( !pPlaybackInfo )
+ return;
+
+ CReplay *pReplay = g_pReplayManager->GetReplay( pPlaybackInfo->m_hReplay );
+ if ( !pReplay )
+ return;
+
+ Assert ( pReplay->m_nStatus != CReplay::REPLAYSTATUS_DOWNLOADPHASE );
+
+ // Reconstruct now if necessary
+ g_pClientReplayContext->ReconstructReplayIfNecessary( pReplay );
+
+ // Open the demo file so we can read the start tick
+ const char *pFilename = pReplay->m_strReconstructedFilename.Get();
+ if ( !g_pFullFileSystem->FileExists( pFilename ) )
+ {
+ Warning( "\n** File %s does not exist!\n\n", pFilename );
+ DisplayFailedToPlayMsg( pPlaybackInfo->m_iPerformance );
+ return;
+ }
+
+ // Construct a con-command to play the demo, starting at the spawn tick.
+ // Play the replay using the 'playreplay' command - pass in the performance as well, -1
+ // meaning play without a performance.
+ const char *pCmd = "replay_hidebrowser\ngameui_hide\nprogress_enable\n";
+
+ // Execute the command
+ Cbuf_AddText( pCmd );
+ Cbuf_Execute();
+
+ // Use the replay demo player
+ extern IDemoPlayer *g_pReplayDemoPlayer;
+ demoplayer = g_pReplayDemoPlayer;
+
+ // Open the demo file
+ if ( demoplayer->StartPlayback( pFilename, false ) )
+ {
+ // Remove extension
+ char szBasename[ MAX_OSPATH ];
+ V_StripExtension( pFilename, szBasename, sizeof( szBasename ) );
+ extern IBaseClientDLL *g_ClientDLL;
+ g_ClientDLL->OnDemoPlaybackStart( szBasename );
+ }
+ else
+ {
+ SCR_EndLoadingPlaque();
+ }
+}
+
+void CReplayDemoPlayer::PlayReplay( ReplayHandle_t hReplay, int iPerformance )
+{
+ // Cache the replay (this function will only ever cache one)
+ s_ReplayDemoPlayer.ClearReplayList();
+ s_ReplayDemoPlayer.AddReplayToList( hReplay, iPerformance );
+ s_ReplayDemoPlayer.PlayNextReplay();
+}
+
+void CReplayDemoPlayer::OnLastDemoInLoopPlayed()
+{
+ g_pReplayMovieManager->CompleteRender( true, true );
+}
+
+float CReplayDemoPlayer::CalcMovieLength() const
+{
+ const PlaybackInfo_t *pPlaybackInfo = GetCurrentPlaybackInfo();
+ if ( !pPlaybackInfo )
+ return 0.0f;
+
+ const CReplay *pReplay = GetCurrentReplay();
+ if ( !pReplay )
+ return 0.0f;
+
+ const int nStartTick = pPlaybackInfo->m_nStartTick >= 0 ? pPlaybackInfo->m_nStartTick : pReplay->m_nSpawnTick;
+ const int nEndTick = pPlaybackInfo->m_nEndTick >= 0 ? pPlaybackInfo->m_nEndTick : ( pReplay->m_nSpawnTick + TIME_TO_TICKS( pReplay->m_flLength ) );
+
+ const bool bInvalidStartTick = nStartTick < 0;
+ const bool bInvalidEndTick = nEndTick < 0;
+
+ if ( bInvalidEndTick )
+ {
+ if ( !bInvalidStartTick )
+ {
+ // Valid start tick, invalid end tick
+ return TICKS_TO_TIME( nStartTick ) + pReplay->m_flLength;
+ }
+ }
+ else // Valid end tick.
+ {
+ if ( !bInvalidStartTick )
+ {
+ // Valid start tick, valid end tick
+ return TICKS_TO_TIME( nEndTick - nStartTick );
+ }
+ }
+
+ // Failed to calculate length
+ return 0.0f;
+}
+
+void CReplayDemoPlayer::StopPlayback()
+{
+ if ( !IsPlayingBack() )
+ return;
+
+ BaseClass::StopPlayback();
+
+ if ( m_bInStartPlayback )
+ return;
+
+ bool bDoneWithBatch = m_nCurReplayIndex >= m_vecReplaysToPlay.Count() - 1;
+ bool bRenderCancelled = g_pReplayMovieManager->RenderingCancelled();
+
+ if ( g_pReplayMovieManager->IsRendering() )
+ {
+ // Update the replay's state
+ CReplay *pReplay = GetCurrentReplay();
+ if ( !pReplay )
+ return;
+
+ pReplay->m_bRendered = true; // We have rendered this replay at least once
+
+ // Save replay
+ g_pReplayManager->FlagReplayForFlush( pReplay, false );
+
+ // Update the movie's state - the render succeeded
+ m_pMovie->SetIsRendered( true );
+
+ // Compute the time it took to render
+ m_pMovie->SetRenderTime( MAX( 0, realtime - m_flStartRenderTime ) );
+
+ // Sets the recorded date & time of the movie
+ m_pMovie->CaptureRecordTime();
+
+ // Get movie length
+ m_pMovie->SetLength( CalcMovieLength() );
+
+ // Save movie
+ g_pReplayMovieManager->FlagMovieForFlush( m_pMovie, true );
+
+ // Kill the renderer, show the browser if we're done rendering all replays
+ g_pReplayMovieManager->CompleteRender( true, bDoneWithBatch );
+ }
+ else if ( !bRenderCancelled ) // Without this check, batch rendering will continue to try and render after cancel
+ {
+ CReplay *pReplay = GetCurrentReplay();
+ if ( !pReplay )
+ return;
+
+ // Get the 'saved' performance from the performance controller, since the performance we initiated playback
+ // with may not be the one we want to select in the replay browser. The user may have save as a new performance,
+ // in which case we'll want to highlight that one.
+ CReplayPerformance *pSavedPerformance = g_pReplayPerformanceController->GetSavedPerformance();
+
+ // Get the index - FindPerformance() will set the output index to -1 if it can't find the performance
+ int iHighlightPerformance;
+ pReplay->FindPerformance( pSavedPerformance, iHighlightPerformance );
+
+ // Notify UI that playback is complete
+ g_pClientReplay->OnPlaybackComplete( pReplay->GetHandle(), iHighlightPerformance );
+
+ // Hide the replay performance editor
+ g_pClientReplay->HidePerformanceEditor();
+
+ // End playback/recording as needed
+ g_pReplayPerformanceController->Stop();
+ }
+
+ // Get the playback info before we incremeent the current replay
+ const PlaybackInfo_t *pPlaybackInfo = GetCurrentPlaybackInfo();
+
+ // Play the next replay, if one was queued
+ ++m_nCurReplayIndex;
+
+ if ( !bDoneWithBatch && !bRenderCancelled )
+ {
+ g_pReplayMovieManager->RenderNextMovie();
+ }
+ else
+ {
+ m_nCurReplayIndex = 0;
+ m_vecReplaysToPlay.PurgeAndDeleteElements();
+
+ if ( !m_bFullSignonStateReached )
+ {
+ DisplayFailedToPlayMsg( pPlaybackInfo ? pPlaybackInfo->m_iPerformance : -1 );
+ }
+ }
+}
+
+bool CReplayDemoPlayer::ShouldLoopDemos()
+{
+ return false;
+}
+
+void CReplayDemoPlayer::DisplayFailedToPlayMsg( int iPerformance )
+{
+ g_pClientReplay->DisplayReplayMessage(
+ iPerformance < 0 ? "#Replay_Err_User_FailedToPlayReplay" : "#Replay_Err_User_FailedToPlayTake",
+ false, true, NULL
+ );
+}
+
+//----------------------------------------------------------------------------------------
+
+#endif // #if defined( REPLAY_ENABLED )