diff options
| author | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
|---|---|---|
| committer | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
| commit | 39ed87570bdb2f86969d4be821c94b722dc71179 (patch) | |
| tree | abc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/client/replay | |
| download | source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip | |
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/client/replay')
54 files changed, 16976 insertions, 0 deletions
diff --git a/mp/src/game/client/replay/cdll_replay.cpp b/mp/src/game/client/replay/cdll_replay.cpp new file mode 100644 index 00000000..1a2b3b31 --- /dev/null +++ b/mp/src/game/client/replay/cdll_replay.cpp @@ -0,0 +1,210 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "replay/cdll_replay.h"
+#include "replay/replaycamera.h"
+#include "replay/replay_ragdoll.h"
+#include "replay/iclientreplay.h"
+#include "replay/ireplayscreenshotsystem.h"
+#include "replay/ireplaysystem.h"
+#include "replay/ienginereplay.h"
+#include "replay/ireplaymoviemanager.h"
+#include "replay/vgui/replayconfirmquitdlg.h"
+#include "replay/vgui/replaybrowsermainpanel.h"
+#include "replay/vgui/replayinputpanel.h"
+#include "replay/vgui/replayperformanceeditor.h"
+#include "replayperformanceplaybackhandler.h"
+#include "vgui/ILocalize.h"
+#include "vgui/ISurface.h"
+#include "clientmode.h"
+#include "iviewrender.h"
+#include "igameevents.h"
+#include "replaycamera.h"
+#if defined( TF_CLIENT_DLL )
+#include "c_tf_gamestats.h"
+#endif
+#include "steamworks_gamestats.h"
+#include "replay_gamestats_shared.h"
+
+//----------------------------------------------------------------------------------------
+
+class IReplayScreenshotSystem;
+
+//----------------------------------------------------------------------------------------
+
+extern void ReplayUI_OpenReplayRenderOverlay();
+extern void ReplayUI_HideRenderOverlay();
+
+//----------------------------------------------------------------------------------------
+
+extern IReplayMovieManager *g_pReplayMovieManager;
+
+//----------------------------------------------------------------------------------------
+
+class CClientReplayImp : public IClientReplay
+{
+public:
+ virtual uint64 GetServerSessionId()
+ {
+ return GetSteamWorksSGameStatsUploader().GetServerSessionID();
+ }
+
+ virtual IReplayScreenshotSystem *GetReplayScreenshotSystem()
+ {
+ if ( g_pEngineReplay->IsSupportedModAndPlatform() )
+ return view->GetReplayScreenshotSystem();
+ return NULL;
+ }
+
+ virtual IReplayPerformancePlaybackHandler *GetPerformancePlaybackHandler()
+ {
+ return g_pReplayPerformancePlaybackHandler;
+ }
+
+ virtual bool CacheReplayRagdolls( const char* pFilename, int nStartTick )
+ {
+ return Replay_CacheRagdolls( pFilename, nStartTick );
+ }
+
+ virtual void OnSaveReplay( ReplayHandle_t hNewReplay, bool bShowInputDlg )
+ {
+ if ( bShowInputDlg )
+ {
+ // Get a name for the replay, saves to disk, add thumbnail to replay browser
+ ShowReplayInputPanel( hNewReplay );
+ }
+ else
+ {
+ // Just add the thumbnail if the replay browser exists
+ CReplayBrowserPanel* pReplayBrowser = ReplayUI_GetBrowserPanel();
+ if ( pReplayBrowser )
+ {
+ pReplayBrowser->OnSaveReplay( hNewReplay );
+ }
+ }
+
+ // Fire a message the game DLL can intercept (for achievements, etc).
+ IGameEvent *event = gameeventmanager->CreateEvent( "replay_saved" );
+ if ( event )
+ {
+ gameeventmanager->FireEventClientSide( event );
+ }
+ }
+
+ virtual void OnDeleteReplay( ReplayHandle_t hReplay )
+ {
+ CReplayBrowserPanel* pReplayBrowser = ReplayUI_GetBrowserPanel();
+ if ( pReplayBrowser )
+ {
+ pReplayBrowser->OnDeleteReplay( hReplay );
+ }
+ }
+
+ virtual void DisplayReplayMessage( const char *pLocalizeStr, bool bUrgent, bool bDlg, const char *pSound )
+ {
+ // Display a message?
+ if ( !pLocalizeStr || !pLocalizeStr[0] )
+ return;
+
+ g_pClientMode->DisplayReplayMessage( pLocalizeStr, -1.0f, bUrgent, pSound, bDlg );
+ }
+
+ virtual void DisplayReplayMessage( const wchar_t *pText, bool bUrgent, bool bDlg, const char *pSound )
+ {
+ if ( !pText || !pText[0] )
+ return;
+
+ const int nLen = wcslen( pText ) + 1;
+ char *pAnsi = new char[ nLen ];
+ g_pVGuiLocalize->ConvertUnicodeToANSI( pText, pAnsi, nLen );
+
+ g_pClientMode->DisplayReplayMessage( pAnsi, -1.0f, bUrgent, pSound, bDlg );
+ }
+
+ virtual bool OnConfirmQuit()
+ {
+ return ReplayUI_ShowConfirmQuitDlg();
+ }
+
+ virtual void OnRenderStart()
+ {
+ ReplayUI_OpenReplayRenderOverlay();
+ }
+
+ virtual void OnRenderComplete( const RenderMovieParams_t &RenderParams, bool bCancelled, bool bSuccess, bool bShowBrowser )
+ {
+ ReplayUI_HideRenderOverlay();
+
+ if ( bShowBrowser )
+ {
+ ReplayUI_ReloadBrowser();
+ }
+
+ // Upload a row to the OGS now that rendering has completed
+ GetReplayGameStatsHelper().SW_ReplayStats_WriteRenderDataEnd( RenderParams, bCancelled ? "cancelled" : bSuccess ? "success" : "failed" );
+ }
+
+ virtual void InitPerformanceEditor( ReplayHandle_t hReplay )
+ {
+ ReplayUI_InitPerformanceEditor( hReplay );
+ }
+
+ virtual void HidePerformanceEditor()
+ {
+ ReplayUI_ClosePerformanceEditor();
+ }
+
+ virtual bool ShouldRender()
+ {
+ extern ConVar replay_enablerenderpreview;
+ return !g_pReplayMovieManager->IsRendering() || replay_enablerenderpreview.GetBool();
+ }
+
+ virtual void PlaySound( const char *pSound )
+ {
+ if ( g_pVGuiSurface )
+ {
+ g_pVGuiSurface->PlaySound( pSound );
+ }
+ }
+
+ virtual void UploadOgsData( KeyValues *pData, bool bIncludeTimeField )
+ {
+ GetReplayGameStatsHelper().UploadError( pData, bIncludeTimeField );
+ }
+
+ virtual bool ShouldCompletePendingReplay( IGameEvent *pEvent )
+ {
+#if defined( TF_CLIENT_DLL )
+ return !( pEvent->GetInt( "death_flags" ) & TF_DEATH_FEIGN_DEATH );
+#else
+ return true;
+#endif
+ }
+
+ virtual void OnPlaybackComplete( ReplayHandle_t hReplay, int iPerformance )
+ {
+ ReplayUI_ReloadBrowser( hReplay, iPerformance );
+ }
+
+ virtual IReplayCamera *GetReplayCamera()
+ {
+ return ReplayCamera();
+ }
+
+ virtual bool OnEndOfReplayReached()
+ {
+ CReplayPerformanceEditorPanel *pEditor = ReplayUI_GetPerformanceEditor();
+ if ( !pEditor )
+ return false;
+
+ return pEditor->OnEndOfReplayReached();
+ }
+};
+
+static CClientReplayImp s_ClientReplayImp;
+EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CClientReplayImp, IClientReplay, CLIENT_REPLAY_INTERFACE_VERSION, s_ClientReplayImp );
+
+#endif // #if defined( REPLAY_ENABLED )
\ No newline at end of file diff --git a/mp/src/game/client/replay/cdll_replay.h b/mp/src/game/client/replay/cdll_replay.h new file mode 100644 index 00000000..6a8c24f5 --- /dev/null +++ b/mp/src/game/client/replay/cdll_replay.h @@ -0,0 +1,16 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//=======================================================================================//
+
+#ifndef CDLL_REPLAY_H
+#define CDLL_REPLAY_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+//----------------------------------------------------------------------------------------
+
+class IReplayEngineClient;
+
+//----------------------------------------------------------------------------------------
+
+#endif
\ No newline at end of file diff --git a/mp/src/game/client/replay/gamedefs.cpp b/mp/src/game/client/replay/gamedefs.cpp new file mode 100644 index 00000000..79e5da90 --- /dev/null +++ b/mp/src/game/client/replay/gamedefs.cpp @@ -0,0 +1,47 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//----------------------------------------------------------------------------------------
+
+#include "cbase.h"
+#include "gamedefs.h"
+
+//----------------------------------------------------------------------------------------
+
+StatInfo_t g_pReplayDisplayGameStats[REPLAY_MAX_DISPLAY_GAMESTATS] =
+{
+#if defined( TF_CLIENT_DLL )
+
+ { TFSTAT_SHOTS_HIT, "#Stat_ShotsHit", },
+ { TFSTAT_SHOTS_FIRED, "#Stat_ShotsFired" },
+ { TFSTAT_DAMAGE, "#Stat_Damage" },
+ { TFSTAT_CAPTURES, "#Stat_Captures" },
+ { TFSTAT_DEFENSES, "#Stat_Defenses" },
+ { TFSTAT_REVENGE, "#Stat_Revenge" },
+ { TFSTAT_POINTSSCORED, "#Stat_PointsScored" },
+ { TFSTAT_BUILDINGSDESTROYED,"#Stat_BuildingsDestroyed" },
+ { TFSTAT_HEADSHOTS, "#Stat_Headshots" },
+ { TFSTAT_PLAYTIME, "#Stat_PlayTime" },
+
+ { TFSTAT_HEALING, "#Stat_Healing" },
+ { TFSTAT_INVULNS, "#Stat_Invulns" },
+ { TFSTAT_KILLASSISTS, "#Stat_KillAssists" },
+ { TFSTAT_BACKSTABS, "#Stat_BackStabs" },
+ { TFSTAT_HEALTHLEACHED, "#Stat_HealthLeached" },
+ { TFSTAT_BUILDINGSBUILT, "#Stat_BuildingsBuilt" },
+ { TFSTAT_MAXSENTRYKILLS, "#Stat_MaxSentryKills" },
+ { TFSTAT_TELEPORTS, "#Stat_Teleports" },
+ { TFSTAT_FIREDAMAGE, "#Stat_FiredDamage" },
+ { TFSTAT_BONUS_POINTS, "#Stat_BonusPoints" },
+
+ { TFSTAT_BLASTDAMAGE, "#Stat_BlastDamage" },
+ { TFSTAT_DAMAGETAKEN, "#Stat_DamageTaken" },
+ { TFSTAT_CRITS, "#Stat_Crits" },
+
+#elif defined( CSTRIKE_DLL )
+
+ { CSSTAT_SHOTS_HIT, "#Stat_ShotsHit" },
+ { CSSTAT_SHOTS_FIRED, "#Stat_ShotsFired" },
+ { CSSTAT_DAMAGE, "#Stat_Damage" },
+
+#endif
+};
\ No newline at end of file diff --git a/mp/src/game/client/replay/gamedefs.h b/mp/src/game/client/replay/gamedefs.h new file mode 100644 index 00000000..04306b02 --- /dev/null +++ b/mp/src/game/client/replay/gamedefs.h @@ -0,0 +1,79 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//----------------------------------------------------------------------------------------
+
+#ifndef GAMEDEFS_H
+#define GAMEDEFS_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#if defined( TF_CLIENT_DLL )
+
+# include "tf_gamestats_shared.h"
+
+# define REPLAY_GAMESTATS_UNDEFINED TFSTAT_UNDEFINED
+# define REPLAY_GAMESTATS_REVENGE TFSTAT_REVENGE
+# define REPLAY_GAMESTATS_DEATHS TFSTAT_DEATHS
+# define REPLAY_GAMESTATS_DOMINATIONS TFSTAT_DOMINATIONS
+# define REPLAY_GAMESTATS_POINTSSCORED TFSTAT_POINTSSCORED
+# define REPLAY_GAMESTATS_MAX TFSTAT_TOTAL
+
+# define REPLAY_TEAM_TEAM0 TF_TEAM_RED
+# define REPLAY_TEAM_TEAM1 TF_TEAM_BLUE
+
+# define REPLAY_CLASS_UNDEFINED TF_CLASS_UNDEFINED
+# define REPLAY_NUM_CLASSES TF_CLASS_MENU_BUTTONS
+
+# define REPLAY_DEATH_DOMINATION TF_DEATH_DOMINATION
+# define REPLAY_DEATH_ASSISTER_DOMINATION TF_DEATH_ASSISTER_DOMINATION
+# define REPLAY_DEATH_REVENGE TF_DEATH_REVENGE
+# define REPLAY_DEATH_ASSISTER_REVENGE TF_DEATH_ASSISTER_REVENGE
+
+# define C_ReplayGame_PlayerResource
+# include "c_tf_playerresource.h"
+typedef C_TF_PlayerResource C_ReplayGame_PlayerResource_t;
+
+# define ReplayStatType_t TFStatType_t
+
+# define REPLAY_MAX_DISPLAY_GAMESTATS 23
+
+#elif defined( CSTRIKE_DLL )
+
+# include "cs_gamestats_shared.h"
+
+# define REPLAY_GAMESTATS_UNDEFINED CSSTAT_UNDEFINED
+# define REPLAY_GAMESTATS_REVENGE CSSTAT_REVENGES
+# define REPLAY_GAMESTATS_DEATHS CSSTAT_DEATHS
+# define REPLAY_GAMESTATS_DOMINATIONS CSSTAT_DOMINATIONS
+# define REPLAY_GAMESTATS_POINTSSCORED CSSTAT_UNDEFINED // Sheeeeeeeeeit
+# define REPLAY_GAMESTATS_MAX CSSTAT_MAX
+
+# define REPLAY_TEAM_TEAM0 TEAM_TERRORIST
+# define REPLAY_TEAM_TEAM1 TEAM_CT
+
+# define REPLAY_CLASS_UNDEFINED CS_CLASS_NONE
+# define REPLAY_NUM_CLASSES CS_NUM_CLASSES
+
+# define REPLAY_DEATH_DOMINATION CS_DEATH_DOMINATION
+# define REPLAY_DEATH_REVENGE CS_DEATH_REVENGE
+
+# include "c_cs_playerresource.h"
+typedef C_CS_PlayerResource C_ReplayGame_PlayerResource_t;
+
+# define ReplayStatType_t CSStatType_t
+# define RoundStats_t StatsCollection_t
+
+# define REPLAY_MAX_DISPLAY_GAMESTATS 3
+
+#endif
+
+struct StatInfo_t
+{
+ ReplayStatType_t m_nStat;
+ const char *m_pStatLocalizationToken;
+};
+
+extern StatInfo_t g_pReplayDisplayGameStats[REPLAY_MAX_DISPLAY_GAMESTATS];
+
+#endif // GAMEDEFS_H
\ No newline at end of file diff --git a/mp/src/game/client/replay/genericclassbased_replay.cpp b/mp/src/game/client/replay/genericclassbased_replay.cpp new file mode 100644 index 00000000..51556d20 --- /dev/null +++ b/mp/src/game/client/replay/genericclassbased_replay.cpp @@ -0,0 +1,426 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "genericclassbased_replay.h"
+#include "clientmode_shared.h"
+#include "replay/ireplaymoviemanager.h"
+#include "replay/ireplayfactory.h"
+#include "replay/ireplayscreenshotmanager.h"
+#include "replay/screenshot.h"
+#include "replay/gamedefs.h"
+#include <time.h>
+
+//----------------------------------------------------------------------------------------
+
+extern IReplayScreenshotManager *g_pReplayScreenshotManager;
+
+//----------------------------------------------------------------------------------------
+
+CGenericClassBasedReplay::CGenericClassBasedReplay()
+: m_nPlayerTeam( 0 )
+{
+ m_szKillerName[ 0 ] = 0;
+
+ m_nPlayerClass = REPLAY_CLASS_UNDEFINED;
+ m_nKillerClass = REPLAY_CLASS_UNDEFINED;
+}
+
+CGenericClassBasedReplay::~CGenericClassBasedReplay()
+{
+ OnEndRecording();
+
+ m_vecKills.PurgeAndDeleteElements();
+ m_vecDominations.PurgeAndDeleteElements();
+ m_vecAssisterDominations.PurgeAndDeleteElements();
+ m_vecRevenges.PurgeAndDeleteElements();
+ m_vecAssisterRevenges.PurgeAndDeleteElements();
+}
+
+void CGenericClassBasedReplay::OnBeginRecording()
+{
+ BaseClass::OnBeginRecording();
+
+ Assert( gameeventmanager );
+ ListenForGameEvent( "player_death" );
+}
+
+void CGenericClassBasedReplay::OnEndRecording()
+{
+ if ( gameeventmanager )
+ {
+ gameeventmanager->RemoveListener( this );
+ }
+
+ BaseClass::OnEndRecording();
+}
+
+void CGenericClassBasedReplay::OnComplete()
+{
+ BaseClass::OnComplete();
+}
+
+bool CGenericClassBasedReplay::ShouldAllowDelete() const
+{
+ return g_pClientReplayContext->GetMovieManager()->GetNumMoviesDependentOnReplay( this ) == 0;
+}
+
+void CGenericClassBasedReplay::OnDelete()
+{
+ BaseClass::OnDelete();
+}
+
+void CGenericClassBasedReplay::FireGameEvent( IGameEvent *pEvent )
+{
+}
+
+void CGenericClassBasedReplay::Update()
+{
+ BaseClass::Update();
+
+ // Record any new stats
+ RecordUpdatedStats();
+
+ // Setup next update
+ m_flNextUpdateTime = engine->Time() + .1f;
+}
+
+float CGenericClassBasedReplay::GetKillScreenshotDelay()
+{
+ ConVarRef replay_screenshotkilldelay( "replay_screenshotkilldelay" );
+ return replay_screenshotkilldelay.IsValid() ? replay_screenshotkilldelay.GetFloat() : 0.5f;
+}
+
+void CGenericClassBasedReplay::RecordUpdatedStats()
+{
+ // Get current stats
+ static RoundStats_t s_curStats;
+ if ( !GetCurrentStats( s_curStats ) )
+ return;
+
+ // Go through each stat and see if it's changed
+ for ( int i = 0; i < REPLAY_GAMESTATS_MAX; ++i )
+ {
+ const int nCurStat = s_curStats.Get( i );
+ const int nRefStat = m_refStats.Get( i );
+
+ if ( nCurStat != nRefStat )
+ {
+ // Calculate new stat based on reference
+ const int nLifeStat = nCurStat - nRefStat;
+
+ if ( nLifeStat != m_lifeStats.Get( i ) )
+ {
+ ConVarRef replay_debug( "replay_debug" );
+ if ( replay_debug.IsValid() && replay_debug.GetBool() )
+ {
+ Msg( "REPLAY: Player stat \"%s\" changed from %i to %i.\n", GetStatString( i ), nRefStat, nCurStat );
+ }
+
+ // Set the new stat
+ m_lifeStats.Set( i, nLifeStat );
+ }
+ }
+ }
+}
+
+bool CGenericClassBasedReplay::Read( KeyValues *pIn )
+{
+ if ( !BaseClass::Read( pIn ) )
+ return false;
+
+ // Read player class
+ m_nPlayerClass = pIn->GetInt( "player_class" ); Assert( IsValidClass( m_nPlayerClass ) );
+
+ // Read player team
+ m_nPlayerTeam = pIn->GetInt( "player_team" ); Assert( IsValidTeam( m_nPlayerTeam ) );
+
+ // Read killer info
+ m_nKillerClass = pIn->GetInt( "killer_class" );
+ V_strcpy( m_szKillerName, pIn->GetString( "killer_name" ) );
+
+ // Make sure vector is clear
+ Assert( GetKillCount() == 0 );
+
+ // Read all kill data and add the kills vector
+ KeyValues *pKills = pIn->FindKey( "kills" );
+ if ( pKills )
+ {
+ FOR_EACH_TRUE_SUBKEY( pKills, pKill )
+ {
+ // Create the kill data
+ AddKill(
+ pKill->GetString( "victim_name" ),
+ pKill->GetInt( "victim_class" )
+ );
+ }
+ }
+
+ AddKillStats( m_vecDominations , pIn, "dominations", REPLAY_GAMESTATS_DOMINATIONS );
+ AddKillStats( m_vecAssisterDominations, pIn, "assister_dominations", REPLAY_GAMESTATS_UNDEFINED );
+ AddKillStats( m_vecRevenges , pIn, "revenges", REPLAY_GAMESTATS_REVENGE );
+ AddKillStats( m_vecAssisterRevenges , pIn, "assister_revenges", REPLAY_GAMESTATS_UNDEFINED );
+
+ // Read stats by index
+ KeyValues *pStats = pIn->FindKey( "stats" );
+ if ( pStats )
+ {
+ for ( int i = 0; i < REPLAY_GAMESTATS_MAX; ++i )
+ {
+ char szStatKey[ 16 ];
+ V_snprintf( szStatKey, sizeof( szStatKey ), "%i", i );
+ m_lifeStats.Set( i, pStats->GetInt( szStatKey ) );
+ }
+ }
+
+ return true;
+}
+
+void CGenericClassBasedReplay::AddKillStats( CUtlVector< GenericStatInfo_t * > &vecKillStats, KeyValues *pIn, const char *pSubKeyName, int iStatIndex )
+{
+ Assert( vecKillStats.Count() == 0 );
+ KeyValues *pSubKey = pIn->FindKey( pSubKeyName );
+ if ( pSubKey )
+ {
+ FOR_EACH_TRUE_SUBKEY( pSubKey, pCurKillStat )
+ {
+ GenericStatInfo_t *pNewKillStat = new GenericStatInfo_t;
+ pNewKillStat->m_nVictimFriendId = pCurKillStat->GetInt( "victim_friend_id" );
+ pNewKillStat->m_nAssisterFriendId = pCurKillStat->GetInt( "assister_friend_id" );
+ vecKillStats.AddToTail( pNewKillStat );
+ }
+ }
+
+ // Duplicate the data in the life stats
+ if ( iStatIndex > m_nStatUndefined )
+ {
+ m_lifeStats.Set( iStatIndex, vecKillStats.Count() );
+ }
+}
+
+void CGenericClassBasedReplay::Write( KeyValues *pOut )
+{
+ BaseClass::Write( pOut );
+
+ // Write player class
+ pOut->SetInt( "player_class", m_nPlayerClass );
+
+ // Write player team
+ pOut->SetInt( "player_team", m_nPlayerTeam );
+
+ // Write killer info
+ pOut->SetInt( "killer_class", m_nKillerClass );
+ pOut->SetString( "killer_name", m_szKillerName );
+
+ // Write kills
+ KeyValues *pKills = new KeyValues( "kills" );
+ pOut->AddSubKey( pKills );
+
+ for ( int i = 0; i < GetKillCount(); ++i )
+ {
+ KillData_t *pCurKill = m_vecKills[ i ];
+
+ KeyValues *pKillOut = new KeyValues( "kill" );
+ pKills->AddSubKey( pKillOut );
+
+ // Write kill data
+ pKillOut->SetString( "victim_name", pCurKill->m_szPlayerName );
+ pKillOut->SetInt( "victim_class", pCurKill->m_nPlayerClass );
+ }
+
+ WriteKillStatVector( m_vecDominations , "dominations" , "domination" , pOut, 1 );
+ WriteKillStatVector( m_vecAssisterDominations, "assister_dominations", "assister_domination", pOut, 2 );
+ WriteKillStatVector( m_vecRevenges , "revenges" , "revenge" , pOut, 1 );
+ WriteKillStatVector( m_vecAssisterRevenges , "assister_revenges" , "assister_revenge" , pOut, 2 );
+
+ // Write non-zero stats by index
+ KeyValues *pStats = new KeyValues( "stats" );
+ pOut->AddSubKey( pStats );
+
+ for ( int i = 0; i < REPLAY_GAMESTATS_MAX; ++i )
+ {
+ const int nCurStat = m_lifeStats.Get( i );
+ if ( nCurStat )
+ {
+ char szStatKey[ 16 ];
+ V_snprintf( szStatKey, sizeof( szStatKey ), "%i", i );
+ pStats->SetInt( szStatKey, nCurStat );
+ }
+ }
+}
+
+void CGenericClassBasedReplay::WriteKillStatVector( CUtlVector< CGenericClassBasedReplay::GenericStatInfo_t * > const &vec, const char *pSubKeyName,
+ const char *pElementKeyName, KeyValues *pRootKey, int nNumMembersToWrite ) const
+{
+ Assert( nNumMembersToWrite >= 1 );
+
+ // Write dominations
+ KeyValues *pSubKey = new KeyValues( pSubKeyName );
+ pRootKey->AddSubKey( pSubKey );
+
+ for ( int i = 0; i < vec.Count(); ++i )
+ {
+ GenericStatInfo_t *pSrcData = vec[ i ];
+
+ KeyValues *pCurSubKey = new KeyValues( pElementKeyName );
+ pSubKey->AddSubKey( pCurSubKey );
+
+ // Always write
+ pCurSubKey->SetInt( "victim_friend_id", pSrcData->m_nVictimFriendId );
+
+ if ( nNumMembersToWrite > 1 )
+ {
+ pCurSubKey->SetInt( "assister_friend_id", pSrcData->m_nAssisterFriendId );
+ }
+ }
+}
+
+void CGenericClassBasedReplay::AddKill( const char *pPlayerName, int nPlayerClass )
+{
+ KillData_t *pNewKillData = new KillData_t;
+
+ V_strcpy( pNewKillData->m_szPlayerName , pPlayerName );
+ pNewKillData->m_nPlayerClass = nPlayerClass;
+
+ ConVarRef replay_debug( "replay_debug" );
+ if ( replay_debug.IsValid() && replay_debug.GetBool() )
+ {
+ DevMsg( "\n\nRecorded kill: name=%s, class=%s (this=%i)\n\n", pPlayerName, GetPlayerClass( nPlayerClass ), (int)this );
+ }
+
+ m_vecKills.AddToTail( pNewKillData );
+}
+
+const char *CGenericClassBasedReplay::GetPlayerClass() const
+{
+ return GetPlayerClass( m_nPlayerClass );
+}
+
+void CGenericClassBasedReplay::AddDomination( int nVictimID )
+{
+ AddKillStatFromUserIds( m_vecDominations, nVictimID );
+}
+
+void CGenericClassBasedReplay::AddAssisterDomination( int nVictimID, int nAssiterID )
+{
+ AddKillStatFromUserIds( m_vecAssisterDominations, nVictimID, nAssiterID );
+}
+
+void CGenericClassBasedReplay::AddRevenge( int nVictimID )
+{
+ AddKillStatFromUserIds( m_vecRevenges, nVictimID );
+}
+
+void CGenericClassBasedReplay::AddAssisterRevenge( int nVictimID, int nAssiterID )
+{
+ AddKillStatFromUserIds( m_vecAssisterRevenges, nVictimID, nAssiterID );
+}
+
+void CGenericClassBasedReplay::AddKillStatFromUserIds( CUtlVector< GenericStatInfo_t * > &vec, int nVictimId, int nAssisterId/*=0*/ )
+{
+ uint32 nVictimFriendId;
+ if ( !GetFriendIdFromUserId( engine->GetPlayerForUserID( nVictimId ), nVictimFriendId ) )
+ return;
+
+ uint32 nAssisterFriendId = 0;
+ if ( nAssisterId && !GetFriendIdFromUserId( engine->GetPlayerForUserID( nAssisterId ), nAssisterFriendId ) )
+ return;
+
+ AddKillStatFromFriendIds( vec, nVictimFriendId, nAssisterFriendId );
+}
+
+void CGenericClassBasedReplay::AddKillStatFromFriendIds( CUtlVector< GenericStatInfo_t * > &vec, uint32 nVictimFriendId, uint32 nAssisterFriendId/*=0*/ )
+{
+ GenericStatInfo_t *pNewKillStat = new GenericStatInfo_t;
+ pNewKillStat->m_nVictimFriendId = nVictimFriendId;
+ pNewKillStat->m_nAssisterFriendId = nAssisterFriendId;
+ vec.AddToTail( pNewKillStat );
+}
+
+bool CGenericClassBasedReplay::GetFriendIdFromUserId( int nPlayerIndex, uint32 &nFriendIdOut ) const
+{
+ player_info_t pi;
+ if ( !steamapicontext->SteamFriends() ||
+ !steamapicontext->SteamUtils() ||
+ !engine->GetPlayerInfo( nPlayerIndex, &pi ) )
+ {
+ AssertMsg( 0, "REPLAY: Failed to add domination" );
+ nFriendIdOut = 0;
+ return false;
+ }
+
+ nFriendIdOut = pi.friendsID;
+
+ return true;
+}
+
+const char *CGenericClassBasedReplay::GetMaterialFriendlyPlayerClass() const
+{
+ return GetPlayerClass();
+}
+
+const char *CGenericClassBasedReplay::GetKillerName() const
+{
+ Assert( WasKilled() );
+ return m_szKillerName;
+}
+
+const char *CGenericClassBasedReplay::GetKillerClass() const
+{
+ Assert( WasKilled() );
+ return GetPlayerClass( m_nKillerClass );
+}
+
+void CGenericClassBasedReplay::DumpGameSpecificData() const
+{
+ DevMsg( " class: %s\n", GetPlayerClass() );
+
+ // Print kills
+ DevMsg( " %i kills:\n", GetKillCount() );
+ for ( int i = 0; i < GetKillCount(); ++i )
+ {
+ KillData_t *pCurKill = m_vecKills[ i ];
+ Msg( " kill %i: name=%s class=%s\n", i, pCurKill->m_szPlayerName, GetPlayerClass( pCurKill->m_nPlayerClass ) );
+ }
+
+ if ( !WasKilled() )
+ {
+ Msg( " No killer.\n" );
+ return;
+ }
+
+ // Print killer info
+ Msg( " killer: name=%s class=%s\n", m_szKillerName, GetPlayerClass( m_nKillerClass ) );
+}
+
+void CGenericClassBasedReplay::SetPlayerClass( int nPlayerClass )
+{
+ //Assert( IsValidClass( nPlayerClass ) );
+ m_nPlayerClass = nPlayerClass;
+
+ // Setup reference stats if this is a valid class
+ if ( IsValidClass( nPlayerClass ) )
+ {
+ GetCurrentStats( m_refStats );
+ }
+}
+
+void CGenericClassBasedReplay::SetPlayerTeam( int nPlayerTeam )
+{
+ m_nPlayerTeam = nPlayerTeam;
+}
+
+void CGenericClassBasedReplay::RecordPlayerDeath( const char *pKillerName, int nKillerClass )
+{
+ V_strcpy( m_szKillerName, pKillerName );
+ m_nKillerClass = nKillerClass;
+}
+
+
+//----------------------------------------------------------------------------------------
+
+#endif
\ No newline at end of file diff --git a/mp/src/game/client/replay/genericclassbased_replay.h b/mp/src/game/client/replay/genericclassbased_replay.h new file mode 100644 index 00000000..34cb0f98 --- /dev/null +++ b/mp/src/game/client/replay/genericclassbased_replay.h @@ -0,0 +1,178 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//=======================================================================================//
+
+#if defined( REPLAY_ENABLED )
+
+#ifndef GENERICCLASSBASED_REPLAY_H
+#define GENERICCLASSBASED_REPLAY_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+//----------------------------------------------------------------------------------------
+
+#include "replay/replay.h"
+#include "replay/iclientreplaycontext.h"
+#include "GameEventListener.h"
+
+// For RoundStats_t
+#include "replay/gamedefs.h"
+
+//----------------------------------------------------------------------------------------
+
+extern IClientReplayContext *g_pClientReplayContext;
+
+//----------------------------------------------------------------------------------------
+
+class CGenericClassBasedReplay : public CReplay,
+ public CGameEventListener
+{
+ typedef CReplay BaseClass;
+public:
+ CGenericClassBasedReplay();
+ ~CGenericClassBasedReplay();
+
+ virtual void OnBeginRecording();
+ virtual void OnEndRecording();
+ virtual void OnComplete();
+ virtual bool ShouldAllowDelete() const;
+ virtual void OnDelete();
+
+ virtual void FireGameEvent( IGameEvent *pEvent );
+
+ virtual bool Read( KeyValues *pIn );
+ virtual void Write( KeyValues *pOut );
+
+ virtual void DumpGameSpecificData() const;
+
+ void SetPlayerClass( int nPlayerClass );
+ void SetPlayerTeam( int nPlayerTeam );
+
+ void RecordPlayerDeath( const char *pKillerName, int nKillerClass );
+
+ // Add a new kill to the list
+ void AddKill( const char *pPlayerName, int nPlayerClass );
+
+ // Get the player class as a string
+ virtual const char *GetPlayerClass() const;
+
+ // Get the player team as a string
+ virtual const char *GetPlayerTeam() const = 0;
+
+ // Utility to get the material-friendly player class (demoman->demo, heavyweapons->heavy)
+ virtual const char *GetMaterialFriendlyPlayerClass() const;
+
+ // Was there a killer?
+ inline bool WasKilled() const { return m_szKillerName[0] != 0; }
+
+ // Get killer name
+ const char *GetKillerName() const;
+
+ // Get the killer class, if there was a killer
+ const char *GetKillerClass() const;
+
+ int GetDownloadStatus() const;
+
+ // Kill info
+ struct KillData_t
+ {
+ char m_szPlayerName[MAX_OSPATH];
+ int m_nPlayerClass;
+ };
+
+ inline int GetKillCount() const { return m_vecKills.Count(); }
+ inline const KillData_t *GetKill( int nKillIndex ) { return m_vecKills[ nKillIndex ]; }
+
+ // A generic info struct used for dominations, assisted dominations, revenges, assisted revenged...
+ // Not all data members are necessarily used
+ struct GenericStatInfo_t
+ {
+ GenericStatInfo_t() : m_nVictimFriendId( 0 ), m_nAssisterFriendId( 0 ) {}
+ uint32 m_nVictimFriendId;
+ uint32 m_nAssisterFriendId;
+ };
+
+ inline int GetDominationCount() const { return m_vecDominations.Count(); }
+ inline const GenericStatInfo_t *GetDomination( int nIndex ) const { return m_vecDominations[ nIndex ]; }
+
+ inline int GetAssisterDominationCount() const { return m_vecAssisterDominations.Count(); }
+ inline const GenericStatInfo_t *GetAssisterDomination( int nIndex ) const { return m_vecAssisterDominations[ nIndex ]; }
+
+ inline int GetRevengeCount() const { return m_vecRevenges.Count(); }
+ inline const GenericStatInfo_t *GetRevenge( int nIndex ) const { return m_vecRevenges[ nIndex ]; }
+
+ inline int GetAssisterRevengeCount() const { return m_vecAssisterRevenges.Count(); }
+ inline const GenericStatInfo_t *GetAssisterRevenge( int nIndex ) const { return m_vecAssisterRevenges[ nIndex ]; }
+
+ RoundStats_t const &GetStats() const { return m_lifeStats; }
+
+protected:
+ int m_nPlayerClass;
+ int m_nPlayerTeam;
+ int m_nStatUndefined;
+
+ char m_szKillerName[ MAX_OSPATH ];
+ int m_nKillerClass;
+
+ virtual bool IsValidClass( int nClass ) const = 0;
+ virtual bool IsValidTeam( int iTeam ) const = 0;
+ virtual bool GetCurrentStats( RoundStats_t &out ) = 0;
+ virtual const char *GetStatString( int iStat ) const = 0;
+ virtual const char *GetPlayerClass( int iClass ) const = 0;
+
+ virtual void Update();
+
+ // Domination
+ void AddDomination( int nVictimID );
+ void AddAssisterDomination( int nVictimID, int nAssiterID );
+
+ void AddRevenge( int nVictimID );
+ void AddAssisterRevenge( int nVictimID, int nAssiterID );
+
+ float GetKillScreenshotDelay();
+
+ RoundStats_t m_refStats; // Reference stats, used to compute current stats
+ RoundStats_t m_lifeStats; // Stats for this life, based on reference stats (m_refStats)
+
+private:
+ void MedicUpdate();
+
+ bool GetFriendIdFromUserId( int nPlayerIndex, uint32 &nFriendIdOut ) const; // Get a friend ID based on player index. Returns true on success
+ void AddKillStatFromUserIds( CUtlVector< GenericStatInfo_t * > &vec, int nVictimId, int nAssisterId = 0 );
+ void AddKillStatFromFriendIds( CUtlVector< GenericStatInfo_t * > &vec, uint32 nVictimFriendId, uint32 nAssisterFriendId = 0 );
+ void WriteKillStatVector( CUtlVector< GenericStatInfo_t * > const &vec, const char *pSubKeyName, const char *pElementKeyName,
+ KeyValues *pRootKey, int nNumMembersToWrite ) const;
+ void AddKillStats( CUtlVector< GenericStatInfo_t * > &vecKillStats, KeyValues *pIn, const char *pSubKeyName, int iStatIndex );
+ void RecordUpdatedStats();
+
+ CUtlVector< KillData_t * > m_vecKills;
+ CUtlVector< GenericStatInfo_t * > m_vecDominations;
+ CUtlVector< GenericStatInfo_t * > m_vecAssisterDominations;
+ CUtlVector< GenericStatInfo_t * > m_vecRevenges;
+ CUtlVector< GenericStatInfo_t * > m_vecAssisterRevenges;
+
+ // TODO... dominations, achievements, etc.
+};
+
+//----------------------------------------------------------------------------------------
+
+inline CGenericClassBasedReplay *ToGenericClassBasedReplay( CReplay *pClientReplay )
+{
+ return static_cast< CGenericClassBasedReplay * >( pClientReplay );
+}
+
+inline const CGenericClassBasedReplay *ToGenericClassBasedReplay( const CReplay *pClientReplay )
+{
+ return static_cast< const CGenericClassBasedReplay * >( pClientReplay );
+}
+
+inline CGenericClassBasedReplay *GetGenericClassBasedReplay( ReplayHandle_t hReplay )
+{
+ return ToGenericClassBasedReplay( g_pClientReplayContext->GetReplay( hReplay ) );
+}
+
+//----------------------------------------------------------------------------------------
+
+#endif // GENERICCLASSBASED_REPLAY_H
+
+#endif
\ No newline at end of file diff --git a/mp/src/game/client/replay/replay_ragdoll.cpp b/mp/src/game/client/replay/replay_ragdoll.cpp new file mode 100644 index 00000000..73b3481e --- /dev/null +++ b/mp/src/game/client/replay/replay_ragdoll.cpp @@ -0,0 +1,747 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// TODO:
+// - Use a mempool
+// - Need to be able to gracefully turn replay ragdolls on/off
+//
+//----------------------------------------------------------------------------------------
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "replay_ragdoll.h"
+#include "tier1/mempool.h"
+#include "debugoverlay_shared.h"
+#include "filesystem.h"
+
+//--------------------------------------------------------------------------------
+
+static matrix3x4_t gs_BoneCache[ MAXSTUDIOBONES ];
+static int gs_nBytesAllocated = 0;
+
+//--------------------------------------------------------------------------------
+
+void OnReplayCacheClientRagdollsCvarChange( IConVar *pVar, const char *pOldValue, float flOldValue )
+{
+ // TODO: need to be able to gracefully turn replay ragdolls on/off
+}
+
+//--------------------------------------------------------------------------------
+
+static ConVar replay_ragdoll_dbg( "replay_ragdoll_dbg", "0", FCVAR_CLIENTDLL, "Display replay ragdoll debugging information." );
+static ConVar replay_cache_client_ragdolls( "replay_cache_client_ragdolls", "0", FCVAR_CLIENTDLL, "Record ragdolls on the client during.", OnReplayCacheClientRagdollsCvarChange );
+
+//--------------------------------------------------------------------------------
+
+void DrawBones( matrix3x4_t const* pBones, int nNumBones, ragdoll_t const* pRagdoll,
+ int nRed, int nGreen, int nBlue, C_BaseAnimating* pBaseAnimating )
+{
+ Assert( pBones );
+ Assert( pRagdoll );
+ Assert( pBaseAnimating );
+
+ Vector from, to;
+ for ( int i = 0; i < nNumBones; ++i )
+ {
+// debugoverlay->AddCoordFrameOverlay( pBones[ i ], 3.0f );
+
+ int const iRagdollParentIndex = pRagdoll->list[ i ].parentIndex;
+ if ( iRagdollParentIndex < 0 )
+ continue;
+
+ int iBoneIndex = pRagdoll->boneIndex[ i ];
+ int iParentIndex = pRagdoll->boneIndex[ iRagdollParentIndex ];
+
+ MatrixPosition( pBones[ iParentIndex ], from );
+ MatrixPosition( pBones[ iBoneIndex ], to );
+
+ debugoverlay->AddLineOverlay( from, to, nRed, nGreen, nBlue, true, 0.0f );
+ }
+}
+
+//--------------------------------------------------------------------------------
+
+inline int GetServerTickCount()
+{
+ int nTick = TIME_TO_TICKS( engine->GetLastTimeStamp() );
+ return nTick;
+}
+
+//--------------------------------------------------------------------------------
+
+/*static*/ RagdollSimulationFrame_t* RagdollSimulationFrame_t::Alloc( int nNumBones )
+{
+ // TODO: use a mempool
+ RagdollSimulationFrame_t* pNew = new RagdollSimulationFrame_t();
+ pNew->pPositions = new Vector[ nNumBones ];
+ pNew->pAngles = new QAngle[ nNumBones ];
+ gs_nBytesAllocated += sizeof( pNew ) + nNumBones * ( sizeof( Vector ) + sizeof( QAngle ) );
+ return pNew;
+}
+
+//--------------------------------------------------------------------------------
+
+RagdollSimulationData_t::RagdollSimulationData_t( C_BaseAnimating* pEntity, int nStartTick, int nNumBones )
+: m_pEntity( pEntity ),
+ m_nEntityIndex( -1 ),
+ m_nStartTick( nStartTick ),
+ m_nNumBones( nNumBones ),
+ m_nDuration( -1 )
+{
+ if ( pEntity )
+ {
+ m_nEntityIndex = pEntity->entindex();
+ }
+
+ Assert( nNumBones >= 0 && nNumBones < MAXSTUDIOBONES );
+}
+
+bool _ComputeRagdollBones( const ragdoll_t *pRagdoll, matrix3x4_t &parentTransform, matrix3x4_t *pBones, Vector *pPositions, QAngle *pAngles )
+{
+ matrix3x4_t inverted, output;
+
+#ifdef _DEBUG
+ CBitVec<MAXSTUDIOBONES> vBonesComputed;
+ vBonesComputed.ClearAll();
+#endif
+
+ for ( int i = 0; i < pRagdoll->listCount; ++i )
+ {
+ const ragdollelement_t& element = pRagdoll->list[ i ];
+
+ // during restore if a model has changed since the file was saved, this could be NULL
+ if ( !element.pObject )
+ return false;
+
+ int const boneIndex = pRagdoll->boneIndex[ i ];
+ if ( boneIndex < 0 )
+ {
+ AssertMsg( 0, "Replay: No mapping for ragdoll bone\n" );
+ return false;
+ }
+
+ // Get global transform and put it into the bone cache
+ element.pObject->GetPositionMatrix( &pBones[ boneIndex ] );
+
+ // Ensure a fixed translation from the parent (no stretching)
+ if ( element.parentIndex >= 0 && !pRagdoll->allowStretch )
+ {
+ int parentIndex = pRagdoll->boneIndex[ element.parentIndex ];
+
+#ifdef _DEBUG
+ // Make sure we computed the parent already
+ Assert( vBonesComputed.IsBitSet(parentIndex) );
+#endif
+
+ // overwrite the position from physics to force rigid attachment
+ // NOTE: On the client we actually override this with the proper parent bone in each LOD
+ Vector out;
+ VectorTransform( element.originParentSpace, pBones[ parentIndex ], out );
+ MatrixSetColumn( out, 3, pBones[ boneIndex ] );
+
+ MatrixInvert( pBones[ parentIndex ], inverted );
+ }
+ else if ( element.parentIndex == - 1 )
+ {
+ // Decompose into parent space
+ MatrixInvert( parentTransform, inverted );
+ }
+
+#ifdef _DEBUG
+ vBonesComputed.Set( boneIndex, true );
+#endif
+
+ // Compute local transform and put into 'output'
+ ConcatTransforms( inverted, pBones[ boneIndex ], output );
+
+ // Cache as Euler/position
+ MatrixAngles( output, pAngles[ i ], pPositions[ i ] );
+ }
+ return true;
+}
+
+void RagdollSimulationData_t::Record()
+{
+ Assert( m_pEntity->m_pRagdoll );
+
+ // Allocate a frame
+ RagdollSimulationFrame_t* pNewFrame = RagdollSimulationFrame_t::Alloc( m_nNumBones );
+ if ( !pNewFrame )
+ return;
+
+ // Set the current tick
+ pNewFrame->nTick = GetServerTickCount();
+
+ // Add new frame to list of frames
+ m_lstFrames.AddToTail( pNewFrame );
+
+ // Compute parent transform
+ matrix3x4_t parentTransform;
+ Vector vRootPosition = m_pEntity->GetRenderOrigin();
+ QAngle angRootAngles = m_pEntity->GetRenderAngles();
+ AngleMatrix( angRootAngles, vRootPosition, parentTransform );
+
+// debugoverlay->AddCoordFrameOverlay( parentTransform, 100 );
+
+ // Cache off root position/orientation
+ pNewFrame->vRootPosition = vRootPosition;
+ pNewFrame->angRootAngles = angRootAngles;
+
+ // Compute actual ragdoll bones
+ matrix3x4_t* pBones = gs_BoneCache;
+ _ComputeRagdollBones( m_pEntity->m_pRagdoll->GetRagdoll(), parentTransform, pBones, pNewFrame->pPositions, pNewFrame->pAngles );
+
+ // Draw bones
+ if ( replay_ragdoll_dbg.GetBool() )
+ {
+ DrawBones( pBones, m_pEntity->m_pRagdoll->RagdollBoneCount(), m_pEntity->m_pRagdoll->GetRagdoll(), 255, 0, 0, m_pEntity );
+ }
+}
+
+//--------------------------------------------------------------------------------
+
+CReplayRagdollRecorder::CReplayRagdollRecorder()
+: m_bIsRecording(false)
+{
+}
+
+CReplayRagdollRecorder::~CReplayRagdollRecorder()
+{
+}
+
+/*static*/ CReplayRagdollRecorder& CReplayRagdollRecorder::Instance()
+{
+ static CReplayRagdollRecorder s_instance;
+ return s_instance;
+}
+
+void CReplayRagdollRecorder::Init()
+{
+ Assert( !m_bIsRecording );
+ m_bIsRecording = true;
+ gs_nBytesAllocated = 0;
+}
+
+void CReplayRagdollRecorder::Shutdown()
+{
+ if ( !m_bIsRecording )
+ return;
+
+ m_lstRagdolls.PurgeAndDeleteElements();
+ gs_nBytesAllocated = 0;
+
+ // RemoveAll() purges, and there is no UnlinkAll() - is there an easier way to do this?
+ Iterator_t i = m_lstRagdollsToRecord.Head();
+ while ( i != m_lstRagdollsToRecord.InvalidIndex() )
+ {
+ m_lstRagdollsToRecord.Unlink( i );
+ i = m_lstRagdollsToRecord.Head();
+ }
+
+ Assert( m_bIsRecording );
+ m_bIsRecording = false;
+}
+
+void CReplayRagdollRecorder::AddEntry( C_BaseAnimating* pEntity, int nStartTick, int nNumBones )
+{
+ DevMsg( "Replay: Processing Ragdoll at time %d\n", nStartTick );
+
+ Assert( pEntity );
+ RagdollSimulationData_t* pNewEntry = new RagdollSimulationData_t( pEntity, nStartTick, nNumBones );
+ gs_nBytesAllocated += sizeof( RagdollSimulationData_t );
+ m_lstRagdolls.AddToTail( pNewEntry );
+
+ // Also add to list of ragdolls to record
+ m_lstRagdollsToRecord.AddToTail( pNewEntry );
+}
+
+void CReplayRagdollRecorder::StopRecordingRagdoll( C_BaseAnimating* pEntity )
+{
+ Assert( pEntity );
+
+ // Find the entry in the recording list
+ Iterator_t nIndex;
+ if ( !FindEntryInRecordingList( pEntity, nIndex ) )
+ return;
+
+ StopRecordingRagdollAtIndex( nIndex );
+}
+
+void CReplayRagdollRecorder::StopRecordingRagdollAtIndex( Iterator_t nIndex )
+{
+ // No longer recording - compute duration
+ RagdollSimulationData_t* pData = m_lstRagdollsToRecord[ nIndex ];
+
+ // Does duration need to be set?
+ if ( pData->m_nDuration < 0 )
+ {
+ pData->m_nDuration = GetServerTickCount() - pData->m_nStartTick; Assert( pData->m_nDuration > 0 );
+ }
+
+ // Remove it from the recording list
+ m_lstRagdollsToRecord.Unlink( nIndex );
+}
+
+void CReplayRagdollRecorder::StopRecordingSleepingRagdolls()
+{
+ Iterator_t i = m_lstRagdollsToRecord.Head();
+ while ( i != m_lstRagdollsToRecord.InvalidIndex() )
+ {
+ if ( RagdollIsAsleep( *m_lstRagdollsToRecord[ i ]->m_pEntity->m_pRagdoll->GetRagdoll() ) )
+ {
+ DevMsg( "entity %d: Removing sleeping ragdoll\n", m_lstRagdollsToRecord[ i ]->m_nEntityIndex );
+
+ StopRecordingRagdollAtIndex( i );
+ i = m_lstRagdollsToRecord.Head();
+ }
+ else
+ {
+ i = m_lstRagdollsToRecord.Next( i );
+ }
+ }
+}
+
+bool CReplayRagdollRecorder::FindEntryInRecordingList( C_BaseAnimating* pEntity,
+ CReplayRagdollRecorder::Iterator_t& nOutIndex )
+{
+ // Find the entry
+ FOR_EACH_LL( m_lstRagdollsToRecord, i )
+ {
+ if ( m_lstRagdollsToRecord[ i ]->m_pEntity == pEntity )
+ {
+ nOutIndex = i;
+ return true;
+ }
+ }
+
+ nOutIndex = m_lstRagdollsToRecord.InvalidIndex();
+ return false;
+}
+
+void CReplayRagdollRecorder::Record()
+{
+ static ConVar* pReplayEnable = NULL;
+ static bool bLookedForConvar = false;
+ if ( bLookedForConvar )
+ {
+ pReplayEnable = (ConVar*)cvar->FindVar( "replay_enable" );
+ bLookedForConvar = true;
+ }
+ if ( !pReplayEnable || !pReplayEnable->GetInt() )
+ return;
+
+ if ( !replay_cache_client_ragdolls.GetInt() )
+ return;
+
+ FOR_EACH_LL( m_lstRagdollsToRecord, i )
+ {
+ Assert( m_lstRagdollsToRecord[ i ]->m_pEntity->IsRagdoll() );
+ m_lstRagdollsToRecord[ i ]->Record();
+ }
+}
+
+void CReplayRagdollRecorder::Think()
+{
+ if ( !IsRecording() )
+ return;
+
+ StopRecordingSleepingRagdolls();
+ Record();
+
+ PrintDebug();
+}
+
+void CReplayRagdollRecorder::PrintDebug()
+{
+ if ( !replay_ragdoll_dbg.GetInt() )
+ return;
+
+ int nLine = 0;
+
+ // Print memory usage
+ engine->Con_NPrintf( nLine++, "ragdolls: %.2f MB", gs_nBytesAllocated / 1048576.0f );
+
+ // Print server time
+ engine->Con_NPrintf( nLine++, "server time: %d", GetServerTickCount() );
+
+ ++nLine; // Blank line
+
+ // Print info about each ragdoll
+ FOR_EACH_LL( m_lstRagdolls, i )
+ {
+ engine->Con_NPrintf( nLine++, "entity %d: start time=%d duration=%d num bones=%d", m_lstRagdolls[i]->m_nEntityIndex, m_lstRagdolls[i]->m_nStartTick, m_lstRagdolls[i]->m_nDuration, m_lstRagdolls[i]->m_nNumBones );
+ }
+}
+
+void CReplayRagdollRecorder::CleanupStartupTicksAndDurations( int nStartTick )
+{
+ FOR_EACH_LL( m_lstRagdolls, i )
+ {
+ RagdollSimulationData_t* pRagdollData = m_lstRagdolls[ i ];
+
+ // Offset start tick with start tick, sent over from server
+ pRagdollData->m_nStartTick -= nStartTick; Assert( pRagdollData->m_nStartTick >= 0 );
+
+ // Setup duration
+ pRagdollData->m_nDuration = GetServerTickCount() - nStartTick; Assert( pRagdollData->m_nDuration > 0 );
+
+ // Go through all frames and subtract the start tick
+ FOR_EACH_LL( pRagdollData->m_lstFrames, j )
+ {
+ pRagdollData->m_lstFrames[ j ]->nTick -= nStartTick;
+ }
+ }
+}
+
+BEGIN_DMXELEMENT_UNPACK( RagdollSimulationData_t )
+ DMXELEMENT_UNPACK_FIELD( "nEntityIndex", "0", int, m_nEntityIndex )
+ DMXELEMENT_UNPACK_FIELD( "nStartTick", "0", int, m_nStartTick )
+ DMXELEMENT_UNPACK_FIELD( "nDuration", "0", int, m_nDuration )
+ DMXELEMENT_UNPACK_FIELD( "nNumBones", "0", int, m_nNumBones )
+END_DMXELEMENT_UNPACK( RagdollSimulationData_t, s_RagdollSimulationDataUnpack )
+
+bool CReplayRagdollRecorder::DumpRagdollsToDisk( char const* pFilename ) const
+{
+ MEM_ALLOC_CREDIT();
+ DECLARE_DMX_CONTEXT();
+
+ CDmxElement* pSimulations = CreateDmxElement( "Simulations" );
+ CDmxElementModifyScope modify( pSimulations );
+
+ int const nNumRagdolls = m_lstRagdolls.Count();
+
+ pSimulations->SetValue( "iNumRagdolls", nNumRagdolls );
+
+ CDmxAttribute* pRagdolls = pSimulations->AddAttribute( "ragdolls" );
+ CUtlVector< CDmxElement* >& ragdolls = pRagdolls->GetArrayForEdit< CDmxElement* >();
+
+ modify.Release();
+
+ char name[32];
+
+ FOR_EACH_LL( m_lstRagdolls, i )
+ {
+ RagdollSimulationData_t const* pData = m_lstRagdolls[ i ];
+
+ // Make sure we've setup all durations properly
+ Assert( pData->m_nDuration >= 0 );
+
+ CDmxElement* pRagdoll = CreateDmxElement( "ragdoll" );
+ ragdolls.AddToTail( pRagdoll );
+
+ V_snprintf( name, sizeof(name), "ragdoll %d", i );
+ pRagdoll->SetValue( "name", name );
+
+ CDmxElementModifyScope modifyClass( pRagdoll );
+
+ pRagdoll->AddAttributesFromStructure( pData, s_RagdollSimulationDataUnpack );
+
+ CDmxAttribute* pFrames = pRagdoll->AddAttribute( "frames" );
+ CUtlVector< CDmxElement* >& frames = pFrames->GetArrayForEdit< CDmxElement* >();
+
+ FOR_EACH_LL( pData->m_lstFrames, j )
+ {
+ CDmxElement* pFrame = CreateDmxElement( "frame" );
+ frames.AddToTail( pFrame );
+
+ V_snprintf( name, sizeof(name), "frame %d", j );
+ pFrame->SetValue( "name", name );
+
+ // Store tick
+ pFrame->SetValue( "tick", pData->m_lstFrames[ j ]->nTick );
+
+ // Store root pos/orientation
+ pFrame->SetValue( "root_pos" , pData->m_lstFrames[ j ]->vRootPosition );
+ pFrame->SetValue( "root_angles", pData->m_lstFrames[ j ]->angRootAngles );
+
+ for ( int k = 0; k < pData->m_nNumBones; ++k )
+ {
+ CDmxAttribute* pPositions = pFrame->AddAttribute( "positions" );
+ CUtlVector< Vector >& positions = pPositions->GetArrayForEdit< Vector >();
+
+ CDmxAttribute* pAngles = pFrame->AddAttribute( "angles" );
+ CUtlVector< QAngle >& angles = pAngles->GetArrayForEdit< QAngle >();
+
+ positions.AddToTail( pData->m_lstFrames[ j ]->pPositions[ k ] );
+ angles.AddToTail( pData->m_lstFrames[ j ]->pAngles[ k ] );
+ }
+ }
+ }
+
+ {
+ MEM_ALLOC_CREDIT();
+ CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
+ if ( !SerializeDMX( buf, pSimulations, pFilename ) )
+ {
+ Warning( "Replay: Failed to write ragdoll cache, %s.\n", pFilename );
+ return false;
+ }
+
+ // Write the file
+ filesystem->WriteFile( pFilename, "MOD", buf );
+ }
+
+ CleanupDMX( pSimulations );
+
+ Msg( "Replay: Cached ragdoll data.\n" );
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------
+
+CReplayRagdollCache::CReplayRagdollCache()
+: m_bInit( false )
+{
+}
+
+/*static*/ CReplayRagdollCache& CReplayRagdollCache::Instance()
+{
+ static CReplayRagdollCache s_instance;
+ return s_instance;
+}
+
+bool CReplayRagdollCache::Init( char const* pFilename )
+{
+ Assert( !m_bInit );
+
+ // Make sure valid filename
+ if ( !pFilename || pFilename[0] == 0 )
+ return false;
+
+ DECLARE_DMX_CONTEXT();
+
+ // Attempt to read from disk
+ CDmxElement* pRagdolls = NULL;
+ if ( !UnserializeDMX( pFilename, "MOD", true, &pRagdolls ) )
+// if ( !UnserializeDMX( pFilename, "GAME", false, &pRagdolls ) )
+ return false;
+
+ CUtlVector< CDmxElement* > const& ragdolls = pRagdolls->GetArray< CDmxElement* >( "ragdolls" );
+ for ( int i = 0; i < ragdolls.Count(); ++i )
+ {
+ CDmxElement* pCurRagdollInput = ragdolls[ i ];
+
+ // Create a new ragdoll entry and add to list
+ RagdollSimulationData_t* pNewSimData = new RagdollSimulationData_t();
+ m_lstRagdolls.AddToTail( pNewSimData );
+
+ // Read
+ pCurRagdollInput->UnpackIntoStructure( pNewSimData, sizeof( *pNewSimData ), s_RagdollSimulationDataUnpack );
+
+ // NOTE: Entity ptr doesn't get linked up here because it doesn't necessarily exist at this point
+
+ // Read frames
+ CUtlVector< CDmxElement* > const& frames = pCurRagdollInput->GetArray< CDmxElement* >( "frames" );
+ for ( int j = 0; j < frames.Count(); ++j )
+ {
+ CDmxElement* pCurFrameInput = frames[ j ];
+
+ // Create a new frame and add it to list of frames
+ RagdollSimulationFrame_t* pNewFrame = RagdollSimulationFrame_t::Alloc( pNewSimData->m_nNumBones );
+ pNewSimData->m_lstFrames.AddToTail( pNewFrame );
+
+ // Read tick
+ pNewFrame->nTick = pCurFrameInput->GetValue( "tick", -1 ); Assert( pNewFrame->nTick != -1 );
+
+ // Read root pos/orientation
+ pNewFrame->vRootPosition = pCurFrameInput->GetValue( "root_pos" , vec3_origin );
+ pNewFrame->angRootAngles = pCurFrameInput->GetValue( "root_angles", vec3_angle );
+
+ CUtlVector< Vector > const& positions = pCurFrameInput->GetArray< Vector >( "positions" );
+ CUtlVector< QAngle > const& angles = pCurFrameInput->GetArray< QAngle >( "angles" );
+
+ for ( int k = 0; k < pNewSimData->m_nNumBones; ++k )
+ {
+ pNewFrame->pPositions[ k ] = positions[ k ];
+ pNewFrame->pAngles[ k ] = angles[ k ];
+ }
+ }
+ }
+
+
+ // Cleanup
+ CleanupDMX( pRagdolls );
+
+ m_bInit = true;
+
+ return true;
+}
+
+void CReplayRagdollCache::Shutdown()
+{
+ if ( !m_bInit )
+ return;
+
+ m_lstRagdolls.PurgeAndDeleteElements();
+ m_bInit = false;
+}
+
+ConVar replay_ragdoll_blending( "replay_ragdoll_blending", "1", FCVAR_DEVELOPMENTONLY );
+ConVar replay_ragdoll_tickoffset( "replay_ragdoll_tickoffset", "0", FCVAR_DEVELOPMENTONLY );
+
+bool CReplayRagdollCache::GetFrame( C_BaseAnimating* pEntity, int nTick, bool* pBoneSimulated, CBoneAccessor* pBoneAccessor ) const
+{
+ nTick += replay_ragdoll_tickoffset.GetInt();
+
+ Assert( pEntity );
+ Assert( pBoneSimulated );
+ Assert( pEntity->m_pRagdoll );
+
+ // Find ragdoll for the given entity - will return NULL if nTick is out of the entry's time window
+ const RagdollSimulationData_t* pRagdollEntry = FindRagdollEntry( pEntity, nTick );
+ if ( !pRagdollEntry )
+ return false;
+
+ // Find frame for the given tick
+ RagdollSimulationFrame_t* pFrame;
+ RagdollSimulationFrame_t* pNextFrame;
+ if ( !FindFrame( pFrame, pNextFrame, pRagdollEntry, nTick ) )
+ return false;
+
+ // Compute root transform
+ matrix3x4_t rootTransform;
+ float flInterpAmount = gpGlobals->interpolation_amount;
+ if ( pNextFrame )
+ {
+ AngleMatrix(
+ (const QAngle &)Lerp( flInterpAmount, pFrame->angRootAngles, pNextFrame->angRootAngles ), // Actually does a slerp
+ Lerp( flInterpAmount, pFrame->vRootPosition, pNextFrame->vRootPosition ),
+ rootTransform
+ );
+ }
+ else
+ {
+ AngleMatrix( pFrame->angRootAngles, pFrame->vRootPosition, rootTransform );
+ }
+
+ // Compute each bone
+ ragdoll_t* pRagdoll = pEntity->m_pRagdoll->GetRagdoll(); Assert( pRagdoll );
+ for ( int k = 0; k < pRagdoll->listCount; ++k )
+ {
+ int objectIndex = k;
+ const ragdollelement_t& element = pRagdoll->list[ objectIndex ];
+
+ int const boneIndex = pRagdoll->boneIndex[ objectIndex ]; Assert( boneIndex >= 0 );
+
+ // Compute blended transform if possible
+ matrix3x4_t localTransform;
+ if ( pNextFrame && replay_ragdoll_blending.GetInt() )
+ {
+ // Get blended Eular angles - NOTE: The Lerp() here actually calls Lerp<QAngle>() which converts to quats and back
+ float flInterpAmount = gpGlobals->interpolation_amount; Assert( flInterpAmount >= 0.0f && flInterpAmount <= 1.0f );
+ AngleMatrix(
+ (const QAngle &)Lerp( flInterpAmount, pFrame->pAngles [ objectIndex ], pNextFrame->pAngles [ objectIndex ] ),
+ Lerp( flInterpAmount, pFrame->pPositions[ objectIndex ], pNextFrame->pPositions[ objectIndex ] ),
+ localTransform
+ );
+ }
+ else
+ {
+ // Last frame
+ AngleMatrix( pFrame->pAngles[ objectIndex ], pFrame->pPositions[ objectIndex ], localTransform );
+ }
+
+ matrix3x4_t& boneMatrix = pBoneAccessor->GetBoneForWrite( boneIndex );
+
+ if ( element.parentIndex < 0 )
+ {
+ ConcatTransforms( rootTransform, localTransform, boneMatrix );
+ }
+ else
+ {
+ int parentBoneIndex = pRagdoll->boneIndex[ element.parentIndex ]; Assert( parentBoneIndex >= 0 );
+ Assert( pBoneSimulated[ parentBoneIndex ] );
+ matrix3x4_t const& parentMatrix = pBoneAccessor->GetBone( parentBoneIndex );
+ ConcatTransforms( parentMatrix, localTransform, boneMatrix );
+ }
+
+ // Simulated this bone
+ pBoneSimulated[ boneIndex ] = true;
+ }
+
+ if ( replay_ragdoll_dbg.GetBool() )
+ {
+ DrawBones( pBoneAccessor->GetBoneArrayForWrite(), pRagdollEntry->m_nNumBones, pRagdoll, 0, 0, 255, pEntity );
+ }
+
+ return true;
+}
+
+RagdollSimulationData_t* CReplayRagdollCache::FindRagdollEntry( C_BaseAnimating* pEntity, int nTick )
+{
+ Assert( pEntity );
+
+ int const nEntIndex = pEntity->entindex();
+
+ FOR_EACH_LL( m_lstRagdolls, i )
+ {
+ RagdollSimulationData_t* pRagdollData = m_lstRagdolls[ i ];
+
+ // If not the right entity or the tick is out range, continue.
+ if ( pRagdollData->m_nEntityIndex != nEntIndex )
+ continue;
+
+ // We've got the ragdoll, but only return it if nTick is in the window
+ if ( nTick < pRagdollData->m_nStartTick ||
+ nTick > pRagdollData->m_nStartTick + pRagdollData->m_nDuration )
+ return NULL;
+
+ return pRagdollData;
+ }
+
+ return NULL;
+}
+
+bool CReplayRagdollCache::FindFrame( RagdollSimulationFrame_t*& pFrameOut, RagdollSimulationFrame_t*& pNextFrameOut,
+ const RagdollSimulationData_t* pRagdollEntry, int nTick )
+{
+ // Look for the appropriate frame
+ FOR_EACH_LL( pRagdollEntry->m_lstFrames, j )
+ {
+ RagdollSimulationFrame_t* pFrame = pRagdollEntry->m_lstFrames[ j ];
+
+ // Get next frame if possible
+ int const nNext = pRagdollEntry->m_lstFrames.Next( j );
+ RagdollSimulationFrame_t* pNextFrame =
+ nNext == pRagdollEntry->m_lstFrames.InvalidIndex() ? NULL : pRagdollEntry->m_lstFrames[ nNext ];
+
+ // Use this frame?
+ if ( nTick >= pFrame->nTick &&
+ ( (pNextFrame && nTick <= pNextFrame->nTick) || !pNextFrame ) ) // Use the last frame if the tick is past the range of frames -
+ { // this is the "sleeping" ragdoll frame
+ pFrameOut = pFrame;
+ pNextFrameOut = pNextFrame;
+
+ return true;
+ }
+ }
+
+ pFrameOut = NULL;
+ pNextFrameOut = NULL;
+
+ return false;
+}
+
+void CReplayRagdollCache::Think()
+{
+ // TODO: Add IsPlayingReplayDemo() to engine interface
+ /*
+ engine->Con_NPrintf( 8, "time: %d", engine->GetDemoPlaybackTick() );
+ FOR_EACH_LL( m_lstRagdolls, i )
+ {
+ engine->Con_NPrintf( 10 + i, "entity %d: start time=%d duration=%d num bones=%d", m_lstRagdolls[i]->m_nEntityIndex, m_lstRagdolls[i]->m_nStartTick, m_lstRagdolls[i]->m_nDuration, m_lstRagdolls[i]->m_nNumBones );
+ }
+ */
+}
+
+//--------------------------------------------------------------------------------
+
+bool Replay_CacheRagdolls( const char* pFilename, int nStartTick )
+{
+ CReplayRagdollRecorder::Instance().CleanupStartupTicksAndDurations( nStartTick );
+ return CReplayRagdollRecorder::Instance().DumpRagdollsToDisk( pFilename );
+}
+
+#endif
\ No newline at end of file diff --git a/mp/src/game/client/replay/replay_ragdoll.h b/mp/src/game/client/replay/replay_ragdoll.h new file mode 100644 index 00000000..a6da8e39 --- /dev/null +++ b/mp/src/game/client/replay/replay_ragdoll.h @@ -0,0 +1,150 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//----------------------------------------------------------------------------------------
+
+#ifndef REPLAY_RAGDOLL_H
+#define REPLAY_RAGDOLL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+//--------------------------------------------------------------------------------
+
+class C_BaseAnimating;
+
+//--------------------------------------------------------------------------------
+
+struct RagdollSimulationFrame_t
+{
+ RagdollSimulationFrame_t() : pPositions(NULL), pAngles(NULL), nTick(-1) {}
+
+ static RagdollSimulationFrame_t* Alloc( int nNumBones );
+
+ int nTick;
+ Vector* pPositions;
+ QAngle* pAngles;
+ Vector vRootPosition;
+ QAngle angRootAngles;
+};
+
+//--------------------------------------------------------------------------------
+
+struct RagdollSimulationData_t
+{
+ RagdollSimulationData_t( C_BaseAnimating* pEntity = NULL, int nStartTick = 0, int nNumBones = 0 );
+
+ void Record();
+
+ int m_nEntityIndex;
+ int m_nStartTick;
+ int m_nDuration;
+ int m_nNumBones;
+
+ typedef unsigned short Iterator_t;
+
+ CUtlLinkedList< RagdollSimulationFrame_t*, Iterator_t > m_lstFrames;
+ C_BaseAnimating* m_pEntity;
+};
+
+//--------------------------------------------------------------------------------
+
+// TODO: Refactor this into an interface and hide implementation in cpp file
+
+class CReplayRagdollRecorder
+{
+private:
+ CReplayRagdollRecorder();
+ ~CReplayRagdollRecorder();
+
+public:
+ static CReplayRagdollRecorder& Instance();
+
+ void Init();
+ void Shutdown();
+
+ void Think();
+
+ void AddEntry( C_BaseAnimating* pEntity, int nStartTick, int nNumBones );
+ void StopRecordingRagdoll( C_BaseAnimating* pEntity );
+
+ void CleanupStartupTicksAndDurations( int nStartTick );
+ bool DumpRagdollsToDisk( char const* pszFilename ) const;
+
+ bool IsRecording() const { return m_bIsRecording; }
+
+private:
+ typedef unsigned short Iterator_t;
+
+ void StopRecordingRagdollAtIndex( Iterator_t nIndex );
+
+ void StopRecordingSleepingRagdolls();
+ void Record();
+
+ bool FindEntryInRecordingList( C_BaseAnimating* pEntity, Iterator_t& nOutIndex );
+
+ void PrintDebug();
+
+ CUtlLinkedList< RagdollSimulationData_t*, Iterator_t > m_lstRagdolls;
+ CUtlLinkedList< RagdollSimulationData_t*, Iterator_t > m_lstRagdollsToRecord; // Contains some of the elements from m_lstRagdolls -
+ // the ones which are still recording
+ bool m_bIsRecording;
+};
+
+//--------------------------------------------------------------------------------
+
+class CReplayRagdollCache
+{
+private:
+ CReplayRagdollCache();
+
+public:
+ static CReplayRagdollCache& Instance();
+
+ bool Init( char const* pszFilename );
+ void Shutdown();
+
+ void Think();
+
+ bool IsInitialized() const { return m_bInit; }
+
+ //
+ // Returns false is no frame exists for the given entity at the given tick.
+ // Otherwise, returns a
+ //
+ bool GetFrame( C_BaseAnimating* pEntity, int nTick, bool* pBoneSimulated, CBoneAccessor* pBoneAccessor ) const;
+
+private:
+ RagdollSimulationData_t* FindRagdollEntry( C_BaseAnimating* pEntity, int nTick );
+ const RagdollSimulationData_t* FindRagdollEntry( C_BaseAnimating* pEntity, int nTick ) const;
+
+ bool FindFrame( RagdollSimulationFrame_t*& pFrameOut, RagdollSimulationFrame_t*& pNextFrameOut,
+ const RagdollSimulationData_t* pRagdollEntry, int nTick );
+ bool FindFrame( RagdollSimulationFrame_t*& pFrameOut, RagdollSimulationFrame_t*& pNextFrameOut,
+ const RagdollSimulationData_t* pRagdollEntry, int nTick ) const;
+
+ typedef unsigned short Iterator_t;
+ bool m_bInit;
+
+ CUtlLinkedList< RagdollSimulationData_t*, Iterator_t > m_lstRagdolls;
+};
+
+//--------------------------------------------------------------------------------
+
+bool Replay_CacheRagdolls( const char* pFilename, int nStartTick );
+
+//--------------------------------------------------------------------------------
+
+inline const RagdollSimulationData_t* CReplayRagdollCache::FindRagdollEntry( C_BaseAnimating* pEntity, int nTick ) const
+{
+ return const_cast< CReplayRagdollCache* >( this )->FindRagdollEntry( pEntity, nTick );
+}
+
+inline bool CReplayRagdollCache::FindFrame( RagdollSimulationFrame_t*& pFrameOut, RagdollSimulationFrame_t*& pNextFrameOut,
+ const RagdollSimulationData_t* pRagdollEntry, int nTick ) const
+{
+ return const_cast< CReplayRagdollCache* >( this )->FindFrame( pFrameOut, pNextFrameOut, pRagdollEntry, nTick );
+}
+
+//--------------------------------------------------------------------------------
+
+#endif // REPLAY_RAGDOLL_H
diff --git a/mp/src/game/client/replay/replay_screenshot.cpp b/mp/src/game/client/replay/replay_screenshot.cpp new file mode 100644 index 00000000..5dd8f3e2 --- /dev/null +++ b/mp/src/game/client/replay/replay_screenshot.cpp @@ -0,0 +1,246 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//===========================================================================//
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "materialsystem/itexture.h"
+#include "materialsystem/imaterialsystem.h"
+#include "replay/iclientreplaycontext.h"
+#include "replay/ireplayscreenshotmanager.h"
+#include "replay_screenshot.h"
+#include "view.h"
+#include "filesystem.h"
+#include "view_shared.h"
+#include "iviewrender.h"
+#include "fasttimer.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//-----------------------------------------------------------------------------
+
+extern IClientReplayContext *g_pClientReplayContext;
+ITexture *CReplayScreenshotTaker::m_pScreenshotTarget;
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CReplayScreenshotTaker::CReplayScreenshotTaker( IViewRender *pViewRender, CViewSetup &view )
+: m_pViewRender( pViewRender ),
+ m_View( view )
+{
+ m_pUnpaddedPixels = NULL;
+ m_pPaddedPixels = NULL;
+ m_pVTFPixels = NULL;
+
+ m_pVTFTexture = NULL;
+
+ m_pBuffer = NULL;
+
+ if ( !m_pScreenshotTarget )
+ return;
+
+ m_aPaddedDims[ 0 ] = m_pScreenshotTarget->GetActualWidth();
+ m_aPaddedDims[ 1 ] = m_pScreenshotTarget->GetActualHeight();
+
+ g_pClientReplayContext->GetScreenshotManager()->GetUnpaddedScreenshotSize( m_aUnpaddedDims[ 0 ], m_aUnpaddedDims[ 1 ] );
+
+ // Calculate sizes
+ int nUnpaddedSize = 3 * m_aUnpaddedDims[ 0 ] * m_aUnpaddedDims[ 1 ];
+ int nPaddedSize = 3 * m_aPaddedDims[ 0 ] * m_aPaddedDims[ 1 ];
+
+ // Allocate for padded & unpadded pixel data
+ m_pUnpaddedPixels = new uint8[ nUnpaddedSize ];
+ m_pPaddedPixels = new uint8[ nPaddedSize ];
+
+ // White out the entire padded image
+ V_memset( m_pPaddedPixels, 255, nPaddedSize );
+
+ // Create the VTF
+#ifndef _X360
+ IVTFTexture *pVTFTexture = CreateVTFTexture();
+ const int nFlags = TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD | TEXTUREFLAGS_SRGB;
+ if ( !pVTFTexture->Init( m_aPaddedDims[ 0 ], m_aPaddedDims[ 1 ], 1, IMAGE_FORMAT_RGB888, nFlags, 1, 1 ) )
+ return;
+
+ m_pVTFTexture = pVTFTexture;
+#else
+ m_pVTFTexture = NULL;
+#endif // _X360
+
+ // Allocate pixels for the output buffer
+ int nVTFSize = 1024 + ( 3 * m_aPaddedDims[ 0 ] * m_aPaddedDims[ 1 ] );
+ m_pVTFPixels = new uint8[ nVTFSize ];
+ m_pBuffer = new CUtlBuffer( m_pVTFPixels, nVTFSize );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CReplayScreenshotTaker::~CReplayScreenshotTaker()
+{
+ delete [] m_pUnpaddedPixels;
+ delete [] m_pPaddedPixels;
+ delete [] m_pVTFPixels;
+
+#ifndef _X360
+ DestroyVTFTexture( m_pVTFTexture );
+#endif // _X360
+
+ delete m_pBuffer;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: takes a screenshot for the replay system
+//-----------------------------------------------------------------------------
+void CReplayScreenshotTaker::TakeScreenshot( WriteReplayScreenshotParams_t ¶ms )
+{
+ if ( !m_pViewRender )
+ return;
+
+ CFastTimer timer;
+ ConVarRef replay_debug( "replay_debug" );
+ bool bDbg = replay_debug.IsValid() && replay_debug.GetBool();
+
+ int width = params.m_nWidth;
+ int height = params.m_nHeight;
+
+ CMatRenderContextPtr pRenderContext( materials );
+ pRenderContext->MatrixMode( MATERIAL_PROJECTION );
+ pRenderContext->PushMatrix();
+
+ pRenderContext->MatrixMode( MATERIAL_VIEW );
+ pRenderContext->PushMatrix();
+
+ extern bool g_bRenderingScreenshot;
+ g_bRenderingScreenshot = true;
+
+ // Push back buffer on the stack with small viewport
+ pRenderContext->PushRenderTargetAndViewport( m_pScreenshotTarget, 0, 0, width, height );
+
+ // setup the view to render
+ CViewSetup viewSetup = m_View;
+ viewSetup.x = 0;
+ viewSetup.y = 0;
+ viewSetup.width = width;
+ viewSetup.height = height;
+ viewSetup.fov = ScaleFOVByWidthRatio( m_View.fov, ( (float)width / (float)height ) / ( 4.0f / 3.0f ) );
+ viewSetup.m_bRenderToSubrectOfLargerScreen = true;
+
+ // Setup view origin/angles
+ if ( params.m_pOrigin )
+ {
+ viewSetup.origin = *params.m_pOrigin;
+ }
+ if ( params.m_pAngles )
+ {
+ viewSetup.angles = *params.m_pAngles;
+ }
+
+ timer.Start();
+
+ // draw out the scene - don't draw the HUD or the viewmodel
+ m_pViewRender->RenderView( viewSetup, VIEW_CLEAR_DEPTH | VIEW_CLEAR_COLOR, 0 );
+
+ timer.End();
+ if ( bDbg ) Warning( "Screenshot RenderView(): %.4f s\n", timer.GetDuration().GetSeconds() );
+
+ timer.Start();
+
+ // Get Bits from the material system
+ pRenderContext->ReadPixels( 0, 0, width, height, m_pUnpaddedPixels, IMAGE_FORMAT_RGB888 );
+
+ timer.End();
+ if ( bDbg ) Warning( "Screenshot ReadPixels(): %.4f s\n", timer.GetDuration().GetSeconds() );
+
+ // Some stuff to be setup dependent on padded vs. not padded
+ int nSrcWidth, nSrcHeight;
+ unsigned char *pSrcImage;
+
+ // Setup dimensions as needed
+ int nPaddedWidth = m_aPaddedDims[0];
+ int nPaddedHeight = m_aPaddedDims[1];
+
+ // Allocate
+ unsigned char *pUnpaddedImage = m_pUnpaddedPixels;
+ unsigned char *pPaddedImage = m_pPaddedPixels;
+
+ timer.Start();
+ // Copy over each row individually
+ for ( int nRow = 0; nRow < height; ++nRow )
+ {
+ unsigned char *pDst = pPaddedImage + 3 * ( nRow * nPaddedWidth );
+ const unsigned char *pSrc = pUnpaddedImage + 3 * ( nRow * width );
+ V_memcpy( pDst, pSrc, 3 * width );
+ }
+ timer.End();
+ if ( bDbg ) Warning( "Screenshot copy image: %.4f s\n", timer.GetDuration().GetSeconds() );
+
+ // Setup source data
+ nSrcWidth = nPaddedWidth;
+ nSrcHeight = nPaddedHeight;
+ pSrcImage = pPaddedImage;
+
+ if ( !m_pVTFTexture )
+ return;
+
+ // Copy the image data over to the VTF
+ unsigned char *pDestBits = m_pVTFTexture->ImageData();
+ int nDstSize = nSrcWidth * nSrcHeight * 3;
+ V_memcpy( pDestBits, pSrcImage, nDstSize );
+
+ bool bWriteResult = true;
+
+ // Reset put
+ m_pBuffer->SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
+
+ timer.Start();
+ // Serialize to the buffer
+ bWriteResult = m_pVTFTexture->Serialize( *m_pBuffer );
+ timer.End();
+ if ( bDbg ) Warning( "Screenshot VTF->Serialize(): %.4f s\n", timer.GetDuration().GetSeconds() );
+
+ if ( !bWriteResult )
+ {
+ Warning( "Couldn't write Replay screenshot.\n" );
+ bWriteResult = false;
+
+ return;
+ }
+
+ // async write to disk (this will take ownership of the memory)
+ char szPathedFileName[_MAX_PATH];
+ Q_snprintf( szPathedFileName, sizeof(szPathedFileName), "//MOD/%s", params.m_pFilename );
+
+ timer.Start();
+ filesystem->AsyncWrite( szPathedFileName, m_pBuffer->Base(), m_pBuffer->TellPut(), false );
+ timer.End();
+ if ( bDbg ) Warning( "Screenshot AsyncWrite(): %.4f s\n", timer.GetDuration().GetSeconds() );
+
+ // restore our previous state
+ pRenderContext->PopRenderTargetAndViewport();
+
+ pRenderContext->MatrixMode( MATERIAL_PROJECTION );
+ pRenderContext->PopMatrix();
+
+ pRenderContext->MatrixMode( MATERIAL_VIEW );
+ pRenderContext->PopMatrix();
+
+ g_bRenderingScreenshot = false;
+}
+
+
+void CReplayScreenshotTaker::CreateRenderTarget( IMaterialSystem *pMaterialSystem )
+{
+ m_pScreenshotTarget = pMaterialSystem->CreateNamedRenderTargetTextureEx2( "rt_ReplayScreenshot", 0, 0, RT_SIZE_REPLAY_SCREENSHOT, IMAGE_FORMAT_RGB888, MATERIAL_RT_DEPTH_SEPARATE );
+ m_pScreenshotTarget->AddRef(); // we will leak this ref, but only at shutdown of the app, which will be cleaned up then
+}
+
+
+#endif
\ No newline at end of file diff --git a/mp/src/game/client/replay/replay_screenshot.h b/mp/src/game/client/replay/replay_screenshot.h new file mode 100644 index 00000000..0fa8ed2d --- /dev/null +++ b/mp/src/game/client/replay/replay_screenshot.h @@ -0,0 +1,54 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#if !defined( REPLAY_SCREENSHOT_H )
+#define REPLAY_SCREENSHOT_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+//-----------------------------------------------------------------------------
+
+#include "replay/screenshot.h"
+
+//-----------------------------------------------------------------------------
+
+class IViewRender;
+
+//-----------------------------------------------------------------------------
+
+//
+// Takes screenshots as VTF's for the replay system - allocates enough
+// memory up-front as opposed to every frame. Should be destroyed and recreated
+// any time the cvar "replay_screenshotresolution" changes OR the actual screen
+// resolution.
+//
+class CReplayScreenshotTaker
+{
+public:
+ CReplayScreenshotTaker( IViewRender *pViewRender, CViewSetup &view );
+ ~CReplayScreenshotTaker();
+
+ void TakeScreenshot( WriteReplayScreenshotParams_t ¶ms );
+
+ static void CreateRenderTarget( IMaterialSystem *pMaterialSystem );
+
+private:
+ IViewRender *m_pViewRender;
+ CViewSetup &m_View;
+ uint8 *m_pUnpaddedPixels;
+ uint8 *m_pPaddedPixels;
+ IVTFTexture *m_pVTFTexture;
+ uint8 *m_pVTFPixels;
+
+ int m_aUnpaddedDims[2]; // Width & height of m_pUnpaddedPixels
+ int m_aPaddedDims[2]; // Width & height of m_pPaddedPixels
+
+ CUtlBuffer *m_pBuffer;
+
+
+ static ITexture *m_pScreenshotTarget;
+};
+
+#endif // REPLAY_SCREENSHOT_H
diff --git a/mp/src/game/client/replay/replaycamera.cpp b/mp/src/game/client/replay/replaycamera.cpp new file mode 100644 index 00000000..dad2d649 --- /dev/null +++ b/mp/src/game/client/replay/replaycamera.cpp @@ -0,0 +1,936 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "replay/ireplaysystem.h"
+#include "replay/ienginereplay.h"
+#include "replay/ireplaymanager.h"
+#include "replay/replay.h"
+#include "replay/replaycamera.h"
+#include "cdll_client_int.h"
+#include "util_shared.h"
+#include "prediction.h"
+#include "movevars_shared.h"
+#include "in_buttons.h"
+#include "text_message.h"
+#include "vgui_controls/Controls.h"
+#include "vgui/ILocalize.h"
+#include "vguicenterprint.h"
+#include "game/client/iviewport.h"
+#include "vgui/IInput.h"
+#include <KeyValues.h>
+#include "iinput.h"
+#include "iclientmode.h"
+#include "ienginevgui.h"
+#include "vgui/IInput.h"
+#include "mathlib/noise.h"
+
+#ifdef CSTRIKE_DLL
+ #include "c_cs_player.h"
+#endif
+
+ConVar replay_editor_camera_length( "replay_editor_camera_length", "15", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "This is the camera length used to simulate camera shake in the replay editor. The larger this number, the more the actual position will change. It can also be set to negative values." );
+
+//ConVar spec_autodirector( "spec_autodirector", "1", FCVAR_CLIENTDLL | FCVAR_CLIENTCMD_CAN_EXECUTE, "Auto-director chooses best view modes while spectating" );
+extern ConVar spec_autodirector;
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#define CHASE_CAM_DISTANCE_MAX 96.0f
+#define WALL_OFFSET 6.0f
+#define DEFAULT_ROAMING_ACCEL 5.0f
+#define DEFAULT_ROAMING_SPEED 3.0f
+
+static Vector WALL_MIN(-WALL_OFFSET,-WALL_OFFSET,-WALL_OFFSET);
+static Vector WALL_MAX(WALL_OFFSET,WALL_OFFSET,WALL_OFFSET);
+
+//////////////////////////////////////////////////////////////////////
+// Construction/Destruction
+//////////////////////////////////////////////////////////////////////
+
+// converts all '\r' characters to '\n', so that the engine can deal with the properly
+// returns a pointer to str
+static wchar_t* ConvertCRtoNL( wchar_t *str )
+{
+ for ( wchar_t *ch = str; *ch != 0; ch++ )
+ if ( *ch == L'\r' )
+ *ch = L'\n';
+ return str;
+}
+
+static C_ReplayCamera s_ReplayCamera;
+
+C_ReplayCamera *ReplayCamera()
+{
+ return &s_ReplayCamera;
+}
+
+C_ReplayCamera::C_ReplayCamera()
+{
+ Reset();
+
+ m_nNumSpectators = 0;
+ m_szTitleText[0] = 0;
+}
+
+C_ReplayCamera::~C_ReplayCamera()
+{
+
+}
+
+void C_ReplayCamera::Init()
+{
+ ListenForGameEvent( "game_newmap" );
+
+ Reset();
+
+ m_nNumSpectators = 0;
+ m_szTitleText[0] = 0;
+}
+
+void C_ReplayCamera::Reset()
+{
+ m_nCameraMode = OBS_MODE_FIXED;
+ m_iTarget1 = m_iTarget2 = 0;
+ m_flFOV = 90.0f;
+ m_flDistance = m_flLastDistance = CHASE_CAM_DISTANCE_MAX;
+ m_flInertia = 3.0f;
+ m_flPhi = 0;
+ m_flTheta = 0;
+ m_flOffset = 0;
+ m_bEntityPacketReceived = false;
+ m_bOverrideView = false;
+ m_flOldTime = 0.0f;
+ m_bInputEnabled = true;
+
+ m_flRoamingAccel = DEFAULT_ROAMING_ACCEL;
+ m_flRoamingSpeed = DEFAULT_ROAMING_SPEED;
+ m_flRoamingFov[0] = m_flRoamingFov[1] = 90.0f;
+ m_flRoamingRotFilterFactor = 10.0f;
+ m_flRoamingShakeAmount = 0.0f;
+ m_flRoamingShakeSpeed = 0.0f;
+ m_flNoiseSample = 0.0f;
+ m_flRoamingShakeDir = Lerp( 0.5f, FREE_CAM_SHAKE_DIR_MIN, FREE_CAM_SHAKE_DIR_MAX );
+
+ m_vCamOrigin.Init();
+ m_aCamAngle.Init();
+ m_aSmoothedRoamingAngles.Init();
+
+ m_OverrideViewData.origin.Init();
+ m_OverrideViewData.angles.Init();
+ m_OverrideViewData.fov = 90;
+
+ m_LastCmd.Reset();
+ m_vecVelocity.Init();
+
+ InitRoamingKeys();
+}
+
+void C_ReplayCamera::InitRoamingKeys()
+{
+ m_aMovementButtons[DIR_FWD ] = KEY_W;
+ m_aMovementButtons[DIR_BACK ] = KEY_S;
+ m_aMovementButtons[DIR_LEFT ] = KEY_A;
+ m_aMovementButtons[DIR_RIGHT] = KEY_D;
+ m_aMovementButtons[DIR_DOWN ] = KEY_X;
+ m_aMovementButtons[DIR_UP ] = KEY_Z;
+}
+
+bool C_ReplayCamera::ShouldUseDefaultRoamingSettings() const
+{
+ return vgui::input()->IsKeyDown( KEY_LSHIFT );
+}
+
+void C_ReplayCamera::CalcChaseCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov, float flDelta )
+{
+ bool bManual = true;
+
+ Vector targetOrigin1, targetOrigin2, cameraOrigin, forward;
+
+ if ( m_iTarget1 == 0 )
+ return;
+
+ // get primary target, also translates to ragdoll
+ C_BaseEntity *target1 = GetPrimaryTarget();
+
+ if ( !target1 )
+ return;
+
+ if ( target1->IsAlive() && target1->IsDormant() )
+ return;
+
+ targetOrigin1 = target1->GetRenderOrigin();
+
+ if ( !target1->IsAlive() )
+ {
+ targetOrigin1 += VEC_DEAD_VIEWHEIGHT;
+ }
+ else if ( target1->GetFlags() & FL_DUCKING )
+ {
+ targetOrigin1 += VEC_DUCK_VIEW;
+ }
+ else
+ {
+ targetOrigin1 += VEC_VIEW;
+ }
+
+ // get secondary target if set
+ C_BaseEntity *target2 = NULL;
+
+ if ( m_iTarget2 > 0 && (m_iTarget2 != m_iTarget1) && !bManual )
+ {
+ target2 = ClientEntityList().GetBaseEntity( m_iTarget2 );
+
+ // if target is out PVS and not dead, it's not valid
+ if ( target2 && target2->IsDormant() && target2->IsAlive() )
+ target2 = NULL;
+
+ if ( target2 )
+ {
+ targetOrigin2 = target2->GetRenderOrigin();
+
+ if ( !target2->IsAlive() )
+ {
+ targetOrigin2 += VEC_DEAD_VIEWHEIGHT;
+ }
+ else if ( target2->GetFlags() & FL_DUCKING )
+ {
+ targetOrigin2 += VEC_DUCK_VIEW;
+ }
+ else
+ {
+ targetOrigin2 += VEC_VIEW;
+ }
+ }
+ }
+
+ // apply angle offset & smoothing
+ QAngle angleOffset( m_flPhi, m_flTheta, 0 );
+ QAngle cameraAngles = m_aCamAngle;
+
+ if ( bManual )
+ {
+ // let spectator choose the view angles
+ engine->GetViewAngles( cameraAngles );
+ }
+ else if ( target2 )
+ {
+ // look into direction of second target
+ forward = targetOrigin2 - targetOrigin1;
+ VectorAngles( forward, cameraAngles );
+ cameraAngles.z = 0; // no ROLL
+ }
+ else if ( m_iTarget2 == 0 || m_iTarget2 == m_iTarget1)
+ {
+ // look into direction where primary target is looking
+ cameraAngles = target1->EyeAngles();
+ cameraAngles.x = 0; // no PITCH
+ cameraAngles.z = 0; // no ROLL
+ }
+ else
+ {
+ // target2 is missing, just keep angelsm, reset offset
+ angleOffset.Init();
+ }
+
+ if ( !bManual )
+ {
+ if ( !target1->IsAlive() )
+ {
+ angleOffset.x = 15;
+ }
+
+ cameraAngles += angleOffset;
+ }
+
+ AngleVectors( cameraAngles, &forward );
+
+ VectorNormalize( forward );
+
+ // calc optimal camera position
+ VectorMA(targetOrigin1, -m_flDistance, forward, cameraOrigin );
+
+ targetOrigin1.z += m_flOffset; // add offset
+
+ // clip against walls
+ trace_t trace;
+ C_BaseEntity::PushEnableAbsRecomputations( false ); // HACK don't recompute positions while doing RayTrace
+ UTIL_TraceHull( targetOrigin1, cameraOrigin, WALL_MIN, WALL_MAX, MASK_SOLID, target1, COLLISION_GROUP_NONE, &trace );
+ C_BaseEntity::PopEnableAbsRecomputations();
+
+ float dist = VectorLength( trace.endpos - targetOrigin1 );
+
+ // grow distance by 32 unit a second
+ m_flLastDistance += flDelta * 32.0f;
+
+ if ( dist > m_flLastDistance )
+ {
+ VectorMA(targetOrigin1, -m_flLastDistance, forward, cameraOrigin );
+ }
+ else
+ {
+ cameraOrigin = trace.endpos;
+ m_flLastDistance = dist;
+ }
+
+ if ( target2 )
+ {
+ // if we have 2 targets look at point between them
+ forward = (targetOrigin1+targetOrigin2)/2 - cameraOrigin;
+ QAngle angle;
+ VectorAngles( forward, angle );
+ cameraAngles.y = angle.y;
+
+ NormalizeAngles( cameraAngles );
+ cameraAngles.x = clamp( cameraAngles.x, -60.f, 60.f );
+
+ SmoothCameraAngle( cameraAngles );
+ }
+ else
+ {
+ SetCameraAngle( cameraAngles );
+ }
+
+ VectorCopy( cameraOrigin, m_vCamOrigin );
+ VectorCopy( m_aCamAngle, eyeAngles );
+ VectorCopy( m_vCamOrigin, eyeOrigin );
+
+ fov = m_flFOV;
+}
+
+int C_ReplayCamera::GetMode()
+{
+ return m_nCameraMode;
+}
+
+C_BaseEntity* C_ReplayCamera::GetPrimaryTarget()
+{
+ if ( m_iTarget1 <= 0 )
+ return NULL;
+
+ C_BaseEntity* target = ClientEntityList().GetEnt( m_iTarget1 );
+
+ return target;
+}
+
+void C_ReplayCamera::CalcInEyeCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov, float flDelta )
+{
+ C_BasePlayer *pPlayer = UTIL_PlayerByIndex( m_iTarget1 );
+
+ if ( !pPlayer )
+ return;
+
+ if ( !pPlayer->IsAlive() )
+ {
+ // if dead, show from 3rd person
+ CalcChaseCamView( eyeOrigin, eyeAngles, fov, flDelta );
+ return;
+ }
+
+ m_aCamAngle = pPlayer->EyeAngles();
+ m_vCamOrigin = pPlayer->GetAbsOrigin();
+ m_flFOV = pPlayer->GetFOV();
+
+ if ( pPlayer->GetFlags() & FL_DUCKING )
+ {
+ m_vCamOrigin += VEC_DUCK_VIEW;
+ }
+ else
+ {
+ m_vCamOrigin += VEC_VIEW;
+ }
+
+ eyeOrigin = m_vCamOrigin;
+ eyeAngles = m_aCamAngle;
+ fov = m_flFOV;
+
+ pPlayer->CalcViewModelView( eyeOrigin, eyeAngles);
+
+ C_BaseViewModel *pViewModel = pPlayer->GetViewModel( 0 );
+
+ if ( pViewModel )
+ {
+ Assert( pViewModel->GetOwner() == pPlayer );
+ pViewModel->UpdateVisibility();
+ }
+
+ // This fixes the bug where going from third or first person to free cam defaults to some arbitrary angle,
+ // because free cam uses engine->GetViewAngles().
+ engine->SetViewAngles( m_aCamAngle );
+}
+
+void C_ReplayCamera::Accelerate( Vector& wishdir, float wishspeed, float accel, float flDelta )
+{
+ float addspeed, accelspeed, currentspeed;
+
+ // See if we are changing direction a bit
+ currentspeed =m_vecVelocity.Dot(wishdir);
+
+ // Reduce wishspeed by the amount of veer.
+ addspeed = wishspeed - currentspeed;
+
+ // If not going to add any speed, done.
+ if (addspeed <= 0)
+ return;
+
+ // Determine amount of acceleration.
+ accelspeed = accel * flDelta * wishspeed;
+
+ // Cap at addspeed
+ if (accelspeed > addspeed)
+ accelspeed = addspeed;
+
+ // Adjust velocity.
+ for (int i=0 ; i<3 ; i++)
+ {
+ m_vecVelocity[i] += accelspeed * wishdir[i];
+ }
+}
+
+bool C_ReplayCamera::ShouldOverrideView( Vector& origin, QAngle& angles, float& fov )
+{
+ if ( !m_bOverrideView )
+ return false;
+
+ origin = m_OverrideViewData.origin;
+ angles = m_OverrideViewData.angles;
+ fov = m_OverrideViewData.fov;
+
+ return true;
+}
+
+// movement code is a copy of CGameMovement::FullNoClipMove()
+void C_ReplayCamera::CalcRoamingView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov, float flDelta)
+{
+ // only if PVS isn't locked by auto-director
+ if ( !IsPVSLocked() )
+ {
+ Vector wishvel;
+ Vector forward, right, up;
+ Vector wishdir;
+ float wishspeed;
+ float factor = ShouldUseDefaultRoamingSettings() ? DEFAULT_ROAMING_SPEED : m_flRoamingSpeed;
+ float maxspeed = sv_maxspeed.GetFloat() * factor;
+
+ AngleVectors ( m_aCamAngle, &forward, &right, &up ); // Determine movement angles
+
+ if ( m_LastCmd.buttons & IN_SPEED )
+ {
+ factor /= 2.0f;
+ }
+
+ // Check for movement
+ float fmove = 0.0f;
+ float smove = 0.0f;
+ float vmove = 0.0f;
+ if ( !enginevgui->IsGameUIVisible() && m_bInputEnabled )
+ {
+ // Forward/backward movement
+ if ( vgui::input()->IsKeyDown( m_aMovementButtons[DIR_FWD] ) )
+ {
+ fmove = factor * maxspeed;
+ }
+ else if ( vgui::input()->IsKeyDown( m_aMovementButtons[DIR_BACK] ) )
+ {
+ fmove = -factor * maxspeed;
+ }
+
+ // Lateral movement
+ if ( vgui::input()->IsKeyDown( m_aMovementButtons[DIR_LEFT] ) )
+ {
+ smove = -factor * maxspeed;
+ }
+ else if ( vgui::input()->IsKeyDown( m_aMovementButtons[DIR_RIGHT] ) )
+ {
+ smove = factor * maxspeed;
+ }
+
+ // Vertical movement
+ if ( vgui::input()->IsKeyDown( m_aMovementButtons[DIR_UP] ) )
+ {
+ vmove = factor * maxspeed;
+ }
+ else if ( vgui::input()->IsKeyDown( m_aMovementButtons[DIR_DOWN] ) )
+ {
+ vmove = -factor * maxspeed;
+ }
+ }
+
+ // Normalize remainder of vectors
+ VectorNormalize(forward);
+ VectorNormalize(right);
+ VectorNormalize(up);
+
+ for (int i=0 ; i<3 ; i++) // Determine x and y parts of velocity
+ wishvel[i] = forward[i]*fmove + right[i]*smove + up[i]*vmove;
+ wishvel[2] += m_LastCmd.upmove * factor;
+
+ VectorCopy (wishvel, wishdir); // Determine magnitude of speed of move
+ wishspeed = VectorNormalize(wishdir);
+
+ //
+ // Clamp to server defined max speed
+ //
+ if (wishspeed > maxspeed )
+ {
+ VectorScale (wishvel, maxspeed/wishspeed, wishvel);
+ wishspeed = maxspeed;
+ }
+
+ const float flRoamingAccel = ShouldUseDefaultRoamingSettings() ?
+ DEFAULT_ROAMING_ACCEL : m_flRoamingAccel;
+
+ if ( flRoamingAccel > 0.0 )
+ {
+ // Set move velocity
+ Accelerate ( wishdir, wishspeed, flRoamingAccel, flDelta );
+
+ float spd = VectorLength( m_vecVelocity );
+ if ( CloseEnough( spd, 0.0f ) )
+ {
+ m_vecVelocity.Init();
+ }
+ else
+ {
+ // Bleed off some speed, but if we have less than the bleed
+ // threshold, bleed the threshold amount.
+ float control = spd;
+
+ float friction = sv_friction.GetFloat();
+
+ // Add the amount to the drop amount.
+ float drop = control * friction * flDelta;
+
+ // scale the velocity
+ float newspeed = spd - drop;
+ if (newspeed < 0)
+ newspeed = 0;
+
+ // Determine proportion of old speed we are using.
+ newspeed /= spd;
+ VectorScale( m_vecVelocity, newspeed, m_vecVelocity );
+ }
+ }
+ else
+ {
+ VectorCopy( wishvel, m_vecVelocity );
+ }
+
+ // Just move ( don't clip or anything )
+ VectorMA( m_vCamOrigin, flDelta, m_vecVelocity, m_vCamOrigin );
+
+ // get camera angle directly from engine
+ engine->GetViewAngles( m_aCamAngle );
+
+ // Zero out velocity if in noaccel mode
+ if ( sv_specaccelerate.GetFloat() < 0.0f )
+ {
+ m_vecVelocity.Init();
+ }
+ }
+
+ // Smooth the angles
+ float flPercent = clamp( flDelta * m_flRoamingRotFilterFactor, 0.0f, 1.0f );
+ m_aSmoothedRoamingAngles = Lerp( flPercent, m_aSmoothedRoamingAngles, m_aCamAngle );
+
+ Vector vCameraShakeOffset;
+ vCameraShakeOffset.Init();
+
+ // Add in camera shake
+ if ( !ShouldUseDefaultRoamingSettings() && m_flRoamingShakeAmount > 0.0f )
+ {
+ QAngle angShake( 0.0f, 0.0f, 0.0f );
+
+ m_flNoiseSample += m_flRoamingShakeSpeed * flDelta;
+
+ float flNoiseX = Lerp( FractalNoise( Vector( m_flNoiseSample, 0.0f, 0.0f ), 1 ), -1.0f, 1.0f );
+ float flNoiseY = Lerp( FractalNoise( Vector( 0.0f, 1000 + m_flNoiseSample, 0.0f ), 1 ), -1.0f, 1.0f );
+
+ // Vertical shake
+ const float flAmplitudeX = m_flRoamingShakeAmount * ( m_flRoamingShakeDir < 0.0f ? ( 1.0f + m_flRoamingShakeDir ) : 1.0f );
+ angShake.x = flAmplitudeX * flNoiseX;
+
+ // Lateral shake
+ const float flAmplitudeY = m_flRoamingShakeAmount * ( m_flRoamingShakeDir > 0.0f ? ( 1.0f - m_flRoamingShakeDir ) : 1.0f );
+ angShake.y = flAmplitudeY * flNoiseY;
+
+ // The math below simulates a camera with length "replay_editor_camera_length," so that the camera position bounces around
+ // as if it were on someone's shoulder. If we were to just use angShake at this point with no translation, we would get a
+ // camera that looks around but is anchored and doesn't feel quite right. With the code below, the camera will translate,
+ // but be centered around the same point as when camera shake is off completely, rather than actually offsetting that point
+ // by the camera length.
+
+ // Get the forward vector from the shake transform/angles
+ Vector vShakeForward;
+ AngleVectors( angShake, &vShakeForward );
+
+ // Calculate an offset, simulating a camera length
+ Vector vCameraOffset = vShakeForward * replay_editor_camera_length.GetFloat();
+
+ // Get the global matrix without any shake
+ matrix3x4_t mGlobal;
+ AngleMatrix( m_aSmoothedRoamingAngles, m_vCamOrigin, mGlobal );
+
+ // Convert local shake angles and offset to a matrix
+ matrix3x4_t mShake;
+ AngleMatrix( angShake, mShake );
+
+ // Setup a translation matrix using the offset
+ matrix3x4_t mOffset;
+ SetIdentityMatrix( mOffset );
+ PositionMatrix( vCameraOffset, mOffset );
+
+ matrix3x4_t mOffsetInv;
+ MatrixInvert( mOffset, mOffsetInv );
+
+ // The meat
+ matrix3x4_t mFinal = mGlobal;
+ MatrixMultiply( mFinal, mOffsetInv, mFinal );
+ MatrixMultiply( mFinal, mShake, mFinal );
+ MatrixMultiply( mFinal, mOffset, mFinal );
+
+ // Convert back to Vector / QAngle
+ MatrixAngles( mFinal, eyeAngles, eyeOrigin );
+ }
+ else
+ {
+ // No shake
+ eyeOrigin = m_vCamOrigin;
+ eyeAngles = m_aSmoothedRoamingAngles;
+ }
+
+ fov = m_flRoamingFov[0];
+}
+
+void C_ReplayCamera::CalcFixedView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov, float flDelta )
+{
+ eyeOrigin = m_vCamOrigin;
+ eyeAngles = m_aCamAngle;
+ fov = m_flFOV;
+
+ if ( m_iTarget1 == 0 )
+ return;
+
+ C_BaseEntity * target = ClientEntityList().GetBaseEntity( m_iTarget1 );
+
+ if ( target && target->IsAlive() )
+ {
+ // if we're chasing a target, change viewangles
+ QAngle angle;
+ VectorAngles( (target->GetAbsOrigin()+VEC_VIEW) - m_vCamOrigin, angle );
+ SmoothCameraAngle( angle );
+ }
+}
+
+void C_ReplayCamera::PostEntityPacketReceived()
+{
+ m_bEntityPacketReceived = true;
+}
+
+void C_ReplayCamera::SmoothFov( float flDelta )
+{
+ m_flRoamingFov[0] = clamp(
+ Lerp( 7 * flDelta, m_flRoamingFov[0], m_flRoamingFov[1] ),
+// Approach( m_flRoamingFov[1], m_flRoamingFov[0], 40 * m_flFrameTime ),
+ FREE_CAM_FOV_MIN,
+ FREE_CAM_FOV_MAX
+ );
+}
+
+void C_ReplayCamera::FixupMovmentParents()
+{
+ // Find resource zone
+
+ for ( ClientEntityHandle_t e = ClientEntityList().FirstHandle();
+ e != ClientEntityList().InvalidHandle(); e = ClientEntityList().NextHandle( e ) )
+ {
+ C_BaseEntity *ent = C_BaseEntity::Instance( e );
+
+ if ( !ent )
+ continue;
+
+ ent->HierarchyUpdateMoveParent();
+ }
+}
+
+void C_ReplayCamera::EnableInput( bool bEnable )
+{
+ m_bInputEnabled = bEnable;
+}
+
+void C_ReplayCamera::ClearOverrideView()
+{
+ if ( m_bOverrideView )
+ {
+ m_vCamOrigin = m_OverrideViewData.origin;
+ m_aCamAngle = m_aSmoothedRoamingAngles = m_OverrideViewData.angles;
+ m_flRoamingFov[0] = m_flRoamingFov[1] = m_OverrideViewData.fov;
+ }
+
+ m_bOverrideView = false;
+
+ // Set view angles in engine so that CalcRoamingView() won't pop to some stupid angle
+ engine->SetViewAngles( m_aCamAngle );
+}
+
+void C_ReplayCamera::OverrideView( const Vector *pOrigin, const QAngle *pAngles, float flFov )
+{
+ m_bOverrideView = true;
+
+ m_OverrideViewData.origin = *pOrigin;
+ m_OverrideViewData.angles = *pAngles;
+ m_OverrideViewData.fov = flFov;
+}
+
+void C_ReplayCamera::CalcView(Vector &origin, QAngle &angles, float &fov )
+{
+ // NOTE ABOUT CLOCKS: 'realtime' is used, because otherwise we can't move the camera round while
+ // the game is paused.
+
+ // Calculate elapsed time since last call to CalcView()
+ if ( m_flOldTime == 0.0f )
+ {
+ m_flOldTime = gpGlobals->realtime;
+ }
+ const float flDelta = gpGlobals->realtime - m_flOldTime;
+ m_flOldTime = gpGlobals->realtime;
+
+ if ( m_bEntityPacketReceived )
+ {
+ // try to fixup movment parents
+ FixupMovmentParents();
+ m_bEntityPacketReceived = false;
+ }
+
+ // Completely override?
+ if ( ShouldOverrideView( origin, angles, fov ) )
+ return;
+
+ switch ( m_nCameraMode )
+ {
+ case OBS_MODE_ROAMING : CalcRoamingView( origin, angles, fov, flDelta );
+ break;
+
+ case OBS_MODE_FIXED : CalcFixedView( origin, angles, fov, flDelta );
+ break;
+
+ case OBS_MODE_IN_EYE : CalcInEyeCamView( origin, angles, fov, flDelta );
+ break;
+
+ case OBS_MODE_CHASE : CalcChaseCamView( origin, angles, fov, flDelta );
+ break;
+ }
+
+ // Cache in case we want to access this data later in the frame
+ m_CachedView.origin = origin;
+ m_CachedView.angles = angles;
+ m_CachedView.fov = fov;
+}
+
+void C_ReplayCamera::GetCachedView( Vector &origin, QAngle &angles, float &fov )
+{
+ origin = m_CachedView.origin;
+ angles = m_CachedView.angles;
+ fov = m_CachedView.fov;
+}
+
+void C_ReplayCamera::SetMode(int iMode)
+{
+ if ( m_nCameraMode == iMode )
+ return;
+
+ Assert( iMode > OBS_MODE_NONE && iMode <= LAST_PLAYER_OBSERVERMODE );
+
+ m_nCameraMode = iMode;
+
+ if ( m_nCameraMode != OBS_MODE_ROAMING && m_nCameraMode != OBS_MODE_CHASE )
+ {
+ ClearOverrideView();
+ }
+}
+
+void C_ReplayCamera::SetPrimaryTarget( int nEntity )
+{
+ if ( m_iTarget1 == nEntity )
+ return;
+
+ m_iTarget1 = nEntity;
+
+ if ( GetMode() == OBS_MODE_ROAMING )
+ {
+ Vector vOrigin;
+ QAngle aAngles;
+ float flFov;
+
+ CalcChaseCamView( vOrigin, aAngles, flFov, 0.015f );
+ }
+ else if ( GetMode() == OBS_MODE_CHASE )
+ {
+ C_BaseEntity* target = ClientEntityList().GetEnt( m_iTarget1 );
+ if ( target )
+ {
+ QAngle eyeAngle = target->EyeAngles();
+ prediction->SetViewAngles( eyeAngle );
+ }
+ }
+
+ m_flLastDistance = m_flDistance;
+ m_flLastAngleUpdateTime = -1;
+}
+
+void C_ReplayCamera::SpecNextPlayer( bool bInverse )
+{
+ int start = 1;
+
+ if ( m_iTarget1 > 0 && m_iTarget1 <= gpGlobals->maxClients )
+ start = m_iTarget1;
+
+ int index = start;
+
+ while ( true )
+ {
+ // got next/prev player
+ if ( bInverse )
+ index--;
+ else
+ index++;
+
+ // check bounds
+ if ( index < 1 )
+ index = gpGlobals->maxClients;
+ else if ( index > gpGlobals->maxClients )
+ index = 1;
+
+ if ( index == start )
+ break; // couldn't find a new valid player
+
+ C_BasePlayer *pPlayer = UTIL_PlayerByIndex( index );
+
+ if ( !pPlayer )
+ continue;
+
+ // only follow living players
+ if ( pPlayer->IsObserver() )
+ continue;
+
+ break; // found a new player
+ }
+
+ SetPrimaryTarget( index );
+
+ // turn off auto director once user tried to change view settings
+ SetAutoDirector( false );
+}
+
+void C_ReplayCamera::SpecNamedPlayer( const char *szPlayerName )
+{
+ for ( int index = 1; index <= gpGlobals->maxClients; ++index )
+ {
+ C_BasePlayer *pPlayer = UTIL_PlayerByIndex( index );
+
+ if ( !pPlayer )
+ continue;
+
+ if ( !FStrEq( szPlayerName, pPlayer->GetPlayerName() ) )
+ continue;
+
+ // only follow living players or dedicated spectators
+ if ( pPlayer->IsObserver() && pPlayer->GetTeamNumber() != TEAM_SPECTATOR )
+ continue;
+
+ SetPrimaryTarget( index );
+ return;
+ }
+}
+
+void C_ReplayCamera::FireGameEvent( IGameEvent * event)
+{
+ if ( !g_pEngineClientReplay->IsPlayingReplayDemo() )
+ return; // not in Replay mode
+
+ const char *type = event->GetName();
+
+ if ( Q_strcmp( "game_newmap", type ) == 0 )
+ {
+ // Do not reset the camera, since we reload the map when "rewinding"
+ // and want to keep our camera settings intact.
+ // Reset(); // reset all camera settings
+
+ // show spectator UI
+ if ( !gViewPortInterface )
+ return;
+
+ if ( g_pEngineClientReplay->IsPlayingReplayDemo() )
+ {
+ SetMode( OBS_MODE_IN_EYE );
+
+ CReplay *pReplay = g_pReplayManager->GetPlayingReplay();
+ SetPrimaryTarget( ( pReplay && pReplay->m_nPlayerSlot >= 0 ) ? pReplay->m_nPlayerSlot : 0 );
+ }
+ else
+ {
+ // during live broadcast only show black bars
+ gViewPortInterface->ShowPanel( PANEL_SPECMENU, true );
+ }
+
+ return;
+ }
+
+ // after this only auto-director commands follow
+ // don't execute them is autodirector is off and PVS is unlocked
+ if ( !spec_autodirector.GetBool() && !IsPVSLocked() )
+ return;
+}
+
+// this is a cheap version of FullNoClipMove():
+void C_ReplayCamera::CreateMove( CUserCmd *cmd)
+{
+ if ( cmd )
+ {
+ m_LastCmd = *cmd;
+ }
+}
+
+void C_ReplayCamera::SetCameraAngle( QAngle& targetAngle )
+{
+ m_aCamAngle = targetAngle;
+ NormalizeAngles( m_aCamAngle );
+ m_flLastAngleUpdateTime = gpGlobals->realtime;
+}
+
+void C_ReplayCamera::SmoothCameraAngle( QAngle& targetAngle )
+{
+ if ( m_flLastAngleUpdateTime > 0 )
+ {
+ float deltaTime = gpGlobals->realtime - m_flLastAngleUpdateTime;
+
+ deltaTime = clamp( deltaTime*m_flInertia, 0.01f, 1.f);
+
+ InterpolateAngles( m_aCamAngle, targetAngle, m_aCamAngle, deltaTime );
+ }
+ else
+ {
+ m_aCamAngle = targetAngle;
+ }
+
+ m_flLastAngleUpdateTime = gpGlobals->realtime;
+}
+
+bool C_ReplayCamera::IsPVSLocked()
+{
+ return false;
+}
+
+void C_ReplayCamera::SetAutoDirector( bool bActive )
+{
+ spec_autodirector.SetValue( bActive?1:0 );
+}
+
+#endif
\ No newline at end of file diff --git a/mp/src/game/client/replay/replaycamera.h b/mp/src/game/client/replay/replaycamera.h new file mode 100644 index 00000000..8ab8e637 --- /dev/null +++ b/mp/src/game/client/replay/replaycamera.h @@ -0,0 +1,158 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#if defined( REPLAY_ENABLED )
+
+#ifndef REPLAYCAMERA_H
+#define REPLAYCAMERA_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "replay/ireplaycamera.h"
+#include "GameEventListener.h"
+
+class C_ReplayCamera : public CGameEventListener,
+ public IReplayCamera
+{
+public:
+ C_ReplayCamera();
+ virtual ~C_ReplayCamera();
+
+ void Init();
+ void Reset();
+
+ //
+ // IReplayCamera:
+ //
+ virtual void ClearOverrideView();
+
+ void EnableInput( bool bEnable );
+
+ void OverrideView( const Vector *pOrigin, const QAngle *pAngles, float flFov );
+ void GetCachedView( Vector &origin, QAngle &angles, float &fov );
+
+ void CalcView(Vector& origin, QAngle& angles, float& fov);
+ void FireGameEvent( IGameEvent *event );
+
+ void SetMode(int iMode);
+ void SetChaseCamParams( float flOffset, float flDistance, float flTheta, float flPhi );
+ void SpecNextPlayer( bool bInverse );
+ void SpecNamedPlayer( const char *szPlayerName );
+ bool IsPVSLocked();
+ void SetAutoDirector( bool bActive );
+
+ int GetMode(); // returns current camera mode
+ C_BaseEntity *GetPrimaryTarget(); // return primary target
+ inline int GetPrimaryTargetIndex() { return m_iTarget1; }
+ void SetPrimaryTarget( int nEntity); // set the primary obs target
+
+ void CreateMove(CUserCmd *cmd);
+ void FixupMovmentParents();
+ void PostEntityPacketReceived();
+ const char* GetTitleText() { return m_szTitleText; }
+ int GetNumSpectators() { return m_nNumSpectators; }
+
+ void SmoothFov( float flDelta );
+
+ float m_flRoamingAccel;
+ float m_flRoamingSpeed;
+ float m_flRoamingFov[2]; // FOV for roaming only - current and target - smoothing done by replay editor
+ float m_flRoamingRotFilterFactor;
+ float m_flRoamingShakeAmount;
+ float m_flRoamingShakeSpeed;
+ float m_flRoamingShakeDir;
+
+protected:
+ void InitRoamingKeys();
+ bool ShouldUseDefaultRoamingSettings() const;
+
+ void CalcChaseCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov, float flDelta );
+ void CalcFixedView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov, float flDelta );
+ void CalcInEyeCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov, float flDelta );
+ void CalcRoamingView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov, float flDelta);
+
+ void SmoothCameraAngle( QAngle& targetAngle );
+ void SetCameraAngle( QAngle& targetAngle );
+ void Accelerate( Vector& wishdir, float wishspeed, float accel, float flDelta );
+
+ bool ShouldOverrideView( Vector& origin, QAngle& angles, float& fov ); // Fills with override data if m_bOverrideView is set
+
+ struct View_t
+ {
+ Vector origin;
+ QAngle angles;
+ float fov;
+ };
+
+ bool m_bInputEnabled;
+ bool m_bOverrideView;
+ View_t m_OverrideViewData;
+ View_t m_CachedView;
+ float m_flOldTime; // Time of last CalcView() (uses gpGlobals->realtime)
+ int m_nCameraMode; // current camera mode
+ Vector m_vCamOrigin; //current camera origin
+ QAngle m_aCamAngle; //current camera angle
+ QAngle m_aSmoothedRoamingAngles;
+ int m_iTarget1; // first tracked target or 0
+ int m_iTarget2; // second tracked target or 0
+ float m_flFOV; // current FOV
+ float m_flOffset; // z-offset from target origin
+ float m_flDistance; // distance to traget origin+offset
+ float m_flLastDistance; // too smooth distance
+ float m_flTheta; // view angle horizontal
+ float m_flPhi; // view angle vertical
+ float m_flInertia; // camera inertia 0..100
+ float m_flLastAngleUpdateTime;
+ bool m_bEntityPacketReceived; // true after a new packet was received
+ int m_nNumSpectators;
+ char m_szTitleText[64];
+ CUserCmd m_LastCmd;
+ Vector m_vecVelocity;
+
+ enum Dir_t
+ {
+ DIR_FWD,
+ DIR_BACK,
+ DIR_LEFT,
+ DIR_RIGHT,
+
+ DIR_UP,
+ DIR_DOWN,
+
+ NUM_DIRS
+ };
+ ButtonCode_t m_aMovementButtons[NUM_DIRS];
+
+ float m_flNoiseSample;
+};
+
+//-----------------------------------------------------------------------------
+
+C_ReplayCamera *ReplayCamera();
+
+//-----------------------------------------------------------------------------
+
+#define FREE_CAM_ACCEL_MIN 1.1f
+#define FREE_CAM_ACCEL_MAX 10.0f
+#define FREE_CAM_SPEED_MIN 0.1f
+#define FREE_CAM_SPEED_MAX 20.0f
+#define FREE_CAM_FOV_MIN 10.0f
+#define FREE_CAM_FOV_MAX 130.0f
+#define FREE_CAM_ROT_FILTER_MIN 30.0f
+#define FREE_CAM_ROT_FILTER_MAX 5.0f
+#define FREE_CAM_SHAKE_SPEED_MIN 0.1f
+#define FREE_CAM_SHAKE_SPEED_MAX 15.0f
+#define FREE_CAM_SHAKE_AMOUNT_MIN 0.0f
+#define FREE_CAM_SHAKE_AMOUNT_MAX 35.0f
+#define FREE_CAM_SHAKE_DIR_MIN -1.0f
+#define FREE_CAM_SHAKE_DIR_MAX 1.0f
+
+//-----------------------------------------------------------------------------
+
+#endif // REPLAYCAMERA_H
+
+#endif
\ No newline at end of file diff --git a/mp/src/game/client/replay/replayperformanceplaybackhandler.cpp b/mp/src/game/client/replay/replayperformanceplaybackhandler.cpp new file mode 100644 index 00000000..1779fdbe --- /dev/null +++ b/mp/src/game/client/replay/replayperformanceplaybackhandler.cpp @@ -0,0 +1,99 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "replayperformanceplaybackhandler.h"
+#include "replay/replaycamera.h"
+#include "replay/vgui/replayperformanceeditor.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+
+class CReplayPerformancePlaybackHandler : public IReplayPerformancePlaybackHandler
+{
+public:
+ CReplayPerformancePlaybackHandler() {}
+
+private:
+ //
+ // IReplayPerformancePlaybackController
+ //
+ virtual void OnEvent_Camera_Change_FirstPerson( float flTime, int nEntityIndex )
+ {
+ ReplayCamera()->SetMode( OBS_MODE_IN_EYE );
+ Editor_UpdateCameraModeIcon( CAM_FIRST );
+ }
+
+ virtual void OnEvent_Camera_Change_ThirdPerson( float flTime, int nEntityIndex )
+ {
+ ReplayCamera()->SetMode( OBS_MODE_CHASE );
+ Editor_UpdateCameraModeIcon( CAM_THIRD );
+ }
+
+ virtual void OnEvent_Camera_Change_Free( float flTime )
+ {
+ ReplayCamera()->SetMode( OBS_MODE_ROAMING );
+ Editor_UpdateCameraModeIcon( CAM_FREE );
+ }
+
+ virtual void OnEvent_Camera_ChangePlayer( float flTime, int nEntIndex )
+ {
+ ReplayCamera()->SetPrimaryTarget( nEntIndex );
+ }
+
+ virtual void OnEvent_Camera_SetView( const SetViewParams_t ¶ms )
+ {
+ ReplayCamera()->OverrideView( params.m_pOrigin, params.m_pAngles, params.m_flFov );
+ Editor_UpdateFreeCamSettings( params );
+ }
+
+ virtual void OnEvent_TimeScale( float flTime, float flScale )
+ {
+ // Update the slider position
+ Editor_UpdateTimeScale( flScale );
+ }
+
+ // ---
+
+ void Editor_UpdateCameraModeIcon( CameraMode_t nMode )
+ {
+ CReplayPerformanceEditorPanel *pEditor = ReplayUI_GetPerformanceEditor();
+ if ( !pEditor )
+ return;
+
+ pEditor->UpdateCameraSelectionPosition( nMode );
+ }
+
+ void Editor_UpdateFreeCamSettings( const SetViewParams_t ¶ms )
+ {
+ CReplayPerformanceEditorPanel *pEditor = ReplayUI_GetPerformanceEditor();
+ if ( !pEditor )
+ return;
+
+ pEditor->UpdateFreeCamSettings( params );
+ }
+
+ void Editor_UpdateTimeScale( float flScale )
+ {
+ CReplayPerformanceEditorPanel *pEditor = ReplayUI_GetPerformanceEditor();
+ if ( !pEditor )
+ return;
+
+ pEditor->UpdateTimeScale( flScale );
+ }
+};
+
+//-----------------------------------------------------------------------------
+
+CReplayPerformancePlaybackHandler s_ReplayPerformancePlaybackHandler;
+IReplayPerformancePlaybackHandler *g_pReplayPerformancePlaybackHandler = &s_ReplayPerformancePlaybackHandler;
+
+//-----------------------------------------------------------------------------
+
+#endif
\ No newline at end of file diff --git a/mp/src/game/client/replay/replayperformanceplaybackhandler.h b/mp/src/game/client/replay/replayperformanceplaybackhandler.h new file mode 100644 index 00000000..61b2dccc --- /dev/null +++ b/mp/src/game/client/replay/replayperformanceplaybackhandler.h @@ -0,0 +1,21 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#ifndef REPLAYPERFORMANCEPLAYER_H
+#define REPLAYPERFORMANCEPLAYER_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+//-----------------------------------------------------------------------------
+
+#include "replay/ireplayperformanceplaybackhandler.h"
+
+//-----------------------------------------------------------------------------
+
+extern IReplayPerformancePlaybackHandler *g_pReplayPerformancePlaybackHandler;
+
+//-----------------------------------------------------------------------------
+
+#endif // REPLAYPERFORMANCEPLAYER_H
diff --git a/mp/src/game/client/replay/replayrenderer.cpp b/mp/src/game/client/replay/replayrenderer.cpp new file mode 100644 index 00000000..9e778fd7 --- /dev/null +++ b/mp/src/game/client/replay/replayrenderer.cpp @@ -0,0 +1,1014 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "replayrenderer.h"
+#include "materialsystem/imaterialvar.h"
+#include "materialsystem/itexture.h"
+#include "materialsystem/imaterialproxy.h"
+#include "replay/vgui/replayrenderoverlay.h"
+#include "replay/replay.h"
+#include "replay/ireplaymoviemanager.h"
+#include "replay/ireplayperformancecontroller.h"
+#include "replay/ireplaymovie.h"
+#include "replay/ireplaymanager.h"
+#include "replay/ienginereplay.h"
+#include "replay/iclientreplaycontext.h"
+#include "view.h"
+#include "iviewrender.h"
+#include "view_shared.h"
+#include "replay/replaycamera.h"
+#include "bitmap/tgawriter.h"
+#include "filesystem.h"
+
+#define REPLAY_RECORDING_ENABLE
+
+#ifdef REPLAY_RECORDING_ENABLE
+#include "video/ivideoservices.h"
+#endif
+
+#define TMP_WAVE_FILENAME "tmpaudio"
+
+//#define TRACE_REPLAY_STATE_MACHINE
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+
+extern IReplayMovieManager *g_pReplayMovieManager;
+extern IReplayPerformanceController *g_pReplayPerformanceController;
+
+// Map quality index to number of samples
+static int s_DoFQualityToSamples[MAX_DOF_QUALITY+1] = {8, 16, 32};//, 64, 128 };
+
+// 4-entry table of values in 2D -1 to +1 range using Poisson disk distribution
+static Vector2D g_vJitterTable4[4] = { Vector2D (0.5318f, -0.6902f ), Vector2D (-0.5123f, 0.8362f ), Vector2D (-0.5193f, -0.2195f ), Vector2D (0.4749f, 0.3478f ) };
+
+// 8-entry table of values in 2D -1 to +1 range using Poisson disk distribution
+static Vector2D g_vJitterTable8[8] = { Vector2D (0.3475f, 0.0042f ),Vector2D (0.8806f, 0.3430f ),Vector2D (-0.0041f, -0.6197f ),Vector2D (0.0472f, 0.4964f ),
+ Vector2D (-0.3730f, 0.0874f ),Vector2D (-0.9217f, -0.3177f ),Vector2D (-0.6289f, 0.7388f ),Vector2D (0.5744f, -0.7741f ) };
+
+// 16-entry table of values in 2D -1 to +1 range using Poisson disk distribution (disk size 0.38f)
+static Vector2D g_vJitterTable16[16] = { Vector2D (0.0747f, -0.8341f ),Vector2D (-0.9138f, 0.3251f ),Vector2D (0.8667f, -0.3029f ),Vector2D (-0.4642f, 0.2187f ),
+ Vector2D (-0.1505f, 0.7320f ),Vector2D (0.7310f, -0.6786f ),Vector2D (0.2859f, -0.3254f ),Vector2D (-0.1311f, -0.2292f ),
+ Vector2D (0.3518f, 0.6470f ),Vector2D (-0.7485f, -0.6307f ),Vector2D (0.1687f, 0.1873f ),Vector2D (-0.3604f, -0.7483f ),
+ Vector2D (-0.5658f, -0.1521f ),Vector2D (0.7102f, 0.0536f ),Vector2D (-0.6056f, 0.7747f ),Vector2D (0.7793f, 0.6194f ) };
+
+// 32-entry table of values in 2D -1 to +1 range using Poisson disk distribution (disk size 0.28f)
+static Vector2D g_vJitterTable32[32] = { Vector2D (0.0854f, -0.0644f ),Vector2D (0.8744f, 0.1665f ),Vector2D (0.2329f, 0.3995f ),Vector2D (-0.7804f, 0.5482f ),
+ Vector2D (-0.4577f, 0.7647f ),Vector2D (-0.1936f, 0.5564f ),Vector2D (0.4205f, -0.5768f ),Vector2D (-0.0304f, -0.9050f ),
+ Vector2D (-0.5215f, 0.1854f ),Vector2D (0.3161f, -0.2954f ),Vector2D (0.0666f, -0.5564f ),Vector2D (-0.2137f, -0.0072f ),
+ Vector2D (-0.4112f, -0.3311f ),Vector2D (0.6438f, -0.2484f ),Vector2D (-0.9055f, -0.0360f ),Vector2D (0.8323f, 0.5268f ),
+ Vector2D (0.5592f, 0.3459f ),Vector2D (-0.6797f, -0.5201f ),Vector2D (-0.4325f, -0.8857f ),Vector2D (0.8768f, -0.4197f ),
+ Vector2D (0.3090f, -0.8646f ),Vector2D (0.5034f, 0.8603f ),Vector2D (0.3752f, 0.0627f ),Vector2D (-0.0161f, 0.2627f ),
+ Vector2D (0.0969f, 0.7054f ),Vector2D (-0.2291f, -0.6595f ),Vector2D (-0.5887f, -0.1100f ),Vector2D (0.7048f, -0.6528f ),
+ Vector2D (-0.8438f, 0.2706f ),Vector2D (-0.5061f, 0.4653f ),Vector2D (-0.1245f, -0.3302f ),Vector2D (-0.1801f, 0.8486f )};
+
+//-----------------------------------------------------------------------------
+
+//
+// Accumulation material proxy for ping-pong accumulation buffer imp.
+//
+
+struct AccumParams_t
+{
+ ITexture *m_pTexture0;
+ ITexture *m_pTexture1;
+ float m_fSampleWeight;
+ bool m_bClear;
+};
+
+class CAccumBuffProxy : public IMaterialProxy
+{
+public:
+ CAccumBuffProxy();
+ virtual ~CAccumBuffProxy();
+ virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
+ virtual void OnBind( void *pC_BaseEntity );
+ virtual void Release( void ) { delete this; }
+ virtual IMaterial *GetMaterial();
+
+private:
+ IMaterialVar *m_pTexture0;
+ IMaterialVar *m_pTexture1;
+ IMaterialVar *m_pAccumBuffWeights;
+};
+
+//-----------------------------------------------------------------------------
+
+CAccumBuffProxy::CAccumBuffProxy()
+{
+ m_pTexture0 = NULL;
+ m_pTexture1 = NULL;
+ m_pAccumBuffWeights = NULL;
+}
+
+CAccumBuffProxy::~CAccumBuffProxy()
+{
+}
+
+bool CAccumBuffProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
+{
+ bool foundVar;
+
+ // Grab the Material variables for the accumulation shader
+ m_pTexture0 = pMaterial->FindVar( "$TEXTURE0", &foundVar, false );
+ if( !foundVar )
+ return false;
+
+ m_pTexture1 = pMaterial->FindVar( "$TEXTURE1", &foundVar, false );
+ if( !foundVar )
+ return false;
+
+ m_pAccumBuffWeights = pMaterial->FindVar( "$WEIGHTS", &foundVar, false );
+ if( !foundVar )
+ return false;
+
+ return true;
+}
+
+void CAccumBuffProxy::OnBind( void *pC_BaseEntity )
+{
+ AccumParams_t *pAccumParams = (AccumParams_t *) pC_BaseEntity;
+
+ if( !m_pTexture0 || !m_pTexture1 || !m_pAccumBuffWeights )
+ {
+ return;
+ }
+
+ m_pTexture0->SetTextureValue( pAccumParams->m_pTexture0 );
+ m_pTexture1->SetTextureValue( pAccumParams->m_pTexture1 );
+
+ // If we're just using this material to do a clear to black...
+ if ( pAccumParams->m_bClear )
+ {
+ m_pAccumBuffWeights->SetVecValue( 0.0f, 0.0f, 0.0f, 0.0f );
+ }
+ else
+ {
+ m_pAccumBuffWeights->SetVecValue( pAccumParams->m_fSampleWeight, 1.0f - pAccumParams->m_fSampleWeight, 0.0f, 0.0f );
+ }
+}
+
+IMaterial *CAccumBuffProxy::GetMaterial()
+{
+ return m_pAccumBuffWeights ? m_pAccumBuffWeights->GetOwningMaterial() : NULL;
+}
+
+//-----------------------------------------------------------------------------
+
+EXPOSE_INTERFACE( CAccumBuffProxy, IMaterialProxy, "accumbuff4sample" IMATERIAL_PROXY_INTERFACE_VERSION );
+
+//-----------------------------------------------------------------------------
+
+CReplayRenderer::CReplayRenderer( CReplayRenderOverlay *pOverlay )
+: m_bIsAudioSyncFrame( false ),
+ m_pRenderOverlay( pOverlay ),
+ m_nCurrentPingPong( 0 ),
+ m_nCurSample( 0 ),
+ m_nTimeStep( 0 ),
+ m_curSampleTime( 0 ),
+ m_nFrame( 0 ),
+ m_nNumJitterSamples( 0 ),
+ m_iTgaFrame( 0 ),
+ m_pLayoffBuf( NULL ),
+ m_pMovie( NULL ),
+ m_pMovieMaker( NULL ),
+ m_pJitterTable( NULL ),
+ m_pViewmodelFov( NULL ),
+ m_pDefaultFov( NULL ),
+ m_bCacheFullSceneState( false ),
+ m_bShutterClosed( false ),
+ m_bForceCheapDoF( false )
+{
+}
+
+CReplayRenderer::~CReplayRenderer()
+{
+}
+
+const CReplayPerformance *CReplayRenderer::GetPerformance() const
+{
+ CReplay *pReplay = g_pReplayManager->GetPlayingReplay();
+ if ( !pReplay )
+ return NULL;
+
+ return m_RenderParams.m_iPerformance >= 0 ? pReplay->GetPerformance( m_RenderParams.m_iPerformance ) : NULL;
+}
+
+const char *CReplayRenderer::GetMovieFilename() const
+{
+ if ( !m_pMovie )
+ return NULL;
+
+ return m_pMovie->GetMovieFilename();
+}
+
+// -------------------------------------------------------------------
+// Functions used by audio engine to distinguish between sub-frames
+// rendered for motion blur, and the actual frames being recorded
+// -------------------------------------------------------------------
+void CReplayRenderer::SetAudioSyncFrame( bool isSync )
+{
+ m_bIsAudioSyncFrame = isSync;
+}
+
+bool CReplayRenderer::IsAudioSyncFrame()
+{
+ return m_bIsAudioSyncFrame;
+}
+
+float CReplayRenderer::GetRecordingFrameDuration()
+{
+ double actualFPS = m_RenderParams.m_Settings.m_FPS.GetFPS();
+ if ( actualFPS <= 0.0 )
+ {
+ Assert( false );
+ return 30.0f;
+ }
+
+ double interval = 1.0 / actualFPS;
+
+ return (float) interval;
+}
+
+bool CReplayRenderer::SetupRenderer( RenderMovieParams_t ¶ms, IReplayMovie *pMovie )
+{
+ // Cache render parameters
+ V_memcpy( &m_RenderParams, ¶ms, sizeof( params ) );
+
+ // Cache movie
+ m_pMovie = pMovie;
+
+ // Reset current frame
+ m_nFrame = 0;
+ m_nTimeStep = 0;
+ m_nCurSample = 0;
+ m_iTgaFrame = 0;
+ m_curSampleTime = DmeTime_t(0);
+
+ m_pViewmodelFov = ( ConVar * )cvar->FindVar( "viewmodel_fov" );
+ m_pDefaultFov = ( ConVar * )cvar->FindVar( "default_fov" );
+
+ InitBuffers( params );
+
+#ifdef REPLAY_RECORDING_ENABLE
+ // Record directly to a .wav file if desired via 'startmovie' and write out TGA's
+ if ( params.m_bExportRaw )
+ {
+ // Create the temporary wave file
+ g_pEngineClientReplay->Wave_CreateTmpFile( TMP_WAVE_FILENAME );
+
+ // Create the path for the movie
+ m_fmtTgaRenderDirName = g_pClientReplayContext->GetMovieManager()->GetRawExportDir();
+
+ g_pFullFileSystem->CreateDirHierarchy( m_fmtTgaRenderDirName.Access() );
+ }
+ else
+ {
+ // Record to a movie using video services.
+ if ( !g_pVideo )
+ return false;
+
+#ifdef USE_WEBM_FOR_REPLAY
+ m_pMovieMaker = g_pVideo->CreateVideoRecorder( VideoSystem::WEBM );
+#else
+ m_pMovieMaker = g_pVideo->CreateVideoRecorder( VideoSystem::QUICKTIME );
+#endif
+ if ( !m_pMovieMaker )
+ return false;
+
+ CFmtStr fmtMovieFullFilename( "%s%s", g_pReplayMovieManager->GetRenderDir(), pMovie->GetMovieFilename() );
+
+ bool bSuccess = false;
+ if ( m_pMovieMaker->CreateNewMovieFile( fmtMovieFullFilename.Access(), true ) )
+ {
+ const ReplayRenderSettings_t &Settings = params.m_Settings;
+
+#ifndef USE_WEBM_FOR_REPLAY
+ ConVarRef QTEncodeGamma( "video_quicktime_encode_gamma" );
+ VideoEncodeGamma_t encodeGamma = ( QTEncodeGamma.IsValid() ) ? (VideoEncodeGamma_t) QTEncodeGamma.GetInt() : VideoEncodeGamma::GAMMA_2_2;
+#else
+ VideoEncodeGamma_t encodeGamma = VideoEncodeGamma::GAMMA_2_2;
+#endif
+
+ if ( m_pMovieMaker->SetMovieVideoParameters( Settings.m_Codec, Settings.m_nEncodingQuality, (int)Settings.m_nWidth, (int)Settings.m_nHeight, Settings.m_FPS, encodeGamma ) )
+ {
+ if ( m_pMovieMaker->SetMovieSourceImageParameters( VideoEncodeSourceFormat::BGRA_32BIT, (int)Settings.m_nWidth, (int)Settings.m_nHeight ) )
+ {
+ AudioEncodeOptions_t audioOptions = AudioEncodeOptions::USE_AUDIO_ENCODE_GROUP_SIZE | AudioEncodeOptions::GROUP_SIZE_IS_VIDEO_FRAME |
+ AudioEncodeOptions::LIMIT_AUDIO_TRACK_TO_VIDEO_DURATION | AudioEncodeOptions::PAD_AUDIO_WITH_SILENCE ;
+
+ if ( m_pMovieMaker->SetMovieSourceAudioParameters( AudioEncodeSourceFormat::AUDIO_16BIT_PCMStereo, 44100, audioOptions ) )
+ {
+ bSuccess = true;
+ }
+ }
+ }
+ }
+
+ if ( !bSuccess )
+ {
+ g_pVideo->DestroyVideoRecorder( m_pMovieMaker );
+ m_pMovieMaker = NULL;
+ return false;
+ }
+ }
+
+ SetupJitterTable();
+#endif
+
+ m_pRenderOverlay->Show();
+
+ return true;
+}
+
+bool CReplayRenderer::SetupJitterTable()
+{
+ const int nNumSamples = NumMotionBlurTimeSteps();
+
+ switch ( nNumSamples )
+ {
+ case 4: m_pJitterTable = g_vJitterTable4; break;
+ case 8: m_pJitterTable = g_vJitterTable8; break;
+ case 16: m_pJitterTable = g_vJitterTable16; break;
+ case 32: m_pJitterTable = g_vJitterTable32; break;
+// case 64: m_pJitterTable = g_vJitterTable64; break;
+// case 128: m_pJitterTable = g_vJitterTable128; break;
+ default: return false;
+ }
+
+ m_nNumJitterSamples = nNumSamples;
+
+ return true;
+}
+
+void CReplayRenderer::InitBuffers( const RenderMovieParams_t ¶ms )
+{
+ const ReplayRenderSettings_t &Settings = params.m_Settings;
+
+ Assert( m_pLayoffBuf == NULL );
+ m_pLayoffBuf = new BGRA8888_t[ Settings.m_nWidth * Settings.m_nHeight ];
+
+ CFmtStr fmtHostFramerateCmd( "host_framerate %f\n", params.m_flEngineFps );
+ engine->ClientCmd_Unrestricted( fmtHostFramerateCmd.Access() );
+
+ g_pMaterialSystem->BeginRenderTargetAllocation(); // Begin allocating RTs which IFM can scribble into
+
+ // Offscreen surface for rendering individual samples
+ ImageFormat AccumSampleFormat = (g_pMaterialSystemHardwareConfig->GetHDRType() == HDR_TYPE_FLOAT) ? IMAGE_FORMAT_RGBA16161616F : g_pMaterialSystem->GetBackBufferFormat();
+ m_AccumBuffSample.Init(
+ g_pMaterialSystem->CreateNamedRenderTargetTextureEx2(
+ "_rt_Replay_Accum_Sample", Settings.m_nWidth, Settings.m_nHeight, RT_SIZE_OFFSCREEN,
+ AccumSampleFormat, MATERIAL_RT_DEPTH_SHARED, TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT | TEXTUREFLAGS_POINTSAMPLE
+ )
+ );
+
+ // Ping-Pong textures for accumulating result prior to final tone map
+ ImageFormat PingPongFormat = IMAGE_FORMAT_BGR888;
+ m_AccumBuffPingPong[0].Init(g_pMaterialSystem->CreateNamedRenderTargetTextureEx2(
+ "_rt_Replay_Ping", Settings.m_nWidth, Settings.m_nHeight, RT_SIZE_OFFSCREEN,
+ PingPongFormat, MATERIAL_RT_DEPTH_NONE, TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT | TEXTUREFLAGS_POINTSAMPLE ));
+ m_AccumBuffPingPong[1].Init(g_pMaterialSystem->CreateNamedRenderTargetTextureEx2(
+ "_rt_Replay_Pong", Settings.m_nWidth, Settings.m_nHeight, RT_SIZE_OFFSCREEN,
+ PingPongFormat, MATERIAL_RT_DEPTH_NONE, TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT | TEXTUREFLAGS_POINTSAMPLE ));
+
+ // LDR final result of either HDR or LDR rendering
+ m_LayoffResult.Init(g_pMaterialSystem->CreateNamedRenderTargetTextureEx2(
+ "_rt_LayoffResult", Settings.m_nWidth, Settings.m_nHeight, RT_SIZE_OFFSCREEN,
+ g_pMaterialSystem->GetBackBufferFormat(), MATERIAL_RT_DEPTH_SHARED, TEXTUREFLAGS_BORDER | TEXTUREFLAGS_POINTSAMPLE ));
+
+ g_pMaterialSystem->EndRenderTargetAllocation(); // Begin allocating RTs which IFM can scribble into
+
+ KeyValues *pVMTKeyValues = new KeyValues( "accumbuff4sample" );
+ pVMTKeyValues->SetString( "$TEXTURE0", m_AccumBuffSample->GetName() ); // Dummy
+ pVMTKeyValues->SetString( "$TEXTURE1", m_AccumBuffSample->GetName() ); // Dummy
+ pVMTKeyValues->SetString( "$TEXTURE2", m_AccumBuffSample->GetName() ); // Dummy
+ pVMTKeyValues->SetString( "$TEXTURE3", m_AccumBuffSample->GetName() ); // Dummy
+ pVMTKeyValues->SetString( "$WEIGHTS", "[0.25 0.75 0.0 0.0]" );
+ pVMTKeyValues->SetInt( "$nocull", 1 );
+ KeyValues *pProxiesKV = pVMTKeyValues->FindKey( "proxies", true ); // create a subkey
+ pProxiesKV->FindKey( "accumbuff4sample", true ); // create
+ m_FourSampleResolveMatRef.Init( "accumbuff4sample", pVMTKeyValues );
+ m_FourSampleResolveMatRef->Refresh();
+}
+
+void CReplayRenderer::ShutdownRenderer()
+{
+ if ( m_LayoffResult.IsValid() )
+ {
+ m_LayoffResult.Shutdown( true );
+ }
+
+ if ( m_AccumBuffSample.IsValid() )
+ {
+ m_AccumBuffSample.Shutdown( true );
+ }
+
+ for ( int i = 0; i < 2; ++i )
+ {
+ if ( m_AccumBuffPingPong[i].IsValid() )
+ {
+ m_AccumBuffPingPong[i].Shutdown( true );
+ }
+ }
+
+ delete [] m_pLayoffBuf;
+ m_pLayoffBuf = NULL;
+
+#ifdef REPLAY_RECORDING_ENABLE
+ if ( m_pMovieMaker )
+ {
+ m_pMovieMaker->FinishMovie( true );
+
+ if ( g_pVideo )
+ {
+ g_pVideo->DestroyVideoRecorder( m_pMovieMaker );
+ }
+
+ m_pMovieMaker = NULL;
+ m_pRenderOverlay->Hide();
+ }
+ else
+#endif
+ if ( m_RenderParams.m_bExportRaw )
+ {
+ // Mimicking what "startmovie" does here.
+ g_pEngineClientReplay->Wave_FixupTmpFile( TMP_WAVE_FILENAME );
+
+ // Move the temp wave file to the destination dir
+ CFmtStr fmtTmpFilename( "%s%c%s.wav", engine->GetGameDirectory(), CORRECT_PATH_SEPARATOR, TMP_WAVE_FILENAME );
+ CFmtStr fmtDstFilename( "%s%s", m_fmtTgaRenderDirName.Access(), "audio.wav" );
+ g_pFullFileSystem->RenameFile( fmtTmpFilename.Access(), fmtDstFilename.Access() );
+ }
+
+ // Reset framerate
+ engine->ClientCmd_Unrestricted( "host_framerate 0" );
+
+ // Notify of performance end
+ g_pReplayPerformanceController->Stop();
+}
+
+void CReplayRenderer::DrawResolvingQuad( int nWidth, int nHeight )
+{
+ CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
+ IMesh *pMesh = pRenderContext->GetDynamicMesh();
+ CMeshBuilder meshBuilder;
+
+ // Epsilons for 1:1 texel to pixel mapping
+ float fWidthEpsilon = IsOSX() ? 0.0f : 0.5f / ((float) nWidth);
+ float fHeightEpsilon = IsOSX() ? 0.0f : 0.5f / ((float) nHeight);
+
+ meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 );
+
+ meshBuilder.Position3f( -1.0f, 1.0f, 0.5f ); // Upper left
+ meshBuilder.TexCoord2f( 0, 0.0f + fWidthEpsilon, 0.0f + fHeightEpsilon );
+ meshBuilder.AdvanceVertex();
+
+ meshBuilder.Position3f( -1.0f, -1.0f, 0.5f ); // Lower left
+ meshBuilder.TexCoord2f( 0, 0.0f + fWidthEpsilon, 1.0f + fHeightEpsilon );
+ meshBuilder.AdvanceVertex();
+
+ meshBuilder.Position3f( 1.0f, -1.0f, 0.5f ); // Lower right
+ meshBuilder.TexCoord2f( 0, 1.0f + fWidthEpsilon, 1.0f + fHeightEpsilon );
+ meshBuilder.AdvanceVertex();
+
+ meshBuilder.Position3f( 1.0f, 1.0f, 0.5f ); // Upper right
+ meshBuilder.TexCoord2f( 0, 1.0f + fWidthEpsilon, 0.0f + fHeightEpsilon );
+ meshBuilder.AdvanceVertex();
+
+ meshBuilder.End();
+ pMesh->Draw();
+}
+
+void CReplayRenderer::BeginRenderingSample( int nSample, int x, int y, int nWidth, int nHeight, float fTonemapScale )
+{
+ // Always start on ping-pong buffer zero
+ if ( nSample == 0 )
+ {
+ m_nCurrentPingPong = 0;
+ }
+
+ CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
+ pRenderContext->PushRenderTargetAndViewport( m_AccumBuffSample, x, y, nWidth, nHeight );
+}
+
+void CReplayRenderer::ResolveSamples( int nSample, DmeTime_t frametime, int x, int y, int nWidth, int nHeight, bool bLayoffResult, float flBloomScale )
+{
+ CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
+
+ // Render resolving quad to current ping-pong buffer
+ AccumParams_t accParms = {
+ m_AccumBuffSample,
+ m_AccumBuffPingPong[ ( m_nCurrentPingPong + 1 ) % 2 ],
+ 1.0f / (float)( nSample + 1 ),
+ false
+ };
+ pRenderContext->Bind( m_FourSampleResolveMatRef, &accParms );
+ pRenderContext->PushRenderTargetAndViewport( m_AccumBuffPingPong[m_nCurrentPingPong], x, y, nWidth, nHeight );
+ DrawResolvingQuad( nWidth, nHeight );
+ pRenderContext->PopRenderTargetAndViewport();
+
+ // If we want to show accumulated result to user...
+ if ( bLayoffResult )
+ {
+ accParms.m_pTexture0 = m_AccumBuffPingPong[m_nCurrentPingPong];
+ accParms.m_pTexture1 = m_AccumBuffPingPong[m_nCurrentPingPong];
+ accParms.m_fSampleWeight = 1.0f;
+ accParms.m_bClear = false;
+ pRenderContext->Bind( m_FourSampleResolveMatRef, &accParms );
+
+ pRenderContext->PushRenderTargetAndViewport( m_LayoffResult, x, y, nWidth, nHeight );
+ DrawResolvingQuad( nWidth, nHeight );
+ pRenderContext->PopRenderTargetAndViewport();
+ }
+
+ m_nCurrentPingPong = (m_nCurrentPingPong + 1) % 2; // Flip the ping-pong buffers
+}
+
+bool CReplayRenderer::IsHDR() const
+{
+ return g_pMaterialSystemHardwareConfig->GetHDRType() == HDR_TYPE_FLOAT;
+}
+
+float CReplayRenderer::GetViewModelFOVOffset()
+{
+// float flVMDefaultFov = m_pViewmodelFov ? m_pViewmodelFov->GetFloat() : 54.0f;
+ float flVMDefaultFov = 54.0f;
+ float flDefaultFov = m_pDefaultFov ? m_pDefaultFov->GetFloat() : 75.0f;
+
+ return flVMDefaultFov - flDefaultFov;
+}
+
+void CReplayRenderer::SetupSampleView( int x, int y, int w, int h, int nSample, CViewSetup& viewSetup )
+{
+ // Frustum stuff
+
+ // FIXME: This currently matches the client DLL for HL2
+ // but we probably need a way of getting this state from the client DLL
+ viewSetup.zNear = 3;
+ viewSetup.zFar = 16384.0f * 1.73205080757f;
+ viewSetup.x = x;
+ viewSetup.y = y;
+ viewSetup.width = w;
+ viewSetup.height = h;
+ viewSetup.m_flAspectRatio = (float)viewSetup.width / (float)viewSetup.height;
+
+ const float fov = viewSetup.fov;
+ float fHalfAngleRadians = DEG2RAD( 0.5f * fov );
+ float t = tan( fHalfAngleRadians ) * (viewSetup.m_flAspectRatio / ( 4.0f / 3.0f ));
+ viewSetup.fov = RAD2DEG( 2.0f * atan( t ) );
+
+ viewSetup.fovViewmodel = viewSetup.fov + GetViewModelFOVOffset();
+ viewSetup.zNearViewmodel = 1;
+ viewSetup.zFarViewmodel = viewSetup.zFar;
+
+ viewSetup.m_bOrtho = false;
+ viewSetup.m_bRenderToSubrectOfLargerScreen = true;
+
+ SetupDOFMatrixSkewView( viewSetup.origin, viewSetup.angles, nSample, viewSetup ); // Sheared matrix method more comparable to image-space DoF approximation
+
+ // Only have the engine do bloom and tone mapping if not HDR
+ viewSetup.m_bDoBloomAndToneMapping = !IsHDR();
+
+ viewSetup.m_bCacheFullSceneState = m_bCacheFullSceneState;
+}
+
+void CReplayRenderer::SetupDOFMatrixSkewView( const Vector &pos, const QAngle &angles, int nSample, CViewSetup& viewSetup )
+{
+ Vector vPosition = pos;
+
+ matrix3x4_t matViewMatrix; // Get transform
+ AngleMatrix( angles, matViewMatrix );
+
+ Vector vViewDirection, vViewLeft, vViewUp;
+ MatrixGetColumn( matViewMatrix, 0, vViewDirection );
+ MatrixGetColumn( matViewMatrix, 1, vViewLeft );
+ MatrixGetColumn( matViewMatrix, 2, vViewUp );
+
+ // Be sure these are normalized
+ vViewDirection.NormalizeInPlace();
+ vViewLeft.NormalizeInPlace();
+ vViewUp.NormalizeInPlace();
+
+ // Set up a non-skewed off-center projection matrix to start with... (Posters already have this set up)
+ viewSetup.m_bOffCenter = true;
+ viewSetup.m_flOffCenterBottom = 0.0f;
+ viewSetup.m_flOffCenterTop = 1.0f;
+ viewSetup.m_flOffCenterLeft = 0.0f;
+ viewSetup.m_flOffCenterRight = 1.0f;
+
+ if ( IsAntialiasingEnabled() && !IsDepthOfFieldEnabled() && !m_bForceCheapDoF ) // AA jitter but no DoF
+ {
+ Vector2D vAAJitter = m_pJitterTable[nSample % m_nNumJitterSamples];
+ const float fHalfPixelRadius = 0.65;
+ viewSetup.m_flOffCenterBottom += (vAAJitter.y / (float) viewSetup.height) * fHalfPixelRadius;
+ viewSetup.m_flOffCenterTop += (vAAJitter.y / (float) viewSetup.height) * fHalfPixelRadius;
+ viewSetup.m_flOffCenterLeft += (vAAJitter.x / (float) viewSetup.width) * fHalfPixelRadius;
+ viewSetup.m_flOffCenterRight += (vAAJitter.x / (float) viewSetup.width) * fHalfPixelRadius;
+
+ viewSetup.origin = vPosition;
+ }
+
+#if 0
+ if ( IsDepthOfFieldEnabled() || m_bForceCheapDoF ) // DoF (independent of AA jitter)
+ {
+ // Try to match the amount of blurriness from legacy fulcrum method
+ const float flDoFHack = 0.0008f;
+ Vector2D vDoFJitter = DepthOfFieldJitter( nSample ) * pCamera->GetAperture() * flDoFHack;
+
+ float fov43 = pCamera->GetFOVx();
+ float fHalfAngleRadians43 = DEG2RAD( 0.5f * fov43 );
+ float t = tan( fHalfAngleRadians43 ) * (viewSetup.m_flAspectRatio / ( 4.0f / 3.0f ));
+
+ float flZFocalWidth = t * pCamera->GetFocalDistance() * 2.0f; // Width of Viewport at Focal plane
+ Vector2D vFocalZJitter = vDoFJitter * flZFocalWidth;
+
+ viewSetup.m_flOffCenterBottom += vDoFJitter.y;
+ viewSetup.m_flOffCenterTop += vDoFJitter.y;
+ viewSetup.m_flOffCenterLeft += vDoFJitter.x;
+ viewSetup.m_flOffCenterRight += vDoFJitter.x;
+
+ viewSetup.origin = vPosition + vViewLeft * vFocalZJitter.x - vViewUp * vFocalZJitter.y * (1.0f / viewSetup.m_flAspectRatio);
+
+ if ( !m_bForceCheapDoF )
+ {
+ Vector2D vAAJitter = g_vJitterTable32[nSample % 32]; // Jitter in addition to DoF offset
+ const float fHalfPixelRadius = 0.6f;
+ viewSetup.m_flOffCenterBottom += (vAAJitter.y / (float) viewSetup.height) * fHalfPixelRadius;
+ viewSetup.m_flOffCenterTop += (vAAJitter.y / (float) viewSetup.height) * fHalfPixelRadius;
+ viewSetup.m_flOffCenterLeft += (vAAJitter.x / (float) viewSetup.width) * fHalfPixelRadius;
+ viewSetup.m_flOffCenterRight += (vAAJitter.x / (float) viewSetup.width) * fHalfPixelRadius;
+ }
+ }
+#endif
+
+ MatrixAngles( matViewMatrix, viewSetup.angles );
+}
+
+int CReplayRenderer::GetMotionBlurQuality() const
+{
+ return m_RenderParams.m_Settings.m_nMotionBlurQuality;
+}
+
+int CReplayRenderer::GetDepthOfFieldQuality() const
+{
+ if ( !IsDepthOfFieldEnabled() )
+ return 0;
+
+ return MAX_DOF_QUALITY;
+}
+
+/*static*/ int CReplayRenderer::GetNumMotionBlurTimeSteps( int nQuality )
+{
+ Assert( nQuality >= 0 && nQuality <= MAX_MOTION_BLUR_QUALITY );
+
+ // Map {0, 1, 2, 3, 4} to {8, 16, 32, 64, 128 }
+ return (int) pow(2.0f, nQuality+2 );
+}
+
+int CReplayRenderer::NumMotionBlurTimeSteps() const
+{
+ return ( IsMotionBlurEnabled() ) ? GetNumMotionBlurTimeSteps( GetMotionBlurQuality() ) : 1;
+}
+
+bool CReplayRenderer::IsMotionBlurEnabled() const
+{
+ return m_RenderParams.m_Settings.m_bMotionBlurEnabled;
+}
+
+bool CReplayRenderer::IsDepthOfFieldEnabled() const
+{
+ return false;
+}
+
+bool CReplayRenderer::IsAntialiasingEnabled() const
+{
+ return m_RenderParams.m_Settings.m_bAAEnabled;
+}
+
+void CReplayRenderer::ComputeSampleCounts( int *pNSamplesPerTimeStep, int *pNTotalSamples ) const
+{
+ *pNSamplesPerTimeStep = *pNTotalSamples = 1;
+
+ if ( IsMotionBlurEnabled() )
+ {
+ *pNTotalSamples *= NumMotionBlurTimeSteps();
+ }
+
+ if ( IsDepthOfFieldEnabled() )
+ {
+ *pNTotalSamples *= s_DoFQualityToSamples[GetDepthOfFieldQuality()];
+ *pNSamplesPerTimeStep *= s_DoFQualityToSamples[GetDepthOfFieldQuality()];
+ }
+}
+
+float CReplayRenderer::GetFramerate() const
+{
+ return m_RenderParams.m_Settings.m_FPS.GetFPS();
+}
+
+double CReplayRenderer::GetShutterSpeed() const
+{
+ return 0.5 / m_RenderParams.m_Settings.m_FPS.GetFPS();
+}
+
+#ifdef TRACE_REPLAY_STATE_MACHINE
+static int nFramesSent = 0;
+#endif
+
+void CReplayRenderer::CompositeAndLayoffFrame( int nFrame )
+{
+ #ifdef TRACE_REPLAY_STATE_MACHINE
+ Msg("CompositeAndLayoffFrame( %3d ) TStep=%d ...... ", nFrame, m_nTimeStep );
+ #endif
+
+ const int nMotionBlurTimeSteps = NumMotionBlurTimeSteps();
+ bool bAppendToMovie = false;
+
+ // Determine if this is a frame we handle audio on
+
+ bool AudioTrigger = (m_nTimeStep == 0) && !m_bShutterClosed;
+ SetAudioSyncFrame( AudioTrigger );
+
+ // If we aren't doing motion blur, just render the frame and add it to the video
+ if ( !IsMotionBlurEnabled() )
+ {
+ m_curSampleTime = DmeTime_t( nFrame, GetFramerate() );
+
+ #ifdef TRACE_REPLAY_STATE_MACHINE
+ Msg( "Rendering Frame at T=%.4f ", m_curSampleTime.GetSeconds() );
+ #endif
+
+ RenderLayoffFrame( m_curSampleTime, 0, 1 ); // Just get one frame
+
+ bAppendToMovie = true;
+ goto render_to_video;
+ }
+
+ // Shutter closed?
+ if ( m_bShutterClosed )
+ {
+ m_nTimeStep++;
+
+ #ifdef TRACE_REPLAY_STATE_MACHINE
+ Msg("Shutter Closed... TStep now %d", m_nTimeStep );
+ #endif
+
+ // If nMotionBlurTimeSteps subframes have passed, open the shutter for the next frame.
+ if ( m_nTimeStep >= nMotionBlurTimeSteps )
+ {
+ Assert( m_nTimeStep == nMotionBlurTimeSteps );
+
+ m_nTimeStep = 0;
+ m_bShutterClosed = false;
+
+ #ifdef TRACE_REPLAY_STATE_MACHINE
+ Msg( ", Shutter OPENED, TStep=0");
+ #endif
+ }
+
+ #ifdef TRACE_REPLAY_STATE_MACHINE
+ ConVarRef HF( "host_framerate" );
+ float frameRate = HF.GetFloat();
+ Msg( ", DONE, ENgine FPS = %f\n", frameRate );
+ #endif
+
+ return;
+ }
+
+ // scope to avoid compiler warnings
+ {
+ // Shutter is open, accumulate sub-frames
+ int nSamplesPerTimeStep = 1;
+ int nNumTotalSamples = 1;
+ ComputeSampleCounts( &nSamplesPerTimeStep, &nNumTotalSamples );
+
+ double frameTime = DmeTime_t( nFrame, GetFramerate() ).GetSeconds();
+ DmeTime_t timeStepSize( GetShutterSpeed() );
+ DmeTime_t remainderStepSize( DmeTime_t( 1, GetFramerate() ) - timeStepSize );
+
+ Assert( timeStepSize.GetSeconds() > 0.0 );
+
+ DmeTime_t curSampleTime( frameTime );
+
+ #ifdef TRACE_REPLAY_STATE_MACHINE
+ Msg("FrameT=%.4lf ", frameTime );
+ #endif
+
+
+ timeStepSize /= nMotionBlurTimeSteps;
+
+ curSampleTime -= timeStepSize * ( nMotionBlurTimeSteps - 1 ) / 2.0f;
+
+ // Loop through all samples for the current timestep, jittering the camera if antialiasing is enabled.
+
+ #ifdef TRACE_REPLAY_STATE_MACHINE
+ Msg(" Shutter's Open, Rendering %d Sub-Frames ", nSamplesPerTimeStep );
+ Msg( "Frame %i: Laying off sub frame at time step %i \n", nFrame, m_nTimeStep );
+ #endif
+
+ RenderLayoffFrame( m_curSampleTime, m_nCurSample++, nNumTotalSamples );
+
+ ++m_nTimeStep;
+ m_curSampleTime += timeStepSize;
+
+ // Catch the very last motionblur timestep and append to movie
+ if ( m_nTimeStep == nMotionBlurTimeSteps )
+ {
+ #ifdef TRACE_REPLAY_STATE_MACHINE
+ Msg( " TStep=Max, Append=TRUE ... ");
+ #endif
+
+ m_nTimeStep = 0;
+ m_nCurSample = 0;
+ m_curSampleTime = curSampleTime;
+ m_bShutterClosed = true; // Close or open the shutter for nMotionBlurTimeSteps subframes
+ bAppendToMovie = true; // Add a frame to the movie we've just closed the shutter
+ }
+ }
+
+render_to_video:
+ // Append the frame to the movie?
+ if ( bAppendToMovie )
+ {
+ #ifdef TRACE_REPLAY_STATE_MACHINE
+ Msg(" -- Appending Frame %d to Movie\n", nFramesSent ); nFramesSent++;
+ #endif
+
+ CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
+ pRenderContext->PushRenderTargetAndViewport( m_LayoffResult );
+
+ // Add this frame to the movie
+ LayoffFrame( nFrame );
+
+ pRenderContext->PopRenderTargetAndViewport();
+ }
+
+ #ifdef TRACE_REPLAY_STATE_MACHINE
+ Msg("\n");
+ #endif
+}
+
+
+void CReplayRenderer::LayoffFrame( int nFrame )
+{
+ VPROF_BUDGET( "CReplayRenderer::LayoffFrame", VPROF_BUDGETGROUP_REPLAY );
+ // FIXME: This is somewhat of a hack to get layoff working again
+ // We're rendering into the full preview size, but stretching down to the actual size
+ Rect_t srcRect;
+ srcRect.x = 0;
+ srcRect.y = 0;
+ srcRect.width = m_RenderParams.m_Settings.m_nWidth;
+ srcRect.height = m_RenderParams.m_Settings.m_nHeight;
+
+ Rect_t dstRect;
+ dstRect.x = 0;
+ dstRect.y = 0;
+ dstRect.width = m_RenderParams.m_Settings.m_nWidth;
+ dstRect.height = m_RenderParams.m_Settings.m_nHeight;
+
+ #ifdef TRACE_REPLAY_STATE_MACHINE
+ Msg( "laying off movie frame %i\n", nFrame );
+ #endif
+
+ CMatRenderContextPtr pRenderContext( materials );
+// pRenderContext->ReadPixelsAndStretch( &srcRect, &dstRect, (unsigned char*)m_pLayoffBuf,
+// IMAGE_FORMAT_BGRA8888, dstRect.width * ImageLoader::SizeInBytes( IMAGE_FORMAT_BGRA8888 ) );
+
+ pRenderContext->ReadPixels( 0, 0, (int) m_RenderParams.m_Settings.m_nWidth, (int) m_RenderParams.m_Settings.m_nHeight, (unsigned char*)m_pLayoffBuf, IMAGE_FORMAT_BGRA8888 );
+
+ static ConVarRef mat_queue_mode( "mat_queue_mode" );
+
+ // Encode the frame
+#ifdef REPLAY_RECORDING_ENABLE
+ if ( m_RenderParams.m_bExportRaw )
+ {
+ CUtlBuffer bufOut;
+ if ( TGAWriter::WriteToBuffer( (unsigned char *)m_pLayoffBuf, bufOut, m_RenderParams.m_Settings.m_nWidth,
+ m_RenderParams.m_Settings.m_nHeight, IMAGE_FORMAT_BGRA8888, IMAGE_FORMAT_RGB888 ) )
+ {
+ // Format filename and write the TGA
+ CFmtStr fmtFilename(
+ "%sFrame_%04i.tga",
+ m_fmtTgaRenderDirName.Access(),
+ m_iTgaFrame++
+ );
+
+ if ( !g_pFullFileSystem->WriteFile( fmtFilename.Access(), NULL, bufOut ) )
+ {
+ Warning( "Couldn't write bitmap data snapshot to file %s.\n", fmtFilename.Access() );
+ }
+ }
+ }
+ else if ( m_pMovieMaker )
+ {
+ // can't run in any other mode
+ Assert( mat_queue_mode.GetInt() == 0 );
+ VPROF_BUDGET( "CReplayRenderer::LayoffFrame - AppendVideoFrame", VPROF_BUDGETGROUP_REPLAY );
+ m_pMovieMaker->AppendVideoFrame( m_pLayoffBuf );
+ }
+#endif
+}
+
+void CReplayRenderer::GetViewSetup( CViewSetup &viewsetup )
+{
+ extern ConVar v_viewmodel_fov;
+
+ viewsetup = *view->GetPlayerViewSetup();
+
+ // HACK: Override the view - this will keep the view from popping if the user toggles the render preview checkbox.
+ ReplayCamera()->CalcView( viewsetup.origin, viewsetup.angles, viewsetup.fov );
+ viewsetup .fovViewmodel = ScaleFOVByWidthRatio( v_viewmodel_fov.GetFloat(), viewsetup.m_flAspectRatio / ( 4.0f / 3.0f ) );
+}
+
+void CReplayRenderer::RenderLayoffFrame( DmeTime_t time, int nCurSample, int nNumTotalSamples )
+{
+ CViewSetup viewSetup;
+ GetViewSetup( viewSetup );
+
+ int x=0, y=0, w=m_RenderParams.m_Settings.m_nWidth, h=m_RenderParams.m_Settings.m_nHeight;
+
+ // FIXME: Using the preview size here is something of a hack
+ // to get layoff working again. We're actually going to stretch down from the preview size to layoff size
+ // during frame capture
+ float fTonemapScale = 0.28f;
+ BeginRenderingSample( nCurSample, x, y, w, h, fTonemapScale);
+
+ // Initialize view setup for this sample
+ SetupSampleView( 0, 0, w, h, nCurSample, viewSetup );
+
+ const int flags = RENDERVIEW_DRAWVIEWMODEL;
+
+ // Tell the engine to tell the client to render the view (sans viewmodel)
+ view->RenderView( viewSetup, VIEW_CLEAR_COLOR | VIEW_CLEAR_DEPTH, flags );
+
+ // Resolve the accumulation buffer samples for display this frame
+ float fBloomScale = 0.28f;
+ bool bRenderFinalFrame = nCurSample == ( nNumTotalSamples - 1 );
+ ResolveSamples( nCurSample, time, 0, 0, w, h, bRenderFinalFrame, fBloomScale );
+
+ // Pop the target
+ CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
+ pRenderContext->PopRenderTargetAndViewport();
+}
+
+void CReplayRenderer::EndRendering()
+{
+ CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
+ pRenderContext->PopRenderTargetAndViewport();
+}
+
+void CReplayRenderer::ClearToBlack( CTextureReference &buf, int x, int y, int nWidth, int nHeight )
+{
+ CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
+
+ // Bind the resolving material
+ AccumParams_t accParms = { m_AccumBuffSample, m_AccumBuffSample, 0.0f, true }; // true to clear to black
+ pRenderContext->Bind( m_FourSampleResolveMatRef, &accParms );
+
+ // Render black quad to the layoff result
+ pRenderContext->PushRenderTargetAndViewport( buf, x, y, nWidth, nHeight );
+ DrawResolvingQuad( nWidth, nHeight );
+ pRenderContext->PopRenderTargetAndViewport();
+}
+
+void CReplayRenderer::RenderVideo()
+{
+#if _DEBUG
+ static ConVarRef replay_fake_render( "replay_fake_render" );
+ if ( replay_fake_render.IsValid() && replay_fake_render.GetBool() )
+ return;
+#endif
+
+ if ( !engine->IsInGame() )
+ return;
+
+ if ( !m_LayoffResult.IsValid() )
+ return;
+
+ CompositeAndLayoffFrame( m_nFrame++ );
+}
+
+void CReplayRenderer::RenderAudio( unsigned char *pBuffer, int nSize, int nNumSamples )
+{
+#ifdef REPLAY_RECORDING_ENABLE
+ if ( m_RenderParams.m_bExportRaw )
+ {
+ g_pEngineClientReplay->Wave_AppendTmpFile( TMP_WAVE_FILENAME, pBuffer, nNumSamples );
+ }
+ else if ( m_pMovieMaker )
+ {
+ m_pMovieMaker->AppendAudioSamples( pBuffer, (size_t)nSize );
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+
+#endif
diff --git a/mp/src/game/client/replay/replayrenderer.h b/mp/src/game/client/replay/replayrenderer.h new file mode 100644 index 00000000..0a6b94fa --- /dev/null +++ b/mp/src/game/client/replay/replayrenderer.h @@ -0,0 +1,111 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#ifndef REPLAYRENDERER_H
+#define REPLAYRENDERER_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+//-----------------------------------------------------------------------------
+
+#include "replay/ireplaymovierenderer.h"
+#include "replay/rendermovieparams.h"
+#include "movieobjects/timeutils.h"
+#include "fmtstr.h"
+
+//-----------------------------------------------------------------------------
+
+class CReplay;
+class CReplayPerformance;
+class IQuickTimeMovieMaker;
+class CReplayRenderOverlay;
+class IVideoRecorder;
+
+//-----------------------------------------------------------------------------
+
+class CReplayRenderer : public IReplayMovieRenderer
+{
+public:
+ CReplayRenderer( CReplayRenderOverlay *pOverlay );
+ ~CReplayRenderer();
+
+ const CReplayPerformance *GetPerformance() const;
+ const char *GetMovieFilename() const;
+ static int GetNumMotionBlurTimeSteps( int nQuality );
+
+ //
+ // IReplayMovieRenderer interface
+ //
+ virtual bool SetupRenderer( RenderMovieParams_t ¶ms, IReplayMovie *pMovie );
+ virtual void ShutdownRenderer();
+ virtual void RenderVideo();
+ virtual void RenderAudio( unsigned char *pBuffer, int nSize, int nNumSamples );
+
+ virtual void SetAudioSyncFrame( bool isSync = false );
+ virtual bool IsAudioSyncFrame();
+ virtual float GetRecordingFrameDuration();
+
+
+private:
+ bool IsDepthOfFieldEnabled() const;
+ bool IsAntialiasingEnabled() const;
+ bool IsHDR() const;
+ bool IsMotionBlurEnabled() const;
+ int GetMotionBlurQuality() const;
+ int GetDepthOfFieldQuality() const;
+ int NumMotionBlurTimeSteps() const;
+ float GetFramerate() const;
+ void ComputeSampleCounts( int *pNSamplesPerTimeStep, int *pNTotalSamples ) const;
+ float GetViewModelFOVOffset();
+ void SetupDOFMatrixSkewView( const Vector &pos, const QAngle &angles, int nSample, CViewSetup& viewSetup );
+ void BeginRenderingSample( int nSample, int x, int y, int nWidth, int nHeight, float fTonemapScale );
+ void EndRendering();
+ void SetupSampleView( int x, int y, int w, int h, int nSample, CViewSetup& viewSetup );
+ void InitBuffers( const RenderMovieParams_t ¶ms );
+ void DrawResolvingQuad( int nWidth, int nHeight );
+ float GetRenderLength( const CReplay *pReplay );
+ void CompositeAndLayoffFrame( int nFrame );
+ void RenderLayoffFrame( DmeTime_t time, int nCurSample, int nNumTotalSamples );
+ void ResolveSamples( int nSample, DmeTime_t frametime, int x, int y, int nWidth, int nHeight, bool bShowUser, float flBloomScale );
+ void LayoffFrame( int nFrame );
+ double GetShutterSpeed() const;
+ void ClearToBlack( CTextureReference &buf, int x, int y, int nWidth, int nHeight );
+ bool SetupJitterTable();
+ void GetViewSetup( CViewSetup &viewsetup );
+
+ bool m_bIsAudioSyncFrame;
+
+ const Vector2D *m_pJitterTable;
+ int m_nNumJitterSamples;
+ IVideoRecorder *m_pMovieMaker;
+ bool m_bCacheFullSceneState;
+ BGRA8888_t *m_pLayoffBuf;
+ IReplayMovie *m_pMovie;
+ RenderMovieParams_t m_RenderParams;
+ CTextureReference m_AccumBuffSample;
+ CTextureReference m_LayoffResult;
+ CTextureReference m_AccumBuffPingPong[2]; // Buffers and materials for ping-pong accumulation buffer
+ CMaterialReference m_FourSampleResolveMatRef;
+ bool m_bForceCheapDoF;
+ bool m_bShutterClosed;
+ int m_nCurrentPingPong;
+ int m_nFrame;
+
+ ConVar *m_pViewmodelFov;
+ ConVar *m_pDefaultFov;
+
+ int m_nTimeStep;
+ int m_nCurSample;
+ DmeTime_t m_curSampleTime;
+
+ int m_iTgaFrame;
+ CFmtStr m_fmtTgaRenderDirName;
+
+ CReplayRenderOverlay *m_pRenderOverlay;
+};
+
+//-----------------------------------------------------------------------------
+
+#endif // REPLAYRENDERER_H
diff --git a/mp/src/game/client/replay/replayvideo.cpp b/mp/src/game/client/replay/replayvideo.cpp new file mode 100644 index 00000000..57c0c6de --- /dev/null +++ b/mp/src/game/client/replay/replayvideo.cpp @@ -0,0 +1,142 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "replayvideo.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+
+static ReplayVideoMode_t s_VideoModes[] =
+{
+ { 720, 480, 24, true, "#Replay_Res_480p" },
+ { 1280, 720, 24, true, "#Replay_Res_720p" },
+ { 1920, 1080, 24, true, "#Replay_Res_1080p" },
+
+ { 320, 240, 15, false, "#Replay_Res_Web" },
+ { 960, 640, 24, true, "#Replay_Res_iPhone_Horizontal" },
+ { 640, 960, 24, true, "#Replay_Res_iPhone_Vertical" },
+};
+
+#define VIDEO_MODE_COUNT ( sizeof( s_VideoModes ) / sizeof( s_VideoModes[0] ) )
+
+//-----------------------------------------------------------------------------
+#ifdef USE_WEBM_FOR_REPLAY
+static ReplayCodec_t s_Codecs[] =
+{
+ { VideoEncodeCodec::WEBM_CODEC, "#Replay_Codec_WEBM" },
+};
+static int s_nNumCodecs = ARRAYSIZE( s_Codecs );
+
+//-----------------------------------------------------------------------------
+
+static ReplayQualityPreset_t s_QualityPresets[] =
+{
+ { "#Replay_RenderSetting_Low", VideoEncodeCodec::WEBM_CODEC, 0, false, 0 },
+ { "#Replay_RenderSetting_Medium", VideoEncodeCodec::WEBM_CODEC, 50, false, 1 },
+ { "#Replay_RenderSetting_High", VideoEncodeCodec::WEBM_CODEC, 100, true, 2 },
+ { "#Replay_RenderSetting_Max", VideoEncodeCodec::WEBM_CODEC, 100, true, 3 },
+};
+static int s_NumQualityPresets = ARRAYSIZE( s_QualityPresets );
+static int s_DefaultQualityPreset = 1;
+
+#else
+static ReplayCodec_t s_Codecs[] =
+{
+ { VideoEncodeCodec::MJPEG_A_CODEC, "#Replay_Codec_MJPEGA" },
+ { VideoEncodeCodec::H264_CODEC, "#Replay_Codec_H264" },
+};
+static int s_nNumCodecs = ARRAYSIZE( s_Codecs );
+
+//-----------------------------------------------------------------------------
+
+static ReplayQualityPreset_t s_QualityPresets[] =
+{
+ { "#Replay_RenderSetting_Low", VideoEncodeCodec::MJPEG_A_CODEC, 0, false, 0 },
+ { "#Replay_RenderSetting_Medium", VideoEncodeCodec::MJPEG_A_CODEC, 50, false, 1 },
+ { "#Replay_RenderSetting_High", VideoEncodeCodec::MJPEG_A_CODEC, 100, true, 2 },
+ { "#Replay_RenderSetting_Max", VideoEncodeCodec::H264_CODEC, 100, true, 3 },
+};
+static int s_NumQualityPresets = ARRAYSIZE( s_QualityPresets );
+static int s_DefaultQualityPreset = 1;
+
+#endif
+
+
+//-----------------------------------------------------------------------------
+
+static const int s_QualityRange = 4;
+static const int s_QualityInterval = 100 / s_QualityRange;
+
+//-----------------------------------------------------------------------------
+
+int ReplayVideo_GetVideoModeCount()
+{
+ return VIDEO_MODE_COUNT;
+}
+
+const ReplayVideoMode_t &ReplayVideo_GetVideoMode( int i )
+{
+ AssertMsg( i >= 0 && i < VIDEO_MODE_COUNT, "Replay video mode out of range!" );
+ return s_VideoModes[ i ];
+}
+
+int ReplayVideo_GetDefaultQualityPreset()
+{
+ return s_DefaultQualityPreset;
+}
+
+int ReplayVideo_GetQualityInterval()
+{
+ return s_QualityInterval;
+}
+
+int ReplayVideo_GetQualityRange()
+{
+ return s_QualityRange;
+}
+
+int ReplayVideo_GetQualityPresetCount()
+{
+ return s_NumQualityPresets;
+}
+
+const ReplayQualityPreset_t &ReplayVideo_GetQualityPreset( int i )
+{
+ return s_QualityPresets[ i ];
+}
+
+int ReplayVideo_GetCodecCount()
+{
+ return s_nNumCodecs;
+}
+
+const ReplayCodec_t &ReplayVideo_GetCodec( int i )
+{
+ AssertMsg( i >= 0 && i < s_nNumCodecs, "Replay codec out of range!" );
+ return s_Codecs[ i ];
+}
+
+int ReplayVideo_FindCodecPresetFromCodec( VideoEncodeCodec_t nCodecId )
+{
+ AssertMsg( nCodecId < VideoEncodeCodec::CODEC_COUNT, "Codec ID out of range!" );
+ for ( int i = 0; i < VideoEncodeCodec::CODEC_COUNT; ++i )
+ {
+ if ( s_Codecs[ i ].m_nCodecId == nCodecId )
+ return i;
+ }
+
+ AssertMsg( 0, "Codec not found! This should never happen!" );
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+
+#endif
diff --git a/mp/src/game/client/replay/replayvideo.h b/mp/src/game/client/replay/replayvideo.h new file mode 100644 index 00000000..1c33bfa4 --- /dev/null +++ b/mp/src/game/client/replay/replayvideo.h @@ -0,0 +1,59 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#ifndef REPLAYVIDEO_H
+#define REPLAYVIDEO_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+//-----------------------------------------------------------------------------
+
+#include "video/ivideoservices.h"
+
+//-----------------------------------------------------------------------------
+
+struct ReplayVideoMode_t
+{
+ int m_nWidth;
+ int m_nHeight;
+ int m_nBaseFPS;
+ bool m_bNTSCRate;
+ const char *m_pName; // Can be a localization token, e.g. "#Replay_Blah"
+};
+
+struct ReplayQualityPreset_t
+{
+ const char *m_pName;
+ VideoEncodeCodec::EVideoEncodeCodec_t m_nCodecId;
+ int m_iQuality;
+ bool m_bMotionBlurEnabled;
+ int m_iMotionBlurQuality;
+};
+
+struct ReplayCodec_t
+{
+ VideoEncodeCodec::EVideoEncodeCodec_t m_nCodecId;
+ const char *m_pName;
+};
+
+//-----------------------------------------------------------------------------
+
+int ReplayVideo_GetVideoModeCount();
+const ReplayVideoMode_t &ReplayVideo_GetVideoMode( int i );
+
+int ReplayVideo_GetDefaultQualityPreset();
+int ReplayVideo_GetQualityInterval(); // TODO: Wtf is this?
+int ReplayVideo_GetQualityRange();
+int ReplayVideo_GetQualityPresetCount();
+const ReplayQualityPreset_t &ReplayVideo_GetQualityPreset( int i );
+
+int ReplayVideo_GetCodecCount();
+const ReplayCodec_t &ReplayVideo_GetCodec( int i );
+
+int ReplayVideo_FindCodecPresetFromCodec( VideoEncodeCodec_t nCodec );
+
+//-----------------------------------------------------------------------------
+
+#endif // REPLAYVIDEO_H
diff --git a/mp/src/game/client/replay/replayyoutubeapi.cpp b/mp/src/game/client/replay/replayyoutubeapi.cpp new file mode 100644 index 00000000..649416b8 --- /dev/null +++ b/mp/src/game/client/replay/replayyoutubeapi.cpp @@ -0,0 +1,649 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "replayyoutubeapi.h"
+
+#include "tier1/KeyValues.h"
+
+#include "replay/ireplaymovie.h"
+#include "replay/ireplaymoviemanager.h"
+#include "replay/genericclassbased_replay.h"
+#include "vgui/ISurface.h"
+#include "vgui/ILocalize.h"
+#include "vgui_controls/ProgressBar.h"
+#include "vgui_controls/TextEntry.h"
+#include "vgui_controls/ScrollBar.h"
+
+#include "confirm_dialog.h"
+#include "replay/vgui/replaybrowserdetailspanel.h"
+
+#include "base_gcmessages.h"
+
+#include "youtubeapi.h"
+#include "steamworks_gamestats.h"
+#include "replayvideo.h"
+
+#include "gc_clientsystem.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+using namespace vgui;
+
+static ConVar youtube_username( "youtube_username", "", FCVAR_ARCHIVE, "Username for YouTube." );
+
+//-----------------------------------------------------------------------------
+
+extern IReplayMovieManager *g_pReplayMovieManager;
+extern const char *COM_GetModDirectory();
+
+//-----------------------------------------------------------------------------
+
+static bool HasInvalidCharacters( const char *pString )
+{
+ while ( *pString != 0 )
+ {
+ switch ( *pString )
+ {
+ case '<': return true;
+ case '>': return true;
+ case '&': return true;
+ }
+ ++pString;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+
+void UploadOgsData( IReplayMovie *pMovie, bool bEnded = false, const char *pEndReason = NULL )
+{
+ static int s_nUploadCounter = 0;
+
+ KeyValues* pKVData = new KeyValues( "TF2ReplayUploads" );
+
+ pKVData->SetInt( "UploadCounter", s_nUploadCounter++ );
+ pKVData->SetInt( "ReplayHandle", (int)pMovie->GetReplayHandle() );
+
+ const ReplayRenderSettings_t &RenderSettings = pMovie->GetRenderSettings();
+ CFmtStr fmtResolution( "%ix%i", RenderSettings.m_nWidth, RenderSettings.m_nHeight );
+ pKVData->SetString( "ResolutionID", fmtResolution.Access() );
+
+ pKVData->SetString( "CodecID", ReplayVideo_GetCodec( ReplayVideo_FindCodecPresetFromCodec( RenderSettings.m_Codec ) ).m_pName );
+ pKVData->SetInt( "MotionBlurQuality", RenderSettings.m_nMotionBlurQuality );
+ pKVData->SetInt( "RenderQuality", RenderSettings.m_nEncodingQuality );
+ pKVData->SetInt( "FPSUPF", RenderSettings.m_FPS.GetUnitsPerFrame() );
+ pKVData->SetInt( "FPSUPS", RenderSettings.m_FPS.GetUnitsPerSecond() );
+
+ if ( bEnded )
+ {
+ pKVData->SetInt( "EndUploadTime", GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch() );
+ pKVData->SetString( "EndUploadReasonID", pEndReason );
+ }
+ else
+ {
+ pKVData->SetInt( "StartUploadTime", GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch() );
+ }
+
+ GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
+}
+
+//-----------------------------------------------------------------------------
+
+class CYouTubeLoginWaitDialog : public CGenericWaitingDialog
+{
+ DECLARE_CLASS_SIMPLE( CYouTubeLoginWaitDialog, CGenericWaitingDialog );
+public:
+ CYouTubeLoginWaitDialog( IReplayMovie *pMovie, CConfirmDialog *pLoginDialog )
+ : CGenericWaitingDialog( pLoginDialog->GetParent() )
+ , m_pMovie( pMovie )
+ , m_pLoginDialog( pLoginDialog )
+ {
+ }
+
+ virtual void OnUserClose()
+ {
+ BaseClass::OnUserClose();
+ YouTube_LoginCancel();
+ ShowMessageBox( "#YouTube_LoginResults_Title", "#YouTube_LoginResults_Cancel", "#GameUI_OK" );
+ }
+
+ virtual void OnTick()
+ {
+ BaseClass::OnTick();
+
+ eYouTubeLoginStatus loginStatus = YouTube_GetLoginStatus();
+ switch ( loginStatus )
+ {
+ case kYouTubeLogin_NotLoggedIn:
+ break;
+ case kYouTubeLogin_LoggedIn:
+ Close();
+ PostMessage( m_pLoginDialog, new KeyValues("Command", "command", "cancel" ), NULL);
+ YouTube_ShowUploadDialog( m_pMovie, m_pLoginDialog->GetParent() );
+ break;
+ case kYouTubeLogin_CouldNotConnect:
+ Close();
+ ShowMessageBox( "#YouTube_LoginResults_Title", "#YouTube_LoginResults_CouldNotConnect", "#GameUI_OK" );
+ break;
+ case kYouTubeLogin_Forbidden:
+ Close();
+ ShowMessageBox( "#YouTube_LoginResults_Title", "#YouTube_LoginResults_Forbidden", "#GameUI_OK" );
+ break;
+ case kYouTubeLogin_GenericFailure:
+ default:
+ Close();
+ ShowMessageBox( "#YouTube_LoginResults_Title", "#YouTube_LoginResults_Failure", "#GameUI_OK" );
+ break;
+ }
+ }
+
+private:
+ IReplayMovie *m_pMovie;
+ CConfirmDialog *m_pLoginDialog;
+};
+
+class CYouTubeUploadWaitDialog : public CGenericWaitingDialog
+{
+ DECLARE_CLASS_SIMPLE( CYouTubeUploadWaitDialog, CGenericWaitingDialog );
+public:
+ CYouTubeUploadWaitDialog( IReplayMovie *pMovie, const char *pTitle, const char *pDescription, YouTubeUploadHandle_t handle, vgui::Panel *pParent )
+ : CGenericWaitingDialog( pParent )
+ , m_pMovie( pMovie )
+ , m_strTitle( pTitle )
+ , m_strDescription( pDescription )
+ , m_uploadHandle( handle )
+ , m_iTick( 0 )
+ {
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+ m_pProgressBar = dynamic_cast< ProgressBar * >( FindChildByName( "Progress" ) );
+ }
+
+ virtual void PerformLayout()
+ {
+ BaseClass::PerformLayout();
+ if ( m_pProgressBar )
+ {
+ m_pProgressBar->SetVisible( true );
+ m_pProgressBar->SetSegmentInfo( XRES(1), XRES(5) );
+ }
+ }
+
+ virtual const char* GetResFile() const { return "resource/UI/YouTubeUploadWaitingDialog.res"; }
+ virtual const char *GetResFilePathId() const { return "GAME"; }
+
+ virtual void OnUserClose()
+ {
+ BaseClass::OnUserClose();
+ YouTube_CancelUpload( m_uploadHandle );
+ UploadOgsData( m_pMovie, true, "cancelled" );
+ }
+
+ virtual void OnTick()
+ {
+ BaseClass::OnTick();
+
+ double ultotal = 0.0;
+ double ulnow = 0.0;
+ if ( YouTube_GetUploadProgress( m_uploadHandle, ultotal, ulnow ) == false )
+ {
+ Close();
+ ShowMessageBox( "#YouTube_Upload_Title", "#YouTube_Upload_Failure", "#GameUI_OK" );
+ }
+ else if ( YouTube_IsUploadFinished( m_uploadHandle ) )
+ {
+ bool bSuccess = true;
+ CUtlString strURLToVideo;
+ CUtlString strURLToVideoStats;
+ if ( YouTube_GetUploadResults( m_uploadHandle, bSuccess, strURLToVideo, strURLToVideoStats ) && bSuccess )
+ {
+ // mark movie uploaded
+ m_pMovie->SetUploaded( true );
+ m_pMovie->SetUploadURL( strURLToVideoStats.Get() );
+ g_pReplayMovieManager->FlagMovieForFlush( m_pMovie, true );
+ // update the UI
+ CReplayDetailsPanel *pDetailsPanel = dynamic_cast< CReplayDetailsPanel *>( GetParent() );
+ if ( pDetailsPanel )
+ {
+ pDetailsPanel->InvalidateLayout( true, false );
+ }
+ ShowMessageBox( "#YouTube_Upload_Title", "#YouTube_Upload_Success", "#GameUI_OK", NULL, GetParent() );
+
+ // notify the GC
+ uint64 uSessionId = g_pClientReplayContext->GetServerSessionId( m_pMovie->GetReplayHandle() );
+ if ( uSessionId != 0 )
+ {
+ GCSDK::CProtoBufMsg< CMsgReplayUploadedToYouTube > msg( k_EMsgGCReplay_UploadedToYouTube );
+ msg.Body().set_youtube_url( strURLToVideoStats.Get() );
+ msg.Body().set_youtube_account_name( YouTube_GetLoginName() );
+ msg.Body().set_session_id( uSessionId );
+ GCClientSystem()->BSendMessage( msg );
+ }
+
+ surface()->PlaySound( "replay\\youtube_uploadfinished.wav" );
+
+ UploadOgsData( m_pMovie, true, "completed" );
+
+ // share on Steam Community
+ if ( steamapicontext && steamapicontext->SteamRemoteStorage() )
+ {
+ CUtlString strPreviewFileName;
+ AppId_t nConsumerAppId = steamapicontext->SteamUtils()->GetAppID();
+ ERemoteStoragePublishedFileVisibility eVisibility = k_ERemoteStoragePublishedFileVisibilityPublic;
+ SteamParamStringArray_t tags;
+ tags.m_ppStrings = NULL;
+ tags.m_nNumStrings = 0;
+
+ // don't bother waiting for result
+ SteamAPICall_t hSteamAPICall = steamapicontext->SteamRemoteStorage()->PublishVideo(
+ k_EWorkshopVideoProviderNone, "",
+ strURLToVideo.Get(),
+ strPreviewFileName.Get(),
+ nConsumerAppId,
+ m_strTitle.Get(),
+ m_strDescription.Get(),
+ eVisibility,
+ &tags
+ );
+ hSteamAPICall;
+ }
+ }
+ else
+ {
+ ShowMessageBox( "#YouTube_Upload_Title", "#YouTube_Upload_Failure", "#GameUI_OK" );
+
+ surface()->PlaySound( "replay\\youtube_uploadfailed.wav" );
+
+ UploadOgsData( m_pMovie, true, "failed" );
+ }
+ // close wait dialog
+ YouTube_ClearUploadResults( m_uploadHandle );
+ Close();
+ }
+ else
+ {
+ float flProgress = MIN( ulnow / MAX( ultotal, 1.0 ), 1.0f );
+ int iProgress = int( 100.0 * flProgress );
+ int ulnow_kb = uint32( ulnow / 1024 );
+ int ultotal_kb = uint32( ultotal / 1024 );
+
+ if ( ulnow_kb == ultotal_kb )
+ {
+ // we tick at 500 ms, so this should be ok
+ m_iTick = ( m_iTick + 1 ) % 4;
+ switch ( m_iTick )
+ {
+ case 0: SetDialogVariable( "duration", g_pVGuiLocalize->Find( "YouTube_UploadFinishing1" ) ); break;
+ case 1: SetDialogVariable( "duration", g_pVGuiLocalize->Find( "YouTube_UploadFinishing2" ) ); break;
+ case 2: SetDialogVariable( "duration", g_pVGuiLocalize->Find( "YouTube_UploadFinishing3" ) ); break;
+ case 3: SetDialogVariable( "duration", g_pVGuiLocalize->Find( "YouTube_UploadFinishing4" ) ); break;
+ }
+ }
+ else
+ {
+ wchar_t wszProgress[1024];
+ wchar_t wszPercentage[32];
+ wchar_t wszNow[32];
+ wchar_t wszTotal[32];
+ _snwprintf( wszPercentage, ARRAYSIZE( wszPercentage ), L"%u", iProgress );
+ _snwprintf( wszNow, ARRAYSIZE( wszNow ), L"%u", ulnow_kb );
+ _snwprintf( wszTotal, ARRAYSIZE( wszTotal ), L"%u", ultotal_kb );
+ g_pVGuiLocalize->ConstructString( wszProgress,sizeof( wszProgress ), g_pVGuiLocalize->Find( "#YouTube_UploadProgress" ), 3,
+ wszPercentage,
+ wszNow,
+ wszTotal );
+
+ SetDialogVariable( "duration", wszProgress );
+ }
+
+ if ( m_pProgressBar )
+ {
+ m_pProgressBar->SetProgress( flProgress );
+ }
+ }
+ }
+
+private:
+ IReplayMovie *m_pMovie;
+ YouTubeUploadHandle_t m_uploadHandle;
+ CUtlString m_strTitle;
+ CUtlString m_strDescription;
+ ProgressBar *m_pProgressBar;
+ int m_iTick;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Dialog for logging into YouTube
+//-----------------------------------------------------------------------------
+class CYouTubeLoginDialog : public CConfirmDialog
+{
+ DECLARE_CLASS_SIMPLE( CYouTubeLoginDialog, CConfirmDialog );
+public:
+ CYouTubeLoginDialog( IReplayMovie *pMovie, Panel *pParent ) : BaseClass( pParent ), m_pMovie( pMovie ) {}
+
+ const wchar_t *GetText() { return NULL; }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+ TextEntry *pTextEntryUserName = dynamic_cast< TextEntry * >( FindChildByName( "UserNameTextEntry" ) );
+ if ( pTextEntryUserName )
+ {
+ pTextEntryUserName->SetText( "" );
+ pTextEntryUserName->InsertString( youtube_username.GetString() );
+ }
+ }
+
+ virtual void OnCommand( const char *command )
+ {
+ if ( !Q_strnicmp( command, "register", 8 ) )
+ {
+ if ( steamapicontext && steamapicontext->SteamFriends() )
+ {
+ steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( "http://www.youtube.com/create_account?next=/" );
+ }
+ }
+ else if ( !Q_strnicmp( command, "confirm", 7 ) )
+ {
+ TextEntry *pTextEntryUserName = dynamic_cast< TextEntry * >( FindChildByName( "UserNameTextEntry" ) );
+ TextEntry *pTextEntryPassword = dynamic_cast< TextEntry * >( FindChildByName( "PasswordTextEntry" ) );
+ if ( pTextEntryUserName && pTextEntryPassword )
+ {
+ char szUserName[256];
+ pTextEntryUserName->GetText( szUserName, sizeof( szUserName ) );
+ char szPassword[256];
+ pTextEntryPassword->GetText( szPassword, sizeof( szPassword ) );
+ youtube_username.SetValue( szUserName );
+ Login( szUserName, szPassword );
+ }
+
+ return;
+ }
+ BaseClass::OnCommand( command );
+ }
+
+protected:
+ virtual const char *GetResFile() { return "Resource/UI/YouTubeLoginDialog.res"; }
+ virtual const char *GetResFilePathId() { return "GAME"; }
+
+ void Login( const char* pUserName, const char *pPassword )
+ {
+ const bool bOnSteamPublic = steamapicontext && steamapicontext->SteamUtils() && steamapicontext->SteamUtils()->GetConnectedUniverse() == k_EUniversePublic;
+ const char *pGameDir = COM_GetModDirectory();
+ const char *pSource = NULL;
+ const char *pDeveloperTag = NULL;
+ const char *pDeveloperKey = NULL;
+
+ // Team Fortress 2?
+ if ( FStrEq( pGameDir, "tf" ) )
+ {
+ pSource = "Team Fortress 2";
+ pDeveloperTag = "TF2";
+ pDeveloperKey = bOnSteamPublic ?
+ "AI39si6dQGxX2TWkOT9V9ihFpKmokaDhqIw3mgJcnnRFjX5f00HMRXqj69Fg1ulTdYF9Aw4wIt5NYHCxQAXHPPEcbQ89rEaCeg" :
+ "AI39si5q3V-l7DhNbdSn-F2P3l0sUOIOnHBqJC5kvdGsuwpIinmcOH5GFC1PGG0olcZM2ID0Fvbsodj6g0pOUkKhuRNxcEErLQ";
+ }
+ // Team Fortress 2 Beta?
+ else if ( FStrEq( pGameDir, "tf_beta" ) )
+ {
+ pSource = "Team Fortress 2 Beta";
+ pDeveloperTag = "TF2";
+ pDeveloperKey = bOnSteamPublic ?
+ "AI39si7XuLuXg3-2T06aVUaM-45HSXYFqzXfyPR6y5K4XotWKf78lfCByXnD1T8Kj9jeIR85NelpjGYGsH8pR3RO4k3TrwlTbw" :
+ "AI39si79TOshUv9FcIT6cjEH0Q9KK_eEOH1q6-_lIdNIsmHrKcal1R8Uf0TzMhdq-rQ7iUEZ3uqSKlLXa2J-lQvuKwNq5SSnMA";
+ }
+ // Counter-Strike: Source?
+ else if ( FStrEq( pGameDir, "cstrike" ) )
+ {
+ pSource = "Counter-Strike: Source";
+ pDeveloperTag = "CSS";
+ pDeveloperKey = bOnSteamPublic ?
+ "AI39si7JIn2nj67YoxWPzmsGHO2R-WSpN0su1f3-BF9lVC5jWz9DEOPbOxFz-MiXuaMtoCZnS3nSPjwGfqHenXC6RKGcICI5HQ" :
+ "AI39si4bpW1q3D0gcWWNWFNHjHgsM7YL3RGCdEAIKV2k_mH5Cwj-YwXinVv933tFhy-6583HcuZ8NWTrvfbB8XTWEI-hYidEjA";
+ }
+ // Counter-Strike: Source Beta?
+ else if ( FStrEq( pGameDir, "cstrike_beta" ) )
+ {
+ pSource = "Counter-Strike: Source Beta";
+ pDeveloperTag = "CSS";
+ pDeveloperKey = bOnSteamPublic ?
+ "AI39si5JUyCvGdavFg02OusWk9lkSxkpEX99KnJCjTwOzLJH7N9MS40YMFk-o8dTwyO0w2Qi2CSU8qrB4bdTohHj35mAa0iMDQ" :
+ "AI39si4Oq2O35MHD5qahqODCKQfsssq5STE6ISolJKsvFuxtPpqhG4sQbDF8pGdZ02c-_s5KRB5nhTjqZMLB4h0lKKHh8I52Tg";
+ }
+
+ Assert( pSource );
+ Assert( pDeveloperTag );
+ Assert( pDeveloperKey );
+
+ YouTube_SetDeveloperSettings( pDeveloperKey, pDeveloperTag );
+
+ // try to log in
+ YouTube_Login( pUserName, pPassword, pSource );
+
+ CYouTubeLoginWaitDialog *pDialog = new CYouTubeLoginWaitDialog( m_pMovie, this );
+ ShowWaitingDialog( pDialog, "#YouTube_LoggingIn", true, true, -1 );
+ }
+
+private:
+ IReplayMovie *m_pMovie;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Dialog for uploading a file to YouTube
+//-----------------------------------------------------------------------------
+class CYouTubeUploadDialog : public CConfirmDialog
+{
+ DECLARE_CLASS_SIMPLE( CYouTubeUploadDialog, CConfirmDialog );
+public:
+ CYouTubeUploadDialog( IReplayMovie *pMovie, Panel *pParent ) : BaseClass( pParent ), m_pMovie( pMovie ) {}
+
+ const wchar_t *GetText() { return NULL; }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+ m_pTextEntryMovieTitle = dynamic_cast< TextEntry * >( FindChildByName( "MovieTitleTextEntry" ) );
+ m_pTextEntryMovieDesc = dynamic_cast< TextEntry * >( FindChildByName( "MovieDescTextEntry" ) );
+
+ // use insert, so that max characters is obeyed
+ m_pTextEntryMovieTitle->InsertString( m_pMovie->GetItemTitle() );
+ // @todo add steam profile to description
+ m_pTextEntryMovieDesc->SetText( "" );
+ m_pTextEntryMovieDesc->SetMultiline( true );
+ m_pTextEntryMovieDesc->SetCatchEnterKey( true );
+ m_pTextEntryMovieDesc->SetVerticalScrollbar( true );
+ ScrollBar *pScrollbar = dynamic_cast< ScrollBar* >( m_pTextEntryMovieDesc->FindChildByName( "Scrollbar" ) );
+ if ( pScrollbar )
+ {
+ pScrollbar->SetAutohideButtons( false );
+ pScrollbar->SetScrollbarButtonsVisible( true );
+ }
+
+ m_pUnlistedCheckbox = dynamic_cast< CheckButton * >( FindChildByName( "UnlistedCheckbox" ) );
+ if ( m_pUnlistedCheckbox )
+ {
+ m_pUnlistedCheckbox->SetMouseInputEnabled( true );
+ }
+
+ }
+
+ void GetGameNameStrings( const char **ppShortGameName, const char **ppFullGameName )
+ {
+ const char *pGameDir = COM_GetModDirectory();
+
+ // Team Fortress 2?
+ if ( FStrEq( pGameDir, "tf" ) )
+ {
+ *ppShortGameName = "TF2";
+ *ppFullGameName = "Team Fortress 2";
+ }
+ // Team Fortress 2 Beta?
+ else if ( FStrEq( pGameDir, "tf_beta" ) )
+ {
+ *ppShortGameName = "TF2";
+ *ppFullGameName = "Team Fortress 2 Beta";
+ }
+ // Counter-Strike: Source?
+ else if ( FStrEq( pGameDir, "cstrike" ) )
+ {
+ *ppShortGameName = "CSS";
+ *ppFullGameName = "Counter-Strike: Source";
+ }
+ // Counter-Strike: Source Beta?
+ else if ( FStrEq( pGameDir, "cstrike_beta" ) )
+ {
+ *ppShortGameName = "CSS";
+ *ppFullGameName = "Counter-Strike: Source Beta";
+ }
+ else
+ {
+ AssertMsg( 0, "Unknown game" );
+ *ppShortGameName = "";
+ *ppFullGameName = "";
+ }
+ }
+
+ virtual void OnCommand( const char *command )
+ {
+ if ( !Q_strnicmp( command, "termsofservice", 14 ) )
+ {
+ if ( steamapicontext && steamapicontext->SteamFriends() )
+ {
+ steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( "http://www.youtube.com/t/terms" );
+ }
+ }
+ else if ( !Q_strnicmp( command, "confirm", 7 ) )
+ {
+ char szTitle[256];
+ m_pTextEntryMovieTitle->GetText( szTitle, sizeof( szTitle ) );
+ char szDesc[2048];
+ m_pTextEntryMovieDesc->GetText( szDesc, sizeof( szDesc ) );
+
+ if ( HasInvalidCharacters( szTitle ) )
+ {
+ ShowMessageBox( "#YouTube_Upload_Title", "#YouTube_Upload_InvalidChars_Title", "#GameUI_OK" );
+ return;
+ }
+ if ( HasInvalidCharacters( szDesc ) )
+ {
+ ShowMessageBox( "#YouTube_Upload_Title", "#YouTube_Upload_InvalidChars_Desc", "#GameUI_OK" );
+ return;
+ }
+
+ CGenericClassBasedReplay *pReplay = ToGenericClassBasedReplay( m_pMovie->GetItemReplay() );
+
+ CFmtStr fmtMovieFullFilename( "%s%s", g_pReplayMovieManager->GetRenderDir(), m_pMovie->GetMovieFilename() );
+ const char *pMimeType = "video/mp4";
+ const char *pTitle = szTitle;
+ const char *pCategory = "Games";
+
+ // add steam profile to the description for verification if necessary
+ EUniverse eSteamUniverse = steamapicontext && steamapicontext->SteamUtils() ? steamapicontext->SteamUtils()->GetConnectedUniverse() : k_EUniverseDev;
+ CUtlString description( szDesc );
+ if ( steamapicontext && steamapicontext->SteamUser() )
+ {
+ const char *pchCommunityURL = "http://steamcommunity.com/";
+ switch ( eSteamUniverse )
+ {
+ case k_EUniverseDev:
+ pchCommunityURL = "http://localhost/community/";
+ break;
+ case k_EUniverseBeta:
+ pchCommunityURL = "http://beta.steamcommunity.com/";
+ break;
+ case k_EUniversePublic:
+ default:
+ pchCommunityURL = "http://steamcommunity.com/";
+ }
+ description.Format( "%s\n\n%sprofiles/%llu", szDesc, pchCommunityURL, steamapicontext->SteamUser()->GetSteamID().ConvertToUint64() );
+ }
+
+ const char *pShortGameName = NULL;
+ const char *pFullGameName = NULL;
+
+ GetGameNameStrings( &pShortGameName, &pFullGameName );
+
+ CFmtStr1024 keywords( "%s Replay, %s, %s, Replay, Valve, %s", pShortGameName, pShortGameName, pFullGameName, pReplay->GetPlayerClass() );
+ bool bUnlisted = m_pUnlistedCheckbox->IsSelected();
+
+ uint64 uSessionId = g_pClientReplayContext->GetServerSessionId( pReplay->GetHandle() );
+ if ( uSessionId != 0 )
+ {
+ char szSessionId[32];
+
+ // Write session ID as hex (this code taken from KeyValues.cpp, modified).
+#ifdef WIN32
+ Q_snprintf( szSessionId, sizeof( szSessionId ), "%I64X", uSessionId );
+#else
+ Q_snprintf( szSessionId, sizeof( szSessionId ), "%llX", uSessionId );
+#endif
+
+ // Add the match tag to the list of keywords.
+ keywords.AppendFormat( ", match_%s", szSessionId );
+ }
+
+ UploadOgsData( m_pMovie );
+
+ YouTubeUploadHandle_t handle = YouTube_Upload( fmtMovieFullFilename.Access(), pMimeType, pTitle, description.Get(), pCategory, keywords.Access(), bUnlisted ? kYouTubeAccessControl_Unlisted : kYouTubeAccessControl_Public );
+ if ( handle != NULL )
+ {
+ // Play a sound
+ surface()->PlaySound( "replay\\youtube_startingupload.wav" );
+
+ CYouTubeUploadWaitDialog *pDialog = new CYouTubeUploadWaitDialog( m_pMovie, pTitle, description.Get(), handle, GetParent() );
+ ShowWaitingDialog( pDialog, "#YouTube_Uploading", true, true, -1 );
+
+ // get rid of this dialog
+ FinishUp();
+ }
+ else
+ {
+ ShowMessageBox( "#YouTube_Upload_Title", "#YouTube_Upload_Failure", "#GameUI_OK" );
+ }
+
+ return;
+ }
+ BaseClass::OnCommand( command );
+ }
+
+protected:
+ virtual const char *GetResFile() { return "Resource/UI/YouTubeUploadDialog.res"; }
+ virtual const char *GetResFilePathId() { return "GAME"; }
+
+private:
+ IReplayMovie *m_pMovie;
+ TextEntry *m_pTextEntryMovieTitle;
+ TextEntry *m_pTextEntryMovieDesc;
+ CheckButton *m_pUnlistedCheckbox;
+};
+
+//-----------------------------------------------------------------------------
+
+void YouTube_ShowLoginDialog( IReplayMovie *pMovie, vgui::Panel *pParent )
+{
+ CYouTubeLoginDialog *pDialog = vgui::SETUP_PANEL( new CYouTubeLoginDialog( pMovie, pParent ) );
+ pDialog->Show( false );
+}
+
+//-----------------------------------------------------------------------------
+
+void YouTube_ShowUploadDialog( IReplayMovie *pMovie, vgui::Panel *pParent )
+{
+ CYouTubeUploadDialog *pDialog = vgui::SETUP_PANEL( new CYouTubeUploadDialog( pMovie, pParent ) );
+ pDialog->Show( false );
+}
+
+//-----------------------------------------------------------------------------
+
+#endif // REPLAY_ENABLED
diff --git a/mp/src/game/client/replay/replayyoutubeapi.h b/mp/src/game/client/replay/replayyoutubeapi.h new file mode 100644 index 00000000..5add6b6f --- /dev/null +++ b/mp/src/game/client/replay/replayyoutubeapi.h @@ -0,0 +1,32 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef REPLAYYOUTUBEAPI_H
+#define REPLAYYOUTUBEAPI_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+class IReplayMovie;
+namespace vgui { class Panel; };
+
+/**
+ * Show the YouTube login dialog and attempt to upload the movie if login was successful
+ * @param pMovie
+ * @param pParent
+ */
+void YouTube_ShowLoginDialog( IReplayMovie *pMovie, vgui::Panel *pParent );
+
+/**
+ *
+ * Show the YouTube upload dialog and upload the movie
+ * @param pMovie
+ * @param pParent
+ */
+void YouTube_ShowUploadDialog( IReplayMovie *pMovie, vgui::Panel *pParent );
+
+#endif // REPLAYYOUTUBEAPI_H
diff --git a/mp/src/game/client/replay/vgui/replaybrowserbasepage.cpp b/mp/src/game/client/replay/vgui/replaybrowserbasepage.cpp new file mode 100644 index 00000000..15065509 --- /dev/null +++ b/mp/src/game/client/replay/vgui/replaybrowserbasepage.cpp @@ -0,0 +1,216 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "vgui/IInput.h"
+#include "vgui/ISurface.h"
+#include "vgui_controls/TextEntry.h"
+
+#include "replaybrowserbasepage.h"
+#include "replaybrowserdetailspanel.h"
+#include "replaybrowsermainpanel.h"
+#include "replaybrowserlistpanel.h"
+#include "replay/ireplaymoviemanager.h"
+#include "replay/ireplaymanager.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+
+extern IReplayMovieManager *g_pReplayMovieManager;
+
+//-----------------------------------------------------------------------------
+
+CReplayBrowserBasePage::CReplayBrowserBasePage( Panel *pParent )
+: BaseClass( pParent, "BasePage" )
+{
+ m_pReplayList = new CReplayListPanel( this, "ReplayList" );
+ m_pReplayList->SetFirstColumnWidth( 0 );
+
+ m_pSearchTextEntry = new vgui::TextEntry( this, "SearchTextEntry" );
+ m_pSearchTextEntry->SelectAllOnFocusAlways( true );
+ m_pSearchTextEntry->AddActionSignalTarget( this );
+ m_pSearchTextEntry->SetCatchEnterKey( true );
+
+ InvalidateLayout( true, true );
+
+ m_pReplayList->AddReplaysToList();
+
+ ivgui()->AddTickSignal( GetVPanel(), 100 );
+}
+
+CReplayBrowserBasePage::~CReplayBrowserBasePage()
+{
+ ivgui()->RemoveTickSignal( GetVPanel() );
+}
+
+void CReplayBrowserBasePage::OnTick()
+{
+ if ( !IsVisible() )
+ return;
+
+ int nCursorX, nCursorY;
+ input()->GetCursorPos( nCursorX, nCursorY );
+
+ if ( input()->IsMouseDown( MOUSE_LEFT ) &&
+ !m_pSearchTextEntry->IsWithin( nCursorX, nCursorY ) &&
+ m_pSearchTextEntry->HasFocus() )
+ {
+ RequestFocus();
+ }
+}
+
+void CReplayBrowserBasePage::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/replaybrowser/basepage.res", "GAME" );
+
+ m_pSearchTextEntry->SetText( "#Replay_SearchText" );
+}
+
+void CReplayBrowserBasePage::OnPageShow()
+{
+ BaseClass::OnPageShow();
+ m_pSearchTextEntry->SetText( "#Replay_SearchText" );
+}
+
+void CReplayBrowserBasePage::OnSelectionStarted()
+{
+ PostActionSignal( new KeyValues("SelectionUpdate", "open", 1 ) );
+}
+
+void CReplayBrowserBasePage::OnSelectionEnded()
+{
+ PostActionSignal( new KeyValues("SelectionUpdate", "open", 0 ) );
+}
+
+void CReplayBrowserBasePage::CleanupUIForReplayItem( ReplayItemHandle_t hReplayItem )
+{
+ m_pReplayList->CleanupUIForReplayItem( hReplayItem );
+}
+
+void CReplayBrowserBasePage::AddReplay( ReplayHandle_t hReplay )
+{
+ m_pReplayList->AddReplayItem( hReplay );
+}
+
+void CReplayBrowserBasePage::DeleteReplay( ReplayHandle_t hReplayItem )
+{
+ IReplayItemManager *pItemManager;
+ if ( FindReplayItem( hReplayItem, &pItemManager ) )
+ {
+ ReplayUI_GetBrowserPanel()->AttemptToDeleteReplayItem( this, hReplayItem, pItemManager, -1 );
+ }
+}
+
+void CReplayBrowserBasePage::OnCancelSelection()
+{
+}
+
+void CReplayBrowserBasePage::GoBack()
+{
+ DeleteDetailsPanelAndShowReplayList();
+}
+
+void CReplayBrowserBasePage::OnReplayItemDeleted( KeyValues *pParams )
+{
+ GoBack();
+}
+
+void CReplayBrowserBasePage::OnTextChanged( KeyValues *data )
+{
+ wchar_t wszText[256];
+ m_pSearchTextEntry->GetText( wszText, ARRAYSIZE( wszText ) );
+ m_pReplayList->ApplyFilter( wszText );
+ InvalidateLayout();
+}
+
+void CReplayBrowserBasePage::OnCommand( const char *pCommand )
+{
+ // User wants details on a replay?
+ if ( !V_strnicmp( pCommand, "details", 7 ) )
+ {
+ // Get rid of preview panel
+ m_pReplayList->ClearPreviewPanel();
+
+ QueryableReplayItemHandle_t hReplayItem = (QueryableReplayItemHandle_t)atoi( pCommand + 7 );
+ IReplayItemManager *pItemManager;
+ IQueryableReplayItem *pReplayItem = FindReplayItem( hReplayItem, &pItemManager ); Assert( pReplayItem );
+ if ( pReplayItem )
+ {
+ // Get performance
+ int iPerformance = -1;
+ const char *pPerformanceStr = V_strstr( pCommand + 8, "_" );
+ if ( pPerformanceStr )
+ {
+ iPerformance = atoi( pPerformanceStr + 1 );
+ }
+
+ m_hReplayDetailsPanel = vgui::SETUP_PANEL( new CReplayDetailsPanel( this, hReplayItem, iPerformance, pItemManager ) );
+ m_hReplayDetailsPanel->SetVisible( true );
+ m_hReplayDetailsPanel->MoveToFront();
+
+ m_pReplayList->SetVisible( false );
+
+ surface()->PlaySound( "replay\\showdetails.wav" );
+ }
+ }
+
+ // "back" button was hit in details panel?
+ else if ( FStrEq( pCommand, "back" ) )
+ {
+ GoBack();
+ }
+
+ BaseClass::OnCommand( pCommand );
+}
+
+void CReplayBrowserBasePage::DeleteDetailsPanelAndShowReplayList()
+{
+ // Delete the panel
+ if ( m_hReplayDetailsPanel )
+ {
+ m_hReplayDetailsPanel->MarkForDeletion();
+ m_hReplayDetailsPanel = NULL;
+ }
+
+ m_pReplayList->SetVisible( true );
+}
+
+void CReplayBrowserBasePage::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ if ( m_pSearchTextEntry )
+ {
+ const bool bHasReplays = g_pReplayManager && g_pReplayManager->GetReplayCount();
+ const bool bHasMovies = g_pReplayMovieManager && g_pReplayMovieManager->GetMovieCount();
+ int aListPos[2];
+ m_pReplayList->GetPos( aListPos[0], aListPos[1] );
+ m_pSearchTextEntry->SetPos( aListPos[0] + m_pReplayList->GetWide() - m_pSearchTextEntry->GetWide(), YRES( 5 ) );
+ m_pSearchTextEntry->SetVisible( bHasReplays || bHasMovies );
+ }
+
+ // Invalidate the list too, because we might be laying out due to a replay being removed from the list.
+ m_pReplayList->InvalidateLayout();
+}
+
+void CReplayBrowserBasePage::FreeDetailsPanelMovieLock()
+{
+ m_hReplayDetailsPanel->FreeMovieFileLock();
+}
+
+bool CReplayBrowserBasePage::IsDetailsViewOpen()
+{
+ return m_hReplayDetailsPanel.Get() != NULL && m_hReplayDetailsPanel->IsVisible();
+}
+
+#endif
\ No newline at end of file diff --git a/mp/src/game/client/replay/vgui/replaybrowserbasepage.h b/mp/src/game/client/replay/vgui/replaybrowserbasepage.h new file mode 100644 index 00000000..a9733d38 --- /dev/null +++ b/mp/src/game/client/replay/vgui/replaybrowserbasepage.h @@ -0,0 +1,67 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef REPLAYBROWSER_BASEPAGE_H
+#define REPLAYBROWSER_BASEPAGE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_controls/PropertyPage.h"
+#include "replaybrowseritemmanager.h"
+#include "replay/genericclassbased_replay.h"
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+// Forward declarations
+//-----------------------------------------------------------------------------
+class CReplayListPanel;
+class CExLabel;
+class CReplayDetailsPanel;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CReplayBrowserBasePage : public PropertyPage
+{
+ DECLARE_CLASS_SIMPLE( CReplayBrowserBasePage, PropertyPage );
+public:
+ CReplayBrowserBasePage( Panel *pParent );
+ virtual ~CReplayBrowserBasePage();
+
+ void DeleteDetailsPanelAndShowReplayList();
+ bool IsDetailsViewOpen();
+ void GoBack();
+
+ // Movie-only stuff
+ void FreeDetailsPanelMovieLock();
+
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void OnCommand( const char *pCommand );
+ virtual void PerformLayout();
+
+ MESSAGE_FUNC( OnPageShow, "PageShow" );
+ MESSAGE_FUNC( OnSelectionStarted, "SelectionStarted" );
+ MESSAGE_FUNC( OnSelectionEnded, "SelectionEnded" );
+ MESSAGE_FUNC( OnCancelSelection, "CancelSelection" );
+ MESSAGE_FUNC_PARAMS( OnReplayItemDeleted, "ReplayItemDeleted", pParams );
+ MESSAGE_FUNC_PARAMS( OnTextChanged, "TextChanged", data );
+
+ void AddReplay( ReplayHandle_t hReplay );
+ void DeleteReplay( ReplayHandle_t hReplay );
+
+ void OnTick();
+
+ virtual void CleanupUIForReplayItem( ReplayItemHandle_t hReplayItem );
+
+ vgui::TextEntry *m_pSearchTextEntry;
+ CReplayListPanel *m_pReplayList;
+ DHANDLE< CReplayDetailsPanel > m_hReplayDetailsPanel;
+};
+
+#endif // REPLAYBROWSER_BASEPAGE_H
diff --git a/mp/src/game/client/replay/vgui/replaybrowserbasepanel.cpp b/mp/src/game/client/replay/vgui/replaybrowserbasepanel.cpp new file mode 100644 index 00000000..b8bd0a9f --- /dev/null +++ b/mp/src/game/client/replay/vgui/replaybrowserbasepanel.cpp @@ -0,0 +1,39 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "replaybrowserbasepanel.h"
+
+//-----------------------------------------------------------------------------
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+
+CReplayBasePanel::CReplayBasePanel( Panel *pParent, const char *pName )
+: BaseClass( pParent, pName )
+{
+}
+
+void CReplayBasePanel::GetPosRelativeToAncestor( Panel *pAncestor, int &nXOut, int &nYOut )
+{
+ nXOut = nYOut = 0;
+
+ Panel *pCurrent = this;
+ while ( pCurrent && pCurrent != pAncestor )
+ {
+ int x,y;
+ pCurrent->GetPos( x, y );
+ nXOut += x;
+ nYOut += y;
+ pCurrent = pCurrent->GetParent();
+ }
+
+ Assert( pAncestor == pCurrent );
+}
+
+#endif
\ No newline at end of file diff --git a/mp/src/game/client/replay/vgui/replaybrowserbasepanel.h b/mp/src/game/client/replay/vgui/replaybrowserbasepanel.h new file mode 100644 index 00000000..62d17bf5 --- /dev/null +++ b/mp/src/game/client/replay/vgui/replaybrowserbasepanel.h @@ -0,0 +1,25 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#ifndef REPLAYBASEPANEL_H
+#define REPLAYBASEPANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_controls/EditablePanel.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Base panel for replay panels
+//-----------------------------------------------------------------------------
+class CReplayBasePanel : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CReplayBasePanel, vgui::EditablePanel );
+public:
+ CReplayBasePanel( Panel *pParent, const char *pName );
+
+ void GetPosRelativeToAncestor( Panel *pAncestor, int &nXOut, int &nYOut );
+};
+
+#endif // REPLAYBASEPANEL_H
diff --git a/mp/src/game/client/replay/vgui/replaybrowserdetailspanel.cpp b/mp/src/game/client/replay/vgui/replaybrowserdetailspanel.cpp new file mode 100644 index 00000000..ef921a43 --- /dev/null +++ b/mp/src/game/client/replay/vgui/replaybrowserdetailspanel.cpp @@ -0,0 +1,1950 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "replaybrowserdetailspanel.h"
+#include "replaybrowsermainpanel.h"
+#include "replaybrowseritemmanager.h"
+#include "replaybrowsermovieplayerpanel.h"
+#include "replaybrowserrenderdialog.h"
+#include "vgui/IVGui.h"
+#include "vgui/ISurface.h"
+#include "vgui/IInput.h"
+#include "vgui/ILocalize.h"
+#include "vgui_controls/FileOpenDialog.h"
+#include "vgui_controls/PanelListPanel.h"
+#include "vgui_controls/ScrollBar.h"
+#include "vgui_controls/ScrollBarSlider.h"
+#include "vgui_controls/TextEntry.h"
+#include "vgui_controls/TextImage.h"
+#include "vgui_avatarimage.h"
+#include "gamestringpool.h"
+#include "replay/genericclassbased_replay.h"
+#include "replaybrowserlistitempanel.h"
+#include "confirm_dialog.h"
+#include "replay/ireplaymoviemanager.h"
+#include "replay/ireplaymanager.h"
+#include "replay/ireplayrenderqueue.h"
+#include "replay/screenshot.h"
+#include "replay/ireplayperformancemanager.h"
+#include "replay/performance.h"
+#include "vgui/ISystem.h"
+#include "youtubeapi.h"
+#include "replay/replayyoutubeapi.h"
+#include "ienginevgui.h"
+#include <filesystem.h>
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+
+extern IClientReplayContext *g_pClientReplayContext;
+extern IReplayMovieManager *g_pReplayMovieManager;
+extern IReplayPerformanceManager *g_pReplayPerformanceManager;
+
+//-----------------------------------------------------------------------------
+
+ConVar replay_movie_reveal_warning( "replay_movie_reveal_warning", "1", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_HIDDEN | FCVAR_DONTRECORD );
+ConVar replay_movie_export_last_dir( "replay_movie_export_last_dir", "", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_HIDDEN | FCVAR_DONTRECORD );
+ConVar replay_renderqueue_first_add( "replay_renderqueue_first_add", "0", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_HIDDEN | FCVAR_DONTRECORD );
+
+//-----------------------------------------------------------------------------
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+
+class CConfirmDisconnectFromServerDialog : public CConfirmDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmDisconnectFromServerDialog, CConfirmDialog );
+public:
+ CConfirmDisconnectFromServerDialog( Panel *pParent )
+ : BaseClass( pParent )
+ {
+ surface()->PlaySound( "replay\\replaydialog_warn.wav" );
+ }
+
+ const wchar_t *GetText() { return g_pVGuiLocalize->Find( "#Replay_ConfirmDisconnectFromServer" ); }
+
+ void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ SetTall( YRES( 170 ) );
+ }
+};
+
+//-----------------------------------------------------------------------------
+
+CKeyValueLabelPanel::CKeyValueLabelPanel( Panel *pParent, const char *pKey, const char *pValue )
+: EditablePanel( pParent, "KeyValueLabelPanel" )
+{
+ SetScheme( "ClientScheme" );
+
+ m_pLabels[ 0 ] = new CExLabel( this, "KeyLabel", pKey );
+ m_pLabels[ 1 ] = new CExLabel( this, "ValueLabel", pValue );
+}
+
+CKeyValueLabelPanel::CKeyValueLabelPanel( Panel *pParent, const char *pKey, const wchar_t *pValue )
+: EditablePanel( pParent, "KeyValueLabelPanel" )
+{
+ SetScheme( "ClientScheme" );
+
+ m_pLabels[ 0 ] = new CExLabel( this, "KeyLabel", pKey );
+ m_pLabels[ 1 ] = new CExLabel( this, "ValueLabel", pValue );
+}
+
+void CKeyValueLabelPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ HFont hFont = scheme()->GetIScheme( GetScheme() )->GetFont( "ReplayBrowserSmallest", true );
+
+ m_pLabels[ 0 ]->SetFont( hFont );
+ m_pLabels[ 1 ]->SetFont( hFont );
+
+ m_pLabels[ 0 ]->SetFgColor( Color( 119, 107, 95, 255 ) );
+ m_pLabels[ 1 ]->SetFgColor( Color( 255, 255, 255, 255 ) );
+}
+
+void CKeyValueLabelPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ int nContentWidth, nContentHeight;
+ m_pLabels[0]->GetContentSize( nContentWidth, nContentHeight );
+
+ int iMidX = GetParent()->GetWide() * 0.55f;
+ m_pLabels[0]->SetBounds( 0, 0, iMidX, nContentHeight );
+
+ m_pLabels[1]->GetContentSize( nContentWidth, nContentHeight );
+ m_pLabels[1]->SetBounds( iMidX, 0, iMidX, nContentHeight );
+}
+
+int CKeyValueLabelPanel::GetHeight() const
+{
+ int nWidth, nHeight;
+ m_pLabels[ 0 ]->GetContentSize( nWidth, nHeight );
+ return nHeight;
+}
+
+int CKeyValueLabelPanel::GetValueHeight() const
+{
+ int nWidth, nHeight;
+ m_pLabels[ 1 ]->GetContentSize( nWidth, nHeight );
+ return nHeight;
+}
+
+void CKeyValueLabelPanel::SetValue( const wchar_t *pValue )
+{
+ m_pLabels[ 1 ]->SetText( pValue );
+ InvalidateLayout();
+}
+
+//-----------------------------------------------------------------------------
+
+CBaseDetailsPanel::CBaseDetailsPanel( Panel *pParent, const char *pName, ReplayHandle_t hReplay )
+: EditablePanel( pParent, pName ),
+ m_hReplay( hReplay ),
+ m_bShouldShow( true )
+{
+ SetScheme( "ClientScheme" );
+
+ m_pInsetPanel = new EditablePanel( this, "InsetPanel" );
+}
+
+void CBaseDetailsPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ SetBorder( pScheme->GetBorder( "ReplayStatsBorder" ) );
+ SetBgColor( Color( 0,0,0, 255 ) );
+}
+
+void CBaseDetailsPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ // Setup inset panel bounds
+ const int n = GetMarginSize();
+ m_pInsetPanel->SetBounds( n, n, GetWide() - 2*n, GetTall() - 2*n );
+}
+
+//-----------------------------------------------------------------------------
+
+CRecordsPanel::CRecordsPanel( Panel *pParent, ReplayHandle_t hReplay )
+: CBaseDetailsPanel( pParent, "RecordsPanel", hReplay )
+{
+ m_pClassImage = new ImagePanel( this, "ClassImage" );
+}
+
+void CRecordsPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ SetBorder( pScheme->GetBorder( "ReplayDefaultBorder" ) );
+ SetBgColor( Color( 0,0,0,0 ) );
+}
+
+void CRecordsPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ // Figure out the class image name
+ char szImage[MAX_OSPATH];
+ const CGenericClassBasedReplay *pReplay = GetGenericClassBasedReplay( m_hReplay );
+ V_snprintf( szImage, sizeof( szImage ), "class_sel_sm_%s_%s", pReplay->GetMaterialFriendlyPlayerClass(), pReplay->GetPlayerTeam() ); // Cause default image to display
+
+ int nHeight = 0;
+
+ // Get the image
+ IImage *pImage = scheme()->GetImage( szImage, true );
+ if ( pImage )
+ {
+ // Get image dimensions
+ int nImageWidth, nImageHeight;
+ pImage->GetSize( nImageWidth, nImageHeight );
+
+ // Compute height of the records panel as a little smaller than the image itself
+ nHeight = nImageHeight * 11 / 16;
+
+ // Setup the image panel - parent to records panel parent so it goes out of the records panel's bounds a bit
+ const int nMargin = 7;
+ const float flScale = 1.2f;
+ m_pClassImage->SetImage( pImage );
+ m_pClassImage->SetParent( GetParent() );
+ m_pClassImage->SetShouldScaleImage( true );
+ m_pClassImage->SetScaleAmount( flScale );
+ int nX, nY;
+ GetPos( nX, nY );
+ m_pClassImage->SetBounds( nX + nMargin, nY - flScale * nImageHeight + GetTall() - nMargin, nImageWidth * flScale, nImageHeight * flScale );
+
+#if !defined( TF_CLIENT_DLL )
+ m_pClassImage->SetVisible( false );
+#endif
+ }
+
+ SetTall( nHeight );
+}
+
+//-----------------------------------------------------------------------------
+
+CStatsPanel::CStatsPanel( Panel *pParent, ReplayHandle_t hReplay )
+: CBaseDetailsPanel( pParent, "StatsPanel", hReplay )
+{
+ CGenericClassBasedReplay *pReplay = GetGenericClassBasedReplay( hReplay );
+
+ // Don't show the panel unless there are stats to display
+ m_bShouldShow = false;
+
+ // Create all stat labels
+ RoundStats_t const &stats = pReplay->GetStats();
+ for ( int i = 0; i < REPLAY_MAX_DISPLAY_GAMESTATS; ++i )
+ {
+ const int nCurStat = stats.Get( g_pReplayDisplayGameStats[i].m_nStat );
+ if ( !nCurStat )
+ {
+ m_paStatLabels[ i ] = NULL;
+ continue;
+ }
+
+ // Setup value
+ char szValue[256];
+ V_snprintf( szValue, sizeof( szValue ), "%i", nCurStat );
+
+ // Create labels for this stat
+ m_paStatLabels[ i ] = new CKeyValueLabelPanel( GetInset(), g_pReplayDisplayGameStats[i].m_pStatLocalizationToken, szValue );
+
+ // At least one stat to display
+ m_bShouldShow = true;
+ }
+}
+
+void CStatsPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+}
+
+void CStatsPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ int nY = 0;
+ for ( int i = 0; i < REPLAY_MAX_DISPLAY_GAMESTATS; ++i )
+ {
+ CKeyValueLabelPanel *pCurStatLabels = m_paStatLabels[ i ];
+ if ( pCurStatLabels )
+ {
+ pCurStatLabels->SetBounds( 0, nY, GetInset()->GetWide(), YRES(13) );
+ nY += YRES(13);
+ }
+ }
+
+ SetTall( nY + GetMarginSize() * 2 );
+}
+
+//-----------------------------------------------------------------------------
+
+CDominationsPanel::CDominationsPanel( Panel *pParent, ReplayHandle_t hReplay )
+: CBaseDetailsPanel( pParent, "DominationsPanel", hReplay )
+{
+ CGenericClassBasedReplay *pReplay = GetGenericClassBasedReplay( hReplay );
+
+ m_pNumDominationsImage = new ImagePanel( GetInset(), "NumDominations" );
+
+ char szImage[256];
+ int nNumDominations = pReplay->GetDominationCount();
+
+ // Setup the # of dominations image
+ V_snprintf( szImage, sizeof( szImage ), "../hud/leaderboard_dom%i", nNumDominations );
+ m_pNumDominationsImage->SetImage( szImage );
+
+ // Add avatars for each person dominated
+ if ( steamapicontext && steamapicontext->SteamUtils() && steamapicontext->SteamUtils()->GetConnectedUniverse() )
+ {
+ for ( int i = 0; i < nNumDominations; ++i )
+ {
+ CAvatarImage *pAvatar = new CAvatarImage();
+ CSteamID id( pReplay->GetDomination( i )->m_nVictimFriendId, 1, steamapicontext->SteamUtils()->GetConnectedUniverse(), k_EAccountTypeIndividual );
+ pAvatar->SetAvatarSteamID( id );
+ pAvatar->SetAvatarSize( 32, 32 );
+ pAvatar->UpdateFriendStatus();
+
+ ImagePanel *pImagePanel = new ImagePanel( GetInset(), "DominationImage" );
+ pImagePanel->SetImage( pAvatar );
+ pImagePanel->SetShouldScaleImage( false );
+
+ m_vecDominationImages.AddToTail( pImagePanel );
+ }
+ }
+}
+
+void CDominationsPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+}
+
+void CDominationsPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ const int nBuffer = 7;
+
+ int nImageWidth, nImageHeight;
+ m_pNumDominationsImage->GetImage()->GetSize( nImageWidth, nImageHeight );
+ m_pNumDominationsImage->SetBounds( 0, 0, nImageWidth, nImageHeight );
+ int nX = nImageWidth + 2*nBuffer;
+ int nY = 0;
+
+ for ( int i = 0; i < m_vecDominationImages.Count(); ++i )
+ {
+ ImagePanel *pImagePanel = m_vecDominationImages[ i ];
+ pImagePanel->GetImage()->GetSize( nImageWidth, nImageHeight );
+ m_vecDominationImages[ i ]->SetBounds( nX, nY, nImageWidth, nImageHeight );
+
+ nX += nImageWidth + nBuffer;
+ if ( nX + nImageWidth > GetInset()->GetWide() )
+ {
+ nX = 0;
+ nY += nImageHeight + nBuffer;
+ }
+ }
+
+ SetTall( nY + nImageHeight + GetMarginSize() * 2 );
+}
+
+//-----------------------------------------------------------------------------
+
+CKillsPanel::CKillsPanel( Panel *pParent, ReplayHandle_t hReplay )
+: CBaseDetailsPanel( pParent, "KillsPanel", hReplay )
+{
+ // Get the replay from the handle and add all kills
+ CGenericClassBasedReplay *pReplay = GetGenericClassBasedReplay( hReplay );
+ char szKillCount[64] = "0";
+ if ( pReplay )
+ {
+ for ( int i = 0; i < pReplay->GetKillCount(); ++i )
+ {
+ // Construct path for image
+ char szImgPath[MAX_OSPATH] = "";
+
+#if defined( TF_CLIENT_DLL )
+ // Get the kill info
+ const CGenericClassBasedReplay::KillData_t *pKill = pReplay->GetKill( i );
+
+ char const *pClass = pKill->m_nPlayerClass == TF_CLASS_DEMOMAN
+ ? "demo"
+ : g_aPlayerClassNames_NonLocalized[ pKill->m_nPlayerClass ];
+
+ V_snprintf( szImgPath, sizeof( szImgPath ), "../hud/leaderboard_class_%s", pClass );
+#elif defined( CSTRIKE_DLL )
+ V_strcpy( szImgPath, "../hud/scoreboard_dead" );
+#endif
+
+ // Get the image
+ IImage *pImage = scheme()->GetImage( szImgPath, true );
+
+ // Create new image panel
+ ImagePanel *pImgPanel = new ImagePanel( GetInset(), "img" );
+ pImgPanel->SetImage( pImage );
+
+ // Cache for later
+ m_vecKillImages.AddToTail( pImgPanel );
+ }
+
+ // Copy kill count
+ V_snprintf( szKillCount, sizeof( szKillCount ), "%i", pReplay->GetKillCount() );
+ }
+
+ // Create labels
+ m_pKillLabels = new CKeyValueLabelPanel( GetInset(), "#Replay_Kills", szKillCount );
+}
+
+void CKillsPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ m_pKillLabels->SetBounds( 0, 0, GetWide(), m_pKillLabels->GetHeight() );
+
+ // Setup image positions
+ int nBuffer = 5;
+ int nY = m_pKillLabels->GetHeight() + nBuffer * 2;
+ int nImageY = nY;
+ int nImageX = 0;
+ for ( int i = 0; i < m_vecKillImages.Count(); ++i )
+ {
+ IImage *pCurImage = m_vecKillImages[ i ]->GetImage();
+ if ( !pCurImage )
+ continue;
+
+ int nImageWidth, nImageHeight;
+ pCurImage->GetSize( nImageWidth, nImageHeight );
+ m_vecKillImages[ i ]->SetBounds( nImageX, nImageY, nImageWidth, nImageHeight );
+
+ nImageX += nImageWidth + nBuffer;
+
+ if ( i == 0 )
+ {
+ nY += nImageHeight;
+ }
+
+ if ( nImageX + nImageWidth > GetInset()->GetWide() )
+ {
+ nImageX = 0;
+ nImageY += nImageHeight + nBuffer;
+ nY += nImageHeight + nBuffer;
+ }
+ }
+
+ // Set the height
+ SetTall( nY + GetMarginSize() * 2 );
+}
+
+void CKillsPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+}
+
+//-----------------------------------------------------------------------------
+
+extern const char *GetMapDisplayName( const char *mapName );
+
+CBasicLifeInfoPanel::CBasicLifeInfoPanel( Panel *pParent, ReplayHandle_t hReplay )
+: CBaseDetailsPanel( pParent, "BasicLifeInfo", hReplay )
+{
+ // Create labels
+ CGenericClassBasedReplay *pReplay = GetGenericClassBasedReplay( hReplay );
+ m_pKilledByLabels = new CKeyValueLabelPanel( GetInset(), "#Replay_StatKilledBy", pReplay->WasKilled() ? pReplay->GetKillerName() : "#Replay_None" );
+ m_pMapLabels = new CKeyValueLabelPanel( GetInset(), "#Replay_OnMap", GetMapDisplayName( pReplay->m_szMapName ) );
+ m_pLifeLabels = new CKeyValueLabelPanel( GetInset(), "#Replay_Life", CReplayTime::FormatTimeString( (int)pReplay->m_flLength ) );
+}
+
+void CBasicLifeInfoPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+}
+
+void CBasicLifeInfoPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ int nBuffer = 5;
+ m_pKilledByLabels->SetBounds( 0, 0, GetWide(), m_pKilledByLabels->GetHeight() );
+ m_pMapLabels->SetBounds( 0, m_pKilledByLabels->GetTall() + nBuffer, GetWide(), m_pMapLabels->GetHeight() );
+
+ int nLifeLabelsY = ( m_pMapLabels->GetTall() + nBuffer ) * 2;
+ m_pLifeLabels->SetBounds( 0, nLifeLabelsY, GetWide(), m_pLifeLabels->GetHeight() );
+
+ SetTall( nLifeLabelsY + m_pLifeLabels->GetTall() + GetMarginSize() * 2 );
+}
+
+//-----------------------------------------------------------------------------
+
+CYouTubeInfoPanel::CYouTubeInfoPanel( Panel *pParent )
+ : CBaseDetailsPanel( pParent, "YouTubeInfo", NULL ),
+ m_pLabels( NULL )
+{
+ m_pLabels = new CKeyValueLabelPanel( GetInset(), "#Replay_YouTube", g_pVGuiLocalize->Find( "YouTube_NoStats" ) );
+}
+
+void CYouTubeInfoPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ m_pLabels->SetBounds( 0, 0, GetWide(), m_pLabels->GetValueHeight() );
+
+ SetTall( m_pLabels->GetTall() + GetMarginSize() * 2 );
+}
+
+void CYouTubeInfoPanel::SetInfo( const wchar_t *pInfo )
+{
+ m_pLabels->SetValue( pInfo );
+ InvalidateLayout();
+}
+
+//-----------------------------------------------------------------------------
+
+CTitleEditPanel::CTitleEditPanel( Panel *pParent, QueryableReplayItemHandle_t hReplayItem, IReplayItemManager *pItemManager )
+: EditablePanel( pParent, "TitleEditPanel" ),
+ m_hReplayItem( hReplayItem ),
+ m_pItemManager( pItemManager ),
+ m_bMouseOver( false ),
+ m_pTitleEntry( NULL ),
+ m_pHeaderLine( NULL ),
+ m_pClickToEditLabel( NULL ),
+ m_pCaratLabel( NULL )
+{
+ ivgui()->AddTickSignal( GetVPanel(), 10 );
+}
+
+CTitleEditPanel::~CTitleEditPanel()
+{
+ ivgui()->RemoveTickSignal( GetVPanel() );
+}
+
+void CTitleEditPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/replaybrowser/titleeditpanel.res", "GAME" );
+
+ // Get ptr to carat label
+ m_pCaratLabel = dynamic_cast< CExLabel * >( FindChildByName( "CaratLabel" ) );
+
+ // Get ptr to "click to edit" label
+ m_pClickToEditLabel = dynamic_cast< CExLabel * >( FindChildByName( "ClickToEditLabel" ) );
+
+ // Setup title entry
+ m_pTitleEntry = dynamic_cast< TextEntry * >( FindChildByName( "TitleInput" ) );
+ m_pTitleEntry->SelectAllOnFocusAlways( true );
+
+#if !defined( TF_CLIENT_DLL )
+ m_pTitleEntry->SetPaintBorderEnabled( false );
+#endif
+
+ // Setup title entry text
+ IQueryableReplayItem *pReplayItem = m_pItemManager->GetItem( m_hReplayItem );
+ const wchar_t *pTitle = pReplayItem->GetItemTitle();
+ m_pTitleEntry->SetText( pTitle[0] ? pTitle : L"#Replay_DefaultDetailsTitle" );
+
+ // Cache pointer to the image
+ m_pHeaderLine = dynamic_cast< ImagePanel * >( FindChildByName( "HeaderLine" ) );
+}
+
+void CTitleEditPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ int nCaratW, nCaratH;
+ m_pCaratLabel->GetContentSize( nCaratW, nCaratH );
+ m_pCaratLabel->SetWide( nCaratW );
+ m_pCaratLabel->SetTall( nCaratH );
+
+ // Get title entry pos
+ int nTitleEntryX, nTitleEntryY;
+ m_pTitleEntry->GetPos( nTitleEntryX, nTitleEntryY );
+
+ // Set width of title entry to be width of parent, which has margins
+ m_pTitleEntry->SetToFullHeight();
+ m_pTitleEntry->SetWide( GetParent()->GetWide() - nTitleEntryX - 1 );
+
+ // Get content size for label
+ int nClickToEditW, nClickToEditH;
+ m_pClickToEditLabel->GetContentSize( nClickToEditW, nClickToEditH );
+
+ // Set click-to-edit bounds
+ int nTitleEntryTall = m_pTitleEntry->GetTall();
+ m_pClickToEditLabel->SetBounds(
+ nTitleEntryX + GetParent()->GetWide() - nClickToEditW * 1.4f,
+ nTitleEntryY + ( nTitleEntryTall - nClickToEditH ) / 2,
+ nClickToEditW,
+ nClickToEditH
+ );
+
+ // Setup header line position
+ m_pHeaderLine->SetPos( 0, nTitleEntryY + m_pTitleEntry->GetTall() * 1.2f );
+}
+
+void CTitleEditPanel::OnTick()
+{
+ int nMouseX, nMouseY;
+ input()->GetCursorPos( nMouseX, nMouseY );
+ m_bMouseOver = m_pTitleEntry->IsWithin( nMouseX, nMouseY );
+}
+
+void CTitleEditPanel::PaintBackground()
+{
+ bool bEditing = m_pTitleEntry->HasFocus();
+ bool bDrawExtraStuff = !vgui::input()->GetAppModalSurface() && ( m_bMouseOver || bEditing ); // Don't draw extra stuff when render dialog is up
+
+ // If mouse is over and we're not editing, show the "click to edit" label
+ m_pClickToEditLabel->SetVisible( m_bMouseOver && !bEditing );
+
+ // Draw border if necessary
+ if ( bDrawExtraStuff )
+ {
+ // Use the game UI panel here, since using this panel's vpanel (PushMakeCurrent() is set in
+ // Panel::PaintTraverse(), the function that calls PaintBackground()) causes dimmed top and
+ // left lines. Using the game UI panel allows painting outside of the text entry itself.
+ vgui::VPANEL vGameUI = enginevgui->GetPanel( PANEL_GAMEUIDLL );
+
+ // Calculate title entry rect (x0,y0,x1,y1) - move the absolute upper-left corner 1 pixel left & up
+ int aTitleRect[4];
+ ipanel()->GetAbsPos( m_pTitleEntry->GetVPanel(), aTitleRect[0], aTitleRect[1] );
+
+ --aTitleRect[0];
+ --aTitleRect[1];
+ aTitleRect[2] = aTitleRect[0] + m_pTitleEntry->GetWide() + 2;
+ aTitleRect[3] = aTitleRect[1] + m_pTitleEntry->GetTall() + 2;
+
+ surface()->PushMakeCurrent( vGameUI, false );
+
+ // Draw background
+ surface()->DrawSetColor( Color( 29, 28, 26, 255 ) );
+ surface()->DrawFilledRect( aTitleRect[0], aTitleRect[1], aTitleRect[2], aTitleRect[3] );
+
+ // Draw stroke
+ surface()->DrawSetColor( Color( 202, 190, 164, 255 ) );
+ surface()->DrawLine( aTitleRect[0], aTitleRect[1], aTitleRect[2], aTitleRect[1] ); // Top
+ surface()->DrawLine( aTitleRect[0], aTitleRect[1], aTitleRect[0], aTitleRect[3] ); // Left
+ surface()->DrawLine( aTitleRect[0], aTitleRect[3], aTitleRect[2], aTitleRect[3] ); // Bottom
+ surface()->DrawLine( aTitleRect[2], aTitleRect[1], aTitleRect[2], aTitleRect[3] ); // Right
+
+ surface()->PopMakeCurrent( vGameUI );
+ }
+}
+
+void CTitleEditPanel::OnKeyCodeTyped( KeyCode code )
+{
+ IQueryableReplayItem *pReplayItem = m_pItemManager->GetItem( m_hReplayItem );
+
+ const wchar_t *pTitle = pReplayItem->GetItemTitle();
+
+ if ( m_pTitleEntry->HasFocus() && pReplayItem )
+ {
+ if ( code == KEY_ESCAPE )
+ {
+ // Get replay text and reset it
+ m_pTitleEntry->SetText( pTitle );
+
+ // Remove focus
+ GetParent()->GetParent()->RequestFocus();
+ }
+ else if ( code == KEY_ENTER )
+ {
+ // If text is empty, reset to old title
+ if ( m_pTitleEntry->GetTextLength() == 0 )
+ {
+ m_pTitleEntry->SetText( pTitle );
+ }
+ else // Save title...
+ {
+ // Copy text into the replay
+ // NOTE: SetItemTitle() will mark replay as dirty
+ wchar_t wszNewTitle[MAX_REPLAY_TITLE_LENGTH];
+ m_pTitleEntry->GetText( wszNewTitle, sizeof( wszNewTitle ) );
+ pReplayItem->SetItemTitle( wszNewTitle );
+
+ // Save!
+ g_pReplayManager->FlagReplayForFlush( pReplayItem->GetItemReplay(), true );
+
+ // Notify the thumbnail
+ void *pUserData = pReplayItem->GetUserData();
+ if ( pUserData )
+ {
+ CReplayBrowserThumbnail *pThumbnail = (CReplayBrowserThumbnail*)pUserData;
+ pThumbnail->UpdateTitleText();
+ }
+ }
+
+ GetParent()->GetParent()->RequestFocus();
+ }
+
+ return;
+ }
+
+ BaseClass::OnKeyCodeTyped( code );
+}
+
+//-----------------------------------------------------------------------------
+
+CPlaybackPanel::CPlaybackPanel( Panel *pParent )
+: EditablePanel( pParent, "PlaybackPanel" )
+{
+}
+
+CPlaybackPanel::~CPlaybackPanel()
+{
+ ivgui()->RemoveTickSignal( GetVPanel() );
+}
+
+void CPlaybackPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/replaybrowser/playbackpanel.res", "GAME" );
+}
+
+void CPlaybackPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+}
+
+//-----------------------------------------------------------------------------
+
+CPlaybackPanelSlideshow::CPlaybackPanelSlideshow( Panel *pParent, ReplayHandle_t hReplay )
+: CPlaybackPanel( pParent ),
+ m_hReplay( hReplay )
+{
+ m_pScreenshotImage = new CReplayScreenshotSlideshowPanel( this, "Screenshot", hReplay );
+}
+
+void CPlaybackPanelSlideshow::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/replaybrowser/playbackpanelslideshow.res", "GAME" );
+
+ m_pNoScreenshotLabel = dynamic_cast< CExLabel * >( FindChildByName( "NoScreenshotLabel" ) );
+
+ // Check to see if there's a screenshot
+ CGenericClassBasedReplay *pReplay = GetGenericClassBasedReplay( m_hReplay );
+ if ( !pReplay )
+ return;
+
+ if ( !pReplay->GetScreenshotCount() && m_pNoScreenshotLabel ) // Show no-screenshot label
+ {
+ m_pNoScreenshotLabel->SetVisible( true );
+ }
+}
+
+void CPlaybackPanelSlideshow::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ CGenericClassBasedReplay *pReplay = GetGenericClassBasedReplay( m_hReplay );
+
+ int nMarginWidth = GetMarginSize();
+ int nScreenshotWidth = GetViewWidth();
+ if ( m_pScreenshotImage )
+ {
+ m_pScreenshotImage->SetBounds( nMarginWidth, nMarginWidth, nScreenshotWidth, GetTall() - 2*nMarginWidth );
+
+ // Setup screenshot scale based on width of first screenshot (if there are any screenshots at all) - otherwise don't scale
+ float flScale = pReplay->GetScreenshotCount() == 0 ? 1.0f : ( (float)nScreenshotWidth / ( .95f * pReplay->GetScreenshot( 0 )->m_nWidth ) );
+ m_pScreenshotImage->GetImagePanel()->SetScaleAmount( flScale );
+ m_pScreenshotImage->GetImagePanel()->SetShouldScaleImage( true );
+ }
+
+ // Setup the label
+ int nLabelW, nLabelH;
+ m_pNoScreenshotLabel->GetContentSize( nLabelW, nLabelH );
+ m_pNoScreenshotLabel->SetBounds( 0, ( GetTall() - nLabelH ) / 2, GetWide(), nLabelH );
+}
+
+//-----------------------------------------------------------------------------
+
+CPlaybackPanelMovie::CPlaybackPanelMovie( Panel *pParent, ReplayHandle_t hMovie )
+: CPlaybackPanel( pParent )
+{
+ IReplayMovie *pMovie = g_pReplayMovieManager->GetMovie( hMovie );
+ m_pMoviePlayerPanel = new CMoviePlayerPanel( this, "MoviePlayer", pMovie->GetMovieFilename() );
+
+ m_pMoviePlayerPanel->SetLooping( true );
+
+ // TODO: show controls and don't play right away
+ m_pMoviePlayerPanel->Play();
+}
+
+void CPlaybackPanelMovie::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+}
+
+void CPlaybackPanelMovie::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ m_pMoviePlayerPanel->SetBounds( 9, 9, GetViewWidth(), GetViewHeight() );
+ m_pMoviePlayerPanel->SetEnabled( true );
+ m_pMoviePlayerPanel->SetVisible( true );
+ m_pMoviePlayerPanel->SetZPos( 101 );
+}
+
+void CPlaybackPanelMovie::FreeMovieMaterial()
+{
+ m_pMoviePlayerPanel->FreeMaterial();
+}
+
+//-----------------------------------------------------------------------------
+
+CCutImagePanel::CCutImagePanel( Panel *pParent, const char *pName )
+: BaseClass( pParent, pName, "" ),
+ m_pSelectedBorder( NULL )
+{
+}
+
+void CCutImagePanel::SetSelected( bool bState )
+{
+ BaseClass::SetSelected( bState );
+}
+
+IBorder *CCutImagePanel::GetBorder( bool bDepressed, bool bArmed, bool bSelected, bool bKeyFocus )
+{
+ if ( bSelected )
+ {
+ return m_pSelectedBorder;
+ }
+
+ return BaseClass::GetBorder( bDepressed, bArmed, bSelected, bKeyFocus );
+}
+
+void CCutImagePanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+}
+
+//-----------------------------------------------------------------------------
+
+#define FOR_EACH_BUTTON( _i ) for ( int _i = 0; _i < BUTTONS_PER_PAGE; ++_i )
+
+CCutsPanel::CCutsPanel( Panel *pParent, ReplayHandle_t hReplay, int iSelectedPerformance )
+: BaseClass( pParent, "CutsPanel", hReplay ),
+ m_iPage( 0 ),
+ m_nVisibleButtons( 0 ),
+ m_pVerticalLine( NULL ),
+ m_pNoCutsLabel( NULL ),
+ m_pOriginalLabel( NULL ),
+ m_pCutsLabel( NULL )
+{
+ m_hDetailsPanel = dynamic_cast< CReplayDetailsPanel * >( pParent->GetParent() );
+
+ FOR_EACH_BUTTON( i )
+ {
+ const int iButton = i;
+ CFmtStr fmtName( "CutButton%i", iButton );
+
+ CExImageButton *pNewButton = new CExImageButton( this, fmtName.Access(), "" );
+ CFmtStr fmtCommand( "select_%i", iButton );
+ pNewButton->SetCommand( fmtCommand.Access() );
+ pNewButton->InvalidateLayout( true, true );
+ pNewButton->AddActionSignalTarget( this );
+ pNewButton->SetSelected( i == 0 );
+#if !defined( TF_CLIENT_DLL )
+ pNewButton->SetSelectedColor( Color( 0, 0, 0, 0 ), Color( 122, 25, 16, 255 ) );
+#endif
+
+ const int iPerformance = i - 1;
+ m_aButtons[ i ].m_pButton = pNewButton;
+ m_aButtons[ i ].m_iPerformance = iPerformance;
+
+ CExButton *pAddToRenderQueueButton = new CExButton( pNewButton, "AddToRenderQueue", "+", this );
+ m_aButtons[ i ].m_pAddToRenderQueueButton = pAddToRenderQueueButton;
+ }
+
+ // Layout right now
+ InvalidateLayout( true, true );
+
+ // Calculate page
+ SetPage(
+ ( 1 + iSelectedPerformance ) / BUTTONS_PER_PAGE,
+ ( 1 + iSelectedPerformance ) % BUTTONS_PER_PAGE
+ );
+
+ ivgui()->AddTickSignal( GetVPanel(), 10 );
+}
+
+CCutsPanel::~CCutsPanel()
+{
+ ivgui()->RemoveTickSignal( GetVPanel() );
+}
+
+void CCutsPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/replaybrowser/cutspanel.res", "GAME" );
+
+ m_pVerticalLine = dynamic_cast< EditablePanel * >( FindChildByName( "VerticalLine" ) );
+ m_pNoCutsLabel = dynamic_cast< CExLabel * >( FindChildByName( "NoCutsLabel" ) );
+ m_pOriginalLabel = dynamic_cast< CExLabel * >( FindChildByName( "OriginalLabel" ) );
+ m_pCutsLabel = dynamic_cast< CExLabel * >( FindChildByName( "CutsLabel" ) );
+ m_pNameLabel = dynamic_cast< CExLabel * >( FindChildByName( "NameLabel" ) );
+ m_pPrevButton = dynamic_cast< CExButton * >( FindChildByName( "PrevButton" ) );
+ m_pNextButton = dynamic_cast< CExButton * >( FindChildByName( "NextButton" ) );
+
+ FOR_EACH_BUTTON( i )
+ {
+ CExImageButton *pCurButton = m_aButtons[ i ].m_pButton;
+#if !defined( TF_CLIENT_DLL )
+ pCurButton->SetPaintBorderEnabled( false );
+#endif
+ pCurButton->InvalidateLayout( true, true );
+ }
+}
+
+void CCutsPanel::ApplySettings( KeyValues *pInResourceData )
+{
+ BaseClass::ApplySettings( pInResourceData );
+
+ KeyValues *pButtonSettings = pInResourceData->FindKey( "button_settings" );
+ if ( pButtonSettings )
+ {
+ KeyValues *pAddToRenderQueueButtonSettings = pButtonSettings->FindKey( "addtorenderqueuebutton_settings" );
+
+ CReplay *pReplay = GetGenericClassBasedReplay( m_hReplay );
+ FOR_EACH_BUTTON( i )
+ {
+ CExImageButton *pCurButton = m_aButtons[ i ].m_pButton;
+
+ pCurButton->ApplySettings( pButtonSettings );
+
+ // Set screenshot as image
+ if ( pReplay && pReplay->GetScreenshotCount() )
+ {
+ const float flScale = (float)m_nCutButtonHeight / pReplay->GetScreenshot( 0 )->m_nHeight;
+ int nImageWidth = m_nCutButtonWidth - 2 * m_nCutButtonBuffer;
+ int nImageHeight = m_nCutButtonHeight - 2 * m_nCutButtonBuffer;
+
+ CFmtStr fmtFile( "replay\\thumbnails\\%s", pReplay->GetScreenshot( 0 )->m_szBaseFilename );
+ pCurButton->SetSubImage( fmtFile.Access() );
+ pCurButton->GetImage()->SetScaleAmount( flScale );
+ pCurButton->GetImage()->SetBounds( m_nCutButtonBuffer, m_nCutButtonBuffer, nImageWidth, nImageHeight );
+ }
+
+ if ( pAddToRenderQueueButtonSettings )
+ {
+ CExButton *pAddToQueueButton = m_aButtons[ i ].m_pAddToRenderQueueButton;
+ pAddToQueueButton->ApplySettings( pAddToRenderQueueButtonSettings );
+ pAddToQueueButton->AddActionSignalTarget( this );
+ }
+ }
+ }
+}
+
+void CCutsPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ CReplay *pReplay = GetGenericClassBasedReplay( m_hReplay );
+ const int nNumCuts = pReplay->GetPerformanceCount();
+
+ int nX = m_iPage > 0 ? m_pPrevButton->GetWide() + m_nCutButtonSpace : 0;
+
+ m_nVisibleButtons = 0;
+
+ FOR_EACH_BUTTON( i )
+ {
+ CExImageButton *pCurButton = m_aButtons[ i ].m_pButton;
+ const bool bVisible = ButtonToPerformance( i ) < nNumCuts;
+
+ pCurButton->SetVisible( bVisible );
+
+ if ( bVisible )
+ {
+ ++m_nVisibleButtons;
+ }
+
+ pCurButton->SetBounds( nX, m_nButtonStartY, m_nCutButtonWidth, m_nCutButtonHeight );
+ nX += m_nCutButtonWidth;
+
+ if ( i == 0 && m_iPage == 0 )
+ {
+ nX += 2 * m_nCutButtonSpaceWide + m_pVerticalLine->GetWide();
+ }
+ else
+ {
+ nX += m_nCutButtonSpace;
+ }
+ }
+
+ if ( m_pVerticalLine )
+ {
+ m_pVerticalLine->SetVisible( m_nVisibleButtons > 0 && m_iPage == 0 );
+ m_pVerticalLine->SetPos( m_nCutButtonWidth + m_nCutButtonSpaceWide, 0 );
+ m_pVerticalLine->SetTall( m_nTopMarginHeight + GetTall() );
+ }
+
+ const int nRightOfVerticalLineX = m_nCutButtonWidth + m_nCutButtonSpaceWide * 2 + m_pVerticalLine->GetWide();
+
+ if ( m_pNoCutsLabel )
+ {
+ m_pNoCutsLabel->SetVisible( m_nVisibleButtons == 1 && m_iPage == 0 );
+
+ int nY = ( GetTall() - m_pNoCutsLabel->GetTall() ) / 2;
+ m_pNoCutsLabel->SetPos( nRightOfVerticalLineX, nY );
+ }
+
+ if ( m_pOriginalLabel )
+ {
+ m_pOriginalLabel->SetVisible( m_iPage == 0 );
+ }
+
+ if ( m_pCutsLabel )
+ {
+ m_pCutsLabel->SetVisible( m_nVisibleButtons > 1 && m_iPage == 0 );
+ m_pCutsLabel->SetPos( m_nCutButtonWidth + 2 * m_nCutButtonSpaceWide + m_pVerticalLine->GetWide(), 0 );
+ }
+
+ bool bPrevCuts = m_iPage > 0;
+ bool bMoreCuts = ( nNumCuts + 1 ) > ( m_iPage + 1 ) * BUTTONS_PER_PAGE;
+ int nY = m_nTopMarginHeight + ( GetTall() - m_pNextButton->GetTall() ) / 2;
+ m_pPrevButton->SetVisible( bPrevCuts );
+ m_pPrevButton->SetPos( 0, nY );
+ m_pNextButton->SetVisible( bMoreCuts );
+ m_pNextButton->SetPos( nX, nY );
+}
+
+void CCutsPanel::OnTick()
+{
+ if ( !TFModalStack()->IsEmpty() )
+ return;
+
+ int nMouseX, nMouseY;
+ input()->GetCursorPos( nMouseX, nMouseY );
+
+ // Early-out if not within the cuts panel at all.
+ if ( !IsWithin( nMouseX, nMouseY ) )
+ return;
+
+ int iHoverPerformance = -2;
+ bool bFoundHoverButton = false;
+ FOR_EACH_BUTTON( i )
+ {
+ CExImageButton *pCurButton = m_aButtons[ i ].m_pButton;
+ bool bIsHoverButton = false;
+ if ( !bFoundHoverButton && pCurButton->IsWithin( nMouseX, nMouseY ) && pCurButton->IsVisible() )
+ {
+ iHoverPerformance = ButtonToPerformance( i );
+ bFoundHoverButton = true;
+ bIsHoverButton = true;
+ }
+
+ CExButton *pAddToRenderQueueButton = m_aButtons[ i ].m_pAddToRenderQueueButton;
+ if ( pAddToRenderQueueButton )
+ {
+ pAddToRenderQueueButton->SetVisible( bIsHoverButton );
+
+ if ( iHoverPerformance >= -1 )
+ {
+ // Set the text and command based on whether or not the take's already been queued
+ const bool bInQueue = g_pClientReplayContext->GetRenderQueue()->IsInQueue( m_hReplay, iHoverPerformance );
+ CFmtStr fmtCmd( "%srenderqueue_%i", bInQueue ? "removefrom" : "addto", iHoverPerformance );
+ pAddToRenderQueueButton->SetCommand( fmtCmd.Access() );
+ pAddToRenderQueueButton->SetText( bInQueue ? "-" : "+" );
+ }
+ }
+ }
+
+ // If the mouse is over a performance button, use that, otherwise use the selected
+ // performance.
+ if ( m_hDetailsPanel.Get() )
+ {
+ int iSelectedPerformance = m_hDetailsPanel->m_iSelectedPerformance;
+ UpdateNameLabel( iHoverPerformance >= 0 ? iHoverPerformance : iSelectedPerformance >= 0 ? iSelectedPerformance : -1 );
+ }
+}
+
+int CCutsPanel::ButtonToPerformance( int iButton ) const
+{
+ return -1 + m_iPage * BUTTONS_PER_PAGE + iButton;
+}
+
+void CCutsPanel::OnCommand( const char *pCommand )
+{
+ if ( !V_strnicmp( pCommand, "select_", 7 ) )
+ {
+ const int iButton = atoi( pCommand + 7 );
+ SelectButtonFromPerformance( ButtonToPerformance( iButton ) );
+ }
+ else if ( !V_stricmp( pCommand, "prevpage" ) )
+ {
+ SetPage( m_iPage - 1 );
+ }
+ else if ( !V_stricmp( pCommand, "nextpage" ) )
+ {
+ SetPage( m_iPage + 1 );
+ }
+ else if ( !V_strnicmp( pCommand, "addtorenderqueue_", 17 ) )
+ {
+ if ( !replay_renderqueue_first_add.GetInt() )
+ {
+ ShowMessageBox( "#Replay_FirstRenderQueueAddTitle", "#Replay_FirstRenderQueueAddMsg", "#GameUI_OK" );
+ replay_renderqueue_first_add.SetValue( 1 );
+ }
+
+ const int iPerformance = atoi( pCommand + 17 );
+ if ( iPerformance >= -1 )
+ {
+ g_pClientReplayContext->GetRenderQueue()->Add( m_hReplay, iPerformance );
+ }
+ }
+ else if ( !V_strnicmp( pCommand, "removefromrenderqueue_", 22 ) )
+ {
+ const int iPerformance = atoi( pCommand + 22 );
+ if ( iPerformance >= -1 )
+ {
+ g_pClientReplayContext->GetRenderQueue()->Remove( m_hReplay, iPerformance );
+ }
+ }
+}
+
+void CCutsPanel::SetPage( int iPage, int iButtonToSelect )
+{
+ m_iPage = iPage;
+
+ FOR_EACH_BUTTON( i )
+ {
+ ButtonInfo_t *pCurButtonInfo = &m_aButtons[ i ];
+ const int iPerformance = ButtonToPerformance( i );
+ pCurButtonInfo->m_iPerformance = iPerformance;
+ }
+
+ InvalidateLayout( true, false );
+ SelectButtonFromPerformance( ButtonToPerformance( iButtonToSelect ) );
+}
+
+const CReplayPerformance *CCutsPanel::GetPerformance( int iPerformance ) const
+{
+ const CReplay *pReplay = GetGenericClassBasedReplay( m_hReplay );
+ if ( !pReplay )
+ return NULL;
+
+ return iPerformance >= 0 ? pReplay->GetPerformance( iPerformance ) : NULL;
+}
+
+void CCutsPanel::SelectButtonFromPerformance( int iPerformance )
+{
+ FOR_EACH_BUTTON( i )
+ {
+ const ButtonInfo_t *pCurButtonInfo = &m_aButtons[ i ];
+ CExImageButton *pCurButton = pCurButtonInfo->m_pButton;
+ pCurButton->SetSelected( pCurButtonInfo->m_iPerformance == iPerformance );
+ pCurButton->InvalidateLayout( true, true );
+ }
+
+ // Cache which performance to use in the details panel
+ if ( m_hDetailsPanel.Get() )
+ {
+ m_hDetailsPanel->m_iSelectedPerformance = iPerformance;
+ }
+
+ UpdateNameLabel( iPerformance );
+}
+
+int CCutsPanel::PerformanceToButton( int iPerformance ) const
+{
+ FOR_EACH_BUTTON( i )
+ {
+ if ( m_aButtons[ i ].m_iPerformance == iPerformance )
+ {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+void CCutsPanel::UpdateNameLabel( int iPerformance )
+{
+ const CReplayPerformance *pPerformance = GetPerformance( iPerformance );
+ m_pNameLabel->SetText( pPerformance ? pPerformance->m_wszTitle : L"" );
+
+ // Get the button (in the range [0,BUTTONS_PER_PAGE]).
+ const int iPerformanceButton = PerformanceToButton( iPerformance ); // Not necessarily the selected button - can be hover button
+
+ // Get position of the button so we can use it's x position.
+ int aSelectedButtonPos[2];
+ m_aButtons[ iPerformanceButton ].m_pButton->GetPos( aSelectedButtonPos[0], aSelectedButtonPos[1] );
+
+ if ( m_pNameLabel )
+ {
+ const int nNameLabelX = aSelectedButtonPos[0];
+ const int nNameLabelY = m_nButtonStartY + m_nCutButtonHeight + m_nNameLabelTopMargin;
+ m_pNameLabel->SetBounds(
+ nNameLabelX,
+ nNameLabelY,
+ GetWide() - nNameLabelX,
+ GetTall() - nNameLabelY
+ );
+ }
+}
+
+void CCutsPanel::OnPerformanceDeleted( int iPerformance )
+{
+ int iButton = PerformanceToButton( iPerformance );
+ if ( iButton < 0 )
+ return;
+
+ // Deleted last performance on page?
+ CReplay *pReplay = GetGenericClassBasedReplay( m_hReplay );
+ const int nNumCuts = pReplay->GetPerformanceCount();
+ if ( iPerformance == m_aButtons[ 0 ].m_iPerformance && iPerformance == nNumCuts )
+ {
+ SetPage( m_iPage - 1, BUTTONS_PER_PAGE - 1 );
+ }
+ else
+ {
+ SelectButtonFromPerformance( ButtonToPerformance( MIN( m_nVisibleButtons - 1, MAX( 0, iButton ) ) ) );
+ }
+
+ // Select the cut prior to the one we just deleted
+ Assert( iPerformance >= 0 );
+
+ InvalidateLayout( true, false );
+}
+
+//-----------------------------------------------------------------------------
+
+static void ConfirmUploadMovie( bool bConfirmed, void *pContext )
+{
+ if ( bConfirmed )
+ {
+ CReplayDetailsPanel *pPanel = (CReplayDetailsPanel*)pContext;
+ IQueryableReplayItem *pReplayItem = pPanel->m_pItemManager->GetItem( pPanel->m_hReplayItem );
+ if ( pReplayItem && pReplayItem->IsItemAMovie() )
+ {
+ IReplayMovie *pMovie = static_cast< IReplayMovie * >( pReplayItem );
+ if ( YouTube_GetLoginStatus() != kYouTubeLogin_LoggedIn )
+ {
+ YouTube_ShowLoginDialog( pMovie, pPanel );
+ }
+ else
+ {
+ YouTube_ShowUploadDialog( pMovie, pPanel );
+ }
+ }
+ }
+}
+
+class CYouTubeGetStatsHandler : public CYouTubeResponseHandler
+{
+public:
+ CYouTubeGetStatsHandler( CReplayDetailsPanel *pPanel )
+ : m_pPanel( pPanel )
+ , m_handle( NULL )
+ {
+ }
+
+ virtual ~CYouTubeGetStatsHandler()
+ {
+ if ( m_handle != NULL )
+ {
+ YouTube_CancelGetVideoInfo( m_handle );
+ }
+ }
+
+ static bool GetEmptyElementTagContents( const char *pXML, const char *pTag, CUtlString &strTagContents )
+ {
+ CFmtStr1024 kLinkTagStart( "<%s ", pTag );
+ const char *kLinkTagEnd = "/>";
+ const char *pStart = strstr( pXML, kLinkTagStart.Access() );
+ if ( pStart != NULL )
+ {
+ pStart += kLinkTagStart.Length();
+ const char *pEnd = strstr( pStart, kLinkTagEnd );
+ if ( pEnd != NULL )
+ {
+ strTagContents.SetDirect( pStart, pEnd - pStart );
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static bool GetEmptyTagValue( const char *pTagContents, const char *pKeyName, CUtlString &value )
+ {
+ CFmtStr1024 kStart( "%s='", pKeyName );
+ const char *kEnd = "'";
+ const char *pStart = strstr( pTagContents, kStart.Access() );
+ if ( pStart != NULL )
+ {
+ pStart += kStart.Length();
+ const char *pEnd = strstr( pStart, kEnd );
+ if ( pEnd != NULL )
+ {
+ value.SetDirect( pStart, pEnd - pStart );
+ return true;
+ }
+ }
+ return false;
+ }
+
+ virtual void HandleResponse( long responseCode, const char *pResponse )
+ {
+ // @note tom bui: wish I had an XML parser
+
+ if ( strstr( pResponse, "<internalReason>Private video</internalReason>" ) != NULL )
+ {
+ m_pPanel->m_pYouTubeInfoPanel->SetInfo( g_pVGuiLocalize->Find( "#YouTube_PrivateVideo" ) );
+ m_pPanel->SetYouTubeStatus( CReplayDetailsPanel::kYouTubeStatus_Private );
+ return;
+ }
+
+ int iNumFavorited = 0;
+ int iNumViews = 0;
+ int iNumLikes = 0;
+
+ wchar_t wszFavorited[256] = L"0";
+ wchar_t wszViews[256] = L"0";
+
+ CUtlString strTagStatistics;
+ if ( GetEmptyElementTagContents( pResponse, "yt:statistics", strTagStatistics ) )
+ {
+ CUtlString favoriteCount;
+ CUtlString viewCount;
+ GetEmptyTagValue( strTagStatistics, "favoriteCount", favoriteCount );
+ GetEmptyTagValue( strTagStatistics, "viewCount", viewCount );
+
+ iNumFavorited = Q_atoi( favoriteCount.Get() );
+ iNumViews = Q_atoi( viewCount.Get() );
+
+ g_pVGuiLocalize->ConvertANSIToUnicode( favoriteCount.Get(), wszFavorited, sizeof( wszFavorited ) );
+ g_pVGuiLocalize->ConvertANSIToUnicode( viewCount.Get(), wszViews, sizeof( wszViews ) );
+ }
+
+ wchar_t wszLikes[256] = L"0";
+ CUtlString strTagRating;
+ if ( GetEmptyElementTagContents( pResponse, "yt:rating", strTagRating ) )
+ {
+ CUtlString likes;
+ GetEmptyTagValue( strTagRating, "numLikes", likes );
+ iNumLikes = Q_atoi( likes.Get() );
+ g_pVGuiLocalize->ConvertANSIToUnicode( likes.Get(), wszLikes, sizeof( wszLikes ) );
+ }
+
+ //const char *kLinkStartTag = "<link rel='alternate' type='text/html' href='";
+ CUtlString strTagLink;
+ if ( GetEmptyElementTagContents( pResponse, "link rel='alternate'", strTagLink ) )
+ {
+ GetEmptyTagValue( strTagLink, "href", m_strVideoURL );
+ }
+
+ wchar_t wszStats[256] = L"";
+ g_pVGuiLocalize->ConstructString( wszStats,sizeof( wszStats ), g_pVGuiLocalize->Find( "#YouTube_Stats" ), 3,
+ wszFavorited,
+ wszViews,
+ wszLikes );
+
+ if ( m_strVideoURL.IsEmpty() == false )
+ {
+ m_pPanel->m_pYouTubeInfoPanel->SetInfo( wszStats );
+ m_pPanel->SetYouTubeStatus( CReplayDetailsPanel::kYouTubeStatus_RetrievedInfo );
+ m_pPanel->InvalidateLayout();
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "replay_youtube_stats" );
+ if ( event )
+ {
+ event->SetInt( "views", iNumViews );
+ event->SetInt( "likes", iNumLikes );
+ event->SetInt( "favorited", iNumFavorited );
+ gameeventmanager->FireEventClientSide( event );
+ }
+ }
+ else
+ {
+ m_pPanel->m_pYouTubeInfoPanel->SetInfo( g_pVGuiLocalize->Find( "#YouTube_CouldNotRetrieveStats" ) );
+ m_pPanel->SetYouTubeStatus( CReplayDetailsPanel::kYouTubeStatus_CouldNotRetrieveInfo );
+ }
+ }
+
+ CReplayDetailsPanel *m_pPanel;
+ YouTubeInfoHandle_t m_handle;
+ CUtlString m_strVideoURL;
+};
+
+CReplayDetailsPanel::CReplayDetailsPanel( Panel *pParent, QueryableReplayItemHandle_t hReplayItem,
+ int iPerformance, IReplayItemManager *pItemManager )
+: EditablePanel( pParent, "DetailsPanel" ),
+ m_hReplayItem( hReplayItem ),
+ m_pItemManager( pItemManager ),
+ m_pCutsPanel( NULL ),
+ m_iSelectedPerformance( iPerformance ),
+ m_pYouTubeResponseHandler( NULL ),
+ m_hExportMovieDialog( NULL )
+{
+ m_hReplay = pItemManager->GetItem( hReplayItem )->GetItemReplayHandle();
+
+ CGenericClassBasedReplay *pReplay = GetGenericClassBasedReplay( m_hReplay );
+
+ m_pInsetPanel = new EditablePanel( this, "InsetPanel" );
+ m_pTitleEditPanel = new CTitleEditPanel( GetInset(), m_hReplayItem, m_pItemManager );
+ m_pPlaybackPanel = new CPlaybackPanelSlideshow( GetInset(), m_hReplay );
+ m_pRecordsPanel = new CRecordsPanel( GetInset(), m_hReplay );
+
+ m_pInfoPanel = new EditablePanel( this, "InfoContainerPanel" );
+ m_pScrollPanel = new vgui::ScrollableEditablePanel( GetInset(), m_pInfoPanel, "StatsScroller" );
+ m_pScrollPanel->GetScrollbar()->SetAutohideButtons( true );
+#if !defined( TF_CLIENT_DLL )
+ for ( int i = 0; i < 2; ++i )
+ {
+ m_pScrollPanel->GetScrollbar()->GetButton( i )->SetPaintBorderEnabled( false );
+ }
+#endif
+
+ m_pBasicInfoPanel = new CBasicLifeInfoPanel( m_pInfoPanel, m_hReplay );
+ m_pStatsPanel = new CStatsPanel( m_pInfoPanel, m_hReplay );
+ m_pKillsPanel = new CKillsPanel( m_pInfoPanel, m_hReplay );
+
+ const bool bIsMoviePanel = pItemManager->AreItemsMovies();
+ if ( bIsMoviePanel )
+ {
+ m_pYouTubeInfoPanel = new CYouTubeInfoPanel( m_pInfoPanel );
+ }
+ else
+ {
+ m_pCutsPanel = new CCutsPanel( GetInset(), m_hReplay, m_iSelectedPerformance );
+ }
+
+ // Add info panels to a list
+
+ if ( pReplay->GetDominationCount() )
+ {
+ m_pDominationsPanel = new CDominationsPanel( m_pInfoPanel, m_hReplay );
+ m_vecInfoPanels.AddToTail( m_pDominationsPanel );
+ }
+
+ m_vecInfoPanels.AddToTail( m_pBasicInfoPanel );
+ m_vecInfoPanels.AddToTail( m_pStatsPanel );
+ m_vecInfoPanels.AddToTail( m_pKillsPanel );
+
+ if ( bIsMoviePanel )
+ {
+ m_vecInfoPanels.AddToTail( m_pYouTubeInfoPanel );
+ }
+
+ m_pYouTubeResponseHandler = new CYouTubeGetStatsHandler( this );
+
+ RequestFocus();
+}
+
+CReplayDetailsPanel::~CReplayDetailsPanel()
+{
+ m_pDeleteButton->MarkForDeletion();
+ m_pRenderButton->MarkForDeletion();
+ m_pPlayButton->MarkForDeletion();
+ delete m_pYouTubeResponseHandler;
+}
+
+void CReplayDetailsPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/replaybrowser/detailspanel.res", "GAME" );
+
+ m_pExportMovie = dynamic_cast< CExButton * >( FindChildByName( "ExportMovieButton" ) );
+ m_pDeleteButton = dynamic_cast< CExButton * >( FindChildByName( "DeleteButton" ) );
+ m_pRenderButton = dynamic_cast< CExButton * >( FindChildByName( "RenderButton" ) );
+ m_pPlayButton = dynamic_cast< CExButton * >( FindChildByName( "PlayButton" ) );
+ m_pYouTubeUpload = dynamic_cast< CExButton * >( FindChildByName( "YouTubeUploadButton" ) );
+ m_pYouTubeView = dynamic_cast< CExButton * >( FindChildByName( "ViewYouTubeButton" ) );
+ m_pYouTubeShareURL = dynamic_cast< CExButton * >( FindChildByName( "ShareYouTubeURLButton" ) );
+ m_pShowRenderInfoButton = dynamic_cast< CExImageButton * >( FindChildByName( "ShowRenderInfoButton") );
+
+ if ( m_pDeleteButton )
+ {
+ SetXToRed( m_pDeleteButton );
+ }
+
+ m_pExportMovie->SetParent( GetInset() );
+ m_pYouTubeUpload->SetParent( GetInset() );
+ m_pYouTubeView->SetParent( GetInset() );
+ m_pYouTubeShareURL->SetParent( GetInset() );
+ m_pShowRenderInfoButton->SetParent( GetInset() );
+
+ m_pDeleteButton->SetParent( GetParent()->GetParent()->GetParent() );
+ m_pPlayButton->SetParent( GetParent()->GetParent()->GetParent() );
+ m_pRenderButton->SetParent( GetParent()->GetParent()->GetParent() );
+
+ m_pDeleteButton->AddActionSignalTarget( this );
+ m_pPlayButton->AddActionSignalTarget( this );
+ m_pRenderButton->AddActionSignalTarget( this );
+}
+
+void CReplayDetailsPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ SetTall( GetParent()->GetTall() );
+
+ int nInsetWidth = GetInset()->GetWide();
+ int nScreenshotWidth = nInsetWidth * .55f;
+
+ // Setup info panels along the right-hand side
+ const int nBuffer = 7;
+ const int nLeftRightBuffer = 19;
+ int aPlaybackPos[2];
+ m_pPlaybackPanel->GetPos( aPlaybackPos[0], aPlaybackPos[1] );
+ int nInfoPanelsStartY = aPlaybackPos[1];
+ int nInfoPanelsCurrentY = nInfoPanelsStartY;
+ int nRightColumnWidth = nInsetWidth - nScreenshotWidth - nLeftRightBuffer - XRES(20);
+
+#if defined( TF_CLIENT_DLL )
+ if ( m_pRecordsPanel->ShouldShow() )
+ {
+ m_pRecordsPanel->SetPos( nScreenshotWidth + nLeftRightBuffer, nInfoPanelsStartY );
+ m_pRecordsPanel->SetWide( nRightColumnWidth );
+ m_pRecordsPanel->InvalidateLayout( true, true );
+ m_pRecordsPanel->SetVisible( true );
+ nInfoPanelsCurrentY += m_pRecordsPanel->GetTall() + nBuffer;
+ }
+ else
+#endif
+ {
+ m_pRecordsPanel->SetVisible( false );
+ }
+
+ int insetX, insetY;
+ GetInset()->GetPos( insetX, insetY );
+ m_pScrollPanel->SetPos( nScreenshotWidth + nLeftRightBuffer, nInfoPanelsCurrentY );
+ m_pScrollPanel->SetWide( nRightColumnWidth + XRES(20) );
+ m_pScrollPanel->SetTall( GetTall() - insetY - nInfoPanelsCurrentY );
+ m_pInfoPanel->SetWide( nRightColumnWidth );
+
+ int nCurrentY = 0;
+ for ( int i = 0; i < m_vecInfoPanels.Count(); ++i )
+ {
+ CBaseDetailsPanel *pPanel = m_vecInfoPanels[ i ];
+
+ if ( pPanel->ShouldShow() )
+ {
+ // Set the width since these panel's PerformLayout()'s depend on it
+ pPanel->SetWide( nRightColumnWidth );
+
+ // Call panel's PerformLayout() now
+ pPanel->InvalidateLayout( true, true );
+
+ pPanel->SetPos( 0, nCurrentY );
+
+ // Show it
+ pPanel->SetVisible( true );
+
+ // Update the current y position based on the panel's height (set in its PerformLayout())
+ nCurrentY += pPanel->GetTall() + nBuffer;
+ }
+ else
+ {
+ pPanel->SetVisible( false );
+ }
+ }
+ m_pInfoPanel->SetTall( nCurrentY );
+ m_pInfoPanel->InvalidateLayout( true );
+ m_pScrollPanel->InvalidateLayout( true );
+ m_pScrollPanel->GetScrollbar()->SetAutohideButtons( true );
+ m_pScrollPanel->GetScrollbar()->InvalidateLayout( true );
+
+ // @note Tom Bui: set the positions AGAIN now that we've invalidated, cause VGUI hates me
+ nCurrentY = 0;
+ for ( int i = 0; i < m_vecInfoPanels.Count(); ++i )
+ {
+ CBaseDetailsPanel *pPanel = m_vecInfoPanels[ i ];
+ if ( pPanel->ShouldShow() )
+ {
+ pPanel->SetPos( 0, nCurrentY );
+ nCurrentY += pPanel->GetTall() + nBuffer;
+ }
+ }
+
+ // Setup playback panel based on dimensions of first screenshot
+ CGenericClassBasedReplay *pReplay = GetGenericClassBasedReplay( m_hReplay );
+ float flAspectRatio;
+ if ( pReplay->GetScreenshotCount() )
+ {
+ const CReplayScreenshot *pScreenshot = pReplay->GetScreenshot( 0 );
+ flAspectRatio = (float)pScreenshot->m_nWidth / pScreenshot->m_nHeight;
+ }
+ else
+ {
+ // Default to 4:3 if there are no screenshots
+ flAspectRatio = 4.0f/3;
+ }
+
+ if ( m_pItemManager->AreItemsMovies() )
+ {
+ m_pRenderButton->SetVisible( false );
+ m_pPlayButton->SetVisible( false );
+ m_pExportMovie->SetVisible( true );
+ m_pShowRenderInfoButton->SetVisible( true );
+
+ int nButtonY = nInfoPanelsStartY + m_pPlaybackPanel->GetTall() + YRES( 5 );
+ int nButtonX = 0;
+ m_pYouTubeUpload->SetPos( nButtonX, nButtonY );
+ m_pYouTubeView->SetPos( nButtonX, nButtonY );
+ nButtonX += m_pYouTubeUpload->GetWide() + XRES( 5 );
+
+ m_pYouTubeShareURL->SetPos( nButtonX, nButtonY );
+ nButtonX += m_pYouTubeShareURL->GetWide() + XRES( 5 );
+
+ m_pExportMovie->SetPos( nButtonX, nButtonY );
+
+ int aDeletePos[2];
+ m_pDeleteButton->GetPos( aDeletePos[0], aDeletePos[1] );
+ m_pDeleteButton->SetPos( ScreenWidth() / 2 + XRES( 195 ), aDeletePos[1] );
+
+ int aScreenshotPos[2];
+ m_pPlaybackPanel->GetPos( aScreenshotPos[0], aScreenshotPos[1] );
+ m_pShowRenderInfoButton->SetPos(
+ aScreenshotPos[0] + m_pPlaybackPanel->GetWide() - m_pShowRenderInfoButton->GetWide() - XRES( 8 ),
+ aScreenshotPos[1] + m_pPlaybackPanel->GetTall() - m_pShowRenderInfoButton->GetTall() - YRES( 8 )
+ );
+
+ // retrieve stats
+ if ( m_pYouTubeResponseHandler->m_handle == NULL )
+ {
+ IQueryableReplayItem *pReplayItem = m_pItemManager->GetItem( m_hReplayItem );
+ if ( pReplayItem && pReplayItem->IsItemAMovie() )
+ {
+ IReplayMovie *pMovie = static_cast< IReplayMovie * >( pReplayItem );
+ if ( pMovie->IsUploaded() )
+ {
+ m_pYouTubeResponseHandler->m_handle = YouTube_GetVideoInfo( pMovie->GetUploadURL(), *m_pYouTubeResponseHandler );
+ SetYouTubeStatus( kYouTubeStatus_RetrievingInfo );
+ }
+ else
+ {
+ SetYouTubeStatus( kYouTubeStatus_NotUploaded );
+ }
+ }
+ }
+ }
+ else
+ {
+ m_pYouTubeUpload->SetVisible( false );
+ m_pYouTubeView->SetVisible( false );
+ m_pYouTubeShareURL->SetVisible( false );
+ m_pShowRenderInfoButton->SetVisible( false );
+
+ // Without this, the name label won't show when we automatically select the recently watched/saved
+ // performance, because the cuts panel width/height isn't set when UpdateNameLabel() gets called
+ // from within CCutsPanel::CCutsPanel().
+ m_pCutsPanel->UpdateNameLabel( m_iSelectedPerformance );
+ }
+}
+
+/*static*/ void CReplayDetailsPanel::OnPlayerWarningDlgConfirm( bool bConfirmed, void *pContext )
+{
+ CReplayDetailsPanel *pPanel = (CReplayDetailsPanel*)pContext;
+ pPanel->ShowExportDialog();
+}
+
+void CReplayDetailsPanel::ShowExportDialog()
+{
+ IQueryableReplayItem *pReplayItem = m_pItemManager->GetItem( m_hReplayItem );
+ if ( pReplayItem && pReplayItem->IsItemAMovie() )
+ {
+ IReplayMovie *pMovie = static_cast< IReplayMovie * >( pReplayItem );
+ CFmtStr srcMovieFullFilename( "%s%s", g_pReplayMovieManager->GetRenderDir(), pMovie->GetMovieFilename() );
+ if ( !g_pFullFileSystem->FileExists( srcMovieFullFilename.Access() ) )
+ {
+ ShowMessageBox( "#Replay_ExportMovieError_Title", "#Replay_ExportMovieNoFile_Text", "#GameUI_OK" );
+ return;
+ }
+ }
+
+ if ( m_hExportMovieDialog == NULL )
+ {
+ m_hExportMovieDialog = new FileOpenDialog(NULL, "#Replay_FindExportMovieLocation", FOD_SAVE );
+#ifdef USE_WEBM_FOR_REPLAY
+ m_hExportMovieDialog->AddFilter("*.webm", "#Replay_WebMMovieFiles", true );
+#else
+ m_hExportMovieDialog->AddFilter("*.mov", "#Replay_MovieFiles", true );
+#endif
+ m_hExportMovieDialog->AddActionSignalTarget( this );
+ if ( !FStrEq( replay_movie_export_last_dir.GetString(), "" ) )
+ {
+ m_hExportMovieDialog->SetStartDirectory( replay_movie_export_last_dir.GetString() );
+ }
+ }
+ m_hExportMovieDialog->DoModal(false);
+ m_hExportMovieDialog->Activate();
+}
+
+void CReplayDetailsPanel::OnFileSelected( const char *fullpath )
+{
+ // this can take a while, put up a waiting cursor
+ surface()->SetCursor(dc_hourglass);
+
+ IQueryableReplayItem *pReplayItem = m_pItemManager->GetItem( m_hReplayItem );
+ if ( pReplayItem && pReplayItem->IsItemAMovie() )
+ {
+ IReplayMovie *pMovie = static_cast< IReplayMovie * >( pReplayItem );
+ CFmtStr srcMovieFullFilename( "%s%s", g_pReplayMovieManager->GetRenderDir(), pMovie->GetMovieFilename() );
+ if ( !engine->CopyLocalFile( srcMovieFullFilename.Access(), fullpath ) )
+ {
+ ShowMessageBox( "#Replay_ExportMovieError_Title", "#Replay_ExportMovieError_Text", "#GameUI_OK" );
+ }
+ else
+ {
+ ShowMessageBox( "#Replay_ExportMovieSuccess_Title", "#Replay_ExportMovieSuccess_Text", "#GameUI_OK" );
+ }
+ char basepath[ MAX_PATH ];
+ Q_ExtractFilePath( fullpath, basepath, sizeof( basepath ) );
+ replay_movie_export_last_dir.SetValue( basepath );
+ }
+
+ // change the cursor back to normal
+ surface()->SetCursor(dc_user);
+}
+
+void CReplayDetailsPanel::OnCommand( const char *pCommand )
+{
+ if ( FStrEq( pCommand, "delete_replayitem" ) )
+ {
+ ReplayUI_GetBrowserPanel()->AttemptToDeleteReplayItem( this, m_hReplayItem, m_pItemManager, m_iSelectedPerformance );
+ return;
+ }
+
+ else if ( FStrEq( pCommand, "render_replay_dlg" ) )
+ {
+ ShowRenderDialog();
+ return;
+ }
+
+ else if ( FStrEq( pCommand, "play" ) )
+ {
+ if ( engine->IsInGame() )
+ {
+ ShowPlayConfirmationDialog();
+ }
+ else
+ {
+ g_pClientReplayContext->PlayReplay( m_hReplay, m_iSelectedPerformance, true );
+ }
+ return;
+ }
+
+ else if ( FStrEq( pCommand, "exportmovie" ) )
+ {
+ IQueryableReplayItem *pReplayItem = m_pItemManager->GetItem( m_hReplayItem );
+ if ( !pReplayItem || !pReplayItem->IsItemAMovie() )
+ return;
+
+ IReplayMovie *pMovie = static_cast< IReplayMovie * >( pReplayItem );
+ if ( !pMovie )
+ return;
+
+ if ( replay_movie_reveal_warning.GetBool() )
+ {
+#ifdef USE_WEBM_FOR_REPLAY
+ CTFMessageBoxDialog *pDialog = ShowMessageBox( "#Replay_Tip", "#Replay_UseVLCPlayer", "#Replay_ThanksIWill", OnPlayerWarningDlgConfirm );
+#else
+ CTFMessageBoxDialog *pDialog = ShowMessageBox( "#Replay_Tip", "#Replay_UseQuickTimePlayer", "#Replay_ThanksIWill", OnPlayerWarningDlgConfirm );
+#endif
+ pDialog->SetContext( this );
+ replay_movie_reveal_warning.SetValue( 0 );
+ }
+ else if ( pMovie->GetRenderSettings().m_bRaw )
+ {
+ ShowMessageBox( "#Replay_CantExport", "#YouTube_Upload_MovieIsRaw", "#GameUI_OK" );
+ }
+ else
+ {
+ ShowExportDialog();
+ }
+ }
+
+ else if ( FStrEq( pCommand, "youtubeupload" ) )
+ {
+ IQueryableReplayItem *pReplayItem = m_pItemManager->GetItem( m_hReplayItem );
+ if ( pReplayItem && pReplayItem->IsItemAMovie() )
+ {
+ IReplayMovie *pMovie = static_cast< IReplayMovie * >( pReplayItem );
+ if ( !pMovie )
+ return;
+
+ if ( pMovie->GetRenderSettings().m_bRaw )
+ {
+ ShowMessageBox( "#Replay_CantUpload", "#YouTube_Upload_MovieIsRaw", "#GameUI_OK" );
+ return;
+ }
+
+ // Movie already exists?
+ CFmtStr srcMovieFullFilename( "%s%s", g_pReplayMovieManager->GetRenderDir(), pMovie->GetMovieFilename() );
+ if ( !g_pFullFileSystem->FileExists( srcMovieFullFilename.Access() ) )
+ {
+ ShowMessageBox( "#YouTube_Upload_Title", "#YouTube_Upload_MissingFile", "#GameUI_OK" );
+ return;
+ }
+ else if ( pMovie->IsUploaded() )
+ {
+ CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#YouTube_Upload_Title", "#YouTube_FileAlreadyUploaded", "#GameUI_OK", "#GameuI_CancelBold", &ConfirmUploadMovie, this );
+ pDialog->SetContext( this );
+ }
+ else
+ {
+ ConfirmUploadMovie( true, this );
+ }
+ }
+ }
+
+ else if ( FStrEq( pCommand, "viewyoutube" ) )
+ {
+ if ( steamapicontext && steamapicontext->SteamFriends() && m_pYouTubeResponseHandler->m_strVideoURL.IsEmpty() == false )
+ {
+ steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( m_pYouTubeResponseHandler->m_strVideoURL.Get() );
+ }
+ }
+
+ else if ( FStrEq( pCommand, "shareyoutubeurl" ) )
+ {
+ system()->SetClipboardText( m_pYouTubeResponseHandler->m_strVideoURL.Get(), m_pYouTubeResponseHandler->m_strVideoURL.Length() );
+ ShowMessageBox( "#Replay_CopyURL_Title", "#Replay_CopyURL_Text", "#GameUI_OK" );
+ }
+
+ else if ( FStrEq( pCommand, "showrenderinfo" ) )
+ {
+ ShowRenderInfo();
+ }
+
+ else
+ {
+ BaseClass::OnCommand( pCommand );
+ }
+}
+
+void CReplayDetailsPanel::ShowRenderInfo()
+{
+ IQueryableReplayItem *pReplayItem = m_pItemManager->GetItem( m_hReplayItem );
+ if ( !pReplayItem || !pReplayItem->IsItemAMovie() )
+ return;
+
+ IReplayMovie *pMovie = static_cast< IReplayMovie * >( pReplayItem );
+ const ReplayRenderSettings_t &Settings = pMovie->GetRenderSettings();
+ const wchar_t *pCodecName = g_pVideo ? g_pVideo->GetCodecName( Settings.m_Codec ) : L"?";
+ wchar_t *pAAEnabled = g_pVGuiLocalize->Find( Settings.m_bAAEnabled ? "#Replay_Enabled" : "#Replay_Disabled" );
+ wchar_t *pRaw = g_pVGuiLocalize->Find( Settings.m_bRaw ? "#Replay_Yes" : "#Replay_No" );
+ CFmtStr fmtRes( "%ix%i", Settings.m_nWidth, Settings.m_nHeight );
+ CFmtStr fmtFramerate( "%.3f", Settings.m_FPS.GetFPS() );
+
+ KeyValuesAD kvParams( "params" );
+ kvParams->SetString( "res", fmtRes.Access() );
+ kvParams->SetString( "framerate", fmtFramerate.Access() );
+ kvParams->SetInt( "motionblurquality", Settings.m_nMotionBlurQuality );
+ kvParams->SetInt( "encodingquality", Settings.m_nEncodingQuality );
+ kvParams->SetWString( "codec", pCodecName );
+ kvParams->SetWString( "antialiasing", pAAEnabled );
+ kvParams->SetString( "rendertime", CReplayTime::FormatTimeString( pMovie->GetRenderTime() ) );
+ kvParams->SetWString( "raw", pRaw );
+
+ wchar_t wszStr[1024];
+ g_pVGuiLocalize->ConstructString(
+ wszStr,
+ sizeof( wszStr ),
+ "#Replay_MovieRenderInfo",
+ kvParams
+ );
+
+ ShowMessageBox( "#Replay_RenderInfo", wszStr, "#GameUI_OK" );
+}
+
+void CReplayDetailsPanel::GoBack()
+{
+ // Send to parent
+ GetParent()->OnCommand( "back" );
+}
+
+void CReplayDetailsPanel::ShowPlayConfirmationDialog()
+{
+ CConfirmDisconnectFromServerDialog *pConfirm = SETUP_PANEL( new CConfirmDisconnectFromServerDialog( this ) );
+ if ( pConfirm )
+ {
+ pConfirm->Show();
+ }
+}
+
+void CReplayDetailsPanel::OnConfirmDisconnect( KeyValues *pParams )
+{
+ if ( pParams->GetBool( "confirmed" ) )
+ {
+ g_pClientReplayContext->PlayReplay( m_hReplay, m_iSelectedPerformance, true );
+ }
+}
+
+void CReplayDetailsPanel::OnMessage( const KeyValues* pParams, VPANEL hFromPanel )
+{
+ if ( FStrEq( pParams->GetName(), "ReplayItemDeleted" ) )
+ {
+ const int iPerformance = const_cast< KeyValues * >( pParams )->GetInt( "perf", -1 );
+ if ( iPerformance >= 0 )
+ {
+ CReplayPerformance *pPerformance = GetGenericClassBasedReplay( m_hReplay )->GetPerformance( m_iSelectedPerformance );
+ g_pReplayPerformanceManager->DeletePerformance( pPerformance );
+ m_pCutsPanel->InvalidateLayout( true, false ); // Without this, m_nVisibleButtons will be wrong.
+ m_pCutsPanel->OnPerformanceDeleted( m_iSelectedPerformance );
+ }
+ else
+ {
+ GoBack();
+ }
+ return;
+ }
+
+ BaseClass::OnMessage( pParams, hFromPanel );
+}
+
+void CReplayDetailsPanel::ShowRenderDialog()
+{
+ ::ReplayUI_ShowRenderDialog( this, m_hReplay, false, m_iSelectedPerformance );
+}
+
+void CReplayDetailsPanel::FreeMovieFileLock()
+{
+ m_pPlaybackPanel->FreeMovieMaterial();
+}
+
+void CReplayDetailsPanel::SetYouTubeStatus( eYouTubeStatus status )
+{
+ m_pYouTubeUpload->SetVisible( status == kYouTubeStatus_CouldNotRetrieveInfo || status == kYouTubeStatus_NotUploaded );
+ m_pYouTubeUpload->SetEnabled( status == kYouTubeStatus_CouldNotRetrieveInfo || status == kYouTubeStatus_NotUploaded );
+ m_pYouTubeView->SetVisible( !m_pYouTubeUpload->IsVisible() );
+ m_pYouTubeView->SetEnabled( status == kYouTubeStatus_RetrievedInfo );
+ m_pYouTubeShareURL->SetEnabled( status == kYouTubeStatus_RetrievedInfo );
+}
+
+void CReplayDetailsPanel::OnMousePressed( MouseCode code )
+{
+ if ( code == MOUSE_LEFT )
+ {
+ RequestFocus();
+ }
+}
+
+void CReplayDetailsPanel::OnKeyCodeTyped( KeyCode code )
+{
+ if ( code == KEY_DELETE )
+ {
+ OnCommand( "delete_replayitem" );
+ }
+
+ BaseClass::OnKeyCodeTyped( code );
+}
+
+#endif
diff --git a/mp/src/game/client/replay/vgui/replaybrowserdetailspanel.h b/mp/src/game/client/replay/vgui/replaybrowserdetailspanel.h new file mode 100644 index 00000000..5a75ae88 --- /dev/null +++ b/mp/src/game/client/replay/vgui/replaybrowserdetailspanel.h @@ -0,0 +1,462 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef REPLAYBROWSER_DETAILSPANEL_H
+#define REPLAYBROWSER_DETAILSPANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <game/client/iviewport.h>
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/ScrollableEditablePanel.h"
+#include "replay/iqueryablereplayitem.h"
+#include "replay/ireplaymovie.h"
+#include "replay/replayhandle.h"
+#include "replay/gamedefs.h"
+#include "econ/econ_controls.h"
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+
+#define NUM_CLASSES_IN_LOADOUT_PANEL (TF_LAST_NORMAL_CLASS-1) // We don't allow unlockables for the civilian
+
+//-----------------------------------------------------------------------------
+// Purpose: Forward declarations
+//-----------------------------------------------------------------------------
+class CExLabel;
+class CExButton;
+class CTFReplay;
+class CReplayPerformance;
+class IReplayItemManager;
+
+//-----------------------------------------------------------------------------
+// Purpose: A panel containing 2 labels: one key, one value
+//-----------------------------------------------------------------------------
+class CKeyValueLabelPanel : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CKeyValueLabelPanel, EditablePanel );
+public:
+ CKeyValueLabelPanel( Panel *pParent, const char *pKey, const char *pValue );
+ CKeyValueLabelPanel( Panel *pParent, const char *pKey, const wchar_t *pValue );
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void PerformLayout();
+
+ int GetHeight() const;
+ int GetValueHeight() const;
+
+ void SetValue( const wchar_t *pValue );
+
+private:
+ CExLabel *m_pLabels[2];
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Base details panel with left/top padding and black border
+//-----------------------------------------------------------------------------
+class CBaseDetailsPanel : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CBaseDetailsPanel, EditablePanel );
+public:
+ CBaseDetailsPanel( Panel *pParent, const char *pName, ReplayHandle_t hReplay );
+
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void PerformLayout();
+
+ int GetMarginSize() const { return XRES(6); }
+
+ bool ShouldShow() const { return m_bShouldShow; }
+
+protected:
+ EditablePanel *GetInset() { return m_pInsetPanel; }
+
+ ReplayHandle_t m_hReplay;
+ bool m_bShouldShow;
+
+private:
+ EditablePanel *m_pInsetPanel; // padding on left/top
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Score panel - contains score & any records from the round
+//-----------------------------------------------------------------------------
+class CRecordsPanel : public CBaseDetailsPanel
+{
+ DECLARE_CLASS_SIMPLE( CRecordsPanel, CBaseDetailsPanel );
+public:
+ CRecordsPanel( Panel *pParent, ReplayHandle_t hReplay );
+
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void PerformLayout();
+
+private:
+ ImagePanel *m_pClassImage;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Stats panel
+//-----------------------------------------------------------------------------
+class CStatsPanel : public CBaseDetailsPanel
+{
+ DECLARE_CLASS_SIMPLE( CStatsPanel, CBaseDetailsPanel );
+public:
+ CStatsPanel( Panel *pParent, ReplayHandle_t hReplay );
+
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void PerformLayout();
+
+private:
+ CKeyValueLabelPanel *m_paStatLabels[ REPLAY_MAX_DISPLAY_GAMESTATS ];
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Dominations panel
+//-----------------------------------------------------------------------------
+class CDominationsPanel : public CBaseDetailsPanel
+{
+ DECLARE_CLASS_SIMPLE( CDominationsPanel, CBaseDetailsPanel );
+public:
+ CDominationsPanel( Panel *pParent, ReplayHandle_t hReplay );
+
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void PerformLayout();
+
+ ImagePanel *m_pNumDominationsImage;
+ CUtlVector< ImagePanel * > m_vecDominationImages;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Kills panel
+//-----------------------------------------------------------------------------
+class CKillsPanel : public CBaseDetailsPanel
+{
+ DECLARE_CLASS_SIMPLE( CKillsPanel, CBaseDetailsPanel );
+public:
+ CKillsPanel( Panel *pParent, ReplayHandle_t hReplay );
+
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void PerformLayout();
+
+ CKeyValueLabelPanel *m_pKillLabels;
+ CUtlVector< ImagePanel * > m_vecKillImages;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CBasicLifeInfoPanel : public CBaseDetailsPanel
+{
+ DECLARE_CLASS_SIMPLE( CBasicLifeInfoPanel, CBaseDetailsPanel );
+public:
+ CBasicLifeInfoPanel( Panel *pParent, ReplayHandle_t hReplay );
+
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void PerformLayout();
+
+private:
+ CKeyValueLabelPanel *m_pKilledByLabels;
+ CKeyValueLabelPanel *m_pMapLabels;
+ CKeyValueLabelPanel *m_pLifeLabels;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CMovieInfoPanel : public CBaseDetailsPanel
+{
+ DECLARE_CLASS_SIMPLE( CMovieInfoPanel, CBaseDetailsPanel );
+public:
+ CMovieInfoPanel( Panel *pParent, ReplayHandle_t hReplay, QueryableReplayItemHandle_t hMovie,
+ IReplayItemManager *pItemManager );
+
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void PerformLayout();
+
+private:
+ CKeyValueLabelPanel *m_pRenderTimeLabels;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CYouTubeInfoPanel : public CBaseDetailsPanel
+{
+ DECLARE_CLASS_SIMPLE( CYouTubeInfoPanel, CBaseDetailsPanel );
+public:
+ CYouTubeInfoPanel( Panel *pParent );
+
+ virtual void PerformLayout();
+
+ void SetInfo( const wchar_t *pInfo );
+
+private:
+ CKeyValueLabelPanel *m_pLabels;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTitleEditPanel : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CTitleEditPanel, EditablePanel );
+public:
+ CTitleEditPanel( Panel *pParent, QueryableReplayItemHandle_t hReplayItem, IReplayItemManager *pItemManager );
+ ~CTitleEditPanel();
+
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void PerformLayout();
+ virtual void PaintBackground();
+
+ virtual void OnKeyCodeTyped(vgui::KeyCode code);
+
+ virtual void OnTick();
+
+ bool m_bMouseOver;
+ TextEntry *m_pTitleEntry;
+ ImagePanel *m_pHeaderLine;
+ CExLabel *m_pClickToEditLabel;
+ CExLabel *m_pCaratLabel;
+ QueryableReplayItemHandle_t m_hReplayItem;
+ IReplayItemManager *m_pItemManager;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CReplayScreenshotSlideshowPanel;
+
+class CPlaybackPanel : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CPlaybackPanel, EditablePanel );
+public:
+ CPlaybackPanel( Panel *pParent );
+ ~CPlaybackPanel();
+
+ virtual void FreeMovieMaterial() {}
+
+protected:
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void PerformLayout();
+
+ inline int GetMarginSize() { return 9; }
+ inline int GetViewWidth() { return GetWide() - 2 * GetMarginSize(); }
+ inline int GetViewHeight() { return GetTall() - 2 * GetMarginSize(); }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CPlaybackPanelSlideshow : public CPlaybackPanel
+{
+ DECLARE_CLASS_SIMPLE( CPlaybackPanelSlideshow, CPlaybackPanel );
+public:
+ CPlaybackPanelSlideshow( Panel *pParent, ReplayHandle_t hReplay );
+
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void PerformLayout();
+
+private:
+ ReplayHandle_t m_hReplay;
+ CExLabel *m_pNoScreenshotLabel;
+ CReplayScreenshotSlideshowPanel *m_pScreenshotImage;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CMoviePlayerPanel;
+
+class CPlaybackPanelMovie : public CPlaybackPanel
+{
+ DECLARE_CLASS_SIMPLE( CPlaybackPanelMovie, CPlaybackPanel );
+public:
+ CPlaybackPanelMovie( Panel *pParent, ReplayHandle_t hReplay );
+
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void PerformLayout();
+
+ virtual void FreeMovieMaterial();
+
+private:
+ CExLabel *m_pLoadingLabel;
+ CMoviePlayerPanel *m_pMoviePlayerPanel;
+ ReplayHandle_t m_hMovie;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CCutImagePanel : public CExImageButton
+{
+ DECLARE_CLASS_SIMPLE( CCutImagePanel, CExImageButton );
+public:
+ CCutImagePanel( Panel *pParent, const char *pName );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+
+ virtual void SetSelected( bool bState );
+
+private:
+ virtual IBorder *GetBorder( bool bDepressed, bool bArmed, bool bSelected, bool bKeyFocus );
+
+ IBorder *m_pSelectedBorder;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CReplayDetailsPanel;
+
+class CCutsPanel : public CBaseDetailsPanel
+{
+ DECLARE_CLASS_SIMPLE( CCutsPanel, CBaseDetailsPanel );
+public:
+ CCutsPanel( Panel *pParent, ReplayHandle_t hReplay, int iPerformance );
+ ~CCutsPanel();
+
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void PerformLayout();
+ virtual void OnCommand( const char *pCommand );
+ virtual void ApplySettings( KeyValues *pInResourceData );
+
+ void OnPerformanceDeleted( int iPerformance );
+
+ CPanelAnimationVarAliasType( int, m_nCutButtonWidth, "cut_button_width", "0", "proportional_xpos" );
+ CPanelAnimationVarAliasType( int, m_nCutButtonHeight, "cut_button_height", "0", "proportional_ypos" );
+ CPanelAnimationVarAliasType( int, m_nCutButtonBuffer, "cut_button_buffer", "0", "proportional_xpos" );
+ CPanelAnimationVarAliasType( int, m_nCutButtonSpace, "cut_button_space", "0", "proportional_xpos" );
+ CPanelAnimationVarAliasType( int, m_nCutButtonSpaceWide, "cut_button_space_wide", "0", "proportional_xpos" );
+ CPanelAnimationVarAliasType( int, m_nTopMarginHeight, "top_margin_height", "0", "proportional_ypos" );
+ CPanelAnimationVarAliasType( int, m_nNameLabelTopMargin, "name_label_top_margin", "0", "proportional_ypos" );
+ CPanelAnimationVarAliasType( int, m_nButtonStartY, "button_start_y", "0", "proportional_ypos" );
+
+ void UpdateNameLabel( int iPerformance );
+
+private:
+ void SelectButtonFromPerformance( int iPerformance );
+ void SetPage( int iPage, int iButtonToSelect = 0 );
+ int ButtonToPerformance( int iButton ) const;
+ int PerformanceToButton( int iPerformance ) const;
+ const CReplayPerformance *GetPerformance( int iPerformance ) const;
+
+ virtual void OnTick();
+
+ struct ButtonInfo_t
+ {
+ CExImageButton *m_pButton;
+ CExButton *m_pAddToRenderQueueButton;
+ int m_iPerformance;
+ };
+
+ enum Consts_t
+ {
+ BUTTONS_PER_PAGE = 4
+ };
+
+ ButtonInfo_t m_aButtons[ BUTTONS_PER_PAGE ];
+ EditablePanel *m_pVerticalLine;
+ CExLabel *m_pNoCutsLabel;
+ CExLabel *m_pOriginalLabel;
+ CExLabel *m_pCutsLabel;
+ CExLabel *m_pNameLabel;
+ CExButton *m_pPrevButton;
+ CExButton *m_pNextButton;
+ int m_iPage;
+ int m_nVisibleButtons;
+ vgui::DHANDLE< CReplayDetailsPanel > m_hDetailsPanel;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class IReplayItemManager;
+class CConfirmDialog;
+class CYouTubeGetStatsHandler;
+
+class CReplayDetailsPanel : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CReplayDetailsPanel, EditablePanel );
+public:
+ CReplayDetailsPanel( Panel *pParent, QueryableReplayItemHandle_t hReplayItem, int iPerformance, IReplayItemManager *pItemManager );
+ ~CReplayDetailsPanel();
+
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void PerformLayout();
+
+ virtual void OnMousePressed( MouseCode code );
+ virtual void OnKeyCodeTyped( KeyCode code );
+
+ virtual void OnCommand( const char *pCommand );
+
+ virtual void OnMessage( const KeyValues* pParams, VPANEL hFromPanel );
+
+ EditablePanel *GetInset() { return m_pInsetPanel; }
+
+ void ShowRenderDialog();
+ void FreeMovieFileLock();
+ void ShowExportDialog();
+
+ static void OnPlayerWarningDlgConfirm( bool bConfirmed, void *pContext );
+
+ enum eYouTubeStatus
+ {
+ kYouTubeStatus_Private,
+ kYouTubeStatus_RetrievingInfo,
+ kYouTubeStatus_RetrievedInfo,
+ kYouTubeStatus_CouldNotRetrieveInfo,
+ kYouTubeStatus_NotUploaded
+ };
+
+ void SetYouTubeStatus( eYouTubeStatus status );
+
+ EditablePanel *m_pInsetPanel; // Parent to most child panels listed here - narrower than screen width
+ EditablePanel *m_pInfoPanel; // Container for info panels
+ ScrollableEditablePanel *m_pScrollPanel;
+
+ CPlaybackPanel *m_pPlaybackPanel; // Contains screenshot, playback button
+ CRecordsPanel *m_pRecordsPanel; // Contains score, records
+ CStatsPanel *m_pStatsPanel; // Contains stats
+ CDominationsPanel *m_pDominationsPanel; // Dominations
+ CBasicLifeInfoPanel *m_pBasicInfoPanel; // Killed by, map, life
+ CKillsPanel *m_pKillsPanel; // # kills, kill class icons
+ CYouTubeInfoPanel *m_pYouTubeInfoPanel; // YouTube Info
+ CCutsPanel *m_pCutsPanel; // Buttons for performances
+ CUtlVector< CBaseDetailsPanel* > m_vecInfoPanels; // List of panels on the right
+ CTitleEditPanel *m_pTitleEditPanel;
+ CExButton *m_pBackButton;
+ CExButton *m_pDeleteButton;
+ CExButton *m_pRenderButton;
+ CExButton *m_pPlayButton;
+ CExButton *m_pExportMovie;
+ CExButton *m_pYouTubeUpload;
+ CExButton *m_pYouTubeView;
+ CExButton *m_pYouTubeShareURL;
+ CExImageButton *m_pShowRenderInfoButton;
+ QueryableReplayItemHandle_t m_hReplayItem;
+ ReplayHandle_t m_hReplay;
+ IReplayItemManager *m_pItemManager;
+ int m_iSelectedPerformance; // Which performance to play/render/delete
+ CYouTubeGetStatsHandler *m_pYouTubeResponseHandler;
+ vgui::FileOpenDialog *m_hExportMovieDialog;
+
+private:
+ void ShowRenderInfo();
+
+ MESSAGE_FUNC_PARAMS( OnConfirmDisconnect, "ConfirmDlgResult", data );
+ MESSAGE_FUNC_CHARPTR( OnFileSelected, "FileSelected", fullpath );
+
+ CPanelAnimationVarAliasType( int, m_nMarginWidth, "margin_width", "0", "proportional_xpos" );
+
+ void GoBack();
+ void ShowPlayConfirmationDialog();
+};
+
+#endif // REPLAYBROWSER_DETAILSPANEL_H
diff --git a/mp/src/game/client/replay/vgui/replaybrowseritemmanager.cpp b/mp/src/game/client/replay/vgui/replaybrowseritemmanager.cpp new file mode 100644 index 00000000..1494c4eb --- /dev/null +++ b/mp/src/game/client/replay/vgui/replaybrowseritemmanager.cpp @@ -0,0 +1,133 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "replaybrowseritemmanager.h"
+#include "replaybrowserbasepage.h"
+#include "replay/ireplaymoviemanager.h"
+#include "replay/ireplaymanager.h"
+#include "replay/ireplaymovie.h"
+
+//-----------------------------------------------------------------------------
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+
+extern IClientReplayContext *g_pClientReplayContext;
+extern IReplayMovieManager *g_pReplayMovieManager;
+
+//-----------------------------------------------------------------------------
+
+class CReplayItemManager : public IReplayItemManager
+{
+public:
+ virtual int GetItemCount()
+ {
+ return g_pReplayManager->GetReplayCount();
+ }
+
+ virtual void GetItems( CUtlLinkedList< IQueryableReplayItem *, int > &items )
+ {
+ g_pReplayManager->GetReplaysAsQueryableItems( items );
+ }
+
+ virtual IQueryableReplayItem *GetItem( ReplayItemHandle_t hItem )
+ {
+ return static_cast< CReplay * >( g_pReplayManager->GetReplay( (ReplayHandle_t)hItem ) );
+ }
+
+ virtual bool AreItemsMovies()
+ {
+ return false;
+ }
+
+ virtual void DeleteItem( Panel *pPage, ReplayItemHandle_t hItem, bool bNotifyUI )
+ {
+ g_pReplayManager->DeleteReplay( (ReplayHandle_t)hItem, bNotifyUI );
+ }
+};
+
+//-----------------------------------------------------------------------------
+
+class CMovieItemManager : public IReplayItemManager
+{
+public:
+ virtual int GetItemCount()
+ {
+ return g_pReplayMovieManager->GetMovieCount();
+ }
+
+ virtual void GetItems( CUtlLinkedList< IQueryableReplayItem *, int > &items )
+ {
+ g_pReplayMovieManager->GetMoviesAsQueryableItems( items );
+ }
+
+ virtual IQueryableReplayItem *GetItem( ReplayItemHandle_t hItem )
+ {
+ return g_pReplayMovieManager->GetMovie( (ReplayHandle_t)hItem );
+ }
+
+ virtual bool AreItemsMovies()
+ {
+ return true;
+ }
+
+ virtual void DeleteItem( Panel *pPage, ReplayItemHandle_t hItem, bool bNotifyUI )
+ {
+ CReplayBrowserBasePage *pBasePage = static_cast< CReplayBrowserBasePage * >( pPage );
+
+ // Free the lock so the file is deletable
+ pBasePage->FreeDetailsPanelMovieLock();
+
+ // Delete the entry & the file
+ g_pReplayMovieManager->DeleteMovie( hItem );
+ }
+};
+
+//-----------------------------------------------------------------------------
+
+static CReplayItemManager s_ReplayItemManager;
+static CMovieItemManager s_MovieItemManager;
+
+//-----------------------------------------------------------------------------
+
+IReplayItemManager *GetReplayItemManager()
+{
+ return &s_ReplayItemManager;
+}
+
+IReplayItemManager *GetReplayMovieItemManager()
+{
+ return &s_MovieItemManager;
+}
+
+IQueryableReplayItem *FindReplayItem( ReplayItemHandle_t hItem, IReplayItemManager **ppItemManager )
+{
+ static IReplayItemManager *s_pItemManagers[] = { &s_ReplayItemManager, &s_MovieItemManager };
+
+ if ( ppItemManager )
+ {
+ *ppItemManager = NULL;
+ }
+
+ for ( int i = 0; i < 2; ++i )
+ {
+ IQueryableReplayItem *pItem = s_pItemManagers[ i ]->GetItem( hItem );
+ if ( pItem )
+ {
+ if ( ppItemManager )
+ {
+ *ppItemManager = s_pItemManagers[ i ];
+ }
+ return pItem;
+ }
+ }
+ return NULL;
+}
+
+#endif
\ No newline at end of file diff --git a/mp/src/game/client/replay/vgui/replaybrowseritemmanager.h b/mp/src/game/client/replay/vgui/replaybrowseritemmanager.h new file mode 100644 index 00000000..311b0cff --- /dev/null +++ b/mp/src/game/client/replay/vgui/replaybrowseritemmanager.h @@ -0,0 +1,40 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#ifndef REPLAYBROWSER_ITEMMANAGER_H
+#define REPLAYBROWSER_ITEMMANAGER_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "utllinkedlist.h"
+#include <vgui_controls/Panel.h>
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+typedef int ReplayItemHandle_t;
+
+//-----------------------------------------------------------------------------
+// Purpose: Layer of abstraction between UI and replay demos or rendered movies
+//-----------------------------------------------------------------------------
+class IQueryableReplayItem;
+
+abstract_class IReplayItemManager : public IBaseInterface
+{
+public:
+ virtual int GetItemCount() = 0;
+ virtual void GetItems( CUtlLinkedList< IQueryableReplayItem *, int > &items ) = 0;
+ virtual IQueryableReplayItem *GetItem( ReplayItemHandle_t hItem ) = 0;
+ virtual bool AreItemsMovies() = 0;
+ virtual void DeleteItem( vgui::Panel *pPage, ReplayItemHandle_t hItem, bool bNotifyUI ) = 0;
+};
+
+IReplayItemManager *GetReplayItemManager();
+IReplayItemManager *GetReplayMovieItemManager();
+
+// Find an item and put the item manager in ppItemManager
+IQueryableReplayItem *FindReplayItem( ReplayItemHandle_t hItem, IReplayItemManager **ppItemManager );
+
+#endif // REPLAYBROWSER_ITEMMANAGER_H
diff --git a/mp/src/game/client/replay/vgui/replaybrowserlistitempanel.cpp b/mp/src/game/client/replay/vgui/replaybrowserlistitempanel.cpp new file mode 100644 index 00000000..77491fc1 --- /dev/null +++ b/mp/src/game/client/replay/vgui/replaybrowserlistitempanel.cpp @@ -0,0 +1,1090 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "replaybrowserlistitempanel.h"
+#include "replaybrowsermainpanel.h"
+#include "replaybrowserlistpanel.h"
+#include "replaybrowserrenderdialog.h"
+#include "replaybrowsermovieplayerpanel.h"
+#include "vgui/IInput.h"
+#include "vgui/IVGui.h"
+#include "filesystem.h"
+#include "replay/screenshot.h"
+#include "replay/ireplaymanager.h"
+#include "confirm_dialog.h"
+#include "replay/ireplaymoviemanager.h"
+#include "econ/econ_controls.h"
+
+//-----------------------------------------------------------------------------
+
+using namespace vgui;
+
+extern IReplayMovieManager *g_pReplayMovieManager;
+
+//-----------------------------------------------------------------------------
+
+#define REPLAY_BORDER_WIDTH XRES(2)
+#define REPLAY_BORDER_HEIGHT YRES(2)
+#define REPLAY_BUFFER_HEIGHT XRES(5)
+
+//-----------------------------------------------------------------------------
+
+CReplayScreenshotSlideshowPanel::CReplayScreenshotSlideshowPanel( Panel *pParent, const char *pName, ReplayHandle_t hReplay )
+: CSlideshowPanel( pParent, pName ),
+ m_hReplay( hReplay )
+{
+ CGenericClassBasedReplay *pReplay = GetGenericClassBasedReplay( hReplay );
+
+ // Add all screenshots for the given replay
+ char szImage[ MAX_OSPATH ];
+ for ( int i = 0; i < pReplay->GetScreenshotCount(); ++i )
+ {
+ const CReplayScreenshot *pScreenshot = pReplay->GetScreenshot( i );
+ V_snprintf( szImage, sizeof( szImage ), "replay/thumbnails/%s.vtf", pScreenshot->m_szBaseFilename );
+
+ // Add image to list of slideshow images
+ AddImage( szImage );
+
+ if ( i == 0 )
+ {
+ // Set first image
+ GetImagePanel()->SetImage( szImage );
+ }
+ }
+}
+
+void CReplayScreenshotSlideshowPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+}
+
+//-----------------------------------------------------------------------------
+
+CReplayBrowserThumbnail::CReplayBrowserThumbnail( Panel *pParent, const char *pName, QueryableReplayItemHandle_t hReplayItem,
+ IReplayItemManager *pReplayItemManager )
+: CReplayBasePanel( pParent, pName ),
+ m_hReplayItem( hReplayItem ),
+ m_pReplayItemManager( pReplayItemManager ),
+ m_bMouseOver( false ),
+ m_pMoviePlayer( NULL ),
+ m_flLastMovieScrubTime( 0.0f ),
+ m_flHoverStartTime( 0.0f ),
+ m_flLastProgressChangeTime( 0.0f )
+{
+ SetScheme( "ClientScheme" );
+
+ ivgui()->AddTickSignal( GetVPanel(), 10 );
+
+ // Add the list panel as an action signal target
+ // NOTE: Vile hack.
+ Panel *pTarget = GetParent();
+ while ( pTarget )
+ {
+ if ( !V_strcmp( "BasePage", pTarget->GetName() ) )
+ break;
+ pTarget = pTarget->GetParent();
+ }
+ Assert( pTarget );
+ AddActionSignalTarget( pTarget );
+
+ m_pScreenshotThumb = new CCrossfadableImagePanel( this, "ScreenshotThumbnail" );
+ m_pTitle = new Label( this, "TitleLabel", "" );
+ m_pDownloadLabel = new Label( this, "DownloadLabel", "" );
+ m_pRecordingInProgressLabel = new Label( this, "RecordingInProgressLabel", "" );
+ m_pDownloadProgress = new ProgressBar( this, "DownloadProgress" );
+ m_pDownloadButton = new CExButton( this, "DownloadButton", "#Replay_Download" );
+ m_pDeleteButton = new CExButton( this, "DeleteButton", "#X_Delete" );
+ m_pErrorLabel = new Label( this, "ErrorLabel", "" );
+ m_pDownloadOverlay = new Panel( this, "DownloadOverlay" );
+ m_pBorderPanel = new EditablePanel( this, "BorderPanel" );
+
+ m_pScreenshotThumb->InstallMouseHandler( this );
+
+ m_pDownloadButton->AddActionSignalTarget( this );
+ m_pDownloadButton->SetCommand( new KeyValues( "download" ) );
+
+ m_pDeleteButton->AddActionSignalTarget( this );
+ m_pDeleteButton->SetCommand( new KeyValues( "delete_replayitem" ) );
+
+ SetProportional( true );
+
+ SetReplayItem( hReplayItem );
+}
+
+CReplayBrowserThumbnail::~CReplayBrowserThumbnail()
+{
+ // Clear the download event handler ptr
+ CGenericClassBasedReplay *pReplay = GetReplay();
+ if ( pReplay )
+ {
+ pReplay->m_pDownloadEventHandler = NULL;
+ }
+
+ SetupReplayItemUserData( NULL );
+
+ ivgui()->RemoveTickSignal( GetVPanel() );
+}
+
+void CReplayBrowserThumbnail::SetReplayItem( QueryableReplayItemHandle_t hReplayItem )
+{
+ if ( m_hReplayItem != REPLAY_HANDLE_INVALID && m_hReplayItem != hReplayItem )
+ {
+ IQueryableReplayItem *pReplayItem = m_pReplayItemManager->GetItem( m_hReplayItem );
+ if ( pReplayItem )
+ {
+ pReplayItem->SetUserData( NULL );
+
+ CGenericClassBasedReplay *pTFReplay = ToGenericClassBasedReplay( pReplayItem->GetItemReplay() );
+ pTFReplay->m_pDownloadEventHandler = NULL;
+ }
+ }
+
+ m_hReplayItem = hReplayItem;
+
+ if ( hReplayItem != REPLAY_HANDLE_INVALID )
+ {
+ // Set this as user data
+ SetupReplayItemUserData( (void *)this );
+ }
+
+ InvalidateLayout();
+}
+
+void CReplayBrowserThumbnail::SetupReplayItemUserData( void *pUserData )
+{
+ IQueryableReplayItem *pReplayItem = m_pReplayItemManager->GetItem( m_hReplayItem );
+ if ( pReplayItem )
+ {
+ pReplayItem->SetUserData( pUserData );
+ }
+}
+
+void CReplayBrowserThumbnail::OnTick()
+{
+ const CGenericClassBasedReplay *pReplay = GetReplay();
+ if ( !pReplay )
+ return;
+
+ // Get out if the delete confirmation dialog is up
+ if ( vgui::input()->GetAppModalSurface() )
+ return;
+
+ // Need to update layout if status has changed, since some buttons may need to be shifted around
+ // TODO: Only layout when necessary
+ InvalidateLayout( true, false );
+
+ // Get mouse position and store state
+ int x,y;
+ vgui::input()->GetCursorPos(x, y);
+ bool bOldMouseOver = m_bMouseOver;
+ m_bMouseOver = m_pBorderPanel->IsWithin(x,y);
+
+ // If we are just starting to hover over the thumbnail, mark the time
+ if ( bOldMouseOver != m_bMouseOver && m_bMouseOver )
+ {
+ m_flHoverStartTime = gpGlobals->realtime;
+ }
+
+ const char *pBorderName = m_bMouseOver ? "ReplayHighlightBorder" : "ReplayDefaultBorder";
+ IBorder *pBorder = scheme()->GetIScheme( GetScheme() )->GetBorder( pBorderName );
+ m_pBorderPanel->SetBorder( pBorder );
+
+ // Set visibility of buttons and such - a player may have saved their replay but not died yet,
+ // in which case pReplay->m_bComplete will be false.
+ const IQueryableReplayItem *pItem = m_pReplayItemManager->GetItem( m_hReplayItem );
+ bool bIncomplete = !pReplay->m_bComplete;
+ bool bDownloadPhase = !pItem->IsItemAMovie() && pReplay->m_nStatus == CReplay::REPLAYSTATUS_DOWNLOADPHASE;
+ bool bErrorState = pReplay->m_nStatus == CReplay::REPLAYSTATUS_ERROR;
+
+ m_pDownloadButton->SetVisible( false );
+ m_pDeleteButton->SetVisible( bErrorState || bDownloadPhase );
+ m_pDownloadOverlay->SetVisible( bErrorState || bDownloadPhase || bIncomplete );
+ m_pDownloadProgress->SetVisible( bDownloadPhase );
+ m_pErrorLabel->SetVisible( bErrorState );
+ m_pRecordingInProgressLabel->SetVisible( bIncomplete );
+
+ UpdateProgress( bDownloadPhase, pReplay );
+}
+
+void CReplayBrowserThumbnail::UpdateProgress( bool bDownloadPhase, const CReplay *pReplay )
+{
+ if ( !bDownloadPhase )
+ return;
+
+ // Get current download progress
+ const float flProgress = g_pClientReplayContext->GetReplayManager()->GetDownloadProgress( pReplay );
+
+ // Has progress changed?
+ const int nNewProgress = 100 * flProgress;
+ const int nOldProgress = 100 * m_pDownloadProgress->GetProgress();
+ const float flCurTime = gpGlobals->realtime;
+ if ( nNewProgress != nOldProgress )
+ {
+ m_flLastProgressChangeTime = flCurTime;
+ }
+
+ // Set download progress
+ m_pDownloadProgress->SetProgress( flProgress );
+
+ const char *pToken = "#Replay_Waiting";
+ if ( ReplayUI_GetBrowserPanel() && ( flCurTime - ReplayUI_GetBrowserPanel()->GetTimeOpened() > 2.0f ) )
+ {
+ // Use "downloading" string if progress has changed in the last two seconds
+ if ( ( flCurTime - m_flLastProgressChangeTime ) < 2.0f )
+ {
+ pToken = "#Replay_Downloading";
+ }
+ }
+
+ const wchar_t *pLocalizedText = g_pVGuiLocalize->Find( pToken );
+ if ( pLocalizedText )
+ {
+ // Add animating '...' to end of string
+ wchar_t wszText[128];
+ wcscpy( wszText, pLocalizedText );
+ int nNumPeriods = fmod( gpGlobals->realtime * 2.0f, 4.0f ); // Max of 3 dots
+ const int nLen = wcslen( wszText );
+ wcscat( wszText, L"..." );
+ wszText[ nLen + nNumPeriods ] = L'\0';
+ m_pDownloadLabel->SetText( wszText );
+ m_pDownloadLabel->SizeToContents();
+ m_pDownloadLabel->SetWide( m_pDownloadProgress->GetWide() );
+ m_pDownloadLabel->SetPos( 3, (m_pDownloadProgress->GetTall() - m_pDownloadLabel->GetTall()) / 2 );
+ }
+}
+
+void CReplayBrowserThumbnail::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/replaybrowser/listthumbnail.res", "GAME" );
+
+ // Get default from the .res file
+ m_clrDefaultBg = GetBgColor();
+ m_clrHighlight = GetSchemeColor( "TanDark", Color( 255, 255, 255, 255 ), pScheme );
+}
+
+void CReplayBrowserThumbnail::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ const CGenericClassBasedReplay *pReplay = GetReplay();
+ AssertValidReadPtr( pReplay );
+
+ if ( !pReplay )
+ return;
+
+ // Get thumbnail for first screenshot
+ char szImage[MAX_OSPATH] = { '\0' };
+ bool bHasScreenshots = false;
+ if ( pReplay->GetScreenshotCount() )
+ {
+ // Use first screenshot for thumbnail
+ bHasScreenshots = true;
+ const CReplayScreenshot *pScreenshot = pReplay->GetScreenshot( 0 );
+ V_snprintf( szImage, sizeof( szImage ), "replay/thumbnails/%s.vtf", pScreenshot->m_szBaseFilename );
+ }
+
+ // See if it exists
+ char szSearch[MAX_OSPATH];
+ V_snprintf( szSearch, sizeof( szSearch ), "materials/vgui/%s", szImage );
+ bool bShowScreenshotThumb = true;
+ if ( !bHasScreenshots || !g_pFullFileSystem || !g_pFullFileSystem->FileExists( szSearch, "GAME" ) )
+ {
+ // Hide it
+ bShowScreenshotThumb = false;
+ }
+
+ // Scale the screenshot so that it clips off the dead area of the power of 2 texture
+ float flScale = pReplay->GetScreenshotCount() == 0 ? 1.0f : ( (float)m_pScreenshotThumb->GetWide() / ( .95f * pReplay->GetScreenshot( 0 )->m_nWidth ) );
+ m_pScreenshotThumb->SetScaleAmount( flScale );
+ m_pScreenshotThumb->SetShouldScaleImage( true );
+ if ( bShowScreenshotThumb )
+ {
+ // We don't want to actually hide it (via SetVisible()), since we need to get mouse click events from the image panel
+ m_pScreenshotThumb->SetImage( szImage );
+ }
+
+ // Setup progress bar & label
+ m_pDownloadProgress->SetWide( GetWide() * .95f );
+ m_pDownloadProgress->SetSegmentInfo( 0, 1 );
+ m_pDownloadProgress->SetBarInset( 2 );
+ m_pDownloadProgress->SetMargin( 2 );
+ m_pDownloadProgress->SetPos(
+ (GetWide() - m_pDownloadProgress->GetWide()) / 2,
+ (m_pScreenshotThumb->GetTall() - m_pDownloadProgress->GetTall()) / 2
+ );
+
+ m_pDownloadLabel->SetParent( m_pDownloadProgress );
+ m_pDownloadLabel->SetWide( m_pDownloadProgress->GetWide() );
+ m_pDownloadLabel->SetPos( 3, (m_pDownloadProgress->GetTall() - m_pDownloadLabel->GetTall()) / 2 );
+
+ m_pErrorLabel->SizeToContents();
+ m_pErrorLabel->SetPos(
+ (GetWide() - m_pErrorLabel->GetWide()) / 2,
+ (m_pScreenshotThumb->GetTall() - m_pErrorLabel->GetTall()) / 2
+ );
+
+ // Center the title control horizontally
+ int nThumbX, nThumbY, nThumbW, nThumbH;
+ UpdateTitleText();
+ m_pScreenshotThumb->GetBounds( nThumbX, nThumbY, nThumbW, nThumbH );
+
+ m_pDownloadOverlay->SetBounds( 0, 0, GetWide(), GetTall() );
+
+ if ( m_pMoviePlayer )
+ {
+ m_pMoviePlayer->SetBounds( nThumbX, nThumbY, nThumbW, nThumbH );
+ }
+
+ // Setup recording-in-progress
+ m_pRecordingInProgressLabel->SetBounds( nThumbX, nThumbY, nThumbW, nThumbH );
+ m_pRecordingInProgressLabel->InvalidateLayout( true, false ); // Need this for centerwrap to work properly
+
+ bool bDownloadPhase = pReplay->m_nStatus == CReplay::REPLAYSTATUS_DOWNLOADPHASE;
+ int nButtonWidth = 9 * GetWide() / 10;
+ int nButtonX = nThumbX + ( m_pScreenshotThumb->GetWide() - nButtonWidth ) / 2;
+ int nDownloadButtonY, nDeleteButtonY;
+ if ( bDownloadPhase )
+ {
+ nDownloadButtonY = nThumbY + 12;
+ nDeleteButtonY = nThumbY + m_pScreenshotThumb->GetTall() - m_pDeleteButton->GetTall() - 12;
+ }
+ else
+ {
+ nDownloadButtonY = 0; // We don't care about this now, since it will not be visible
+ nDeleteButtonY = ( m_pScreenshotThumb->GetTall() - m_pDeleteButton->GetTall() ) / 2;
+ }
+
+ // Adjust download button position
+ m_pDownloadButton->SetPos( nButtonX, nDownloadButtonY );
+ m_pDownloadButton->SetWide( nButtonWidth );
+}
+
+void CReplayBrowserThumbnail::OnDownloadClicked( KeyValues *pParams )
+{
+ // Begin the download
+ OnCommand( "download" );
+}
+
+void CReplayBrowserThumbnail::OnDeleteReplay( KeyValues *pParams )
+{
+ OnCommand( "delete_replayitem" );
+}
+
+void CReplayBrowserThumbnail::OnCommand( const char *pCommand )
+{
+ const CGenericClassBasedReplay *pReplay = GetReplay();
+ AssertValidReadPtr( pReplay );
+ if ( !pReplay )
+ return;
+
+ if ( FStrEq( pCommand, "details" ) ) // Display replay details?
+ {
+ char szCmd[256];
+ V_snprintf( szCmd, sizeof( szCmd ), "details%i", (int)m_hReplayItem );
+ PostActionSignal( new KeyValues( "Command", "command", szCmd ) );
+ }
+ else if ( FStrEq( pCommand, "delete_replayitem" ) ) // Delete the replay?
+ {
+ ReplayUI_GetBrowserPanel()->AttemptToDeleteReplayItem( this, m_hReplayItem, m_pReplayItemManager, -1 );
+ }
+ else
+ {
+ BaseClass::OnCommand( pCommand );
+ }
+}
+
+void CReplayBrowserThumbnail::OnMousePressed( MouseCode code )
+{
+ if ( code == MOUSE_LEFT )
+ {
+ OnCommand( "details" );
+ }
+}
+
+void CReplayBrowserThumbnail::UpdateTitleText()
+{
+ IQueryableReplayItem *pReplayItem = m_pReplayItemManager->GetItem( m_hReplayItem );
+ m_pTitle->SetText( pReplayItem->GetItemTitle() );
+}
+
+CGenericClassBasedReplay *CReplayBrowserThumbnail::GetReplay()
+{
+ IQueryableReplayItem *pItem = m_pReplayItemManager->GetItem( m_hReplayItem );
+ if ( !pItem )
+ return NULL;
+
+ return ToGenericClassBasedReplay( pItem->GetItemReplay() );
+}
+
+IQueryableReplayItem *CReplayBrowserThumbnail::GetReplayItem()
+{
+ return m_pReplayItemManager->GetItem( m_hReplayItem );
+}
+
+//-----------------------------------------------------------------------------
+
+CReplayBrowserThumbnailRow::CReplayBrowserThumbnailRow( Panel *pParent, const char *pName, IReplayItemManager *pReplayItemManager )
+: BaseClass( pParent, pName ),
+ m_pReplayItemManager( pReplayItemManager )
+{
+ SetProportional( true );
+}
+
+void CReplayBrowserThumbnailRow::AddReplayThumbnail( const IQueryableReplayItem *pItem )
+{
+ CReplayBrowserThumbnail *pThumbnail = new CReplayBrowserThumbnail( this, "ListThumbnail", pItem->GetItemHandle(), m_pReplayItemManager );
+ m_vecThumbnails.AddToTail( pThumbnail );
+}
+void CReplayBrowserThumbnailRow::AddReplayThumbnail( QueryableReplayItemHandle_t hReplayItem )
+{
+ CReplayBrowserThumbnail *pThumbnail = new CReplayBrowserThumbnail( this, "ListThumbnail", hReplayItem, m_pReplayItemManager );
+ m_vecThumbnails.AddToTail( pThumbnail );
+}
+
+void CReplayBrowserThumbnailRow::DeleteReplayItemThumbnail( const IQueryableReplayItem *pReplayItem )
+{
+ CReplayBrowserThumbnail *pThumbnail = FindThumbnail( pReplayItem );
+ int nThumbnailElement = m_vecThumbnails.Find( pThumbnail );
+ if ( nThumbnailElement == m_vecThumbnails.InvalidIndex() )
+ {
+ AssertMsg( 0, "REPLAY: Should have found replay thumbnail while attempting to delete it from the browser." );
+ return;
+ }
+
+ // Delete the actual panel
+ ivgui()->RemoveTickSignal( pThumbnail->GetVPanel() );
+ pThumbnail->MarkForDeletion();
+
+ // Remove from list of thumbs
+ m_vecThumbnails.Remove( nThumbnailElement );
+}
+
+int CReplayBrowserThumbnailRow::GetNumVisibleReplayItems() const
+{
+ int iCount = 0;
+ FOR_EACH_VEC( m_vecThumbnails, i )
+ {
+ CReplayBrowserThumbnail *pThumbnail = m_vecThumbnails[ i ];
+ if ( pThumbnail->IsVisible() )
+ {
+ iCount++;
+ }
+ }
+ return iCount;
+}
+
+CReplayBrowserThumbnail *CReplayBrowserThumbnailRow::FindThumbnail( const IQueryableReplayItem *pReplayItem )
+{
+ AssertValidReadPtr( pReplayItem );
+ FOR_EACH_VEC( m_vecThumbnails, i )
+ {
+ CReplayBrowserThumbnail *pThumbnail = m_vecThumbnails[ i ];
+ if ( pThumbnail->GetReplayItem() == pReplayItem )
+ return pThumbnail;
+ }
+ return NULL;
+}
+
+void CReplayBrowserThumbnailRow::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/replaybrowser/thumbnailrow.res", "GAME" );
+}
+
+void CReplayBrowserThumbnailRow::PerformLayout()
+{
+ for ( int i = 0; i < m_vecThumbnails.Count(); ++i )
+ {
+ CReplayBrowserThumbnail *pThumbnail = m_vecThumbnails[ i ];
+ pThumbnail->SetPos( i * ( pThumbnail->GetWide() + 2 * REPLAY_BORDER_WIDTH ), 0 );
+ bool bShouldBeVisible = pThumbnail->m_hReplayItem != REPLAY_HANDLE_INVALID;
+ if ( pThumbnail->IsVisible() != bShouldBeVisible )
+ {
+ pThumbnail->SetVisible( bShouldBeVisible );
+ }
+ }
+
+ BaseClass::PerformLayout();
+}
+
+//-----------------------------------------------------------------------------
+
+CBaseThumbnailCollection::CBaseThumbnailCollection( CReplayListPanel *pParent, const char *pName, IReplayItemManager *pReplayItemManager )
+: EditablePanel( pParent, pName ),
+ m_pReplayItemManager( pReplayItemManager ),
+ m_nStartX( XRES(15) ),
+ m_pCaratLabel( NULL ),
+ m_pTitleLabel( NULL ),
+ m_pNoReplayItemsLabel( NULL ),
+ m_pRenderAllButton( NULL )
+{
+ m_pParentListPanel = static_cast< CReplayListPanel * >( pParent );
+ m_pShowNextButton = NULL;
+ m_pShowPrevButton = NULL;
+ m_iViewingPage = 0;
+
+ m_nReplayThumbnailsPerRow = 6;
+ m_nMaxRows = 2;
+}
+
+void CBaseThumbnailCollection::AddReplay( const IQueryableReplayItem *pItem )
+{
+ m_vecReplays.AddToTail( pItem->GetItemHandle() );
+}
+
+void CBaseThumbnailCollection::CleanupUIForReplayItem( ReplayItemHandle_t hReplayItem )
+{
+ IQueryableReplayItem *pReplayItem = m_pReplayItemManager->GetItem( hReplayItem ); Assert( pReplayItem );
+ if ( !pReplayItem )
+ return;
+
+ // Find the replay thumbnail
+ CReplayBrowserThumbnailRow *pThumbnailRow = FindReplayItemThumbnailRow( pReplayItem );
+ if ( !pThumbnailRow )
+ {
+ AssertMsg( 0, "REPLAY: Should have found replay thumbnail row while attempting to delete it from the browser." );
+ return;
+ }
+
+ // Remove it from the replay list and refresh the page
+ m_vecReplays.FindAndRemove( hReplayItem );
+ UpdateViewingPage();
+ InvalidateLayout();
+}
+
+int CBaseThumbnailCollection::GetRowStartY()
+{
+ int nVerticalBuffer = YRES( 15 );
+ Panel *pLowestPanel = m_pReplayItemManager->GetItemCount() == 0 ? m_pNoReplayItemsLabel : GetLowestPanel( nVerticalBuffer );
+
+ int x,y;
+ pLowestPanel->GetPos( x,y );
+
+ bool bMakeRoomForNextPrev = (m_pShowPrevButton && m_pShowNextButton && (m_pShowPrevButton->IsVisible() || m_pShowNextButton->IsVisible()));
+ if ( bMakeRoomForNextPrev )
+ {
+ nVerticalBuffer += m_pShowPrevButton->GetTall();
+ }
+ return y + pLowestPanel->GetTall() + nVerticalBuffer;
+}
+
+CReplayBrowserThumbnailRow *CBaseThumbnailCollection::FindReplayItemThumbnailRow( const IQueryableReplayItem *pReplayItem )
+{
+ AssertValidReadPtr( pReplayItem );
+
+ FOR_EACH_VEC( m_vecRows, i )
+ {
+ CReplayBrowserThumbnailRow *pRow = m_vecRows[ i ];
+
+ // If the replay thumbnail exists in the given row, return it
+ if ( pRow->FindThumbnail( pReplayItem ) )
+ return pRow;
+ }
+
+ return NULL;
+}
+
+void CBaseThumbnailCollection::RemoveEmptyRows()
+{
+ // Get a pointer to the row
+ CUtlVector< CReplayBrowserThumbnailRow * > vecRowsToDelete;
+ FOR_EACH_VEC( m_vecRows, i )
+ {
+ CReplayBrowserThumbnailRow *pRow = m_vecRows[ i ];
+ if ( pRow->GetNumVisibleReplayItems() == 0 )
+ {
+ vecRowsToDelete.AddToTail( pRow );
+ }
+ }
+
+ // Delete any rows
+ FOR_EACH_VEC( vecRowsToDelete, i )
+ {
+ // Remove it
+ int nElement = m_vecRows.Find( vecRowsToDelete[ i ] );
+ m_vecRows[ nElement ]->MarkForDeletion();
+ m_vecRows.Remove( nElement );
+ }
+
+ // If we deleted any rows...
+ if ( vecRowsToDelete.Count() )
+ {
+ // Reposition and repaint
+ ReplayUI_GetBrowserPanel()->InvalidateLayout();
+ ReplayUI_GetBrowserPanel()->Repaint();
+
+ // If we don't have any rows left...
+ if ( !m_vecRows.Count() )
+ {
+ m_pParentListPanel->RemoveCollection( this );
+ }
+ }
+}
+
+void CBaseThumbnailCollection::RemoveAll()
+{
+ m_vecReplays.RemoveAll();
+ RemoveEmptyRows();
+ UpdateViewingPage();
+ InvalidateLayout();
+}
+
+
+void CBaseThumbnailCollection::OnUpdated()
+{
+ m_iViewingPage = 0;
+ UpdateViewingPage();
+ InvalidateLayout( true, false );
+}
+
+void CBaseThumbnailCollection::OnCommand( const char *pCommand )
+{
+ if ( FStrEq( pCommand, "render_queued_replays" ) )
+ {
+ ::ReplayUI_ShowRenderDialog( this, REPLAY_HANDLE_INVALID, false, -1 );
+ return;
+ }
+ else if ( FStrEq( pCommand, "show_next" ) )
+ {
+ int iThumbnailsPerPage = (m_nReplayThumbnailsPerRow * m_nMaxRows);
+ m_iViewingPage = clamp( m_iViewingPage + 1, 0, Ceil2Int((float)m_vecReplays.Count() / (float)iThumbnailsPerPage) );
+ UpdateViewingPage();
+ return;
+ }
+ else if ( FStrEq( pCommand, "show_prev" ) )
+ {
+ int iThumbnailsPerPage = (m_nReplayThumbnailsPerRow * m_nMaxRows);
+ m_iViewingPage = clamp( m_iViewingPage - 1, 0, Ceil2Int((float)m_vecReplays.Count() / (float)iThumbnailsPerPage) );
+ UpdateViewingPage();
+ return;
+ }
+
+ BaseClass::OnCommand( pCommand );
+}
+
+void CBaseThumbnailCollection::UpdateViewingPage( void )
+{
+ int iThumbnailsPerPage = (m_nReplayThumbnailsPerRow * m_nMaxRows);
+ int iFirstReplayOnPage = (m_iViewingPage * iThumbnailsPerPage);
+
+ // If the page we're on is not the first page, and we have no replays on it, move back a page.
+ while (iFirstReplayOnPage >= m_vecReplays.Count() && m_iViewingPage > 0 )
+ {
+ m_iViewingPage--;
+ iFirstReplayOnPage = (m_iViewingPage * iThumbnailsPerPage);
+ }
+
+ for ( int i = 0; i < iThumbnailsPerPage; i++ )
+ {
+ int iReplayIndex = (iFirstReplayOnPage + i);
+
+ int iRow = floor( i / (float)m_nReplayThumbnailsPerRow );
+ int iColumn = i % m_nReplayThumbnailsPerRow;
+
+ // Hit the max number of rows we show?
+ if ( iRow >= m_nMaxRows )
+ break;
+
+ // Need a new row?
+ if ( iRow >= m_vecRows.Count() )
+ {
+ // If we don't actually have any more replays, we don't need to make the new row.
+ if ( iReplayIndex >= m_vecReplays.Count() )
+ break;
+
+ // Create a new row and add there
+ CReplayBrowserThumbnailRow *pNewRow = new CReplayBrowserThumbnailRow( this, "ThumbnailRow", m_pReplayItemManager );
+ m_vecRows.AddToTail( pNewRow );
+ InvalidateLayout();
+ }
+
+ // Need another thumbnail in this row?
+ if ( iColumn >= m_vecRows[iRow]->GetNumReplayItems() )
+ {
+ // Hit the max number of thumbnails in this row?
+ if ( iColumn >= m_nReplayThumbnailsPerRow )
+ break;
+
+ m_vecRows[iRow]->AddReplayThumbnail( REPLAY_HANDLE_INVALID );
+ }
+
+ if ( iReplayIndex >= m_vecReplays.Count() )
+ {
+ m_vecRows[iRow]->m_vecThumbnails[iColumn]->SetReplayItem( REPLAY_HANDLE_INVALID );
+ m_vecRows[iRow]->m_vecThumbnails[iColumn]->SetVisible( false );
+ }
+ else
+ {
+ m_vecRows[iRow]->m_vecThumbnails[iColumn]->SetReplayItem( m_vecReplays[iReplayIndex] );
+ m_vecRows[iRow]->m_vecThumbnails[iColumn]->SetVisible( true );
+ }
+ }
+
+ // Update the button counts
+ wchar_t wszTemp[256];
+ wchar_t wszCount[10];
+ int iNextReplays = clamp( m_vecReplays.Count() - iFirstReplayOnPage - iThumbnailsPerPage, 0, iThumbnailsPerPage );
+ if ( iNextReplays > 0 )
+ {
+ _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", iNextReplays );
+ g_pVGuiLocalize->ConstructString( wszTemp, sizeof( wszTemp ), g_pVGuiLocalize->Find("#Replay_NextX"), 1, wszCount );
+ SetDialogVariable( "nextbuttontext", wszTemp );
+ m_pShowNextButton->SetVisible( true );
+ }
+ else
+ {
+ m_pShowNextButton->SetVisible( false );
+ }
+
+ int iPrevReplays = clamp( iFirstReplayOnPage, 0, iThumbnailsPerPage );
+ if ( iPrevReplays > 0 )
+ {
+ _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", iPrevReplays );
+ g_pVGuiLocalize->ConstructString( wszTemp, sizeof( wszTemp ), g_pVGuiLocalize->Find("#Replay_PrevX"), 1, wszCount );
+ SetDialogVariable( "prevbuttontext", wszTemp );
+ m_pShowPrevButton->SetVisible( true );
+ }
+ else
+ {
+ m_pShowPrevButton->SetVisible( false );
+ }
+
+ RemoveEmptyRows();
+
+ // We may have changed our size, so we need to tell our parent that it should re-layout
+ m_pParentListPanel->InvalidateLayout();
+}
+
+void CBaseThumbnailCollection::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/replaybrowser/thumbnailcollection.res", "GAME" );
+
+ m_pCaratLabel = dynamic_cast< CExLabel * >( FindChildByName( "CaratLabel" ) );
+ m_pTitleLabel = dynamic_cast< CExLabel * >( FindChildByName( "TitleLabel" ) );
+ m_pNoReplayItemsLabel = dynamic_cast< CExLabel * >( FindChildByName( "NoReplayItemsLabel" ) );
+ m_pShowNextButton = dynamic_cast< CExButton * >( FindChildByName( "ShowNextButton" ) );
+ if ( m_pShowNextButton )
+ {
+ m_pShowNextButton->AddActionSignalTarget( this );
+ }
+ m_pShowPrevButton = dynamic_cast< CExButton * >( FindChildByName( "ShowPrevButton" ) );
+ if ( m_pShowPrevButton )
+ {
+ m_pShowPrevButton->AddActionSignalTarget( this );
+ }
+
+ UpdateViewingPage();
+}
+
+void CBaseThumbnailCollection::PerformLayout()
+{
+ int nUnconvertedBgWidth = GetWide();
+
+ // Layout no replay items label
+ m_pNoReplayItemsLabel->SetPos( ( GetWide() - m_pNoReplayItemsLabel->GetWide() ) / 2, YRES( 40 ) );
+ m_pNoReplayItemsLabel->SetVisible( !m_pReplayItemManager->GetItemCount() );
+ m_pNoReplayItemsLabel->SetContentAlignment( Label::a_center );
+
+ int nStartY = YRES(5);
+
+ // Update the title count
+ wchar_t wszCount[10];
+ _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", m_vecReplays.Count() );
+ wchar_t wszTemp[256];
+ const char *pszLocString = IsMovieCollection() ? "#Replay_SavedMovies" : "#Replay_UnrenderedReplays";
+ g_pVGuiLocalize->ConstructString( wszTemp, sizeof( wszTemp ), g_pVGuiLocalize->Find(pszLocString), 1, wszCount );
+ SetDialogVariable( "titleandcount", wszTemp );
+
+ // Setup labels for unconverted replay display
+ LayoutUpperPanels( nStartY, nUnconvertedBgWidth );
+
+ int nCurrentY = GetRowStartY();
+
+ // Position our prev button
+ int nButtonX = (GetWide() - m_pShowPrevButton->GetWide()) * 0.5;
+ if ( m_pShowPrevButton )
+ {
+ m_pShowPrevButton->SetPos( nButtonX, nCurrentY - m_pShowPrevButton->GetTall() - YRES(2) );
+ nCurrentY += YRES(2);
+ }
+
+ // Setup converted row positions
+ for ( int i = 0; i < m_vecRows.Count(); ++i )
+ {
+ CReplayBrowserThumbnailRow *pRow = m_vecRows[ i ];
+ pRow->SetPos( m_nStartX, nCurrentY );
+ pRow->InvalidateLayout( true, true );
+ int nRowTall = pRow->GetTall();
+ nCurrentY += nRowTall;
+ }
+
+ int nTotalHeight = nCurrentY;
+
+ // Position our next button
+ if ( m_pShowNextButton )
+ {
+ m_pShowNextButton->SetPos( nButtonX, nCurrentY );
+
+ bool bMakeRoomForNextPrev = (m_pShowPrevButton && m_pShowNextButton && (m_pShowPrevButton->IsVisible() || m_pShowNextButton->IsVisible()));
+ if ( bMakeRoomForNextPrev )
+ {
+ nTotalHeight += m_pShowNextButton->GetTall() + YRES(5);
+ }
+ }
+
+ LayoutBackgroundPanel( nUnconvertedBgWidth, nTotalHeight );
+
+ // TODO: Resizing can cause an InvalidateLayout() call if the new & old dimensions differ,
+ // perhaps calling this here is not the best idea.
+ // Adjust total height of panel
+ SetTall( nTotalHeight );
+
+ BaseClass::PerformLayout();
+}
+
+//-----------------------------------------------------------------------------
+
+CReplayThumbnailCollection::CReplayThumbnailCollection( CReplayListPanel *pParent, const char *pName, IReplayItemManager *pReplayItemManager )
+: BaseClass( pParent, pName, pReplayItemManager ),
+ m_pWarningLabel( NULL ),
+ m_pUnconvertedBg( NULL )
+{
+ // Create additional panels for unconverted rows
+ m_pLinePanel = new Panel( this, "Line" );
+ m_pWarningLabel = new CExLabel( this, "WarningLabel", "#Replay_ConversionWarning" );
+ m_pUnconvertedBg = new Panel( this, "UnconvertedBg" );
+ m_pRenderAllButton = new CExButton( this, "RenderAllButton", "#Replay_RenderAll" );
+}
+
+bool CReplayThumbnailCollection::IsMovieCollection() const
+{
+ return false;
+}
+
+void CReplayThumbnailCollection::PerformLayout()
+{
+ BaseClass::PerformLayout();
+}
+
+void CReplayThumbnailCollection::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+#if defined( TF_CLIENT_DLL )
+ if ( m_pUnconvertedBg )
+ {
+ vgui::IBorder *pBorder = pScheme->GetBorder( "ButtonBorder" );
+ m_pUnconvertedBg->SetBorder( pBorder );
+ SetPaintBorderEnabled( true );
+ }
+#else
+ SetPaintBorderEnabled( false );
+#endif
+
+ // Get current key binding for "save_replay", if any.
+ const char *pBoundKey = engine->Key_LookupBinding( "save_replay" );
+ if ( !pBoundKey || FStrEq( pBoundKey, "(null)" ) )
+ {
+ m_pNoReplayItemsLabel->SetText( "#Replay_NoKeyBoundNoReplays" );
+ }
+ else
+ {
+ char szKey[16];
+ Q_snprintf( szKey, sizeof(szKey), "%s", pBoundKey );
+
+ wchar_t wKey[16];
+ wchar_t wLabel[256];
+
+ g_pVGuiLocalize->ConvertANSIToUnicode( szKey, wKey, sizeof( wKey ) );
+ g_pVGuiLocalize->ConstructString( wLabel, sizeof( wLabel ), g_pVGuiLocalize->Find("#Replay_NoReplays" ), 1, wKey );
+
+ m_pNoReplayItemsLabel->SetText( wLabel );
+ }
+}
+
+void CReplayThumbnailCollection::LayoutUpperPanels( int nStartY, int nBgWidth )
+{
+ int nUnconvertedY = nStartY + 2 * REPLAY_BUFFER_HEIGHT;
+ if ( !m_pTitleLabel )
+ return;
+
+ m_pTitleLabel->SizeToContents();
+ m_pTitleLabel->SetBounds( m_nStartX, nUnconvertedY, GetWide(), m_pTitleLabel->GetTall() );
+ m_pTitleLabel->SetVisible( true );
+
+ int cx, cy;
+ int nWarningStartY = nUnconvertedY + m_pTitleLabel->GetTall() + REPLAY_BUFFER_HEIGHT;
+ m_pWarningLabel->GetContentSize( cx, cy );
+ m_pWarningLabel->SetBounds( m_nStartX, nWarningStartY, 2 * GetWide() / 3, cy ); // For when "convert all" button is shown
+ m_pWarningLabel->SetVisible( m_pReplayItemManager->GetItemCount() > 0 );
+
+ // Setup carat label
+ if ( m_pCaratLabel )
+ {
+ m_pCaratLabel->SizeToContents();
+ m_pCaratLabel->SetPos( m_nStartX - m_pCaratLabel->GetWide() - XRES(3), nUnconvertedY );
+ m_pCaratLabel->SetVisible( true );
+ }
+
+ // Setup line
+ int nLineBuffer = m_pReplayItemManager->GetItemCount() > 0 ? YRES( 15 ) : nStartY;
+ int nLineY = nWarningStartY + nLineBuffer;
+ m_pLinePanel->SetBounds( 0, nLineY, nBgWidth, 1 );
+ m_pLinePanel->SetVisible( true );
+
+ int nButtonX = (GetWide() - m_pShowPrevButton->GetWide()) * 0.5;
+ if ( m_pShowPrevButton )
+ {
+ m_pShowPrevButton->SetPos( nButtonX, nLineY );
+ }
+}
+
+void CReplayThumbnailCollection::LayoutBackgroundPanel( int nWide, int nTall )
+{
+ // Setup bounds for dark background, if there are unconverted replays in this collection
+ if ( m_pUnconvertedBg )
+ {
+ m_pUnconvertedBg->SetBounds(
+ 0,
+ 0,
+ nWide,
+ nTall
+ );
+ m_pUnconvertedBg->SetVisible( true );
+
+ // Setup convert all button
+ int nWarningLabelX, nWarningLabelY;
+ m_pWarningLabel->GetPos( nWarningLabelX, nWarningLabelY );
+ int nRenderAllX = m_pUnconvertedBg->GetWide() - m_pRenderAllButton->GetWide() - XRES( 5 );
+ m_pRenderAllButton->SetPos( nRenderAllX, nWarningLabelY - m_pRenderAllButton->GetTall()/2 );
+ m_pRenderAllButton->SetVisible( m_pReplayItemManager->GetItemCount() > 0 );
+ }
+}
+
+Panel *CReplayThumbnailCollection::GetLowestPanel( int &nVerticalBuffer )
+{
+ nVerticalBuffer = YRES( 8 );
+ return m_pLinePanel;
+}
+
+//-----------------------------------------------------------------------------
+
+CMovieThumbnailCollection::CMovieThumbnailCollection( CReplayListPanel *pParent, const char *pName, IReplayItemManager *pReplayItemManager,
+ int nDay, int nMonth, int nYear, bool bShowSavedMoviesLabel )
+: BaseClass( pParent, pName, pReplayItemManager )
+{
+ Init( nDay, nMonth, nYear, bShowSavedMoviesLabel );
+}
+
+CMovieThumbnailCollection::CMovieThumbnailCollection( CReplayListPanel *pParent, const char *pName, IReplayItemManager *pReplayItemManager, bool bShowSavedMoviesLabel )
+: BaseClass( pParent, pName, pReplayItemManager )
+{
+ Init( -1, -1, -1, bShowSavedMoviesLabel );
+}
+
+void CMovieThumbnailCollection::Init( int nDay, int nMonth, int nYear, bool bShowSavedMoviesLabel )
+{
+ m_nDay = nDay;
+ m_nMonth = nMonth;
+ m_nYear = nYear;
+
+ if ( m_nDay == OLDER_MOVIES_COLLECTION )
+ {
+ m_pDateLabel = new CExLabel( this, "DateLabel", "#Replay_OlderMovies" );
+ }
+ else
+ {
+ m_pDateLabel = m_nDay >= 0 ? new CExLabel( this, "DateLabel", CReplayTime::GetLocalizedDate( g_pVGuiLocalize, nDay, nMonth, nYear ) ) : NULL;
+ }
+ m_bShowSavedMoviesLabel = bShowSavedMoviesLabel;
+}
+
+bool CMovieThumbnailCollection::IsMovieCollection() const
+{
+ return true;
+}
+
+void CMovieThumbnailCollection::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ // Movies have multiple collections under a single header. So we use the total movies, not the amount in this collection.
+ if ( g_pReplayMovieManager )
+ {
+ wchar_t wszCount[10];
+ _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", g_pReplayMovieManager->GetMovieCount() );
+ wchar_t wszTemp[256];
+ g_pVGuiLocalize->ConstructString( wszTemp, sizeof( wszTemp ), g_pVGuiLocalize->Find("#Replay_SavedMovies"), 1, wszCount );
+ SetDialogVariable( "titleandcount", wszTemp );
+ }
+
+ if ( m_pDateLabel )
+ {
+ m_pDateLabel->SetVisible( m_pReplayItemManager->GetItemCount() );
+ }
+}
+
+void CMovieThumbnailCollection::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ if ( m_pDateLabel )
+ {
+ m_pDateLabel->SetVisible( true );
+ }
+
+ if ( m_pCaratLabel )
+ {
+ m_pCaratLabel->SetVisible( m_bShowSavedMoviesLabel );
+ }
+ if ( m_pTitleLabel )
+ {
+ m_pTitleLabel->SetVisible( m_bShowSavedMoviesLabel );
+ }
+
+ m_pNoReplayItemsLabel->SetText( "#Replay_NoMovies" );
+}
+
+void CMovieThumbnailCollection::LayoutUpperPanels( int nStartY, int nBgWidth )
+{
+ if ( m_pTitleLabel && m_pTitleLabel->IsVisible() )
+ {
+ m_pTitleLabel->SizeToContents();
+ m_pTitleLabel->SetPos( m_nStartX, nStartY );
+
+ m_pCaratLabel->SizeToContents();
+ m_pCaratLabel->SetPos( m_nStartX - m_pCaratLabel->GetWide() - XRES(3), nStartY + ( m_pCaratLabel->GetTall() - m_pCaratLabel->GetTall() ) / 2 );
+
+ nStartY += m_pTitleLabel->GetTall() + YRES( 5 );
+ }
+
+ // Date label
+ if ( m_pDateLabel && m_pDateLabel->IsVisible() )
+ {
+ m_pDateLabel->SizeToContents();
+ m_pDateLabel->SetPos( m_nStartX, nStartY );
+ }
+}
+
+Panel *CMovieThumbnailCollection::GetLowestPanel( int &nVerticalBuffer )
+{
+ nVerticalBuffer = YRES( 8 );
+ return m_pDateLabel ? m_pDateLabel : m_pTitleLabel;
+}
+
+bool CMovieThumbnailCollection::DoesDateMatch( int nDay, int nMonth, int nYear )
+{
+ return ( nDay == m_nDay ) && ( nMonth == m_nMonth ) && ( nYear == m_nYear );
+}
+
+#endif
diff --git a/mp/src/game/client/replay/vgui/replaybrowserlistitempanel.h b/mp/src/game/client/replay/vgui/replaybrowserlistitempanel.h new file mode 100644 index 00000000..ad80985f --- /dev/null +++ b/mp/src/game/client/replay/vgui/replaybrowserlistitempanel.h @@ -0,0 +1,239 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#ifndef REPLAYLISTITEMPANEL_H
+#define REPLAYLISTITEMPANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "replaybrowserbasepanel.h"
+#include "replaybrowseritemmanager.h"
+#include "replay/genericclassbased_replay.h"
+#include "game_controls/slideshowpanel.h"
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+// Purpose: Slideshow panel that adds all screenshots associated
+// with a given replay.
+//-----------------------------------------------------------------------------
+class CReplayScreenshotSlideshowPanel : public CSlideshowPanel
+{
+ DECLARE_CLASS_SIMPLE( CReplayScreenshotSlideshowPanel, CSlideshowPanel );
+public:
+ CReplayScreenshotSlideshowPanel( Panel *pParent, const char *pName, ReplayHandle_t hReplay );
+
+ virtual void PerformLayout();
+
+private:
+ ReplayHandle_t m_hReplay;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: An individual Replay thumbnail, with download button, title, etc.
+//-----------------------------------------------------------------------------
+class CExButton;
+class CExLabel;
+class IReplayItemManager;
+class CMoviePlayerPanel;
+
+class CReplayBrowserThumbnail : public CReplayBasePanel
+{
+ DECLARE_CLASS_SIMPLE( CReplayBrowserThumbnail, CReplayBasePanel );
+public:
+ CReplayBrowserThumbnail( Panel *pParent, const char *pName, QueryableReplayItemHandle_t hReplayItem, IReplayItemManager *pReplayItemManager );
+ ~CReplayBrowserThumbnail();
+
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void PerformLayout();
+ virtual void OnMousePressed( MouseCode code );
+
+ virtual void OnTick();
+
+ virtual void OnCommand( const char *pCommand );
+
+ void UpdateTitleText();
+
+ void SetReplayItem( QueryableReplayItemHandle_t hReplayItem );
+
+ CGenericClassBasedReplay *GetReplay();
+ IQueryableReplayItem *GetReplayItem();
+
+ MESSAGE_FUNC_PARAMS( OnDownloadClicked, "Download", pParams );
+ MESSAGE_FUNC_PARAMS( OnDeleteReplay, "delete_replayitem", pParams );
+
+ CCrossfadableImagePanel *m_pScreenshotThumb;
+ QueryableReplayItemHandle_t m_hReplayItem;
+
+private:
+ void SetupReplayItemUserData( void *pUserData );
+ void UpdateProgress( bool bDownloadPhase, const CReplay *pReplay );
+
+ Label *m_pTitle;
+ Label *m_pDownloadLabel;
+ Label *m_pRecordingInProgressLabel;
+ ProgressBar *m_pDownloadProgress;
+ CExButton *m_pDownloadButton;
+ CExButton *m_pDeleteButton;
+ Label *m_pErrorLabel;
+ CMoviePlayerPanel *m_pMoviePlayer;
+ Panel *m_pDownloadOverlay;
+ EditablePanel *m_pBorderPanel;
+ Color m_clrHighlight;
+ Color m_clrDefaultBg;
+ bool m_bMouseOver;
+ IReplayItemManager *m_pReplayItemManager;
+ float m_flLastMovieScrubTime;
+ float m_flHoverStartTime;
+ float m_flLastProgressChangeTime;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: A row of Replay thumbnails (CReplayBrowserThumbnail's)
+//-----------------------------------------------------------------------------
+class CReplayBrowserThumbnailRow : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CReplayBrowserThumbnailRow, EditablePanel );
+public:
+ CReplayBrowserThumbnailRow( Panel *pParent, const char *pName, IReplayItemManager *pReplayItemManager );
+
+ void AddReplayThumbnail( const IQueryableReplayItem *pReplay );
+ void AddReplayThumbnail( QueryableReplayItemHandle_t hReplayItem );
+ void DeleteReplayItemThumbnail( const IQueryableReplayItem *pReplayItem );
+ int GetNumReplayItems() const { return m_vecThumbnails.Count(); }
+ int GetNumVisibleReplayItems() const;
+
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void PerformLayout();
+
+ CReplayBrowserThumbnail *FindThumbnail( const IQueryableReplayItem *pReplay );
+
+ CUtlVector< CReplayBrowserThumbnail * > m_vecThumbnails;
+ IReplayItemManager *m_pReplayItemManager;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: A collection of CReplayBrowserThumbnailRows containing replays
+// recorded on a given day.
+//-----------------------------------------------------------------------------
+class CExLabel;
+class CExButton;
+class CReplayListPanel;
+
+class CBaseThumbnailCollection : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CBaseThumbnailCollection, EditablePanel );
+public:
+ CBaseThumbnailCollection( CReplayListPanel *pParent, const char *pName, IReplayItemManager *pReplayItemManager );
+
+ void AddReplay( const IQueryableReplayItem *pItem );
+
+ virtual bool IsMovieCollection() const = 0;
+
+ void CleanupUIForReplayItem( ReplayItemHandle_t hReplayItem );
+
+ virtual void PerformLayout();
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+
+ void RemoveEmptyRows();
+ void RemoveAll();
+
+ void OnUpdated();
+
+ void OnCommand( const char *pCommand );
+
+ CReplayBrowserThumbnailRow *FindReplayItemThumbnailRow( const IQueryableReplayItem *pReplayItem );
+
+ inline int GetNumRows() const { return m_vecRows.Count(); }
+
+ typedef CUtlVector< CReplayBrowserThumbnailRow * > RowContainer_t;
+ RowContainer_t m_vecRows;
+
+protected:
+ // Called from PerformLayout() - layout any panels that should appear at the top (vertically)-most position
+ virtual void LayoutUpperPanels( int nStartY, int nBgWidth ) = 0;
+ virtual void LayoutBackgroundPanel( int nWide, int nTall ) {}
+ virtual Panel *GetLowestPanel( int &nVerticalBuffer ) = 0;
+
+ void UpdateViewingPage( void );
+
+ int m_nStartX;
+
+protected:
+ CExLabel *m_pNoReplayItemsLabel;
+ IReplayItemManager *m_pReplayItemManager;
+
+ CExButton *m_pShowNextButton;
+ CExButton *m_pShowPrevButton;
+ CUtlVector<ReplayItemHandle_t> m_vecReplays;
+ int m_iViewingPage;
+
+ int m_nReplayThumbnailsPerRow;
+ int m_nMaxRows;
+
+ CExLabel *m_pCaratLabel;
+ CExLabel *m_pTitleLabel;
+ CExButton *m_pRenderAllButton;
+
+private:
+ int GetRowStartY();
+
+ CReplayListPanel *m_pParentListPanel; // Parent gets altered so we keep this cached ptr around
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CReplayThumbnailCollection : public CBaseThumbnailCollection
+{
+ DECLARE_CLASS_SIMPLE( CReplayThumbnailCollection, CBaseThumbnailCollection );
+public:
+ CReplayThumbnailCollection( CReplayListPanel *pParent, const char *pName, IReplayItemManager *pReplayItemManager );
+
+ virtual bool IsMovieCollection() const;
+
+ virtual void PerformLayout();
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+
+ virtual void LayoutUpperPanels( int nStartY, int nBgWidth );
+ virtual void LayoutBackgroundPanel( int nWide, int nTall );
+ virtual Panel *GetLowestPanel( int &nVerticalBuffer );
+
+ Panel *m_pLinePanel;
+ CExLabel *m_pWarningLabel;
+ Panel *m_pUnconvertedBg;
+};
+
+#define OLDER_MOVIES_COLLECTION -2
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CMovieThumbnailCollection : public CBaseThumbnailCollection
+{
+ DECLARE_CLASS_SIMPLE( CMovieThumbnailCollection, CBaseThumbnailCollection );
+public:
+ CMovieThumbnailCollection( CReplayListPanel *pParent, const char *pName, IReplayItemManager *pReplayItemManager,
+ int nDay, int nMonth, int nYear, bool bShowSavedMoviesLabel );
+ CMovieThumbnailCollection( CReplayListPanel *pParent, const char *pName, IReplayItemManager *pReplayItemManager,
+ bool bShowSavedMoviesLabel );
+
+ bool DoesDateMatch( int nDay, int nMonth, int nYear );
+ virtual bool IsMovieCollection() const;
+
+private:
+ void Init( int nDay, int nMonth, int nYear, bool bShowSavedMoviesLabel );
+ virtual void PerformLayout();
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+
+ Panel *GetLowestPanel( int &nVerticalBuffer );
+ void LayoutUpperPanels( int nStartY, int nBgWidth );
+
+ int m_nDay, m_nMonth, m_nYear;
+ CExLabel *m_pDateLabel;
+ bool m_bShowSavedMoviesLabel;
+};
+
+#endif // REPLAYLISTITEMPANEL_H
diff --git a/mp/src/game/client/replay/vgui/replaybrowserlistpanel.cpp b/mp/src/game/client/replay/vgui/replaybrowserlistpanel.cpp new file mode 100644 index 00000000..c1d34f6d --- /dev/null +++ b/mp/src/game/client/replay/vgui/replaybrowserlistpanel.cpp @@ -0,0 +1,484 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "replaybrowserlistpanel.h"
+#include "ienginevgui.h"
+#include "vgui/ISurface.h"
+#include "vgui/IInput.h"
+#include "vgui/IVGui.h"
+#include "vgui_controls/ScrollBar.h"
+#include "vgui_controls/ScrollBarSlider.h"
+#include "replaybrowserlistitempanel.h"
+#include "replaybrowserpreviewpanel.h"
+#include "replaybrowserbasepage.h"
+#include "replay/ireplaymoviemanager.h"
+#include "replay/ireplaymanager.h"
+#include "replaybrowsermainpanel.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+
+extern IClientReplayContext *g_pClientReplayContext;
+extern IReplayMovieManager *g_pReplayMovieManager;
+extern const char *GetMapDisplayName( const char *mapName );
+
+//-----------------------------------------------------------------------------
+
+DECLARE_BUILD_FACTORY( CReplayListPanel );
+
+//-----------------------------------------------------------------------------
+
+#define MAX_MOVIE_THUMBNAILS 12 // The remaining movies will be put into a single collection
+
+//-----------------------------------------------------------------------------
+
+CReplayListPanel::CReplayListPanel( Panel *pParent, const char *pName )
+: BaseClass( pParent, pName ),
+ m_pPrevHoverPanel( NULL ),
+ m_pPreviewPanel( NULL )
+{
+ ivgui()->AddTickSignal( GetVPanel(), 10 );
+
+ m_pBorderArrowImg = new ImagePanel( this, "ArrowImage" );
+
+ // Add replays and movies collections, which will contain all replays & movies.
+ m_pReplaysCollection = new CReplayThumbnailCollection( this, "ReplayThumbnailCollection", GetReplayItemManager() );
+ m_pMoviesCollection = new CMovieThumbnailCollection( this, "MovieThumbnailCollection", GetReplayMovieItemManager(), true );
+
+ m_vecCollections.AddToTail( m_pReplaysCollection );
+ m_vecCollections.AddToTail( m_pMoviesCollection );
+
+ m_wszFilter[0] = L' ';
+ m_wszFilter[1] = NULL;
+}
+
+CReplayListPanel::~CReplayListPanel()
+{
+ ivgui()->RemoveTickSignal( GetVPanel() );
+}
+
+void CReplayListPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/replaybrowser/replaylistpanel.res", "GAME" );
+
+#if !defined( TF_CLIENT_DLL )
+ SetPaintBorderEnabled( false );
+#endif
+
+ MoveScrollBarToTop();
+
+ vgui::ScrollBar *pScrollBar = dynamic_cast< vgui::ScrollBar * >( FindChildByName( "PanelListPanelVScroll" ) );
+ pScrollBar->SetScrollbarButtonsVisible( false );
+ Color clrButtonColor = GetSchemeColor( "Yellow", Color( 255, 255, 255, 255 ), pScheme );
+ Color clrBgColor = GetSchemeColor( "TanDark", Color( 255, 255, 255, 255 ), pScheme );
+ const int nWidth = XRES( 5 );
+ pScrollBar->SetSize( nWidth, GetTall() );
+ pScrollBar->GetSlider()->SetSize( nWidth, GetTall() );
+}
+
+void CReplayListPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+}
+
+void CReplayListPanel::OnMouseWheeled(int delta)
+{
+ if ( !GetScrollbar()->IsVisible() )
+ return;
+
+ BaseClass::OnMouseWheeled( delta );
+}
+
+void CReplayListPanel::SetupBorderArrow( bool bLeft )
+{
+ m_pBorderArrowImg->SetVisible( true );
+
+ m_pBorderArrowImg->SetImage( bLeft ? "replay/replay_balloon_arrow_left" : "replay/replay_balloon_arrow_right" );
+ m_pBorderArrowImg->SetZPos( 1000 );
+
+ m_pBorderArrowImg->GetImage()->GetContentSize( m_aBorderArrowDims[0], m_aBorderArrowDims[1] );
+ m_pBorderArrowImg->SetSize( m_aBorderArrowDims[0], m_aBorderArrowDims[1] );
+}
+
+void CReplayListPanel::ClearPreviewPanel()
+{
+ if ( m_pPreviewPanel )
+ {
+ m_pPreviewPanel->MarkForDeletion();
+ m_pPreviewPanel = NULL;
+ }
+}
+
+void CReplayListPanel::ApplyFilter( const wchar_t *pFilterText )
+{
+ Q_wcsncpy( m_wszFilter, pFilterText, sizeof( m_wszFilter ) );
+ V_wcslower( m_wszFilter );
+
+ m_pReplaysCollection->RemoveAll();
+ m_pMoviesCollection->RemoveAll();
+ m_pPrevHoverPanel = NULL;
+ ClearPreviewPanel();
+
+ RemoveAll();
+ AddReplaysToList();
+
+ FOR_EACH_VEC( m_vecCollections, i )
+ {
+ m_vecCollections[i]->OnUpdated();
+ }
+
+ InvalidateLayout();
+}
+
+void CReplayListPanel::OnTick()
+{
+ if ( !enginevgui->IsGameUIVisible() )
+ return;
+
+ CReplayBrowserPanel *pReplayBrowser = ReplayUI_GetBrowserPanel();
+ if ( !pReplayBrowser || !pReplayBrowser->IsVisible() )
+ return;
+
+ int x,y;
+ vgui::input()->GetCursorPos(x, y);
+
+ // If the deletion confirmation dialog is up
+ if ( vgui::input()->GetAppModalSurface() )
+ {
+ ClearPreviewPanel();
+
+ // Hide the preview arrow
+ m_pBorderArrowImg->SetVisible( false );
+
+ return;
+ }
+
+ CReplayBrowserThumbnail *pOverPanel = FindThumbnailAtCursor( x, y );
+ if ( m_pPrevHoverPanel != pOverPanel )
+ {
+ if ( m_pPrevHoverPanel )
+ {
+ OnItemPanelExited( m_pPrevHoverPanel );
+ }
+
+ m_pPrevHoverPanel = pOverPanel;
+
+ if ( m_pPrevHoverPanel )
+ {
+ OnItemPanelEntered( m_pPrevHoverPanel );
+ }
+ }
+}
+
+void CReplayListPanel::OnItemPanelEntered( vgui::Panel *pPanel )
+{
+ CReplayBrowserThumbnail *pThumbnail = dynamic_cast< CReplayBrowserThumbnail * >( pPanel );
+
+ if ( IsVisible() && pThumbnail && pThumbnail->IsVisible() )
+ {
+ ClearPreviewPanel();
+
+ // Determine which type of preview panel to display
+ IReplayItemManager *pItemManager;
+ IQueryableReplayItem *pReplayItem = FindReplayItem( pThumbnail->m_hReplayItem, &pItemManager );
+
+ AssertMsg( pReplayItem, "Why is this happening?" );
+ if ( !pReplayItem )
+ return;
+
+ if ( pReplayItem->IsItemAMovie() )
+ {
+ m_pPreviewPanel = new CReplayPreviewPanelBase( this, pReplayItem->GetItemHandle(), pItemManager );
+ }
+ else
+ {
+ m_pPreviewPanel = new CReplayPreviewPanelSlideshow( this, pReplayItem->GetItemHandle(), pItemManager );
+ }
+
+ m_pPreviewPanel->InvalidateLayout( true, true );
+
+ int x,y;
+ pThumbnail->GetPosRelativeToAncestor( this, x, y );
+
+ int nXPos, nYPos;
+ int nOffset = XRES( 1 );
+ nXPos = ( x > GetWide()/2 ) ? ( x - m_pPreviewPanel->GetWide() - nOffset ) : ( x + pThumbnail->GetWide() + nOffset );
+ nYPos = y + ( pThumbnail->GetTall() - m_pPreviewPanel->GetTall() ) / 2;
+
+ // Make sure the popup stays onscreen.
+ if ( nXPos < 0 )
+ {
+ nXPos = 0;
+ }
+ else if ( (nXPos + m_pPreviewPanel->GetWide()) > GetWide() )
+ {
+ nXPos = GetWide() - m_pPreviewPanel->GetWide();
+ }
+
+ if ( nYPos < 0 )
+ {
+ nYPos = 0;
+ }
+ else if ( (nYPos + m_pPreviewPanel->GetTall()) > GetTall() )
+ {
+ // Move it up as much as we can without it going below the bottom
+ nYPos = GetTall() - m_pPreviewPanel->GetTall();
+ }
+
+ // Setup the balloon's arrow
+ bool bLeftArrow = x < (GetWide() / 2);
+ SetupBorderArrow( bLeftArrow ); // Sets proper image and caches image dims in m_aBorderArrowDims
+ int nArrowXPos, nArrowYPos;
+ const int nPreviewBorderWidth = 2; // Should be just big enough to cover the preview's border width
+ if ( bLeftArrow )
+ {
+ // Setup the arrow along the left-hand side
+ nArrowXPos = nXPos - m_aBorderArrowDims[0] + nPreviewBorderWidth;
+ }
+ else
+ {
+ nArrowXPos = nXPos + m_pPreviewPanel->GetWide() - nPreviewBorderWidth;
+ }
+ nArrowYPos = MIN( nYPos + m_pPreviewPanel->GetTall() - m_pBorderArrowImg->GetTall() * 2, y + ( pThumbnail->m_pScreenshotThumb->GetTall() - m_aBorderArrowDims[1] ) / 2 );
+ m_pBorderArrowImg->SetPos( nArrowXPos, nArrowYPos );
+
+ m_pPreviewPanel->SetPos( nXPos, nYPos );
+ m_pPreviewPanel->SetVisible( true );
+
+ surface()->PlaySound( "replay\\replaypreviewpopup.wav" );
+ }
+}
+
+void CReplayListPanel::OnItemPanelExited( vgui::Panel *pPanel )
+{
+ CReplayBrowserThumbnail *pThumbnail = dynamic_cast < CReplayBrowserThumbnail * > ( pPanel );
+
+ if ( pThumbnail && IsVisible() && m_pPreviewPanel )
+ {
+ m_pBorderArrowImg->SetVisible( false );
+ ClearPreviewPanel();
+ }
+}
+
+CBaseThumbnailCollection *CReplayListPanel::FindOrAddReplayThumbnailCollection( const IQueryableReplayItem *pItem, IReplayItemManager *pItemManager )
+{
+ Assert( pItem );
+
+ if ( pItem->IsItemAMovie() )
+ {
+ return m_pMoviesCollection;
+ }
+
+ return m_pReplaysCollection;
+}
+
+void CReplayListPanel::AddReplaysToList()
+{
+ // Cache off list item pointers into a temp list for processing
+ CUtlLinkedList< IQueryableReplayItem *, int > lstMovies;
+ CUtlLinkedList< IQueryableReplayItem *, int > lstReplays;
+
+ // Add all replays to a replays list
+ g_pReplayManager->GetReplaysAsQueryableItems( lstReplays );
+
+ // Add all movies to a movies list
+ g_pReplayMovieManager->GetMoviesAsQueryableItems( lstMovies );
+
+ // Go through all movies, and add them to the proper collection, based on date
+ FOR_EACH_LL( lstMovies, i )
+ {
+ if ( PassesFilter( lstMovies[ i ] ) )
+ {
+ AddReplayItem( lstMovies[ i ]->GetItemHandle() );
+ }
+ }
+
+ // Add any replays to the "temporary replays" collection
+ FOR_EACH_LL( lstReplays, i )
+ {
+ if ( PassesFilter( lstReplays[ i ] ) )
+ {
+ m_pReplaysCollection->AddReplay( lstReplays[ i ] );
+ }
+ }
+
+ // Add all collection panels to the list panel
+ FOR_EACH_VEC( m_vecCollections, i )
+ {
+ AddItem( NULL, m_vecCollections[ i ] );
+ }
+}
+
+void CReplayListPanel::RemoveCollection( CBaseThumbnailCollection *pCollection )
+{
+ // Never remove our two base collections. If they have no entries, they display messages instead.
+ if ( pCollection == m_pMoviesCollection || pCollection == m_pReplaysCollection )
+ return;
+
+ // Find the item and remove it
+ int i = FirstItem();
+ while ( i != InvalidItemID() )
+ {
+ if ( GetItemPanel( i ) == pCollection )
+ {
+ int nNextI = NextItem( i );
+ RemoveItem( i );
+ i = nNextI;
+ }
+ else
+ {
+ i = NextItem( i );
+ }
+ }
+
+ // Remove our own cached ptr
+ i = m_vecCollections.Find( pCollection );
+ if ( i != m_vecCollections.InvalidIndex() )
+ {
+ m_vecCollections.Remove( i );
+ }
+}
+
+CReplayBrowserThumbnail *CReplayListPanel::FindThumbnailAtCursor( int x, int y )
+{
+ // Is the cursor hovering over any of the thumbnails?
+ FOR_EACH_VEC( m_vecCollections, i )
+ {
+ CBaseThumbnailCollection *pCollection = m_vecCollections[ i ];
+ if ( pCollection->IsWithin( x, y ) )
+ {
+ FOR_EACH_VEC( pCollection->m_vecRows, j )
+ {
+ CReplayBrowserThumbnailRow *pRow = pCollection->m_vecRows[ j ];
+ if ( pRow->IsWithin( x, y ) )
+ {
+ FOR_EACH_VEC( pRow->m_vecThumbnails, k )
+ {
+ CReplayBrowserThumbnail *pThumbnail = pRow->m_vecThumbnails[ k ];
+ if ( pThumbnail->IsWithin( x, y ) )
+ {
+ return pThumbnail;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+#if defined( WIN32 )
+ #define Q_wcstok( text, delimiters, context ) wcstok( text, delimiters ); context;
+#elif defined( OSX ) || defined( LINUX )
+ #define Q_wcstok( text, delimiters, context ) wcstok( text, delimiters, context )
+#endif
+
+bool CReplayListPanel::PassesFilter( IQueryableReplayItem *pItem )
+{
+ CGenericClassBasedReplay *pReplay = ToGenericClassBasedReplay( pItem->GetItemReplay() );
+ if ( !pReplay )
+ return false;
+
+ wchar_t wszSearchableText[1024] = L"";
+ wchar_t wszTemp[256];
+ // title
+ const wchar_t *pTitle = pItem->GetItemTitle();
+ V_wcscat_safe( wszSearchableText, pTitle );
+ V_wcscat_safe( wszSearchableText, L" " );
+ // map
+ const char *pMapName = GetMapDisplayName( pReplay->m_szMapName );
+ g_pVGuiLocalize->ConvertANSIToUnicode( pMapName, wszTemp, sizeof( wszTemp ) );
+ V_wcscat_safe( wszSearchableText, wszTemp );
+ V_wcscat_safe( wszSearchableText, L" " );
+ // player class
+ g_pVGuiLocalize->ConvertANSIToUnicode( pReplay->GetPlayerClass(), wszTemp, sizeof( wszTemp ) );
+ V_wcscat_safe( wszSearchableText, wszTemp );
+ V_wcscat_safe( wszSearchableText, L" " );
+ // killer name
+ if ( pReplay->WasKilled() )
+ {
+ g_pVGuiLocalize->ConvertANSIToUnicode( pReplay->GetKillerName(), wszTemp, sizeof( wszTemp ) );
+ V_wcscat_safe( wszSearchableText, wszTemp );
+ V_wcscat_safe( wszSearchableText, L" " );
+ }
+ // lower case
+ V_wcslower( wszSearchableText );
+
+ wchar_t wszFilter[256];
+ Q_wcsncpy( wszFilter, m_wszFilter, sizeof( wszFilter ) );
+
+ bool bPasses = true;
+ wchar_t seps[] = L" ";
+ wchar_t *last = NULL;
+ wchar_t *token = Q_wcstok( wszFilter, seps, &last );
+ while ( token && bPasses )
+ {
+ bPasses &= wcsstr( wszSearchableText, token ) != NULL;
+ token = Q_wcstok( NULL, seps, &last );
+ }
+
+ return bPasses;
+}
+
+void CReplayListPanel::AddReplayItem( ReplayItemHandle_t hItem )
+{
+ IReplayItemManager *pItemManager;
+ const IQueryableReplayItem *pItem = FindReplayItem( hItem, &pItemManager );
+ if ( !pItem )
+ return;
+
+ // Find or add the collection
+ CBaseThumbnailCollection *pCollection = FindOrAddReplayThumbnailCollection( pItem, pItemManager );
+
+ // Add the replay
+ pCollection->AddReplay( pItem );
+}
+
+void CReplayListPanel::CleanupUIForReplayItem( ReplayItemHandle_t hReplayItem )
+{
+ IReplayItemManager *pItemManager;
+ const IQueryableReplayItem *pReplayItem = FindReplayItem( hReplayItem, &pItemManager ); AssertValidReadPtr( pReplayItem );
+
+ CBaseThumbnailCollection *pCollection = NULL;
+
+ FOR_EACH_VEC( m_vecCollections, i )
+ {
+ CBaseThumbnailCollection *pCurCollection = m_vecCollections[ i ];
+ if ( pCurCollection->FindReplayItemThumbnailRow( pReplayItem ) )
+ {
+ pCollection = pCurCollection;
+ break;
+ }
+ }
+
+ // Find the collection associated with the given replay - NOTE: we pass false here for the "bAddIfNotFound" param
+ if ( !pCollection )
+ {
+ AssertMsg( 0, "REPLAY: Should have found collection while attempting to delete a replay from the browser." );
+ return;
+ }
+
+ // Clear the previous hover pointer to avoid a potential crash where we've got a stale ptr
+ m_pPrevHoverPanel = NULL;
+
+ // Clear out the preview panel if it exists and is for the given replay necessary
+ if ( m_pPreviewPanel && m_pPreviewPanel->GetReplayHandle() == pReplayItem->GetItemReplayHandle() )
+ {
+ ClearPreviewPanel();
+ m_pBorderArrowImg->SetVisible( false );
+ }
+
+ pCollection->CleanupUIForReplayItem( hReplayItem );
+}
+
+#endif
\ No newline at end of file diff --git a/mp/src/game/client/replay/vgui/replaybrowserlistpanel.h b/mp/src/game/client/replay/vgui/replaybrowserlistpanel.h new file mode 100644 index 00000000..e3134030 --- /dev/null +++ b/mp/src/game/client/replay/vgui/replaybrowserlistpanel.h @@ -0,0 +1,80 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#ifndef REPLAYBROWSER_LISTPANEL_H
+#define REPLAYBROWSER_LISTPANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <game/client/iviewport.h>
+#include "vgui_controls/PropertyPage.h"
+#include "vgui_controls/Button.h"
+#include "vgui_controls/PanelListPanel.h"
+#include "vgui_controls/EditablePanel.h"
+#include "replaybrowseritemmanager.h"
+#include "replay/genericclassbased_replay.h"
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+// Forward declarations
+//-----------------------------------------------------------------------------
+class CBaseThumbnailCollection;
+class CReplayPreviewPanelBase;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CReplayBrowserThumbnail;
+class CExLabel;
+
+class CReplayListPanel : public PanelListPanel
+{
+ DECLARE_CLASS_SIMPLE( CReplayListPanel, PanelListPanel );
+public:
+ CReplayListPanel( Panel *pParent, const char *pName );
+ ~CReplayListPanel();
+
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void PerformLayout();
+
+ void AddReplayItem( ReplayItemHandle_t hItem );
+ void CleanupUIForReplayItem( ReplayItemHandle_t hReplayItem );
+ void AddReplaysToList();
+ void RemoveCollection( CBaseThumbnailCollection *pCollection );
+
+ virtual void OnTick();
+
+ void OnItemPanelEntered( Panel *pPanel );
+ void OnItemPanelExited( Panel *pPanel );
+
+ void SetupBorderArrow( bool bLeft );
+
+ void ClearPreviewPanel();
+
+ void ApplyFilter( const wchar_t *pFilterText );
+
+protected:
+ virtual void OnMouseWheeled(int delta);
+
+private:
+ const IQueryableReplayItem *FindItem( ReplayItemHandle_t hItem, int *pItemManagerIndex );
+ CBaseThumbnailCollection *FindOrAddReplayThumbnailCollection( const IQueryableReplayItem *pItem, IReplayItemManager *pItemManager );
+ CReplayBrowserThumbnail *FindThumbnailAtCursor( int x, int y );
+ bool PassesFilter( IQueryableReplayItem *pItem );
+
+ CBaseThumbnailCollection *m_pReplaysCollection;
+ CBaseThumbnailCollection *m_pMoviesCollection;
+
+ CUtlVector< CBaseThumbnailCollection * > m_vecCollections;
+ CReplayPreviewPanelBase *m_pPreviewPanel;
+ Panel *m_pPrevHoverPanel;
+
+ ImagePanel *m_pBorderArrowImg;
+ int m_aBorderArrowDims[2];
+ wchar_t m_wszFilter[256];
+};
+
+#endif // REPLAYBROWSER_LISTPANEL_H
diff --git a/mp/src/game/client/replay/vgui/replaybrowsermainpanel.cpp b/mp/src/game/client/replay/vgui/replaybrowsermainpanel.cpp new file mode 100644 index 00000000..8d04b945 --- /dev/null +++ b/mp/src/game/client/replay/vgui/replaybrowsermainpanel.cpp @@ -0,0 +1,449 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "replaybrowsermainpanel.h"
+#include "replaybrowserbasepage.h"
+#include "confirm_delete_dialog.h"
+#include "vgui_controls/PropertySheet.h"
+#include "vgui_controls/TextImage.h"
+#include "vgui/IInput.h"
+#include "vgui/ISurface.h"
+#include "ienginevgui.h"
+#include "replay/ireplaymanager.h"
+#include "replay/ireplaymoviemanager.h"
+#include "econ/econ_controls.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+// Purpose: Replay deletion confirmation dialog
+//-----------------------------------------------------------------------------
+class CConfirmDeleteReplayDialog : public CConfirmDeleteDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmDeleteReplayDialog, CConfirmDeleteDialog );
+public:
+ CConfirmDeleteReplayDialog( Panel *pParent, IReplayItemManager *pItemManager, int iPerformance )
+ : BaseClass( pParent )
+ {
+ m_pTextId = iPerformance >= 0 ? "#Replay_DeleteEditConfirm" : pItemManager->AreItemsMovies() ? "#Replay_DeleteMovieConfirm" : "#Replay_DeleteReplayConfirm";
+ }
+
+ const wchar_t *GetText()
+ {
+ return g_pVGuiLocalize->Find( m_pTextId );
+ }
+
+ const char *m_pTextId;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CReplayBrowserPanel::CReplayBrowserPanel( Panel *parent )
+: PropertyDialog(parent, "ReplayBrowser"),
+ m_pConfirmDeleteDialog( NULL )
+{
+ // Clear out delete info
+ V_memset( &m_DeleteInfo, 0, sizeof( m_DeleteInfo ) );
+
+ // Replay browser is parented to the game UI panel
+ vgui::VPANEL gameuiPanel = enginevgui->GetPanel( PANEL_GAMEUIDLL );
+ SetParent( gameuiPanel );
+
+ SetMoveable( false );
+ SetSizeable( false );
+
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
+ SetScheme(scheme);
+ SetProportional( true );
+
+ // Setup page
+ m_pReplaysPage = new CReplayBrowserBasePage( this );
+ m_pReplaysPage->AddActionSignalTarget( this );
+
+ AddPage( m_pReplaysPage, "#Replay_MyReplays" );
+
+ m_pReplaysPage->SetVisible( true );
+
+ ListenForGameEvent( "gameui_hidden" );
+
+ // Create this now, so that it can be the default button (if created in .res file, it fights with PropertyDialog's OkButton & generates asserts)
+ CExButton *pCloseButton = new CExButton( this, "BackButton", "" );
+ GetFocusNavGroup().SetDefaultButton(pCloseButton);
+
+ m_flTimeOpened = 0.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CReplayBrowserPanel::~CReplayBrowserPanel()
+{
+ if ( m_pConfirmDeleteDialog )
+ {
+ m_pConfirmDeleteDialog->MarkForDeletion();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReplayBrowserPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/replaybrowser/mainpanel.res", "GAME" );
+
+ SetOKButtonVisible(false);
+ SetCancelButtonVisible(false);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReplayBrowserPanel::PerformLayout( void )
+{
+ if ( GetVParent() )
+ {
+ int w,h;
+ vgui::ipanel()->GetSize( GetVParent(), w, h );
+ SetBounds(0,0,w,h);
+ }
+
+ BaseClass::PerformLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReplayBrowserPanel::ShowPanel(bool bShow, ReplayHandle_t hReplayDetails/*=REPLAY_HANDLE_INVALID*/,
+ int iPerformance/*=-1*/ )
+{
+ if ( bShow )
+ {
+ GetPropertySheet()->SetActivePage( m_pReplaysPage );
+ InvalidateLayout( false, true );
+ Activate();
+
+ m_flTimeOpened = gpGlobals->realtime;
+ }
+ else
+ {
+ PostMessage( m_pReplaysPage, new KeyValues("CancelSelection") );
+ }
+
+ SetVisible( bShow );
+ m_pReplaysPage->SetVisible( bShow );
+
+ if ( hReplayDetails != REPLAY_HANDLE_INVALID )
+ {
+ char szDetails[32];
+ V_snprintf( szDetails, sizeof( szDetails ), "details%i_%i", (int)hReplayDetails, iPerformance );
+ m_pReplaysPage->OnCommand( szDetails );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReplayBrowserPanel::FireGameEvent( IGameEvent *event )
+{
+ const char * type = event->GetName();
+
+ if ( Q_strcmp(type, "gameui_hidden") == 0 )
+ {
+ ShowPanel( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReplayBrowserPanel::OnCommand( const char *command )
+{
+ if ( !Q_stricmp( command, "back" ) )
+ {
+ if ( m_pReplaysPage->IsDetailsViewOpen() )
+ {
+ m_pReplaysPage->DeleteDetailsPanelAndShowReplayList();
+ }
+ else
+ {
+ // Close the main panel
+ ShowPanel( false );
+
+ // TODO: Properly manage the browser so that we don't have to recreate it ever time its opened
+ MarkForDeletion();
+
+ // If we're connected to a game server, we also close the game UI.
+ if ( engine->IsInGame() )
+ {
+ engine->ClientCmd_Unrestricted( "gameui_hide" );
+ }
+ }
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+void CReplayBrowserPanel::OnKeyCodeTyped(vgui::KeyCode code)
+{
+ if ( code == KEY_ESCAPE )
+ {
+ ShowPanel( false );
+ }
+ else
+ {
+ BaseClass::OnKeyCodeTyped( code );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReplayBrowserPanel::OnKeyCodePressed(vgui::KeyCode code)
+{
+ if ( GetBaseButtonCode( code ) == KEY_XBUTTON_B )
+ {
+ ShowPanel( false );
+ }
+ else if ( code == KEY_ENTER )
+ {
+ // do nothing, the default is to close the panel!
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReplayBrowserPanel::ShowDeleteReplayDenialDlg()
+{
+ ShowMessageBox( "#Replay_DeleteDenialTitle", "#Replay_DeleteDenialText", "#GameUI_OK" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReplayBrowserPanel::AttemptToDeleteReplayItem( Panel *pHandler, ReplayItemHandle_t hReplayItem,
+ IReplayItemManager *pItemManager, int iPerformance )
+{
+ IQueryableReplayItem *pItem = pItemManager->GetItem( hReplayItem );
+ CGenericClassBasedReplay *pReplay = ToGenericClassBasedReplay( pItem->GetItemReplay() );
+
+ // If this is an actual replay the user is trying to delete, only allow it
+ // if the replay says it's OK. Don't execute this code for performances.
+ if ( !pItemManager->AreItemsMovies() && iPerformance < 0 && !pReplay->ShouldAllowDelete() )
+ {
+ ShowDeleteReplayDenialDlg();
+ return;
+ }
+
+ // Otherwise, show the confirm delete dlg
+ vgui::surface()->PlaySound( "replay\\replaydialog_warn.wav" );
+ ConfirmReplayItemDelete( pHandler, hReplayItem, pItemManager, iPerformance );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReplayBrowserPanel::ConfirmReplayItemDelete( Panel *pHandler, ReplayItemHandle_t hReplayItem,
+ IReplayItemManager *pItemManager, int iPerformance )
+{
+ CConfirmDeleteReplayDialog *pConfirm = vgui::SETUP_PANEL( new CConfirmDeleteReplayDialog( this, pItemManager, iPerformance ) );
+ if ( pConfirm )
+ {
+ // Cache replay and handler for later
+ m_DeleteInfo.m_hReplayItem = hReplayItem;
+ m_DeleteInfo.m_pItemManager = pItemManager;
+ m_DeleteInfo.m_hHandler = pHandler->GetVPanel();
+ m_DeleteInfo.m_iPerformance = iPerformance;
+
+ // Display the panel!
+ pConfirm->Show();
+
+ // Cache confirm dialog ptr
+ m_pConfirmDeleteDialog = pConfirm;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReplayBrowserPanel::OnConfirmDelete( KeyValues *data )
+{
+ // Clear confirm ptr
+ m_pConfirmDeleteDialog = NULL;
+
+ // User confirmed delete?
+ int nConfirmed = data->GetInt( "confirmed", 0 );
+ if ( !nConfirmed )
+ return;
+
+ // Get the replay from the dialog
+ ReplayItemHandle_t hReplayItem = m_DeleteInfo.m_hReplayItem;
+
+ // Post actions signal to the handler
+ KeyValues *pMsg = new KeyValues( "ReplayItemDeleted" );
+ pMsg->SetInt( "replayitem", (int)hReplayItem );
+ pMsg->SetInt( "perf", m_DeleteInfo.m_iPerformance );
+ PostMessage( m_DeleteInfo.m_hHandler, pMsg );
+
+ // Delete actual replay item
+ if ( m_DeleteInfo.m_iPerformance < 0 )
+ {
+ // Cleanup UI related to the replay/movie
+ CleanupUIForReplayItem( hReplayItem );
+
+ // Delete the replay/movie
+ m_DeleteInfo.m_pItemManager->DeleteItem( GetActivePage(), hReplayItem, false );
+ }
+
+ vgui::surface()->PlaySound( "replay\\deleted_take.wav" );
+
+ // Clear delete info
+ V_memset( &m_DeleteInfo, 0, sizeof( m_DeleteInfo ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReplayBrowserPanel::OnSaveReplay( ReplayHandle_t hNewReplay )
+{
+ // Verify that the handle is valid
+ Assert( g_pReplayManager->GetReplay( hNewReplay ) );
+
+ m_pReplaysPage->AddReplay( hNewReplay );
+ m_pReplaysPage->Repaint();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReplayBrowserPanel::OnDeleteReplay( ReplayHandle_t hDeletedReplay )
+{
+ // Verify that the handle is valid
+ Assert( g_pReplayManager->GetReplay( hDeletedReplay ) );
+
+ DeleteReplay( hDeletedReplay );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReplayBrowserPanel::DeleteReplay( ReplayHandle_t hReplay )
+{
+ m_pReplaysPage->DeleteReplay( hReplay );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReplayBrowserPanel::CleanupUIForReplayItem( ReplayItemHandle_t hReplayItem )
+{
+ if ( GetActivePage() == m_pReplaysPage )
+ {
+ m_pReplaysPage->CleanupUIForReplayItem( hReplayItem );
+ }
+}
+
+static vgui::DHANDLE<CReplayBrowserPanel> g_ReplayBrowserPanel;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CReplayBrowserPanel *ReplayUI_OpenReplayBrowserPanel( ReplayHandle_t hReplayDetails,
+ int iPerformance )
+{
+ if ( !g_ReplayBrowserPanel.Get() )
+ {
+ g_ReplayBrowserPanel = vgui::SETUP_PANEL( new CReplayBrowserPanel( NULL ) );
+ g_ReplayBrowserPanel->InvalidateLayout( false, true );
+ }
+
+ engine->ClientCmd_Unrestricted( "gameui_activate" );
+ g_ReplayBrowserPanel->ShowPanel( true, hReplayDetails, iPerformance );
+
+ extern IReplayMovieManager *g_pReplayMovieManager;
+ if ( g_pReplayMovieManager->GetMovieCount() > 0 )
+ {
+ // Fire a message the game DLL can intercept (for achievements, etc).
+ IGameEvent *event = gameeventmanager->CreateEvent( "browse_replays" );
+ if ( event )
+ {
+ gameeventmanager->FireEventClientSide( event );
+ }
+ }
+
+ return g_ReplayBrowserPanel;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CReplayBrowserPanel *ReplayUI_GetBrowserPanel( void )
+{
+ return g_ReplayBrowserPanel.Get();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void ReplayUI_CloseReplayBrowser()
+{
+ if ( g_ReplayBrowserPanel )
+ {
+ g_ReplayBrowserPanel->MarkForDeletion();
+ g_ReplayBrowserPanel = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void ReplayUI_ReloadBrowser( ReplayHandle_t hReplay/*=REPLAY_HANDLE_INVALID*/,
+ int iPerformance/*=-1*/ )
+{
+ delete g_ReplayBrowserPanel.Get();
+ g_ReplayBrowserPanel = NULL;
+ ReplayUI_OpenReplayBrowserPanel( hReplay, iPerformance );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CON_COMMAND_F( open_replaybrowser, "Open the replay browser.", FCVAR_CLIENTDLL )
+{
+ ReplayUI_OpenReplayBrowserPanel( REPLAY_HANDLE_INVALID, -1 );
+ g_ReplayBrowserPanel->InvalidateLayout( false, true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CON_COMMAND_F( replay_reloadbrowser, "Reloads replay data and display replay browser", FCVAR_CLIENTDLL | FCVAR_CLIENTCMD_CAN_EXECUTE )
+{
+ ReplayUI_ReloadBrowser();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CON_COMMAND_F( replay_hidebrowser, "Hides replay browser", FCVAR_CLIENTDLL )
+{
+ ReplayUI_CloseReplayBrowser();
+}
+
+#endif
\ No newline at end of file diff --git a/mp/src/game/client/replay/vgui/replaybrowsermainpanel.h b/mp/src/game/client/replay/vgui/replaybrowsermainpanel.h new file mode 100644 index 00000000..b73d5af0 --- /dev/null +++ b/mp/src/game/client/replay/vgui/replaybrowsermainpanel.h @@ -0,0 +1,89 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#if defined( REPLAY_ENABLED )
+
+#ifndef REPLAYBROWSER_MAIN_PANEL_H
+#define REPLAYBROWSER_MAIN_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_controls/PropertyDialog.h"
+#include "replay/replayhandle.h"
+#include "GameEventListener.h"
+#include "replaybrowseritemmanager.h"
+
+//-----------------------------------------------------------------------------
+
+class CReplayBrowserBasePage;
+class CConfirmDeleteDialog;
+class CExButton;
+
+//-----------------------------------------------------------------------------
+
+class CReplayBrowserPanel : public vgui::PropertyDialog,
+ public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CReplayBrowserPanel, vgui::PropertyDialog );
+public:
+ CReplayBrowserPanel( Panel *parent );
+ virtual ~CReplayBrowserPanel();
+
+ void OnSaveReplay( ReplayHandle_t hNewReplay );
+ void OnDeleteReplay( ReplayHandle_t hDeletedReplay );
+
+ void DeleteReplay( ReplayHandle_t hReplay );
+
+ virtual void CleanupUIForReplayItem( ReplayItemHandle_t hReplay ); // After a replay has been deleted - deletes all UI (thumbnail, but maybe also row and/or collection as well)
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout( void );
+ virtual void OnCommand( const char *command );
+ virtual void ShowPanel( bool bShow, ReplayHandle_t hReplayDetails = REPLAY_HANDLE_INVALID, int iPerformance = -1 );
+ virtual void OnKeyCodeTyped(vgui::KeyCode code);
+ virtual void OnKeyCodePressed(vgui::KeyCode code);
+
+ virtual void FireGameEvent( IGameEvent *event );
+
+ MESSAGE_FUNC_PARAMS( OnConfirmDelete, "ConfirmDlgResult", data );
+
+ void AttemptToDeleteReplayItem( Panel *pHandler, ReplayItemHandle_t hReplayItem, IReplayItemManager *pItemManager, int iPerformance );
+
+ CReplayBrowserBasePage *m_pReplaysPage;
+ CConfirmDeleteDialog *m_pConfirmDeleteDialog;
+
+ struct DeleteInfo_t
+ {
+ ReplayItemHandle_t m_hReplayItem;
+ IReplayItemManager *m_pItemManager;
+ vgui::VPANEL m_hHandler;
+ int m_iPerformance;
+ };
+
+ DeleteInfo_t m_DeleteInfo;
+
+ float GetTimeOpened( void ){ return m_flTimeOpened; }
+
+private:
+ void ShowDeleteReplayDenialDlg();
+ void ConfirmReplayItemDelete( Panel *pHandler, ReplayItemHandle_t hReplayItem, IReplayItemManager *pItemManager, int iPerformance );
+
+ float m_flTimeOpened;
+};
+
+//-----------------------------------------------------------------------------
+
+CReplayBrowserPanel *ReplayUI_GetBrowserPanel();
+void ReplayUI_ReloadBrowser( ReplayHandle_t hReplay = REPLAY_HANDLE_INVALID, int iPerformance = -1 );
+void ReplayUI_CloseReplayBrowser();
+
+//-----------------------------------------------------------------------------
+
+#endif // REPLAYBROWSER_MAIN_PANEL_H
+
+#endif
\ No newline at end of file diff --git a/mp/src/game/client/replay/vgui/replaybrowsermovieplayerpanel.cpp b/mp/src/game/client/replay/vgui/replaybrowsermovieplayerpanel.cpp new file mode 100644 index 00000000..23883f2f --- /dev/null +++ b/mp/src/game/client/replay/vgui/replaybrowsermovieplayerpanel.cpp @@ -0,0 +1,220 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "replaybrowsermovieplayerpanel.h"
+#include "vgui/IVGui.h"
+#include "vgui/IInput.h"
+#include "engine/IEngineSound.h"
+#include "iclientmode.h"
+
+//-----------------------------------------------------------------------------
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+
+CMoviePlayerPanel::CMoviePlayerPanel( Panel *pParent, const char *pName, const char *pMovieFilename )
+: CReplayBasePanel( pParent, pName ),
+ m_flCurFrame( 0.0f ),
+ m_flLastTime( 0.0f ),
+ m_nLastMouseXPos( 0 ),
+ m_bPlaying( false ),
+ m_bLooping( false ),
+ m_bFullscreen( false ),
+ m_bMouseOverScrub( false ),
+ m_pOldParent( NULL ),
+ m_pVideoMaterial( NULL )
+{
+ if ( g_pVideo )
+ {
+ m_pVideoMaterial = g_pVideo->CreateVideoMaterial( pMovieFilename, pMovieFilename, "GAME" );
+ if ( m_pVideoMaterial )
+ {
+ m_pMaterial = m_pVideoMaterial->GetMaterial();
+ m_pMaterial->AddRef();
+ m_nNumFrames = m_pVideoMaterial->GetFrameCount();
+
+ }
+ }
+
+ ivgui()->AddTickSignal( GetVPanel(), 0 );
+}
+
+CMoviePlayerPanel::~CMoviePlayerPanel()
+{
+ FreeMaterial();
+
+ ivgui()->RemoveTickSignal( GetVPanel() );
+}
+
+void CMoviePlayerPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ GetPosRelativeToAncestor( NULL, m_nGlobalPos[0], m_nGlobalPos[1] );
+
+ if ( m_bFullscreen )
+ {
+ // Cache parent
+ m_pOldParent = GetParent();
+ GetBounds( m_aOldBounds[0], m_aOldBounds[1], m_aOldBounds[2], m_aOldBounds[3] );
+
+ // Adjust parent for fullscreen mode
+ SetParent( g_pClientMode->GetViewport() );
+
+ // Adjust bounds for fullscreen
+ SetBounds( 0, 0, ScreenWidth(), ScreenHeight() );
+ }
+ else if ( m_pOldParent )
+ {
+ // Restore old parent/bounds
+ SetParent( m_pOldParent );
+ SetBounds( m_aOldBounds[0], m_aOldBounds[1], m_aOldBounds[2], m_aOldBounds[3] );
+ }
+}
+
+void CMoviePlayerPanel::OnMousePressed( MouseCode code )
+{
+// ToggleFullscreen();
+}
+
+void CMoviePlayerPanel::SetScrubOnMouseOverMode( bool bOn )
+{
+ if ( bOn )
+ {
+ m_bPlaying = false;
+ }
+
+ m_bMouseOverScrub = bOn;
+}
+
+void CMoviePlayerPanel::Play()
+{
+ m_bPlaying = true;
+ m_flLastTime = gpGlobals->realtime;
+
+ enginesound->NotifyBeginMoviePlayback();
+}
+
+void CMoviePlayerPanel::FreeMaterial()
+{
+ if ( m_pVideoMaterial )
+ {
+ if ( g_pVideo )
+ {
+ g_pVideo->DestroyVideoMaterial( m_pVideoMaterial );
+ }
+
+ m_pVideoMaterial = NULL;
+ }
+
+ if ( m_pMaterial )
+ {
+ m_pMaterial->Release();
+ m_pMaterial = NULL;
+ }
+}
+
+void CMoviePlayerPanel::OnTick()
+{
+ if ( !IsEnabled() )
+ return;
+
+ if ( m_bMouseOverScrub )
+ {
+ int nMouseX, nMouseY;
+ input()->GetCursorPos( nMouseX, nMouseY );
+ if ( IsWithin( nMouseX, nMouseY ) &&
+ nMouseX != m_nLastMouseXPos )
+ {
+ float flPercent = (float)( nMouseX - m_nGlobalPos[0] ) / GetWide();
+ m_flCurFrame = flPercent * ( m_nNumFrames - 1 );
+ m_nLastMouseXPos = nMouseX;
+ }
+ }
+ else if ( m_bPlaying )
+ {
+ float flElapsed = gpGlobals->realtime - m_flLastTime;
+ m_flLastTime = gpGlobals->realtime;
+
+ m_flCurFrame += flElapsed * m_pVideoMaterial->GetVideoFrameRate().GetFPS();
+ // Loop if necessary
+ if ( m_flCurFrame >= m_nNumFrames )
+ {
+ if ( m_bLooping )
+ {
+ m_flCurFrame = m_flCurFrame - m_nNumFrames;
+ }
+ else
+ {
+ // Don't go past last frame
+ m_flCurFrame = m_nNumFrames - 1;
+ }
+ }
+ }
+
+ m_pVideoMaterial->SetFrame( m_flCurFrame );
+
+// Msg( "frame: %f / %i\n", m_flCurFrame, m_nNumFrames );
+}
+
+void CMoviePlayerPanel::Paint()
+{
+ if ( m_pVideoMaterial == NULL )
+ return;
+
+ // Get panel position/dimensions
+ int x,y;
+ int w,h;
+ GetPosRelativeToAncestor( NULL, x, y );
+ GetSize( w,h );
+
+ CMatRenderContextPtr pRenderContext( materials );
+ pRenderContext->Bind( m_pMaterial );
+
+ IMesh* pMesh = pRenderContext->GetDynamicMesh( true );
+
+ float flMinU = 0.0f, flMinV = 0.0f;
+ float flMaxU, flMaxV;
+ m_pVideoMaterial->GetVideoTexCoordRange( &flMaxU, &flMaxV );
+
+ CMeshBuilder meshBuilder;
+ meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 );
+
+ meshBuilder.Position3f( x, y, 0.0f );
+ meshBuilder.TexCoord2f( 0, flMinU, flMinV );
+ meshBuilder.Color4ub( 255, 255, 255, 255 );
+ meshBuilder.AdvanceVertex();
+
+ meshBuilder.Position3f( x + w, y, 0.0f );
+ meshBuilder.TexCoord2f( 0, flMaxU, flMinV );
+ meshBuilder.Color4ub( 255, 255, 255, 255 );
+ meshBuilder.AdvanceVertex();
+
+ meshBuilder.Position3f( x + w, y + h, 0.0f );
+ meshBuilder.TexCoord2f( 0, flMaxU, flMaxV );
+ meshBuilder.Color4ub( 255, 255, 255, 255 );
+ meshBuilder.AdvanceVertex();
+
+ meshBuilder.Position3f( x, y + h, 0.0f );
+ meshBuilder.TexCoord2f( 0, flMinU, flMaxV );
+ meshBuilder.Color4ub( 255, 255, 255, 255 );
+ meshBuilder.AdvanceVertex();
+
+ meshBuilder.End();
+ pMesh->Draw();
+}
+
+void CMoviePlayerPanel::ToggleFullscreen()
+{
+ m_bFullscreen = !m_bFullscreen;
+
+ InvalidateLayout( false, false );
+}
+
+#endif
\ No newline at end of file diff --git a/mp/src/game/client/replay/vgui/replaybrowsermovieplayerpanel.h b/mp/src/game/client/replay/vgui/replaybrowsermovieplayerpanel.h new file mode 100644 index 00000000..39ad98a7 --- /dev/null +++ b/mp/src/game/client/replay/vgui/replaybrowsermovieplayerpanel.h @@ -0,0 +1,57 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#ifndef REPLAYBROWSERMOVIEPLAYERPANEL_H
+#define REPLAYBROWSERMOVIEPLAYERPANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "replaybrowserbasepanel.h"
+#include "video/ivideoservices.h"
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+// Purpose: A panel that plays AVI's
+//-----------------------------------------------------------------------------
+class CMoviePlayerPanel : public CReplayBasePanel
+{
+ DECLARE_CLASS_SIMPLE( CMoviePlayerPanel, CReplayBasePanel );
+public:
+ CMoviePlayerPanel( Panel *pParent, const char *pName, const char *pMovieFilename );
+ ~CMoviePlayerPanel();
+
+ virtual void Paint();
+
+ void Play();
+ void SetLooping( bool bLooping ) { m_bLooping = bLooping; }
+
+ bool IsPlaying() { return m_bPlaying; }
+ void SetScrubOnMouseOverMode( bool bOn );
+ void FreeMaterial();
+ void ToggleFullscreen();
+
+private:
+ virtual void PerformLayout();
+ virtual void OnMousePressed( MouseCode code );
+ virtual void OnTick();
+
+ IVideoMaterial *m_pVideoMaterial;
+
+ IMaterial *m_pMaterial;
+ float m_flCurFrame;
+ int m_nNumFrames;
+ bool m_bPlaying;
+ bool m_bLooping;
+ float m_flLastTime;
+ int m_nGlobalPos[2];
+ int m_nLastMouseXPos;
+ bool m_bFullscreen;
+ Panel *m_pOldParent;
+ int m_aOldBounds[4];
+ bool m_bMouseOverScrub; // In this mode, we don't playback, only scrub on mouse over
+};
+
+#endif // REPLAYBROWSERMOVIEPLAYERPANEL_H
diff --git a/mp/src/game/client/replay/vgui/replaybrowserpreviewpanel.cpp b/mp/src/game/client/replay/vgui/replaybrowserpreviewpanel.cpp new file mode 100644 index 00000000..4fa8cdac --- /dev/null +++ b/mp/src/game/client/replay/vgui/replaybrowserpreviewpanel.cpp @@ -0,0 +1,301 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+
+
+#if defined( REPLAY_ENABLED )
+
+#include "replaybrowserpreviewpanel.h"
+#include "replaybrowsermainpanel.h"
+#include "replaybrowsermovieplayerpanel.h"
+#include "replaybrowserlistitempanel.h"
+#include "replay/ireplaymovie.h"
+#include "replay/screenshot.h"
+#include "vgui/ISurface.h"
+#include "econ/econ_controls.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+
+CReplayPreviewPanelBase::CReplayPreviewPanelBase( Panel *pParent, QueryableReplayItemHandle_t hItem, IReplayItemManager *pItemManager )
+: EditablePanel( pParent, "PreviewPanel" ),
+ m_hItem( hItem ),
+ m_pItemManager( pItemManager )
+{
+ CGenericClassBasedReplay *pReplay = GetReplay();
+ IQueryableReplayItem *pItem = pItemManager->GetItem( hItem );
+
+ // Setup class image
+ char szImage[MAX_OSPATH];
+ m_pClassImage = new ImagePanel( this, "ClassImage" );
+ V_snprintf( szImage, sizeof( szImage ), "class_sel_sm_%s_%s", pReplay->GetMaterialFriendlyPlayerClass(), pReplay->GetPlayerTeam() ); // Cause default image to display
+ m_pClassImage->SetImage( szImage );
+
+ m_pInfoPanel = new vgui::EditablePanel( this, "InfoPanel" );
+
+ // Setup map label
+ const char *pMapName = pReplay->m_szMapName;
+ const char *pUnderscore = V_strstr( pMapName, "_" );
+ if ( pUnderscore )
+ {
+ pMapName = pUnderscore + 1;
+ }
+ m_pMapLabel = new CExLabel( m_pInfoPanel, "MapLabel", pMapName );
+
+ // Setup record date/time
+ const CReplayTime &RecordTime = pItem->GetItemDate();
+ int nDay, nMonth, nYear;
+ RecordTime.GetDate( nDay, nMonth, nYear );
+ int nHour, nMin, nSec;
+ RecordTime.GetTime( nHour, nMin, nSec );
+ const wchar_t *pDateAndTime = CReplayTime::GetLocalizedDate( g_pVGuiLocalize, nDay, nMonth, nYear, &nHour, &nMin, &nSec );
+
+ // Setup date / time label
+ m_pDateTimeLabel = new CExLabel( m_pInfoPanel, "DateTimeLabel", pDateAndTime );
+
+ // Setup info labels
+ for ( int i = 0; i < NUM_INFO_LABELS; ++i )
+ {
+ for ( int j = 0; j < 2; ++j )
+ {
+ m_pReplayInfoLabels[i][j] = new CExLabel( m_pInfoPanel, VarArgs("Label%d_%d", i, j), "" );
+ }
+ }
+ m_pReplayInfoLabels[ LABEL_PLAYED_AS ][1]->SetText( pReplay->GetPlayerClass() );
+ m_pReplayInfoLabels[ LABEL_KILLED_BY ][1]->SetText( pReplay->WasKilled() ? pReplay->GetKillerName() : "#Replay_NoKiller" );
+ m_pReplayInfoLabels[ LABEL_LIFE_LENGTH ][1]->SetText( CReplayTime::FormatTimeString( (int)pItem->GetItemLength() ) );
+}
+
+CReplayPreviewPanelBase::~CReplayPreviewPanelBase()
+{
+}
+
+void CReplayPreviewPanelBase::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/replaybrowser/previewpanel.res", "GAME" );
+
+#if !defined( TF_CLIENT_DLL )
+ m_pClassImage->SetVisible( false );
+#endif
+}
+
+void CReplayPreviewPanelBase::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ CGenericClassBasedReplay *pReplay = GetReplay();
+ if ( !pReplay )
+ return;
+
+ int nWide = XRES(18);
+ int nTall = YRES(18);
+ int nScreenshotH = 0; // Represents the height of the screenshot OR the "no screenshot" label
+ LayoutView( nWide, nTall, nScreenshotH );
+ int iInfoHeight = m_pInfoPanel->GetTall();
+ nTall += iInfoHeight;
+
+ if ( m_pClassImage )
+ {
+ int w, h;
+ m_pClassImage->GetImage()->GetContentSize( w, h );
+ float s = ShoudlUseLargeClassImage() ? 1.25f : 1.0f;
+ int nClassTall = s*h;
+ m_pClassImage->SetSize( s*w, nClassTall );
+ m_pClassImage->SetShouldScaleImage( true );
+ m_pClassImage->SetScaleAmount( s );
+
+ // The panel should be at least as tall as the height of the screenshot
+ // (or "no screenshot" label) and the class image.
+ if ( nTall < nClassTall )
+ {
+ nTall = nClassTall;
+ }
+ m_pClassImage->SetPos( XRES(9), nTall - nClassTall );
+ }
+
+ const int nLabelX = m_pClassImage->GetWide() + XRES( 18 );
+ int iInfoWidth = nWide - nLabelX;
+ m_pMapLabel->SetSize( iInfoWidth, m_pMapLabel->GetTall() );
+ m_pDateTimeLabel->SetSize( iInfoWidth, m_pMapLabel->GetTall() );
+ m_pInfoPanel->SetBounds( nLabelX, nTall - iInfoHeight, iInfoWidth, iInfoHeight );
+
+ nTall += YRES(9);
+
+ SetSize( nWide, nTall );
+}
+
+void CReplayPreviewPanelBase::LayoutView( int &nWide, int &nTall, int &nCurY )
+{
+ nWide = XRES( 188 );
+ nTall = YRES(9);
+ nCurY = nTall;
+}
+
+CGenericClassBasedReplay *CReplayPreviewPanelBase::GetReplay()
+{
+ return ToGenericClassBasedReplay( m_pItemManager->GetItem( m_hItem )->GetItemReplay() );
+}
+
+ReplayHandle_t CReplayPreviewPanelBase::GetReplayHandle()
+{
+ return GetReplay()->GetHandle();
+}
+
+//-----------------------------------------------------------------------------
+
+CReplayPreviewPanelSlideshow::CReplayPreviewPanelSlideshow( Panel *pParent, QueryableReplayItemHandle_t hReplay, IReplayItemManager *pItemManager )
+: BaseClass( pParent, hReplay, pItemManager ),
+ m_pScreenshotPanel( NULL )
+{
+ // Setup screenshot slideshow panel
+ CGenericClassBasedReplay *pReplay = GetReplay();
+ const int nScreenshotCount = pReplay->GetScreenshotCount();
+ if ( nScreenshotCount )
+ {
+ m_pScreenshotPanel = new CReplayScreenshotSlideshowPanel( this, "ScreenshotSlideshowPanel", hReplay );
+
+ // Set pretty quick transition times based on the screenshot count
+ m_pScreenshotPanel->SetInterval( ( nScreenshotCount == 2 ) ? 3.0f : 2.0f );
+ m_pScreenshotPanel->SetTransitionTime( 0.5f );
+ }
+
+ // Setup the no screenshot label
+ m_pNoScreenshotLabel = new CExLabel( this, "NoScreenshotLabel", "#Replay_NoScreenshot" );
+ m_pNoScreenshotLabel->SetVisible( false );
+}
+
+void CReplayPreviewPanelSlideshow::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ m_pNoScreenshotLabel->SizeToContents();
+ m_pNoScreenshotLabel->SetWide( GetWide() );
+}
+
+void CReplayPreviewPanelSlideshow::LayoutView( int &nWide, int &nTall, int &nCurY )
+{
+ if ( m_pScreenshotPanel )
+ {
+ // Use the dimensions from the first screenshot to figure out the scale, even though the dimensions
+ // may vary if the user changed resolutions during gameplay
+ CGenericClassBasedReplay *pReplay = GetReplay();
+ const CReplayScreenshot *pScreenshot = pReplay->GetScreenshot( 0 );
+ int nScreenshotW = pScreenshot->m_nWidth;
+ int nScreenshotH = pScreenshot->m_nHeight;
+
+ // Scale the screenshot if it's too big for the current resolution
+ float flScreenshotScale = 1.0f;
+ int nMaxScreenshotWidth = ScreenWidth() / 3;
+ if ( nScreenshotW > nMaxScreenshotWidth )
+ {
+ flScreenshotScale = (float)nMaxScreenshotWidth / pScreenshot->m_nWidth;
+ nScreenshotW = nMaxScreenshotWidth;
+ }
+ nCurY = nScreenshotH * flScreenshotScale;
+
+ m_pScreenshotPanel->GetImagePanel()->SetShouldScaleImage( true );
+ m_pScreenshotPanel->GetImagePanel()->SetScaleAmount( flScreenshotScale );
+
+ nWide += nScreenshotW;
+ nTall += nCurY;
+
+ m_pScreenshotPanel->SetBounds( (nWide - nScreenshotW) * 0.5, YRES(9), nScreenshotW, nCurY );
+ }
+ else
+ {
+ int w, h;
+ m_pNoScreenshotLabel->SetContentAlignment( Label::a_center );
+ m_pNoScreenshotLabel->GetContentSize( w, h );
+ nTall += YRES( 20 );
+ m_pNoScreenshotLabel->SetBounds( 0, nTall, w, h );
+ nTall += YRES( 20 );
+ m_pNoScreenshotLabel->SetVisible( true );
+
+ nWide += XRES( 213 ); // Default width (maps to 640 on 1920x1200)
+ nTall += h;
+ nCurY = nTall;
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+CReplayPreviewPanelMovie::CReplayPreviewPanelMovie( Panel *pParent, QueryableReplayItemHandle_t hItem, IReplayItemManager *pItemManager )
+: BaseClass( pParent, hItem, pItemManager ),
+ m_pMoviePlayerPanel( NULL )
+{
+ m_flCreateTime = gpGlobals->realtime;
+
+ ivgui()->AddTickSignal( GetVPanel(), 10 );
+}
+
+CReplayPreviewPanelMovie::~CReplayPreviewPanelMovie()
+{
+ ivgui()->RemoveTickSignal( GetVPanel() );
+}
+
+void CReplayPreviewPanelMovie::OnTick()
+{
+ if ( gpGlobals->realtime >= m_flCreateTime + 0.5f )
+ {
+ if ( !m_pMoviePlayerPanel )
+ {
+ m_pMoviePlayerPanel = new CMoviePlayerPanel( this, "MoviePlayer", GetReplayMovie()->GetMovieFilename() );
+ InvalidateLayout( true, false );
+ }
+
+ if ( !m_pMoviePlayerPanel->IsPlaying() )
+ {
+ m_pMoviePlayerPanel->SetLooping( true );
+ m_pMoviePlayerPanel->Play();
+ }
+ }
+}
+
+IReplayMovie* CReplayPreviewPanelMovie::GetReplayMovie()
+{
+ return static_cast< IReplayMovie * >( m_pItemManager->GetItem( m_hItem ) );
+}
+
+void CReplayPreviewPanelMovie::LayoutView( int &nWide, int &nTall, int &nCurY )
+{
+ // Get frame dimensions
+ int nFrameWidth, nFrameHeight;
+ IReplayMovie* pReplayMovie = GetReplayMovie();
+ pReplayMovie->GetFrameDimensions( nFrameWidth, nFrameHeight );
+
+ int nScaledWidth = nFrameWidth;
+ int nScaledHeight = nFrameHeight;
+
+ // Scale the screenshot if it's too big for the current resolution
+ float flScale = 1.0f;
+ int nMaxWidth = ScreenWidth() / 3;
+ if ( nFrameWidth > nMaxWidth )
+ {
+ flScale = (float)nMaxWidth / nFrameWidth;
+ nScaledWidth = nMaxWidth;
+ nScaledHeight = nFrameHeight * flScale;
+ }
+
+ nWide += nScaledWidth;
+ nTall += nScaledHeight;
+ nCurY = nTall;
+
+ // Layout movie player panel if it's ready
+ if ( m_pMoviePlayerPanel )
+ {
+ m_pMoviePlayerPanel->SetBounds( 9, 9, nScaledWidth, nScaledHeight );
+ m_pMoviePlayerPanel->SetEnabled( true );
+ m_pMoviePlayerPanel->SetVisible( true );
+ m_pMoviePlayerPanel->SetZPos( 101 );
+ }
+}
+
+#endif
\ No newline at end of file diff --git a/mp/src/game/client/replay/vgui/replaybrowserpreviewpanel.h b/mp/src/game/client/replay/vgui/replaybrowserpreviewpanel.h new file mode 100644 index 00000000..53b173bc --- /dev/null +++ b/mp/src/game/client/replay/vgui/replaybrowserpreviewpanel.h @@ -0,0 +1,120 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef REPLAYBROWSER_PREVIEWPANEL_H
+#define REPLAYBROWSER_PREVIEWPANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <game/client/iviewport.h>
+#include "vgui_controls/PropertyPage.h"
+#include "vgui_controls/Button.h"
+#include "vgui_controls/PanelListPanel.h"
+#include "vgui_controls/EditablePanel.h"
+#include "replaybrowseritemmanager.h"
+#include "replay/genericclassbased_replay.h"
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+// Forward declarations
+//-----------------------------------------------------------------------------
+class CExLabel;
+class CBaseThumbnailCollection;
+class CReplayDetailsPanel;
+class CReplayScreenshotSlideshowPanel;
+
+//-----------------------------------------------------------------------------
+// Purpose: Preview balloon
+//-----------------------------------------------------------------------------
+class CGenericClassBasedReplay;
+class CCrossfadableImagePanel;
+class CSlideshowPanel;
+
+class CReplayPreviewPanelBase : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CReplayPreviewPanelBase, EditablePanel );
+public:
+ CReplayPreviewPanelBase( Panel *pParent, QueryableReplayItemHandle_t hItem, IReplayItemManager *pItemManager );
+ ~CReplayPreviewPanelBase();
+
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void PerformLayout();
+
+ ReplayHandle_t GetReplayHandle();
+
+protected:
+ CGenericClassBasedReplay *GetReplay();
+
+ virtual bool ShoudlUseLargeClassImage() { return false; }
+ virtual void LayoutView( int &nWide, int &nTall, int &nCurY );
+
+protected:
+ IReplayItemManager *m_pItemManager;
+ QueryableReplayItemHandle_t m_hItem;
+
+private:
+ ImagePanel *m_pClassImage;
+ vgui::EditablePanel *m_pInfoPanel;
+
+ CExLabel *m_pMapLabel;
+ CExLabel *m_pDateTimeLabel;
+
+ enum ELabels
+ {
+ LABEL_PLAYED_AS,
+ LABEL_KILLED_BY,
+ LABEL_LIFE_LENGTH,
+ NUM_INFO_LABELS
+ };
+ CExLabel *m_pReplayInfoLabels[NUM_INFO_LABELS][2];
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Preview balloon for slideshows (actual replays)
+//-----------------------------------------------------------------------------
+class CReplayPreviewPanelSlideshow : public CReplayPreviewPanelBase
+{
+ DECLARE_CLASS_SIMPLE( CReplayPreviewPanelSlideshow, CReplayPreviewPanelBase );
+public:
+ CReplayPreviewPanelSlideshow( Panel *pParent, QueryableReplayItemHandle_t hItem, IReplayItemManager *pItemManager );
+
+private:
+ virtual void PerformLayout();
+ virtual void LayoutView( int &nWide, int &nTall, int &nCurY );
+
+ CReplayScreenshotSlideshowPanel *m_pScreenshotPanel;
+ CExLabel *m_pNoScreenshotLabel;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Preview balloon for movies (rendered replays)
+//-----------------------------------------------------------------------------
+class CMoviePlayerPanel;
+class IReplayMovie;
+
+class CReplayPreviewPanelMovie : public CReplayPreviewPanelBase
+{
+ DECLARE_CLASS_SIMPLE( CReplayPreviewPanelMovie, CReplayPreviewPanelBase );
+public:
+ CReplayPreviewPanelMovie( Panel *pParent, QueryableReplayItemHandle_t hItem, IReplayItemManager *pItemManager );
+ ~CReplayPreviewPanelMovie();
+
+private:
+ virtual void OnTick();
+ virtual void LayoutView( int &nWide, int &nTall, int &nCurY );
+
+ virtual IReplayMovie *GetReplayMovie();
+
+ CMoviePlayerPanel *m_pMoviePlayerPanel;
+ float m_flCreateTime;
+};
+
+
+
+#endif // REPLAYBROWSER_PREVIEWPANEL_H
diff --git a/mp/src/game/client/replay/vgui/replaybrowserrenderdialog.cpp b/mp/src/game/client/replay/vgui/replaybrowserrenderdialog.cpp new file mode 100644 index 00000000..eef4db2f --- /dev/null +++ b/mp/src/game/client/replay/vgui/replaybrowserrenderdialog.cpp @@ -0,0 +1,639 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "replaybrowserrenderdialog.h"
+#include "vgui_controls/TextImage.h"
+#include "vgui_controls/CheckButton.h"
+#include "vgui_controls/TextEntry.h"
+#include "vgui/IInput.h"
+#include "replay/genericclassbased_replay.h"
+#include "ienginevgui.h"
+#include "replayrenderoverlay.h"
+#include "replay/ireplaymanager.h"
+#include "replay/ireplaymoviemanager.h"
+#include "video/ivideoservices.h"
+#include "confirm_dialog.h"
+#include "replay/replayrenderer.h"
+
+#include "replay/performance.h"
+#include "replay/replayvideo.h"
+#include "replay_gamestats_shared.h"
+
+#include "econ/econ_controls.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+
+extern IReplayMovieManager *g_pReplayMovieManager;
+
+//-----------------------------------------------------------------------------
+
+ConVar replay_rendersetting_quitwhendone( "replay_rendersetting_quitwhendone", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD, "Quit after rendering is completed.", true, 0.0f, true, 1.0f );
+ConVar replay_rendersetting_exportraw( "replay_rendersetting_exportraw", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_ARCHIVE, "Export raw TGA frames and a .wav file, instead of encoding a movie file.", true, 0.0f, true, 1.0f );
+ConVar replay_rendersetting_motionblurquality( "replay_rendersetting_motionblurquality", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD, "Motion blur quality.", true, 0, true, MAX_MOTION_BLUR_QUALITY );
+ConVar replay_rendersetting_motionblurenabled( "replay_rendersetting_motionblurenabled", "1", FCVAR_CLIENTDLL | FCVAR_DONTRECORD, "Motion blur enabled/disabled.", true, 0.0f, true, 1.0f );
+ConVar replay_rendersetting_encodingquality( "replay_rendersetting_encodingquality", "100", FCVAR_CLIENTDLL | FCVAR_DONTRECORD, "Render quality: the higher the quality, the larger the resulting movie file size.", true, 0, true, 100 );
+ConVar replay_rendersetting_motionblur_can_toggle( "replay_rendersetting_motionblur_can_toggle", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD, "" );
+ConVar replay_rendersetting_renderglow( "replay_rendersetting_renderglow", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_ARCHIVE, "Glow effect enabled/disabled.", true, 0.0f, true, 1.0f );
+
+//-----------------------------------------------------------------------------
+
+CReplayRenderDialog::CReplayRenderDialog( Panel *pParent, ReplayHandle_t hReplay, bool bSetQuit, int iPerformance )
+: BaseClass( pParent, "RenderDialog" ),
+ m_bShowAdvancedOptions( false ),
+ m_hReplay( hReplay ),
+ m_bSetQuit( bSetQuit ),
+ m_iPerformance( iPerformance ),
+ m_pVideoModesCombo( NULL ),
+ m_pCodecCombo( NULL ),
+ m_pPlayVoiceCheck( NULL ),
+ m_pShowAdvancedOptionsCheck( NULL ),
+ m_pQuitWhenDoneCheck( NULL ),
+ m_pExportRawCheck( NULL ),
+ m_pTitleText( NULL ),
+ m_pResolutionNoteLabel( NULL ),
+ m_pEnterANameLabel( NULL ),
+ m_pVideoModeLabel( NULL ),
+ m_pCodecLabel( NULL ),
+ m_pMotionBlurLabel( NULL ),
+ m_pMotionBlurSlider( NULL ),
+ m_pQualityLabel( NULL ),
+ m_pQualitySlider( NULL ),
+ m_pTitleLabel( NULL ),
+ m_pCancelButton( NULL ),
+ m_pRenderButton( NULL ),
+ m_pBgPanel( NULL ),
+ m_pMotionBlurCheck( NULL ),
+ m_pQualityPresetLabel( NULL ),
+ m_pQualityPresetCombo( NULL ),
+ m_pSeparator( NULL ),
+ m_pGlowEnabledCheck( NULL )
+{
+ m_iQualityPreset = ReplayVideo_GetDefaultQualityPreset();
+}
+
+void CReplayRenderDialog::UpdateControlsValues()
+{
+ ConVarRef replay_voice_during_playback( "replay_voice_during_playback" );
+
+ m_pQuitWhenDoneCheck->SetSelected( replay_rendersetting_quitwhendone.GetBool() );
+ m_pExportRawCheck->SetSelected( replay_rendersetting_exportraw.GetBool() );
+ m_pShowAdvancedOptionsCheck->SetSelected( m_bShowAdvancedOptions );
+ m_pMotionBlurSlider->SetValue( replay_rendersetting_motionblurquality.GetInt() );
+ m_pMotionBlurCheck->SetSelected( replay_rendersetting_motionblurenabled.GetBool() );
+ m_pQualitySlider->SetValue( replay_rendersetting_encodingquality.GetInt() / ReplayVideo_GetQualityInterval() );
+
+ if ( m_pGlowEnabledCheck )
+ {
+ m_pGlowEnabledCheck->SetSelected( replay_rendersetting_renderglow.GetBool() );
+ }
+
+ if ( replay_voice_during_playback.IsValid() )
+ {
+ m_pPlayVoiceCheck->SetSelected( replay_voice_during_playback.GetBool() );
+ }
+ else
+ {
+ m_pPlayVoiceCheck->SetEnabled( false );
+ }
+}
+
+void CReplayRenderDialog::AddControlToAutoLayout( Panel *pPanel, bool bAdvanced )
+{
+ LayoutInfo_t *pNewLayoutInfo = new LayoutInfo_t;
+ pNewLayoutInfo->pPanel = pPanel;
+
+ // Use the positions from the .res file as relative positions for auto-layout
+ pPanel->GetPos( pNewLayoutInfo->nOffsetX, pNewLayoutInfo->nOffsetY );
+
+ pNewLayoutInfo->bAdvanced = bAdvanced;
+
+ // Add to the list
+ m_lstControls.AddToTail( pNewLayoutInfo );
+}
+
+void CReplayRenderDialog::SetValuesFromQualityPreset()
+{
+ const ReplayQualityPreset_t &preset = ReplayVideo_GetQualityPreset( m_iQualityPreset );
+ replay_rendersetting_motionblurquality.SetValue( preset.m_iMotionBlurQuality );
+ replay_rendersetting_motionblurenabled.SetValue( (int)preset.m_bMotionBlurEnabled );
+ replay_rendersetting_encodingquality.SetValue( preset.m_iQuality );
+ for ( int i = 0; i < ReplayVideo_GetCodecCount(); ++i )
+ {
+ const ReplayCodec_t &CurCodec = ReplayVideo_GetCodec( i );
+ if ( CurCodec.m_nCodecId == preset.m_nCodecId )
+ {
+ m_pCodecCombo->ActivateItem( m_pCodecCombo->GetItemIDFromRow( i ) );
+ break;
+ }
+ }
+ UpdateControlsValues();
+ InvalidateLayout();
+}
+
+void CReplayRenderDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ int i;
+
+ // Link in TF scheme
+ extern IEngineVGui *enginevgui;
+ vgui::HScheme pTFScheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme" );
+ SetScheme( pTFScheme );
+ SetProportional( true );
+
+ BaseClass::ApplySchemeSettings( vgui::scheme()->GetIScheme( pTFScheme ) );
+
+ LoadControlSettings( "Resource/UI/replaybrowser/renderdialog.res", "GAME" );
+
+ // retrieve controls
+ m_pPlayVoiceCheck = dynamic_cast< CheckButton * >( FindChildByName( "PlayVoice" ) );
+ m_pShowAdvancedOptionsCheck = dynamic_cast< CheckButton * >( FindChildByName( "ShowAdvancedOptions" ) );
+ m_pQuitWhenDoneCheck = dynamic_cast< CheckButton * >( FindChildByName( "QuitWhenDone" ) );
+ m_pExportRawCheck = dynamic_cast< CheckButton * >( FindChildByName( "ExportRaw" ) );
+ m_pTitleText = dynamic_cast< TextEntry * >( FindChildByName( "TitleInput" ) );
+ m_pResolutionNoteLabel = dynamic_cast< CExLabel * >( FindChildByName( "ResolutionNoteLabel" ) );
+ m_pEnterANameLabel = dynamic_cast< CExLabel * >( FindChildByName( "EnterANameLabel" ) );
+ m_pVideoModeLabel = dynamic_cast< CExLabel * >( FindChildByName( "VideoModeLabel" ) );
+ m_pCodecLabel = dynamic_cast< CExLabel * >( FindChildByName( "CodecLabel" ) );
+ m_pMotionBlurLabel = dynamic_cast< CExLabel * >( FindChildByName( "MotionBlurLabel" ) );
+ m_pMotionBlurSlider = dynamic_cast< Slider * >( FindChildByName( "MotionBlurSlider" ) );
+ m_pQualityLabel = dynamic_cast< CExLabel * >( FindChildByName( "QualityLabel" ) );
+ m_pQualitySlider = dynamic_cast< Slider * >( FindChildByName( "QualitySlider" ) );
+ m_pTitleLabel = dynamic_cast< CExLabel * >( FindChildByName( "TitleLabel" ) );
+ m_pRenderButton = dynamic_cast< CExButton * >( FindChildByName( "RenderButton" ) );
+ m_pCancelButton = dynamic_cast< CExButton * >( FindChildByName( "CancelButton" ) );
+ m_pBgPanel = dynamic_cast< EditablePanel * >( FindChildByName( "BGPanel" ) );
+ m_pMotionBlurCheck = dynamic_cast< CheckButton * >( FindChildByName( "MotionBlurEnabled" ) );
+ m_pQualityPresetLabel = dynamic_cast< CExLabel * >( FindChildByName( "QualityPresetLabel" ) );
+ m_pQualityPresetCombo = dynamic_cast< vgui::ComboBox * >( FindChildByName( "QualityPresetCombo" ) );
+ m_pCodecCombo = dynamic_cast< vgui::ComboBox * >( FindChildByName( "CodecCombo" ) );
+ m_pVideoModesCombo = dynamic_cast< vgui::ComboBox * >( FindChildByName( "VideoModeCombo" ) );
+ m_pEstimateTimeLabel = dynamic_cast< CExLabel * >( FindChildByName( "EstimateTimeLabel" ) );
+ m_pEstimateFileLabel = dynamic_cast< CExLabel * >( FindChildByName( "EstimateFileLabel" ) );
+ m_pSeparator = FindChildByName( "SeparatorLine" );
+ m_pGlowEnabledCheck = dynamic_cast< CheckButton * >( FindChildByName( "GlowEnabled" ) );
+ m_pLockWarningLabel = dynamic_cast< CExLabel * >( FindChildByName( "LockWarningLabel" ) );
+
+#if defined( TF_CLIENT_DLL )
+ if ( m_pBgPanel )
+ {
+ m_pBgPanel->SetPaintBackgroundType( 2 ); // Rounded.
+ }
+#endif
+
+ AddControlToAutoLayout( m_pTitleLabel, false );
+
+ // The replay may be REPLAY_HANDLE_INVALID in the case that we are about to render all unrendered replays
+ if ( m_hReplay != REPLAY_HANDLE_INVALID )
+ {
+ CGenericClassBasedReplay *pReplay = GetGenericClassBasedReplay( m_hReplay );
+ m_pTitleText->SetText( pReplay->m_wszTitle );
+ m_pTitleText->SetVisible( true );
+ m_pTitleLabel->SetText( "#Replay_RenderReplay" );
+ m_pEnterANameLabel->SetVisible( true );
+
+ AddControlToAutoLayout( m_pEnterANameLabel, false );
+ }
+ else
+ {
+ m_pTitleLabel->SetText( "#Replay_RenderReplays" );
+ }
+
+ m_pTitleText->SelectAllOnFocusAlways( true );
+
+ AddControlToAutoLayout( m_pTitleText, false );
+
+ // Update controls based on preset
+ SetValuesFromQualityPreset();
+
+ // Set quit button if necessary
+ if ( m_bSetQuit )
+ {
+ m_pQuitWhenDoneCheck->SetSelected( true );
+ }
+
+ m_pPlayVoiceCheck->SetProportional( false );
+ m_pQuitWhenDoneCheck->SetProportional( false );
+ m_pShowAdvancedOptionsCheck->SetProportional( false );
+ m_pMotionBlurCheck->SetProportional( false );
+ m_pMotionBlurSlider->InvalidateLayout( false, true ); // Without this, the range labels show up with "..." because of an invalid font in TextImage::ApplySchemeSettings().
+ m_pExportRawCheck->SetProportional( false );
+ m_pQualitySlider->InvalidateLayout( false, true ); // Without this, the range labels show up with "..." because of an invalid font in TextImage::ApplySchemeSettings().
+
+ if ( m_pGlowEnabledCheck )
+ {
+ m_pGlowEnabledCheck->SetProportional( false );
+ }
+
+ // Fill in combo box with preset quality levels
+ const int nQualityPresetCount = ReplayVideo_GetQualityPresetCount();
+ m_pQualityPresetCombo->SetNumberOfEditLines( nQualityPresetCount );
+ for ( i = 0; i < nQualityPresetCount; ++i )
+ {
+ const ReplayQualityPreset_t &CurQualityPreset = ReplayVideo_GetQualityPreset( i );
+ m_pQualityPresetCombo->AddItem( CurQualityPreset.m_pName, NULL );
+ m_pQualityPresetCombo->SetItemEnabled( i, true );
+ }
+ m_pQualityPresetCombo->ActivateItem( m_pQualityPresetCombo->GetItemIDFromRow( m_iQualityPreset ) );
+
+ // Fill in combo box with video modes
+ int nScreenW = ScreenWidth();
+ int nScreenH = ScreenHeight();
+ const int nVidModeCount = ReplayVideo_GetVideoModeCount();
+ m_pVideoModesCombo->SetNumberOfEditLines( nVidModeCount );
+ bool bAtLeastOneVideoModeAdded = false;
+ bool bEnable = false;
+ bool bSkipped = false;
+
+ for ( i = 0; i < nVidModeCount; ++i )
+ {
+ // Only offer display modes less than the current window size
+ const ReplayVideoMode_t &CurVideoMode = ReplayVideo_GetVideoMode( i );
+
+ int nMw = CurVideoMode.m_nWidth;
+ int nMh = CurVideoMode.m_nHeight;
+
+ // Only display modes that fit in the current window
+ bEnable = ( nMw <= nScreenW && nMh <= nScreenH );
+ if (!bEnable)
+ bSkipped = true;
+
+ m_pVideoModesCombo->AddItem( CurVideoMode.m_pName, NULL );
+ m_pVideoModesCombo->SetItemEnabled( i, bEnable );
+
+ if (bEnable)
+ bAtLeastOneVideoModeAdded = true;
+ }
+ if ( bAtLeastOneVideoModeAdded )
+ {
+ m_pVideoModesCombo->ActivateItem( m_pVideoModesCombo->GetItemIDFromRow( 0 ) );
+ }
+
+ // fill in the combo box with codecs
+ const int nNumCodecs = ReplayVideo_GetCodecCount();
+ m_pCodecCombo->SetNumberOfEditLines( nNumCodecs );
+ for ( i = 0; i < nNumCodecs; ++i )
+ {
+ const ReplayCodec_t &CurCodec = ReplayVideo_GetCodec( i );
+ m_pCodecCombo->AddItem( CurCodec.m_pName, NULL );
+ m_pCodecCombo->SetItemEnabled( i, true );
+ }
+ m_pCodecCombo->ActivateItem( m_pCodecCombo->GetItemIDFromRow( 0 ) );
+
+ // now layout
+
+ // simplified options
+ AddControlToAutoLayout( m_pVideoModeLabel, false );
+ AddControlToAutoLayout( m_pVideoModesCombo, false );
+ // Show the note about "not all resolutions are available?"
+ if ( bSkipped && m_pResolutionNoteLabel )
+ {
+ m_pResolutionNoteLabel->SetVisible( true );
+ AddControlToAutoLayout( m_pResolutionNoteLabel, false );
+ }
+ // other simplified options
+ AddControlToAutoLayout( m_pQualityPresetLabel, false );
+ AddControlToAutoLayout( m_pQualityPresetCombo, false );
+ AddControlToAutoLayout( m_pEstimateTimeLabel, false );
+ AddControlToAutoLayout( m_pEstimateFileLabel, false );
+ AddControlToAutoLayout( m_pPlayVoiceCheck, false );
+ AddControlToAutoLayout( m_pShowAdvancedOptionsCheck, false );
+ AddControlToAutoLayout( m_pQuitWhenDoneCheck, false );
+
+ AddControlToAutoLayout( m_pLockWarningLabel, false );
+
+ // now advanced options
+ AddControlToAutoLayout( m_pSeparator, true );
+
+ AddControlToAutoLayout( m_pCodecLabel, true );
+ AddControlToAutoLayout( m_pCodecCombo, true );
+
+ if ( replay_rendersetting_motionblur_can_toggle.GetBool() )
+ {
+ AddControlToAutoLayout( m_pMotionBlurCheck, true );
+ }
+ else
+ {
+ m_pMotionBlurCheck->SetVisible( false );
+ }
+ AddControlToAutoLayout( m_pMotionBlurLabel, true );
+ AddControlToAutoLayout( m_pMotionBlurSlider, true );
+
+ AddControlToAutoLayout( m_pQualityLabel, true );
+ AddControlToAutoLayout( m_pQualitySlider, true );
+
+ AddControlToAutoLayout( m_pExportRawCheck, true );
+
+ if ( m_pGlowEnabledCheck )
+ {
+ AddControlToAutoLayout( m_pGlowEnabledCheck, true );
+ }
+
+ // these buttons always show up
+ AddControlToAutoLayout( m_pRenderButton, false );
+ AddControlToAutoLayout( m_pCancelButton, false );
+}
+
+void CReplayRenderDialog::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ m_pResolutionNoteLabel->SizeToContents(); // Get the proper height
+
+ int nY = m_nStartY;
+ Panel *pPrevPanel = NULL;
+ int nLastCtrlHeight = 0;
+
+ FOR_EACH_LL( m_lstControls, i )
+ {
+ LayoutInfo_t *pLayoutInfo = m_lstControls[ i ];
+ Panel *pPanel = pLayoutInfo->pPanel;
+
+ // should an advanced option be shown?
+ if ( pLayoutInfo->bAdvanced )
+ {
+ if ( pPanel->IsVisible() != m_bShowAdvancedOptions )
+ {
+ pPanel->SetVisible( m_bShowAdvancedOptions );
+ }
+ }
+
+ if ( !pPanel->IsVisible() )
+ continue;
+
+ if ( pPrevPanel && pLayoutInfo->nOffsetY >= 0 )
+ {
+ nY += pPrevPanel->GetTall() + pLayoutInfo->nOffsetY + m_nVerticalBuffer;
+ }
+
+ pPanel->SetPos( pLayoutInfo->nOffsetX ? pLayoutInfo->nOffsetX : m_nDefaultX, nY );
+
+ pPrevPanel = pPanel;
+ nLastCtrlHeight = pPanel->GetTall();
+ }
+
+ m_pBgPanel->SetTall( nY + nLastCtrlHeight + 2 * m_nVerticalBuffer );
+}
+
+void CReplayRenderDialog::Close()
+{
+ SetVisible( false );
+ MarkForDeletion();
+ TFModalStack()->PopModal( this );
+}
+
+void CReplayRenderDialog::OnCommand( const char *pCommand )
+{
+ if ( FStrEq( pCommand, "cancel" ) )
+ {
+ Close();
+ }
+ else if ( FStrEq( pCommand, "render" ) )
+ {
+ Close();
+ Render();
+ }
+ else
+ {
+ engine->ClientCmd( const_cast<char *>( pCommand ) );
+ }
+
+ BaseClass::OnCommand( pCommand );
+}
+
+void CReplayRenderDialog::Render()
+{
+ // Only complain about QuickTime if we aren't exporting raw TGA's/WAV
+ if ( !m_pExportRawCheck->IsSelected() )
+ {
+#ifndef USE_WEBM_FOR_REPLAY
+ if ( !g_pVideo || !g_pVideo->IsVideoSystemAvailable( VideoSystem::QUICKTIME ) )
+ {
+ ShowMessageBox( "#Replay_QuicktimeTitle", "#Replay_NeedQuicktime", "#GameUI_OK" );
+ return;
+ }
+
+ if ( g_pVideo->GetVideoSystemStatus( VideoSystem::QUICKTIME ) != VideoSystemStatus::OK )
+ {
+ if ( g_pVideo->GetVideoSystemStatus( VideoSystem::QUICKTIME ) == VideoSystemStatus::NOT_CURRENT_VERSION )
+ {
+ ShowMessageBox( "#Replay_QuicktimeTitle", "#Replay_NeedQuicktimeNewer", "#GameUI_OK" );
+ return;
+ }
+
+ ShowMessageBox( "#Replay_QuicktimeTitle", "#Replay_Err_QT_FailedToLoad", "#GameUI_OK" );
+ return;
+ }
+#endif
+ }
+
+ // Update convars from settings
+ const int nMotionBlurQuality = clamp( m_pMotionBlurSlider->GetValue(), 0, MAX_MOTION_BLUR_QUALITY );
+ replay_rendersetting_quitwhendone.SetValue( (int)m_pQuitWhenDoneCheck->IsSelected() );
+ replay_rendersetting_exportraw.SetValue( (int)m_pExportRawCheck->IsSelected() );
+ replay_rendersetting_motionblurquality.SetValue( nMotionBlurQuality );
+ replay_rendersetting_motionblurenabled.SetValue( replay_rendersetting_motionblur_can_toggle.GetBool() ? (int)m_pMotionBlurCheck->IsSelected() : 1 );
+ replay_rendersetting_encodingquality.SetValue( clamp( m_pQualitySlider->GetValue() * ReplayVideo_GetQualityInterval(), 0, 100 ) );
+
+ if ( m_pGlowEnabledCheck )
+ {
+ replay_rendersetting_renderglow.SetValue( m_pGlowEnabledCheck->IsSelected() );
+ }
+
+ ConVarRef replay_voice_during_playback( "replay_voice_during_playback" );
+ if ( replay_voice_during_playback.IsValid() && m_pPlayVoiceCheck->IsEnabled() )
+ {
+ replay_voice_during_playback.SetValue( (int)m_pPlayVoiceCheck->IsSelected() );
+ }
+
+ // Setup parameters for render
+ RenderMovieParams_t params;
+ params.m_hReplay = m_hReplay;
+ params.m_iPerformance = m_iPerformance; // Use performance passed in from details panel
+ params.m_bQuitWhenFinished = m_pQuitWhenDoneCheck->IsSelected();
+ params.m_bExportRaw = m_pExportRawCheck->IsSelected();
+ m_pTitleText->GetText( params.m_wszTitle, sizeof( params.m_wszTitle ) );
+#ifdef USE_WEBM_FOR_REPLAY
+ V_strcpy( params.m_szExtension, ".webm" ); // Use .webm
+#else
+ V_strcpy( params.m_szExtension, ".mov" ); // Use .mov for Quicktime
+#endif
+
+ const int iRes = m_pVideoModesCombo->GetActiveItem();
+ const ReplayVideoMode_t &VideoMode = ReplayVideo_GetVideoMode( iRes );
+
+ params.m_Settings.m_bMotionBlurEnabled = replay_rendersetting_motionblurenabled.GetBool();
+ params.m_Settings.m_bAAEnabled = replay_rendersetting_motionblurenabled.GetBool();
+ params.m_Settings.m_nMotionBlurQuality = nMotionBlurQuality;
+ params.m_Settings.m_nWidth = VideoMode.m_nWidth;
+ params.m_Settings.m_nHeight = VideoMode.m_nHeight;
+ params.m_Settings.m_FPS.SetFPS( VideoMode.m_nBaseFPS, VideoMode.m_bNTSCRate );
+ params.m_Settings.m_Codec = ReplayVideo_GetCodec( m_pCodecCombo->GetActiveItem() ).m_nCodecId;
+ params.m_Settings.m_nEncodingQuality = replay_rendersetting_encodingquality.GetInt();
+ params.m_Settings.m_bRaw = m_pExportRawCheck->IsSelected();
+
+ // Calculate the framerate for the engine - for each engine frame, we need the # of motion blur timesteps,
+ // x 2, since the shutter is open for nNumMotionBlurTimeSteps and closed for nNumMotionBlurTimeSteps,
+ // with the engine frame centered in the shutter open state (ie when we're half way through the motion blur
+ // timesteps). Antialiasing does not factor in here because it doesn't require extra frames - the AA jitter
+ // is interwoven in with the motion sub-frames.
+ const int nNumMotionBlurTimeSteps = ( params.m_Settings.m_bMotionBlurEnabled ) ? CReplayRenderer::GetNumMotionBlurTimeSteps( params.m_Settings.m_nMotionBlurQuality ) : 1;
+
+ if ( params.m_Settings.m_bMotionBlurEnabled )
+ {
+ params.m_flEngineFps = 2 * nNumMotionBlurTimeSteps * params.m_Settings.m_FPS.GetFPS();
+ }
+ else
+ {
+ Assert( nNumMotionBlurTimeSteps == 1 );
+ params.m_flEngineFps = params.m_Settings.m_FPS.GetFPS();
+ }
+
+ // Close the browser
+ extern void ReplayUI_CloseReplayBrowser();
+ ReplayUI_CloseReplayBrowser();
+
+ // Hide the console
+ engine->ExecuteClientCmd( "hideconsole" );
+
+ // Stats tracking.
+ GetReplayGameStatsHelper().SW_ReplayStats_WriteRenderDataStart( params, this );
+
+ // Render the movie
+ g_pReplayMovieManager->RenderMovie( params );
+}
+
+void CReplayRenderDialog::OnKeyCodeTyped( vgui::KeyCode code )
+{
+ if( code == KEY_ENTER )
+ {
+ OnCommand( "render" );
+ }
+ else if ( code == KEY_ESCAPE )
+ {
+ MarkForDeletion();
+ }
+ else
+ {
+ BaseClass::OnKeyCodeTyped( code );
+ }
+}
+
+void CReplayRenderDialog::OnThink()
+{
+ if ( m_pEstimateTimeLabel == NULL || m_pEstimateFileLabel == NULL )
+ return;
+
+ // The replay may be NULL if this dialog is created by 'save all' from the quit confirmation dialog. In this
+ // case, we don't want to a replay-specific time estimate anyway, so we can just early out here.
+ CGenericClassBasedReplay *pReplay = ToGenericClassBasedReplay( g_pReplayManager->GetReplay( m_hReplay) );
+ if ( !pReplay )
+ return;
+
+ const int nMotionBlurQuality = clamp( m_pMotionBlurSlider->GetValue(), 0, MAX_MOTION_BLUR_QUALITY );
+ const int nCodecQuality = clamp( m_pQualitySlider->GetValue(), 0, ReplayVideo_GetQualityRange() );
+ VideoEncodeCodec::EVideoEncodeCodec_t eCodec = ReplayVideo_GetCodec( m_pCodecCombo->GetActiveItem() ).m_nCodecId;
+
+ // fFrameSize is the scale factor based on the size of the rendered frame.
+ const int iRes = m_pVideoModesCombo->GetActiveItem();
+ const ReplayVideoMode_t &VideoMode = ReplayVideo_GetVideoMode( iRes );
+ float fFrameSize = (float)(VideoMode.m_nWidth * VideoMode.m_nHeight)/(float)(640*480);
+
+
+ float flEstimatedFileSize = 0;
+ float flEstimatedRenderTime_Min = 0;
+
+ static float mjpegToMotionBlurMultiplierTable[] = { 2.0f, 3.0f, 5.5f, 12.0f };
+ static float h264ToMotionBlurMultiplierTable[] = { 2.8f, 4.2f, 6.4f, 13.0f };
+ static float webmToMotionBlurMultiplierTable[] = { 2.8f, 4.2f, 6.4f, 13.0f };
+
+ static float mjpegToQualityMultiplierTable[] = { 620.0f, 736.0f, 1284.0f, 2115.0f, 3028.0f };
+ static float h264ToQualityMultiplierTable[] = { 276.0f, 384.0f, 595.0f, 1026.0f, 1873.0f };
+ static float webmToQualityMultiplierTable[] = { 125.0f, 250.0f, 312.0f, 673.0f, 1048.0f };
+
+ switch ( eCodec )
+ {
+ case VideoEncodeCodec::WEBM_CODEC:
+ flEstimatedFileSize = pReplay->m_flLength * webmToQualityMultiplierTable[nCodecQuality]*fFrameSize;
+ flEstimatedRenderTime_Min = pReplay->m_flLength * webmToMotionBlurMultiplierTable[nMotionBlurQuality];
+ break;
+ case VideoEncodeCodec::H264_CODEC:
+ flEstimatedFileSize = pReplay->m_flLength * h264ToQualityMultiplierTable[nCodecQuality];
+ flEstimatedRenderTime_Min = pReplay->m_flLength * h264ToMotionBlurMultiplierTable[nMotionBlurQuality];
+ break;
+ case VideoEncodeCodec::MJPEG_A_CODEC:
+ flEstimatedFileSize = pReplay->m_flLength * mjpegToQualityMultiplierTable[nCodecQuality];
+ flEstimatedRenderTime_Min = pReplay->m_flLength * mjpegToMotionBlurMultiplierTable[nMotionBlurQuality];
+ break;
+ }
+
+ float flEstimatedRenderTime_Max = flEstimatedRenderTime_Min * 3.0f;
+
+ // @todo Tom Bui: if this goes into hours, we are in trouble...
+ wchar_t wzFileSize[64];
+ _snwprintf( wzFileSize, ARRAYSIZE( wzFileSize ), L"%d", (int)flEstimatedFileSize );
+ wchar_t wzTimeMin[64];
+ wchar_t wzTimeMax[64];
+ g_pVGuiLocalize->ConvertANSIToUnicode( CReplayTime::FormatTimeString( flEstimatedRenderTime_Min ), wzTimeMin, sizeof( wzTimeMin ) );
+ g_pVGuiLocalize->ConvertANSIToUnicode( CReplayTime::FormatTimeString( flEstimatedRenderTime_Max ), wzTimeMax, sizeof( wzTimeMax ) );
+
+ wchar_t wzText[256] = L"";
+
+ g_pVGuiLocalize->ConstructString( wzText, sizeof( wzText ), g_pVGuiLocalize->Find( "#Replay_RenderEstimate_File" ), 1,
+ wzFileSize,
+ wzTimeMin,
+ wzTimeMax );
+ m_pEstimateFileLabel->SetText( wzText );
+
+ g_pVGuiLocalize->ConstructString( wzText, sizeof( wzText ), g_pVGuiLocalize->Find( "#Replay_RenderEstimate_Time" ), 2,
+ wzTimeMin,
+ wzTimeMax );
+ m_pEstimateTimeLabel->SetText( wzText );
+}
+
+void CReplayRenderDialog::OnTextChanged( KeyValues *data )
+{
+ Panel *pPanel = reinterpret_cast<vgui::Panel *>( data->GetPtr("panel") );
+ vgui::ComboBox *pComboBox = dynamic_cast<vgui::ComboBox *>( pPanel );
+
+ if ( pComboBox == m_pQualityPresetCombo )
+ {
+ m_iQualityPreset = m_pQualityPresetCombo->GetActiveItem();
+ SetValuesFromQualityPreset();
+ }
+}
+
+void CReplayRenderDialog::OnCheckButtonChecked( vgui::Panel *panel )
+{
+ if ( panel == m_pShowAdvancedOptionsCheck )
+ {
+ m_bShowAdvancedOptions = m_pShowAdvancedOptionsCheck->IsSelected();
+ InvalidateLayout( true, false );
+ }
+}
+
+void CReplayRenderDialog::OnSetFocus()
+{
+ m_pTitleText->RequestFocus();
+}
+
+void ReplayUI_ShowRenderDialog( Panel* pParent, ReplayHandle_t hReplay, bool bSetQuit, int iPerformance )
+{
+ CReplayRenderDialog *pRenderDialog = vgui::SETUP_PANEL( new CReplayRenderDialog( pParent, hReplay, bSetQuit, iPerformance ) );
+
+ pRenderDialog->SetVisible( true );
+ pRenderDialog->MakePopup();
+ pRenderDialog->MoveToFront();
+ pRenderDialog->SetKeyBoardInputEnabled( true );
+ pRenderDialog->SetMouseInputEnabled( true );
+ TFModalStack()->PushModal( pRenderDialog );
+}
+
+#endif
diff --git a/mp/src/game/client/replay/vgui/replaybrowserrenderdialog.h b/mp/src/game/client/replay/vgui/replaybrowserrenderdialog.h new file mode 100644 index 00000000..e3cd75b3 --- /dev/null +++ b/mp/src/game/client/replay/vgui/replaybrowserrenderdialog.h @@ -0,0 +1,109 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef REPLAYBROWSER_RENDERDIALOG_H
+#define REPLAYBROWSER_RENDERDIALOG_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "replaybrowserbasepanel.h"
+#include "vgui/IScheme.h"
+#include "vgui_controls/CheckButton.h"
+#include "vgui_controls/ComboBox.h"
+#include "vgui_controls/Slider.h"
+#include "replay/replayhandle.h"
+
+using namespace vgui;
+
+class CExLabel;
+class CExButton;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CReplayRenderDialog : public CReplayBasePanel
+{
+ DECLARE_CLASS_SIMPLE( CReplayRenderDialog, CReplayBasePanel );
+public:
+ CReplayRenderDialog( Panel *pParent, ReplayHandle_t hReplay, bool bSetQuit, int iPerformance );
+
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void PerformLayout();
+ virtual void OnCommand( const char *pCommand );
+ virtual void OnKeyCodeTyped( vgui::KeyCode code );
+ virtual void OnThink();
+
+ MESSAGE_FUNC_PARAMS( OnTextChanged, "TextChanged", data );
+ MESSAGE_FUNC_PTR( OnCheckButtonChecked, "CheckButtonChecked", panel );
+
+private:
+ MESSAGE_FUNC( OnSetFocus, "SetFocus" );
+
+ void Close();
+ void Render();
+ void ValidateRenderData();
+ void UpdateControlsValues();
+ void AddControlToAutoLayout( Panel *pPanel, bool bAdvanced );
+ void SetValuesFromQualityPreset();
+
+ bool m_bShowAdvancedOptions;
+ int m_iQualityPreset;
+ ReplayHandle_t m_hReplay;
+ bool m_bSetQuit;
+ int m_iPerformance;
+ CheckButton *m_pPlayVoiceCheck;
+ CheckButton *m_pShowAdvancedOptionsCheck;
+ CheckButton *m_pQuitWhenDoneCheck;
+ CheckButton *m_pExportRawCheck;
+ CExButton *m_pCancelButton;
+ CExButton *m_pRenderButton;
+ TextEntry *m_pTitleText;
+ ComboBox *m_pVideoModesCombo;
+ ComboBox *m_pCodecCombo;
+ CExLabel *m_pQualityPresetLabel;
+ ComboBox *m_pQualityPresetCombo;
+ CExLabel *m_pResolutionNoteLabel;
+ CExLabel *m_pEnterANameLabel;
+ CExLabel *m_pVideoModeLabel;
+ CExLabel *m_pTitleLabel;
+ CExLabel *m_pLockWarningLabel;
+ CExLabel *m_pCodecLabel;
+ CExLabel *m_pEstimateTimeLabel;
+ CExLabel *m_pEstimateFileLabel;
+ CheckButton *m_pMotionBlurCheck;
+ CExLabel *m_pMotionBlurLabel;
+ Slider *m_pMotionBlurSlider;
+ CExLabel *m_pQualityLabel;
+ Slider *m_pQualitySlider;
+ EditablePanel *m_pBgPanel;
+ Panel *m_pSeparator;
+ CheckButton *m_pGlowEnabledCheck;
+
+ struct LayoutInfo_t
+ {
+ Panel *pPanel;
+ int nOffsetX;
+ int nOffsetY;
+ bool bAdvanced;
+ };
+
+ CUtlLinkedList< LayoutInfo_t * > m_lstControls;
+ CPanelAnimationVarAliasType( int, m_nStartY, "start_y", "0", "proportional_ypos" );
+ CPanelAnimationVarAliasType( int, m_nVerticalBuffer, "vertical_buffer", "0", "proportional_ypos" );
+ CPanelAnimationVarAliasType( int, m_nDefaultX, "default_x", "0", "proportional_xpos" );
+
+ friend class CReplayGameStatsHelper;
+};
+
+//-----------------------------------------------------------------------------
+
+void ReplayUI_ShowRenderDialog( Panel* pParent, ReplayHandle_t hReplay, bool bSetQuit, int iPerformance );
+
+//-----------------------------------------------------------------------------
+
+#endif // REPLAYBROWSER_RENDERDIALOG_H
diff --git a/mp/src/game/client/replay/vgui/replayconfirmquitdlg.cpp b/mp/src/game/client/replay/vgui/replayconfirmquitdlg.cpp new file mode 100644 index 00000000..9cea0c90 --- /dev/null +++ b/mp/src/game/client/replay/vgui/replayconfirmquitdlg.cpp @@ -0,0 +1,167 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "replayconfirmquitdlg.h"
+#include "vgui_controls/TextImage.h"
+#include "vgui_controls/CheckButton.h"
+#include "vgui_controls/TextEntry.h"
+#include "vgui/IInput.h"
+#include "vgui/ISurface.h"
+#include "ienginevgui.h"
+#include "replay/genericclassbased_replay.h"
+#include "replaybrowserrenderdialog.h"
+#include "econ/econ_controls.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+
+ConVar replay_quitmsg_dontaskagain( "replay_quitmsg_dontaskagain", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_ARCHIVE, "The replay system will ask you to render your replays on quit, unless this cvar is 1.", true, 0, true, 1 );
+
+//-----------------------------------------------------------------------------
+
+CReplayConfirmQuitDialog::CReplayConfirmQuitDialog( Panel *pParent )
+: BaseClass( pParent, "confirmquitdlg" ),
+ m_pDontShowAgain( NULL ),
+ m_pQuitButton( NULL )
+{
+ SetScheme( "ClientScheme" );
+ InvalidateLayout( true, true );
+}
+
+void CReplayConfirmQuitDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ // Link in TF scheme
+ extern IEngineVGui *enginevgui;
+ vgui::HScheme pTFScheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme" );
+ SetScheme( pTFScheme );
+ SetProportional( true );
+
+ BaseClass::ApplySchemeSettings( vgui::scheme()->GetIScheme( pTFScheme ) );
+
+ LoadControlSettings( "Resource/UI/replaybrowser/confirmquitdlg.res", "GAME" );
+
+ m_pDontShowAgain = dynamic_cast< CheckButton * >( FindChildByName( "DontShowThisAgainCheckbox" ) );
+ m_pQuitButton = dynamic_cast< CExButton * >( FindChildByName( "QuitButton" ) );
+
+ if ( m_pQuitButton )
+ {
+ m_pQuitButton->GetTextImage()->ClearColorChangeStream();
+ m_pQuitButton->GetTextImage()->AddColorChange( Color(200,80,60,255), 0 );
+ }
+}
+
+void CReplayConfirmQuitDialog::OnCommand( const char *pCommand )
+{
+ // Store the setting of our "never show this again" checkbox if the user picked anything
+ // except cancel.
+ if ( !FStrEq( pCommand, "cancel" ) && m_pDontShowAgain && m_pDontShowAgain->IsSelected() )
+ {
+ replay_quitmsg_dontaskagain.SetValue( 1 );
+ }
+
+ if ( FStrEq( pCommand, "rendernow_delay" ) )
+ {
+ // Delete this
+ SetVisible( false );
+ MarkForDeletion();
+
+ // Render all unrendered replays now
+ ReplayUI_ShowRenderDialog( NULL, REPLAY_HANDLE_INVALID, true, -1 );
+ }
+ else if ( FStrEq( pCommand, "rendernow" ) )
+ {
+ // Sometimes this message comes in just before input is processed when using a controller
+ // Refire after a delay
+ PostMessage( this, new KeyValues( "Command", "command", "rendernow_delay" ), 0.001f );
+ }
+ else if ( FStrEq( pCommand, "quit" ) )
+ {
+ MarkForDeletion();
+ engine->ClientCmd_Unrestricted( "quit\n" );
+ }
+ else if ( FStrEq( pCommand, "cancel" ) )
+ {
+ MarkForDeletion();
+ }
+ else if ( FStrEq( pCommand, "gotoreplays"))
+ {
+ // "Go to replays"
+ MarkForDeletion();
+ engine->ClientCmd( "replay_reloadbrowser" );
+ }
+}
+
+void CReplayConfirmQuitDialog::OnKeyCodeTyped( vgui::KeyCode code )
+{
+ if ( code == KEY_ESCAPE )
+ {
+ OnCommand( "cancel" );
+ }
+ else
+ {
+ BaseClass::OnKeyCodeTyped( code );
+ }
+}
+
+void CReplayConfirmQuitDialog::OnKeyCodePressed( vgui::KeyCode code )
+{
+ if ( GetBaseButtonCode( code ) == KEY_XBUTTON_B )
+ {
+ OnCommand( "cancel" );
+ }
+ else if ( GetBaseButtonCode( code ) == KEY_XBUTTON_A )
+ {
+ OnCommand( "quit" );
+ }
+ else if ( GetBaseButtonCode( code ) == KEY_XBUTTON_X )
+ {
+ if ( m_pDontShowAgain )
+ {
+ m_pDontShowAgain->SetSelected( !m_pDontShowAgain->IsSelected() );
+ }
+ }
+ else if ( GetBaseButtonCode( code ) == KEY_XBUTTON_Y )
+ {
+ OnCommand( "gotoreplays" );
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+}
+
+bool ReplayUI_ShowConfirmQuitDlg()
+{
+ if ( replay_quitmsg_dontaskagain.GetBool() )
+ return false;
+
+ CReplayConfirmQuitDialog *pConfirmQuitDlg = vgui::SETUP_PANEL( new CReplayConfirmQuitDialog( NULL ) );
+ if ( pConfirmQuitDlg )
+ {
+ vgui::surface()->PlaySound( "replay\\replaydialog_warn.wav" );
+
+ // Display the panel!
+ pConfirmQuitDlg->SetVisible( true );
+ pConfirmQuitDlg->MakePopup();
+ pConfirmQuitDlg->MoveToFront();
+ pConfirmQuitDlg->SetKeyBoardInputEnabled( true );
+ pConfirmQuitDlg->SetMouseInputEnabled( true );
+ TFModalStack()->PushModal( pConfirmQuitDlg );
+ }
+
+ return true;
+}
+
+#endif
\ No newline at end of file diff --git a/mp/src/game/client/replay/vgui/replayconfirmquitdlg.h b/mp/src/game/client/replay/vgui/replayconfirmquitdlg.h new file mode 100644 index 00000000..b63921bf --- /dev/null +++ b/mp/src/game/client/replay/vgui/replayconfirmquitdlg.h @@ -0,0 +1,45 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef REPLAYBROWSER_CONFIRMQUITDLG_H
+#define REPLAYBROWSER_CONFIRMQUITDLG_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "replaybrowserbasepanel.h"
+#include "vgui/IScheme.h"
+#include "vgui_controls/CheckButton.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CExButton;
+
+class CReplayConfirmQuitDialog : public CReplayBasePanel
+{
+ DECLARE_CLASS_SIMPLE( CReplayConfirmQuitDialog, CReplayBasePanel );
+public:
+ CReplayConfirmQuitDialog( Panel *pParent );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void OnCommand( const char *pCommand );
+ virtual void OnKeyCodeTyped( vgui::KeyCode code );
+ virtual void OnKeyCodePressed( vgui::KeyCode code );
+
+private:
+ vgui::CheckButton *m_pDontShowAgain;
+ CExButton *m_pQuitButton;
+};
+
+//-----------------------------------------------------------------------------
+
+bool ReplayUI_ShowConfirmQuitDlg();
+
+//-----------------------------------------------------------------------------
+
+#endif // REPLAYBROWSER_CONFIRMQUITDLG_H
diff --git a/mp/src/game/client/replay/vgui/replayinputpanel.cpp b/mp/src/game/client/replay/vgui/replayinputpanel.cpp new file mode 100644 index 00000000..7ec78ab1 --- /dev/null +++ b/mp/src/game/client/replay/vgui/replayinputpanel.cpp @@ -0,0 +1,245 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "replayinputpanel.h"
+#include "replaybrowsermainpanel.h"
+#include "replay/replay.h"
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/TextEntry.h"
+#include "vgui/IInput.h"
+#include "vgui/ILocalize.h"
+#include "ienginevgui.h"
+#include "vgui_int.h"
+#include "vgui/ISurface.h"
+#include "iclientmode.h"
+#include "replay/ireplaymanager.h"
+#include "econ/econ_controls.h"
+
+#if defined( TF_CLIENT_DLL )
+#include "tf_item_inventory.h"
+#endif
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+
+static bool s_bPanelVisible = false;
+
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// Purpose: Player input dialog for a replay
+//-----------------------------------------------------------------------------
+class CReplayInputPanel : public EditablePanel
+{
+private:
+ DECLARE_CLASS_SIMPLE( CReplayInputPanel, EditablePanel );
+
+public:
+ CReplayInputPanel( Panel *pParent, const char *pName, ReplayHandle_t hReplay );
+ ~CReplayInputPanel();
+
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void PerformLayout();
+ virtual void OnCommand( const char *command );
+ virtual void OnKeyCodePressed( KeyCode code );
+ virtual void OnKeyCodeTyped( KeyCode code );
+
+ MESSAGE_FUNC( OnSetFocus, "SetFocus" );
+
+private:
+ Panel *m_pDlg;
+ TextEntry *m_pTitleEntry;
+ ReplayHandle_t m_hReplay;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: CReplayInputPanel implementation
+//-----------------------------------------------------------------------------
+CReplayInputPanel::CReplayInputPanel( Panel *pParent, const char *pName, ReplayHandle_t hReplay )
+: BaseClass( pParent, pName ),
+ m_hReplay( hReplay ),
+ m_pDlg( NULL ),
+ m_pTitleEntry( NULL )
+{
+ SetScheme( "ClientScheme" );
+ SetProportional( true );
+}
+
+CReplayInputPanel::~CReplayInputPanel()
+{
+}
+
+void CReplayInputPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/replayinputpanel.res", "GAME" );
+
+ // Cache off the dlg pointer
+ m_pDlg = FindChildByName( "Dlg" );
+
+ // Setup some action sigsies
+ m_pDlg->FindChildByName( "SaveButton" )->AddActionSignalTarget( this );
+ m_pDlg->FindChildByName( "CancelButton" )->AddActionSignalTarget( this );
+
+ m_pTitleEntry = static_cast< TextEntry * >( m_pDlg->FindChildByName( "TitleInput" ) );
+ m_pTitleEntry->SelectAllOnFocusAlways( true );
+ m_pTitleEntry->SetSelectionBgColor( GetSchemeColor( "Yellow", Color( 255, 255, 255, 255), pScheme ) );
+ m_pTitleEntry->SetSelectionTextColor( Color( 255, 255, 255, 255 ) );
+
+ if ( m_hReplay != REPLAY_HANDLE_INVALID )
+ {
+ CReplay *pReplay = g_pReplayManager->GetReplay( m_hReplay );
+ m_pTitleEntry->SetText( pReplay->m_wszTitle );
+ }
+}
+
+void CReplayInputPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ SetWide( ScreenWidth() );
+ SetTall( ScreenHeight() );
+
+ // Center
+ m_pDlg->SetPos( ( ScreenWidth() - m_pDlg->GetWide() ) / 2, ( ScreenHeight() - m_pDlg->GetTall() ) / 2 );
+}
+
+void CReplayInputPanel::OnKeyCodeTyped( KeyCode code )
+{
+ if ( code == KEY_ESCAPE )
+ {
+ OnCommand( "cancel" );
+ }
+
+ BaseClass::OnKeyCodeTyped( code );
+}
+
+void CReplayInputPanel::OnKeyCodePressed( KeyCode code )
+{
+ if ( code == KEY_ENTER )
+ {
+ OnCommand( "save" );
+ }
+
+ BaseClass::OnKeyCodePressed( code );
+}
+
+void CReplayInputPanel::OnSetFocus()
+{
+ m_pTitleEntry->RequestFocus();
+}
+
+void CReplayInputPanel::OnCommand( const char *command )
+{
+ bool bCloseWindow = false;
+ bool bLocalPlayerDead = false;
+ if ( !Q_strnicmp( command, "save", 4 ) )
+ {
+ if ( m_hReplay != REPLAY_HANDLE_INVALID )
+ {
+ // Store the title
+ CReplay *pReplay = g_pReplayManager->GetReplay( m_hReplay );
+ if ( pReplay )
+ {
+ m_pTitleEntry->GetText( pReplay->m_wszTitle, sizeof( pReplay->m_wszTitle ) );
+ }
+
+ // Cache to disk
+ g_pReplayManager->FlagReplayForFlush( pReplay, false );
+
+ // Add the replay to the browser
+ CReplayBrowserPanel* pReplayBrowser = ReplayUI_GetBrowserPanel();
+ if ( pReplayBrowser )
+ {
+ pReplayBrowser->OnSaveReplay( m_hReplay );
+ }
+
+ // Display a message - if we somehow disconnect, we can crash here if local player isn't checked
+ C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( pLocalPlayer )
+ {
+ g_pClientMode->DisplayReplayMessage( pLocalPlayer->IsAlive() ? "#Replay_ReplaySavedAlive" : "#Replay_ReplaySavedDead", -1.0f, false, "replay\\saved.wav", false );
+
+ // Check to see if player's dead - used later to determine if we should show items window
+ bLocalPlayerDead = !pLocalPlayer->IsAlive();
+ }
+ }
+ bCloseWindow = true;
+ }
+ else if ( !Q_strnicmp( command, "cancel", 6 ) )
+ {
+ bCloseWindow = true;
+ }
+
+ // Close the window?
+ if ( bCloseWindow )
+ {
+ s_bPanelVisible = false;
+ SetVisible( false );
+ TFModalStack()->PopModal( this );
+ MarkForDeletion();
+
+ // This logic is perhaps a smidge of a hack. We have to be careful about executing "gameui_hide"
+ // since it will hide the item pickup panel. If there are no items to be picked up, we can safely
+ // hide the gameui panel, but we have to call CheckForRoomAndForceDiscard() (as ShowItemsPickedUp()
+ // does if no items are picked up). Otherwise, skip the "gameui_hide" call and show the item pickup
+ // panel.
+#if defined( TF_CLIENT_DLL )
+ if ( TFInventoryManager()->GetNumItemPickedUpItems() == 0 )
+ {
+ TFInventoryManager()->CheckForRoomAndForceDiscard();
+ engine->ClientCmd_Unrestricted( "gameui_hide" );
+ }
+ else if ( bLocalPlayerDead )
+ {
+ // Now show the items pickup screen if player's dead
+ TFInventoryManager()->ShowItemsPickedUp();
+ }
+#endif
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool IsReplayInputPanelVisible()
+{
+ return s_bPanelVisible;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void ShowReplayInputPanel( ReplayHandle_t hReplay )
+{
+ vgui::DHANDLE< CReplayInputPanel > hReplayInputPanel;
+
+ hReplayInputPanel = vgui::SETUP_PANEL( new CReplayInputPanel( NULL, "ReplayInputPanel", hReplay ) );
+ hReplayInputPanel->SetVisible( true );
+ hReplayInputPanel->MakePopup();
+ hReplayInputPanel->MoveToFront();
+ hReplayInputPanel->SetKeyBoardInputEnabled(true);
+ hReplayInputPanel->SetMouseInputEnabled(true);
+ TFModalStack()->PushModal( hReplayInputPanel );
+ engine->ClientCmd_Unrestricted( "gameui_hide" );
+ s_bPanelVisible = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Test the replay input dialog
+//-----------------------------------------------------------------------------
+CON_COMMAND_F( open_replayinputpanel, "Open replay input panel test", FCVAR_NONE )
+{
+ ShowReplayInputPanel( NULL );
+}
+
+#endif
diff --git a/mp/src/game/client/replay/vgui/replayinputpanel.h b/mp/src/game/client/replay/vgui/replayinputpanel.h new file mode 100644 index 00000000..2143e9a8 --- /dev/null +++ b/mp/src/game/client/replay/vgui/replayinputpanel.h @@ -0,0 +1,25 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#ifndef REPLAY_INPUT_PANEL_H
+#define REPLAY_INPUT_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+//-----------------------------------------------------------------------------
+
+#include "replay/replayhandle.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Show Replay input panel for entering a title, etc.
+//-----------------------------------------------------------------------------
+void ShowReplayInputPanel( ReplayHandle_t hReplay );
+
+//-----------------------------------------------------------------------------
+// Purpose: Is the panel visible?
+//-----------------------------------------------------------------------------
+bool IsReplayInputPanelVisible();
+
+#endif // REPLAY_INPUT_PANEL_H
diff --git a/mp/src/game/client/replay/vgui/replaymessagepanel.cpp b/mp/src/game/client/replay/vgui/replaymessagepanel.cpp new file mode 100644 index 00000000..a03623c9 --- /dev/null +++ b/mp/src/game/client/replay/vgui/replaymessagepanel.cpp @@ -0,0 +1,375 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//----------------------------------------------------------------------------------------
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "replaymessagepanel.h"
+#include "vgui_controls/CheckButton.h"
+#include "ienginevgui.h"
+#include "vgui_controls/PHandle.h"
+#include "econ/econ_controls.h"
+#if defined( CSTRIKE_DLL )
+# include "cstrike/clientmode_csnormal.h"
+#elif defined( TF_CLIENT_DLL )
+# include "tf/clientmode_tf.h"
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+
+#if _DEBUG
+CON_COMMAND( testreplaymessagepanel, "" )
+{
+ CReplayMessagePanel *pPanel = new CReplayMessagePanel( "#Replay_StartRecord", replay_msgduration_misc.GetFloat(), rand()%2==0);
+ pPanel->Show();
+}
+
+CON_COMMAND( testreplaymessagedlg, "" )
+{
+ CReplayMessageDlg *pPanel = SETUP_PANEL( new CReplayMessageDlg( "text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text." ) );
+ pPanel->SetVisible( true );
+ pPanel->MakePopup();
+ pPanel->MoveToFront();
+ pPanel->SetKeyBoardInputEnabled( true );
+ pPanel->SetMouseInputEnabled( true );
+ pPanel->RequestFocus();
+ engine->ClientCmd_Unrestricted( "gameui_hide" );
+}
+#endif
+
+//-----------------------------------------------------------------------------
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+
+typedef vgui::DHANDLE< CReplayMessagePanel > ReplayMessagePanelHandle_t;
+static CUtlVector< ReplayMessagePanelHandle_t > g_vecReplayMessagePanels;
+
+//-----------------------------------------------------------------------------
+
+ConVar replay_msgduration_startrecord( "replay_msgduration_startrecord", "6", FCVAR_DONTRECORD, "Duration for start record message.", true, 0.0f, true, 10.0f );
+ConVar replay_msgduration_stoprecord( "replay_msgduration_stoprecord", "6", FCVAR_DONTRECORD, "Duration for stop record message.", true, 0.0f, true, 10.0f );
+ConVar replay_msgduration_replaysavailable( "replay_msgduration_replaysavailable", "6", FCVAR_DONTRECORD, "Duration for replays available message.", true, 0.0f, true, 10.0f );
+ConVar replay_msgduration_error( "replay_msgduration_error", "6", FCVAR_DONTRECORD, "Duration for replays available message.", true, 0.0f, true, 10.0f );
+ConVar replay_msgduration_misc( "replay_msgduration_misc", "5", FCVAR_DONTRECORD, "Duration for misc replays messages (server errors and such).", true, 0.0f, true, 10.0f );
+ConVar replay_msgduration_connectrecording( "replay_msgduration_connectrecording", "8", FCVAR_DONTRECORD, "Duration for the message that pops up when you connect to a server already recording replays.", true, 0.0f, true, 15.0f );
+
+//-----------------------------------------------------------------------------
+
+CReplayMessageDlg::CReplayMessageDlg( const char *pText )
+: BaseClass( NULL, "ReplayMessageDlg" ),
+ m_pOKButton( NULL ),
+ m_pDlg( NULL ),
+ m_pMsgLabel( NULL )
+{
+ InvalidateLayout( true, true );
+
+ m_pMsgLabel->SetText( pText );
+}
+
+CReplayMessageDlg::~CReplayMessageDlg()
+{
+}
+
+void CReplayMessageDlg::ApplySchemeSettings( IScheme *pScheme )
+{
+ // Link in TF scheme
+ extern IEngineVGui *enginevgui;
+ vgui::HScheme pTFScheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme" );
+ SetScheme( pTFScheme );
+ SetProportional( true );
+
+ BaseClass::ApplySchemeSettings( vgui::scheme()->GetIScheme( pTFScheme ) );
+
+ LoadControlSettings( "resource/ui/replaymessagedlg.res", "GAME" );
+
+ m_pDlg = FindChildByName( "Dlg" );
+
+ m_pOKButton = dynamic_cast< CExButton * >( m_pDlg->FindChildByName( "OKButton" ) );
+ m_pMsgLabel = dynamic_cast< CExLabel * >( m_pDlg->FindChildByName( "TextLabel") );
+
+ m_pOKButton->AddActionSignalTarget( this );
+}
+
+void CReplayMessageDlg::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ SetWide( ScreenWidth() );
+ SetTall( ScreenHeight() );
+
+ // Center dlg on screen
+ m_pDlg->SetPos( ( ScreenWidth() - m_pDlg->GetWide() ) / 2, ( ScreenHeight() - m_pDlg->GetTall() ) / 2 );
+
+ // Position OK below text label, centered horizontally
+ int nButtonX = XRES(13);
+ int nButtonY = m_pDlg->GetTall() - m_pOKButton->GetTall() - YRES( 10 );
+ m_pOKButton->SetPos( nButtonX, nButtonY );
+}
+
+void CReplayMessageDlg::Close()
+{
+ // Hide / delete / hide game UI
+ SetVisible( false );
+ MarkForDeletion();
+ engine->ClientCmd_Unrestricted( "gameui_hide" );
+}
+
+void CReplayMessageDlg::OnCommand( const char *pCommand )
+{
+ if ( FStrEq( pCommand, "close" ) )
+ {
+ Close();
+ }
+
+ BaseClass::OnCommand( pCommand );
+}
+
+void CReplayMessageDlg::OnKeyCodeTyped( KeyCode nCode )
+{
+ switch ( nCode )
+ {
+ case KEY_ESCAPE:
+ case KEY_SPACE:
+ case KEY_ENTER:
+ Close();
+ return;
+ }
+
+ BaseClass::OnKeyCodeTyped( nCode );
+}
+
+//-----------------------------------------------------------------------------
+
+int CReplayMessagePanel::InstanceCount()
+{
+ return g_vecReplayMessagePanels.Count();
+}
+
+void CReplayMessagePanel::RemoveAll()
+{
+ FOR_EACH_VEC( g_vecReplayMessagePanels, i )
+ {
+ CReplayMessagePanel *pCurPanel = g_vecReplayMessagePanels[ i ];
+ pCurPanel->MarkForDeletion();
+ }
+
+ g_vecReplayMessagePanels.RemoveAll();
+}
+
+//-----------------------------------------------------------------------------
+
+ReplayMessagePanelHandle_t GetReplayMessagePanelHandle( CReplayMessagePanel *pPanel )
+{
+ ReplayMessagePanelHandle_t hThis;
+ hThis = pPanel;
+ return hThis;
+}
+
+CReplayMessagePanel::CReplayMessagePanel( const char *pLocalizeName, float flDuration, bool bUrgent )
+: EditablePanel( g_pClientMode->GetViewport(), "ReplayMessagePanel" ),
+ m_bUrgent( bUrgent )
+{
+ m_flShowStartTime = 0;
+ m_flShowDuration = flDuration;
+
+ m_pMessageLabel = new CExLabel( this, "MessageLabel", pLocalizeName );
+ m_pReplayLabel = new CExLabel( this, "ReplayLabel", "" );
+ m_pIcon = new ImagePanel( this, "Icon" );
+
+#if defined( TF_CLIENT_DLL )
+ const char *pBorderName = bUrgent ? "ReplayFatLineBorderRedBGOpaque" : "ReplayFatLineBorderOpaque";
+ V_strncpy( m_szBorderName, pBorderName, sizeof( m_szBorderName ) );
+#endif
+
+ g_vecReplayMessagePanels.AddToTail( GetReplayMessagePanelHandle( const_cast< CReplayMessagePanel * >( this ) ) );
+
+ InvalidateLayout( true, true );
+
+ ivgui()->AddTickSignal( GetVPanel(), 10 );
+}
+
+CReplayMessagePanel::~CReplayMessagePanel()
+{
+ // CUtlVector<>::Find() vomits.
+ int iFind = g_vecReplayMessagePanels.InvalidIndex();
+ FOR_EACH_VEC( g_vecReplayMessagePanels, i )
+ {
+ if ( g_vecReplayMessagePanels[ i ].Get() == this )
+ {
+ iFind = i;
+ }
+ }
+
+ // Remove, if found.
+ if ( iFind != g_vecReplayMessagePanels.InvalidIndex() )
+ {
+ g_vecReplayMessagePanels.FastRemove( iFind );
+ }
+
+ ivgui()->RemoveTickSignal( GetVPanel() );
+}
+
+void CReplayMessagePanel::Show()
+{
+ m_pMessageLabel->SetVisible( true );
+
+ // Setup start time
+ m_flShowStartTime = gpGlobals->curtime;
+
+ m_pMessageLabel->MoveToFront();
+
+ SetAlpha( 0 );
+}
+
+inline float LerpScale( float flIn, float flInMin, float flInMax, float flOutMin, float flOutMax )
+{
+ float flDenom = flInMax - flInMin;
+ if ( flDenom == 0.0f )
+ return 0.0f;
+
+ float t = clamp( ( flIn - flInMin ) / flDenom, 0.0f, 1.0f );
+ return Lerp( t, flOutMin, flOutMax );
+}
+
+inline float SCurve( float t )
+{
+ t = clamp( t, 0.0f, 1.0f );
+ return t * t * (3 - 2*t);
+}
+
+void CReplayMessagePanel::OnTick()
+{
+ // Hide if taking screenshot
+ extern ConVar hud_freezecamhide;
+ extern bool IsTakingAFreezecamScreenshot();
+ if ( hud_freezecamhide.GetBool() && IsTakingAFreezecamScreenshot() )
+ {
+ SetVisible( false );
+ return;
+ }
+
+ // Delete the panel if life exceeded
+ const float flEndTime = m_flShowStartTime + m_flShowDuration;
+ if ( gpGlobals->curtime >= flEndTime )
+ {
+ SetVisible( false );
+ MarkForDeletion();
+ return;
+ }
+
+ SetVisible( true );
+
+ const float flFadeDuration = .4f;
+ float flAlpha;
+
+ // Fade out?
+ if ( gpGlobals->curtime >= flEndTime - flFadeDuration )
+ {
+ flAlpha = LerpScale( gpGlobals->curtime, flEndTime - flFadeDuration, flEndTime, 1.0f, 0.0f );
+ }
+
+ // Fade in?
+ else if ( gpGlobals->curtime <= m_flShowStartTime + flFadeDuration )
+ {
+ flAlpha = LerpScale( gpGlobals->curtime, m_flShowStartTime, m_flShowStartTime + flFadeDuration, 0.0f, 1.0f );
+ }
+
+ // Otherwise, we must be in between fade in/fade out
+ else
+ {
+ flAlpha = 1.0f;
+ }
+
+ SetAlpha( 255 * SCurve( flAlpha ) );
+}
+
+void CReplayMessagePanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/replaymessage.res", "GAME" );
+
+#if defined( CSTRIKE_DLL )
+ SetPaintBackgroundEnabled( true );
+ SetPaintBorderEnabled( false );
+ SetPaintBackgroundType( 0 );
+ SetBgColor( pScheme->GetColor( m_bUrgent ? "DarkRed" : "DarkGray", Color( 255, 255, 255, 255 ) ) );
+#endif
+}
+
+void CReplayMessagePanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+#if defined( TF_CLIENT_DLL )
+ // Set the border if one was specified
+ if ( m_szBorderName[0] )
+ {
+ SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( m_szBorderName ) );
+ }
+#endif
+
+ // Adjust overall panel size depending on min-mode
+#if defined( TF_CLIENT_DLL )
+ extern ConVar cl_hud_minmode;
+ bool bMinMode = cl_hud_minmode.GetBool();
+#else
+ bool bMinMode = false;
+#endif
+ int nVerticalBuffer = bMinMode ? YRES(3) : YRES(5);
+ int nMessageLabelY = nVerticalBuffer;
+ int nVerticalOffsetBetweenPanels = YRES(6);
+
+ // Only display replay icon and "replay" label if this is the top-most (vertically) panel
+ // and we're not in min-mode
+ Assert( InstanceCount() > 0 );
+ if ( !InstanceCount() || bMinMode || g_vecReplayMessagePanels[ 0 ].Get() != this )
+ {
+ m_pIcon->SetTall( 0 );
+ m_pReplayLabel->SetTall( 0 );
+ nVerticalOffsetBetweenPanels = YRES(1);
+ }
+ else
+ {
+ m_pReplayLabel->SizeToContents();
+ nMessageLabelY += m_pReplayLabel->GetTall();
+ nVerticalOffsetBetweenPanels = YRES(6);
+ }
+
+ // Resize the message label to fit the text
+ m_pMessageLabel->SizeToContents();
+
+ // Adjust this panel's height to fit the label size
+ SetTall( nMessageLabelY + m_pMessageLabel->GetTall() + nVerticalBuffer );
+
+ // Set the message label's position
+ m_pMessageLabel->SetPos( XRES(8), nMessageLabelY );
+
+ // Get the bottom of the bottom-most message panel
+ int nMaxY = 0;
+ FOR_EACH_VEC( g_vecReplayMessagePanels, it )
+ {
+ CReplayMessagePanel *pPanel = g_vecReplayMessagePanels[ it ];
+ if ( pPanel == this )
+ continue;
+
+ int nX, nY;
+ pPanel->GetPos( nX, nY );
+ nMaxY = MAX( nMaxY, pPanel->GetTall() + nY );
+ }
+
+ // Adjust this panel's position to be below bottom-most panel
+ // NOTE: Intentionally using YRES() for xpos, since we want to match offsets in both x & y margins
+ SetPos( YRES(6), nMaxY + nVerticalOffsetBetweenPanels );
+}
+
+//-----------------------------------------------------------------------------
+
+#endif
\ No newline at end of file diff --git a/mp/src/game/client/replay/vgui/replaymessagepanel.h b/mp/src/game/client/replay/vgui/replaymessagepanel.h new file mode 100644 index 00000000..1d36c19e --- /dev/null +++ b/mp/src/game/client/replay/vgui/replaymessagepanel.h @@ -0,0 +1,85 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//----------------------------------------------------------------------------------------
+
+#ifndef REPLAYMESSAGEPANEL_H
+#define REPLAYMESSAGEPANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_controls/EditablePanel.h"
+
+using namespace vgui;
+
+//----------------------------------------------------------------------------------------
+// Purpose:
+//----------------------------------------------------------------------------------------
+extern ConVar replay_msgduration_startrecord;
+extern ConVar replay_msgduration_stoprecord;
+extern ConVar replay_msgduration_replaysavailable;
+extern ConVar replay_msgduration_error;
+extern ConVar replay_msgduration_misc;
+extern ConVar replay_msgduration_connectrecording;
+
+//----------------------------------------------------------------------------------------
+// Purpose: Forward declarations
+//----------------------------------------------------------------------------------------
+class CExLabel;
+class CExButton;
+
+class CReplayMessageDlg : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CReplayMessageDlg, EditablePanel );
+public:
+ CReplayMessageDlg( const char *pText );
+ ~CReplayMessageDlg();
+
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void PerformLayout();
+
+ virtual void OnKeyCodeTyped( KeyCode nCode );
+ virtual void OnCommand( const char *pCommand );
+
+private:
+ void Close();
+
+ Panel *m_pDlg;
+ CExLabel *m_pMsgLabel;
+ CExButton *m_pOKButton;
+};
+
+//----------------------------------------------------------------------------------------
+// Purpose: A panel for display messages from the replay system during gameplay
+//----------------------------------------------------------------------------------------
+class CReplayMessagePanel : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CReplayMessagePanel, EditablePanel );
+public:
+ CReplayMessagePanel( const char *pLocalizeName, float flDuration, bool bUrgent );
+ virtual ~CReplayMessagePanel();
+
+ void Show();
+ virtual void OnTick();
+
+ static int InstanceCount();
+ static void RemoveAll();
+
+private:
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void PerformLayout();
+
+
+ CExLabel *m_pMessageLabel;
+ CExLabel *m_pReplayLabel;
+ ImagePanel *m_pIcon;
+ float m_flShowStartTime;
+ float m_flShowDuration;
+ bool m_bUrgent;
+
+#if defined( TF_CLIENT_DLL )
+ char m_szBorderName[ 64 ];
+#endif
+};
+
+#endif // REPLAYMESSAGEPANEL_H
\ No newline at end of file diff --git a/mp/src/game/client/replay/vgui/replayperformanceeditor.cpp b/mp/src/game/client/replay/vgui/replayperformanceeditor.cpp new file mode 100644 index 00000000..071d1320 --- /dev/null +++ b/mp/src/game/client/replay/vgui/replayperformanceeditor.cpp @@ -0,0 +1,2658 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "replayperformanceeditor.h"
+#include "replay/replay.h"
+#include "replay/ireplayperformanceeditor.h"
+#include "replay/ireplayperformancecontroller.h"
+#include "replay/performance.h"
+#include "ienginevgui.h"
+#include "iclientmode.h"
+#include "vgui_controls/ImagePanel.h"
+#include "vgui_controls/TextImage.h"
+#include "vgui_controls/Slider.h"
+#include "vgui_controls/Menu.h"
+#include "vgui/ILocalize.h"
+#include "vgui/IImage.h"
+#include "c_team.h"
+#include "vgui_avatarimage.h"
+#include "vgui/ISurface.h"
+#include "vgui/IInput.h"
+#include "replay/replaycamera.h"
+#include "replay/ireplaymanager.h"
+#include "replay/iclientreplaycontext.h"
+#include "confirm_dialog.h"
+#include "replayperformancesavedlg.h"
+#include "replay/irecordingsessionmanager.h"
+#include "achievementmgr.h"
+#include "c_playerresource.h"
+#include "replay/gamedefs.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+extern CAchievementMgr g_AchievementMgrTF;
+
+//-----------------------------------------------------------------------------
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+
+extern IReplayPerformanceController *g_pReplayPerformanceController;
+
+//-----------------------------------------------------------------------------
+
+// Hack-y global bool to communicate when we are rewinding for map load screens.
+// Order of operations issues preclude the use of engine->IsPlayingDemo().
+bool g_bIsReplayRewinding = false;
+
+//-----------------------------------------------------------------------------
+
+// TODO: Make these archive? Right now, the tips are reset every time the game starts
+ConVar replay_perftip_count_enter( "replay_perftip_count_enter", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_HIDDEN, "", true, 0, false, 0 );
+ConVar replay_perftip_count_exit( "replay_perftip_count_exit", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_HIDDEN, "", true, 0, false, 0 );
+ConVar replay_perftip_count_freecam_enter( "replay_perftip_count_freecam_enter", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_HIDDEN, "", true, 0, false, 0 );
+ConVar replay_perftip_count_freecam_exit( "replay_perftip_count_freecam_exit", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_HIDDEN, "", true, 0, false, 0 );
+ConVar replay_perftip_count_freecam_exit2( "replay_perftip_count_freecam_exit2", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_HIDDEN, "", true, 0, false, 0 );
+
+ConVar replay_editor_fov_mousewheel_multiplier( "replay_editor_fov_mousewheel_multiplier", "5", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_DONTRECORD, "The multiplier on mousewheel input for adjusting camera FOV in the replay editor." );
+ConVar replay_editor_fov_mousewheel_invert( "replay_editor_fov_mousewheel_invert", "0", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_DONTRECORD, "Invert FOV zoom/unzoom on mousewheel in the replay editor." );
+
+ConVar replay_replayeditor_rewindmsgcounter( "replay_replayeditor_rewindmsgcounter", "0", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_HIDDEN, "" );
+
+//-----------------------------------------------------------------------------
+
+#define MAX_TIP_DISPLAYS 1
+
+//-----------------------------------------------------------------------------
+
+#define TIMESCALE_MIN 0.01f
+#define TIMESCALE_MAX 3.0f
+
+//-----------------------------------------------------------------------------
+
+#define SLIDER_RANGE_MAX 10000.0f
+
+//-----------------------------------------------------------------------------
+
+#define REPLAY_SOUND_DIALOG_POPUP "replay\\replaydialog_warn.wav"
+
+//-----------------------------------------------------------------------------
+
+static const char *gs_pCamNames[ NCAMS ] =
+{
+ "free",
+ "third",
+ "first",
+ "timescale",
+};
+
+static const char *gs_pBaseComponentNames[ NCAMS ] =
+{
+ "replay/replay_camera_%s%s",
+ "replay/replay_camera_%s%s",
+ "replay/replay_camera_%s%s",
+ "replay/replay_%s%s",
+};
+
+//-----------------------------------------------------------------------------
+
+void PlayDemo()
+{
+ engine->ClientCmd_Unrestricted( "demo_resume" );
+}
+
+void PauseDemo()
+{
+ engine->ClientCmd_Unrestricted( "demo_pause" );
+}
+
+//-----------------------------------------------------------------------------
+
+inline float SCurve( float t )
+{
+ t = clamp( t, 0.0f, 1.0f );
+ return t * t * (3 - 2*t);
+}
+
+inline float CubicEaseIn( float t )
+{
+ t = clamp( t, 0.0f, 1.0f );
+ return t * t * t;
+}
+
+inline float LerpScale( float flIn, float flInMin, float flInMax, float flOutMin, float flOutMax )
+{
+ float flDenom = flInMax - flInMin;
+ if ( flDenom == 0.0f )
+ return 0.0f;
+
+ float t = clamp( ( flIn - flInMin ) / flDenom, 0.0f, 1.0f );
+ return Lerp( t, flOutMin, flOutMax );
+}
+
+//-----------------------------------------------------------------------------
+
+void HighlightTipWords( Label *pLabel )
+{
+ // Setup coloring - get # of words that should be highlighted
+ wchar_t *pwNumWords = g_pVGuiLocalize->Find( "#Replay_PerfTip_Highlight_NumWords" );
+ if ( !pwNumWords )
+ return;
+
+ // Get the current label text
+ wchar_t wszLabelText[512];
+ pLabel->GetText( wszLabelText, sizeof( wszLabelText ) );
+
+ pLabel->GetTextImage()->ClearColorChangeStream();
+ pLabel->GetTextImage()->AddColorChange( pLabel->GetFgColor(), 0 );
+
+ int nNumWords = _wtoi( pwNumWords );
+ for ( int i = 0; i < nNumWords; ++i )
+ {
+ char szWordFindStr[64];
+ V_snprintf( szWordFindStr, sizeof( szWordFindStr ), "#Replay_PerfTip_Highlight_Word%i", i );
+ wchar_t *pwWord = g_pVGuiLocalize->Find( szWordFindStr );
+ if ( !pwWord )
+ continue;
+
+ const int nWordLen = wcslen( pwWord );
+
+ // Find any instance of the word in the label text and highlight it in red
+ const wchar_t *p = wszLabelText;
+ do
+ {
+ const wchar_t *pInst = wcsstr( p, pwWord );
+ if ( !pInst )
+ break;
+
+ // Highlight the text
+ int nStartPos = pInst - wszLabelText;
+ int nEndPos = nStartPos + nWordLen;
+
+ // If start pos is non-zero, clear color changes
+ bool bChangeColor = true;
+ if ( nStartPos == 0 )
+ {
+ pLabel->GetTextImage()->ClearColorChangeStream();
+ }
+ else if ( iswalpha( wszLabelText[ nStartPos - 1 ] ) )
+ {
+ // If this is not the beginning of the string, check the previous character. If it's
+ // not whitespace, etc, we found an instance of a keyword within another word. Skip.
+ bChangeColor = false;
+ }
+
+ if ( bChangeColor )
+ {
+ pLabel->GetTextImage()->AddColorChange( Color(200,80,60,255), nStartPos );
+ pLabel->GetTextImage()->AddColorChange( pLabel->GetFgColor(), nEndPos );
+ }
+
+ p = pInst + nWordLen;
+ } while ( 1 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+class CSavingDialog : public CGenericWaitingDialog
+{
+ DECLARE_CLASS_SIMPLE( CSavingDialog, CGenericWaitingDialog );
+public:
+ CSavingDialog( CReplayPerformanceEditorPanel *pEditorPanel )
+ : CGenericWaitingDialog( pEditorPanel )
+ {
+ m_pEditorPanel = pEditorPanel;
+ }
+
+ virtual void OnTick()
+ {
+ BaseClass::OnTick();
+
+ if ( !g_pReplayPerformanceController )
+ return;
+
+ // Update async save
+ if ( g_pReplayPerformanceController->IsSaving() )
+ {
+ g_pReplayPerformanceController->SaveThink();
+ }
+ else
+ {
+ if ( m_pEditorPanel.Get() )
+ {
+ m_pEditorPanel->OnSaveComplete();
+ }
+
+ Close();
+ }
+ }
+
+private:
+ CConfirmDialog *m_pLoginDialog;
+ vgui::DHANDLE< CReplayPerformanceEditorPanel > m_pEditorPanel;
+};
+
+//-----------------------------------------------------------------------------
+
+class CReplayTipLabel : public Label
+{
+ DECLARE_CLASS_SIMPLE( CReplayTipLabel, Label );
+public:
+ CReplayTipLabel( Panel *pParent, const char *pName, const char *pText )
+ : BaseClass( pParent, pName, pText )
+ {
+ }
+
+ virtual void ApplySchemeSettings( IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+ HighlightTipWords( this );
+ }
+};
+
+DECLARE_BUILD_FACTORY_DEFAULT_TEXT( CReplayTipLabel, Label );
+
+//-----------------------------------------------------------------------------
+
+class CPerformanceTip : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CPerformanceTip, EditablePanel );
+public:
+ static DHANDLE< CPerformanceTip > s_pTip;
+
+ static CPerformanceTip *CreateInstance( const char *pText )
+ {
+ if ( s_pTip )
+ {
+ s_pTip->SetVisible( false );
+ s_pTip->MarkForDeletion();
+ s_pTip = NULL;
+ }
+
+ s_pTip = SETUP_PANEL( new CPerformanceTip( pText ) );
+
+ return s_pTip;
+ }
+
+ CPerformanceTip( const char *pText )
+ : BaseClass( g_pClientMode->GetViewport(), "Tip" ),
+ m_flBornTime( gpGlobals->realtime ),
+ m_flAge( 0.0f ),
+ m_flShowDuration( 15.0f )
+ {
+ m_pTextLabel = new CReplayTipLabel( this, "TextLabel", pText );
+ }
+
+ virtual void OnThink()
+ {
+ // Delete the panel if life exceeded
+ const float flEndTime = m_flBornTime + m_flShowDuration;
+ if ( gpGlobals->realtime >= flEndTime )
+ {
+ SetVisible( false );
+ MarkForDeletion();
+ s_pTip = NULL;
+ return;
+ }
+
+ SetVisible( true );
+
+ const float flFadeDuration = .4f;
+ float flAlpha;
+
+ // Fade out?
+ if ( gpGlobals->realtime >= flEndTime - flFadeDuration )
+ {
+ flAlpha = LerpScale( gpGlobals->realtime, flEndTime - flFadeDuration, flEndTime, 1.0f, 0.0f );
+ }
+
+ // Fade in?
+ else if ( gpGlobals->realtime <= m_flBornTime + flFadeDuration )
+ {
+ flAlpha = LerpScale( gpGlobals->realtime, m_flBornTime, m_flBornTime + flFadeDuration, 0.0f, 1.0f );
+ }
+
+ // Otherwise, we must be in between fade in/fade out
+ else
+ {
+ flAlpha = 1.0f;
+ }
+
+ SetAlpha( 255 * SCurve( flAlpha ) );
+ }
+
+ virtual void ApplySchemeSettings( IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/replayperformanceeditor/tip.res", "GAME" );
+
+ // Center relative to parent
+ const int nScreenW = ScreenWidth();
+ const int nScreenH = ScreenHeight();
+ int aContentSize[2];
+ m_pTextLabel->GetContentSize( aContentSize[0], aContentSize[1] );
+ const int nLabelHeight = aContentSize[1];
+ SetBounds(
+ 0,
+ 3 * nScreenH / 4 - nLabelHeight / 2,
+ nScreenW,
+ nLabelHeight + 2 * m_nTopBottomMargin
+ );
+ m_pTextLabel->SetBounds(
+ m_nLeftRightMarginWidth,
+ m_nTopBottomMargin,
+ nScreenW - 2 * m_nLeftRightMarginWidth,
+ nLabelHeight
+ );
+ }
+
+ static void Cleanup()
+ {
+ if ( s_pTip )
+ {
+ s_pTip->MarkForDeletion();
+ s_pTip = NULL;
+ }
+ }
+
+ CPanelAnimationVarAliasType( int, m_nLeftRightMarginWidth, "left_right_margin", "0", "proportional_xpos" );
+ CPanelAnimationVarAliasType( int, m_nTopBottomMargin , "top_bottom_margin", "0", "proportional_ypos" );
+
+ CReplayTipLabel *m_pTextLabel;
+ float m_flBornTime;
+ float m_flAge;
+ float m_flShowDuration;
+};
+
+DHANDLE< CPerformanceTip > CPerformanceTip::s_pTip;
+
+// Display the performance tip if we haven't already displayed it nMaxTimesToDisplay times or more
+inline void DisplayPerformanceTip( const char *pText, ConVar* pCountCv = NULL, int nMaxTimesToDisplay = -1 )
+{
+ // Already displayed too many times? Get out.
+ if ( pCountCv && nMaxTimesToDisplay >= 0 )
+ {
+ int nCount = pCountCv->GetInt();
+ if ( nCount >= nMaxTimesToDisplay )
+ return;
+
+ // Incremement count cvar
+ pCountCv->SetValue( nCount + 1 );
+ }
+
+ // Display the tip
+ CPerformanceTip::CreateInstance( pText );
+}
+
+//-----------------------------------------------------------------------------
+
+inline float GetPlaybackTime()
+{
+ CReplay *pPlayingReplay = g_pReplayManager->GetPlayingReplay();
+ return gpGlobals->curtime - TICKS_TO_TIME( pPlayingReplay->m_nSpawnTick );
+}
+
+//-----------------------------------------------------------------------------
+
+class CPlayerCell : public CExImageButton
+{
+ DECLARE_CLASS_SIMPLE( CPlayerCell, CExImageButton );
+public:
+ CPlayerCell( Panel *pParent, const char *pName, int *pCurTargetPlayerIndex )
+ : CExImageButton( pParent, pName, "" ),
+ m_iPlayerIndex( -1 ),
+ m_pCurTargetPlayerIndex( pCurTargetPlayerIndex )
+ {
+ }
+
+ virtual void ApplySchemeSettings( IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ GetImage()->SetImage( "" );
+ SetFont( pScheme->GetFont( "ReplaySmall" ) );
+ SetContentAlignment( Label::a_center );
+ }
+
+ MESSAGE_FUNC( DoClick, "PressButton" )
+ {
+ ReplayCamera()->SetPrimaryTarget( m_iPlayerIndex );
+ *m_pCurTargetPlayerIndex = m_iPlayerIndex;
+
+ float flCurTime = GetPlaybackTime();
+
+ extern IReplayPerformanceController *g_pReplayPerformanceController;
+ g_pReplayPerformanceController->AddEvent_Camera_ChangePlayer( flCurTime, m_iPlayerIndex );
+ }
+
+ int m_iPlayerIndex;
+ int *m_pCurTargetPlayerIndex; // Allow the button to write current target in outer class when pressed
+};
+
+//-----------------------------------------------------------------------------
+
+/*
+class CReplayEditorSlider : public Slider
+{
+ DECLARE_CLASS_SIMPLE( CReplayEditorSlider, Slider );
+public:
+ CReplayEditorSlider( Panel *pParent, const char *pName )
+ : Slider( pParent, pName )
+ {
+ }
+
+ virtual void SetDefault( float flDefault ) { m_flDefault = flDefault; }
+
+ ON_MESSAGE( Reset, OnReset )
+ {
+ SetValue(
+ }
+
+private:
+ float m_flDefault;
+};
+*/
+
+//-----------------------------------------------------------------------------
+
+class CCameraOptionsPanel : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CCameraOptionsPanel, EditablePanel );
+public:
+ CCameraOptionsPanel( Panel *pParent, const char *pName, const char *pTitle )
+ : EditablePanel( pParent, pName ),
+ m_bControlsAdded( false )
+ {
+ m_pTitleLabel = new CExLabel( this, "TitleLabel", pTitle );
+
+ AddControlToLayout( m_pTitleLabel );
+ }
+
+ ~CCameraOptionsPanel()
+ {
+ m_lstSliderInfos.PurgeAndDeleteElements();
+ }
+
+ void AddControlToLayout( Panel *pControl )
+ {
+ if ( pControl )
+ {
+ m_lstControls.AddToTail( pControl );
+ pControl->SetMouseInputEnabled( true );
+ }
+ }
+
+ // NOTE: Default value is assumed to be stored in flOut
+ void AddSliderToLayout( int nId, Slider *pSlider, const char *pLabelText,
+ float flMinValue, float flMaxValue, float &flOut )
+ {
+ SliderInfo_t *pNewSliderInfo = new SliderInfo_t;
+
+ pNewSliderInfo->m_nId = nId;
+ pNewSliderInfo->m_pSlider = pSlider;
+ pNewSliderInfo->m_flRange[ 0 ] = flMinValue;
+ pNewSliderInfo->m_flRange[ 1 ] = flMaxValue;
+ pNewSliderInfo->m_flDefault = flOut;
+ pNewSliderInfo->m_pValueOut = &flOut;
+
+ m_lstSliderInfos.AddToTail( pNewSliderInfo );
+
+ AddControlToLayout( new EditablePanel( this, "Buffer" ) );
+ AddControlToLayout( NewLabel( pLabelText ) );
+ AddControlToLayout( NewSetDefaultButton( nId ) );
+ AddControlToLayout( pSlider );
+
+ pSlider->AddActionSignalTarget( this );
+ }
+
+ void ResetSlider( int nId )
+ {
+ const SliderInfo_t *pSliderInfo = FindSliderInfoFromId( nId );
+ if ( !pSliderInfo )
+ return;
+
+ SetValue( pSliderInfo, pSliderInfo->m_flDefault );
+ }
+
+ void SetValue( int nId, float flValue )
+ {
+ const SliderInfo_t *pSliderInfo = FindSliderInfoFromId( nId );
+ if ( !pSliderInfo )
+ return;
+
+ SetValue( pSliderInfo, flValue );
+ }
+
+ virtual void ApplySchemeSettings( IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ // Setup border
+ SetBorder( pScheme->GetBorder( "ButtonBorder" ) );
+
+ HFont hFont = pScheme->GetFont( "ReplayBrowserSmallest", true );
+ m_pTitleLabel->SetFont( hFont );
+ m_pTitleLabel->SizeToContents();
+ m_pTitleLabel->SetTall( YRES( 20 ) );
+ m_pTitleLabel->SetColorStr( "235 235 235 255" );
+
+ if ( !m_bControlsAdded )
+ {
+ const char *pResFile = GetResFile();
+ if ( pResFile )
+ {
+ LoadControlSettings( pResFile, "GAME" );
+ }
+
+ AddControls();
+ m_bControlsAdded = true;
+ }
+
+ FOR_EACH_LL( m_lstSliderInfos, it )
+ {
+ SliderInfo_t *pInfo = m_lstSliderInfos[ it ];
+ Slider *pSlider = pInfo->m_pSlider;
+ pSlider->SetRange( 0, SLIDER_RANGE_MAX );
+ pSlider->SetNumTicks( 10 );
+ float flDenom = fabs( pInfo->m_flRange[1] - pInfo->m_flRange[0] );
+ pSlider->SetValue( SLIDER_RANGE_MAX * fabs( pInfo->m_flDefault - pInfo->m_flRange[0] ) / flDenom );
+ }
+ }
+
+ virtual void PerformLayout()
+ {
+ BaseClass::PerformLayout();
+
+ int nWidth = XRES( 140 );
+ int nMargins[2] = { XRES( 5 ), YRES( 5 ) };
+ int nVBuf = YRES( 0 );
+ int nLastY = -1;
+ int nY = nMargins[1];
+ Panel *pPrevPanel = NULL;
+ int nLastCtrlHeight = 0;
+
+ FOR_EACH_LL( m_lstControls, i )
+ {
+ Panel *pPanel = m_lstControls[ i ];
+ if ( !pPanel->IsVisible() )
+ continue;
+
+ int aPos[2];
+ pPanel->GetPos( aPos[0], aPos[1] );
+
+ if ( pPrevPanel && aPos[1] >= 0 )
+ {
+ nY += pPrevPanel->GetTall() + nVBuf;
+ }
+
+ // Gross hack to see if the control is a default button
+ if ( dynamic_cast< CExButton * >( pPanel ) )
+ {
+ pPanel->SetWide( XRES( 36 ) );
+ pPanel->SetPos( pPrevPanel ? ( GetWide() - nMargins[0] - pPanel->GetWide() ) : 0, nLastY );
+ }
+ else
+ {
+ pPanel->SetWide( nWidth - 2 * nMargins[0] );
+ pPanel->SetPos( nMargins[0], nY );
+ }
+
+ nLastY = nY;
+ pPrevPanel = pPanel;
+ nLastCtrlHeight = MAX( nLastCtrlHeight, pPanel->GetTall() );
+ }
+
+ SetSize( nWidth, nY + nLastCtrlHeight + 2 * YRES( 3 ) );
+ }
+
+ virtual void OnCommand( const char *pCommand )
+ {
+ if ( !V_strnicmp( pCommand, "reset_", 6 ) )
+ {
+ const int nSliderInfoId = atoi( pCommand + 6 );
+ ResetSlider( nSliderInfoId );
+ }
+ else
+ {
+ BaseClass::OnCommand( pCommand );
+ }
+ }
+
+ Label *NewLabel( const char *pText )
+ {
+ Label *pLabel = new Label( this, "Label", pText );
+ pLabel->SetTall( YRES( 9 ) );
+ pLabel->SetPos( -1, 0 ); // Use default x and accumulated y
+
+ // Set font
+ IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
+ HFont hFont = pScheme->GetFont( "DefaultVerySmall", true );
+ pLabel->SetFont( hFont );
+
+ return pLabel;
+ }
+
+ CExButton *NewSetDefaultButton( int nSliderInfoId )
+ {
+ CExButton *pButton = new CExButton( this, "DefaultButton", "#Replay_SetDefaultSetting" );
+ pButton->SetTall( YRES( 11 ) );
+ pButton->SetPos( XRES( 30 ), -1 ); // Set y to -1 so it will stay on the same line
+ pButton->SetContentAlignment( Label::a_center );
+ CFmtStr fmtResetCommand( "reset_%i", nSliderInfoId );
+ pButton->SetCommand( fmtResetCommand.Access() );
+ pButton->AddActionSignalTarget( this );
+
+ // Set font
+ IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
+ HFont hFont = pScheme->GetFont( "DefaultVerySmall", true );
+ pButton->SetFont( hFont );
+
+ return pButton;
+ }
+
+protected:
+ MESSAGE_FUNC_PARAMS( OnSliderMoved, "SliderMoved", pParams )
+ {
+ Panel *pSlider = (Panel *)pParams->GetPtr( "panel" );
+ float flPercent = pParams->GetInt( "position" ) / SLIDER_RANGE_MAX;
+
+ FOR_EACH_LL( m_lstSliderInfos, it )
+ {
+ SliderInfo_t *pInfo = m_lstSliderInfos[ it ];
+ if ( pSlider == pInfo->m_pSlider )
+ {
+ *pInfo->m_pValueOut = Lerp( flPercent, pInfo->m_flRange[0], pInfo->m_flRange[1] );
+ }
+ }
+ }
+
+ virtual const char *GetResFile() { return NULL; }
+
+ virtual void AddControls()
+ {
+ }
+
+ struct SliderInfo_t
+ {
+ Slider *m_pSlider;
+ float m_flRange[2];
+ float m_flDefault;
+ int m_nId;
+ float *m_pValueOut;
+ };
+
+ const SliderInfo_t *FindSliderInfoFromId( int nId )
+ {
+ FOR_EACH_LL( m_lstSliderInfos, it )
+ {
+ SliderInfo_t *pInfo = m_lstSliderInfos[ it ];
+ if ( pInfo->m_nId == nId )
+ return pInfo;
+ }
+
+ AssertMsg( 0, "Should always find a slider here." );
+
+ return NULL;
+ }
+
+ void SetValue( const SliderInfo_t *pSliderInfo, float flValue )
+ {
+ if ( !pSliderInfo )
+ {
+ AssertMsg( 0, "This should not happen." );
+ return;
+ }
+
+ // Calculate the range
+ const float flRange = fabs( pSliderInfo->m_flRange[1] - pSliderInfo->m_flRange[0] );
+ AssertMsg( flRange > 0, "Bad slider range!" );
+
+ // Calculate the percentile based on the specified value and the range.
+ const float flPercent = fabs( flValue - pSliderInfo->m_flRange[0] ) / flRange;
+ pSliderInfo->m_pSlider->SetValue( flPercent * SLIDER_RANGE_MAX, true );
+ }
+
+ CUtlLinkedList< Panel * > m_lstControls;
+ CUtlLinkedList< SliderInfo_t *, int > m_lstSliderInfos;
+ CExLabel *m_pTitleLabel;
+ bool m_bControlsAdded;
+};
+
+//-----------------------------------------------------------------------------
+
+class CTimeScaleOptionsPanel : public CCameraOptionsPanel
+{
+ DECLARE_CLASS_SIMPLE( CTimeScaleOptionsPanel, CCameraOptionsPanel );
+public:
+ CTimeScaleOptionsPanel( Panel *pParent, float *pTimeScaleProxy )
+ : BaseClass( pParent, "TimeScaleSettings", "#Replay_TimeScale" ),
+ m_pTimeScaleSlider( NULL ),
+ m_pTimeScaleProxy( pTimeScaleProxy )
+ {
+ }
+
+ virtual const char *GetResFile()
+ {
+ return "resource/ui/replayperformanceeditor/settings_timescale.res";
+ }
+
+ virtual void AddControls()
+ {
+ m_pTimeScaleSlider = dynamic_cast< Slider * >( FindChildByName( "TimeScaleSlider" ) );
+
+ AddSliderToLayout( SLIDER_TIMESCALE, m_pTimeScaleSlider, "#Replay_Scale", TIMESCALE_MIN, TIMESCALE_MAX, *m_pTimeScaleProxy );
+ }
+
+ enum FreeCamSliders_t
+ {
+ SLIDER_TIMESCALE,
+ };
+
+ Slider *m_pTimeScaleSlider;
+ float *m_pTimeScaleProxy;
+};
+
+//-----------------------------------------------------------------------------
+class CCameraOptionsPanel_Free : public CCameraOptionsPanel
+{
+ DECLARE_CLASS_SIMPLE( CCameraOptionsPanel_Free, CCameraOptionsPanel );
+public:
+ CCameraOptionsPanel_Free( Panel *pParent )
+ : BaseClass( pParent, "FreeCameraSettings", "#Replay_FreeCam" ),
+ m_pAccelSlider( NULL ),
+ m_pSpeedSlider( NULL ),
+ m_pFovSlider( NULL ),
+ m_pRotFilterSlider( NULL ),
+ m_pShakeSpeedSlider( NULL ),
+ m_pShakeAmountSlider( NULL )
+ {
+ }
+
+ virtual const char *GetResFile()
+ {
+ return "resource/ui/replayperformanceeditor/camsettings_free.res";
+ }
+
+ virtual void AddControls()
+ {
+ m_pAccelSlider = dynamic_cast< Slider * >( FindChildByName( "AccelSlider" ) );
+ m_pSpeedSlider = dynamic_cast< Slider * >( FindChildByName( "SpeedSlider" ) );
+ m_pFovSlider = dynamic_cast< Slider * >( FindChildByName( "FovSlider" ) );
+ m_pRotFilterSlider = dynamic_cast< Slider * >( FindChildByName( "RotFilterSlider" ) );
+ m_pShakeSpeedSlider = dynamic_cast< Slider * >( FindChildByName( "ShakeSpeedSlider" ) );
+ m_pShakeAmountSlider = dynamic_cast< Slider * >( FindChildByName( "ShakeAmountSlider" ) );
+ m_pShakeDirSlider = dynamic_cast< Slider * >( FindChildByName( "ShakeDirSlider" ) );
+
+ AddSliderToLayout( SLIDER_ACCEL, m_pAccelSlider, "#Replay_Accel", FREE_CAM_ACCEL_MIN, FREE_CAM_ACCEL_MAX, ReplayCamera()->m_flRoamingAccel );
+ AddSliderToLayout( SLIDER_SPEED, m_pSpeedSlider, "#Replay_Speed", FREE_CAM_SPEED_MIN, FREE_CAM_SPEED_MAX, ReplayCamera()->m_flRoamingSpeed );
+ AddSliderToLayout( SLIDER_FOV, m_pFovSlider, "#Replay_Fov", FREE_CAM_FOV_MIN, FREE_CAM_FOV_MAX, ReplayCamera()->m_flRoamingFov[1] );
+ AddSliderToLayout( SLIDER_ROTFILTER, m_pRotFilterSlider, "#Replay_RotFilter", FREE_CAM_ROT_FILTER_MIN, FREE_CAM_ROT_FILTER_MAX, ReplayCamera()->m_flRoamingRotFilterFactor );
+ AddSliderToLayout( SLIDER_SHAKE_SPEED, m_pShakeSpeedSlider, "#Replay_ShakeSpeed", FREE_CAM_SHAKE_SPEED_MIN, FREE_CAM_SHAKE_SPEED_MAX, ReplayCamera()->m_flRoamingShakeSpeed );
+ AddSliderToLayout( SLIDER_SHAKE_AMOUNT, m_pShakeAmountSlider, "#Replay_ShakeAmount", FREE_CAM_SHAKE_AMOUNT_MIN, FREE_CAM_SHAKE_AMOUNT_MAX, ReplayCamera()->m_flRoamingShakeAmount );
+ AddSliderToLayout( SLIDER_SHAKE_DIR, m_pShakeDirSlider, "#Replay_ShakeDir", FREE_CAM_SHAKE_DIR_MIN, FREE_CAM_SHAKE_DIR_MAX, ReplayCamera()->m_flRoamingShakeDir );
+ }
+
+ enum FreeCamSliders_t
+ {
+ SLIDER_ACCEL,
+ SLIDER_SPEED,
+ SLIDER_FOV,
+ SLIDER_ROTFILTER,
+ SLIDER_SHAKE_SPEED,
+ SLIDER_SHAKE_AMOUNT,
+ SLIDER_SHAKE_DIR,
+ };
+
+ Slider *m_pAccelSlider;
+ Slider *m_pSpeedSlider;
+ Slider *m_pFovSlider;
+ Slider *m_pRotFilterSlider;
+ Slider *m_pShakeSpeedSlider;
+ Slider *m_pShakeAmountSlider;
+ Slider *m_pShakeDirSlider;
+};
+
+//-----------------------------------------------------------------------------
+
+class CReplayButton : public CExImageButton
+{
+ DECLARE_CLASS_SIMPLE( CReplayButton, CExImageButton );
+public:
+ CReplayButton( Panel *pParent, const char *pName, const char *pText )
+ : BaseClass( pParent, pName, pText ),
+ m_pTipText( NULL )
+ {
+ }
+
+ virtual void ApplySettings( KeyValues *pInResourceData )
+ {
+ BaseClass::ApplySettings( pInResourceData );
+
+ const char *pTipName = pInResourceData->GetString( "tipname" );
+ if ( pTipName && pTipName[0] )
+ {
+ const wchar_t *pTipText = g_pVGuiLocalize->Find( pTipName );
+ if ( pTipText && pTipText[0] )
+ {
+ const int nTipLength = V_wcslen( pTipText );
+ m_pTipText = new wchar_t[ nTipLength + 1 ];
+ V_wcsncpy( m_pTipText, pTipText, sizeof(wchar_t) * ( nTipLength + 1 ) );
+ m_pTipText[ nTipLength ] = L'\0';
+ }
+ }
+ }
+
+ virtual void OnCursorEntered()
+ {
+ BaseClass::OnCursorEntered();
+
+ CReplayPerformanceEditorPanel *pEditor = ReplayUI_GetPerformanceEditor();
+ if ( pEditor && m_pTipText )
+ {
+ pEditor->SetButtonTip( m_pTipText, this );
+ pEditor->ShowButtonTip( true );
+ }
+ }
+
+ virtual void OnCursorExited()
+ {
+ BaseClass::OnCursorExited();
+
+ CReplayPerformanceEditorPanel *pEditor = ReplayUI_GetPerformanceEditor();
+ if ( pEditor && m_pTipText )
+ {
+ pEditor->ShowButtonTip( false );
+ }
+ }
+
+private:
+ wchar_t *m_pTipText;
+};
+
+DECLARE_BUILD_FACTORY_DEFAULT_TEXT( CReplayButton, CExImageButton );
+
+//-----------------------------------------------------------------------------
+
+#define MAX_FF_RAMP_TIME 8.0f // The amount of time until we ramp to max scale value.
+
+class CReplayEditorFastForwardButton : public CReplayButton
+{
+ DECLARE_CLASS_SIMPLE( CReplayEditorFastForwardButton, CReplayButton );
+public:
+ CReplayEditorFastForwardButton( Panel *pParent, const char *pName, const char *pText )
+ : BaseClass( pParent, pName, pText ),
+ m_flPressTime( 0.0f )
+ {
+ m_pHostTimescale = cvar->FindVar( "host_timescale" );
+ AssertMsg( m_pHostTimescale, "host_timescale lookup failed!" );
+
+ ivgui()->AddTickSignal( GetVPanel(), 10 );
+ }
+
+ ~CReplayEditorFastForwardButton()
+ {
+ ivgui()->RemoveTickSignal( GetVPanel() );
+
+ // Avoid a non-1.0 host_timescale after replay edit, which can happen if
+ // the user is still holding downt he FF button at the end of the replay.
+ if ( m_pHostTimescale )
+ {
+ m_pHostTimescale->SetValue( 1.0f );
+ }
+
+ // Resume demo playback so that any demo played later won't start paused.
+ PlayDemo();
+ }
+
+ virtual void OnMousePressed( MouseCode code )
+ {
+ m_flPressTime = gpGlobals->realtime;
+ PlayDemo();
+
+ BaseClass::OnMousePressed( code );
+ }
+
+ virtual void OnMouseReleased( MouseCode code )
+ {
+ m_flPressTime = 0.0f;
+ PauseDemo();
+
+ BaseClass::OnMouseReleased( code );
+ }
+
+ void OnTick()
+ {
+ float flScale;
+
+ if ( m_flPressTime == 0.0f )
+ {
+ flScale = 1.0f;
+ }
+ else
+ {
+ const float flElapsed = clamp( gpGlobals->realtime - m_flPressTime, 0.0f, MAX_FF_RAMP_TIME );
+ const float t = CubicEaseIn( flElapsed / MAX_FF_RAMP_TIME );
+
+ // If a shift key is down...
+ if ( input()->IsKeyDown( KEY_LSHIFT ) || input()->IsKeyDown( KEY_RSHIFT ) )
+ {
+ // ...slow down host_timescale.
+ flScale = .1f + .4f * t;
+ }
+ // If alt key down...
+ else if ( input()->IsKeyDown( KEY_LALT ) || input()->IsKeyDown( KEY_RALT ) )
+ {
+ // ...FF very quickly, ramp from 5 to 10.
+ flScale = 5.0f + 5.0f * t;
+ }
+ else
+ {
+ // Otherwise, start at 1.5 and ramp upwards over time.
+ flScale = 1.5f + 3.5f * t;
+ }
+ }
+
+ // Set host_timescale.
+ if ( m_pHostTimescale )
+ {
+ m_pHostTimescale->SetValue( flScale );
+ }
+ }
+
+private:
+ float m_flPressTime;
+ ConVar *m_pHostTimescale;
+};
+
+DECLARE_BUILD_FACTORY_DEFAULT_TEXT( CReplayEditorFastForwardButton, CExImageButton );
+
+//-----------------------------------------------------------------------------
+
+class CRecLightPanel : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CRecLightPanel, vgui::EditablePanel );
+public:
+ CRecLightPanel( Panel *pParent )
+ : EditablePanel( pParent, "RecLightPanel" ),
+ m_flPlayPauseTime( 0.0f ),
+ m_bPaused( false ),
+ m_bPerforming( false )
+ {
+ m_pRecLights[ 0 ] = NULL;
+ m_pRecLights[ 1 ] = NULL;
+ m_pPlayPause[ 0 ] = NULL;
+ m_pPlayPause[ 1 ] = NULL;
+ }
+
+ virtual void ApplySchemeSettings( IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/replayperformanceeditor/reclight.res", "GAME" );
+
+ m_pRecLights[ 0 ] = dynamic_cast< ImagePanel * >( FindChildByName( "RecLightOffImg" ) );
+ m_pRecLights[ 1 ] = dynamic_cast< ImagePanel * >( FindChildByName( "RecLightOnImg" ) );
+
+ m_pPlayPause[ 0 ] = dynamic_cast< ImagePanel * >( FindChildByName( "PlayImg" ) );
+ m_pPlayPause[ 1 ] = dynamic_cast< ImagePanel * >( FindChildByName( "PauseImg" ) );
+
+ m_pCameraFringe = dynamic_cast< ImagePanel *>( FindChildByName( "CameraFringe" ) );
+ m_pCameraCrosshair = dynamic_cast< ImagePanel *>( FindChildByName( "CameraCrosshair" ) );
+ }
+
+ virtual void PerformLayout()
+ {
+ BaseClass::PerformLayout();
+
+ SetVisible( m_bPerforming );
+
+ const int nScreenWidth = ScreenWidth();
+ const int nRecLightW = m_pRecLights[ 0 ]->GetWide();
+ int nXPos = nScreenWidth - nRecLightW + XRES( 6 );
+ int nYPos = -YRES( 8 );
+ m_pRecLights[ 0 ]->SetPos( nXPos, nYPos );
+ m_pRecLights[ 1 ]->SetPos( nXPos, nYPos );
+
+ const int nWidth = GetWide();
+ const int nHeight = GetTall();
+
+ // Setup camera fringe height
+ if ( m_pCameraFringe )
+ {
+ m_pCameraFringe->SetSize( nWidth, nHeight );
+ m_pCameraFringe->InstallMouseHandler( this );
+ }
+
+ // Setup camera cross hair height
+ if ( m_pCameraCrosshair )
+ {
+ int aImageSize[2];
+ IImage *pImage = m_pCameraCrosshair->GetImage();
+ pImage->GetSize( aImageSize[0], aImageSize[1] );
+
+ aImageSize[0] = m_pCameraCrosshair->GetWide();
+ aImageSize[1] = m_pCameraCrosshair->GetTall();
+
+ const int nStartY = YRES( 13 );
+
+ m_pCameraCrosshair->SetBounds(
+ nStartY + ( nWidth - aImageSize[0] ) / 2,
+ nStartY + ( nHeight - aImageSize[1] ) / 2,
+ aImageSize[0] - 2 * nStartY,
+ aImageSize[1] - 2 * nStartY
+ );
+
+ m_pCameraCrosshair->InstallMouseHandler( this );
+ }
+ }
+
+ void UpdateBackgroundVisibility()
+ {
+ m_pCameraCrosshair->SetVisible( m_bPaused );
+ m_pCameraFringe->SetVisible( m_bPaused );
+ }
+
+ virtual void OnThink()
+ {
+ const float flTime = gpGlobals->realtime;
+ bool bPauseAnimating = m_flPlayPauseTime > 0.0f &&
+ flTime >= m_flPlayPauseTime &&
+ flTime < ( m_flPlayPauseTime + m_flAnimTime );
+
+ // Setup light visibility
+ int nOnOff = fmod( flTime * 2.0f, 2.0f );
+ bool bOnLightVisible = (bool)nOnOff;
+ bool bRecording = g_pReplayPerformanceController->IsRecording();
+ m_pRecLights[ 0 ]->SetVisible( m_bPaused || ( bRecording && !bOnLightVisible ) );
+ m_pRecLights[ 1 ]->SetVisible( bRecording && ( !m_bPaused && bOnLightVisible ) );
+
+ // Deal with fringe and crosshair vis
+ UpdateBackgroundVisibility();
+
+ int iPlayPauseActive = (int)m_bPaused;
+
+ // Animate the pause icon
+ if ( bPauseAnimating )
+ {
+ const float t = clamp( ( flTime - m_flPlayPauseTime ) / m_flAnimTime, 0.0f, 1.0f );
+ const float s = SCurve( t );
+ const int nSize = (int)Lerp( s, 60.0f, 60.0f * m_nAnimScale );
+ int aCrossHairPos[2];
+ m_pCameraCrosshair->GetPos( aCrossHairPos[0], aCrossHairPos[1] );
+ const int nScreenXCenter = aCrossHairPos[0] + m_pCameraCrosshair->GetWide() / 2;
+ const int nScreenYCenter = aCrossHairPos[1] + m_pCameraCrosshair->GetTall() / 2;
+
+ m_pPlayPause[ iPlayPauseActive ]->SetBounds(
+ nScreenXCenter - nSize / 2,
+ nScreenYCenter - nSize / 2,
+ nSize,
+ nSize
+ );
+
+ m_pPlayPause[ iPlayPauseActive ]->SetAlpha( (int)( MIN( 0.5f, 1.0f - s ) * 255) );
+ }
+
+ m_pPlayPause[ iPlayPauseActive ]->SetVisible( bPauseAnimating );
+ m_pPlayPause[ !iPlayPauseActive ]->SetVisible( false );
+ }
+
+ void UpdatePauseState( bool bPaused )
+ {
+ if ( bPaused == m_bPaused )
+ return;
+
+ m_bPaused = bPaused;
+
+ m_flPlayPauseTime = gpGlobals->realtime;
+ }
+
+ void SetPerforming( bool bPerforming )
+ {
+ if ( bPerforming == m_bPerforming )
+ return;
+
+ m_bPerforming = bPerforming;
+ InvalidateLayout( true, false );
+ }
+
+ float m_flPlayPauseTime;
+ bool m_bPaused;
+ bool m_bPerforming;
+ ImagePanel *m_pPlayPause[2]; // 0=play, 1=pause
+ ImagePanel *m_pRecLights[2]; // 0=off, 1=on
+ ImagePanel *m_pCameraFringe;
+ ImagePanel *m_pCameraCrosshair;
+
+ CPanelAnimationVar( int, m_nAnimScale, "anim_scale", "4" );
+ CPanelAnimationVar( float, m_flAnimTime, "anim_time", "1.5" );
+};
+
+//-----------------------------------------------------------------------------
+
+CReplayPerformanceEditorPanel::CReplayPerformanceEditorPanel( Panel *parent, ReplayHandle_t hReplay )
+: EditablePanel( parent, "ReplayPerformanceEditor" ),
+ m_hReplay( hReplay ),
+ m_flLastTime( -1 ),
+ m_nRedBlueLabelRightX( 0 ),
+ m_nBottomPanelStartY( 0 ),
+ m_nBottomPanelHeight( 0 ),
+ m_nLastRoundedTime( -1 ),
+ m_flSpaceDownStart( 0.0f ),
+ m_flOldFps( -1.0f ),
+ m_flLastTimeSpaceBarPressed( 0.0f ),
+ m_flActiveTimeInEditor( 0.0f ),
+ m_flTimeScaleProxy( 1.0f ),
+ m_iCameraSelection( CAM_FIRST ),
+ m_bMousePressed( false ),
+ m_bMouseDown( false ),
+ m_nMouseClickedOverCameraSettingsPanel( CAM_INVALID ),
+ m_bShownAtLeastOnce( false ),
+ m_bAchievementAwarded( false ),
+ m_pImageList( NULL ),
+ m_pCurTimeLabel( NULL ),
+ m_pTotalTimeLabel( NULL ),
+ m_pPlayerNameLabel( NULL ),
+ m_pMouseTargetPanel( NULL ),
+ m_pSlowMoButton( NULL ),
+ m_pRecLightPanel( NULL ),
+ m_pPlayerCellData( NULL ),
+ m_pBottom( NULL ),
+ m_pMenuButton( NULL ),
+ m_pMenu( NULL ),
+ m_pPlayerCellsPanel( NULL ),
+ m_pButtonTip( NULL ),
+ m_pSavingDlg( NULL )
+{
+ V_memset( m_pCameraButtons, 0, sizeof( m_pCameraButtons ) );
+ V_memset( m_pCtrlButtons, 0, sizeof( m_pCtrlButtons ) );
+ V_memset( m_pCameraOptionsPanels, NULL, sizeof( m_pCameraOptionsPanels ) );
+
+ m_pCameraOptionsPanels[ CAM_FREE ] = new CCameraOptionsPanel_Free( this );
+ m_pCameraOptionsPanels[ COMPONENT_TIMESCALE ] = new CTimeScaleOptionsPanel( this, &m_flTimeScaleProxy );
+
+ m_nRedBlueSigns[0] = -1;
+ m_nRedBlueSigns[1] = 1;
+ m_iCurPlayerTarget = -1;
+
+ m_pImageList = new ImageList( false );
+
+ SetParent( g_pClientMode->GetViewport() );
+
+ HScheme hScheme = scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme" );
+ SetScheme( hScheme );
+
+ ivgui()->AddTickSignal( GetVPanel(), 16 ); // Roughly 60hz
+
+ MakePopup( true );
+ SetMouseInputEnabled( true );
+
+ // Create bottom
+ m_pBottom = new EditablePanel( this, "BottomPanel" );
+
+ // Add player cells
+ m_pPlayerCellsPanel = new EditablePanel( m_pBottom, "PlayerCellsPanel" );
+ for ( int i = 0; i < 2; ++i )
+ {
+ for ( int j = 0; j <= MAX_PLAYERS; ++j )
+ {
+ m_pPlayerCells[i][j] = new CPlayerCell( m_pPlayerCellsPanel, "PlayerCell", &m_iCurPlayerTarget );
+ m_pPlayerCells[i][j]->SetVisible( false );
+ AddPanelKeyboardInputDisableList( m_pPlayerCells[i][j] );
+ }
+ }
+
+ // Create rec light panel
+ m_pRecLightPanel = SETUP_PANEL( new CRecLightPanel( g_pClientMode->GetViewport() ) );
+
+ // Display "enter performance mode" tip
+ DisplayPerformanceTip( "#Replay_PerfTip_EnterPerfMode", &replay_perftip_count_enter, MAX_TIP_DISPLAYS );
+
+ // Create menu
+ m_pMenu = new Menu( this, "Menu" );
+ m_aMenuItemIds[ MENU_SAVE ] = m_pMenu->AddMenuItem( "#Replay_Save", "menu_save", this );
+ m_aMenuItemIds[ MENU_SAVEAS ] = m_pMenu->AddMenuItem( "#Replay_SaveAs", "menu_saveas", this );
+ m_pMenu->AddSeparator();
+ m_aMenuItemIds[ MENU_EXIT ] = m_pMenu->AddMenuItem( "#Replay_Exit", "menu_exit", this );
+
+ m_pMenu->EnableUseMenuManager( false ); // The menu manager doesn't play nice with the menu button
+}
+
+CReplayPerformanceEditorPanel::~CReplayPerformanceEditorPanel()
+{
+ m_pRecLightPanel->MarkForDeletion();
+ m_pRecLightPanel = NULL;
+
+ m_pButtonTip->MarkForDeletion();
+ m_pButtonTip = NULL;
+
+ g_bIsReplayRewinding = false;
+
+ surface()->PlaySound( "replay\\performanceeditorclosed.wav" );
+
+ CPerformanceTip::Cleanup();
+
+ ClearPlayerCellData();
+}
+
+void CReplayPerformanceEditorPanel::ClearPlayerCellData()
+{
+ if ( m_pPlayerCellData )
+ {
+ m_pPlayerCellData->deleteThis();
+ m_pPlayerCellData = NULL;
+ }
+}
+
+void CReplayPerformanceEditorPanel::AddPanelKeyboardInputDisableList( Panel *pPanel )
+{
+ m_lstDisableKeyboardInputPanels.AddToTail( pPanel );
+}
+
+void CReplayPerformanceEditorPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/replayperformanceeditor/main.res", "GAME" );
+
+ m_lstDisableKeyboardInputPanels.RemoveAll();
+
+ int nParentWidth = GetParent()->GetWide();
+ int nParentHeight = GetParent()->GetTall();
+
+ // Set size of this panel
+ SetSize( nParentWidth, nParentHeight );
+
+ // Layout bottom
+ if ( m_pBottom )
+ {
+ m_nBottomPanelHeight = m_pBottom->GetTall(); // Get from .res
+ m_nBottomPanelStartY = nParentHeight - m_nBottomPanelHeight;
+ m_pBottom->SetBounds( 0, m_nBottomPanelStartY, nParentWidth, m_nBottomPanelHeight );
+ }
+
+ // Layout rec light panel - don't overlap bottom panel
+ m_pRecLightPanel->SetBounds( 0, 0, ScreenWidth(), m_nBottomPanelStartY );
+
+ // Setup camera buttons
+ const int nNumCameraButtons = NCAMS;
+ const char *pCameraButtonNames[nNumCameraButtons] = { "CameraFree", "CameraThird", "CameraFirst", "TimeScaleButton" };
+ int nCurButtonX = nParentWidth - m_nRightMarginWidth;
+ int nLeftmostCameraButtonX = 0;
+ for ( int i = 0; i < nNumCameraButtons; ++i )
+ {
+ m_pCameraButtons[i] = dynamic_cast< CExImageButton * >( FindChildByName( pCameraButtonNames[ i ] ) );
+ if ( m_pCameraButtons[i] )
+ {
+ CExImageButton *pCurButton = m_pCameraButtons[ i ];
+ if ( !pCurButton )
+ continue;
+
+ nCurButtonX -= pCurButton->GetWide();
+
+ int nX, nY;
+ pCurButton->GetPos( nX, nY );
+ pCurButton->SetPos( nCurButtonX, nY );
+
+ pCurButton->SetParent( m_pBottom );
+ pCurButton->AddActionSignalTarget( this );
+
+#if !defined( TF_CLIENT_DLL )
+ pCurButton->SetPaintBorderEnabled( false );
+#endif
+
+ AddPanelKeyboardInputDisableList( pCurButton );
+ }
+ }
+ nLeftmostCameraButtonX = nCurButtonX;
+
+ static const char *s_pControlButtonNames[NUM_CTRLBUTTONS] = {
+ "InButton", "GotoBeginningButton", "RewindButton",
+ "PlayButton",
+ "FastForwardButton", "GotoEndButton", "OutButton"
+ };
+ for ( int i = 0; i < NUM_CTRLBUTTONS; ++i )
+ {
+ CExImageButton *pCurButton = dynamic_cast< CExImageButton * >( FindChildByName( s_pControlButtonNames[ i ] ) ); Assert( pCurButton );
+ if ( !pCurButton )
+ continue;
+
+ pCurButton->SetParent( m_pBottom );
+ pCurButton->AddActionSignalTarget( this );
+
+ AddPanelKeyboardInputDisableList( pCurButton );
+
+#if !defined( TF_CLIENT_DLL )
+ pCurButton->SetPaintBorderEnabled( false );
+#endif
+
+ m_pCtrlButtons[ i ] = pCurButton;
+ }
+
+ // If the performance in tick is set, highlight the in point button
+ {
+ CReplayPerformance *pSavedPerformance = GetSavedPerformance();
+ m_pCtrlButtons[ CTRLBUTTON_IN ]->SetSelected( pSavedPerformance && pSavedPerformance->HasInTick() );
+ m_pCtrlButtons[ CTRLBUTTON_OUT ]->SetSelected( pSavedPerformance && pSavedPerformance->HasOutTick() );
+ }
+
+ // Select first-person camera by default.
+ UpdateCameraSelectionPosition( CAM_FIRST );
+
+ // Position time label
+ m_pCurTimeLabel = dynamic_cast< CExLabel * >( FindChildByName( "CurTimeLabel" ) );
+ m_pTotalTimeLabel = dynamic_cast< CExLabel * >( FindChildByName( "TotalTimeLabel" ) );
+
+ m_pCurTimeLabel->SetParent( m_pBottom );
+ m_pTotalTimeLabel->SetParent( m_pBottom );
+
+ // Get player name label
+ m_pPlayerNameLabel = dynamic_cast< CExLabel * >( FindChildByName( "PlayerNameLabel" ) );
+
+ // Get mouse target panel
+ m_pMouseTargetPanel = dynamic_cast< EditablePanel * >( FindChildByName( "MouseTargetPanel" ) );
+
+ for ( int i = 0; i < 2; ++i )
+ {
+ for ( int j = 0; j <= MAX_PLAYERS; ++j )
+ {
+ m_pPlayerCells[i][j]->SetMouseInputEnabled( true );
+ }
+ }
+
+ // Get menu button
+ m_pMenuButton = dynamic_cast< CExImageButton * >( FindChildByName( "MenuButton" ) );
+ AddPanelKeyboardInputDisableList( m_pMenuButton );
+ m_pMenuButton->SetMouseInputEnabled( true );
+#if !defined( TF_CLIENT_DLL )
+ m_pMenuButton->SetPaintBorderEnabled( false );
+#endif
+
+ // Get button tip
+ m_pButtonTip = dynamic_cast< CReplayTipLabel * >( FindChildByName( "ButtonTip" ) );
+ m_pButtonTip->SetParent( g_pClientMode->GetViewport() );
+}
+
+static void Replay_GotoTick( bool bConfirmed, void *pContext )
+{
+ if ( bConfirmed )
+ {
+ int nGotoTick = (int)pContext;
+ CFmtStr fmtCmd( "demo_gototick %i\ndemo_pause\n", nGotoTick );
+ engine->ClientCmd_Unrestricted( fmtCmd.Access() );
+ }
+}
+
+void CReplayPerformanceEditorPanel::OnSliderMoved( KeyValues *pParams )
+{
+}
+
+void CReplayPerformanceEditorPanel::OnInGameMouseWheelEvent( int nDelta )
+{
+ HandleMouseWheel( nDelta );
+}
+
+void CReplayPerformanceEditorPanel::HandleMouseWheel( int nDelta )
+{
+ if ( ReplayCamera()->GetMode() == OBS_MODE_ROAMING )
+ {
+ // Invert mousewheel input if necessary
+ if ( replay_editor_fov_mousewheel_invert.GetBool() )
+ {
+ nDelta *= -1;
+ }
+
+ float &flFov = ReplayCamera()->m_flRoamingFov[1];
+ flFov = clamp( flFov - nDelta * replay_editor_fov_mousewheel_multiplier.GetFloat(), FREE_CAM_FOV_MIN, FREE_CAM_FOV_MAX );
+
+ // Update FOV slider in free camera settings
+ CCameraOptionsPanel_Free *pFreeCamOptions = static_cast< CCameraOptionsPanel_Free * >( m_pCameraOptionsPanels[ CAM_FREE ] );
+ pFreeCamOptions->m_pFovSlider->SetValue( flFov - FREE_CAM_FOV_MIN, false );
+ }
+}
+
+void CReplayPerformanceEditorPanel::ApplySettings( KeyValues *pInResourceData )
+{
+ BaseClass::ApplySettings( pInResourceData );
+
+ ClearPlayerCellData();
+
+ KeyValues *pPlayerCellData = pInResourceData->FindKey( "PlayerCell" );
+ if ( pPlayerCellData )
+ {
+ m_pPlayerCellData = new KeyValues( "PlayerCell" );
+ pPlayerCellData->CopySubkeys( m_pPlayerCellData );
+ }
+}
+
+CameraMode_t CReplayPerformanceEditorPanel::IsMouseOverActiveCameraOptionsPanel( int nMouseX, int nMouseY )
+{
+ // In one of the camera options panels?
+ for ( int i = 0; i < NCAMS; ++i )
+ {
+ CCameraOptionsPanel *pCurPanel = m_pCameraOptionsPanels[ i ];
+ if ( pCurPanel && pCurPanel->IsVisible() && pCurPanel->IsWithin( nMouseX, nMouseY ) )
+ return (CameraMode_t)i;
+ }
+
+ return CAM_INVALID;
+}
+
+void CReplayPerformanceEditorPanel::OnMouseWheeled( int nDelta )
+{
+ HandleMouseWheel( nDelta );
+}
+
+void CReplayPerformanceEditorPanel::OnTick()
+{
+ BaseClass::OnTick();
+
+// engine->Con_NPrintf( 0, "timescale: %f", g_pReplayPerformanceController->GetPlaybackTimeScale() );
+
+ C_ReplayCamera *pCamera = ReplayCamera();
+ if ( !pCamera )
+ return;
+
+ // Calc elapsed time
+ float flElapsed = gpGlobals->realtime - m_flLastTime;
+ m_flLastTime = gpGlobals->realtime;
+
+ // If this is the first time we're running and camera is valid, get primary target
+ if ( m_iCurPlayerTarget < 0 )
+ {
+ m_iCurPlayerTarget = pCamera->GetPrimaryTargetIndex();
+ }
+
+ // NOTE: Third-person is not "controllable" yet
+ int nCameraMode = pCamera->GetMode();
+ bool bInAControllableCameraMode = nCameraMode == OBS_MODE_ROAMING || nCameraMode == OBS_MODE_CHASE;
+
+ // Get mouse cursor pos
+ int nMouseX, nMouseY;
+ input()->GetCursorPos( nMouseX, nMouseY );
+
+ // Toggle in and out of camera control if appropriate
+ // Mouse pressed?
+ bool bMouseDown = input()->IsMouseDown( MOUSE_LEFT );
+ m_bMousePressed = bMouseDown && !m_bMouseDown;
+ m_bMouseDown = bMouseDown;
+
+ // Reset this flag if mouse is no longer down
+ if ( !m_bMouseDown )
+ {
+ m_nMouseClickedOverCameraSettingsPanel = CAM_INVALID;
+ }
+
+ bool bNoDialogsUp = TFModalStack()->IsEmpty();
+ bool bMouseCursorOverPerfEditor = nMouseY >= m_nBottomPanelStartY;
+ bool bMouseOverMenuButton = m_pMenuButton->IsWithin( nMouseX, nMouseY );
+ bool bMouseOverMenu = m_pMenu->IsWithin( nMouseX, nMouseY );
+ bool bRecording = g_pReplayPerformanceController->IsRecording();
+ if ( IsVisible() && m_bMousePressed )
+ {
+ CameraMode_t nActiveOptionsPanel = IsMouseOverActiveCameraOptionsPanel( nMouseX, nMouseY );
+ if ( nActiveOptionsPanel != CAM_INVALID )
+ {
+ m_nMouseClickedOverCameraSettingsPanel = nActiveOptionsPanel;
+ }
+ else if ( m_pMenu->IsVisible() && !m_pMenu->IsWithin( nMouseX, nMouseY ) )
+ {
+ ToggleMenu();
+ }
+ else if ( bInAControllableCameraMode && !bMouseCursorOverPerfEditor && !bMouseOverMenuButton &&
+ !bMouseOverMenu && bNoDialogsUp )
+ {
+ if ( bRecording )
+ {
+ bool bMouseInputEnabled = IsMouseInputEnabled();
+
+ // Already in a controllable camera mode?
+ if ( bMouseInputEnabled )
+ {
+ DisplayPerformanceTip( "#Replay_PerfTip_ExitFreeCam", &replay_perftip_count_freecam_exit, MAX_TIP_DISPLAYS );
+ surface()->PlaySound( "replay\\cameracontrolmodeentered.wav" );
+ }
+ else
+ {
+ DisplayPerformanceTip( "#Replay_PerfTip_EnterFreeCam", &replay_perftip_count_freecam_enter, MAX_TIP_DISPLAYS );
+ surface()->PlaySound( "replay\\cameracontrolmodeexited.wav" );
+ }
+
+ SetMouseInputEnabled( !bMouseInputEnabled );
+ }
+ else
+ {
+ // Play an error sound
+ surface()->PlaySound( "replay\\cameracontrolerror.wav" );
+ }
+ }
+ }
+
+ // Show panel if space key bar is down
+ bool bSpaceDown = bNoDialogsUp && !enginevgui->IsGameUIVisible() && input()->IsKeyDown( KEY_SPACE );
+ m_bSpacePressed = bSpaceDown && !m_bSpaceDown;
+ m_bSpaceDown = bSpaceDown;
+
+ // Modify visibility?
+ bool bShow = IsVisible();
+ if ( m_bSpacePressed )
+ {
+ bShow = !IsVisible();
+ }
+
+ // Set visibility?
+ if ( IsVisible() != bShow )
+ {
+ ShowPanel( bShow );
+ m_bShownAtLeastOnce = true;
+
+ // For achievements:
+ Achievements_OnSpaceBarPressed();
+ }
+
+ // Factor in host_timescale.
+ float flScaledElapsed = flElapsed;
+ ConVarRef host_timescale( "host_timescale" );
+ if ( host_timescale.GetFloat() > 0 )
+ {
+ flScaledElapsed *= host_timescale.GetFloat();
+ }
+
+ // Do FOV smoothing
+ ReplayCamera()->SmoothFov( flScaledElapsed );
+
+ // Don't do any more processing if not needed
+ if ( !m_bShownAtLeastOnce )
+ return;
+
+ // Update time text if necessary
+ UpdateTimeLabels();
+
+ // Make all player cells invisible
+ int nTeamCounts[2] = {0,0};
+ int nCurTeam = 0;
+ for ( int i = 0; i < 2; ++i )
+ for ( int j = 0; j <= MAX_PLAYERS; ++j )
+ {
+ m_pPlayerCells[i][j]->SetVisible( false );
+ }
+
+ int iMouseOverPlayerIndex = -1;
+ CPlayerCell *pMouseOverCell = NULL;
+
+ // Update player cells
+ bool bLayoutPlayerCells = true; // TODO: only layout when necessary
+ C_ReplayGame_PlayerResource_t *pGamePlayerResource = dynamic_cast< C_ReplayGame_PlayerResource_t * >( g_PR );
+ for ( int iPlayer = 1; iPlayer <= MAX_PLAYERS; ++iPlayer )
+ {
+ IGameResources *pGR = GameResources();
+
+ if ( !pGR || !pGR->IsConnected( iPlayer ) )
+ continue;
+
+ // Which team?
+ int iTeam = pGR->GetTeam( iPlayer );
+ switch ( iTeam )
+ {
+ case REPLAY_TEAM_TEAM0:
+ ++nTeamCounts[0];
+ nCurTeam = 0;
+ break;
+ case REPLAY_TEAM_TEAM1:
+ ++nTeamCounts[1];
+ nCurTeam = 1;
+ break;
+ default:
+ nCurTeam = -1;
+ break;
+ }
+
+ if ( nCurTeam < 0 )
+ continue;
+
+#if !defined( CSTRIKE_DLL )
+ int iPlayerClass = pGamePlayerResource->GetPlayerClass( iPlayer );
+ if ( iPlayerClass == REPLAY_CLASS_UNDEFINED )
+ continue;
+#endif
+
+ int nCurTeamCount = nTeamCounts[ nCurTeam ];
+ CPlayerCell* pCell = m_pPlayerCells[ nCurTeam ][ nCurTeamCount-1 ];
+
+ // Cache the player index
+ pCell->m_iPlayerIndex = iPlayer;
+
+ // Make visible
+ pCell->SetVisible( true );
+
+ // Show leaderboard icon
+#if defined( TF_CLIENT_DLL )
+ char szClassImg[64];
+ extern const char *g_aPlayerClassNames_NonLocalized[ REPLAY_NUM_CLASSES ];
+ char const *pClassName = iPlayerClass == TF_CLASS_DEMOMAN
+ ? "demo"
+ : g_aPlayerClassNames_NonLocalized[ iPlayerClass ];
+ V_snprintf( szClassImg, sizeof( szClassImg ), "../HUD/leaderboard_class_%s", pClassName );
+
+ // Show dead icon instead?
+ if ( !pGamePlayerResource->IsAlive( iPlayer ) )
+ {
+ V_strcat( szClassImg, "_d", sizeof( szClassImg ) );
+ }
+
+ IImage *pImage = scheme()->GetImage( szClassImg, true );
+ if ( pImage )
+ {
+ pImage->SetSize( 32, 32 );
+ pCell->GetImage()->SetImage( pImage );
+ }
+
+#elif defined( CSTRIKE_DLL )
+ // TODO - create and use class icons
+ char szText[16];
+ V_snprintf( szText, sizeof( szText ), "%i", nTeamCounts[ nCurTeam ] );
+ pCell->SetText( szText );
+#endif
+
+ // Display player name if mouse is over the current cell
+ if ( pCell->IsWithin( nMouseX, nMouseY ) )
+ {
+ iMouseOverPlayerIndex = iPlayer;
+ pMouseOverCell = pCell;
+ }
+ }
+
+ // Check to see if we're hovering over a camera-mode, and if so, display its options panel if it has one
+ if ( bRecording )
+ {
+ for ( int i = 0; i < NCAMS; ++i )
+ {
+ CCameraOptionsPanel *pCurOptionsPanel = m_pCameraOptionsPanels[ i ];
+ if ( !pCurOptionsPanel )
+ continue;
+
+ bool bMouseOverButton = m_pCameraButtons[ i ]->IsWithin( nMouseX, nMouseY );
+ bool bMouseOverOptionsPanel = pCurOptionsPanel->IsWithin( nMouseX, nMouseY );
+ bool bInCameraModeThatMouseIsOver = ReplayCamera()->GetMode() == GetCameraModeFromButtonIndex( (CameraMode_t)i );
+ bool bDontCareAboutCameraMode = i == COMPONENT_TIMESCALE;
+ bool bActivate = ( i == m_nMouseClickedOverCameraSettingsPanel ) ||
+ ( ( ( bInCameraModeThatMouseIsOver || bDontCareAboutCameraMode ) && bMouseOverButton ) || ( bMouseOverOptionsPanel && pCurOptionsPanel->IsVisible() ) );
+ pCurOptionsPanel->SetVisible( bActivate );
+ }
+ }
+
+ if ( bLayoutPlayerCells )
+ {
+ LayoutPlayerCells();
+ }
+
+ // Setup player name label and temporary camera view
+ if ( m_pPlayerNameLabel && pGamePlayerResource && pMouseOverCell )
+ {
+ m_pPlayerNameLabel->SetText( pGamePlayerResource->GetPlayerName( iMouseOverPlayerIndex ) );
+ m_pPlayerNameLabel->SizeToContents();
+
+ int nCellPos[2];
+ pMouseOverCell->GetPos( nCellPos[0], nCellPos[1] );
+
+ int nLabelX = MAX(
+ nCellPos[0],
+ m_nRedBlueLabelRightX
+ );
+ int nLabelY = m_nBottomPanelStartY + ( m_nBottomPanelHeight - m_pPlayerNameLabel->GetTall() ) / 2;
+ m_pPlayerNameLabel->SetPos( nLabelX, nLabelY );
+
+ m_pPlayerNameLabel->SetVisible( true );
+
+ // Setup camera
+ pCamera->SetPrimaryTarget( iMouseOverPlayerIndex );
+ }
+ else
+ {
+ m_pPlayerNameLabel->SetVisible( false );
+
+ // Set camera to last valid target
+ Assert( m_iCurPlayerTarget >= 0 );
+ pCamera->SetPrimaryTarget( m_iCurPlayerTarget );
+ }
+
+ // If user clicked, assume it was the selected cell and set primary target in camera
+ if ( iMouseOverPlayerIndex >= 0 )
+ {
+ pCamera->SetPrimaryTarget( iMouseOverPlayerIndex );
+ }
+ else
+ {
+ pCamera->SetPrimaryTarget( m_iCurPlayerTarget );
+ }
+
+ // If in free-cam mode, add set view event if we're not paused
+ if ( bInAControllableCameraMode && m_bShownAtLeastOnce && bRecording )
+ {
+ AddSetViewEvent();
+ AddTimeScaleEvent( m_flTimeScaleProxy );
+ }
+
+ // Set paused state in rec light
+ const bool bPaused = IsPaused();
+ m_pRecLightPanel->UpdatePauseState( bPaused );
+
+ Achievements_Think( flElapsed );
+}
+
+void CReplayPerformanceEditorPanel::Achievements_OnSpaceBarPressed()
+{
+ m_flLastTimeSpaceBarPressed = gpGlobals->realtime;
+}
+
+void CReplayPerformanceEditorPanel::Achievements_Think( float flElapsed )
+{
+// engine->Con_NPrintf( 10, "total time: %f", m_flActiveTimeInEditor );
+// engine->Con_NPrintf( 11, "last time space bar pressed: %f", m_flLastTimeSpaceBarPressed );
+
+ // Already awarded one this editing session?
+ if ( m_bAchievementAwarded )
+ return;
+
+ // Too much idle time since last activity?
+ if ( gpGlobals->realtime - m_flLastTimeSpaceBarPressed > 60.0f )
+ {
+ m_flActiveTimeInEditor = 0.0f;
+ return;
+ }
+
+ // Accumulate active time
+ m_flActiveTimeInEditor += flElapsed;
+
+ // Award now if three-minutes of non-idle time has passed
+ const float flMinutes = 60.0f * 3.0f;
+ if ( m_flActiveTimeInEditor < flMinutes )
+ return;
+
+ Achievements_Grant();
+}
+
+void CReplayPerformanceEditorPanel::Achievements_Grant()
+{
+#if defined( TF_CLIENT_DLL )
+ g_AchievementMgrTF.AwardAchievement( ACHIEVEMENT_TF_REPLAY_EDIT_TIME );
+#endif
+
+ // Awarded
+ m_bAchievementAwarded = true;
+}
+
+bool CReplayPerformanceEditorPanel::IsPaused()
+{
+ return IsVisible();
+}
+
+CReplayPerformance *CReplayPerformanceEditorPanel::GetPerformance() const
+{
+ return g_pReplayPerformanceController->GetPerformance();
+}
+
+CReplayPerformance *CReplayPerformanceEditorPanel::GetSavedPerformance() const
+{
+ return g_pReplayPerformanceController->GetSavedPerformance();
+}
+
+int CReplayPerformanceEditorPanel::GetCameraModeFromButtonIndex( CameraMode_t iCamera )
+{
+ switch ( iCamera )
+ {
+ case CAM_FREE: return OBS_MODE_ROAMING;
+ case CAM_THIRD: return OBS_MODE_CHASE;
+ case CAM_FIRST: return OBS_MODE_IN_EYE;
+ }
+ return CAM_INVALID;
+}
+
+void CReplayPerformanceEditorPanel::UpdateTimeLabels()
+{
+ CReplay *pPlayingReplay = g_pReplayManager->GetPlayingReplay();
+
+ if ( !pPlayingReplay || !m_pCurTimeLabel || !m_pTotalTimeLabel )
+ return;
+
+ float flCurTime, flTotalTime;
+ g_pClientReplayContext->GetPlaybackTimes( flCurTime, flTotalTime, pPlayingReplay, GetPerformance() );
+
+ int nCurRoundedTime = (int)flCurTime; // Essentially floor'd
+ if ( nCurRoundedTime == m_nLastRoundedTime )
+ return;
+
+ m_nLastRoundedTime = nCurRoundedTime;
+
+ // Set current time text
+ char szTimeText[64];
+ V_snprintf( szTimeText, sizeof( szTimeText ), "%s", CReplayTime::FormatTimeString( nCurRoundedTime ) );
+ m_pCurTimeLabel->SetText( szTimeText );
+
+ // Set total time text
+ V_snprintf( szTimeText, sizeof( szTimeText ), "%s", CReplayTime::FormatTimeString( (int)flTotalTime ) );
+ m_pTotalTimeLabel->SetText( szTimeText );
+
+ // Center between left-most camera button and play/pause button
+ m_pCurTimeLabel->SizeToContents();
+ m_pTotalTimeLabel->SizeToContents();
+}
+
+void CReplayPerformanceEditorPanel::UpdateCameraSelectionPosition( CameraMode_t nCameraMode )
+{
+ Assert( nCameraMode >= 0 && nCameraMode < NCAMS );
+ m_iCameraSelection = nCameraMode;
+
+ UpdateCameraButtonImages();
+}
+
+void CReplayPerformanceEditorPanel::UpdateFreeCamSettings( const SetViewParams_t ¶ms )
+{
+ CCameraOptionsPanel_Free *pSettingsPanel = dynamic_cast< CCameraOptionsPanel_Free * >( m_pCameraOptionsPanels[ CAM_FREE ] );
+ if ( !pSettingsPanel )
+ return;
+
+ pSettingsPanel->SetValue( CCameraOptionsPanel_Free::SLIDER_ACCEL, params.m_flAccel );
+ pSettingsPanel->SetValue( CCameraOptionsPanel_Free::SLIDER_SPEED, params.m_flSpeed );
+ pSettingsPanel->SetValue( CCameraOptionsPanel_Free::SLIDER_FOV, params.m_flFov );
+ pSettingsPanel->SetValue( CCameraOptionsPanel_Free::SLIDER_ROTFILTER, params.m_flRotationFilter );
+}
+
+void CReplayPerformanceEditorPanel::UpdateTimeScale( float flScale )
+{
+ CTimeScaleOptionsPanel *pSettingsPanel = dynamic_cast< CTimeScaleOptionsPanel * >( m_pCameraOptionsPanels[ COMPONENT_TIMESCALE ] );
+ if ( !pSettingsPanel )
+ return;
+
+ pSettingsPanel->SetValue( CTimeScaleOptionsPanel::SLIDER_TIMESCALE, flScale );
+}
+
+void CReplayPerformanceEditorPanel::LayoutPlayerCells()
+{
+ int nPanelHeight = m_pPlayerCellsPanel->GetTall();
+ int nCellBuffer = XRES(1);
+ for ( int i = 0; i < 2; ++i )
+ {
+ int nCurX = m_nRedBlueLabelRightX;
+
+ for ( int j = 0; j <= MAX_PLAYERS; ++j )
+ {
+ CPlayerCell *pCurCell = m_pPlayerCells[i][j];
+ if ( !pCurCell->IsVisible() )
+ continue;
+
+ // Apply cached settings from .res file
+ if ( m_pPlayerCellData )
+ {
+ pCurCell->ApplySettings( m_pPlayerCellData );
+ }
+
+ const int nY = nPanelHeight/2 + m_nRedBlueSigns[i] * nPanelHeight/4 - pCurCell->GetTall()/2;
+ pCurCell->SetPos(
+ nCurX,
+ nY
+ );
+
+ nCurX += pCurCell->GetWide() + nCellBuffer;
+ }
+ }
+}
+
+void CReplayPerformanceEditorPanel::PerformLayout()
+{
+ int w = ScreenWidth(), h = ScreenHeight();
+ SetBounds(0,0,w,h);
+
+ // Layout camera options panels
+ for ( int i = 0; i < NCAMS; ++i )
+ {
+ CCameraOptionsPanel *pCurOptionsPanel = m_pCameraOptionsPanels[ i ];
+ if ( !pCurOptionsPanel )
+ continue;
+
+ CExImageButton *pCurCameraButton = m_pCameraButtons[ i ];
+ if ( !pCurCameraButton )
+ continue;
+
+ // Get camera button position
+ int aCameraButtonPos[2];
+ int aBottomPos[2];
+ pCurCameraButton->GetPos( aCameraButtonPos[ 0 ], aCameraButtonPos[ 1 ] );
+ m_pBottom->GetPos( aBottomPos[ 0 ], aBottomPos[ 1 ] );
+
+ // Layout the panel now - it should set its own size, which we need to know to position it properly
+ pCurOptionsPanel->InvalidateLayout( true, true );
+
+ // Position it
+ pCurOptionsPanel->SetPos(
+ aBottomPos[ 0 ] + aCameraButtonPos[ 0 ] + pCurCameraButton->GetWide() - pCurOptionsPanel->GetWide() - XRES( 3 ),
+ aBottomPos[ 1 ] + aCameraButtonPos[ 1 ] - pCurOptionsPanel->GetTall()
+ );
+ }
+
+ // Setup menu position relative to menu button
+ int aMenuButtonPos[2];
+ m_pMenuButton->GetPos( aMenuButtonPos[0], aMenuButtonPos[1] );
+ m_pMenu->SetPos( aMenuButtonPos[0], aMenuButtonPos[1] + m_pMenuButton->GetTall() );
+
+ // Set player cell panel to be the size of half the bottom panel
+ int aBottomSize[2];
+ m_pBottom->GetSize( aBottomSize[0], aBottomSize[1] );
+ m_pPlayerCellsPanel->SetBounds( 0, 0, aBottomSize[0] / 2, m_pPlayerCellsPanel->GetTall() );
+
+ CExLabel *pRedBlueLabels[2] = {
+ dynamic_cast< CExLabel * >( m_pPlayerCellsPanel->FindChildByName( "RedLabel" ) ),
+ dynamic_cast< CExLabel * >( m_pPlayerCellsPanel->FindChildByName( "BlueLabel" ) )
+ };
+ int nMargins[2] = { XRES( 5 ), YRES( 2 ) };
+ for ( int i = 0; i < 2; ++i )
+ {
+ pRedBlueLabels[i]->SizeToContents();
+
+ const int nY = m_pPlayerCellsPanel->GetTall()/2 + m_nRedBlueSigns[i] * m_pPlayerCellsPanel->GetTall()/4 - pRedBlueLabels[i]->GetTall()/2;
+ pRedBlueLabels[i]->SetPos( nMargins[0], nY );
+
+ m_nRedBlueLabelRightX = MAX( m_nRedBlueLabelRightX, nMargins[0] + pRedBlueLabels[i]->GetWide() + nMargins[0] );
+ }
+
+ // Position player cells
+ LayoutPlayerCells();
+
+ BaseClass::PerformLayout();
+}
+
+bool CReplayPerformanceEditorPanel::OnStateChangeRequested( const char *pEventStr )
+{
+ // If we're already recording, allow the change.
+ if ( g_pReplayPerformanceController->IsRecording() )
+ return true;
+
+ // If we aren't recording and there is no forthcoming data in the playback stream, allow the change.
+ if ( !g_pReplayPerformanceController->IsPlaybackDataLeft() )
+ return true;
+
+ // Otherwise, record the event string and show a dialog asking the user if they're sure they want to nuke.
+ V_strncpy( m_szSuspendedEvent, pEventStr, sizeof( m_szSuspendedEvent ) );
+ ShowConfirmDialog( "#Replay_Warning", "#Replay_NukePerformanceChanges", "#GameUI_Confirm", "#GameUI_CancelBold", OnConfirmDestroyChanges, this, this, REPLAY_SOUND_DIALOG_POPUP );
+
+ return false;
+}
+
+void CReplayPerformanceEditorPanel::SetButtonTip( wchar_t *pTipText, Panel *pContextPanel )
+{
+ // Set the text
+ m_pButtonTip->SetText( pTipText );
+ m_pButtonTip->InvalidateLayout( true, true );
+
+ // Center relative to context panel
+ int aPos[2];
+ ipanel()->GetAbsPos( pContextPanel->GetVPanel(), aPos[0], aPos[1] );
+ const int nX = clamp(
+ aPos[0] - m_pButtonTip->GetWide() / 2,
+ 0,
+ ScreenWidth() - m_pButtonTip->GetWide() - (int) XRES( 40 )
+ );
+ const int nY = m_nBottomPanelStartY - m_pButtonTip->GetTall() - (int) YRES( 2 );
+ m_pButtonTip->SetPos( nX, nY );
+}
+
+void CReplayPerformanceEditorPanel::ShowButtonTip( bool bShow )
+{
+ m_pButtonTip->SetVisible( bShow );
+}
+
+void CReplayPerformanceEditorPanel::ShowSavingDialog()
+{
+ Assert( !m_pSavingDlg );
+ m_pSavingDlg = new CSavingDialog( ReplayUI_GetPerformanceEditor() );
+ ShowWaitingDialog( m_pSavingDlg, "#Replay_Saving", true, false, -1 );
+}
+
+void CReplayPerformanceEditorPanel::ShowPanel( bool bShow )
+{
+ if ( bShow == IsVisible() )
+ return;
+
+ if ( bShow )
+ {
+ // We are now performing.
+ m_pRecLightPanel->SetPerforming( true );
+
+ // Disable keyboard input on all panels added to the list
+ FOR_EACH_LL( m_lstDisableKeyboardInputPanels, it )
+ {
+ m_lstDisableKeyboardInputPanels[ it ]->SetKeyBoardInputEnabled( false );
+ }
+
+ DisplayPerformanceTip( "#Replay_PerfTip_ExitPerfMode", &replay_perftip_count_exit, MAX_TIP_DISPLAYS );
+
+ // Fire a message the game DLL can intercept (for achievements, etc).
+ IGameEvent *event = gameeventmanager->CreateEvent( "entered_performance_mode" );
+ if ( event )
+ {
+ gameeventmanager->FireEventClientSide( event );
+ }
+
+ // Play a sound
+ surface()->PlaySound( "replay\\enterperformancemode.wav" );
+ }
+ else
+ {
+ // Display a tip
+ DisplayPerformanceTip( "#Replay_PerfTip_EnterPerfMode", &replay_perftip_count_enter, MAX_TIP_DISPLAYS );
+
+ // Play a sound
+ surface()->PlaySound( "replay\\exitperformancemode.wav" );
+ }
+
+ // Show mouse cursor
+ SetMouseInputEnabled( bShow );
+ SetVisible( bShow );
+ MakePopup( bShow );
+
+ // Avoid waiting for next OnThink() to hide background images
+ m_pRecLightPanel->UpdatePauseState( bShow );
+ m_pRecLightPanel->UpdateBackgroundVisibility();
+
+ // Play or pause
+ if ( bShow )
+ {
+ PauseDemo();
+ }
+ else
+ {
+ PlayDemo();
+ }
+
+ // Keep controller informed about pause state so that it can throw away unimportant events during pause if it's recording.
+ g_pReplayPerformanceController->NotifyPauseState( bShow );
+}
+
+bool CReplayPerformanceEditorPanel::OnEndOfReplayReached()
+{
+ if ( m_bShownAtLeastOnce )
+ {
+ ShowPanel( true );
+ DisplayPerformanceTip( "#Replay_PerfTip_EndOfReplayReached" );
+
+ // Don't end demo playback yet.
+ return true;
+ }
+
+ // Let the demo player end demo playback
+ return false;
+}
+
+void CReplayPerformanceEditorPanel::AddSetViewEvent()
+{
+ if ( !g_pReplayManager->GetPlayingReplay() )
+ return;
+
+ if ( !g_pReplayPerformanceController )
+ return;
+
+ Vector pos;
+ QAngle angles;
+ float fov;
+ ReplayCamera()->GetCachedView( pos, angles, fov );
+
+ SetViewParams_t params;
+ params.m_flTime = GetPlaybackTime();
+ params.m_flFov = fov;
+ params.m_pOrigin = &pos;
+ params.m_pAngles = &angles;
+
+ params.m_flAccel = ReplayCamera()->m_flRoamingAccel;
+ params.m_flSpeed = ReplayCamera()->m_flRoamingSpeed;
+ params.m_flRotationFilter = ReplayCamera()->m_flRoamingRotFilterFactor;
+
+ g_pReplayPerformanceController->AddEvent_Camera_SetView( params );
+}
+
+// Input should be in [0,1]
+void CReplayPerformanceEditorPanel::AddTimeScaleEvent( float flTimeScale )
+{
+ if ( !g_pReplayManager->GetPlayingReplay() )
+ return;
+
+ if ( !g_pReplayPerformanceController )
+ return;
+
+ g_pReplayPerformanceController->AddEvent_TimeScale( GetPlaybackTime(), flTimeScale );
+}
+
+void CReplayPerformanceEditorPanel::UpdateCameraButtonImages( bool bForceUnselected/*=false*/ )
+{
+ CReplayPerformance *pPerformance = GetPerformance();
+ for ( int i = 0; i < NCAMS; ++i )
+ {
+ CFmtStr fmtFile(
+ gs_pBaseComponentNames[i],
+ gs_pCamNames[i],
+ ( !bForceUnselected && ( !pPerformance || g_pReplayPerformanceController->IsRecording() ) && i == m_iCameraSelection ) ? "_selected" : ""
+ );
+
+ if ( m_pCameraButtons[ i ] )
+ {
+ m_pCameraButtons[ i ]->SetSubImage( fmtFile.Access() );
+ }
+ }
+}
+
+void CReplayPerformanceEditorPanel::EnsureRecording( bool bShouldSnip )
+{
+ // Not recording?
+ if ( !g_pReplayPerformanceController->IsRecording() )
+ {
+ // Start recording - snip if needed.
+ g_pReplayPerformanceController->StartRecording( GetReplay(), bShouldSnip );
+ }
+}
+
+void CReplayPerformanceEditorPanel::ToggleMenu()
+{
+ if ( !m_pMenu )
+ return;
+
+ // Show/hide
+ const bool bShow = !m_pMenu->IsVisible();
+ m_pMenu->SetVisible( bShow );
+}
+
+void CReplayPerformanceEditorPanel::SaveAs( const wchar_t *pTitle )
+{
+ if ( !g_pReplayPerformanceController->SaveAsAsync( pTitle ) )
+ {
+ DisplaySavedTip( false );
+ }
+
+ ShowSavingDialog();
+}
+
+/*static*/ void CReplayPerformanceEditorPanel::OnConfirmSaveAs( bool bShouldSave, wchar_t *pTitle, void *pContext )
+{
+ // NOTE: Assumes that overwriting has already been confirmed by the user.
+
+ if ( !bShouldSave )
+ return;
+
+ CReplayPerformanceEditorPanel *pThis = (CReplayPerformanceEditorPanel *)pContext;
+ pThis->SaveAs( pTitle );
+
+ surface()->PlaySound( "replay\\saved_take.wav" );
+}
+
+void CReplayPerformanceEditorPanel::ShowRewindConfirmMessage()
+{
+ ShowMessageBox( "#Replay_RewindWarningTitle", "#Replay_RewindWarningMsg", "#GameUI_OK", OnConfirmRewind, NULL, (void *)this );
+ surface()->PlaySound( "replay\\replaydialog_warn.wav" );
+}
+
+/*static*/ void CReplayPerformanceEditorPanel::OnConfirmRewind( bool bConfirmed, void *pContext )
+{
+ if ( bConfirmed )
+ {
+ if ( pContext )
+ {
+ CReplayPerformanceEditorPanel *pEditor = (CReplayPerformanceEditorPanel *)pContext;
+ pEditor->OnCommand( "goto_back" );
+ }
+ }
+}
+
+void CReplayPerformanceEditorPanel::OnMenuCommand_Save( bool bExitEditorWhenDone/*=false*/ )
+{
+ // If this is the first time we're saving this performance, do a save-as.
+ if ( !g_pReplayPerformanceController->HasSavedPerformance() )
+ {
+ OnMenuCommand_SaveAs( bExitEditorWhenDone );
+ return;
+ }
+
+ // Regular save
+ if ( !g_pReplayPerformanceController->SaveAsync() )
+ {
+ DisplaySavedTip( false );
+ }
+
+ // Show saving dialog
+ ShowSavingDialog();
+
+ // Exit editor?
+ if ( bExitEditorWhenDone )
+ {
+ OnMenuCommand_Exit();
+ }
+}
+
+void CReplayPerformanceEditorPanel::OnMenuCommand_SaveAs( bool bExitEditorWhenDone/*=false*/ )
+{
+ ReplayUI_ShowPerformanceSaveDlg( OnConfirmSaveAs, this, GetReplay(), bExitEditorWhenDone );
+}
+
+void CReplayPerformanceEditorPanel::DisplaySavedTip( bool bSucceess )
+{
+ DisplayPerformanceTip( bSucceess ? "#Replay_PerfTip_Saved" : "#Replay_PerfTip_SaveFailed" );
+}
+
+void CReplayPerformanceEditorPanel::OnSaveComplete()
+{
+ DisplaySavedTip( g_pReplayPerformanceController->GetLastSaveStatus() );
+
+ m_pSavingDlg = NULL;
+}
+
+void CReplayPerformanceEditorPanel::HandleUiToggle()
+{
+ if ( !TFModalStack()->IsEmpty() )
+ return;
+
+ PauseDemo();
+ Exit_ShowDialogs();
+}
+
+void CReplayPerformanceEditorPanel::Exit()
+{
+ engine->ClientCmd_Unrestricted( "disconnect" );
+}
+
+void CReplayPerformanceEditorPanel::Exit_ShowDialogs()
+{
+ if ( g_pReplayPerformanceController->IsDirty() )
+ {
+ ShowConfirmDialog( "#Replay_DiscardTitle", "#Replay_DiscardChanges", "#Replay_Discard", "#Replay_Cancel", OnConfirmDiscard, NULL, this, REPLAY_SOUND_DIALOG_POPUP );
+ }
+ else
+ {
+ ShowConfirmDialog( "#Replay_ExitEditorTitle", "#Replay_BackToReplays", "#GameUI_Confirm", "#Replay_Cancel", OnConfirmExit, NULL, this, REPLAY_SOUND_DIALOG_POPUP );
+ }
+}
+
+void CReplayPerformanceEditorPanel::OnMenuCommand_Exit()
+{
+ Exit_ShowDialogs();
+}
+
+void CReplayPerformanceEditorPanel::OnCommand( const char *command )
+{
+ float flCurTime = GetPlaybackTime();
+
+ g_bIsReplayRewinding = false;
+
+ if ( !V_stricmp( command, "toggle_menu" ) )
+ {
+ ToggleMenu();
+ }
+ else if ( !V_strnicmp( command, "menu_", 5 ) )
+ {
+ const char *pMenuCommand = command + 5;
+
+ if ( !V_stricmp( pMenuCommand, "save" ) )
+ {
+ OnMenuCommand_Save();
+ }
+ else if ( !V_stricmp( pMenuCommand, "saveas" ) )
+ {
+ OnMenuCommand_SaveAs();
+ }
+ else if ( !V_stricmp( pMenuCommand, "exit" ) )
+ {
+ OnMenuCommand_Exit();
+ }
+ }
+ else if ( !V_stricmp( command, "close" ) )
+ {
+ ShowPanel( false );
+ MarkForDeletion();
+ return;
+ }
+ else if ( !V_stricmp( command, "play" ) )
+ {
+ ShowPanel( false );
+ return;
+ }
+ else if ( !V_stricmp( command, "pause" ) )
+ {
+ ShowPanel( true );
+ return;
+ }
+ else if ( !V_strnicmp( command, "timescale_", 10 ) )
+ {
+ const char *pTimeScaleCmd = command + 10;
+ if ( !V_stricmp( pTimeScaleCmd, "showpanel" ) )
+ {
+ // If we're playing back, pop up a dialog asking if the user is sure they want to nuke the
+ // rest of whatever is playing back.
+ if ( !OnStateChangeRequested( command ) )
+ return;
+
+ EnsureRecording();
+ }
+ }
+ else if ( !V_strnicmp( command, "settick_", 8 ) )
+ {
+ const char *pSetType = command + 8;
+ const int nCurTick = engine->GetDemoPlaybackTick();
+
+ if ( !V_stricmp( pSetType, "in" ) )
+ {
+ SetOrRemoveInTick( nCurTick, true );
+ }
+ else if ( !V_stricmp( pSetType, "out" ) )
+ {
+ SetOrRemoveOutTick( nCurTick, true );
+ }
+
+ // Save the replay
+ CReplay *pReplay = GetReplay();
+ if ( pReplay )
+ {
+ g_pReplayManager->FlagReplayForFlush( pReplay, true );
+ }
+
+ return;
+ }
+ else if ( !V_strnicmp( command, "goto_", 5 ) )
+ {
+ const char *pGotoType = command + 5;
+ CReplay *pReplay = GetReplay();
+ if ( pReplay )
+ {
+ const CReplayPerformance *pScratchPerformance = g_pReplayPerformanceController->GetPerformance();
+ const CReplayPerformance *pSavedPerformance = g_pReplayPerformanceController->GetSavedPerformance();
+ const CReplayPerformance *pPerformance = pScratchPerformance ? pScratchPerformance : pSavedPerformance;
+
+ const int nCurTick = engine->GetDemoPlaybackTick();
+
+ // If in or out ticks are set in the performance, use those for the 'full' rewind/fast-forward
+ const int nStartTick = MAX( 0, ( pPerformance && pPerformance->HasInTick() ) ? pPerformance->m_nTickIn : pReplay->m_nSpawnTick );
+ const int nEndTick = MAX( // The MAX() here will keep us from going back in time if we're already past the "end" tick
+ nCurTick,
+ ( ( pPerformance && pPerformance->HasOutTick() ) ?
+ pPerformance->m_nTickOut :
+ ( nStartTick + TIME_TO_TICKS( pReplay->m_flLength ) ) )
+ - TIME_TO_TICKS( 0.1f )
+ );
+
+ int nGotoTick = 0;
+ bool bGoingBack = false;
+
+ if ( !V_stricmp( pGotoType, "start" ) )
+ {
+ bGoingBack = true;
+ nGotoTick = nStartTick;
+ }
+ else if ( !V_stricmp( pGotoType, "back" ) )
+ {
+ // If this is the first time rewinding, display a message
+ if ( !replay_replayeditor_rewindmsgcounter.GetBool() )
+ {
+ replay_replayeditor_rewindmsgcounter.SetValue( 1 );
+ ShowRewindConfirmMessage();
+ return;
+ }
+
+ bGoingBack = true;
+ nGotoTick = nCurTick - TIME_TO_TICKS( 10.0f );
+ }
+ else if ( !V_stricmp( pGotoType, "end" ) )
+ {
+ nGotoTick = nEndTick; // Don't go back in time
+ }
+
+ // Clamp it
+ nGotoTick = clamp( nGotoTick, nStartTick, nEndTick );
+
+ // If going back...
+ if ( bGoingBack )
+ {
+ // ...and notify the recorder that we're skipping, which we only need to do if we're going backwards
+ g_pReplayPerformanceController->NotifyRewinding();
+ g_bIsReplayRewinding = true;
+ }
+
+ // Go to the given tick and pause
+ CFmtStr fmtCmd( "demo_gototick %i\ndemo_pause\n", nGotoTick );
+ engine->ClientCmd_Unrestricted( fmtCmd.Access() );
+ }
+ return;
+ }
+ else if ( !V_strnicmp( command, "setcamera_", 10 ) )
+ {
+ const char *pCamType = command + 10;
+ int nEntIndex = ReplayCamera()->GetPrimaryTargetIndex();
+
+ // If we're playing back, pop up a dialog asking if the user is sure they want to nuke the
+ // rest of whatever is playing back.
+ if ( !OnStateChangeRequested( command ) )
+ return;
+
+ EnsureRecording();
+
+ if ( !V_stricmp( pCamType, "first" ) )
+ {
+ ReplayCamera()->SetMode( OBS_MODE_IN_EYE );
+ UpdateCameraSelectionPosition( CAM_FIRST );
+ g_pReplayPerformanceController->AddEvent_Camera_Change_FirstPerson( flCurTime, nEntIndex );
+ }
+ else if ( !V_stricmp( pCamType, "third" ) )
+ {
+ ReplayCamera()->SetMode( OBS_MODE_CHASE );
+ UpdateCameraSelectionPosition( CAM_THIRD );
+ g_pReplayPerformanceController->AddEvent_Camera_Change_ThirdPerson( flCurTime, nEntIndex );
+ AddSetViewEvent();
+ }
+ else if ( !V_stricmp( pCamType, "free" ) )
+ {
+ ReplayCamera()->SetMode( OBS_MODE_ROAMING );
+ UpdateCameraSelectionPosition( CAM_FREE );
+ g_pReplayPerformanceController->AddEvent_Camera_Change_Free( flCurTime );
+ AddSetViewEvent();
+ DisplayPerformanceTip( "#Replay_PerfTip_EnterFreeCam", &replay_perftip_count_freecam_enter, MAX_TIP_DISPLAYS );
+ }
+
+ return;
+ }
+ else
+ {
+ engine->ClientCmd( const_cast<char *>( command ) );
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+void CReplayPerformanceEditorPanel::OnConfirmDestroyChanges( bool bConfirmed, void *pContext )
+{
+ AssertMsg( pContext, "Should have a context! Fix me!" );
+ if ( pContext && bConfirmed )
+ {
+ CReplayPerformanceEditorPanel *pEditorPanel = (CReplayPerformanceEditorPanel *)pContext;
+ if ( bConfirmed )
+ {
+ CReplay *pReplay = pEditorPanel->GetReplay();
+ g_pReplayPerformanceController->StartRecording( pReplay, true );
+
+ // Reissue the command.
+ pEditorPanel->OnCommand( pEditorPanel->m_szSuspendedEvent );
+
+ // Play a sound
+ surface()->PlaySound( "replay\\snip.wav" );
+ }
+
+ // Clear suspended event
+ pEditorPanel->m_szSuspendedEvent[ 0 ] = '\0';
+
+ // Make sure mouse is free
+ pEditorPanel->SetMouseInputEnabled( true );
+
+ DisplayPerformanceTip( "#Replay_PerfTip_Snip" );
+ }
+}
+
+/*static*/ void CReplayPerformanceEditorPanel::OnConfirmDiscard( bool bConfirmed, void *pContext )
+{
+ CReplayPerformanceEditorPanel *pEditor = (CReplayPerformanceEditorPanel *)pContext;
+ if ( bConfirmed )
+ {
+ pEditor->Exit();
+ }
+ else
+ {
+ if ( !pEditor->IsVisible() )
+ {
+ PlayDemo();
+ }
+ }
+}
+
+/*static*/ void CReplayPerformanceEditorPanel::OnConfirmExit( bool bConfirmed, void *pContext )
+{
+ CReplayPerformanceEditorPanel *pEditor = (CReplayPerformanceEditorPanel *)pContext;
+ if ( bConfirmed )
+ {
+ pEditor->Exit();
+ }
+ else
+ {
+ if ( !pEditor->IsVisible() )
+ {
+ PlayDemo();
+ }
+ }
+}
+
+void CReplayPerformanceEditorPanel::SetOrRemoveInTick( int nTick, bool bRemoveIfSet )
+{
+ SetOrRemoveTick( nTick, true, bRemoveIfSet );
+}
+
+void CReplayPerformanceEditorPanel::SetOrRemoveOutTick( int nTick, bool bRemoveIfSet )
+{
+ SetOrRemoveTick( nTick, false, bRemoveIfSet );
+}
+
+void CReplayPerformanceEditorPanel::SetOrRemoveTick( int nTick, bool bUseInTick, bool bRemoveIfSet )
+{
+ CReplayPerformance *pPerformance = GetPerformance();
+ AssertMsg( pPerformance, "Performance should always be valid by this point." );
+
+ ControlButtons_t iButton;
+ int *pResultTick;
+ const char *pSetTickKey;
+ const char *pUnsetTickKey;
+ if ( bUseInTick )
+ {
+ pResultTick = &pPerformance->m_nTickIn;
+ iButton = CTRLBUTTON_IN;
+ pSetTickKey = "#Replay_PerfTip_InPointSet";
+ pUnsetTickKey = "#Replay_PerfTip_InPointRemoved";
+ }
+ else
+ {
+ pResultTick = &pPerformance->m_nTickOut;
+ iButton = CTRLBUTTON_OUT;
+ pSetTickKey = "#Replay_PerfTip_OutPointSet";
+ pUnsetTickKey = "#Replay_PerfTip_OutPointRemoved";
+ }
+
+ // Tick explicitly being removed? Caller passing in -1?
+ const bool bRemoving = nTick < 0;
+
+ // If tick already exists and we want to remove, remove it
+ bool bSetting;
+ if ( ( *pResultTick >= 0 && bRemoveIfSet ) || bRemoving )
+ {
+ *pResultTick = -1;
+ bSetting = false;
+ }
+ else
+ {
+ *pResultTick = nTick;
+ bSetting = true;
+ }
+
+ // Display the appropriate tip
+ DisplayPerformanceTip( bSetting ? pSetTickKey : pUnsetTickKey );
+
+ // Select/unselect button
+ CExImageButton *pButton = m_pCtrlButtons[ iButton ];
+ pButton->SetSelected( bSetting );
+ pButton->InvalidateLayout( true, true ); // Without this, buttons don't update immediately
+
+ // Mark the performance as dirty
+ g_pReplayPerformanceController->NotifyDirty();
+}
+
+CReplay *CReplayPerformanceEditorPanel::GetReplay()
+{
+ return g_pReplayManager->GetReplay( m_hReplay );
+}
+
+void CReplayPerformanceEditorPanel::OnRewindComplete()
+{
+ // Get rid of any "selected" icon - this will happen as soon as we actually start playing back
+ // events, but if we aren't playing back events yet we need to explicitly tell the icons not
+ // to display their "selected" versions.
+ UpdateCameraButtonImages( true );
+}
+
+//-----------------------------------------------------------------------------
+
+static DHANDLE<CReplayPerformanceEditorPanel> g_ReplayPerformanceEditorPanel;
+
+//-----------------------------------------------------------------------------
+
+CReplayPerformanceEditorPanel *ReplayUI_InitPerformanceEditor( ReplayHandle_t hReplay )
+{
+ if ( !g_ReplayPerformanceEditorPanel.Get() )
+ {
+ g_ReplayPerformanceEditorPanel = SETUP_PANEL( new CReplayPerformanceEditorPanel( NULL, hReplay ) );
+ g_ReplayPerformanceEditorPanel->InvalidateLayout( false, true );
+ }
+
+ // Notify recorder of editor
+ g_pReplayPerformanceController->SetEditor( g_ReplayPerformanceEditorPanel.Get() );
+
+ return g_ReplayPerformanceEditorPanel;
+}
+
+void ReplayUI_ClosePerformanceEditor()
+{
+ if ( g_ReplayPerformanceEditorPanel )
+ {
+ g_ReplayPerformanceEditorPanel->MarkForDeletion();
+ g_ReplayPerformanceEditorPanel = NULL;
+ }
+}
+
+CReplayPerformanceEditorPanel *ReplayUI_GetPerformanceEditor()
+{
+ return g_ReplayPerformanceEditorPanel;
+}
+
+#if _DEBUG
+CON_COMMAND_F( replay_showperfeditor, "Show performance editor", FCVAR_CLIENTDLL )
+{
+ ReplayUI_ClosePerformanceEditor();
+ ReplayUI_InitPerformanceEditor( REPLAY_HANDLE_INVALID );
+}
+
+CON_COMMAND_F( replay_tiptest, "", FCVAR_CLIENTDLL )
+{
+ DisplayPerformanceTip( "#Replay_PerfTip_EnterFreeCam" );
+}
+#endif
+
+#endif
diff --git a/mp/src/game/client/replay/vgui/replayperformanceeditor.h b/mp/src/game/client/replay/vgui/replayperformanceeditor.h new file mode 100644 index 00000000..1fe11f20 --- /dev/null +++ b/mp/src/game/client/replay/vgui/replayperformanceeditor.h @@ -0,0 +1,239 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#if defined( REPLAY_ENABLED )
+
+#ifndef REPLAYPERFORMANCEEDITOR_H
+#define REPLAYPERFORMANCEEDITOR_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/ImagePanel.h"
+#include "vgui_controls/ImageList.h"
+#include "tf/vgui/tf_controls.h"
+#include "replay/replayhandle.h"
+#include "replay/ireplayperformanceeditor.h"
+#include "replay/ireplayperformancecontroller.h"
+
+//-----------------------------------------------------------------------------
+
+class CPlayerCell;
+class CCameraOptionsPanel;
+class CRecLightPanel;
+class CReplay;
+class CReplayPerformance;
+class CReplayTipLabel;
+class CSavingDialog;
+
+//-----------------------------------------------------------------------------
+
+// NOTE: Should not change order here - if you do, you need to modify g_pCamNames.
+enum CameraMode_t
+{
+ CAM_INVALID = -1,
+ CAM_FREE,
+ CAM_THIRD,
+ CAM_FIRST,
+ COMPONENT_TIMESCALE,
+ NCAMS
+};
+
+//-----------------------------------------------------------------------------
+
+class CReplayPerformanceEditorPanel : public vgui::EditablePanel,
+ public IReplayPerformanceEditor
+{
+ DECLARE_CLASS_SIMPLE( CReplayPerformanceEditorPanel, vgui::EditablePanel );
+public:
+ CReplayPerformanceEditorPanel( Panel *parent, ReplayHandle_t hReplay );
+ virtual ~CReplayPerformanceEditorPanel();
+
+ virtual void ShowPanel( bool bShow );
+
+ bool OnEndOfReplayReached();
+ void OnInGameMouseWheelEvent( int nDelta );
+ void UpdateCameraSelectionPosition( CameraMode_t nCameraMode );
+ void UpdateFreeCamSettings( const SetViewParams_t ¶ms );
+ void UpdateTimeScale( float flScale );
+ void HandleUiToggle();
+ void Exit();
+ void Exit_ShowDialogs();
+
+private:
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void ApplySettings( KeyValues *pInResourceData );
+ virtual void PerformLayout();
+ virtual void OnCommand( const char *command );
+ virtual void OnMouseWheeled( int nDelta );
+ virtual void OnTick();
+
+ void Achievements_Think( float flElapsed );
+ void Achievements_OnSpaceBarPressed();
+ void Achievements_Grant();
+
+ friend class CReplayButton;
+ friend class CSavingDialog;
+
+ void SetButtonTip( wchar_t *pTipText, Panel *pContextPanel );
+ void ShowButtonTip( bool bShow );
+
+ void ShowSavingDialog();
+
+ //
+ // IReplayPerformanceEditor:
+ //
+ virtual CReplay *GetReplay();
+ virtual void OnRewindComplete();
+
+ // Called when the user attempts to change to a different camera, etc.
+ // Returns true if request is immediately granted - false means the event
+ // was queued and the user has been asked if they are OK with nuking any
+ // changes after the current time.
+ bool OnStateChangeRequested( const char *pEventStr );
+
+ void EnsureRecording( bool bShouldSnip = true ); // Start recording now if not already doing so
+
+ bool IsPaused();
+
+ void UpdateCameraButtonImages( bool bForceUseUnselected = false );
+ void LayoutPlayerCells();
+ void SetupHighlightPanel( EditablePanel *pPanel, CPlayerCell *pPlayerCell );
+ void UpdateTimeLabels();
+ void ClearPlayerCellData();
+
+ void HandleMouseWheel( int nDelta );
+
+private:
+ enum ControlButtons_t
+ {
+ CTRLBUTTON_IN,
+ CTRLBUTTON_GOTOBEGINNING,
+ CTRLBUTTON_REWIND,
+ CTRLBUTTON_PLAY,
+ CTRLBUTTON_FF,
+ CTRLBUTTON_GOTOEND,
+ CTRLBUTTON_OUT,
+
+ NUM_CTRLBUTTONS
+ };
+
+ CReplayPerformance *GetPerformance() const;
+ CReplayPerformance *GetSavedPerformance() const;
+
+ int GetCameraModeFromButtonIndex( CameraMode_t iCamera );
+ void AddSetViewEvent();
+ void AddTimeScaleEvent( float flTimeScale );
+ void AddPanelKeyboardInputDisableList( vgui::Panel *pPanel );
+ CameraMode_t IsMouseOverActiveCameraOptionsPanel( int nMouseX, int nMouseY );
+ void SetOrRemoveInTick( int nTick, bool bRemoveIfSet );
+ void SetOrRemoveOutTick( int nTick, bool bRemoveIfSet );
+ void SetOrRemoveTick( int nTick, bool bUseInTick, bool bRemoveIfSet );
+ void ToggleMenu();
+ void OnMenuCommand_Save( bool bExitEditorWhenDone = false );
+ void OnMenuCommand_SaveAs( bool bExitEditorWhenDone = false );
+ void OnMenuCommand_Exit();
+ void DisplaySavedTip( bool bSucceess );
+ void OnSaveComplete();
+
+ void SaveAs( const wchar_t *pTitle );
+
+ void ShowRewindConfirmMessage();
+
+ static void OnConfirmSaveAs( bool bShouldSave, wchar_t *pTitle, void *pContext );
+ static void OnConfirmDestroyChanges( bool bConfirmed, void *pContext );
+ static void OnConfirmDiscard( bool bConfirmed, void *pContext );
+ static void OnConfirmExit( bool bConfirmed, void *pContext );
+ static void OnConfirmRewind( bool bConfirmed, void *pContext );
+
+ MESSAGE_FUNC_PARAMS( OnSliderMoved, "SliderMoved", pParams );
+
+ ReplayHandle_t m_hReplay;
+
+ float m_flLastTime; // Can't use gpGlobals->frametime when playback is paused
+ float m_flOldFps;
+
+ CExLabel *m_pCurTimeLabel;
+ CExLabel *m_pTotalTimeLabel;
+ CExLabel *m_pPlayerNameLabel;
+
+ KeyValues *m_pPlayerCellData;
+ CPlayerCell *m_pPlayerCells[2][MAX_PLAYERS+1];
+ vgui::ImageList *m_pImageList;
+
+ EditablePanel *m_pMouseTargetPanel;
+ EditablePanel *m_pBottom;
+ CPlayerCell *m_pCurTargetCell;
+
+ CExImageButton *m_pCameraButtons[NCAMS];
+ CExImageButton *m_pCtrlButtons[NUM_CTRLBUTTONS];
+
+ float m_flTimeScaleProxy;
+
+ EditablePanel *m_pPlayerCellsPanel;
+
+ vgui::ImagePanel *m_pCameraSelection;
+ CameraMode_t m_iCameraSelection; // NOTE: Indexes into some arrays
+
+ CReplayTipLabel *m_pButtonTip;
+ CSavingDialog *m_pSavingDlg;
+
+ enum MenuItems_t
+ {
+ MENU_SAVE,
+ MENU_SAVEAS,
+ MENU_EXIT,
+
+ NUM_MENUITEMS
+ };
+
+ CExImageButton *m_pMenuButton;
+ vgui::Menu *m_pMenu;
+ int m_aMenuItemIds[ NUM_MENUITEMS ];
+
+ CExButton *m_pSlowMoButton;
+
+ CCameraOptionsPanel *m_pCameraOptionsPanels[NCAMS];
+
+ CUtlLinkedList< vgui::Panel *, int > m_lstDisableKeyboardInputPanels;
+
+ int m_nRedBlueLabelRightX;
+ int m_nBottomPanelStartY;
+ int m_nBottomPanelHeight;
+ int m_nRedBlueSigns[2];
+ int m_iCurPlayerTarget;
+ float m_flSpaceDownStart; // The time at which user started holding down space bar
+ bool m_bSpaceDown;
+ bool m_bSpacePressed;
+ int m_nLastRoundedTime;
+ bool m_bMousePressed;
+ bool m_bMouseDown;
+ float m_flDefaultFramerate; // host_framerate before perf editor started mucking about with it
+ CameraMode_t m_nMouseClickedOverCameraSettingsPanel; // Allows user to drag slider outside of camera settings panel w/o the panel disappearing
+ CRecLightPanel *m_pRecLightPanel;
+ bool m_bShownAtLeastOnce; // Has the replay editor shown at least once? In other words, has the user hit the space bar at all yet?
+ char m_szSuspendedEvent[128];
+
+ bool m_bAchievementAwarded; // Was an achievement awarded during this editing session?
+ float m_flLastTimeSpaceBarPressed;
+ float m_flActiveTimeInEditor; // Will be zero'd out if user is idle (ie if they don't press space bar often enough)
+
+ CPanelAnimationVarAliasType( int, m_nRightMarginWidth, "right_margin_width", "0", "proportional_xpos" );
+};
+
+//-----------------------------------------------------------------------------
+
+CReplayPerformanceEditorPanel *ReplayUI_InitPerformanceEditor( ReplayHandle_t hReplay );
+CReplayPerformanceEditorPanel *ReplayUI_GetPerformanceEditor();
+void ReplayUI_ClosePerformanceEditor();
+
+//-----------------------------------------------------------------------------
+
+#endif // REPLAYPERFORMANCEEDITOR_H
+
+#endif
\ No newline at end of file diff --git a/mp/src/game/client/replay/vgui/replayperformancesavedlg.cpp b/mp/src/game/client/replay/vgui/replayperformancesavedlg.cpp new file mode 100644 index 00000000..f7352a77 --- /dev/null +++ b/mp/src/game/client/replay/vgui/replayperformancesavedlg.cpp @@ -0,0 +1,261 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "replayperformancesavedlg.h"
+#include "replay/performance.h"
+#include "replay/ireplaymanager.h"
+#include "replay/ireplayperformancecontroller.h"
+#include "replay/replay.h"
+#include "econ/confirm_dialog.h"
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/TextEntry.h"
+#include "vgui_controls/TextImage.h"
+#include "vgui/ISurface.h"
+#include "replay/replaycamera.h"
+#include "replayperformanceeditor.h"
+
+//-----------------------------------------------------------------------------
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+// Purpose: Player input dialog for a replay
+//-----------------------------------------------------------------------------
+class CReplayPerformanceSaveDlg : public EditablePanel
+{
+private:
+ DECLARE_CLASS_SIMPLE( CReplayPerformanceSaveDlg, EditablePanel );
+
+public:
+ CReplayPerformanceSaveDlg( Panel *pParent, const char *pName,
+ OnConfirmSaveCallback pfnCallback, void *pContext, CReplay *pReplay, bool bExitEditorWhenDone );
+ ~CReplayPerformanceSaveDlg();
+
+ static void Show( OnConfirmSaveCallback pfnCallback, void *pContext, CReplay *pReplay,
+ bool bExitEditorWhenDone );
+
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void PerformLayout();
+ virtual void OnCommand( const char *command );
+ virtual void OnKeyCodePressed( KeyCode code );
+ virtual void OnKeyCodeTyped( KeyCode code );
+
+ bool ConfirmOverwriteOrSaveNow();
+ void CloseWindow();
+
+ static void OnConfirmOverwrite( bool bConfirm, void *pContext );
+
+ MESSAGE_FUNC( OnSetFocus, "SetFocus" );
+
+ static vgui::DHANDLE< CReplayPerformanceSaveDlg > ms_hDlg;
+
+private:
+ OnConfirmSaveCallback m_pfnCallback;
+ void *m_pContext;
+ Panel *m_pDlg;
+ CReplay *m_pReplay;
+ TextEntry *m_pTitleEntry;
+ bool m_bExitEditorWhenDone;
+ wchar_t m_wszTitle[ MAX_TAKE_TITLE_LENGTH ];
+};
+
+vgui::DHANDLE< CReplayPerformanceSaveDlg > CReplayPerformanceSaveDlg::ms_hDlg;
+
+//-----------------------------------------------------------------------------
+// Purpose: CReplayPerformanceSaveDlg implementation
+//-----------------------------------------------------------------------------
+CReplayPerformanceSaveDlg::CReplayPerformanceSaveDlg( Panel *pParent, const char *pName,
+ OnConfirmSaveCallback pfnCallback, void *pContext,
+ CReplay *pReplay, bool bExitEditorWhenDone )
+: BaseClass( pParent, pName ),
+ m_pfnCallback( pfnCallback ),
+ m_pContext( pContext ),
+ m_pReplay( pReplay ),
+ m_bExitEditorWhenDone( bExitEditorWhenDone ),
+ m_pDlg( NULL ),
+ m_pTitleEntry( NULL )
+{
+ Assert( m_pContext );
+
+ SetScheme( "ClientScheme" );
+ SetProportional( true );
+}
+
+CReplayPerformanceSaveDlg::~CReplayPerformanceSaveDlg()
+{
+ ms_hDlg = NULL;
+}
+
+/*static*/ void CReplayPerformanceSaveDlg::Show( OnConfirmSaveCallback pfnCallback, void *pContext, CReplay *pReplay,
+ bool bExitEditorWhenDone )
+{
+ Assert( !ms_hDlg.Get() );
+
+ ms_hDlg = vgui::SETUP_PANEL( new CReplayPerformanceSaveDlg( NULL, "ReplayInputPanel", pfnCallback, pContext, pReplay, bExitEditorWhenDone ) );
+ ms_hDlg->SetVisible( true );
+ ms_hDlg->MakePopup();
+ ms_hDlg->MoveToFront();
+ ms_hDlg->SetKeyBoardInputEnabled(true);
+ ms_hDlg->SetMouseInputEnabled(true);
+ TFModalStack()->PushModal( ms_hDlg );
+ engine->ClientCmd_Unrestricted( "gameui_hide" );
+
+ ReplayCamera()->EnableInput( false );
+}
+
+void CReplayPerformanceSaveDlg::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/replayperformanceeditor/savedlg.res", "GAME" );
+
+ // Cache off the dlg pointer
+ m_pDlg = FindChildByName( "Dlg" );
+
+ CExButton *pDiscardButton;
+ pDiscardButton = dynamic_cast< CExButton * >( m_pDlg->FindChildByName( "DiscardButton" ) );
+ SetXToRed( pDiscardButton );
+
+ // Setup some action sigs
+ m_pDlg->FindChildByName( "SaveButton" )->AddActionSignalTarget( this );
+ m_pDlg->FindChildByName( "CancelButton" )->AddActionSignalTarget( this );
+ pDiscardButton->AddActionSignalTarget( this );
+
+ m_pTitleEntry = static_cast< TextEntry * >( m_pDlg->FindChildByName( "TitleInput" ) );
+ m_pTitleEntry->SelectAllOnFocusAlways( true );
+ m_pTitleEntry->SetSelectionBgColor( GetSchemeColor( "Yellow", Color( 255, 255, 255, 255), pScheme ) );
+ m_pTitleEntry->SetSelectionTextColor( Color( 255, 255, 255, 255 ) );
+ m_pTitleEntry->SetText( L"" );
+}
+
+void CReplayPerformanceSaveDlg::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ SetWide( ScreenWidth() );
+ SetTall( ScreenHeight() );
+
+ // Center
+ m_pDlg->SetPos( ( ScreenWidth() - m_pDlg->GetWide() ) / 2, ( ScreenHeight() - m_pDlg->GetTall() ) / 2 );
+}
+
+void CReplayPerformanceSaveDlg::OnKeyCodeTyped( KeyCode code )
+{
+ if ( code == KEY_ESCAPE )
+ {
+ surface()->PlaySound( "replay\\record_fail.wav" );
+ return;
+ }
+
+ BaseClass::OnKeyCodeTyped( code );
+}
+
+void CReplayPerformanceSaveDlg::OnKeyCodePressed( KeyCode code )
+{
+ if ( code == KEY_ENTER )
+ {
+ OnCommand( "save" );
+ }
+
+ BaseClass::OnKeyCodePressed( code );
+}
+
+void CReplayPerformanceSaveDlg::OnSetFocus()
+{
+ m_pTitleEntry->RequestFocus();
+}
+
+/*static*/ void CReplayPerformanceSaveDlg::OnConfirmOverwrite( bool bConfirm, void *pContext )
+{
+ CReplayPerformanceSaveDlg *pThis = (CReplayPerformanceSaveDlg *)pContext;
+ pThis->m_pfnCallback( bConfirm, pThis->m_wszTitle, pThis->m_pContext );
+ pThis->CloseWindow();
+}
+
+bool CReplayPerformanceSaveDlg::ConfirmOverwriteOrSaveNow()
+{
+ // Using the same title as an existing performance?
+ CReplayPerformance *pExistingPerformance = m_pReplay->GetPerformanceWithTitle( m_wszTitle );
+ if ( pExistingPerformance )
+ {
+ ShowConfirmDialog( "#Replay_OverwriteDlgTitle", "#Replay_OverwriteDlgText",
+ "#Replay_ConfirmOverwrite", "#Replay_Cancel", OnConfirmOverwrite, NULL, this );
+ return false;
+ }
+
+ m_pfnCallback( true, m_wszTitle, m_pContext );
+
+ return true;
+}
+
+void CReplayPerformanceSaveDlg::OnCommand( const char *command )
+{
+ bool bCloseWindow = false;
+
+ extern IReplayPerformanceController *g_pReplayPerformanceController;
+
+ if ( !Q_strnicmp( command, "save", 4 ) )
+ {
+ // Get the text and save the replay/performance immediately
+ m_pTitleEntry->GetText( m_wszTitle, MAX_TAKE_TITLE_LENGTH );
+
+ // If we aren't overwriting an existing performance, this func will return true.
+ bCloseWindow = ConfirmOverwriteOrSaveNow();
+ }
+ else if ( !Q_strnicmp( command, "cancel", 6 ) )
+ {
+ bCloseWindow = true;
+ }
+
+ // Close the window?
+ if ( bCloseWindow )
+ {
+ CloseWindow();
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+void CReplayPerformanceSaveDlg::CloseWindow()
+{
+ SetVisible( false );
+ MarkForDeletion();
+ TFModalStack()->PopModal( ms_hDlg.Get() );
+ ReplayCamera()->EnableInput( true );
+
+ CReplayPerformanceEditorPanel *pEditor = ReplayUI_GetPerformanceEditor();
+ if ( m_bExitEditorWhenDone && pEditor )
+ {
+ pEditor->Exit();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void ReplayUI_ShowPerformanceSaveDlg( OnConfirmSaveCallback pfnCallback,
+ void *pContext, CReplay *pReplay,
+ bool bExitEditorWhenDone )
+{
+ CReplayPerformanceSaveDlg::Show( pfnCallback, pContext, pReplay, bExitEditorWhenDone );
+}
+
+bool ReplayUI_IsPerformanceSaveDlgOpen()
+{
+ return CReplayPerformanceSaveDlg::ms_hDlg.Get() != NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Test the replay input dialog
+//-----------------------------------------------------------------------------
+CON_COMMAND_F( replay_test_take_save_dlg, "Open replay save take dlg", FCVAR_NONE )
+{
+ ReplayUI_ShowPerformanceSaveDlg( NULL, NULL, NULL, false );
+}
+
+#endif
\ No newline at end of file diff --git a/mp/src/game/client/replay/vgui/replayperformancesavedlg.h b/mp/src/game/client/replay/vgui/replayperformancesavedlg.h new file mode 100644 index 00000000..fb4dbd55 --- /dev/null +++ b/mp/src/game/client/replay/vgui/replayperformancesavedlg.h @@ -0,0 +1,27 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#ifndef REPLAYPERFORMANCESAVEDLG_H
+#define REPLAYPERFORMANCESAVEDLG_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+//-----------------------------------------------------------------------------
+
+class CReplay;
+
+//-----------------------------------------------------------------------------
+
+typedef void (*OnConfirmSaveCallback)( bool bConfirmed, wchar_t *pTitle, void *pContext );
+
+//-----------------------------------------------------------------------------
+
+void ReplayUI_ShowPerformanceSaveDlg( OnConfirmSaveCallback pfnCallback, void *pContext, CReplay *pReplay,
+ bool bExitEditorWhenDone );
+bool ReplayUI_IsPerformanceSaveDlgOpen();
+
+//-----------------------------------------------------------------------------
+
+#endif // REPLAYPERFORMANCESAVEDLG_H
diff --git a/mp/src/game/client/replay/vgui/replayreminderpanel.cpp b/mp/src/game/client/replay/vgui/replayreminderpanel.cpp new file mode 100644 index 00000000..db06c701 --- /dev/null +++ b/mp/src/game/client/replay/vgui/replayreminderpanel.cpp @@ -0,0 +1,163 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "replayreminderpanel.h"
+#include "replay/ireplaysystem.h"
+#include "replay/replay.h"
+#include "replay/ireplayscreenshotmanager.h"
+#include "replay/ireplaymanager.h"
+#include "replay/screenshot.h"
+#include "iclientmode.h"
+#include "vgui_controls/AnimationController.h"
+
+//-----------------------------------------------------------------------------
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//-----------------------------------------------------------------------------
+
+DECLARE_HUDELEMENT( CReplayReminderPanel );
+
+//-----------------------------------------------------------------------------
+
+CReplayReminderPanel::CReplayReminderPanel( const char *pElementName )
+: EditablePanel( g_pClientMode->GetViewport(), "ReplayReminder" ),
+ CHudElement( pElementName )
+{
+ SetScheme( "ClientScheme" );
+
+ m_flShowTime = 0;
+ m_bShouldDraw = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReplayReminderPanel::SetupText()
+{
+ // Get current key binding, if any.
+ const char *pBoundKey = engine->Key_LookupBinding( "save_replay" );
+ if ( !pBoundKey || FStrEq( pBoundKey, "(null)" ) )
+ {
+ pBoundKey = " ";
+ }
+
+ char szKey[16];
+ Q_snprintf( szKey, sizeof(szKey), "%s", pBoundKey );
+
+ wchar_t wKey[16];
+ wchar_t wLabel[256];
+
+ g_pVGuiLocalize->ConvertANSIToUnicode( szKey, wKey, sizeof( wKey ) );
+ g_pVGuiLocalize->ConstructString( wLabel, sizeof( wLabel ), g_pVGuiLocalize->Find("#Replay_freezecam_replay" ), 1, wKey );
+
+ // Set the text
+ SetDialogVariable( "text", wLabel );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReplayReminderPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ LoadControlSettings("Resource/UI/ReplayReminder.res", "GAME");
+
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ SetupText();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReplayReminderPanel::Show()
+{
+ m_flShowTime = gpGlobals->curtime;
+ SetVisible( true );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( GetParent(), "HudReplayReminderIn" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReplayReminderPanel::Hide()
+{
+ SetVisible( false );
+ m_flShowTime = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CReplayReminderPanel::HudElementKeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding )
+{
+ if ( ShouldDraw() && pszCurrentBinding )
+ {
+ if ( FStrEq (pszCurrentBinding, "save_replay" ) )
+ {
+ SetVisible( false );
+ }
+ }
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReplayReminderPanel::OnThink()
+{
+ BaseClass::OnThink();
+
+ if ( !IsVisible() )
+ return;
+
+ // If we're displaying the element for some specific duration...
+ if ( m_flShowTime )
+ {
+ // Get maximum duration
+ ConVarRef replay_postwinreminderduration( "replay_postwinreminderduration" );
+ float flShowLength = replay_postwinreminderduration.IsValid() ? replay_postwinreminderduration.GetFloat() : 5.0f;
+
+ if ( gpGlobals->curtime >= m_flShowTime + flShowLength )
+ {
+ m_flShowTime = 0;
+ SetVisible( false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReplayReminderPanel::SetVisible( bool bState )
+{
+ if ( bState )
+ {
+ SetupText();
+ }
+
+ // Store this state for ShouldDraw()
+ m_bShouldDraw = bState;
+
+ BaseClass::SetVisible( bState );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CReplayReminderPanel::ShouldDraw()
+{
+ return m_bShouldDraw;
+}
+
+#endif // #if defined( REPLAY_ENABLED )
\ No newline at end of file diff --git a/mp/src/game/client/replay/vgui/replayreminderpanel.h b/mp/src/game/client/replay/vgui/replayreminderpanel.h new file mode 100644 index 00000000..2e1b8927 --- /dev/null +++ b/mp/src/game/client/replay/vgui/replayreminderpanel.h @@ -0,0 +1,50 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#ifndef REPLAYREMINDERPANEL_H
+#define REPLAYREMINDERPANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <vgui_controls/EditablePanel.h>
+#include <game/client/iviewport.h>
+#include <vgui/IScheme.h>
+#include "hud.h"
+#include "hudelement.h"
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+// Purpose: Replay reminder panel
+//-----------------------------------------------------------------------------
+class CReplayReminderPanel : public EditablePanel, public CHudElement
+{
+ DECLARE_CLASS_SIMPLE( CReplayReminderPanel, vgui::EditablePanel );
+public:
+ CReplayReminderPanel( const char *pElementName );
+
+ void Hide(); // To be used by HUD only
+ void Show(); // To be used by HUD only
+
+ // CHudElement overrides
+ virtual bool ShouldDraw();
+ virtual void OnThink();
+ virtual int HudElementKeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding );
+
+ // EditablePanel overrides
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void SetVisible( bool bState );
+
+private:
+ void SetupText();
+
+ float m_flShowTime; // Used by the HUD only, to display the panel only for a certain period of time
+ bool m_bShouldDraw; // Store this state for ShouldDraw(), which allows us to use a single panel for
+ // both the post-win reminder and the freezepanel reminder.
+};
+
+#endif // REPLAYREMINDERPANEL_H
diff --git a/mp/src/game/client/replay/vgui/replayrenderoverlay.cpp b/mp/src/game/client/replay/vgui/replayrenderoverlay.cpp new file mode 100644 index 00000000..1300acc4 --- /dev/null +++ b/mp/src/game/client/replay/vgui/replayrenderoverlay.cpp @@ -0,0 +1,380 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "replayrenderoverlay.h"
+#include "vgui_controls/TextImage.h"
+#include "replay/genericclassbased_replay.h"
+#include "iclientmode.h"
+#include "VGuiMatSurface/IMatSystemSurface.h"
+#include "ienginevgui.h"
+#include "vgui/IVGui.h"
+#include "econ/confirm_dialog.h"
+#include "replay/ireplaymanager.h"
+#include "replay/irecordingsessionmanager.h"
+#include "replay/ireplaymoviemanager.h"
+#include "replay/replayrenderer.h"
+#include "econ/econ_controls.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+
+extern IReplayMovieManager *g_pReplayMovieManager;
+
+//-----------------------------------------------------------------------------
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+
+#define TMP_ENCODED_AUDIO ".tmp.aac"
+#ifdef USE_WEBM_FOR_REPLAY
+#define TMP_ENCODED_VIDEO ".tmp.webm"
+#else
+#define TMP_ENCODED_VIDEO ".tmp.mov"
+#endif
+
+//-----------------------------------------------------------------------------
+
+ConVar replay_enablerenderpreview( "replay_enablerenderpreview", "1", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_ARCHIVE, "Enable preview during replay render." );
+
+//-----------------------------------------------------------------------------
+
+void OnRenderCancelDialogButtonPressed( bool bConfirm, void *pContext )
+{
+ if ( bConfirm )
+ {
+ g_pReplayMovieManager->CancelRender();
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+CReplayRenderOverlay::CReplayRenderOverlay( Panel *pParent )
+: BaseClass( pParent, "ReplayRenderOverlay" ),
+ m_pBottom( NULL ),
+ m_pCancelButton( NULL ),
+ m_pTitleLabel( NULL ),
+ m_pProgressLabel( NULL ),
+ m_pFilenameLabel( NULL ),
+ m_pRenderProgress( NULL ),
+ m_pRenderer( NULL ),
+ m_pPreviewCheckButton( NULL ),
+ m_unNumFrames( 0 ),
+ m_flStartTime( 0.0f ),
+ m_flPreviousTimeLeft( 0.0f )
+{
+ if ( pParent == NULL )
+ {
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
+ SetScheme(scheme);
+ SetProportional( true );
+ }
+
+ ivgui()->AddTickSignal( GetVPanel(), 10 );
+
+ m_pRenderer = new CReplayRenderer( this );
+}
+
+CReplayRenderOverlay::~CReplayRenderOverlay()
+{
+ ivgui()->RemoveTickSignal( GetVPanel() );
+
+ delete m_pRenderer;
+}
+
+void CReplayRenderOverlay::Show()
+{
+ // Setup panel
+ SetVisible( true );
+ SetMouseInputEnabled( true );
+ SetKeyBoardInputEnabled( true );
+ MakePopup( true );
+ MoveToFront();
+ TFModalStack()->PushModal( this );
+
+ // Make sure game UI is hidden
+ engine->ClientCmd_Unrestricted( "gameui_hide" );
+
+ InvalidateLayout( false, true );
+}
+
+void CReplayRenderOverlay::Hide()
+{
+ SetVisible( false );
+ TFModalStack()->PopModal( this );
+ MarkForDeletion();
+}
+
+void CReplayRenderOverlay::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ // Load controls
+ LoadControlSettings( "Resource/UI/replayrenderoverlay.res", "GAME" );
+
+ // Layout bottom
+ m_pBottom = dynamic_cast< EditablePanel * >( FindChildByName( "BottomPanel" ) );
+ if ( !m_pBottom )
+ return;
+
+ // Find some controls
+ m_pTitleLabel = dynamic_cast< CExLabel * >( FindChildByName( "TitleLabel" ) );
+ m_pProgressLabel = dynamic_cast< CExLabel * >( FindChildByName( "ProgressLabel" ) );
+ m_pRenderProgress = dynamic_cast< ProgressBar * >( FindChildByName( "RenderProgress" ) );
+ m_pCancelButton = dynamic_cast< CExButton * >( FindChildByName( "CancelButton" ) );
+ m_pFilenameLabel = dynamic_cast< CExLabel * >( FindChildByName( "FilenameLabel" ) );
+ m_pPreviewCheckButton = dynamic_cast< CheckButton * >( FindChildByName( "PreviewCheckButton" ) );
+
+ m_pPreviewCheckButton->SetProportional( false );
+ m_pPreviewCheckButton->SetSelected( replay_enablerenderpreview.GetBool() );
+ m_pPreviewCheckButton->AddActionSignalTarget( this );
+
+ const char *pMovieFilename = m_pRenderer->GetMovieFilename();
+ if ( m_pFilenameLabel && pMovieFilename )
+ {
+ const char *pFilename = V_UnqualifiedFileName( pMovieFilename );
+ m_pFilenameLabel->SetText( pFilename );
+ }
+}
+
+void CReplayRenderOverlay::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ if ( !m_pBottom )
+ return;
+
+ int sw, sh;
+ vgui::surface()->GetScreenSize( sw, sh );
+ SetBounds( 0, 0, sw, sh );
+
+ int nBottomPanelHeight = sh * .13f;
+ int nBottomPanelStartY = sh - nBottomPanelHeight;
+ m_pBottom->SetBounds( 0, nBottomPanelStartY, sw, nBottomPanelHeight );
+
+ int nBottomW = sw;
+ int nBottomH = nBottomPanelHeight;
+
+ // Setup progress bar
+ if ( !m_pRenderProgress )
+ return;
+
+ int nProgHeight = YRES(20);
+ int nMargin = nBottomW/5;
+ int nProgX = nMargin;
+ int nProgY = nBottomPanelStartY + ( nBottomH - nProgHeight ) / 2;
+ int nProgW = nBottomW - 2*nMargin;
+
+ // Only show progress bar if replay is valid and length of render is non-zero, and the record start tick exists
+ CReplay *pReplay = g_pReplayManager->GetPlayingReplay();
+ if ( pReplay )
+ {
+ const float flTotalTime = pReplay->m_flLength;
+ const int nServerRecordStartTick = g_pClientReplayContext->GetRecordingSessionManager()->GetServerStartTickForSession( pReplay->m_hSession ); // NOTE: Returns -1 on fail
+ if ( flTotalTime > 0.0f && nServerRecordStartTick >= 0 )
+ {
+ m_pRenderProgress->SetVisible( true );
+ m_pRenderProgress->SetBounds( nProgX, nProgY, nProgW, nProgHeight );
+ m_pRenderProgress->SetSegmentInfo( XRES(1), XRES(8) );
+ }
+ }
+
+ // Layout title label
+ const int nTitleLabelY = nBottomPanelStartY + ( m_pBottom->GetTall() - m_pTitleLabel->GetTall() ) / 2;
+ if ( m_pTitleLabel )
+ {
+ m_pTitleLabel->SizeToContents();
+ m_pTitleLabel->SetPos( ( nProgX - m_pTitleLabel->GetWide() ) / 2, nTitleLabelY );
+ }
+
+ // Layout preview check button
+ if ( m_pPreviewCheckButton )
+ {
+ m_pPreviewCheckButton->SizeToContents();
+ m_pPreviewCheckButton->SetPos( ( nProgX - m_pPreviewCheckButton->GetWide() ) / 2, nTitleLabelY + m_pTitleLabel->GetTall() + YRES(3) );
+ }
+
+ // Layout filename label
+ if ( m_pFilenameLabel )
+ {
+ int nProgBottomY = nProgY + nProgHeight;
+ m_pFilenameLabel->SizeToContents();
+ m_pFilenameLabel->SetPos( nProgX, nProgBottomY + ( sh - nProgBottomY - m_pFilenameLabel->GetTall() ) / 2 );
+ }
+
+ // Layout progress label
+ if ( m_pProgressLabel )
+ {
+ int nProgBottomY = nProgY + nProgHeight;
+ m_pProgressLabel->SizeToContents();
+ m_pProgressLabel->SetPos( nProgX, nProgBottomY + ( sh - nProgBottomY - m_pProgressLabel->GetTall() ) / 2 );
+ m_pProgressLabel->SetWide( nProgW );
+ }
+
+ // Layout cancel button
+ if ( !m_pCancelButton )
+ return;
+
+ // Put cancel button half way in between progress bar and screen right
+ int nProgRightX = nProgX + nProgW;
+ m_pCancelButton->SetPos(
+ nProgRightX + ( m_pBottom->GetWide() - nProgRightX - m_pCancelButton->GetWide() ) / 2,
+ nBottomPanelStartY + ( m_pBottom->GetTall() - m_pCancelButton->GetTall() ) / 2
+ );
+
+ SetXToRed( m_pCancelButton );
+
+ m_pCancelButton->RequestFocus();
+}
+
+void CReplayRenderOverlay::OnTick()
+{
+#if _DEBUG
+ if ( m_bReloadScheme )
+ {
+ InvalidateLayout( true, true );
+ m_bReloadScheme = false;
+ }
+#endif
+
+ // Update progress
+ if ( m_pRenderProgress )
+ {
+ CReplay *pReplay = g_pReplayManager->GetPlayingReplay();
+ if ( pReplay && m_pRenderProgress->IsVisible() )
+ {
+ float flCurTime, flTotalTime;
+ g_pClientReplayContext->GetPlaybackTimes( flCurTime, flTotalTime, pReplay, m_pRenderer->GetPerformance() );
+ const float flProgress = ( flTotalTime == 0.0f ) ? 1.0f : ( flCurTime / flTotalTime );
+
+ Assert( flTotalTime > 0.0f ); // NOTE: Progress bar will always be invisible if total time is 0, but check anyway to be safe.
+ m_pRenderProgress->SetProgress( MAX( m_pRenderProgress->GetProgress(), flProgress ) ); // The MAX() here keeps the progress bar from thrashing
+
+ if ( m_pProgressLabel )
+ {
+ // @note Tom Bui: this is a horribly ugly hack, but the first couple of frames take a really freaking long time, so that
+ // really blows out the estimate
+ float flTimePassed = 0.0f;
+ ++m_unNumFrames;
+ const uint32 kNumFramesToWait = 10;
+ if ( m_unNumFrames < kNumFramesToWait )
+ {
+ m_flStartTime = gpGlobals->realtime;
+ }
+ else if ( m_unNumFrames > kNumFramesToWait )
+ {
+ flTimePassed = gpGlobals->realtime - m_flStartTime;
+ float flEstimatedTimeLeft = flProgress > 0.0f ? ( flTimePassed / flProgress ) - flTimePassed : 0.0f;
+ // exponential moving average FIR filter
+ // S(t) = smoothing_factor * Y(t) + (1 - smoothing_factor)* Y(t-1)
+ // previous value is essentially 90% of the current value
+ const float kSmoothingFactor = 0.1f;
+ if ( m_flPreviousTimeLeft == 0.0f )
+ {
+ m_flPreviousTimeLeft = flEstimatedTimeLeft;
+ }
+ else
+ {
+ m_flPreviousTimeLeft = kSmoothingFactor * flEstimatedTimeLeft + ( 1 - kSmoothingFactor ) * m_flPreviousTimeLeft;
+ }
+ }
+
+ wchar_t wszTimeLeft[256];
+ wchar_t wszTime[256];
+ {
+ const char *pRenderTime = CReplayTime::FormatTimeString( RoundFloatToInt( m_flPreviousTimeLeft ) );
+ g_pVGuiLocalize->ConvertANSIToUnicode( pRenderTime, wszTimeLeft, sizeof( wszTimeLeft ) );
+ }
+ {
+ const char *pRenderTime = CReplayTime::FormatTimeString( RoundFloatToInt( flTimePassed ) );
+ g_pVGuiLocalize->ConvertANSIToUnicode( pRenderTime, wszTime, sizeof( wszTime ) );
+ }
+ wchar_t wszText[256];
+ g_pVGuiLocalize->ConstructString( wszText,sizeof( wszText ), g_pVGuiLocalize->Find( "#Replay_RenderOverlay_TimeLeft" ), 2, wszTime, wszTimeLeft );
+ m_pProgressLabel->SetText( wszText );
+ }
+ }
+ }
+}
+
+void CReplayRenderOverlay::OnMousePressed( MouseCode nCode )
+{
+#if _DEBUG
+ m_bReloadScheme = true;
+#endif
+ BaseClass::OnMousePressed( nCode );
+}
+
+void CReplayRenderOverlay::OnKeyCodeTyped( vgui::KeyCode nCode )
+{
+ if ( nCode == KEY_ESCAPE )
+ {
+ if ( TFModalStack()->Top() == GetVPanel() )
+ {
+ OnCommand( "confirmcancel" );
+ return;
+ }
+ }
+
+ BaseClass::OnKeyCodeTyped( nCode );
+}
+
+void CReplayRenderOverlay::OnCommand( const char *pCommand )
+{
+ if ( !V_stricmp( pCommand, "confirmcancel" ) )
+ {
+ ShowConfirmDialog( "#Replay_CancelRenderTitle", "#Replay_ConfirmCancelRender", "#Replay_YesCancel", "#Replay_No", OnRenderCancelDialogButtonPressed, this, NULL, "replay\\replaydialog_warn.wav" );
+ return;
+ }
+
+ BaseClass::OnCommand( pCommand );
+}
+
+void CReplayRenderOverlay::OnCheckButtonChecked( Panel *pPanel )
+{
+ replay_enablerenderpreview.SetValue( (int)m_pPreviewCheckButton->IsSelected() );
+}
+
+//-----------------------------------------------------------------------------
+
+static CReplayRenderOverlay *s_pRenderOverlay = NULL;
+
+void ReplayUI_OpenReplayRenderOverlay()
+{
+ if ( !g_pReplayMovieManager->IsRendering() )
+ return;
+
+ // Delete any existing panel
+ if ( s_pRenderOverlay )
+ {
+ s_pRenderOverlay->MarkForDeletion();
+ }
+
+ // Create the panel - get the render resolution from the settings
+ s_pRenderOverlay = SETUP_PANEL( new CReplayRenderOverlay( NULL ) ); // Parenting to NULL allows us to turn off world rendering in engine/view.cpp (V_RenderView())
+
+ // Set the panel as the movie renderer, so it can receive begin/end render calls from the engine
+ g_pClientReplayContext->SetMovieRenderer( s_pRenderOverlay->m_pRenderer );
+}
+
+void ReplayUI_HideRenderOverlay()
+{
+ if ( s_pRenderOverlay )
+ {
+ s_pRenderOverlay->MarkForDeletion();
+ s_pRenderOverlay = NULL;
+ }
+
+ g_pClientReplayContext->SetMovieRenderer( NULL );
+}
+
+//-----------------------------------------------------------------------------
+
+#endif
diff --git a/mp/src/game/client/replay/vgui/replayrenderoverlay.h b/mp/src/game/client/replay/vgui/replayrenderoverlay.h new file mode 100644 index 00000000..e65170a2 --- /dev/null +++ b/mp/src/game/client/replay/vgui/replayrenderoverlay.h @@ -0,0 +1,73 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#ifndef REPLAY_RENDEROVERLAY_H
+#define REPLAY_RENDEROVERLAY_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+//-----------------------------------------------------------------------------
+
+#include "vgui_controls/Frame.h"
+#include "vgui_controls/ProgressBar.h"
+#include "replay/rendermovieparams.h"
+
+//-----------------------------------------------------------------------------
+
+class CExButton;
+class CExLabel;
+class IQuickTimeMovieMaker;
+class CReplay;
+class CReplayRenderer;
+
+//-----------------------------------------------------------------------------
+
+class CReplayRenderOverlay : public vgui::Frame
+{
+ DECLARE_CLASS_SIMPLE( CReplayRenderOverlay, vgui::Frame );
+public:
+ CReplayRenderOverlay( Panel *pParent );
+ ~CReplayRenderOverlay();
+
+ void Show();
+ void Hide();
+
+ CReplayRenderer *m_pRenderer;
+
+private:
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout();
+ virtual void OnTick();
+ virtual void OnMousePressed( vgui::MouseCode nCode );
+ virtual void OnKeyCodeTyped( vgui::KeyCode nCode );
+ virtual void OnCommand( const char *pCommand );
+
+private:
+ MESSAGE_FUNC_PTR( OnCheckButtonChecked, "CheckButtonChecked", pPanel );
+
+#if _DEBUG
+ bool m_bReloadScheme;
+#endif
+
+ int m_unNumFrames;
+ float m_flStartTime;
+ float m_flPreviousTimeLeft;
+ EditablePanel *m_pBottom;
+ vgui::ProgressBar *m_pRenderProgress;
+ vgui::CheckButton *m_pPreviewCheckButton;
+ CExButton *m_pCancelButton;
+ CExLabel *m_pTitleLabel;
+ CExLabel *m_pFilenameLabel;
+ CExLabel *m_pProgressLabel;
+};
+
+//-----------------------------------------------------------------------------
+
+void ReplayUI_OpenReplayRenderOverlay();
+void ReplayUI_HideRenderOverlay();
+
+//-----------------------------------------------------------------------------
+
+#endif // REPLAY_RENDEROVERLAY_H
|