diff options
Diffstat (limited to 'engine/replaydemoplayer.cpp')
| -rw-r--r-- | engine/replaydemoplayer.cpp | 511 |
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 ) |