source-engine/game/client/econ/econ_notifications.cpp
FluorescentCIAAfricanAmerican 3bf9df6b27 1
2020-04-22 12:56:21 -04:00

1437 lines
37 KiB
C++

//========= 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