You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
511 lines
14 KiB
511 lines
14 KiB
//========= 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 )
|
|
|