//========= 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 )