//========= 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 extern CAchievementMgr g_AchievementMgrTF; //----------------------------------------------------------------------------- using namespace vgui; //----------------------------------------------------------------------------- extern IReplayPerformanceController *g_pReplayPerformanceController; //----------------------------------------------------------------------------- // Hack-y global bool to communicate when we are rewinding for map load screens. // Order of operations issues preclude the use of engine->IsPlayingDemo(). bool g_bIsReplayRewinding = false; //----------------------------------------------------------------------------- // TODO: Make these archive? Right now, the tips are reset every time the game starts ConVar replay_perftip_count_enter( "replay_perftip_count_enter", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_HIDDEN, "", true, 0, false, 0 ); ConVar replay_perftip_count_exit( "replay_perftip_count_exit", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_HIDDEN, "", true, 0, false, 0 ); ConVar replay_perftip_count_freecam_enter( "replay_perftip_count_freecam_enter", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_HIDDEN, "", true, 0, false, 0 ); ConVar replay_perftip_count_freecam_exit( "replay_perftip_count_freecam_exit", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_HIDDEN, "", true, 0, false, 0 ); ConVar replay_perftip_count_freecam_exit2( "replay_perftip_count_freecam_exit2", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_HIDDEN, "", true, 0, false, 0 ); ConVar replay_editor_fov_mousewheel_multiplier( "replay_editor_fov_mousewheel_multiplier", "5", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_DONTRECORD, "The multiplier on mousewheel input for adjusting camera FOV in the replay editor." ); ConVar replay_editor_fov_mousewheel_invert( "replay_editor_fov_mousewheel_invert", "0", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_DONTRECORD, "Invert FOV zoom/unzoom on mousewheel in the replay editor." ); ConVar replay_replayeditor_rewindmsgcounter( "replay_replayeditor_rewindmsgcounter", "0", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_HIDDEN, "" ); //----------------------------------------------------------------------------- #define MAX_TIP_DISPLAYS 1 //----------------------------------------------------------------------------- #define TIMESCALE_MIN 0.01f #define TIMESCALE_MAX 3.0f //----------------------------------------------------------------------------- #define SLIDER_RANGE_MAX 10000.0f //----------------------------------------------------------------------------- #define REPLAY_SOUND_DIALOG_POPUP "replay\\replaydialog_warn.wav" //----------------------------------------------------------------------------- static const char *gs_pCamNames[ NCAMS ] = { "free", "third", "first", "timescale", }; static const char *gs_pBaseComponentNames[ NCAMS ] = { "replay/replay_camera_%s%s", "replay/replay_camera_%s%s", "replay/replay_camera_%s%s", "replay/replay_%s%s", }; //----------------------------------------------------------------------------- void PlayDemo() { engine->ClientCmd_Unrestricted( "demo_resume" ); } void PauseDemo() { engine->ClientCmd_Unrestricted( "demo_pause" ); } //----------------------------------------------------------------------------- inline float SCurve( float t ) { t = clamp( t, 0.0f, 1.0f ); return t * t * (3 - 2*t); } inline float CubicEaseIn( float t ) { t = clamp( t, 0.0f, 1.0f ); return t * t * t; } inline float LerpScale( float flIn, float flInMin, float flInMax, float flOutMin, float flOutMax ) { float flDenom = flInMax - flInMin; if ( flDenom == 0.0f ) return 0.0f; float t = clamp( ( flIn - flInMin ) / flDenom, 0.0f, 1.0f ); return Lerp( t, flOutMin, flOutMax ); } //----------------------------------------------------------------------------- void HighlightTipWords( Label *pLabel ) { // Setup coloring - get # of words that should be highlighted wchar_t *pwNumWords = g_pVGuiLocalize->Find( "#Replay_PerfTip_Highlight_NumWords" ); if ( !pwNumWords ) return; // Get the current label text wchar_t wszLabelText[512]; pLabel->GetText( wszLabelText, sizeof( wszLabelText ) ); pLabel->GetTextImage()->ClearColorChangeStream(); pLabel->GetTextImage()->AddColorChange( pLabel->GetFgColor(), 0 ); int nNumWords = _wtoi( pwNumWords ); for ( int i = 0; i < nNumWords; ++i ) { char szWordFindStr[64]; V_snprintf( szWordFindStr, sizeof( szWordFindStr ), "#Replay_PerfTip_Highlight_Word%i", i ); wchar_t *pwWord = g_pVGuiLocalize->Find( szWordFindStr ); if ( !pwWord ) continue; const int nWordLen = wcslen( pwWord ); // Find any instance of the word in the label text and highlight it in red const wchar_t *p = wszLabelText; do { const wchar_t *pInst = wcsstr( p, pwWord ); if ( !pInst ) break; // Highlight the text int nStartPos = pInst - wszLabelText; int nEndPos = nStartPos + nWordLen; // If start pos is non-zero, clear color changes bool bChangeColor = true; if ( nStartPos == 0 ) { pLabel->GetTextImage()->ClearColorChangeStream(); } else if ( iswalpha( wszLabelText[ nStartPos - 1 ] ) ) { // If this is not the beginning of the string, check the previous character. If it's // not whitespace, etc, we found an instance of a keyword within another word. Skip. bChangeColor = false; } if ( bChangeColor ) { pLabel->GetTextImage()->AddColorChange( Color(200,80,60,255), nStartPos ); pLabel->GetTextImage()->AddColorChange( pLabel->GetFgColor(), nEndPos ); } p = pInst + nWordLen; } while ( 1 ); } } //----------------------------------------------------------------------------- class CSavingDialog : public CGenericWaitingDialog { DECLARE_CLASS_SIMPLE( CSavingDialog, CGenericWaitingDialog ); public: CSavingDialog( CReplayPerformanceEditorPanel *pEditorPanel ) : CGenericWaitingDialog( pEditorPanel ) { m_pEditorPanel = pEditorPanel; } virtual void OnTick() { BaseClass::OnTick(); if ( !g_pReplayPerformanceController ) return; // Update async save if ( g_pReplayPerformanceController->IsSaving() ) { g_pReplayPerformanceController->SaveThink(); } else { if ( m_pEditorPanel.Get() ) { m_pEditorPanel->OnSaveComplete(); } Close(); } } private: CConfirmDialog *m_pLoginDialog; vgui::DHANDLE< CReplayPerformanceEditorPanel > m_pEditorPanel; }; //----------------------------------------------------------------------------- class CReplayTipLabel : public Label { DECLARE_CLASS_SIMPLE( CReplayTipLabel, Label ); public: CReplayTipLabel( Panel *pParent, const char *pName, const char *pText ) : BaseClass( pParent, pName, pText ) { } virtual void ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); HighlightTipWords( this ); } }; DECLARE_BUILD_FACTORY_DEFAULT_TEXT( CReplayTipLabel, Label ); //----------------------------------------------------------------------------- class CPerformanceTip : public EditablePanel { DECLARE_CLASS_SIMPLE( CPerformanceTip, EditablePanel ); public: static DHANDLE< CPerformanceTip > s_pTip; static CPerformanceTip *CreateInstance( const char *pText ) { if ( s_pTip ) { s_pTip->SetVisible( false ); s_pTip->MarkForDeletion(); s_pTip = NULL; } s_pTip = SETUP_PANEL( new CPerformanceTip( pText ) ); return s_pTip; } CPerformanceTip( const char *pText ) : BaseClass( g_pClientMode->GetViewport(), "Tip" ), m_flBornTime( gpGlobals->realtime ), m_flAge( 0.0f ), m_flShowDuration( 15.0f ) { m_pTextLabel = new CReplayTipLabel( this, "TextLabel", pText ); } virtual void OnThink() { // Delete the panel if life exceeded const float flEndTime = m_flBornTime + m_flShowDuration; if ( gpGlobals->realtime >= flEndTime ) { SetVisible( false ); MarkForDeletion(); s_pTip = NULL; return; } SetVisible( true ); const float flFadeDuration = .4f; float flAlpha; // Fade out? if ( gpGlobals->realtime >= flEndTime - flFadeDuration ) { flAlpha = LerpScale( gpGlobals->realtime, flEndTime - flFadeDuration, flEndTime, 1.0f, 0.0f ); } // Fade in? else if ( gpGlobals->realtime <= m_flBornTime + flFadeDuration ) { flAlpha = LerpScale( gpGlobals->realtime, m_flBornTime, m_flBornTime + flFadeDuration, 0.0f, 1.0f ); } // Otherwise, we must be in between fade in/fade out else { flAlpha = 1.0f; } SetAlpha( 255 * SCurve( flAlpha ) ); } virtual void ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); LoadControlSettings( "resource/ui/replayperformanceeditor/tip.res", "GAME" ); // Center relative to parent const int nScreenW = ScreenWidth(); const int nScreenH = ScreenHeight(); int aContentSize[2]; m_pTextLabel->GetContentSize( aContentSize[0], aContentSize[1] ); const int nLabelHeight = aContentSize[1]; SetBounds( 0, 3 * nScreenH / 4 - nLabelHeight / 2, nScreenW, nLabelHeight + 2 * m_nTopBottomMargin ); m_pTextLabel->SetBounds( m_nLeftRightMarginWidth, m_nTopBottomMargin, nScreenW - 2 * m_nLeftRightMarginWidth, nLabelHeight ); } static void Cleanup() { if ( s_pTip ) { s_pTip->MarkForDeletion(); s_pTip = NULL; } } CPanelAnimationVarAliasType( int, m_nLeftRightMarginWidth, "left_right_margin", "0", "proportional_xpos" ); CPanelAnimationVarAliasType( int, m_nTopBottomMargin , "top_bottom_margin", "0", "proportional_ypos" ); CReplayTipLabel *m_pTextLabel; float m_flBornTime; float m_flAge; float m_flShowDuration; }; DHANDLE< CPerformanceTip > CPerformanceTip::s_pTip; // Display the performance tip if we haven't already displayed it nMaxTimesToDisplay times or more inline void DisplayPerformanceTip( const char *pText, ConVar* pCountCv = NULL, int nMaxTimesToDisplay = -1 ) { // Already displayed too many times? Get out. if ( pCountCv && nMaxTimesToDisplay >= 0 ) { int nCount = pCountCv->GetInt(); if ( nCount >= nMaxTimesToDisplay ) return; // Incremement count cvar pCountCv->SetValue( nCount + 1 ); } // Display the tip CPerformanceTip::CreateInstance( pText ); } //----------------------------------------------------------------------------- inline float GetPlaybackTime() { CReplay *pPlayingReplay = g_pReplayManager->GetPlayingReplay(); return gpGlobals->curtime - TICKS_TO_TIME( pPlayingReplay->m_nSpawnTick ); } //----------------------------------------------------------------------------- class CPlayerCell : public CExImageButton { DECLARE_CLASS_SIMPLE( CPlayerCell, CExImageButton ); public: CPlayerCell( Panel *pParent, const char *pName, int *pCurTargetPlayerIndex ) : CExImageButton( pParent, pName, "" ), m_iPlayerIndex( -1 ), m_pCurTargetPlayerIndex( pCurTargetPlayerIndex ) { } virtual void ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); GetImage()->SetImage( "" ); SetFont( pScheme->GetFont( "ReplaySmall" ) ); SetContentAlignment( Label::a_center ); } MESSAGE_FUNC( DoClick, "PressButton" ) { ReplayCamera()->SetPrimaryTarget( m_iPlayerIndex ); *m_pCurTargetPlayerIndex = m_iPlayerIndex; float flCurTime = GetPlaybackTime(); extern IReplayPerformanceController *g_pReplayPerformanceController; g_pReplayPerformanceController->AddEvent_Camera_ChangePlayer( flCurTime, m_iPlayerIndex ); } int m_iPlayerIndex; int *m_pCurTargetPlayerIndex; // Allow the button to write current target in outer class when pressed }; //----------------------------------------------------------------------------- /* class CReplayEditorSlider : public Slider { DECLARE_CLASS_SIMPLE( CReplayEditorSlider, Slider ); public: CReplayEditorSlider( Panel *pParent, const char *pName ) : Slider( pParent, pName ) { } virtual void SetDefault( float flDefault ) { m_flDefault = flDefault; } ON_MESSAGE( Reset, OnReset ) { SetValue( } private: float m_flDefault; }; */ //----------------------------------------------------------------------------- class CCameraOptionsPanel : public EditablePanel { DECLARE_CLASS_SIMPLE( CCameraOptionsPanel, EditablePanel ); public: CCameraOptionsPanel( Panel *pParent, const char *pName, const char *pTitle ) : EditablePanel( pParent, pName ), m_bControlsAdded( false ) { m_pTitleLabel = new CExLabel( this, "TitleLabel", pTitle ); AddControlToLayout( m_pTitleLabel ); } ~CCameraOptionsPanel() { m_lstSliderInfos.PurgeAndDeleteElements(); } void AddControlToLayout( Panel *pControl ) { if ( pControl ) { m_lstControls.AddToTail( pControl ); pControl->SetMouseInputEnabled( true ); } } // NOTE: Default value is assumed to be stored in flOut void AddSliderToLayout( int nId, Slider *pSlider, const char *pLabelText, float flMinValue, float flMaxValue, float &flOut ) { SliderInfo_t *pNewSliderInfo = new SliderInfo_t; pNewSliderInfo->m_nId = nId; pNewSliderInfo->m_pSlider = pSlider; pNewSliderInfo->m_flRange[ 0 ] = flMinValue; pNewSliderInfo->m_flRange[ 1 ] = flMaxValue; pNewSliderInfo->m_flDefault = flOut; pNewSliderInfo->m_pValueOut = &flOut; m_lstSliderInfos.AddToTail( pNewSliderInfo ); AddControlToLayout( new EditablePanel( this, "Buffer" ) ); AddControlToLayout( NewLabel( pLabelText ) ); AddControlToLayout( NewSetDefaultButton( nId ) ); AddControlToLayout( pSlider ); pSlider->AddActionSignalTarget( this ); } void ResetSlider( int nId ) { const SliderInfo_t *pSliderInfo = FindSliderInfoFromId( nId ); if ( !pSliderInfo ) return; SetValue( pSliderInfo, pSliderInfo->m_flDefault ); } void SetValue( int nId, float flValue ) { const SliderInfo_t *pSliderInfo = FindSliderInfoFromId( nId ); if ( !pSliderInfo ) return; SetValue( pSliderInfo, flValue ); } virtual void ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); // Setup border SetBorder( pScheme->GetBorder( "ButtonBorder" ) ); HFont hFont = pScheme->GetFont( "ReplayBrowserSmallest", true ); m_pTitleLabel->SetFont( hFont ); m_pTitleLabel->SizeToContents(); m_pTitleLabel->SetTall( YRES( 20 ) ); m_pTitleLabel->SetColorStr( "235 235 235 255" ); if ( !m_bControlsAdded ) { const char *pResFile = GetResFile(); if ( pResFile ) { LoadControlSettings( pResFile, "GAME" ); } AddControls(); m_bControlsAdded = true; } FOR_EACH_LL( m_lstSliderInfos, it ) { SliderInfo_t *pInfo = m_lstSliderInfos[ it ]; Slider *pSlider = pInfo->m_pSlider; pSlider->SetRange( 0, SLIDER_RANGE_MAX ); pSlider->SetNumTicks( 10 ); float flDenom = fabs( pInfo->m_flRange[1] - pInfo->m_flRange[0] ); pSlider->SetValue( SLIDER_RANGE_MAX * fabs( pInfo->m_flDefault - pInfo->m_flRange[0] ) / flDenom ); } } virtual void PerformLayout() { BaseClass::PerformLayout(); int nWidth = XRES( 140 ); int nMargins[2] = { XRES( 5 ), YRES( 5 ) }; int nVBuf = YRES( 0 ); int nLastY = -1; int nY = nMargins[1]; Panel *pPrevPanel = NULL; int nLastCtrlHeight = 0; FOR_EACH_LL( m_lstControls, i ) { Panel *pPanel = m_lstControls[ i ]; if ( !pPanel->IsVisible() ) continue; int aPos[2]; pPanel->GetPos( aPos[0], aPos[1] ); if ( pPrevPanel && aPos[1] >= 0 ) { nY += pPrevPanel->GetTall() + nVBuf; } // Gross hack to see if the control is a default button if ( dynamic_cast< CExButton * >( pPanel ) ) { pPanel->SetWide( XRES( 36 ) ); pPanel->SetPos( pPrevPanel ? ( GetWide() - nMargins[0] - pPanel->GetWide() ) : 0, nLastY ); } else { pPanel->SetWide( nWidth - 2 * nMargins[0] ); pPanel->SetPos( nMargins[0], nY ); } nLastY = nY; pPrevPanel = pPanel; nLastCtrlHeight = MAX( nLastCtrlHeight, pPanel->GetTall() ); } SetSize( nWidth, nY + nLastCtrlHeight + 2 * YRES( 3 ) ); } virtual void OnCommand( const char *pCommand ) { if ( !V_strnicmp( pCommand, "reset_", 6 ) ) { const int nSliderInfoId = atoi( pCommand + 6 ); ResetSlider( nSliderInfoId ); } else { BaseClass::OnCommand( pCommand ); } } Label *NewLabel( const char *pText ) { Label *pLabel = new Label( this, "Label", pText ); pLabel->SetTall( YRES( 9 ) ); pLabel->SetPos( -1, 0 ); // Use default x and accumulated y // Set font IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); HFont hFont = pScheme->GetFont( "DefaultVerySmall", true ); pLabel->SetFont( hFont ); return pLabel; } CExButton *NewSetDefaultButton( int nSliderInfoId ) { CExButton *pButton = new CExButton( this, "DefaultButton", "#Replay_SetDefaultSetting" ); pButton->SetTall( YRES( 11 ) ); pButton->SetPos( XRES( 30 ), -1 ); // Set y to -1 so it will stay on the same line pButton->SetContentAlignment( Label::a_center ); CFmtStr fmtResetCommand( "reset_%i", nSliderInfoId ); pButton->SetCommand( fmtResetCommand.Access() ); pButton->AddActionSignalTarget( this ); // Set font IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); HFont hFont = pScheme->GetFont( "DefaultVerySmall", true ); pButton->SetFont( hFont ); return pButton; } protected: MESSAGE_FUNC_PARAMS( OnSliderMoved, "SliderMoved", pParams ) { Panel *pSlider = (Panel *)pParams->GetPtr( "panel" ); float flPercent = pParams->GetInt( "position" ) / SLIDER_RANGE_MAX; FOR_EACH_LL( m_lstSliderInfos, it ) { SliderInfo_t *pInfo = m_lstSliderInfos[ it ]; if ( pSlider == pInfo->m_pSlider ) { *pInfo->m_pValueOut = Lerp( flPercent, pInfo->m_flRange[0], pInfo->m_flRange[1] ); } } } virtual const char *GetResFile() { return NULL; } virtual void AddControls() { } struct SliderInfo_t { Slider *m_pSlider; float m_flRange[2]; float m_flDefault; int m_nId; float *m_pValueOut; }; const SliderInfo_t *FindSliderInfoFromId( int nId ) { FOR_EACH_LL( m_lstSliderInfos, it ) { SliderInfo_t *pInfo = m_lstSliderInfos[ it ]; if ( pInfo->m_nId == nId ) return pInfo; } AssertMsg( 0, "Should always find a slider here." ); return NULL; } void SetValue( const SliderInfo_t *pSliderInfo, float flValue ) { if ( !pSliderInfo ) { AssertMsg( 0, "This should not happen." ); return; } // Calculate the range const float flRange = fabs( pSliderInfo->m_flRange[1] - pSliderInfo->m_flRange[0] ); AssertMsg( flRange > 0, "Bad slider range!" ); // Calculate the percentile based on the specified value and the range. const float flPercent = fabs( flValue - pSliderInfo->m_flRange[0] ) / flRange; pSliderInfo->m_pSlider->SetValue( flPercent * SLIDER_RANGE_MAX, true ); } CUtlLinkedList< Panel * > m_lstControls; CUtlLinkedList< SliderInfo_t *, int > m_lstSliderInfos; CExLabel *m_pTitleLabel; bool m_bControlsAdded; }; //----------------------------------------------------------------------------- class CTimeScaleOptionsPanel : public CCameraOptionsPanel { DECLARE_CLASS_SIMPLE( CTimeScaleOptionsPanel, CCameraOptionsPanel ); public: CTimeScaleOptionsPanel( Panel *pParent, float *pTimeScaleProxy ) : BaseClass( pParent, "TimeScaleSettings", "#Replay_TimeScale" ), m_pTimeScaleSlider( NULL ), m_pTimeScaleProxy( pTimeScaleProxy ) { } virtual const char *GetResFile() { return "resource/ui/replayperformanceeditor/settings_timescale.res"; } virtual void AddControls() { m_pTimeScaleSlider = dynamic_cast< Slider * >( FindChildByName( "TimeScaleSlider" ) ); AddSliderToLayout( SLIDER_TIMESCALE, m_pTimeScaleSlider, "#Replay_Scale", TIMESCALE_MIN, TIMESCALE_MAX, *m_pTimeScaleProxy ); } enum FreeCamSliders_t { SLIDER_TIMESCALE, }; Slider *m_pTimeScaleSlider; float *m_pTimeScaleProxy; }; //----------------------------------------------------------------------------- class CCameraOptionsPanel_Free : public CCameraOptionsPanel { DECLARE_CLASS_SIMPLE( CCameraOptionsPanel_Free, CCameraOptionsPanel ); public: CCameraOptionsPanel_Free( Panel *pParent ) : BaseClass( pParent, "FreeCameraSettings", "#Replay_FreeCam" ), m_pAccelSlider( NULL ), m_pSpeedSlider( NULL ), m_pFovSlider( NULL ), m_pRotFilterSlider( NULL ), m_pShakeSpeedSlider( NULL ), m_pShakeAmountSlider( NULL ) { } virtual const char *GetResFile() { return "resource/ui/replayperformanceeditor/camsettings_free.res"; } virtual void AddControls() { m_pAccelSlider = dynamic_cast< Slider * >( FindChildByName( "AccelSlider" ) ); m_pSpeedSlider = dynamic_cast< Slider * >( FindChildByName( "SpeedSlider" ) ); m_pFovSlider = dynamic_cast< Slider * >( FindChildByName( "FovSlider" ) ); m_pRotFilterSlider = dynamic_cast< Slider * >( FindChildByName( "RotFilterSlider" ) ); m_pShakeSpeedSlider = dynamic_cast< Slider * >( FindChildByName( "ShakeSpeedSlider" ) ); m_pShakeAmountSlider = dynamic_cast< Slider * >( FindChildByName( "ShakeAmountSlider" ) ); m_pShakeDirSlider = dynamic_cast< Slider * >( FindChildByName( "ShakeDirSlider" ) ); AddSliderToLayout( SLIDER_ACCEL, m_pAccelSlider, "#Replay_Accel", FREE_CAM_ACCEL_MIN, FREE_CAM_ACCEL_MAX, ReplayCamera()->m_flRoamingAccel ); AddSliderToLayout( SLIDER_SPEED, m_pSpeedSlider, "#Replay_Speed", FREE_CAM_SPEED_MIN, FREE_CAM_SPEED_MAX, ReplayCamera()->m_flRoamingSpeed ); AddSliderToLayout( SLIDER_FOV, m_pFovSlider, "#Replay_Fov", FREE_CAM_FOV_MIN, FREE_CAM_FOV_MAX, ReplayCamera()->m_flRoamingFov[1] ); AddSliderToLayout( SLIDER_ROTFILTER, m_pRotFilterSlider, "#Replay_RotFilter", FREE_CAM_ROT_FILTER_MIN, FREE_CAM_ROT_FILTER_MAX, ReplayCamera()->m_flRoamingRotFilterFactor ); AddSliderToLayout( SLIDER_SHAKE_SPEED, m_pShakeSpeedSlider, "#Replay_ShakeSpeed", FREE_CAM_SHAKE_SPEED_MIN, FREE_CAM_SHAKE_SPEED_MAX, ReplayCamera()->m_flRoamingShakeSpeed ); AddSliderToLayout( SLIDER_SHAKE_AMOUNT, m_pShakeAmountSlider, "#Replay_ShakeAmount", FREE_CAM_SHAKE_AMOUNT_MIN, FREE_CAM_SHAKE_AMOUNT_MAX, ReplayCamera()->m_flRoamingShakeAmount ); AddSliderToLayout( SLIDER_SHAKE_DIR, m_pShakeDirSlider, "#Replay_ShakeDir", FREE_CAM_SHAKE_DIR_MIN, FREE_CAM_SHAKE_DIR_MAX, ReplayCamera()->m_flRoamingShakeDir ); } enum FreeCamSliders_t { SLIDER_ACCEL, SLIDER_SPEED, SLIDER_FOV, SLIDER_ROTFILTER, SLIDER_SHAKE_SPEED, SLIDER_SHAKE_AMOUNT, SLIDER_SHAKE_DIR, }; Slider *m_pAccelSlider; Slider *m_pSpeedSlider; Slider *m_pFovSlider; Slider *m_pRotFilterSlider; Slider *m_pShakeSpeedSlider; Slider *m_pShakeAmountSlider; Slider *m_pShakeDirSlider; }; //----------------------------------------------------------------------------- class CReplayButton : public CExImageButton { DECLARE_CLASS_SIMPLE( CReplayButton, CExImageButton ); public: CReplayButton( Panel *pParent, const char *pName, const char *pText ) : BaseClass( pParent, pName, pText ), m_pTipText( NULL ) { } virtual void ApplySettings( KeyValues *pInResourceData ) { BaseClass::ApplySettings( pInResourceData ); const char *pTipName = pInResourceData->GetString( "tipname" ); if ( pTipName && pTipName[0] ) { const wchar_t *pTipText = g_pVGuiLocalize->Find( pTipName ); if ( pTipText && pTipText[0] ) { const int nTipLength = V_wcslen( pTipText ); m_pTipText = new wchar_t[ nTipLength + 1 ]; V_wcsncpy( m_pTipText, pTipText, sizeof(wchar_t) * ( nTipLength + 1 ) ); m_pTipText[ nTipLength ] = L'\0'; } } } virtual void OnCursorEntered() { BaseClass::OnCursorEntered(); CReplayPerformanceEditorPanel *pEditor = ReplayUI_GetPerformanceEditor(); if ( pEditor && m_pTipText ) { pEditor->SetButtonTip( m_pTipText, this ); pEditor->ShowButtonTip( true ); } } virtual void OnCursorExited() { BaseClass::OnCursorExited(); CReplayPerformanceEditorPanel *pEditor = ReplayUI_GetPerformanceEditor(); if ( pEditor && m_pTipText ) { pEditor->ShowButtonTip( false ); } } private: wchar_t *m_pTipText; }; DECLARE_BUILD_FACTORY_DEFAULT_TEXT( CReplayButton, CExImageButton ); //----------------------------------------------------------------------------- #define MAX_FF_RAMP_TIME 8.0f // The amount of time until we ramp to max scale value. class CReplayEditorFastForwardButton : public CReplayButton { DECLARE_CLASS_SIMPLE( CReplayEditorFastForwardButton, CReplayButton ); public: CReplayEditorFastForwardButton( Panel *pParent, const char *pName, const char *pText ) : BaseClass( pParent, pName, pText ), m_flPressTime( 0.0f ) { m_pHostTimescale = cvar->FindVar( "host_timescale" ); AssertMsg( m_pHostTimescale, "host_timescale lookup failed!" ); ivgui()->AddTickSignal( GetVPanel(), 10 ); } ~CReplayEditorFastForwardButton() { ivgui()->RemoveTickSignal( GetVPanel() ); // Avoid a non-1.0 host_timescale after replay edit, which can happen if // the user is still holding downt he FF button at the end of the replay. if ( m_pHostTimescale ) { m_pHostTimescale->SetValue( 1.0f ); } // Resume demo playback so that any demo played later won't start paused. PlayDemo(); } virtual void OnMousePressed( MouseCode code ) { m_flPressTime = gpGlobals->realtime; PlayDemo(); BaseClass::OnMousePressed( code ); } virtual void OnMouseReleased( MouseCode code ) { m_flPressTime = 0.0f; PauseDemo(); BaseClass::OnMouseReleased( code ); } void OnTick() { float flScale; if ( m_flPressTime == 0.0f ) { flScale = 1.0f; } else { const float flElapsed = clamp( gpGlobals->realtime - m_flPressTime, 0.0f, MAX_FF_RAMP_TIME ); const float t = CubicEaseIn( flElapsed / MAX_FF_RAMP_TIME ); // If a shift key is down... if ( input()->IsKeyDown( KEY_LSHIFT ) || input()->IsKeyDown( KEY_RSHIFT ) ) { // ...slow down host_timescale. flScale = .1f + .4f * t; } // If alt key down... else if ( input()->IsKeyDown( KEY_LALT ) || input()->IsKeyDown( KEY_RALT ) ) { // ...FF very quickly, ramp from 5 to 10. flScale = 5.0f + 5.0f * t; } else { // Otherwise, start at 1.5 and ramp upwards over time. flScale = 1.5f + 3.5f * t; } } // Set host_timescale. if ( m_pHostTimescale ) { m_pHostTimescale->SetValue( flScale ); } } private: float m_flPressTime; ConVar *m_pHostTimescale; }; DECLARE_BUILD_FACTORY_DEFAULT_TEXT( CReplayEditorFastForwardButton, CExImageButton ); //----------------------------------------------------------------------------- class CRecLightPanel : public EditablePanel { DECLARE_CLASS_SIMPLE( CRecLightPanel, vgui::EditablePanel ); public: CRecLightPanel( Panel *pParent ) : EditablePanel( pParent, "RecLightPanel" ), m_flPlayPauseTime( 0.0f ), m_bPaused( false ), m_bPerforming( false ) { m_pRecLights[ 0 ] = NULL; m_pRecLights[ 1 ] = NULL; m_pPlayPause[ 0 ] = NULL; m_pPlayPause[ 1 ] = NULL; } virtual void ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); LoadControlSettings( "resource/ui/replayperformanceeditor/reclight.res", "GAME" ); m_pRecLights[ 0 ] = dynamic_cast< ImagePanel * >( FindChildByName( "RecLightOffImg" ) ); m_pRecLights[ 1 ] = dynamic_cast< ImagePanel * >( FindChildByName( "RecLightOnImg" ) ); m_pPlayPause[ 0 ] = dynamic_cast< ImagePanel * >( FindChildByName( "PlayImg" ) ); m_pPlayPause[ 1 ] = dynamic_cast< ImagePanel * >( FindChildByName( "PauseImg" ) ); m_pCameraFringe = dynamic_cast< ImagePanel *>( FindChildByName( "CameraFringe" ) ); m_pCameraCrosshair = dynamic_cast< ImagePanel *>( FindChildByName( "CameraCrosshair" ) ); } virtual void PerformLayout() { BaseClass::PerformLayout(); SetVisible( m_bPerforming ); const int nScreenWidth = ScreenWidth(); const int nRecLightW = m_pRecLights[ 0 ]->GetWide(); int nXPos = nScreenWidth - nRecLightW + XRES( 6 ); int nYPos = -YRES( 8 ); m_pRecLights[ 0 ]->SetPos( nXPos, nYPos ); m_pRecLights[ 1 ]->SetPos( nXPos, nYPos ); const int nWidth = GetWide(); const int nHeight = GetTall(); // Setup camera fringe height if ( m_pCameraFringe ) { m_pCameraFringe->SetSize( nWidth, nHeight ); m_pCameraFringe->InstallMouseHandler( this ); } // Setup camera cross hair height if ( m_pCameraCrosshair ) { int aImageSize[2]; IImage *pImage = m_pCameraCrosshair->GetImage(); pImage->GetSize( aImageSize[0], aImageSize[1] ); aImageSize[0] = m_pCameraCrosshair->GetWide(); aImageSize[1] = m_pCameraCrosshair->GetTall(); const int nStartY = YRES( 13 ); m_pCameraCrosshair->SetBounds( nStartY + ( nWidth - aImageSize[0] ) / 2, nStartY + ( nHeight - aImageSize[1] ) / 2, aImageSize[0] - 2 * nStartY, aImageSize[1] - 2 * nStartY ); m_pCameraCrosshair->InstallMouseHandler( this ); } } void UpdateBackgroundVisibility() { m_pCameraCrosshair->SetVisible( m_bPaused ); m_pCameraFringe->SetVisible( m_bPaused ); } virtual void OnThink() { const float flTime = gpGlobals->realtime; bool bPauseAnimating = m_flPlayPauseTime > 0.0f && flTime >= m_flPlayPauseTime && flTime < ( m_flPlayPauseTime + m_flAnimTime ); // Setup light visibility int nOnOff = fmod( flTime * 2.0f, 2.0f ); bool bOnLightVisible = (bool)nOnOff; bool bRecording = g_pReplayPerformanceController->IsRecording(); m_pRecLights[ 0 ]->SetVisible( m_bPaused || ( bRecording && !bOnLightVisible ) ); m_pRecLights[ 1 ]->SetVisible( bRecording && ( !m_bPaused && bOnLightVisible ) ); // Deal with fringe and crosshair vis UpdateBackgroundVisibility(); int iPlayPauseActive = (int)m_bPaused; // Animate the pause icon if ( bPauseAnimating ) { const float t = clamp( ( flTime - m_flPlayPauseTime ) / m_flAnimTime, 0.0f, 1.0f ); const float s = SCurve( t ); const int nSize = (int)Lerp( s, 60.0f, 60.0f * m_nAnimScale ); int aCrossHairPos[2]; m_pCameraCrosshair->GetPos( aCrossHairPos[0], aCrossHairPos[1] ); const int nScreenXCenter = aCrossHairPos[0] + m_pCameraCrosshair->GetWide() / 2; const int nScreenYCenter = aCrossHairPos[1] + m_pCameraCrosshair->GetTall() / 2; m_pPlayPause[ iPlayPauseActive ]->SetBounds( nScreenXCenter - nSize / 2, nScreenYCenter - nSize / 2, nSize, nSize ); m_pPlayPause[ iPlayPauseActive ]->SetAlpha( (int)( MIN( 0.5f, 1.0f - s ) * 255) ); } m_pPlayPause[ iPlayPauseActive ]->SetVisible( bPauseAnimating ); m_pPlayPause[ !iPlayPauseActive ]->SetVisible( false ); } void UpdatePauseState( bool bPaused ) { if ( bPaused == m_bPaused ) return; m_bPaused = bPaused; m_flPlayPauseTime = gpGlobals->realtime; } void SetPerforming( bool bPerforming ) { if ( bPerforming == m_bPerforming ) return; m_bPerforming = bPerforming; InvalidateLayout( true, false ); } float m_flPlayPauseTime; bool m_bPaused; bool m_bPerforming; ImagePanel *m_pPlayPause[2]; // 0=play, 1=pause ImagePanel *m_pRecLights[2]; // 0=off, 1=on ImagePanel *m_pCameraFringe; ImagePanel *m_pCameraCrosshair; CPanelAnimationVar( int, m_nAnimScale, "anim_scale", "4" ); CPanelAnimationVar( float, m_flAnimTime, "anim_time", "1.5" ); }; //----------------------------------------------------------------------------- CReplayPerformanceEditorPanel::CReplayPerformanceEditorPanel( Panel *parent, ReplayHandle_t hReplay ) : EditablePanel( parent, "ReplayPerformanceEditor" ), m_hReplay( hReplay ), m_flLastTime( -1 ), m_nRedBlueLabelRightX( 0 ), m_nBottomPanelStartY( 0 ), m_nBottomPanelHeight( 0 ), m_nLastRoundedTime( -1 ), m_flSpaceDownStart( 0.0f ), m_flOldFps( -1.0f ), m_flLastTimeSpaceBarPressed( 0.0f ), m_flActiveTimeInEditor( 0.0f ), m_flTimeScaleProxy( 1.0f ), m_iCameraSelection( CAM_FIRST ), m_bMousePressed( false ), m_bMouseDown( false ), m_nMouseClickedOverCameraSettingsPanel( CAM_INVALID ), m_bShownAtLeastOnce( false ), m_bAchievementAwarded( false ), m_pImageList( NULL ), m_pCurTimeLabel( NULL ), m_pTotalTimeLabel( NULL ), m_pPlayerNameLabel( NULL ), m_pMouseTargetPanel( NULL ), m_pSlowMoButton( NULL ), m_pRecLightPanel( NULL ), m_pPlayerCellData( NULL ), m_pBottom( NULL ), m_pMenuButton( NULL ), m_pMenu( NULL ), m_pPlayerCellsPanel( NULL ), m_pButtonTip( NULL ), m_pSavingDlg( NULL ) { V_memset( m_pCameraButtons, 0, sizeof( m_pCameraButtons ) ); V_memset( m_pCtrlButtons, 0, sizeof( m_pCtrlButtons ) ); V_memset( m_pCameraOptionsPanels, NULL, sizeof( m_pCameraOptionsPanels ) ); m_pCameraOptionsPanels[ CAM_FREE ] = new CCameraOptionsPanel_Free( this ); m_pCameraOptionsPanels[ COMPONENT_TIMESCALE ] = new CTimeScaleOptionsPanel( this, &m_flTimeScaleProxy ); m_nRedBlueSigns[0] = -1; m_nRedBlueSigns[1] = 1; m_iCurPlayerTarget = -1; m_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] = { XRES( 5 ), YRES( 2 ) }; for ( int i = 0; i < 2; ++i ) { pRedBlueLabels[i]->SizeToContents(); const int nY = m_pPlayerCellsPanel->GetTall()/2 + m_nRedBlueSigns[i] * m_pPlayerCellsPanel->GetTall()/4 - pRedBlueLabels[i]->GetTall()/2; pRedBlueLabels[i]->SetPos( nMargins[0], nY ); m_nRedBlueLabelRightX = MAX( m_nRedBlueLabelRightX, nMargins[0] + pRedBlueLabels[i]->GetWide() + nMargins[0] ); } // Position player cells LayoutPlayerCells(); BaseClass::PerformLayout(); } bool CReplayPerformanceEditorPanel::OnStateChangeRequested( const char *pEventStr ) { // If we're already recording, allow the change. if ( g_pReplayPerformanceController->IsRecording() ) return true; // If we aren't recording and there is no forthcoming data in the playback stream, allow the change. if ( !g_pReplayPerformanceController->IsPlaybackDataLeft() ) return true; // Otherwise, record the event string and show a dialog asking the user if they're sure they want to nuke. V_strncpy( m_szSuspendedEvent, pEventStr, sizeof( m_szSuspendedEvent ) ); ShowConfirmDialog( "#Replay_Warning", "#Replay_NukePerformanceChanges", "#GameUI_Confirm", "#GameUI_CancelBold", OnConfirmDestroyChanges, this, this, REPLAY_SOUND_DIALOG_POPUP ); return false; } void CReplayPerformanceEditorPanel::SetButtonTip( wchar_t *pTipText, Panel *pContextPanel ) { // Set the text m_pButtonTip->SetText( pTipText ); m_pButtonTip->InvalidateLayout( true, true ); // Center relative to context panel int aPos[2]; ipanel()->GetAbsPos( pContextPanel->GetVPanel(), aPos[0], aPos[1] ); const int nX = clamp( aPos[0] - m_pButtonTip->GetWide() / 2, 0, ScreenWidth() - m_pButtonTip->GetWide() - (int) XRES( 40 ) ); const int nY = m_nBottomPanelStartY - m_pButtonTip->GetTall() - (int) YRES( 2 ); m_pButtonTip->SetPos( nX, nY ); } void CReplayPerformanceEditorPanel::ShowButtonTip( bool bShow ) { m_pButtonTip->SetVisible( bShow ); } void CReplayPerformanceEditorPanel::ShowSavingDialog() { Assert( !m_pSavingDlg ); m_pSavingDlg = new CSavingDialog( ReplayUI_GetPerformanceEditor() ); ShowWaitingDialog( m_pSavingDlg, "#Replay_Saving", true, false, -1 ); } void CReplayPerformanceEditorPanel::ShowPanel( bool bShow ) { if ( bShow == IsVisible() ) return; if ( bShow ) { // We are now performing. m_pRecLightPanel->SetPerforming( true ); // Disable keyboard input on all panels added to the list FOR_EACH_LL( m_lstDisableKeyboardInputPanels, it ) { m_lstDisableKeyboardInputPanels[ it ]->SetKeyBoardInputEnabled( false ); } DisplayPerformanceTip( "#Replay_PerfTip_ExitPerfMode", &replay_perftip_count_exit, MAX_TIP_DISPLAYS ); // Fire a message the game DLL can intercept (for achievements, etc). IGameEvent *event = gameeventmanager->CreateEvent( "entered_performance_mode" ); if ( event ) { gameeventmanager->FireEventClientSide( event ); } // Play a sound surface()->PlaySound( "replay\\enterperformancemode.wav" ); } else { // Display a tip DisplayPerformanceTip( "#Replay_PerfTip_EnterPerfMode", &replay_perftip_count_enter, MAX_TIP_DISPLAYS ); // Play a sound surface()->PlaySound( "replay\\exitperformancemode.wav" ); } // Show mouse cursor SetMouseInputEnabled( bShow ); SetVisible( bShow ); MakePopup( bShow ); // Avoid waiting for next OnThink() to hide background images m_pRecLightPanel->UpdatePauseState( bShow ); m_pRecLightPanel->UpdateBackgroundVisibility(); // Play or pause if ( bShow ) { PauseDemo(); } else { PlayDemo(); } // Keep controller informed about pause state so that it can throw away unimportant events during pause if it's recording. g_pReplayPerformanceController->NotifyPauseState( bShow ); } bool CReplayPerformanceEditorPanel::OnEndOfReplayReached() { if ( m_bShownAtLeastOnce ) { ShowPanel( true ); DisplayPerformanceTip( "#Replay_PerfTip_EndOfReplayReached" ); // Don't end demo playback yet. return true; } // Let the demo player end demo playback return false; } void CReplayPerformanceEditorPanel::AddSetViewEvent() { if ( !g_pReplayManager->GetPlayingReplay() ) return; if ( !g_pReplayPerformanceController ) return; Vector pos; QAngle angles; float fov; ReplayCamera()->GetCachedView( pos, angles, fov ); SetViewParams_t params; params.m_flTime = GetPlaybackTime(); params.m_flFov = fov; params.m_pOrigin = &pos; params.m_pAngles = &angles; params.m_flAccel = ReplayCamera()->m_flRoamingAccel; params.m_flSpeed = ReplayCamera()->m_flRoamingSpeed; params.m_flRotationFilter = ReplayCamera()->m_flRoamingRotFilterFactor; g_pReplayPerformanceController->AddEvent_Camera_SetView( params ); } // Input should be in [0,1] void CReplayPerformanceEditorPanel::AddTimeScaleEvent( float flTimeScale ) { if ( !g_pReplayManager->GetPlayingReplay() ) return; if ( !g_pReplayPerformanceController ) return; g_pReplayPerformanceController->AddEvent_TimeScale( GetPlaybackTime(), flTimeScale ); } void CReplayPerformanceEditorPanel::UpdateCameraButtonImages( bool bForceUnselected/*=false*/ ) { CReplayPerformance *pPerformance = GetPerformance(); for ( int i = 0; i < NCAMS; ++i ) { CFmtStr fmtFile( gs_pBaseComponentNames[i], gs_pCamNames[i], ( !bForceUnselected && ( !pPerformance || g_pReplayPerformanceController->IsRecording() ) && i == m_iCameraSelection ) ? "_selected" : "" ); if ( m_pCameraButtons[ i ] ) { m_pCameraButtons[ i ]->SetSubImage( fmtFile.Access() ); } } } void CReplayPerformanceEditorPanel::EnsureRecording( bool bShouldSnip ) { // Not recording? if ( !g_pReplayPerformanceController->IsRecording() ) { // Start recording - snip if needed. g_pReplayPerformanceController->StartRecording( GetReplay(), bShouldSnip ); } } void CReplayPerformanceEditorPanel::ToggleMenu() { if ( !m_pMenu ) return; // Show/hide const bool bShow = !m_pMenu->IsVisible(); m_pMenu->SetVisible( bShow ); } void CReplayPerformanceEditorPanel::SaveAs( const wchar_t *pTitle ) { if ( !g_pReplayPerformanceController->SaveAsAsync( pTitle ) ) { DisplaySavedTip( false ); } ShowSavingDialog(); } /*static*/ void CReplayPerformanceEditorPanel::OnConfirmSaveAs( bool bShouldSave, wchar_t *pTitle, void *pContext ) { // NOTE: Assumes that overwriting has already been confirmed by the user. if ( !bShouldSave ) return; CReplayPerformanceEditorPanel *pThis = (CReplayPerformanceEditorPanel *)pContext; pThis->SaveAs( pTitle ); surface()->PlaySound( "replay\\saved_take.wav" ); } void CReplayPerformanceEditorPanel::ShowRewindConfirmMessage() { ShowMessageBox( "#Replay_RewindWarningTitle", "#Replay_RewindWarningMsg", "#GameUI_OK", OnConfirmRewind, NULL, (void *)this ); surface()->PlaySound( "replay\\replaydialog_warn.wav" ); } /*static*/ void CReplayPerformanceEditorPanel::OnConfirmRewind( bool bConfirmed, void *pContext ) { if ( bConfirmed ) { if ( pContext ) { CReplayPerformanceEditorPanel *pEditor = (CReplayPerformanceEditorPanel *)pContext; pEditor->OnCommand( "goto_back" ); } } } void CReplayPerformanceEditorPanel::OnMenuCommand_Save( bool bExitEditorWhenDone/*=false*/ ) { // If this is the first time we're saving this performance, do a save-as. if ( !g_pReplayPerformanceController->HasSavedPerformance() ) { OnMenuCommand_SaveAs( bExitEditorWhenDone ); return; } // Regular save if ( !g_pReplayPerformanceController->SaveAsync() ) { DisplaySavedTip( false ); } // Show saving dialog ShowSavingDialog(); // Exit editor? if ( bExitEditorWhenDone ) { OnMenuCommand_Exit(); } } void CReplayPerformanceEditorPanel::OnMenuCommand_SaveAs( bool bExitEditorWhenDone/*=false*/ ) { ReplayUI_ShowPerformanceSaveDlg( OnConfirmSaveAs, this, GetReplay(), bExitEditorWhenDone ); } void CReplayPerformanceEditorPanel::DisplaySavedTip( bool bSucceess ) { DisplayPerformanceTip( bSucceess ? "#Replay_PerfTip_Saved" : "#Replay_PerfTip_SaveFailed" ); } void CReplayPerformanceEditorPanel::OnSaveComplete() { DisplaySavedTip( g_pReplayPerformanceController->GetLastSaveStatus() ); m_pSavingDlg = NULL; } void CReplayPerformanceEditorPanel::HandleUiToggle() { if ( !TFModalStack()->IsEmpty() ) return; PauseDemo(); Exit_ShowDialogs(); } void CReplayPerformanceEditorPanel::Exit() { engine->ClientCmd_Unrestricted( "disconnect" ); } void CReplayPerformanceEditorPanel::Exit_ShowDialogs() { if ( g_pReplayPerformanceController->IsDirty() ) { ShowConfirmDialog( "#Replay_DiscardTitle", "#Replay_DiscardChanges", "#Replay_Discard", "#Replay_Cancel", OnConfirmDiscard, NULL, this, REPLAY_SOUND_DIALOG_POPUP ); } else { ShowConfirmDialog( "#Replay_ExitEditorTitle", "#Replay_BackToReplays", "#GameUI_Confirm", "#Replay_Cancel", OnConfirmExit, NULL, this, REPLAY_SOUND_DIALOG_POPUP ); } } void CReplayPerformanceEditorPanel::OnMenuCommand_Exit() { Exit_ShowDialogs(); } void CReplayPerformanceEditorPanel::OnCommand( const char *command ) { float flCurTime = GetPlaybackTime(); g_bIsReplayRewinding = false; if ( !V_stricmp( command, "toggle_menu" ) ) { ToggleMenu(); } else if ( !V_strnicmp( command, "menu_", 5 ) ) { const char *pMenuCommand = command + 5; if ( !V_stricmp( pMenuCommand, "save" ) ) { OnMenuCommand_Save(); } else if ( !V_stricmp( pMenuCommand, "saveas" ) ) { OnMenuCommand_SaveAs(); } else if ( !V_stricmp( pMenuCommand, "exit" ) ) { OnMenuCommand_Exit(); } } else if ( !V_stricmp( command, "close" ) ) { ShowPanel( false ); MarkForDeletion(); return; } else if ( !V_stricmp( command, "play" ) ) { ShowPanel( false ); return; } else if ( !V_stricmp( command, "pause" ) ) { ShowPanel( true ); return; } else if ( !V_strnicmp( command, "timescale_", 10 ) ) { const char *pTimeScaleCmd = command + 10; if ( !V_stricmp( pTimeScaleCmd, "showpanel" ) ) { // If we're playing back, pop up a dialog asking if the user is sure they want to nuke the // rest of whatever is playing back. if ( !OnStateChangeRequested( command ) ) return; EnsureRecording(); } } else if ( !V_strnicmp( command, "settick_", 8 ) ) { const char *pSetType = command + 8; const int nCurTick = engine->GetDemoPlaybackTick(); if ( !V_stricmp( pSetType, "in" ) ) { SetOrRemoveInTick( nCurTick, true ); } else if ( !V_stricmp( pSetType, "out" ) ) { SetOrRemoveOutTick( nCurTick, true ); } // Save the replay CReplay *pReplay = GetReplay(); if ( pReplay ) { g_pReplayManager->FlagReplayForFlush( pReplay, true ); } return; } else if ( !V_strnicmp( command, "goto_", 5 ) ) { const char *pGotoType = command + 5; CReplay *pReplay = GetReplay(); if ( pReplay ) { const CReplayPerformance *pScratchPerformance = g_pReplayPerformanceController->GetPerformance(); const CReplayPerformance *pSavedPerformance = g_pReplayPerformanceController->GetSavedPerformance(); const CReplayPerformance *pPerformance = pScratchPerformance ? pScratchPerformance : pSavedPerformance; const int nCurTick = engine->GetDemoPlaybackTick(); // If in or out ticks are set in the performance, use those for the 'full' rewind/fast-forward const int nStartTick = MAX( 0, ( pPerformance && pPerformance->HasInTick() ) ? pPerformance->m_nTickIn : pReplay->m_nSpawnTick ); const int nEndTick = MAX( // The MAX() here will keep us from going back in time if we're already past the "end" tick nCurTick, ( ( pPerformance && pPerformance->HasOutTick() ) ? pPerformance->m_nTickOut : ( nStartTick + TIME_TO_TICKS( pReplay->m_flLength ) ) ) - TIME_TO_TICKS( 0.1f ) ); int nGotoTick = 0; bool bGoingBack = false; if ( !V_stricmp( pGotoType, "start" ) ) { bGoingBack = true; nGotoTick = nStartTick; } else if ( !V_stricmp( pGotoType, "back" ) ) { // If this is the first time rewinding, display a message if ( !replay_replayeditor_rewindmsgcounter.GetBool() ) { replay_replayeditor_rewindmsgcounter.SetValue( 1 ); ShowRewindConfirmMessage(); return; } bGoingBack = true; nGotoTick = nCurTick - TIME_TO_TICKS( 10.0f ); } else if ( !V_stricmp( pGotoType, "end" ) ) { nGotoTick = nEndTick; // Don't go back in time } // Clamp it nGotoTick = clamp( nGotoTick, nStartTick, nEndTick ); // If going back... if ( bGoingBack ) { // ...and notify the recorder that we're skipping, which we only need to do if we're going backwards g_pReplayPerformanceController->NotifyRewinding(); g_bIsReplayRewinding = true; } // Go to the given tick and pause CFmtStr fmtCmd( "demo_gototick %i\ndemo_pause\n", nGotoTick ); engine->ClientCmd_Unrestricted( fmtCmd.Access() ); } return; } else if ( !V_strnicmp( command, "setcamera_", 10 ) ) { const char *pCamType = command + 10; int nEntIndex = ReplayCamera()->GetPrimaryTargetIndex(); // If we're playing back, pop up a dialog asking if the user is sure they want to nuke the // rest of whatever is playing back. if ( !OnStateChangeRequested( command ) ) return; EnsureRecording(); if ( !V_stricmp( pCamType, "first" ) ) { ReplayCamera()->SetMode( OBS_MODE_IN_EYE ); UpdateCameraSelectionPosition( CAM_FIRST ); 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( 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 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