source-engine/engine/replaydemoplayer.cpp

512 lines
14 KiB
C++
Raw Permalink Normal View History

2020-04-22 16:56:21 +00:00
//========= 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 )