summaryrefslogtreecommitdiff
path: root/game/client/econ/econ_notifications.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/client/econ/econ_notifications.cpp
downloadarchived-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.cpp1436
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 *> &notifications )
+ {
+ 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 *> &notifications = 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 *> &notifications = 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 *> &notifications = 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 *> &notifications = 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