diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/client/replay/vgui/replayperformanceeditor.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/client/replay/vgui/replayperformanceeditor.cpp')
| -rw-r--r-- | game/client/replay/vgui/replayperformanceeditor.cpp | 2675 |
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 ¶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] = { (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 |