summaryrefslogtreecommitdiff
path: root/game/client/replay/vgui/replayperformanceeditor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/client/replay/vgui/replayperformanceeditor.cpp')
-rw-r--r--game/client/replay/vgui/replayperformanceeditor.cpp2675
1 files changed, 2675 insertions, 0 deletions
diff --git a/game/client/replay/vgui/replayperformanceeditor.cpp b/game/client/replay/vgui/replayperformanceeditor.cpp
new file mode 100644
index 0000000..be274cc
--- /dev/null
+++ b/game/client/replay/vgui/replayperformanceeditor.cpp
@@ -0,0 +1,2675 @@
+//========= 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] = { (int)XRES( 5 ), (int)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_bCurrentTargetNeedsVisibilityUpdate = false;
+
+ 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 );
+ }
+
+ // fixes a case where the replay would be paused and the player would cycle cameras but the
+ // target's visibility wouldn't be updated until the replay was unpaused (they would be invisible)
+ if ( m_bCurrentTargetNeedsVisibilityUpdate )
+ {
+ C_BaseEntity *pTarget = ClientEntityList().GetEnt( pCamera->GetPrimaryTargetIndex() );
+ if ( pTarget )
+ {
+ pTarget->UpdateVisibility();
+ }
+
+ m_bCurrentTargetNeedsVisibilityUpdate = false;
+ }
+
+ // 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 &params )
+{
+ 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] = { (int)XRES( 5 ), (int)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 );
+ m_bCurrentTargetNeedsVisibilityUpdate = true;
+ g_pReplayPerformanceController->AddEvent_Camera_Change_FirstPerson( flCurTime, nEntIndex );
+ }
+ else if ( !V_stricmp( pCamType, "third" ) )
+ {
+ ReplayCamera()->SetMode( OBS_MODE_CHASE );
+ UpdateCameraSelectionPosition( CAM_THIRD );
+ m_bCurrentTargetNeedsVisibilityUpdate = true;
+ g_pReplayPerformanceController->AddEvent_Camera_Change_ThirdPerson( flCurTime, nEntIndex );
+ AddSetViewEvent();
+ }
+ else if ( !V_stricmp( pCamType, "free" ) )
+ {
+ ReplayCamera()->SetMode( OBS_MODE_ROAMING );
+ UpdateCameraSelectionPosition( CAM_FREE );
+ m_bCurrentTargetNeedsVisibilityUpdate = true;
+ 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