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/econ/econ_notifications.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/client/econ/econ_notifications.cpp')
| -rw-r--r-- | game/client/econ/econ_notifications.cpp | 1436 |
1 files changed, 1436 insertions, 0 deletions
diff --git a/game/client/econ/econ_notifications.cpp b/game/client/econ/econ_notifications.cpp new file mode 100644 index 0000000..824a07c --- /dev/null +++ b/game/client/econ/econ_notifications.cpp @@ -0,0 +1,1436 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= +#include "cbase.h" + +#include "econ_notifications.h" + +#include "hudelement.h" +#include "iclientmode.h" +#include "ienginevgui.h" +#include "vgui_avatarimage.h" +#include "vgui_controls/Controls.h" +#include "vgui_controls/EditablePanel.h" +#include "vgui_controls/TextImage.h" +#include "vgui/ILocalize.h" +#include "vgui/ISurface.h" +#include "vgui/IVGui.h" +#include "rtime.h" +#include "econ_controls.h" +#include "hud_basechat.h" +#include "hud_vote.h" +#include "inputsystem/iinputsystem.h" +#include "iinput.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +//----------------------------------------------------------------------------- + +ConVar cl_notifications_show_ingame( "cl_notifications_show_ingame", "1", FCVAR_ARCHIVE, "Whether notifications should show up in-game." ); +ConVar cl_notifications_max_num_visible( "cl_notifications_max_num_visible", "3", FCVAR_ARCHIVE, "How many notifications are visible in-game." ); +ConVar cl_notifications_move_time( "cl_notifications_move_time", "0.5", FCVAR_ARCHIVE, "How long it takes for a notification to move." ); + +// notification queue holds all the notifications +class CEconNotificationQueue +{ +public: + CEconNotificationQueue(); + ~CEconNotificationQueue(); + + int AddNotification( CEconNotification *pNotification ); + void RemoveAllNotifications(); + void RemoveNotification( int iID ); + void RemoveNotification( CEconNotification *pNotification ); + void RemoveNotifications( NotificationFilterFunc func ); + int CountNotifications( NotificationFilterFunc func ); + void VisitNotifications( CEconNotificationVisitor &visitor ); + CEconNotification *GetNotification( int iID ); + CEconNotification *GetNotificationByIndex( int idx ); + void Update(); + bool HasItems() { return m_vecNotifications.Count() != 0; } + const CUtlVector< CEconNotification *> &GetItems() { return m_vecNotifications; } + +private: + int m_iIDGenerator; + CUtlVector< CEconNotification *> m_vecNotifications; +}; +static CEconNotificationQueue g_notificationQueue; + +CEconNotificationQueue::CEconNotificationQueue() + : m_iIDGenerator(0) +{ +} + +CEconNotificationQueue::~CEconNotificationQueue() +{ +} + +int CEconNotificationQueue::AddNotification( CEconNotification *pNotification ) +{ + int iID = ++m_iIDGenerator; + pNotification->m_iID = iID; + m_vecNotifications.AddToTail( pNotification ); + return iID; +} + +void CEconNotificationQueue::RemoveAllNotifications() +{ + m_vecNotifications.PurgeAndDeleteElements(); +} + +void CEconNotificationQueue::RemoveNotification( int iID ) +{ + FOR_EACH_VEC( m_vecNotifications, i ) + { + CEconNotification *pNotification = m_vecNotifications[i]; + if ( pNotification->GetID() == iID ) + { + delete pNotification; + m_vecNotifications.Remove( i ); + return; + } + } +} + +void CEconNotificationQueue::RemoveNotification( CEconNotification *pNotification ) +{ + if ( pNotification ) + { + RemoveNotification( pNotification->GetID() ); + } +} + +void CEconNotificationQueue::RemoveNotifications( NotificationFilterFunc func ) +{ + for ( int i = 0; i < m_vecNotifications.Count(); ++i) + { + CEconNotification *pNotification = m_vecNotifications[i]; + if ( func( pNotification ) ) + { + pNotification->MarkForDeletion(); + } + } +} + +int CEconNotificationQueue::CountNotifications( NotificationFilterFunc func ) +{ + int nResult = 0; + for ( int i = 0; i < m_vecNotifications.Count(); ++i) + { + CEconNotification *pNotification = m_vecNotifications[i]; + if ( func( pNotification ) ) + { + ++nResult; + } + } + + return nResult; +} + +void CEconNotificationQueue::VisitNotifications( CEconNotificationVisitor &visitor ) +{ + for ( int i = 0; i < m_vecNotifications.Count(); ++i ) + { + CEconNotification *pNotification = m_vecNotifications[i]; + visitor.Visit( *pNotification ); + } +} + +CEconNotification *CEconNotificationQueue::GetNotification( int iID ) +{ + FOR_EACH_VEC( m_vecNotifications, i ) + { + CEconNotification *pNotification = m_vecNotifications[i]; + if ( pNotification->GetID() == iID ) + { + return pNotification; + } + } + return NULL; +} + +CEconNotification *CEconNotificationQueue::GetNotificationByIndex( int idx ) +{ + if ( idx < 0 || idx >= m_vecNotifications.Count() ) + { + Assert( !"Invalid index passed to GetNotificationByIndex" ); + return NULL; + } + return m_vecNotifications[idx]; +} + +void CEconNotificationQueue::Update() +{ + float flNowTime = engine->Time(); + for ( int i = 0; i < m_vecNotifications.Count(); ) + { + CEconNotification *pNotification = m_vecNotifications[i]; + if ( pNotification->GetIsInUse() == false && pNotification->GetExpireTime() >= 0 && pNotification->GetExpireTime() < flNowTime ) + { + pNotification->Expired(); + delete pNotification; + m_vecNotifications.Remove( i ); + continue; + } + pNotification->UpdateTick(); + ++i; + } +} + +//----------------------------------------------------------------------------- + +static void ColorizeText( CEconNotification *pNotification, CExLabel *pControl, const wchar_t* wszText ) +{ + static wchar_t wszStrippedText[2048]; + + if ( pControl == NULL ) + return; + + pControl->GetTextImage()->ClearColorChangeStream(); + + if ( wszText == NULL ) + { + pControl->SetText( L"" ); + return; + } + + Color newColor = pControl->GetFgColor(); + int endIdx = 0; + int insertIdx = 0; + bool bContinue = true; + while ( bContinue ) + { + bool bSetColor = false; + switch ( wszText[endIdx] ) + { + case 0: + bContinue = false; + break; + case COLOR_NORMAL: + case COLOR_USEOLDCOLORS: + newColor = pControl->GetFgColor(); + bSetColor = true; + break; + case COLOR_PLAYERNAME: + newColor = g_ColorYellow; + bSetColor = true; + break; + case COLOR_LOCATION: + newColor = g_ColorDarkGreen; + bSetColor = true; + break; + case COLOR_ACHIEVEMENT: + { + vgui::IScheme *pSourceScheme = vgui::scheme()->GetIScheme( vgui::scheme()->GetScheme( "SourceScheme" ) ); + if ( pSourceScheme ) + { + newColor = pSourceScheme->GetColor( "SteamLightGreen", pControl->GetBgColor() ); + } + else + { + newColor = pControl->GetFgColor(); + } + bSetColor = true; + } + break; + case COLOR_CUSTOM: + newColor = pControl->GetFgColor(); + KeyValues *pKeyValues = pNotification->GetKeyValues(); + if ( pKeyValues ) + { + KeyValues* pColor = pKeyValues->FindKey( "custom_color" ); + if ( pColor ) + { + newColor = pColor->GetColor(); + } + } + bSetColor = true; + break; + } + if ( bSetColor ) + { + pControl->GetTextImage()->AddColorChange( newColor, insertIdx ); + } + else + { + wszStrippedText[insertIdx++] = wszText[endIdx]; + } + ++endIdx; + } + pControl->SetText( wszStrippedText ); +} + +// generic "toast" for notifications +class CGenericNotificationToast : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CGenericNotificationToast, vgui::EditablePanel ); +public: + CGenericNotificationToast( vgui::Panel *parent, int iNotificationID, bool bMainMenu ); + virtual ~CGenericNotificationToast(); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void PerformLayout(); +protected: + int m_iNotificationID; + vgui::Panel *m_pAvatarBG; + CAvatarImagePanel *m_pAvatar; + bool m_bMainMenu; +}; + +CGenericNotificationToast::CGenericNotificationToast( vgui::Panel *parent, int iNotificationID, bool bMainMenu ) + : BaseClass( parent, "GenericNotificationToast" ) + , m_iNotificationID( iNotificationID ) + , m_pAvatar( NULL ) + , m_pAvatarBG( NULL ) + , m_bMainMenu( bMainMenu ) +{ +} + +CGenericNotificationToast::~CGenericNotificationToast() +{ +} + +void CGenericNotificationToast::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + CEconNotification *pNotification = NotificationQueue_Get( m_iNotificationID ); + bool bHighPriority = pNotification && pNotification->BHighPriority(); + KeyValues *pConditions = NULL; + + if ( bHighPriority ) + { + pConditions = new KeyValues( "conditions" ); + if ( bHighPriority ) + { + KeyValues *pSubKey = new KeyValues( "if_high_priority" ); + pConditions->AddSubKey( pSubKey ); + } + } + + if ( m_bMainMenu ) + { + LoadControlSettings( "Resource/UI/Econ/GenericNotificationToastMainMenu.res", NULL, NULL, pConditions ); + } + else + { + LoadControlSettings( "Resource/UI/Econ/GenericNotificationToast.res", NULL, NULL, pConditions ); + } + + if ( pConditions ) + { + pConditions->deleteThis(); + } + + m_pAvatar = dynamic_cast< CAvatarImagePanel *>( FindChildByName("AvatarImage") ); + m_pAvatarBG = FindChildByName("AvatarBGPanel"); + + if ( pNotification ) + { + if ( pNotification->GetSteamID() == CSteamID() ) + { + ColorizeText( pNotification, dynamic_cast< CExLabel* >( FindChildByName( "TextLabel" ) ), pNotification->GetText() ); + } + else + { + ColorizeText( pNotification, dynamic_cast< CExLabel* >( FindChildByName( "AvatarTextLabel" ) ), pNotification->GetText() ); + } + } +} + +void CGenericNotificationToast::PerformLayout() +{ + BaseClass::PerformLayout(); + + CSteamID steamID; + CEconNotification *pNotification = NotificationQueue_Get( m_iNotificationID ); + if ( pNotification ) + { + steamID = pNotification->GetSteamID(); + } + + int iMinHeight = 0; + if ( m_pAvatar ) + { + if ( steamID != CSteamID() ) + { + m_pAvatar->SetVisible( true ); + m_pAvatar->SetShouldDrawFriendIcon( false ); + m_pAvatar->SetPlayer( steamID, k_EAvatarSize64x64 ); + // make sure there's a minimum height + // note we use iY to ensure that there's a buffer below too + int iX, iY, iWidth, iHeight; + m_pAvatar->GetBounds( iX, iY, iWidth, iHeight ); + iMinHeight = 2 * iY + iHeight; + } + else + { + m_pAvatar->SetVisible( false ); + m_pAvatar->ClearAvatar(); + } + } + if ( m_pAvatarBG ) + { + m_pAvatarBG->SetVisible( m_pAvatar != NULL && m_pAvatar->IsVisible() ); + } + + const char *pTextLabelName = steamID != CSteamID() ? "AvatarTextLabel" : "TextLabel"; + CExLabel* pText = dynamic_cast< CExLabel *>( FindChildByName( pTextLabelName ) ); + if ( pText ) + { + pText->SetVisible( true ); + pText->InvalidateLayout( true, false ); + int iWidth, iHeight; + pText->GetSize( iWidth, iHeight ); + int iContentWidth, iContentHeight; + pText->GetContentSize( iContentWidth, iContentHeight ); + pText->SetSize( iWidth, iContentHeight ); + int iDelta = iContentHeight - iHeight; + // resize ourselves to fit + int iContainerWidth, iContainerHeight; + GetSize( iContainerWidth, iContainerHeight ); + SetSize( iContainerWidth, MAX( iContainerHeight + iDelta, iMinHeight ) ); + } +} + +//----------------------------------------------------------------------------- + +class CNotificationToastControl : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CNotificationToastControl, vgui::EditablePanel ); +public: + CNotificationToastControl( vgui::EditablePanel *pParent, vgui::EditablePanel *pNotificationToast, int iNotificationID, bool bAddControls ) + : BaseClass( pParent, bAddControls ? "NotificationToastControl" : "NotificationToastContainer" ) + , m_pChild( pNotificationToast ) + , m_iNotificationID( iNotificationID ) + , m_bAddControls( bAddControls ) + , m_pTriggerButton( NULL ) + , m_pAcceptButton( NULL ) + , m_pDeclineButton( NULL ) + , m_iOverrideHeight( 0 ) + { + m_pChild->SetParent( this ); + } + + virtual ~CNotificationToastControl() + { + } + + virtual void ApplySchemeSettings( vgui::IScheme *scheme ) + { + CEconNotification *pNotification = g_notificationQueue.GetNotification( m_iNotificationID ); + + // It is not entirely clear why pNotification is allowed to be NULL. Weapon switching + // with pyro was causing crashes because of pNotification being NULL, and there were + // previously existing checks, but it's not clear why. + CEconNotification::EType eNotificationType = pNotification ? pNotification->NotificationType() \ + : CEconNotification::eType_Basic; + + bool bHighPriority = pNotification && pNotification->BHighPriority(); + bool bCanDelete = false; + bool bCanAcceptDecline = false; + bool bCanTrigger = false; + bool bOneButton = false; + + switch ( eNotificationType ) + { + case CEconNotification::eType_AcceptDecline: + bCanAcceptDecline = true; + break; + case CEconNotification::eType_Basic: + bCanDelete = true; + bOneButton = true; + break; + case CEconNotification::eType_MustTrigger: + bCanTrigger = true; + bOneButton = true; + break; + case CEconNotification::eType_Trigger: + bCanTrigger = true; + bCanDelete = true; + break; + default: + Assert( !"Unhandled enum type" ); + } + + KeyValues *pConditions = NULL; + + if ( bOneButton || bHighPriority ) + { + pConditions = new KeyValues( "conditions" ); + if ( bOneButton ) + { + KeyValues *pSubKey = new KeyValues( "if_one_button" ); + pConditions->AddSubKey( pSubKey ); + } + if ( bHighPriority ) + { + KeyValues *pSubKey = new KeyValues( "if_high_priority" ); + pConditions->AddSubKey( pSubKey ); + } + } + + if ( m_bAddControls ) + { + LoadControlSettings( "Resource/UI/Econ/NotificationToastControl.res", NULL, NULL, pConditions ); + } + else + { + LoadControlSettings( "Resource/UI/Econ/NotificationToastContainer.res", NULL, NULL, pConditions ); + } + + if ( pConditions ) + { + pConditions->deleteThis(); + } + + BaseClass::ApplySchemeSettings( scheme ); + + GetSize( m_iOriginalWidth, m_iOriginalHeight ); + + CExButton *pDeleteButton = dynamic_cast< CExButton *>( FindChildByName( "DeleteButton" ) ); + + if ( pDeleteButton && bCanDelete ) + { + pDeleteButton->AddActionSignalTarget( this ); + pDeleteButton->SetVisible ( pNotification != NULL ); + } + + if ( pNotification == NULL ) + return; + + if ( bCanAcceptDecline ) + { + m_pAcceptButton = dynamic_cast< CExButton * >( FindChildByName( "AcceptButton" ) ); + m_pDeclineButton = dynamic_cast< CExButton * >( FindChildByName( "DeclineButton" ) ); + if ( m_pAcceptButton && m_pDeclineButton ) + { + m_pAcceptButton->AddActionSignalTarget( this ); + m_pDeclineButton->AddActionSignalTarget( this ); + m_pAcceptButton->SetVisible( true ); + m_pDeclineButton->SetVisible( true ); + int posX, posY; + m_pAcceptButton->GetPos( posX, posY ); + m_iButtonOffsetY = GetTall() - posY; + } + } + if ( bCanTrigger ) + { + m_pTriggerButton = dynamic_cast< CExButton *>( FindChildByName( "TriggerButton" ) ); + if ( m_pTriggerButton ) + { + m_pTriggerButton->AddActionSignalTarget( this ); + m_pTriggerButton->SetVisible( true ); + int posX, posY; + m_pTriggerButton->GetPos( posX, posY ); + m_iButtonOffsetY = GetTall() - posY; + } + } + } + + virtual void PerformLayout() + { + BaseClass::PerformLayout(); + m_pChild->PerformLayout(); + int iWidth, iHeight; + m_pChild->GetSize( iWidth, iHeight ); + + // position control buttons + if ( iHeight + m_iButtonOffsetY > m_iOriginalHeight ) + { + if ( m_pAcceptButton && m_pDeclineButton ) + { + int posX, posY; + m_pAcceptButton->GetPos( posX, posY ); +// int newPosY = iHeight; +// iHeight += m_iButtonOffsetY; +// m_pAcceptButton->SetPos( posX, newPosY ); +// m_pDeclineButton->GetPos( posX, posY ); +// m_pDeclineButton->SetPos( posX, newPosY ); + } + else if ( m_pTriggerButton ) + { + int posX, posY; + m_pTriggerButton->GetPos( posX, posY ); +// posY = iHeight; +// iHeight += m_iButtonOffsetY; +// m_pTriggerButton->SetPos( posX, posY ); + } + } + + // position help label + CEconNotification *pNotification = g_notificationQueue.GetNotification( m_iNotificationID ); + CExLabel *pHelpLabel = dynamic_cast< CExLabel* >( FindChildByName( "HelpTextLabel" ) ); + if ( pHelpLabel ) + { + if ( pNotification ) + { + const wchar_t *pszText = NULL; + const char *pszTextKey = pNotification->GetUnlocalizedHelpText(); + if ( pszTextKey ) + { + pszText = g_pVGuiLocalize->Find( pszTextKey ); + } + if ( pszText ) + { + wchar_t wzFinal[512] = L""; + if ( ::input->IsSteamControllerActive() ) + { + UTIL_ReplaceKeyBindings( pszText, 0, wzFinal, sizeof( wzFinal ), GAME_ACTION_SET_FPSCONTROLS ); + } + else + { + UTIL_ReplaceKeyBindings( pszText, 0, wzFinal, sizeof( wzFinal ) ); + } + ColorizeText( pNotification, pHelpLabel, wzFinal ); + } + } + + pHelpLabel->InvalidateLayout( true, false ); + int posX, posY; + pHelpLabel->GetPos( posX, posY ); + int iContentWidth, iContentHeight; + pHelpLabel->GetContentSize( iContentWidth, iContentHeight ); + int iLabelWidth, iLabelHeight; + pHelpLabel->GetSize( iLabelWidth, iLabelHeight ); + int iTextInsetX, iTextInsetY; + pHelpLabel->GetTextInset( &iTextInsetX, &iTextInsetY ); + pHelpLabel->SetSize( iLabelWidth, iContentHeight + iTextInsetY ); + posY = iHeight; + pHelpLabel->SetPos( posX, posY - iTextInsetY ); + iHeight += iContentHeight + iTextInsetY; + } + + // resize ourselves to fit the child height wise + int iContainerHeight = MAX( m_iOriginalHeight, iHeight ); + SetSize( m_iOriginalWidth, m_iOverrideHeight != 0 ? m_iOverrideHeight : iContainerHeight ); + } + + virtual void OnCommand( const char *command ) + { + CEconNotification *pNotification = g_notificationQueue.GetNotification( m_iNotificationID ); + + if ( pNotification != NULL ) + { + if ( !Q_strncmp( command, "delete", ARRAYSIZE( "delete" ) ) ) + { + pNotification->Deleted(); + g_notificationQueue.RemoveNotification( m_iNotificationID ); + return; + } + else if ( !Q_strncmp( command, "trigger", ARRAYSIZE( "trigger" ) ) ) + { + pNotification->Trigger(); + } + else if ( !Q_strncmp( command, "accept", ARRAYSIZE( "accept" ) ) ) + { + pNotification->Accept(); + } + else if ( !Q_strncmp( command, "decline", ARRAYSIZE( "decline" ) ) ) + { + pNotification->Decline(); + } + else + { + BaseClass::OnCommand( command ); + } + } + else + { + BaseClass::OnCommand( command ); + } + } + + int GetOverrideHeight() const + { + return m_iOverrideHeight; + } + + void SetOverrideHeight( int iHeight ) + { + m_iOverrideHeight = iHeight; + } + +private: + vgui::EditablePanel* m_pChild; + vgui::Panel* m_pTriggerButton; + vgui::Panel* m_pAcceptButton; + vgui::Panel* m_pDeclineButton; + int m_iNotificationID; + int m_iOriginalWidth; + int m_iOriginalHeight; + int m_iButtonOffsetY; + int m_iOverrideHeight; + bool m_bAddControls; +}; + +//----------------------------------------------------------------------------- + +struct NotificationUIInfo_t +{ + CNotificationToastControl *m_pPanel; + int m_iStartPosX; + int m_iStartPosY; +}; + +// notification queue panel that is a HUD element +// this is the visualization of the notifications while in game +class CNotificationQueuePanel : public CHudElement, public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CNotificationQueuePanel, vgui::EditablePanel ); +public: + CNotificationQueuePanel( const char *pElementName ) + : CHudElement( pElementName ) + , BaseClass( NULL, "NotificationQueuePanel" ) + , m_mapNotificationPanels( DefLessFunc(int) ) + , m_flInvalidateTime( 0.0f ) + , m_bInvalidated( false ) + { + vgui::Panel *pParent = g_pClientMode->GetViewport(); + SetParent( pParent ); + + SetHiddenBits( HIDEHUD_MISCSTATUS ); + } + + virtual ~CNotificationQueuePanel() + { + } + + virtual bool ShouldDraw( void ) + { + if ( !CHudElement::ShouldDraw() ) + { + return false; + } + + if ( engine->IsPlayingDemo() ) + { + return false; + } + + if ( cl_notifications_show_ingame.GetInt() == 0 ) + { + return false; + } + + CHudVote *pHudVote = GET_HUDELEMENT( CHudVote ); + if ( pHudVote && pHudVote->IsVoteUIActive() ) + { + return false; + } + + return m_mapNotificationPanels.Count() > 0 || g_notificationQueue.HasItems(); + } + + virtual void PerformLayout( void ) + { + BaseClass::PerformLayout(); + + // Get filtered list of only the notifications that show some in-game content + CUtlVector< CEconNotification *> notifications; + GetNotifications( notifications ); + + const float flMoveTime = cl_notifications_move_time.GetFloat(); + float lerpPercentage = flMoveTime > 0 ? clamp( ( flMoveTime - m_flInvalidateTime ) / flMoveTime, 0.0f, 1.0f ) : 1.0f; + float flCurrTime = engine->Time(); + + // move the notifications around + const int kMaxVisibleNotifications = cl_notifications_max_num_visible.GetInt(); + + int iPosY = MIN( notifications.Count() - 1, kMaxVisibleNotifications - 1 ) * m_iOverlapOffset_Y; + int iPosX = MIN( notifications.Count() - 1, kMaxVisibleNotifications - 1 ) * m_iOverlapOffset_X; + int zpos = 100; + int iPreviousHeight = 0; + for ( int i = 0; i < notifications.Count(); ++i ) + { + CEconNotification *pNotification = notifications[i]; + int mapIdx = m_mapNotificationPanels.Find( pNotification->GetID() ); + if ( m_mapNotificationPanels.IsValidIndex( mapIdx ) == false || pNotification->GetInGameLifeTime() < flCurrTime ) + { + continue; + } + NotificationUIInfo_t &info = m_mapNotificationPanels[mapIdx]; + CNotificationToastControl *pPanel = info.m_pPanel; + if ( pPanel ) + { + if ( pPanel->IsVisible() == false ) + { + pPanel->SetVisible( true ); + } + if ( i == 0 && pPanel->GetOverrideHeight() != 0 ) + { + pPanel->SetOverrideHeight( 0 ); + pPanel->InvalidateLayout( true, false ); + } + int iPanelX; + int iPanelY; + pPanel->GetPos( iPanelX, iPanelY ); + if ( m_bInvalidated ) + { + info.m_iStartPosX = iPanelX; + info.m_iStartPosY = iPanelY; + } + int iNewPosX = iPosX; + int iNewPosY = iPosY; + iNewPosX = Lerp( lerpPercentage, info.m_iStartPosX, iNewPosX ); + iNewPosY = Lerp( lerpPercentage, info.m_iStartPosY, iNewPosY ); + pPanel->SetPos( iNewPosX, iNewPosY ); + pPanel->SetZPos( --zpos ); + bool bStoppedMoving = iNewPosX == iPanelX && iNewPosY == iPosY; + // only show panels that are more than we want visible if they are moving + if ( i > kMaxVisibleNotifications - 1 ) + { + if ( bStoppedMoving ) + { + pPanel->SetVisible( false ); + } + continue; + } + // don't poke out underneath if we are visible and stopped moving + if ( i != 0 && pPanel->GetOverrideHeight() == 0 && bStoppedMoving ) + { + pPanel->SetOverrideHeight( MIN( pPanel->GetTall(), iPreviousHeight ) ); + pPanel->InvalidateLayout( true, false ); + } + iPreviousHeight = pPanel->GetTall(); + + iPosY = MAX( 0, iPosY - m_iOverlapOffset_Y ); + iPosX = MAX( 0, iPosX - m_iOverlapOffset_X ); + } + } + m_bInvalidated = false; + SetTall( ScreenHeight() - m_iOriginalY ); + } + + virtual void ApplySchemeSettings( vgui::IScheme *scheme ) + { + LoadControlSettings( "Resource/UI/Econ/NotificationQueuePanel.res" ); + GetBounds( m_iOriginalX, m_iOriginalY, m_iOriginalWidth, m_iOriginalHeight ); + + BaseClass::ApplySchemeSettings( scheme ); + } + + virtual void OnThink() + { + BaseClass::OnThink(); + + if ( IsVisible() == false ) + { + return; + } + + // Get filtered list of only the notifications that show some in-game content + CUtlVector< CEconNotification *> notifications; + GetNotifications( notifications ); + + float flCurrTime = engine->Time(); + + // check to see if we have a panel for each notification + int i = 0; + for ( i = 0; i < notifications.Count(); ++i ) + { + CEconNotification *pNotification = notifications[i]; + int mapIdx = m_mapNotificationPanels.Find( pNotification->GetID() ); + if ( m_mapNotificationPanels.IsValidIndex( mapIdx ) == false || pNotification->GetInGameLifeTime() < flCurrTime ) + { + m_bInvalidated = true; + // create the panel and add it to the UI + // have it slide from the bottom + CNotificationToastControl *pControl = NULL; + int iPosX = 0, iPosY = 0; + vgui::EditablePanel *pPanel = pNotification->CreateUIElement( false ); + if ( pPanel ) + { + pControl = new CNotificationToastControl( this, pPanel, pNotification->GetID(), false ); + pControl->GetPos( iPosX, iPosY ); + pControl->SetPos( iPosX, ScreenHeight() ); + iPosY = ScreenHeight(); + } + NotificationUIInfo_t info = { pControl, iPosX, iPosY }; + m_mapNotificationPanels.Insert( pNotification->GetID(), info ); + } + } + + // now check to see if we have panels and there is no matching notification + i = m_mapNotificationPanels.FirstInorder(); + while ( m_mapNotificationPanels.IsValidIndex( i ) ) + { + int idx = i; + i = m_mapNotificationPanels.NextInorder( i ); + int iID = m_mapNotificationPanels.Key( idx ); + + CEconNotification *pNotification = g_notificationQueue.GetNotification( iID ); + if ( pNotification == NULL || pNotification->GetInGameLifeTime() < flCurrTime ) + { + // fade here, cause we don't really want to re-layout + NotificationUIInfo_t &info = m_mapNotificationPanels[idx]; + vgui::EditablePanel *pPanel = info.m_pPanel; + if ( pPanel ) + { + pPanel->MarkForDeletion(); + } + m_mapNotificationPanels.RemoveAt( idx ); + m_bInvalidated = true; + } + } + + if ( m_bInvalidated ) + { + m_flInvalidateTime = MAX( cl_notifications_move_time.GetFloat(), 0.0f ); + } + if ( m_flInvalidateTime > 0 ) + { + m_flInvalidateTime -= gpGlobals->frametime; + InvalidateLayout( true, false ); + } + } + +protected: + typedef CUtlMap< int, NotificationUIInfo_t > tNotificationPanels; + tNotificationPanels m_mapNotificationPanels; + int m_iOriginalX; + int m_iOriginalY; + int m_iOriginalWidth; + int m_iOriginalHeight; + float m_flInvalidateTime; + bool m_bInvalidated; + + CPanelAnimationVar( int, m_iVisibleBuffer, "buffer_between_visible", "5" ); + CPanelAnimationVar( int, m_iOverlapOffset_X, "overlap_offset_x", "10" ); + CPanelAnimationVar( int, m_iOverlapOffset_Y, "overlap_offset_y", "10" ); + + void GetNotifications(CUtlVector< CEconNotification *> ¬ifications ) + { + const CUtlVector< CEconNotification *> &allNotifications = g_notificationQueue.GetItems(); + + for (int i = 0 ; i < allNotifications.Count() ; ++i ) + { + CEconNotification *pNotification = allNotifications[i]; + if ( pNotification->BShowInGameElements() ) + { + notifications.AddToTail( pNotification ); + } + } + } +}; + +DECLARE_HUDELEMENT( CNotificationQueuePanel ); + +//----------------------------------------------------------------------------- + +CEconNotification::CEconNotification() + : m_pText("") + , m_pSoundFilename( NULL ) + , m_flExpireTime( engine->Time() + 10.0f ) + , m_pKeyValues( NULL ) + , m_bInUse( false ) + , m_steamID() +{ +} + +CEconNotification::~CEconNotification() +{ + if ( m_pKeyValues ) + { + m_pKeyValues->deleteThis(); + } +} + +void CEconNotification::SetText( const char *pText ) +{ + m_pText = pText; +} + +void CEconNotification::AddStringToken( const char* pToken, const wchar_t* pValue ) +{ + if ( m_pKeyValues == NULL ) + { + m_pKeyValues = new KeyValues( "CEconNotification" ); + } + m_pKeyValues->SetWString( pToken, pValue ); +} + +void CEconNotification::SetKeyValues( KeyValues *pKeyValues ) +{ + if ( m_pKeyValues != NULL ) + { + m_pKeyValues->deleteThis(); + } + m_pKeyValues = pKeyValues->MakeCopy(); +} + +KeyValues *CEconNotification::GetKeyValues() const +{ + return m_pKeyValues; +} + +const wchar_t *CEconNotification::GetText() +{ + g_pVGuiLocalize->ConstructString_safe( m_wszBuffer, m_pText, m_pKeyValues ); + return m_wszBuffer; +} + +int CEconNotification::GetID() const +{ + return m_iID; +} + +void CEconNotification::SetLifetime( float flSeconds ) +{ + m_flExpireTime = engine->Time() + flSeconds; +} + +float CEconNotification::GetExpireTime() const +{ + return m_flExpireTime; +} + +float CEconNotification::GetInGameLifeTime() const +{ + return m_flExpireTime; // default's to passed in time unless otherwise set (for derived classes) +} + +void CEconNotification::SetIsInUse( bool bInUse) +{ + m_bInUse = bInUse; +} + +bool CEconNotification::GetIsInUse() const +{ + return m_bInUse; +} + +void CEconNotification::SetSteamID( const CSteamID &steamID ) +{ + m_steamID = steamID; +} + +const CSteamID &CEconNotification::GetSteamID() const +{ + return m_steamID; +} + +void CEconNotification::MarkForDeletion() +{ + // to be deleted ASAP + m_flExpireTime = 0.0f; +} + +CEconNotification::EType CEconNotification::NotificationType() +{ + return eType_Basic; +} + +bool CEconNotification::BHighPriority() +{ + return false; +} + +void CEconNotification::Trigger() +{ +} + +void CEconNotification::Accept() +{ +} + +void CEconNotification::Decline() +{ +} + +void CEconNotification::Deleted() +{ +} + +void CEconNotification::Expired() +{ +} +vgui::EditablePanel *CEconNotification::CreateUIElement( bool bMainMenu ) const +{ + CGenericNotificationToast *pToast = new CGenericNotificationToast( NULL, m_iID, bMainMenu ); + return pToast; +} + +const char *CEconNotification::GetUnlocalizedHelpText() +{ + switch ( NotificationType() ) + { + case eType_AcceptDecline: + return "#Notification_AcceptOrDecline_Help"; + case eType_MustTrigger: + case eType_Trigger: + return "#Notification_CanTrigger_Help"; + default: + Assert( !"Unhandled enum value" ); + // ---v + case eType_Basic: + return "#Notification_Remove_Help"; + } + +} + +//----------------------------------------------------------------------------- + +class CMainMenuNotificationsControl : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CMainMenuNotificationsControl, vgui::EditablePanel ); +public: + CMainMenuNotificationsControl( vgui::EditablePanel *pParent, const char *pElementName ) + : BaseClass( pParent, pElementName ) + , m_mapNotificationPanels( DefLessFunc(int) ) + , m_iNumItems( 0 ) + { + vgui::ivgui()->AddTickSignal( GetVPanel(), 250 ); + } + + virtual ~CMainMenuNotificationsControl() + { + vgui::ivgui()->RemoveTickSignal( GetVPanel() ); + } + + virtual void PerformLayout( void ) + { + BaseClass::PerformLayout(); + + const CUtlVector< CEconNotification *> ¬ifications = g_notificationQueue.GetItems(); + + // position the notifications around + // grow down + int iTotalHeight = 0; + const int kBuffer = 5; + for ( int i = 0; i < notifications.Count(); ++i ) + { + CEconNotification *pNotification = notifications[i]; + int mapIdx = m_mapNotificationPanels.Find( pNotification->GetID() ); + if ( m_mapNotificationPanels.IsValidIndex( mapIdx ) == false ) + { + continue; + } + NotificationUIInfo_t &info = m_mapNotificationPanels[mapIdx]; + vgui::EditablePanel *pPanel = info.m_pPanel; + if ( pPanel ) + { + int iPanelX; + int iPanelY; + int iWidth; + int iHeight; + pPanel->GetBounds( iPanelX, iPanelY, iWidth, iHeight ); + int iNewPosX = iPanelX; + int iNewPosY = iTotalHeight; + pPanel->SetPos( iNewPosX, iNewPosY ); + iTotalHeight += iHeight + kBuffer; + } + } + int iWidth, iHeight; + GetSize( iWidth, iHeight ); + SetSize( iWidth, iTotalHeight ); + if ( iTotalHeight != iHeight ) + { + GetParent()->InvalidateLayout( false, false ); + } + } + + virtual void OnTick() + { + if ( m_iNumItems != g_notificationQueue.GetItems().Count() ) + { + m_iNumItems = g_notificationQueue.GetItems().Count(); + PostActionSignal( new KeyValues("Command", "command", "notifications_update" ) ); + } + } + + virtual void OnThink() + { + BaseClass::OnThink(); + + if ( IsVisible() == false ) + { + return; + } + + const CUtlVector< CEconNotification *> ¬ifications = g_notificationQueue.GetItems(); + bool bInvalidated = false; + + // check to see if we have a panel for each notification + int i = 0; + for ( i = 0; i < notifications.Count(); ++i ) + { + CEconNotification *pNotification = notifications[i]; + int mapIdx = m_mapNotificationPanels.Find( pNotification->GetID() ); + if ( m_mapNotificationPanels.IsValidIndex( mapIdx ) == false ) + { + bInvalidated = true; + CNotificationToastControl *pControl = NULL; + vgui::EditablePanel *pPanel = pNotification->CreateUIElement( true ); + if ( pPanel ) + { + pControl = new CNotificationToastControl( this, pPanel, pNotification->GetID(), true ); + } + NotificationUIInfo_t info = { pControl, 0, 0 }; + m_mapNotificationPanels.Insert( pNotification->GetID(), info ); + } + } + + // now check to see if we have panels and there is no matching notification + i = m_mapNotificationPanels.FirstInorder(); + while ( m_mapNotificationPanels.IsValidIndex( i ) ) + { + int idx = i; + i = m_mapNotificationPanels.NextInorder( i ); + int iID = m_mapNotificationPanels.Key( idx ); + if ( g_notificationQueue.GetNotification( iID ) == NULL ) + { + NotificationUIInfo_t &info = m_mapNotificationPanels[idx]; + vgui::EditablePanel *pPanel = info.m_pPanel; + if ( pPanel ) + { + pPanel->MarkForDeletion(); + } + m_mapNotificationPanels.RemoveAt( idx ); + bInvalidated = true; + } + } + + if ( bInvalidated ) + { + InvalidateLayout( true, false ); + } + } + +protected: + typedef CUtlMap< int, NotificationUIInfo_t > tNotificationPanels; + tNotificationPanels m_mapNotificationPanels; + int m_iNumItems; +}; + +// Show in UI +class CNotificationsPresentPanel : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CNotificationsPresentPanel, vgui::EditablePanel ); +public: + CNotificationsPresentPanel( vgui::Panel *pParent, const char* pElementName ) : vgui::EditablePanel( pParent, "NotificationsPresentPanel" ) + { + SetMouseInputEnabled( true ); + } + + virtual ~CNotificationsPresentPanel() + { + } + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "Resource/UI/Econ/NotificationsPresentPanel.res" ); + + for ( int i = 0; i < GetChildCount(); i++ ) + { + vgui::Panel *pChild = GetChild( i ); + pChild->SetMouseInputEnabled( false ); + } + } + + virtual void OnMousePressed(vgui::MouseCode code) + { + if ( code != MOUSE_LEFT ) + return; + + PostActionSignal( new KeyValues("Close") ); + + // audible feedback + const char *soundFilename = "ui/buttonclick.wav"; + + vgui::surface()->PlaySound( soundFilename ); + } +}; + +DECLARE_BUILD_FACTORY( CNotificationsPresentPanel ); + +//----------------------------------------------------------------------------- +// External interface for the notification queue + +int NotificationQueue_Add( CEconNotification *pNotification ) +{ + if ( !engine->IsInGame() || (cl_notifications_show_ingame.GetBool() && pNotification->BShowInGameElements()) ) + { + vgui::surface()->PlaySound( pNotification->GetSoundFilename() ); + } + return g_notificationQueue.AddNotification( pNotification ); +} + +CEconNotification *NotificationQueue_Get( int iID ) +{ + return g_notificationQueue.GetNotification( iID ); +} + +CEconNotification *NotificationQueue_GetByIndex( int idx ) +{ + return g_notificationQueue.GetNotificationByIndex( idx ); +} + +void NotificationQueue_RemoveAll() +{ + g_notificationQueue.RemoveAllNotifications(); +} + +void NotificationQueue_Remove( int iID ) +{ + g_notificationQueue.RemoveNotification( iID ); +} + +void NotificationQueue_Remove( CEconNotification *pNotification ) +{ + g_notificationQueue.RemoveNotification( pNotification ); +} + +void NotificationQueue_Remove( NotificationFilterFunc func ) +{ + g_notificationQueue.RemoveNotifications( func ); +} + +int NotificationQueue_Count( NotificationFilterFunc func ) +{ + return g_notificationQueue.CountNotifications( func ); +} + +void NotificationQueue_Visit( CEconNotificationVisitor &visitor ) +{ + g_notificationQueue.VisitNotifications( visitor ); +} + +void NotificationQueue_Update() +{ + g_notificationQueue.Update(); +} + +int NotificationQueue_GetNumNotifications() +{ + return g_notificationQueue.GetItems().Count(); +} + +vgui::EditablePanel* NotificationQueue_CreateMainMenuUIElement( vgui::EditablePanel *pParent, const char *pElementName ) +{ + CMainMenuNotificationsControl *pControl = new CMainMenuNotificationsControl( pParent, pElementName ); + pControl->AddActionSignalTarget( pParent ); + return pControl; +} + +//----------------------------------------------------------------------------- + +CON_COMMAND( cl_trigger_first_notification, "Tries to accept/trigger the first notification" ) +{ + const CUtlVector< CEconNotification *> ¬ifications = g_notificationQueue.GetItems(); + if ( notifications.Count() > 0 ) + { + CEconNotification *pNotification = notifications[0]; + switch ( pNotification->NotificationType() ) + { + case CEconNotification::eType_AcceptDecline: + pNotification->Accept(); + break; + case CEconNotification::eType_MustTrigger: + case CEconNotification::eType_Trigger: + pNotification->Trigger(); + case CEconNotification::eType_Basic: + break; + default: + Assert( !"Unhandled enum value" ); + } + } +} + +CON_COMMAND( cl_decline_first_notification, "Tries to decline/remove the first notification" ) +{ + const CUtlVector< CEconNotification *> ¬ifications = g_notificationQueue.GetItems(); + if ( notifications.Count() > 0 ) + { + CEconNotification *pNotification = notifications[0]; + switch ( pNotification->NotificationType() ) + { + case CEconNotification::eType_AcceptDecline: + pNotification->Decline(); + break; + case CEconNotification::eType_MustTrigger: + break; // YOU MUUUSSTTTTT + case CEconNotification::eType_Trigger: + case CEconNotification::eType_Basic: + pNotification->Deleted(); + pNotification->MarkForDeletion(); + break; + default: + Assert( !"Unhandled enum value" ); + } + } +} + +#ifdef _DEBUG + +#include "confirm_dialog.h" + +class CTFTestNotification : public CEconNotification +{ +public: + CTFTestNotification( const char* pText, EType eType ) + : CEconNotification() + , m_pText( pText ) + , m_eType( eType ) + { + } + + virtual EType NotificationType() OVERRIDE { return m_eType; } + + virtual void Trigger() OVERRIDE + { + ShowMessageBox( "", m_pText, "#GameUI_OK" ); + MarkForDeletion(); + } + virtual void Accept() OVERRIDE + { + ShowMessageBox( "Accept", m_pText, "#GameUI_OK" ); + MarkForDeletion(); + } + virtual void Decline() OVERRIDE + { + ShowMessageBox( "Decline", m_pText, "#GameUI_OK" ); + MarkForDeletion(); + } +private: + const char *m_pText; + EType m_eType; +}; + +CON_COMMAND( cl_add_notification, "Adds a notification" ) +{ + if ( args.ArgC() >= 2 ) + { + CEconNotification::EType eType = CEconNotification::eType_Basic; + if ( args.ArgC() >= 5 ) + { + eType = (CEconNotification::EType)atoi( args[4] ); + } + + CEconNotification *pNotification = new CTFTestNotification( args[1], eType ); + + pNotification->SetText( args[1] ); + if ( args.ArgC() >= 3 ) + { + int iLifetime = atoi( args[2] ); + pNotification->SetLifetime( iLifetime ); + } + if ( args.ArgC() >= 4 ) + { + if ( steamapicontext && steamapicontext->SteamUser() ) + { + pNotification->SetSteamID( steamapicontext->SteamUser()->GetSteamID() ); + } + } + int id = NotificationQueue_Add( pNotification ); + Msg( "Added notification %d\n", id); + } +} + +#endif |