diff options
Diffstat (limited to 'game/client/tf/vgui')
176 files changed, 65156 insertions, 0 deletions
diff --git a/game/client/tf/vgui/ObjectControlPanel.cpp b/game/client/tf/vgui/ObjectControlPanel.cpp new file mode 100644 index 0000000..da24fd2 --- /dev/null +++ b/game/client/tf/vgui/ObjectControlPanel.cpp @@ -0,0 +1,183 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "ObjectControlPanel.h" +#include <vgui_controls/Controls.h> +#include <vgui_controls/Label.h> +#include "vgui_bitmapbutton.h" +#include <vgui/ISurface.h> +#include <vgui/IVGui.h> +#include "c_tf_player.h" +#include "clientmode_tf.h" +#include <vgui/IScheme.h> +#include <vgui_controls/Slider.h> +#include "vgui_rotation_slider.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +#define DISMANTLE_WAIT_TIME 5.0 + + +//----------------------------------------------------------------------------- +// Standard VGUI panel for objects +//----------------------------------------------------------------------------- +DECLARE_VGUI_SCREEN_FACTORY( CObjectControlPanel, "object_control_panel" ); + + +//----------------------------------------------------------------------------- +// Constructor: +//----------------------------------------------------------------------------- +CObjectControlPanel::CObjectControlPanel( vgui::Panel *parent, const char *panelName ) + : BaseClass( parent, panelName, NULL ) +{ + // Make some high-level panels to group stuff we want to activate/deactivate + m_pActivePanel = new CCommandChainingPanel( this, "ActivePanel" ); + + SetCursor( vgui::dc_none ); // don't draw a VGUI cursor for this panel, and for its children + + // Make sure these are behind everything + m_pActivePanel->SetZPos( -1 ); +} + + +//----------------------------------------------------------------------------- +// Initialization +//----------------------------------------------------------------------------- +bool CObjectControlPanel::Init( KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData ) +{ + // Make sure we get ticked... + vgui::ivgui()->AddTickSignal( GetVPanel() ); + + if (!BaseClass::Init(pKeyValues, pInitData)) + return false; + + SetCursor( vgui::dc_none ); // don't draw a VGUI cursor for this panel, and for its children + + // Make the bounds of the sub-panels match + int x, y, w, h; + GetBounds( x, y, w, h ); + m_pActivePanel->SetBounds( x, y, w, h ); + + // Make em all invisible + m_pActivePanel->SetVisible( false ); + m_pCurrentPanel = m_pActivePanel; + + return true; +} + + +//----------------------------------------------------------------------------- +// Returns the object it's attached to +//----------------------------------------------------------------------------- +C_BaseObject *CObjectControlPanel::GetOwningObject() const +{ + C_BaseEntity *pScreenEnt = GetEntity(); + if (!pScreenEnt) + return NULL; + + C_BaseEntity *pObj = pScreenEnt->GetOwnerEntity(); + if (!pObj) + return NULL; + + Assert( dynamic_cast<C_BaseObject*>(pObj) ); + return static_cast<C_BaseObject*>(pObj); +} + + +//----------------------------------------------------------------------------- +// Ticks the panel when its in its various states +//----------------------------------------------------------------------------- +void CObjectControlPanel::OnTickActive( C_BaseObject *pObj, C_TFPlayer *pLocalPlayer ) +{ + //ShowDismantleButton( !(pObj->GetFlags() & OF_CANNOT_BE_DISMANTLED) && pObj->GetOwner() == pLocalPlayer ); +} + +vgui::Panel* CObjectControlPanel::TickCurrentPanel() +{ + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + C_BaseObject *pObj = GetOwningObject(); + + m_pCurrentPanel = GetActivePanel(); + OnTickActive(pObj, pLocalPlayer); + + return m_pCurrentPanel; +} + +void CObjectControlPanel::SendToServerObject( const char *pMsg ) +{ + C_BaseObject *pObj = GetOwningObject(); + if (pObj) + { + pObj->SendClientCommand( pMsg ); + } +} + +//----------------------------------------------------------------------------- +// Frame-based update +//----------------------------------------------------------------------------- +void CObjectControlPanel::OnTick() +{ + BaseClass::OnTick(); + + C_BaseObject *pObj = GetOwningObject(); + if (!pObj) + return; + + if ( IsVisible() ) + { + // Update the current subpanel + m_pCurrentPanel->SetVisible( false ); + + m_pCurrentPanel = TickCurrentPanel(); + + m_pCurrentPanel->SetVisible( true ); + } +} + +//----------------------------------------------------------------------------- +// Button click handlers +//----------------------------------------------------------------------------- +void CObjectControlPanel::OnCommand( const char *command ) +{ + BaseClass::OnCommand(command); +} + +DECLARE_VGUI_SCREEN_FACTORY( CRotatingObjectControlPanel, "rotating_object_control_panel" ); + + +//----------------------------------------------------------------------------- +// This is a panel for an object that has rotational controls +//----------------------------------------------------------------------------- +CRotatingObjectControlPanel::CRotatingObjectControlPanel( vgui::Panel *parent, const char *panelName ) + : BaseClass( parent, panelName ) +{ +} + +bool CRotatingObjectControlPanel::Init( KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData ) +{ + // Grab ahold of certain well-known controls + m_pRotationSlider = new CRotationSlider( GetActivePanel(), "RotationSlider" ); + m_pRotationLabel = new vgui::Label( GetActivePanel(), "RotationLabel", "Rotation Control" ); + + if (!BaseClass::Init(pKeyValues, pInitData)) + return false; + + m_pRotationSlider->SetControlledObject( GetOwningObject() ); + + return true; +} + +void CRotatingObjectControlPanel::OnTickActive( C_BaseObject *pObj, C_TFPlayer *pLocalPlayer ) +{ + BaseClass::OnTickActive( pObj, pLocalPlayer ); + bool bEnable = (pObj->GetOwner() == pLocalPlayer); + m_pRotationSlider->SetVisible( bEnable ); + m_pRotationLabel->SetVisible( bEnable ); +} + diff --git a/game/client/tf/vgui/ObjectControlPanel.h b/game/client/tf/vgui/ObjectControlPanel.h new file mode 100644 index 0000000..9db962a --- /dev/null +++ b/game/client/tf/vgui/ObjectControlPanel.h @@ -0,0 +1,108 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Clients CBaseObject +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef OBJECTCONTROLPANEL_H +#define OBJECTCONTROLPANEL_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "c_vguiscreen.h" + +namespace vgui +{ + class Panel; + class Label; + class Button; +} + +class C_BaseObject; +class CRotationSlider; +class C_TFPlayer; + +//----------------------------------------------------------------------------- +// Base class for all vgui screens on objects: +//----------------------------------------------------------------------------- +class CObjectControlPanel : public CVGuiScreenPanel +{ + DECLARE_CLASS( CObjectControlPanel, CVGuiScreenPanel ); + +public: + CObjectControlPanel( vgui::Panel *parent, const char *panelName ); + + virtual bool Init( KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData ); + virtual void OnCommand( const char *command ); + virtual void OnTick(); + +protected: + // Method to add controls to particular panels + vgui::Panel *GetActivePanel() { return m_pActivePanel; } + + // Override these to deal with various controls in various modes + virtual void OnTickActive( C_BaseObject *pObj, C_TFPlayer *pLocalPlayer ); + + C_BaseObject *GetOwningObject() const; + + // This should update the current panel and return that panel. + virtual vgui::Panel* TickCurrentPanel(); + + // Send a message to the owner. + void SendToServerObject( const char *pMsg ); + +private: + + vgui::EditablePanel *m_pActivePanel; + + vgui::Panel *m_pCurrentPanel; +}; + + +// This is used for child panels. It forwards the messages to the parent panel. +class CCommandChainingPanel : public vgui::EditablePanel +{ + typedef vgui::EditablePanel BaseClass; + +public: + CCommandChainingPanel( vgui::Panel *parent, const char *panelName ) : + BaseClass( parent, panelName ) + { + SetPaintBackgroundEnabled( false ); + } + + void OnCommand( const char *command ) + { + BaseClass::OnCommand( command ); + if (GetParent()) + { + GetParent()->OnCommand(command); + } + } +}; + + +//----------------------------------------------------------------------------- +// This is a panel for an object that has rotational controls +//----------------------------------------------------------------------------- +class CRotatingObjectControlPanel : public CObjectControlPanel +{ + DECLARE_CLASS( CRotatingObjectControlPanel, CObjectControlPanel ); + +public: + CRotatingObjectControlPanel( vgui::Panel *parent, const char *panelName ); + + virtual bool Init( KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData ); + +protected: + virtual void OnTickActive( C_BaseObject *pObj, C_TFPlayer *pLocalPlayer ); + +private: + CRotationSlider *m_pRotationSlider; + vgui::Label *m_pRotationLabel; +}; + +#endif // OBJECTCONTROLPANEL_H diff --git a/game/client/tf/vgui/backgroundpanel.cpp b/game/client/tf/vgui/backgroundpanel.cpp new file mode 100644 index 0000000..9783c21 --- /dev/null +++ b/game/client/tf/vgui/backgroundpanel.cpp @@ -0,0 +1,677 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "backgroundpanel.h" + +#include <vgui/IVGui.h> +#include <vgui/IScheme.h> +#include <vgui/ISurface.h> +#include <vgui_controls/Label.h> +#include <vgui/ILocalize.h> +#include "vgui_controls/BuildGroup.h" +#include "vgui_controls/BitmapImagePanel.h" + +using namespace vgui; + +#define DEBUG_WINDOW_RESIZING 0 +#define DEBUG_WINDOW_REPOSITIONING 0 + +//----------------------------------------------------------------------------- +const int NumSegments = 7; +static int coord[NumSegments+1] = { + 0, + 1, + 2, + 3, + 4, + 6, + 9, + 10 +}; + +//----------------------------------------------------------------------------- +void DrawRoundedBackground( Color bgColor, int wide, int tall ) +{ + int x1, x2, y1, y2; + surface()->DrawSetColor(bgColor); + surface()->DrawSetTextColor(bgColor); + + int i; + + // top-left corner -------------------------------------------------------- + int xDir = 1; + int yDir = -1; + int xIndex = 0; + int yIndex = NumSegments - 1; + int xMult = 1; + int yMult = 1; + int x = 0; + int y = 0; + for ( i=0; i<NumSegments; ++i ) + { + x1 = MIN( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult ); + x2 = MAX( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult ); + y1 = MAX( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult ); + y2 = y + coord[NumSegments]; + surface()->DrawFilledRect( x1, y1, x2, y2 ); + + xIndex += xDir; + yIndex += yDir; + } + + // top-right corner ------------------------------------------------------- + xDir = 1; + yDir = -1; + xIndex = 0; + yIndex = NumSegments - 1; + x = wide; + y = 0; + xMult = -1; + yMult = 1; + for ( i=0; i<NumSegments; ++i ) + { + x1 = MIN( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult ); + x2 = MAX( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult ); + y1 = MAX( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult ); + y2 = y + coord[NumSegments]; + surface()->DrawFilledRect( x1, y1, x2, y2 ); + xIndex += xDir; + yIndex += yDir; + } + + // bottom-right corner ---------------------------------------------------- + xDir = 1; + yDir = -1; + xIndex = 0; + yIndex = NumSegments - 1; + x = wide; + y = tall; + xMult = -1; + yMult = -1; + for ( i=0; i<NumSegments; ++i ) + { + x1 = MIN( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult ); + x2 = MAX( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult ); + y1 = y - coord[NumSegments]; + y2 = MIN( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult ); + surface()->DrawFilledRect( x1, y1, x2, y2 ); + xIndex += xDir; + yIndex += yDir; + } + + // bottom-left corner ----------------------------------------------------- + xDir = 1; + yDir = -1; + xIndex = 0; + yIndex = NumSegments - 1; + x = 0; + y = tall; + xMult = 1; + yMult = -1; + for ( i=0; i<NumSegments; ++i ) + { + x1 = MIN( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult ); + x2 = MAX( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult ); + y1 = y - coord[NumSegments]; + y2 = MIN( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult ); + surface()->DrawFilledRect( x1, y1, x2, y2 ); + xIndex += xDir; + yIndex += yDir; + } + + // paint between top left and bottom left --------------------------------- + x1 = 0; + x2 = coord[NumSegments]; + y1 = coord[NumSegments]; + y2 = tall - coord[NumSegments]; + surface()->DrawFilledRect( x1, y1, x2, y2 ); + + // paint between left and right ------------------------------------------- + x1 = coord[NumSegments]; + x2 = wide - coord[NumSegments]; + y1 = 0; + y2 = tall; + surface()->DrawFilledRect( x1, y1, x2, y2 ); + + // paint between top right and bottom right ------------------------------- + x1 = wide - coord[NumSegments]; + x2 = wide; + y1 = coord[NumSegments]; + y2 = tall - coord[NumSegments]; + surface()->DrawFilledRect( x1, y1, x2, y2 ); +} + +//----------------------------------------------------------------------------- +void DrawRoundedBorder( Color borderColor, int wide, int tall ) +{ + int x1, x2, y1, y2; + surface()->DrawSetColor(borderColor); + surface()->DrawSetTextColor(borderColor); + + int i; + + // top-left corner -------------------------------------------------------- + int xDir = 1; + int yDir = -1; + int xIndex = 0; + int yIndex = NumSegments - 1; + int xMult = 1; + int yMult = 1; + int x = 0; + int y = 0; + for ( i=0; i<NumSegments; ++i ) + { + x1 = MIN( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult ); + x2 = MAX( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult ); + y1 = MIN( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult ); + y2 = MAX( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult ); + surface()->DrawFilledRect( x1, y1, x2, y2 ); + + xIndex += xDir; + yIndex += yDir; + } + + // top-right corner ------------------------------------------------------- + xDir = 1; + yDir = -1; + xIndex = 0; + yIndex = NumSegments - 1; + x = wide; + y = 0; + xMult = -1; + yMult = 1; + for ( i=0; i<NumSegments; ++i ) + { + x1 = MIN( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult ); + x2 = MAX( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult ); + y1 = MIN( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult ); + y2 = MAX( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult ); + surface()->DrawFilledRect( x1, y1, x2, y2 ); + xIndex += xDir; + yIndex += yDir; + } + + // bottom-right corner ---------------------------------------------------- + xDir = 1; + yDir = -1; + xIndex = 0; + yIndex = NumSegments - 1; + x = wide; + y = tall; + xMult = -1; + yMult = -1; + for ( i=0; i<NumSegments; ++i ) + { + x1 = MIN( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult ); + x2 = MAX( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult ); + y1 = MIN( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult ); + y2 = MAX( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult ); + surface()->DrawFilledRect( x1, y1, x2, y2 ); + xIndex += xDir; + yIndex += yDir; + } + + // bottom-left corner ----------------------------------------------------- + xDir = 1; + yDir = -1; + xIndex = 0; + yIndex = NumSegments - 1; + x = 0; + y = tall; + xMult = 1; + yMult = -1; + for ( i=0; i<NumSegments; ++i ) + { + x1 = MIN( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult ); + x2 = MAX( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult ); + y1 = MIN( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult ); + y2 = MAX( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult ); + surface()->DrawFilledRect( x1, y1, x2, y2 ); + xIndex += xDir; + yIndex += yDir; + } + + // top -------------------------------------------------------------------- + x1 = coord[NumSegments]; + x2 = wide - coord[NumSegments]; + y1 = 0; + y2 = 1; + surface()->DrawFilledRect( x1, y1, x2, y2 ); + + // bottom ----------------------------------------------------------------- + x1 = coord[NumSegments]; + x2 = wide - coord[NumSegments]; + y1 = tall - 1; + y2 = tall; + surface()->DrawFilledRect( x1, y1, x2, y2 ); + + // left ------------------------------------------------------------------- + x1 = 0; + x2 = 1; + y1 = coord[NumSegments]; + y2 = tall - coord[NumSegments]; + surface()->DrawFilledRect( x1, y1, x2, y2 ); + + // right ------------------------------------------------------------------ + x1 = wide - 1; + x2 = wide; + y1 = coord[NumSegments]; + y2 = tall - coord[NumSegments]; + surface()->DrawFilledRect( x1, y1, x2, y2 ); +} + +//----------------------------------------------------------------------------- +class CaptionLabel : public Label +{ +public: + CaptionLabel(Panel *parent, const char *panelName, const char *text) : Label(parent, panelName, text) + { + } + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) + { + Label::ApplySchemeSettings( pScheme ); + SetFont( pScheme->GetFont( "MenuTitle", IsProportional() ) ); + } +}; + +//----------------------------------------------------------------------------- +// Purpose: transform a normalized value into one that is scaled based the minimum +// of the horizontal and vertical ratios +//----------------------------------------------------------------------------- +static int GetAlternateProportionalValueFromNormal(int normalizedValue) +{ + int wide, tall; + GetHudSize( wide, tall ); + int proH, proW; + surface()->GetProportionalBase( proW, proH ); + double scaleH = (double)tall / (double)proH; + double scaleW = (double)wide / (double)proW; + double scale = (scaleW < scaleH) ? scaleW : scaleH; + + return (int)( normalizedValue * scale ); +} + +//----------------------------------------------------------------------------- +// Purpose: transform a standard scaled value into one that is scaled based the minimum +// of the horizontal and vertical ratios +//----------------------------------------------------------------------------- +int GetAlternateProportionalValueFromScaled(vgui::HScheme hScheme, int scaledValue) +{ + return GetAlternateProportionalValueFromNormal( scheme()->GetProportionalNormalizedValueEx( hScheme, scaledValue ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: moves and resizes a single control +//----------------------------------------------------------------------------- +static void RepositionControl( Panel *pPanel ) +{ + int x, y, w, h; + pPanel->GetBounds(x, y, w, h); + +#if DEBUG_WINDOW_RESIZING + int x1, y1, w1, h1; + pPanel->GetBounds(x1, y1, w1, h1); + int x2, y2, w2, h2; + x2 = scheme()->GetProportionalNormalizedValueEx( pPanel->GetScheme(),x1 ); + y2 = scheme()->GetProportionalNormalizedValueEx( pPanel->GetScheme(),y1 ); + w2 = scheme()->GetProportionalNormalizedValueEx( pPanel->GetScheme(),w1 ); + h2 = scheme()->GetProportionalNormalizedValueEx( pPanel->GetScheme(),h1 ); +#endif + + x = GetAlternateProportionalValueFromScaled(pPanel->GetScheme(),x); + y = GetAlternateProportionalValueFromScaled(pPanel->GetScheme(),y); + w = GetAlternateProportionalValueFromScaled(pPanel->GetScheme(),w); + h = GetAlternateProportionalValueFromScaled(pPanel->GetScheme(),h); + + pPanel->SetBounds(x, y, w, h); + +#if DEBUG_WINDOW_RESIZING + DevMsg( "Resizing '%s' from (%d,%d) %dx%d to (%d,%d) %dx%d -- initially was (%d,%d) %dx%d\n", + pPanel->GetName(), x1, y1, w1, h1, x, y, w, h, x2, y2, w2, h2 ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Sets colors etc for background image panels +//----------------------------------------------------------------------------- +void ApplyBackgroundSchemeSettings( EditablePanel *pWindow, vgui::IScheme *pScheme ) +{ + Color bgColor = Color( 255, 255, 255, pScheme->GetColor( "BgColor", Color( 0, 0, 0, 0 ) )[3] ); + Color fgColor = pScheme->GetColor( "FgColor", Color( 0, 0, 0, 0 ) ); + + if ( !pWindow ) + return; + + CBitmapImagePanel *pBitmapPanel; + + // corners -------------------------------------------- + pBitmapPanel = dynamic_cast< CBitmapImagePanel * >(pWindow->FindChildByName( "TopLeftPanel" )); + if ( pBitmapPanel ) + { + pBitmapPanel->setImageColor( bgColor ); + } + pBitmapPanel = dynamic_cast< CBitmapImagePanel * >(pWindow->FindChildByName( "TopRightPanel" )); + if ( pBitmapPanel ) + { + pBitmapPanel->setImageColor( bgColor ); + } + pBitmapPanel = dynamic_cast< CBitmapImagePanel * >(pWindow->FindChildByName( "BottomLeftPanel" )); + if ( pBitmapPanel ) + { + pBitmapPanel->setImageColor( bgColor ); + } + pBitmapPanel = dynamic_cast< CBitmapImagePanel * >(pWindow->FindChildByName( "BottomRightPanel" )); + if ( pBitmapPanel ) + { + pBitmapPanel->setImageColor( bgColor ); + } + + // background ----------------------------------------- + pBitmapPanel = dynamic_cast< CBitmapImagePanel * >(pWindow->FindChildByName( "TopSolid" )); + if ( pBitmapPanel ) + { + pBitmapPanel->setImageColor( bgColor ); + } + pBitmapPanel = dynamic_cast< CBitmapImagePanel * >(pWindow->FindChildByName( "UpperMiddleSolid" )); + if ( pBitmapPanel ) + { + pBitmapPanel->setImageColor( bgColor ); + } + pBitmapPanel = dynamic_cast< CBitmapImagePanel * >(pWindow->FindChildByName( "LowerMiddleSolid" )); + if ( pBitmapPanel ) + { + pBitmapPanel->setImageColor( bgColor ); + } + pBitmapPanel = dynamic_cast< CBitmapImagePanel * >(pWindow->FindChildByName( "BottomSolid" )); + if ( pBitmapPanel ) + { + pBitmapPanel->setImageColor( bgColor ); + } + + // Logo ----------------------------------------------- +/* pBitmapPanel = dynamic_cast< CBitmapImagePanel * >(pWindow->FindChildByName( "ExclamationPanel" )); + if ( pBitmapPanel ) + { + pBitmapPanel->setImageColor( fgColor ); + } +*/ +} + +//----------------------------------------------------------------------------- +// Purpose: Re-aligns background image panels so they are touching. +//----------------------------------------------------------------------------- +static void FixupBackgroundPanels( EditablePanel *pWindow, int offsetX, int offsetY ) +{ + if ( !pWindow ) + return; + + int screenWide, screenTall; + pWindow->GetSize( screenWide, screenTall ); + + int inset = GetAlternateProportionalValueFromNormal( 20 ); + int cornerSize = GetAlternateProportionalValueFromNormal( 10 ); + + int titleHeight = GetAlternateProportionalValueFromNormal( 42 ); + int mainHeight = GetAlternateProportionalValueFromNormal( 376 ); + + int logoSize = titleHeight; + + int captionInset = GetAlternateProportionalValueFromNormal( 76 ); + + Panel *pPanel; + + // corners -------------------------------------------- + pPanel = pWindow->FindChildByName( "TopLeftPanel" ); + if ( pPanel ) + { + pPanel->SetZPos( -20 ); + pPanel->SetBounds( offsetX + inset, offsetY + inset, cornerSize, cornerSize ); + } + + pPanel = pWindow->FindChildByName( "TopRightPanel" ); + if ( pPanel ) + { + pPanel->SetZPos( -20 ); + pPanel->SetBounds( screenWide - offsetX - inset - cornerSize, offsetY + inset, cornerSize, cornerSize ); + } + + pPanel = pWindow->FindChildByName( "BottomLeftPanel" ); + if ( pPanel ) + { + pPanel->SetZPos( -20 ); + pPanel->SetBounds( offsetX + inset, screenTall - offsetY - inset - cornerSize, cornerSize, cornerSize ); + } + + pPanel = pWindow->FindChildByName( "BottomRightPanel" ); + if ( pPanel ) + { + pPanel->SetZPos( -20 ); + pPanel->SetBounds( screenWide - offsetX - inset - cornerSize, screenTall - offsetY - inset - cornerSize, cornerSize, cornerSize ); + } + + // background ----------------------------------------- + pPanel = pWindow->FindChildByName( "TopSolid" ); + if ( pPanel ) + { + pPanel->SetZPos( -20 ); + pPanel->SetBounds( offsetX + inset + cornerSize, offsetY + inset, screenWide - 2*offsetX - 2*inset - 2*cornerSize, cornerSize ); + } + + pPanel = pWindow->FindChildByName( "UpperMiddleSolid" ); + if ( pPanel ) + { + pPanel->SetZPos( -20 ); + pPanel->SetBounds( offsetX + inset, offsetY + inset + cornerSize, screenWide - 2*offsetX - 2*inset, titleHeight ); + } + + pPanel = pWindow->FindChildByName( "LowerMiddleSolid" ); + if ( pPanel ) + { + pPanel->SetZPos( -20 ); + pPanel->SetBounds( offsetX + inset + cornerSize, screenTall - offsetY - inset - cornerSize, screenWide - 2*offsetX - 2*inset - 2*cornerSize, cornerSize ); + } + + pPanel = pWindow->FindChildByName( "BottomSolid" ); + if ( pPanel ) + { + pPanel->SetZPos( -20 ); + pPanel->SetBounds( offsetX + inset, screenTall - offsetY - inset - cornerSize - mainHeight, screenWide - 2*offsetX - 2*inset, mainHeight ); + } + + // transparent border --------------------------------- + pPanel = pWindow->FindChildByName( "TopClear" ); + if ( pPanel ) + { + pPanel->SetZPos( -20 ); + pPanel->SetBounds( 0, 0, screenWide, offsetY + inset ); + } + + pPanel = pWindow->FindChildByName( "BottomClear" ); + if ( pPanel ) + { + pPanel->SetZPos( -20 ); + pPanel->SetBounds( 0, screenTall - offsetY - inset, screenWide, offsetY + inset ); + } + + pPanel = pWindow->FindChildByName( "LeftClear" ); + if ( pPanel ) + { + pPanel->SetZPos( -20 ); + pPanel->SetBounds( 0, offsetY + inset, offsetX + inset, screenTall - 2*offsetY - 2*inset ); + } + + pPanel = pWindow->FindChildByName( "RightClear" ); + if ( pPanel ) + { + pPanel->SetZPos( -20 ); + pPanel->SetBounds( screenWide - offsetX - inset, offsetY + inset, offsetX + inset, screenTall - 2*offsetY - 2*inset ); + } + + // Logo ----------------------------------------------- +/* int logoInset = (cornerSize + titleHeight - logoSize)/2; + pPanel = pWindow->FindChildByName( "ExclamationPanel" ); + if ( pPanel ) + { + pPanel->SetZPos( -19 ); // higher than the background + pPanel->SetBounds( offsetX + inset + logoInset, offsetY + inset + logoInset, logoSize, logoSize ); + } +*/ + // Title caption -------------------------------------- + pPanel = dynamic_cast< Label * >(pWindow->FindChildByName( "CaptionLabel" )); + if ( pPanel ) + { + pPanel->SetZPos( -19 ); // higher than the background + pPanel->SetBounds( offsetX + captionInset/*inset + 2*logoInset + logoSize*/, offsetY + inset /*+ logoInset*/, screenWide, logoSize ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Creates background image panels +//----------------------------------------------------------------------------- +void CreateBackground( EditablePanel *pWindow ) +{ + // corners -------------------------------------------- + new CBitmapImagePanel( pWindow, "TopLeftPanel", "gfx/vgui/round_corner_nw" ); + new CBitmapImagePanel( pWindow, "TopRightPanel", "gfx/vgui/round_corner_ne" ); + new CBitmapImagePanel( pWindow, "BottomLeftPanel", "gfx/vgui/round_corner_sw" ); + new CBitmapImagePanel( pWindow, "BottomRightPanel", "gfx/vgui/round_corner_se" ); + + // background ----------------------------------------- + new CBitmapImagePanel( pWindow, "TopSolid", "gfx/vgui/solid_background" ); + new CBitmapImagePanel( pWindow, "UpperMiddleSolid", "gfx/vgui/solid_background" ); + new CBitmapImagePanel( pWindow, "LowerMiddleSolid", "gfx/vgui/solid_background" ); + new CBitmapImagePanel( pWindow, "BottomSolid", "gfx/vgui/solid_background" ); + + // transparent border --------------------------------- + new CBitmapImagePanel( pWindow, "TopClear", "gfx/vgui/trans_background" ); + new CBitmapImagePanel( pWindow, "BottomClear", "gfx/vgui/trans_background" ); + new CBitmapImagePanel( pWindow, "LeftClear", "gfx/vgui/trans_background" ); + new CBitmapImagePanel( pWindow, "RightClear", "gfx/vgui/trans_background" ); + + // Logo ----------------------------------------------- +// new CBitmapImagePanel( pWindow, "ExclamationPanel", "gfx/vgui/TF_logo" ); + + // Title caption -------------------------------------- + Panel *pPanel = dynamic_cast< Label * >(pWindow->FindChildByName( "CaptionLabel" )); + if ( !pPanel ) + new CaptionLabel( pWindow, "CaptionLabel", "" ); +} + +void ResizeWindowControls( EditablePanel *pWindow, int tall, int wide, int offsetX, int offsetY ) +{ + if (!pWindow || !pWindow->GetBuildGroup() || !pWindow->GetBuildGroup()->GetPanelList()) + return; + + CUtlVector<PHandle> *panelList = pWindow->GetBuildGroup()->GetPanelList(); + CUtlVector<Panel *> resizedPanels; + CUtlVector<Panel *> movedPanels; + + // Resize to account for 1.25 aspect ratio (1280x1024) screens + { + for ( int i = 0; i < panelList->Size(); ++i ) + { + PHandle handle = (*panelList)[i]; + + Panel *panel = handle.Get(); + + bool found = false; + for ( int j = 0; j < resizedPanels.Size(); ++j ) + { + if (panel == resizedPanels[j]) + found = true; + } + + if (!panel || found) + { + continue; + } + + resizedPanels.AddToTail( panel ); // don't move a panel more than once + + if ( panel != pWindow ) + { + RepositionControl( panel ); + } + } + } + + // and now re-center them. Woohoo! + for ( int i = 0; i < panelList->Size(); ++i ) + { + PHandle handle = (*panelList)[i]; + + Panel *panel = handle.Get(); + + bool found = false; + for ( int j = 0; j < movedPanels.Size(); ++j ) + { + if (panel == movedPanels[j]) + found = true; + } + + if (!panel || found) + { + continue; + } + + movedPanels.AddToTail( panel ); // don't move a panel more than once + + if ( panel != pWindow ) + { + int x, y; + + panel->GetPos( x, y ); + panel->SetPos( x + offsetX, y + offsetY ); + +#if DEBUG_WINDOW_REPOSITIONING + DevMsg( "Repositioning '%s' from (%d,%d) to (%d,%d) -- a distance of (%d,%d)\n", + panel->GetName(), x, y, x + offsetX, y + offsetY, offsetX, offsetY ); +#endif + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Resizes windows to fit completely on-screen (for 1280x1024), and +// centers them on the screen. Sub-controls are also resized and moved. +//----------------------------------------------------------------------------- +void LayoutBackgroundPanel( EditablePanel *pWindow ) +{ + if ( !pWindow ) + return; + + int screenW, screenH; + GetHudSize( screenW, screenH ); + + int wide, tall; + pWindow->GetSize( wide, tall ); + + int offsetX = 0; + int offsetY = 0; + + // Slide everything over to the center + pWindow->SetBounds( 0, 0, screenW, screenH ); + + if ( wide != screenW || tall != screenH ) + { + wide = GetAlternateProportionalValueFromScaled(pWindow->GetScheme(), wide); + tall = GetAlternateProportionalValueFromScaled(pWindow->GetScheme(), tall); + + offsetX = (screenW - wide)/2; + offsetY = (screenH - tall)/2; + + ResizeWindowControls( pWindow, tall, wide, offsetX, offsetY ); + } + + // now that the panels are moved/resized, look for some bg panels, and re-align them + FixupBackgroundPanels( pWindow, offsetX, offsetY ); +} + +//----------------------------------------------------------------------------- + diff --git a/game/client/tf/vgui/backgroundpanel.h b/game/client/tf/vgui/backgroundpanel.h new file mode 100644 index 0000000..18ebc23 --- /dev/null +++ b/game/client/tf/vgui/backgroundpanel.h @@ -0,0 +1,53 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TFBACKGROUND_H +#define TFBACKGROUND_H + +#include <vgui_controls/Frame.h> +#include <vgui_controls/EditablePanel.h> + +//----------------------------------------------------------------------------- +// Purpose: Creates background image panels +//----------------------------------------------------------------------------- +void CreateBackground( vgui::EditablePanel *pWindow ); + +//----------------------------------------------------------------------------- +// Purpose: Resizes windows to fit completely on-screen (for 1280x1024), and +// centers them on the screen. Sub-controls are also resized and moved. +//----------------------------------------------------------------------------- +void LayoutBackgroundPanel( vgui::EditablePanel *pWindow ); + +//----------------------------------------------------------------------------- +// Purpose: Sets colors etc for background image panels +//----------------------------------------------------------------------------- +void ApplyBackgroundSchemeSettings( vgui::EditablePanel *pWindow, vgui::IScheme *pScheme ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ResizeWindowControls( vgui::EditablePanel *pWindow, int tall, int wide, int offsetX, int offsetY ); + +//----------------------------------------------------------------------------- +// Purpose: transform a standard scaled value into one that is scaled based the minimum +// of the horizontal and vertical ratios +//----------------------------------------------------------------------------- +int GetAlternateProportionalValueFromScaled( vgui::HScheme hScheme, int scaledValue ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void DrawRoundedBackground( Color bgColor, int wide, int tall ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void DrawRoundedBorder( Color borderColor, int wide, int tall ); + +//----------------------------------------------------------------------------- + +#endif // TFBACKGROUND_H diff --git a/game/client/tf/vgui/blueprint_panel.cpp b/game/client/tf/vgui/blueprint_panel.cpp new file mode 100644 index 0000000..c0bc484 --- /dev/null +++ b/game/client/tf/vgui/blueprint_panel.cpp @@ -0,0 +1,185 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "vgui/IInput.h" +#include <vgui/IVGui.h> +#include <vgui/IScheme.h> +#include "blueprint_panel.h" +#include "vgui_controls/TextImage.h" +#include "vgui_controls/Label.h" +#include "vgui_controls/Button.h" +#include "ienginevgui.h" +#include "VGuiMatSurface/IMatSystemSurface.h" +#include "renderparm.h" + +DECLARE_BUILD_FACTORY( CBlueprintPanel ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBlueprintPanel::CBlueprintPanel( vgui::Panel *parent, const char *name ) : vgui::EditablePanel( parent, name ) +{ + m_bClickable = false; + m_bMouseOver = false; + + m_bInStack = false; + + SetActAsButton( false, false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBlueprintPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "Resource/UI/build_menu/base_selectable.res" ); + + m_pItemNameLabel = dynamic_cast<vgui::Label*>( FindChildByName("ItemNameLabel") ); + m_pItemCostLabel = dynamic_cast<vgui::Label*>( FindChildByName("CostLabel") ); + m_pIcon = dynamic_cast<CIconPanel*>( FindChildByName("BuildingIcon") ); + m_pMetalIcon = dynamic_cast<CIconPanel*>( FindChildByName("MetalIcon") ); + m_pBackground = dynamic_cast<CIconPanel*>( FindChildByName("ItemBackground") ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBlueprintPanel::SetObjectInfo( const CObjectInfo* pNewInfo ) +{ + m_pObjectInfo = pNewInfo; + + bool bVisible = pNewInfo != NULL; + + if ( m_pItemNameLabel ) + { + if ( m_pObjectInfo ) + { + m_pItemNameLabel->SetText( m_pObjectInfo->m_pBuilderWeaponName ); + } + m_pItemNameLabel->SetVisible( bVisible ); + } + + if ( m_pItemCostLabel ) + { + if ( m_pObjectInfo ) + { + V_snprintf( m_pszCost, sizeof( m_pszCost ), "%i", m_pObjectInfo->m_Cost ); + m_pItemCostLabel->SetText( m_pszCost ); + } + m_pItemCostLabel->SetVisible( bVisible ); + } + + if ( m_pIcon ) + { + if ( m_pObjectInfo ) + { + m_pIcon->SetIcon( m_pObjectInfo->m_pIconMenu ); + } + m_pIcon->SetVisible( bVisible ); + } + + if ( m_pMetalIcon ) + { + m_pMetalIcon->SetVisible( bVisible ); + } + + if ( m_pBackground ) + { + m_pBackground->SetVisible( bVisible ); + } + + SetVisible( bVisible ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBlueprintPanel::SetActAsButton( bool bClickable, bool bMouseOver ) +{ + m_bClickable = bClickable; + m_bMouseOver = bMouseOver; + + SetMouseInputEnabled( m_bClickable || m_bMouseOver ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBlueprintPanel::OnCursorEntered( void ) +{ + if ( !m_bMouseOver ) + return; + + PostActionSignal( new KeyValues("BlueprintPanelEntered") ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBlueprintPanel::OnCursorExited( void ) +{ + if ( !m_bMouseOver ) + return; + + PostActionSignal( new KeyValues("BlueprintPanelExited") ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBlueprintPanel::OnMousePressed(vgui::MouseCode code) +{ + if ( !m_bClickable || code != MOUSE_LEFT ) + return; + + PostActionSignal( new KeyValues("BlueprintPanelMousePressed") ); + + vgui::surface()->PlaySound( "UI/buttonclick.wav" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBlueprintPanel::OnMouseReleased(vgui::MouseCode code) +{ + if ( !m_bClickable || code != MOUSE_LEFT ) + return; + + PostActionSignal( new KeyValues("BlueprintPanelMouseReleased") ); + + vgui::surface()->PlaySound( "UI/buttonclickrelease.wav" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBlueprintPanel::OnMouseDoublePressed(vgui::MouseCode code) +{ + if ( !m_bClickable || code != MOUSE_LEFT ) + return; + + PostActionSignal( new KeyValues("BlueprintPanelMouseDoublePressed") ); + + vgui::surface()->PlaySound( "UI/buttonclickrelease.wav" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBlueprintPanel::OnCursorMoved( int x, int y ) +{ + if ( !m_bClickable ) + return; + + // Add our own xpos/ypos offset + int iXPos; + int iYPos; + GetPos( iXPos, iYPos ); + PostActionSignal( new KeyValues("BlueprintPanelCursorMoved", "x", x + iXPos, "y", y + iYPos) ); +}
\ No newline at end of file diff --git a/game/client/tf/vgui/blueprint_panel.h b/game/client/tf/vgui/blueprint_panel.h new file mode 100644 index 0000000..dd71205 --- /dev/null +++ b/game/client/tf/vgui/blueprint_panel.h @@ -0,0 +1,61 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef BLUEPRINT_PANEL_H +#define BLUEPRINT_PANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include <vgui_controls/Panel.h> +#include <vgui_controls/Frame.h> +#include "tf_shareddefs.h" +#include "IconPanel.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CBlueprintPanel : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CBlueprintPanel, vgui::EditablePanel ); +public: + CBlueprintPanel( vgui::Panel *parent, const char *name ); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + + void SetObjectInfo( const CObjectInfo* pNewInfo ); + const CObjectInfo* GetObjectInfo( void ) { return m_pObjectInfo; } + + // Button functionality + void SetActAsButton( bool bClickable, bool bMouseOver ); + virtual void OnCursorEntered(); + virtual void OnCursorExited(); + virtual void OnMousePressed(vgui::MouseCode code); + virtual void OnMouseDoublePressed(vgui::MouseCode code); + virtual void OnMouseReleased(vgui::MouseCode code); + MESSAGE_FUNC_INT_INT( OnCursorMoved, "OnCursorMoved", x, y ); + + void SetInStack( bool bVal ) { m_bInStack = bVal; } + bool IsInStack( void ) { return m_bInStack; } + + vgui::Label *m_pItemNameLabel; + vgui::Label *m_pItemCostLabel; + char m_pszCost[8]; + + CIconPanel *m_pMetalIcon; + CIconPanel *m_pIcon; + CIconPanel *m_pBackground; + + const CObjectInfo* m_pObjectInfo; + + bool m_bClickable; + bool m_bMouseOver; + + bool m_bInStack; +}; + +#endif // BLUEPRINT_PANEL_H diff --git a/game/client/tf/vgui/character_info_panel.cpp b/game/client/tf/vgui/character_info_panel.cpp new file mode 100644 index 0000000..6737a90 --- /dev/null +++ b/game/client/tf/vgui/character_info_panel.cpp @@ -0,0 +1,864 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "character_info_panel.h" +#include "tf_statsummary.h" +#include "vgui_controls/PropertySheet.h" +#include "vgui/IInput.h" +#include "baseviewport.h" +#include "iclientmode.h" +#include "charinfo_loadout_subpanel.h" +#include "charinfo_armory_subpanel.h" +#include "ienginevgui.h" +#include "tf_hud_statpanel.h" +#include "c_tf_player.h" +#include "tf_item_inventory.h" +#include "econ_notifications.h" +#include <vgui/ILocalize.h> +#include <vgui_controls/AnimationController.h> +#include "econ_ui.h" +#include "c_tf_gamestats.h" +#include "tf_item_pickup_panel.h" +#include "store/v1/tf_store_panel.h" +#include "store/v2/tf_store_panel2.h" +#include "store/tf_store.h" +#include "tf_matchmaking_dashboard.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +static vgui::DHANDLE<CCharacterInfoPanel> g_CharInfoPanel; +IEconRootUI* EconUI( void ) +{ + if (!g_CharInfoPanel.Get()) + { + g_CharInfoPanel = new CCharacterInfoPanel( NULL ); + g_CharInfoPanel->MakeReadyForUse(); + g_CharInfoPanel->InvalidateLayout( false, true ); + } + return g_CharInfoPanel; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CServerNotConnectedToSteamDialog *OpenServerNotConnectedToSteamDialog( vgui::Panel *pParent ); + +//----------------------------------------------------------------------------- +// Purpose: Basic help dialog +//----------------------------------------------------------------------------- +CCharacterInfoPanel::CCharacterInfoPanel( Panel *parent ) : PropertyDialog(parent, "character_info") +{ + // Character info is parented to the game UI panel + vgui::VPANEL gameuiPanel = enginevgui->GetPanel( PANEL_GAMEUIDLL ); + SetParent( gameuiPanel ); + + // We don't want the gameui to delete us, or things get messy + SetAutoDelete( false ); + + SetMoveable( false ); + SetSizeable( false ); + + vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme"); + SetScheme(scheme); + SetProportional( true ); + + // Character loadouts + m_pLoadoutPanel = new CCharInfoLoadoutSubPanel(this); + m_pLoadoutPanel->AddActionSignalTarget( this ); + AddPage( m_pLoadoutPanel, "#Loadout"); + + // Stat summary + CTFStatsSummaryPanel *pStatSummaryPanel = new CTFStatsSummaryPanel(this); + pStatSummaryPanel->SetupForEmbedded(); + AddPage( pStatSummaryPanel, "#Stats"); + CTFStatPanel *pStatPanel = GET_HUDELEMENT( CTFStatPanel ); + if ( pStatPanel ) + { + // Ask for our embedded stat summary be updated immediately + pStatPanel->UpdateStatSummaryPanel(); + } + + // Achievements + //AddPage(new CCharacterInfoSubAchievements(this), "#Achievements"); + + ListenForGameEvent( "gameui_hidden" ); + + m_pLoadoutPanel->SetVisible( false ); + + m_pNotificationsPresentPanel = NULL; + m_bPreventClosure = false; + m_iClosePanel = ECONUI_BASEUI; + m_iDefaultTeam = TF_TEAM_RED; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCharacterInfoPanel::~CCharacterInfoPanel() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharacterInfoPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "Resource/UI/CharInfoPanel.res" ); + + SetOKButtonVisible(false); + SetCancelButtonVisible(false); + + m_pNotificationsPresentPanel = FindChildByName( "NotificationsPresentPanel" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharacterInfoPanel::PerformLayout( void ) +{ + if ( GetVParent() ) + { + int w,h; + vgui::ipanel()->GetSize( GetVParent(), w, h ); + SetBounds(0,0,w,h); + } + + BaseClass::PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharacterInfoPanel::ShowPanel(bool bShow) +{ + m_bPreventClosure = false; + + // Keep the MM dashboard on top of us + bShow ? GetMMDashboardParentManager()->PushModalFullscreenPopup( this ) + : GetMMDashboardParentManager()->PopModalFullscreenPopup( this ); + + if ( bShow ) + { + if ( GetPropertySheet()->GetActivePage() != m_pLoadoutPanel ) + { + GetPropertySheet()->SetActivePage( m_pLoadoutPanel ); + } + else + { + // VGUI doesn't tell the starting active page that it's active, so we post a pageshow to it + ivgui()->PostMessage( m_pLoadoutPanel->GetVPanel(), new KeyValues("PageShow"), GetPropertySheet()->GetVPanel() ); + } + + //InvalidateLayout( false, true ); + Activate(); + + int iClass = m_pLoadoutPanel->GetCurrentClassIndex(); + OpenLoadoutToClass( iClass, false ); + } + else + { + PostMessage( m_pLoadoutPanel, new KeyValues("CancelSelection") ); + } + + bool bWasVisible = IsVisible() && m_pLoadoutPanel->IsVisible(); + SetVisible( bShow ); + if ( bWasVisible && !bShow ) + { + m_pLoadoutPanel->OnCharInfoClosing(); + + // Clear this out so it doesn't affect anything the next time the econ UI is opened + m_iClosePanel = ECONUI_BASEUI; + m_iDefaultTeam = TF_TEAM_RED; + } + m_pLoadoutPanel->SetVisible( bShow ); + + // When we first appear, if we're on a server that couldn't get our loadout, show the failure dialog. + if ( !bWasVisible && bShow ) + { + if ( engine->IsInGame() ) + { + C_TFPlayer *pLocal = C_TFPlayer::GetLocalTFPlayer(); + if ( pLocal && pLocal->m_Shared.IsLoadoutUnavailable() ) + { + OpenServerNotConnectedToSteamDialog( this ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharacterInfoPanel::FireGameEvent( IGameEvent *event ) +{ + const char * type = event->GetName(); + + if ( Q_strcmp(type, "gameui_hidden") == 0 ) + { + if ( m_bPreventClosure ) + { + engine->ClientCmd_Unrestricted( "gameui_activate" ); + } + else + { + ShowPanel( false ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharacterInfoPanel::Close() +{ + ShowPanel( false ); + + PostMessage( m_pLoadoutPanel, new KeyValues("CharInfoClosing") ); + + // If we're connected to a game server, we also close the game UI. + if ( engine->IsInGame() ) + { + bool bClose = true; + if ( m_bCheckForRoomOnExit ) + { + // Check to make sure the player has room for all his items. If not, bring up the discard panel. Otherwise, go away. + // We need to do this to catch players who used the "Change Loadout" button in the pickup panel, and may be out of room. + bClose = !TFInventoryManager()->CheckForRoomAndForceDiscard(); + } + + if ( bClose ) + { + engine->ClientCmd_Unrestricted( "gameui_hide" ); + } + } + + // Notify any listeners that we're closed + NotifyListenersOfCloseEvent(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharacterInfoPanel::NotifyListenersOfCloseEvent() +{ + FOR_EACH_VEC( m_vecOnCloseListeners, i ) + { + if ( m_vecOnCloseListeners[i].Get() ) + { + PostMessage( m_vecOnCloseListeners[i].Get(), new KeyValues( "EconUIClosed" ) ); + } + } + + // Clear that motherfucker out + m_vecOnCloseListeners.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharacterInfoPanel::OnCommand( const char *command ) +{ + if ( FStrEq( command, "back" ) ) + { + // If we're at the base loadout page, or if we want to force it, close the dialog completely... + // NOTE: Right now we don't support closing from the item selection screen. + const int iShowingPanel = m_pLoadoutPanel->GetShowingPanel(); + const int iCurrentClassIndex = m_pLoadoutPanel->GetCurrentClassIndex(); + const bool bIsInSelectionPanel = iShowingPanel == CHAP_LOADOUT && m_pLoadoutPanel->GetClassLoadoutPanel()->IsInSelectionPanel(); + const bool bNoClass = iCurrentClassIndex == TF_CLASS_UNDEFINED; + const bool bAtClosePanel = !bIsInSelectionPanel && + ( ( iShowingPanel == m_iClosePanel && bNoClass ) || ( iShowingPanel == CHAP_LOADOUT && -m_iClosePanel == iCurrentClassIndex ) ); + const bool bAtBaseLoadoutPage = iShowingPanel == CHAP_LOADOUT && bNoClass; + if ( bAtClosePanel || bAtBaseLoadoutPage ) + { + Close(); + } + // In the item selection panel? + else if ( bIsInSelectionPanel ) + { + m_pLoadoutPanel->GetClassLoadoutPanel()->GetItemSelectionPanel()->OnBackPressed(); + } + // In any other panel, just go back. + else + { + ShowPanel( true ); + } + } + else + { + engine->ClientCmd( const_cast<char *>( command ) ); + } + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharacterInfoPanel::OpenLoadoutToClass( int iClassIndex, bool bOpenClassLoadout ) +{ + Assert(iClassIndex >= TF_CLASS_UNDEFINED && iClassIndex < TF_CLASS_COUNT); + m_pLoadoutPanel->SetClassIndex( iClassIndex, bOpenClassLoadout ); + m_pLoadoutPanel->SetTeamIndex( m_iDefaultTeam ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharacterInfoPanel::OpenLoadoutToBackpack( void ) +{ + m_pLoadoutPanel->OpenToBackpack(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharacterInfoPanel::OpenLoadoutToCrafting( void ) +{ + m_pLoadoutPanel->OpenToCrafting(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharacterInfoPanel::OpenLoadoutToArmory( void ) +{ + m_pLoadoutPanel->OpenToArmory(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharacterInfoPanel::OnOpenArmoryDirect( KeyValues *data ) +{ + int iItemDef = data->GetInt( "itemdef", 0 ); + m_pLoadoutPanel->OpenToArmory( iItemDef ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharacterInfoPanel::OnKeyCodeTyped(vgui::KeyCode code) +{ + if ( code == KEY_ESCAPE ) + { + if ( !m_bPreventClosure ) + { + OnCommand( "back" ); + } + } + else + { + BaseClass::OnKeyCodeTyped( code ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharacterInfoPanel::OnKeyCodePressed(vgui::KeyCode code) +{ + ButtonCode_t nButtonCode = GetBaseButtonCode( code ); + + if ( nButtonCode == KEY_XBUTTON_B ) + { + if ( !m_bPreventClosure ) + { + OnCommand( "back" ); + } + } + else + { + BaseClass::OnKeyCodePressed( code ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharacterInfoPanel::OnThink() +{ + bool bShouldBeVisible = NotificationQueue_GetNumNotifications() != 0; + if ( m_pNotificationsPresentPanel != NULL && m_pNotificationsPresentPanel->IsVisible() != bShouldBeVisible ) + { + m_pNotificationsPresentPanel->SetVisible( bShouldBeVisible ); + if ( bShouldBeVisible ) + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "NotificationsPresentBlink" ); + } + else + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "NotificationsPresentBlinkStop" ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +IEconRootUI *CCharacterInfoPanel::OpenEconUI( int iDirectToPage, bool bCheckForInventorySpaceOnExit ) +{ + engine->ClientCmd_Unrestricted( "gameui_activate" ); + ShowPanel( true ); + + if ( iDirectToPage == ECONUI_BACKPACK ) + { + OpenLoadoutToBackpack(); + } + else if ( iDirectToPage == ECONUI_CRAFTING ) + { + OpenLoadoutToCrafting(); + } + else if ( iDirectToPage == ECONUI_ARMORY ) + { + OpenLoadoutToArmory(); + } + else if ( iDirectToPage < 0 ) + { + // Negative numbers go directly to the class loadout + OpenLoadoutToClass( -(iDirectToPage), true ); + } + + SetCheckForRoomOnExit( bCheckForInventorySpaceOnExit ); + + return this; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharacterInfoPanel::CloseEconUI( void ) +{ + if ( IsVisible() ) + { + ShowPanel( false ); + NotifyListenersOfCloseEvent(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CCharacterInfoPanel::IsUIPanelVisible( EconBaseUIPanels_t iPanel ) +{ + if ( !IsVisible() ) + return false; + + switch ( iPanel ) + { + case ECONUI_BACKPACK: + return (GetBackpackPanel() && GetBackpackPanel()->IsVisible()); + + case ECONUI_CRAFTING: + return (GetCraftingPanel() && GetCraftingPanel()->IsVisible()); + + case ECONUI_ARMORY: + return (GetArmoryPanel() && GetArmoryPanel()->IsVisible()); + + case ECONUI_TRADING: + break; + + default: + Assert(0); + break; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Open_CharInfo( const CCommand &args ) +{ + EconUI()->OpenEconUI(); +} +ConCommand open_charinfo( "open_charinfo", Open_CharInfo, "Open the character info panel", FCVAR_NONE ); + +void CCharacterInfoPanel::SetPreventClosure( bool bPrevent ) +{ + m_bPreventClosure = bPrevent; + + Panel* pBackButton = FindChildByName( "BackButton" ); + if ( pBackButton ) + { + pBackButton->SetEnabled( !bPrevent ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Open_CharInfoDirect( const CCommand &args ) +{ + // If we're in-game, start by opening the class we're currently playing + int iClass = TF_CLASS_UNDEFINED; + if ( engine->IsInGame() ) + { + C_TFPlayer *pLocal = C_TFPlayer::GetLocalTFPlayer(); + if ( pLocal ) + { + iClass = -(pLocal->m_Shared.GetDesiredPlayerClassIndex()); + if ( iClass == TF_CLASS_UNDEFINED ) + { + iClass = -(pLocal->GetPlayerClass()->GetClassIndex()); + } + } + } + + // override with command arg + if ( args.ArgC() > 1 ) + { + iClass = -atoi( args.Arg( 1 ) ); + } + + EconUI()->OpenEconUI( iClass ); +} +ConCommand open_charinfo_direct( "open_charinfo_direct", Open_CharInfoDirect, "Open the character info panel directly to the class you're currently playing.", FCVAR_NONE ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Open_CharInfoBackpack( const CCommand &args ) +{ + EconUI()->OpenEconUI( ECONUI_BACKPACK ); +} +ConCommand open_charinfo_backpack( "open_charinfo_backpack", Open_CharInfoBackpack, "Open the character info panel directly to backpack.", FCVAR_NONE ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Open_CharInfoCrafting( const CCommand &args ) +{ + EconUI()->OpenEconUI( ECONUI_CRAFTING ); +} +ConCommand open_charinfo_crafting( "open_charinfo_crafting", Open_CharInfoCrafting, "Open the character info panel directly to crafting screen.", FCVAR_NONE ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Open_CharInfoArmory( const CCommand &args ) +{ + EconUI()->OpenEconUI( ECONUI_ARMORY ); +} +ConCommand open_charinfo_armory( "open_charinfo_armory", Open_CharInfoArmory, "Open the character info panel directly to armory.", FCVAR_NONE ); + + +//================================================================================================================================ +// NOT CONNECTED TO STEAM WARNING DIALOG +//================================================================================================================================ +static vgui::DHANDLE<CServerNotConnectedToSteamDialog> g_ServerNotConnectedPanel; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CServerNotConnectedToSteamDialog::CServerNotConnectedToSteamDialog( vgui::Panel *pParent, const char *pElementName ) : BaseClass( pParent, "ServerNotConnectedToSteamDialog" ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CServerNotConnectedToSteamDialog::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + // load control settings... + LoadControlSettings( "resource/UI/ServerNotConnectedToSteam.res" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CServerNotConnectedToSteamDialog::OnCommand( const char *command ) +{ + if ( !Q_stricmp( command, "close" ) ) + { + TFModalStack()->PopModal( this ); + SetVisible( false ); + return; + } + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CServerNotConnectedToSteamDialog *OpenServerNotConnectedToSteamDialog( vgui::Panel *pParent ) +{ + if (!g_ServerNotConnectedPanel.Get()) + { + g_ServerNotConnectedPanel = vgui::SETUP_PANEL( new CServerNotConnectedToSteamDialog( pParent, NULL ) ); + } + g_ServerNotConnectedPanel->InvalidateLayout( false, true ); + + g_ServerNotConnectedPanel->SetVisible( true ); + g_ServerNotConnectedPanel->MakePopup(); + g_ServerNotConnectedPanel->MoveToFront(); + g_ServerNotConnectedPanel->SetKeyBoardInputEnabled(true); + g_ServerNotConnectedPanel->SetMouseInputEnabled(true); + TFModalStack()->PushModal( g_ServerNotConnectedPanel ); + return g_ServerNotConnectedPanel; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBackpackPanel *CCharacterInfoPanel::GetBackpackPanel( void ) +{ + return m_pLoadoutPanel->GetBackpackPanel(); +} +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCraftingPanel *CCharacterInfoPanel::GetCraftingPanel( void ) +{ + return m_pLoadoutPanel->GetCraftingPanel(); +} +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CArmoryPanel *CCharacterInfoPanel::GetArmoryPanel( void ) +{ + return m_pLoadoutPanel->GetArmoryPanel(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharacterInfoPanel::Gamestats_ItemTransaction( int eventID, CEconItemView *item, const char *pszReason, int iQuality ) +{ + C_CTF_GameStats.Event_ItemTransaction( eventID, item, pszReason, iQuality ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharacterInfoPanel::Gamestats_Store( int eventID, CEconItemView* item, const char* panelName, int classId, + const cart_item_t* cartItem, int checkoutAttempts, const char* storeError, int totalPrice, int currencyCode ) +{ + C_CTF_GameStats.Event_Store( eventID, item, panelName, classId, cartItem, checkoutAttempts, storeError, totalPrice, currencyCode ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharacterInfoPanel::SetExperimentValue( uint64 experimentValue ) +{ + C_CTF_GameStats.SetExperimentValue( experimentValue ); +} + +static vgui::DHANDLE<CTFItemPickupPanel> g_TFItemPickupPanel; +static vgui::DHANDLE<CTFItemDiscardPanel> g_TFItemDiscardPanel; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CItemPickupPanel *CCharacterInfoPanel::OpenItemPickupPanel( void ) +{ + if (!g_TFItemPickupPanel.Get()) + { + g_TFItemPickupPanel = vgui::SETUP_PANEL( new CTFItemPickupPanel( NULL ) ); + g_TFItemPickupPanel->InvalidateLayout( false, true ); + } + + engine->ClientCmd_Unrestricted( "gameui_activate" ); + g_TFItemPickupPanel->ShowPanel( true ); + + return g_TFItemPickupPanel; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CItemDiscardPanel *CCharacterInfoPanel::OpenItemDiscardPanel( void ) +{ + if (!g_TFItemDiscardPanel.Get()) + { + g_TFItemDiscardPanel = vgui::SETUP_PANEL( new CTFItemDiscardPanel( NULL ) ); + g_TFItemDiscardPanel->InvalidateLayout( false, true ); + } + + engine->ClientCmd_Unrestricted( "gameui_activate" ); + g_TFItemDiscardPanel->ShowPanel( true ); + + return g_TFItemDiscardPanel; +} + +static vgui::DHANDLE<CTFBaseStorePanel> g_StorePanel; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharacterInfoPanel::CreateStorePanel( void ) +{ + // Clean up previous store panel? + if ( g_StorePanel.Get() != NULL ) + { + g_StorePanel->MarkForDeletion(); + } + + // Create the store panel + CTFBaseStorePanel *pStorePanel = NULL; + if ( ShouldUseNewStore() ) + { + pStorePanel = new CTFStorePanel2( NULL ); + } + else + { + pStorePanel = new CTFStorePanel1( NULL ); + } + + g_StorePanel = vgui::SETUP_PANEL( pStorePanel ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CStorePanel *CCharacterInfoPanel::OpenStorePanel( int iItemDef, bool bAddToCart ) +{ + // Make sure we've got the appropriate connections to Steam + if ( !steamapicontext || !steamapicontext->SteamUtils() ) + { + OpenStoreStatusDialog( NULL, "#StoreUpdate_SteamRequired", true, false ); + return NULL; + } + + if ( !steamapicontext->SteamUtils()->IsOverlayEnabled() ) + { + OpenStoreStatusDialog( NULL, "#StoreUpdate_OverlayRequired", true, false ); + return NULL; + } + + if ( !CStorePanel::IsPricesheetLoaded() ) + { + OpenStoreStatusDialog( NULL, "#StoreUpdate_Loading", false, false ); + + CStorePanel::SetShouldShowWarnings( true ); + CStorePanel::RequestPricesheet(); + return NULL; + } + + if ( !g_StorePanel ) + return NULL; + + engine->ClientCmd_Unrestricted( "gameui_activate" ); + + if ( iItemDef ) + { + g_StorePanel->StartAtItemDef( iItemDef, bAddToCart ); + } + + g_StorePanel->ShowPanel( true ); + + return g_StorePanel; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CStorePanel *CCharacterInfoPanel::GetStorePanel( void ) +{ + return g_StorePanel; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharacterInfoPanel::AddPanelCloseListener( vgui::Panel *pListener ) +{ + if ( !pListener ) + return; + + VPanelHandle hPanel; + hPanel.Set( pListener->GetVPanel() ); + m_vecOnCloseListeners.AddToHead( hPanel ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharacterInfoPanel::SetClosePanel( int iPanel ) +{ + AssertMsg( ( iPanel < 0 && IsValidTFPlayerClass( -iPanel ) ) || + ( iPanel >= ECONUI_FIRST_PANEL && iPanel <= ECONUI_LAST_PANEL ), + "Panel out of range!" + ); + m_iClosePanel = iPanel; +} + +void CCharacterInfoPanel::SetDefaultTeam( int iTeam ) +{ + AssertMsg( iTeam == TF_TEAM_RED || iTeam == TF_TEAM_BLUE, "Invalid team" ); + m_iDefaultTeam = iTeam; +} + +//================================================================================================================================ +// NOT CONNECTED TO STEAM WARNING DIALOG +//================================================================================================================================ +static vgui::DHANDLE<CCheatDetectionDialog> g_CheatDetectionDialog; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCheatDetectionDialog::CCheatDetectionDialog( vgui::Panel *pParent, const char *pElementName ) : BaseClass( pParent, "CheatDetectionDialog" ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCheatDetectionDialog::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + // load control settings... + LoadControlSettings( "resource/UI/CheatDetectionDialog.res" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCheatDetectionDialog::OnCommand( const char *command ) +{ + if ( !Q_stricmp( command, "close" ) ) + { + TFModalStack()->PopModal( this ); + SetVisible( false ); + return; + } + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCheatDetectionDialog *OpenCheatDetectionDialog( vgui::Panel *pParent, const char *pszCheatMessage ) +{ + if (!g_CheatDetectionDialog.Get()) + { + g_CheatDetectionDialog = vgui::SETUP_PANEL( new CCheatDetectionDialog( pParent, NULL ) ); + } + g_CheatDetectionDialog->InvalidateLayout( false, true ); + + g_CheatDetectionDialog->SetVisible( true ); + g_CheatDetectionDialog->MakePopup(); + g_CheatDetectionDialog->MoveToFront(); + g_CheatDetectionDialog->SetKeyBoardInputEnabled(true); + g_CheatDetectionDialog->SetMouseInputEnabled(true); + TFModalStack()->PushModal( g_CheatDetectionDialog ); + g_CheatDetectionDialog->SetDialogVariable( "reason", g_pVGuiLocalize->Find( pszCheatMessage ) ); + return g_CheatDetectionDialog; +} diff --git a/game/client/tf/vgui/character_info_panel.h b/game/client/tf/vgui/character_info_panel.h new file mode 100644 index 0000000..8f435cd --- /dev/null +++ b/game/client/tf/vgui/character_info_panel.h @@ -0,0 +1,135 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef CHARACTER_INFO_PANEL_H +#define CHARACTER_INFO_PANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "econ_ui.h" +#include "vgui_controls/PropertyDialog.h" +#include "tf_shareddefs.h" +#include "GameEventListener.h" +#include "vgui_controls/Panel.h" +#include "vgui_controls/PHandle.h" + +class CCharInfoLoadoutSubPanel; +class CArmoryPanel; +class CBackpackPanel; +class CCraftingPanel; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CServerNotConnectedToSteamDialog : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CServerNotConnectedToSteamDialog, vgui::EditablePanel ); + +public: + CServerNotConnectedToSteamDialog( vgui::Panel *pParent, const char *pElementName ); + + virtual void ApplySchemeSettings( vgui::IScheme *scheme ); + virtual void OnCommand( const char *command ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CCheatDetectionDialog : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CCheatDetectionDialog, vgui::EditablePanel ); + +public: + CCheatDetectionDialog( vgui::Panel *pParent, const char *pElementName ); + + virtual void ApplySchemeSettings( vgui::IScheme *scheme ); + virtual void OnCommand( const char *command ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CCharacterInfoPanel : public vgui::PropertyDialog, public IEconRootUI, public CGameEventListener +{ + DECLARE_CLASS_SIMPLE( CCharacterInfoPanel, vgui::PropertyDialog ); +public: + CCharacterInfoPanel( Panel *parent ); + virtual ~CCharacterInfoPanel(); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void PerformLayout( void ); + virtual void OnCommand( const char *command ); + virtual void ShowPanel( bool bShow ); + virtual void OnKeyCodeTyped(vgui::KeyCode code) OVERRIDE; + virtual void OnKeyCodePressed(vgui::KeyCode code) OVERRIDE; + virtual void OnThink(); + + void OpenLoadoutToClass( int iClassIndex, bool bOpenClassLoadout ); + void OpenLoadoutToBackpack( void ); + void OpenLoadoutToCrafting( void ); + void OpenLoadoutToArmory( void ); + void SetCheckForRoomOnExit( bool bCheck ) { m_bCheckForRoomOnExit = bCheck; } + + void FireGameEvent( IGameEvent *event ); + + CArmoryPanel *GetArmoryPanel( void ); + + MESSAGE_FUNC_PARAMS( OnOpenArmoryDirect, "OpenArmoryDirect", data ); + + //--------------------------------------- + // IEconRootUI + virtual IEconRootUI *OpenEconUI( int iDirectToPage = 0, bool bCheckForInventorySpaceOnExit = false ); + virtual void CloseEconUI( void ); + virtual bool IsUIPanelVisible( EconBaseUIPanels_t iPanel ); + virtual void SetPreventClosure( bool bPrevent ) OVERRIDE; + + // Sub panel access. + // These are panels that are parented to the root EconUI. + virtual CBackpackPanel *GetBackpackPanel( void ); + virtual CCraftingPanel *GetCraftingPanel( void ); + + // Gamestats access + virtual void Gamestats_ItemTransaction( int eventID, CEconItemView *item, const char *pszReason = NULL, int iQuality = 0 ); + virtual void Gamestats_Store( int eventID, CEconItemView* item=NULL, const char* panelName=NULL, + int classId=0, const cart_item_t* in_cartItem=NULL, int in_checkoutAttempts=0, const char* storeError=NULL, int in_totalPrice=0, int in_currencyCode=0 ); + virtual void SetExperimentValue( uint64 experimentValue ); + + // Open separate economy panels (they're not parented to the root EconUI) + // This is here so that games can customize the implementation of these panels. + virtual CItemPickupPanel *OpenItemPickupPanel( void ); + virtual CItemDiscardPanel *OpenItemDiscardPanel( void ); + virtual void CreateStorePanel( void ); + virtual CStorePanel *OpenStorePanel( int iItemDef, bool bAddToCart ); + virtual CStorePanel *GetStorePanel( void ); + + // When the root UI is closed, send an "EconUIClosed" message to pListener. + virtual void AddPanelCloseListener( vgui::Panel *pListener ); + + // The panel at which we want back to actually close the UI - defaults to the root panel - a negative value can be passed in for class loadout panels + virtual void SetClosePanel( int iPanel ); + + // Call this to set which team the class loadout should display + virtual void SetDefaultTeam( int iTeam ); + +private: + void Close(); + void NotifyListenersOfCloseEvent(); + + vgui::Panel *m_pNotificationsPresentPanel; + CCharInfoLoadoutSubPanel *m_pLoadoutPanel; + bool m_bCheckForRoomOnExit; + bool m_bPreventClosure; + int m_iClosePanel; + int m_iDefaultTeam; + + CUtlVector< vgui::VPanelHandle > m_vecOnCloseListeners; +}; + +CCheatDetectionDialog *OpenCheatDetectionDialog( vgui::Panel *pParent, const char *pszCheatMessage ); + +#endif // CHARACTER_INFO_PANEL_H diff --git a/game/client/tf/vgui/charinfo_armory_subpanel.cpp b/game/client/tf/vgui/charinfo_armory_subpanel.cpp new file mode 100644 index 0000000..ceab6f7 --- /dev/null +++ b/game/client/tf/vgui/charinfo_armory_subpanel.cpp @@ -0,0 +1,945 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "charinfo_armory_subpanel.h" +#include "vgui/ISurface.h" +#include "vgui/IInput.h" +#include "vgui/ILocalize.h" +#include "c_tf_player.h" +#include "c_tf_gamestats.h" +#include "gamestringpool.h" +#include "tf_item_inventory.h" +#include "econ_item_system.h" +#include "iachievementmgr.h" +#include "store/store_panel.h" +#include "character_info_panel.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +ConVar tf_explanations_charinfo_armory_panel( "tf_explanations_charinfo_armory_panel", "0", FCVAR_ARCHIVE, "Whether the user has seen explanations for this panel." ); + +const char *g_szArmoryFilterStrings[ARMFILT_TOTAL] = +{ + "#ArmoryFilter_AllItems", // ARMFILT_ALL_ITEMS. + "#ArmoryFilter_Weapons", // ARMFILT_WEAPONS, + "#ArmoryFilter_Headgear", // ARMFILT_HEADGEAR, + "#ArmoryFilter_MiscItems", // ARMFILT_MISCITEMS, + "#ArmoryFilter_ActionItems", // ARMFILT_ACTIONITEMS, + "#ArmoryFilter_CraftItems", // ARMFILT_CRAFTITEMS, + "#ArmoryFilter_Tools", // ARMFILT_TOOLS, + "#ArmoryFilter_AllClass", // ARMFILT_CLASS_ALL, + "#ArmoryFilter_Scout", // ARMFILT_CLASS_SCOUT, + "#ArmoryFilter_Sniper", // ARMFILT_CLASS_SNIPER, + "#ArmoryFilter_Soldier", // ARMFILT_CLASS_SOLDIER, + "#ArmoryFilter_Demoman", // ARMFILT_CLASS_DEMOMAN, + "#ArmoryFilter_Medic", // ARMFILT_CLASS_MEDIC, + "#ArmoryFilter_Heavy", // ARMFILT_CLASS_HEAVY, + "#ArmoryFilter_Pyro", // ARMFILT_CLASS_PYRO, + "#ArmoryFilter_Spy", // ARMFILT_CLASS_SPY, + "#ArmoryFilter_Engineer", // ARMFILT_CLASS_ENGINEER, + "#ArmoryFilter_Donationitems", // ARMFILT_DONATIONITEMS, + + "", // ARMFILT_NUM_IN_DROPDOWN + "Not Used", // ARMFILT_CUSTOM +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CArmoryPanel::CArmoryPanel(Panel *parent, const char *panelName) : vgui::EditablePanel( parent, panelName ) +{ + m_pSelectedItemModelPanel = new CItemModelPanel( this, "SelectedItemModelPanel" ); + m_pSelectedItemImageModelPanel = new CItemModelPanel( this, "SelectedItemImageModelPanel" ); + m_pThumbnailModelPanelKVs = NULL; + m_bReapplyItemKVs = false; + m_CurrentFilter = ARMFILT_ALL_ITEMS; + m_OldFilter = ARMFILT_ALL_ITEMS; + m_iFilterPage = 0; + m_pNextPageButton = NULL; + m_pPrevPageButton = NULL; + m_pViewSetButton = NULL; + m_pStoreButton = NULL; + m_bAllowGotoStore = false; + + m_pDataPanel = new vgui::EditablePanel( this, "DataPanel" ); + m_pDataTextRichText = NULL; + + m_pMouseOverItemPanel = new CItemModelPanel( this, "mouseoveritempanel" ); + m_pMouseOverTooltip = new CItemModelPanelToolTip( this ); + m_pMouseOverTooltip->SetupPanels( this, m_pMouseOverItemPanel ); + m_pMouseOverTooltip->SetPositioningStrategy( IPTTP_BOTTOM_SIDE ); + + m_pFilterComboBox = new vgui::ComboBox( this, "FilterComboBox", ARMFILT_NUM_IN_DROPDOWN, false ); + m_pFilterComboBox->AddActionSignalTarget( this ); + + REGISTER_COLOR_AS_OVERRIDABLE( m_colThumbnailBG, "thumbnail_bgcolor" ); + REGISTER_COLOR_AS_OVERRIDABLE( m_colThumbnailBGMouseover, "thumbnail_bgcolor_mouseover" ); + REGISTER_COLOR_AS_OVERRIDABLE( m_colThumbnailBGSelected, "thumbnail_bgcolor_selected" ); + + m_bEventLogging = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CArmoryPanel::~CArmoryPanel() +{ + if ( m_pThumbnailModelPanelKVs ) + { + m_pThumbnailModelPanelKVs->deleteThis(); + m_pThumbnailModelPanelKVs = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CArmoryPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "Resource/UI/CharInfoArmorySubPanel.res" ); + + m_bReapplyItemKVs = true; + m_pMouseOverItemPanel->SetBorder( pScheme->GetBorder("LoadoutItemPopupBorder") ); + + m_pDataTextRichText = dynamic_cast<CEconItemDetailsRichText*>( m_pDataPanel->FindChildByName( "Data_TextRichText" ) ); + m_pNextPageButton = dynamic_cast<CExButton*>( FindChildByName("NextPageButton") ); + m_pPrevPageButton = dynamic_cast<CExButton*>( FindChildByName("PrevPageButton") ); + m_pViewSetButton = dynamic_cast<CExButton*>( FindChildByName("ViewSetButton") ); + m_pStoreButton = dynamic_cast<CExButton*>( FindChildByName("StoreButton") ); + + m_pDataTextRichText->SetURLClickedHandler( this ); + + m_colSetName = GetSchemeColor( "ItemSetName", Color(255, 255, 255, 255), pScheme ); + + SetupComboBox( NULL ); + UpdateSelectedItem(); + m_pFilterComboBox->SetBorder( NULL ); + + m_pMouseOverItemPanel->SetVisible( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CArmoryPanel::SetupComboBox( const char *pszCustomAddition ) +{ + m_pFilterComboBox->RemoveAll(); + + vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); + vgui::HFont hFont = pScheme->GetFont( "HudFontSmallestBold", true ); + m_pFilterComboBox->SetFont( hFont ); + + KeyValues *pKeyValues = new KeyValues( "data" ); + for ( int i = 0; i < ARMFILT_NUM_IN_DROPDOWN; i++ ) + { + pKeyValues->SetInt( "setfilter", i ); + m_pFilterComboBox->AddItem( g_szArmoryFilterStrings[i], pKeyValues ); + } + + if ( pszCustomAddition ) + { + pKeyValues->SetInt( "setfilter", ARMFILT_CUSTOM ); + m_pFilterComboBox->AddItem( g_pVGuiLocalize->Find( pszCustomAddition ), pKeyValues ); + + // Start with the custom filter selected + m_pFilterComboBox->SetNumberOfEditLines( ARMFILT_NUM_IN_DROPDOWN + 1 ); + m_pFilterComboBox->ActivateItemByRow( ARMFILT_NUM_IN_DROPDOWN ); + } + else + { + m_pFilterComboBox->SetNumberOfEditLines( ARMFILT_NUM_IN_DROPDOWN ); + m_pFilterComboBox->ActivateItemByRow( 0 ); + } + + pKeyValues->deleteThis(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CArmoryPanel::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + KeyValues *pItemKV = inResourceData->FindKey( "thumbnail_modelpanels_kv" ); + if ( pItemKV ) + { + if ( m_pThumbnailModelPanelKVs ) + { + m_pThumbnailModelPanelKVs->deleteThis(); + } + m_pThumbnailModelPanelKVs = new KeyValues("thumbnail_modelpanels_kv"); + pItemKV->CopySubkeys( m_pThumbnailModelPanelKVs ); + } +} + + // C_CTF_GameStats.Event_Item( IE_ARMORY_EXITED ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CArmoryPanel::OnShowPanel( void ) +{ + InvalidateLayout( true, true ); + + m_pMouseOverItemPanel->SetVisible( false ); + + UpdateSelectedItem(); + + // If this is the first time we've opened the armory, start the armory explanations + if ( !tf_explanations_charinfo_armory_panel.GetBool() && ShouldShowExplanations() ) + { + m_flStartExplanationsAt = engine->Time() + 0.5; + } + + SetVisible( true ); + + if ( !m_bEventLogging ) + { + C_CTF_GameStats.Event_Catalog( IE_ARMORY_ENTERED ); + m_bEventLogging = true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Select the given item and jump to the appropriate page +//----------------------------------------------------------------------------- +void CArmoryPanel::JumpToItem( int iItemDef, armory_filters_t nFilter ) +{ + // Setup filter and select iItemDef + SetFilterTo( iItemDef, nFilter ); + + // If we have an item def, find out what page it's on and move to it + if ( iItemDef > 0 ) + { + const CEconItemDefinition *pDef = ItemSystem()->GetStaticDataForItemByDefIndex( iItemDef ); + if ( pDef ) + { + // Attempt to get item def from armory remap parameter + int iArmoryRemap = pDef->GetArmoryRemap(); + if ( iArmoryRemap > 0 ) + { + iItemDef = iArmoryRemap; + + SetSelectedItem( iItemDef ); + } + + // If the item specified is a stock item, find the upgradeable version of it instead + else if ( pDef->GetQuality() == AE_NORMAL ) + { + // Prepend the upgradeable string + char szTmpName[256]; + Q_snprintf( szTmpName, sizeof(szTmpName), "Upgradeable %s", pDef->GetDefinitionName() ); + + pDef = ItemSystem()->GetStaticDataForItemByName( szTmpName ); + if ( pDef ) + { + iItemDef = pDef->GetDefinitionIndex(); + } + } + } + + // Find and select the page iItemDef is on + FOR_EACH_VEC( m_FilteredItemList, i ) + { + if ( m_FilteredItemList[i] == (item_definition_index_t)iItemDef ) + { + int iThumbnailsPerPage = (m_iThumbnailRows * m_iThumbnailColumns); + m_iFilterPage = floor( (float)i / (float)iThumbnailsPerPage ); + break; + } + } + } + else + { + // Default behavior - select first item on first page + m_iFilterPage = 0; + SetSelectedItem( m_FilteredItemList[0] ); + } + + UpdateItemList(); + UpdateSelectedItem(); + + m_pMouseOverItemPanel->SetVisible( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: Show the armory with one of the default filters set +//----------------------------------------------------------------------------- +void CArmoryPanel::ShowPanel( int iItemDef, armory_filters_t nFilter ) +{ + JumpToItem( iItemDef, nFilter ); + OnShowPanel(); + m_pFilterComboBox->ActivateItemByRow( 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Show the armory with a custom list of item definitions, and a custom filter string +//----------------------------------------------------------------------------- +void CArmoryPanel::ShowPanel( const char *pszFilterString, CUtlVector<item_definition_index_t> *vecItems ) +{ + m_CustomFilteredList = *vecItems; + SetupComboBox( pszFilterString ); + ShowPanel( 0, ARMFILT_CUSTOM ); + + // Move to the custom entry + m_pFilterComboBox->ActivateItemByRow( ARMFILT_NUM_IN_DROPDOWN ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CArmoryPanel::OnClosing() +{ + if ( m_bEventLogging ) + { + C_CTF_GameStats.Event_Catalog( IE_ARMORY_EXITED ); + m_bEventLogging = false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CArmoryPanel::OnCommand( const char *command ) +{ + if ( !Q_strnicmp( command, "prevpage", 8 ) ) + { + if ( m_iFilterPage > 0 ) + { + m_iFilterPage--; + UpdateItemList(); + UpdateSelectedItem(); + } + return; + } + else if ( !Q_strnicmp( command, "nextpage", 8 ) ) + { + int nMaxPages = MAX( 1, ceil(m_FilteredItemList.Count() / (float)(m_iThumbnailRows * m_iThumbnailColumns)) ); + if ( m_iFilterPage < (nMaxPages-1) ) + { + m_iFilterPage++; + UpdateItemList(); + UpdateSelectedItem(); + } + return; + } + else if ( !Q_strnicmp( command, "back", 4 ) ) + { + PostMessage( GetParent(), new KeyValues("ArmoryClosed") ); + return; + } + else if ( !Q_stricmp( command, "reloadscheme" ) ) + { + InvalidateLayout( false, true ); + SetTall( YRES(400) ); + SetVisible( true ); + } + else if ( !Q_stricmp( command, "openstore" ) ) + { + // Only available in the loadout->catalog path. So we close down the character info, and move to the store. + // Bit of a hack. + EconUI()->CloseEconUI(); + + int iItemDef = m_SelectedItem.IsValid() ? m_SelectedItem.GetItemDefIndex() : 0; + EconUI()->OpenStorePanel( iItemDef, false ); + return; + } + else if ( !Q_stricmp( command, "wiki" ) ) + { + if ( steamapicontext && steamapicontext->SteamFriends() ) + { + if ( IsVisible() && m_SelectedItem.IsValid() ) + { + // Determine which language we should use + char uilanguage[ 64 ]; + uilanguage[0] = 0; + engine->GetUILanguage( uilanguage, sizeof( uilanguage ) ); + ELanguage iLang = PchLanguageToELanguage( uilanguage ); + + char szURL[512]; + Q_snprintf( szURL, sizeof(szURL), "http://wiki.teamfortress.com/scripts/itemredirect.php?id=%d&lang=%s", m_SelectedItem.GetItemDefIndex(), GetLanguageICUName( iLang ) ); + steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( szURL ); + + C_CTF_GameStats.Event_Catalog( IE_ARMORY_BROWSE_WIKI, NULL, &m_SelectedItem ); + } + } + } + else if ( !Q_stricmp( command, "viewset" ) ) + { + if ( m_SelectedItem.IsValid() ) + { + const CEconItemSetDefinition *pItemSet = m_SelectedItem.GetStaticData()->GetItemSetDefinition(); + if ( pItemSet ) + { + m_CustomFilteredList.Purge(); + FOR_EACH_VEC( pItemSet->m_iItemDefs, i ) + { + m_CustomFilteredList.AddToTail( pItemSet->m_iItemDefs[i] ); + } + + SetupComboBox( pItemSet->m_pszLocalizedName ); + SetFilterTo( m_SelectedItem.GetItemDefIndex(), ARMFILT_CUSTOM ); + } + } + } + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CArmoryPanel::MoveItem( int iDelta ) +{ + int iIdx = m_FilteredItemList.Find( m_SelectedItem.GetItemDefIndex() ); + m_SelectedItem.Invalidate(); + + if ( iIdx != m_FilteredItemList.InvalidIndex() ) + { + iIdx += iDelta; + if ( iIdx >= m_FilteredItemList.Count() ) + { + iIdx = 0; + } + else if ( iIdx < 0 ) + { + iIdx = m_FilteredItemList.Count() - 1; + } + + if ( iIdx < m_FilteredItemList.Count() ) + { + const CEconItemDefinition *pDef = ItemSystem()->GetStaticDataForItemByDefIndex( m_FilteredItemList[iIdx] ); + if ( pDef ) + { + SetSelectedItem( pDef->GetDefinitionIndex() ); + } + } + } + + UpdateSelectedItem(); + } + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CArmoryPanel::PerformLayout( void ) +{ + if ( m_bReapplyItemKVs ) + { + m_bReapplyItemKVs = false; + + if ( m_pThumbnailModelPanelKVs ) + { + FOR_EACH_VEC( m_pThumbnailModelPanels, i ) + { + m_pThumbnailModelPanels[i]->ApplySettings( m_pThumbnailModelPanelKVs ); + SetBorderForItem( m_pThumbnailModelPanels[i], false ); + m_pThumbnailModelPanels[i]->InvalidateLayout(); + } + } + } + + BaseClass::PerformLayout(); + + if ( m_pThumbnailModelPanels.Count() > 0 && m_iThumbnailColumns ) + { + int iThumbnailModelWide = m_pThumbnailModelPanels[0]->GetWide(); + int iThumbnailModelTall = m_pThumbnailModelPanels[0]->GetTall(); + FOR_EACH_VEC( m_pThumbnailModelPanels, i ) + { + if ( m_pThumbnailModelPanels[i]->HasItem() ) + { + m_pThumbnailModelPanels[i]->SetVisible( true ); + } + + int iXPos = ( i % m_iThumbnailColumns ); + int iYPos = ( i / m_iThumbnailColumns ); + + int iX = m_iThumbnailX + (m_iThumbnailDeltaX * iXPos) + (iThumbnailModelWide * iXPos); + int iY = m_iThumbnailY + (m_iThumbnailDeltaY * iYPos) + (iThumbnailModelTall * iYPos); + m_pThumbnailModelPanels[i]->SetPos( iX, iY ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CArmoryPanel::SetFilterTo( int iItemDef, armory_filters_t nFilter ) +{ + m_FilteredItemList.Purge(); + m_OldFilter = m_CurrentFilter; + m_CurrentFilter = nFilter; + m_iFilterPage = 0; + + if ( nFilter == ARMFILT_CUSTOM ) + { + m_FilteredItemList = m_CustomFilteredList; + } + else + { + // First, build a list of all the items that match the filter + const CEconItemSchema::SortedItemDefinitionMap_t& mapItemDefs = ItemSystem()->GetItemSchema()->GetSortedItemDefinitionMap(); + FOR_EACH_MAP( mapItemDefs, i ) + { + const CTFItemDefinition *pDef = dynamic_cast<const CTFItemDefinition *>( mapItemDefs[i] ); + + // Never show: + // - Hidden items + // - Items that don't have fixed qualities + // - Normal quality items + // - Items that haven't asked to be shown + if ( pDef->IsHidden() || pDef->GetQuality() == k_unItemQuality_Any || pDef->GetQuality() == AE_NORMAL || !pDef->ShouldShowInArmory() ) + continue; + +#ifdef DEBUG + // In Debug, make sure that every item shows up in a filter other than the All Items list + bool bFoundMatchingFilter = false; + for ( int iFilter = ARMFILT_WEAPONS; iFilter < ARMFILT_NUM_IN_DROPDOWN; iFilter++ ) + { + if ( DefPassesFilter(pDef,(armory_filters_t)iFilter) ) + { + bFoundMatchingFilter = true; + break; + } + } + Assert( bFoundMatchingFilter ); +#endif + + if ( DefPassesFilter( pDef, m_CurrentFilter ) ) + { + m_FilteredItemList.AddToTail( pDef->GetDefinitionIndex() ); + + if ( iItemDef == pDef->GetDefinitionIndex() ) + { + SetSelectedItem( pDef->GetDefinitionIndex() ); + } + } + } + } + + // Make sure our current item is in the list + if ( m_SelectedItem.IsValid() ) + { + if ( m_FilteredItemList.Find( m_SelectedItem.GetItemDefIndex() ) == m_FilteredItemList.InvalidIndex() ) + { + m_SelectedItem.Invalidate(); + } + } + + UpdateItemList(); + + if ( m_CurrentFilter != m_OldFilter ) + { + C_CTF_GameStats.Event_Catalog( IE_ARMORY_CHANGE_FILTER, g_szArmoryFilterStrings[m_CurrentFilter] ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CArmoryPanel::DefPassesFilter( const CTFItemDefinition *pDef, armory_filters_t iFilter ) +{ + bool bInList = false; + + switch (iFilter) + { + case ARMFILT_ALL_ITEMS: + { + bInList = true; + break; + } + + case ARMFILT_WEAPONS: + { + int iSlot = pDef->GetDefaultLoadoutSlot(); + bInList = ( iSlot == LOADOUT_POSITION_PRIMARY || iSlot == LOADOUT_POSITION_SECONDARY || iSlot == LOADOUT_POSITION_MELEE ); + break; + } + + case ARMFILT_HEADGEAR: + { + bInList = (pDef->GetDefaultLoadoutSlot() == LOADOUT_POSITION_HEAD); + break; + } + + case ARMFILT_MISCITEMS: + { + bInList = (pDef->GetDefaultLoadoutSlot() == LOADOUT_POSITION_MISC); + break; + } + + case ARMFILT_ACTIONITEMS: + { + bInList = (pDef->GetDefaultLoadoutSlot() == LOADOUT_POSITION_ACTION); + break; + } + + case ARMFILT_CRAFTITEMS: + { + bInList = pDef->GetItemClass() && ( !V_strcmp( pDef->GetItemClass(), "craft_item" ) || !V_strcmp( pDef->GetItemClass(), "class_token" ) || !V_strcmp( pDef->GetItemClass(), "slot_token" ) ); + break; + } + + case ARMFILT_TOOLS: + { + // For now, put the supply crates into the tool list, since it's the only item that shows up in no other lists + bInList = pDef->GetItemClass() && ( !V_strcmp( pDef->GetItemClass(), "tool" ) || !V_strcmp( pDef->GetItemClass(), "supply_crate" ) ); + break; + } + + case ARMFILT_CLASS_ALL: + { + bInList = pDef->CanBeUsedByAllClasses(); + break; + } + + case ARMFILT_CLASS_SCOUT: + case ARMFILT_CLASS_SNIPER: + case ARMFILT_CLASS_SOLDIER: + case ARMFILT_CLASS_DEMOMAN: + case ARMFILT_CLASS_MEDIC: + case ARMFILT_CLASS_HEAVY: + case ARMFILT_CLASS_PYRO: + case ARMFILT_CLASS_SPY: + case ARMFILT_CLASS_ENGINEER: + { + // Don't show class/slot usage for class/slot tokens + if ( pDef->GetItemClass() && !V_strcmp( pDef->GetItemClass(), "class_token" ) ) + break; + + bInList = ( !pDef->CanBeUsedByAllClasses() && pDef->CanBeUsedByClass( iFilter - ARMFILT_CLASS_SCOUT + 1 ) ); + break; + } + + case ARMFILT_DONATIONITEMS: + { + // Don't show class/slot usage for class/slot tokens + bInList = pDef->GetItemClass() && !V_strcmp( pDef->GetItemClass(), "map_token" ); + break; + } + } + + return bInList; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CArmoryPanel::UpdateItemList( void ) +{ + int iMaxThumbnails = (m_iThumbnailRows * m_iThumbnailColumns); + int iNumThumbnails = MIN( m_FilteredItemList.Count(), iMaxThumbnails ); + + if ( m_pThumbnailModelPanels.Count() < iNumThumbnails ) + { + for ( int i = m_pThumbnailModelPanels.Count(); i < iNumThumbnails; i++ ) + { + CItemModelPanel *pPanel = vgui::SETUP_PANEL( new CItemModelPanel( this, VarArgs("thumbnailmodelpanel%d", i) ) ); + pPanel->SetActAsButton( true, true ); + pPanel->ApplySettings( m_pThumbnailModelPanelKVs ); + SetBorderForItem( pPanel, false ); + m_pThumbnailModelPanels.AddToTail( pPanel ); + + pPanel->SetTooltip( m_pMouseOverTooltip, "" ); + } + } + else if ( m_pThumbnailModelPanels.Count() > iMaxThumbnails ) + { + FOR_EACH_VEC_BACK( m_pThumbnailModelPanels, i ) + { + if ( i < iMaxThumbnails ) + break; + + m_pThumbnailModelPanels[i]->MarkForDeletion(); + m_pThumbnailModelPanels.Remove( i ); + } + } + + int iStartPos = (m_iFilterPage * iMaxThumbnails); + + CEconItemView *pItemData = new CEconItemView(); + FOR_EACH_VEC( m_pThumbnailModelPanels, i ) + { + int iItemPos = iStartPos + i; + if ( iItemPos >= m_FilteredItemList.Count() ) + { + m_pThumbnailModelPanels[i]->SetItem( NULL ); + m_pThumbnailModelPanels[i]->SetVisible( false ); + continue; + } + + pItemData->Init( m_FilteredItemList[iItemPos], AE_USE_SCRIPT_VALUE, AE_USE_SCRIPT_VALUE, true ); + m_pThumbnailModelPanels[i]->SetItem( pItemData ); + m_pThumbnailModelPanels[i]->SetVisible( true ); + } + delete pItemData; + + char szTmp[16]; + int nMaxPages = MAX( 1, ceil(m_FilteredItemList.Count() / (float)(m_iThumbnailRows * m_iThumbnailColumns)) ); + Q_snprintf(szTmp, 16, "%d/%d", m_iFilterPage+1, nMaxPages ); + SetDialogVariable( "thumbnailpage", szTmp ); + + bool bNextEnabled = m_iFilterPage < (nMaxPages-1); + m_pNextPageButton->SetEnabled( bNextEnabled ); + m_pPrevPageButton->SetEnabled( m_iFilterPage > 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CArmoryPanel::UpdateSelectedItem( void ) +{ + if ( !m_SelectedItem.IsValid() ) + { + if ( m_FilteredItemList.Count() ) + { + const CEconItemDefinition *pDef = ItemSystem()->GetStaticDataForItemByDefIndex( m_FilteredItemList[0] ); + if ( pDef ) + { + SetSelectedItem( pDef->GetDefinitionIndex() ); + } + } + } + + if ( m_pSelectedItemModelPanel ) + { + m_pSelectedItemModelPanel->SetItem( &m_SelectedItem ); + m_pSelectedItemModelPanel->InvalidateLayout( true ); + + int iYDelta = YRES(10); + + // Resize & position the atribute background image + int iItemX, iItemY, iItemW, iItemH; + m_pSelectedItemModelPanel->GetBounds( iItemX, iItemY, iItemW, iItemH ); + + // Never shrink the attribute background below a certain size + int iNewPaperH = MAX( iItemH + (iYDelta*2), YRES(100) ); + int iNewPaperY = iItemY - iYDelta; + + int iNewY = iNewPaperY + iNewPaperH; + + // Reposition the data panel now that we know how big the item is + int iX,iY; + m_pDataTextRichText->GetPos( iX, iY ); + int iDataPanelX, iDataPanelY; + m_pDataPanel->GetPos( iDataPanelX, iDataPanelY ); + int iRichTextYPosInDataPanel = iNewY - iDataPanelY; + m_pDataTextRichText->SetBounds( iX, iRichTextYPosInDataPanel, m_pDataTextRichText->GetWide(), m_pDataPanel->GetTall() - iRichTextYPosInDataPanel ); + } + if ( m_pSelectedItemImageModelPanel ) + { + m_pSelectedItemImageModelPanel->SetItem( &m_SelectedItem ); + } + + FOR_EACH_VEC( m_pThumbnailModelPanels, i ) + { + if ( !m_pThumbnailModelPanels[i]->IsVisible() || !m_pThumbnailModelPanels[i]->HasItem() ) + continue; + + bool bSelected = (m_SelectedItem.GetItemDefIndex() == m_pThumbnailModelPanels[i]->GetItem()->GetItemDefIndex() ); + if ( bSelected != m_pThumbnailModelPanels[i]->IsSelected() ) + { + m_pThumbnailModelPanels[i]->SetSelected( bSelected ); + SetBorderForItem( m_pThumbnailModelPanels[i], false ); + } + } + + UpdateDataBlock(); + + if ( m_pViewSetButton ) + { + m_pViewSetButton->SetVisible( false ); + if ( m_SelectedItem.IsValid() ) + { + if ( m_SelectedItem.GetStaticData()->GetItemSetDefinition() ) + { + m_pViewSetButton->SetVisible( true ); + } + } + } + + if ( m_pStoreButton ) + { + bool bShowStoreButton = m_bAllowGotoStore && EconUI()->GetStorePanel() && EconUI()->GetStorePanel()->GetPriceSheet() && EconUI()->GetStorePanel()->GetPriceSheet()->GetEntry( m_SelectedItem.GetItemDefIndex() ); + m_pStoreButton->SetVisible( bShowStoreButton ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CArmoryPanel::UpdateDataBlock( void ) +{ + if ( !CalculateDataText() ) + { + m_pDataPanel->SetVisible( false ); + return; + } + + m_pDataPanel->SetVisible( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CArmoryPanel::CalculateDataText( void ) +{ + if ( !m_SelectedItem.IsValid() ) + return false; + CTFItemDefinition *pDef = ItemSystem()->GetStaticDataForItemByDefIndex( m_SelectedItem.GetItemDefIndex() ); + if ( !pDef ) + return false; + + m_pDataTextRichText->UpdateDetailsForItem( pDef ); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CArmoryPanel::OnItemPanelEntered( vgui::Panel *panel ) +{ + CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel ); + + if ( pItemPanel && IsVisible() ) + { + CEconItemView *pItem = pItemPanel->GetItem(); + SetBorderForItem( pItemPanel, pItem != NULL ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CArmoryPanel::OnItemPanelExited( vgui::Panel *panel ) +{ + CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel ); + + if ( pItemPanel && IsVisible() ) + { + SetBorderForItem( pItemPanel, false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CArmoryPanel::OnItemPanelMouseReleased( vgui::Panel *panel ) +{ + CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel ); + + if ( pItemPanel && IsVisible() && pItemPanel->HasItem() && !pItemPanel->IsSelected() ) + { + SetSelectedItem( pItemPanel->GetItem() ); + + // Hide the mouseover panel now, so it doesn't obscure the rich text info + m_pMouseOverItemPanel->SetVisible( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CArmoryPanel::SetSelectedItem( CEconItemView* newItem ) +{ + m_PreviousItem = m_SelectedItem; + m_SelectedItem = *newItem; + m_SelectedItem.SetClientItemFlags( kEconItemFlagClient_Preview ); + UpdateSelectedItem(); + + if ( m_bEventLogging && m_SelectedItem.IsValid() && m_SelectedItem != m_PreviousItem ) + { + C_CTF_GameStats.Event_Catalog( IE_ARMORY_SELECT_ITEM, g_szArmoryFilterStrings[m_CurrentFilter], &m_SelectedItem ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CArmoryPanel::SetSelectedItem( int newIndex ) +{ + m_PreviousItem = m_SelectedItem; + m_SelectedItem.Init( newIndex, AE_USE_SCRIPT_VALUE, AE_USE_SCRIPT_VALUE, true ); + m_SelectedItem.SetClientItemFlags( kEconItemFlagClient_Preview ); + + if ( m_bEventLogging && m_SelectedItem.IsValid() && m_SelectedItem != m_PreviousItem ) + { + C_CTF_GameStats.Event_Catalog( IE_ARMORY_SELECT_ITEM, g_szArmoryFilterStrings[m_CurrentFilter], &m_SelectedItem ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CArmoryPanel::SetBorderForItem( CItemModelPanel *pItemPanel, bool bMouseOver ) +{ + if ( !pItemPanel ) + return; + + // Store panels use backgrounds instead of borders + pItemPanel->SetBorder( NULL ); + pItemPanel->SetPaintBackgroundEnabled( true ); + + if ( pItemPanel->IsSelected() ) + { + pItemPanel->SetBgColor( m_colThumbnailBGSelected ); + } + else if ( bMouseOver ) + { + pItemPanel->SetBgColor( m_colThumbnailBGMouseover ); + } + else + { + pItemPanel->SetBgColor( m_colThumbnailBG ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called when text changes in combo box +//----------------------------------------------------------------------------- +void CArmoryPanel::OnTextChanged( KeyValues *data ) +{ + if ( !m_pFilterComboBox ) + return; + + Panel *pPanel = reinterpret_cast<vgui::Panel *>( data->GetPtr("panel") ); + vgui::ComboBox *pComboBox = dynamic_cast<vgui::ComboBox *>( pPanel ); + + if ( pComboBox == m_pFilterComboBox ) + { + // the class selection combo box changed, update class details + KeyValues *pUserData = m_pFilterComboBox->GetActiveItemUserData(); + if ( !pUserData ) + return; + + armory_filters_t nFilter = (armory_filters_t)pUserData->GetInt( "setfilter", -1 ); + if ( nFilter != armory_filters_t(-1) ) + { + SetFilterTo( 0, nFilter ); + UpdateSelectedItem(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CArmoryPanel::OnItemLinkClicked( KeyValues *pParams ) +{ + const char *pURL = pParams->GetString( "url" ); + int iItemDef = atoi( pURL + 7 ); + JumpToItem( iItemDef, ARMFILT_ALL_ITEMS ); + m_pFilterComboBox->ActivateItemByRow( 0 ); +} + diff --git a/game/client/tf/vgui/charinfo_armory_subpanel.h b/game/client/tf/vgui/charinfo_armory_subpanel.h new file mode 100644 index 0000000..d27af0b --- /dev/null +++ b/game/client/tf/vgui/charinfo_armory_subpanel.h @@ -0,0 +1,151 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef CHARINFO_ARMORY_SUBPANEL_H +#define CHARINFO_ARMORY_SUBPANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include <game/client/iviewport.h> +#include "vgui_controls/PropertyPage.h" +#include <vgui_controls/Button.h> +#include <vgui_controls/ComboBox.h> +#include "tf_controls.h" +#include "tf_shareddefs.h" +#include "backpack_panel.h" +#include "class_loadout_panel.h" + +enum armory_filters_t +{ + // These are listed in the dropdown, for players to select + ARMFILT_ALL_ITEMS, + ARMFILT_WEAPONS, + ARMFILT_HEADGEAR, + ARMFILT_MISCITEMS, + ARMFILT_ACTIONITEMS, + ARMFILT_CRAFTITEMS, + ARMFILT_TOOLS, + ARMFILT_CLASS_ALL, + ARMFILT_CLASS_SCOUT, + ARMFILT_CLASS_SNIPER, + ARMFILT_CLASS_SOLDIER, + ARMFILT_CLASS_DEMOMAN, + ARMFILT_CLASS_MEDIC, + ARMFILT_CLASS_HEAVY, + ARMFILT_CLASS_PYRO, + ARMFILT_CLASS_SPY, + ARMFILT_CLASS_ENGINEER, + ARMFILT_DONATIONITEMS, + + ARMFILT_NUM_IN_DROPDOWN, + + ARMFILT_CUSTOM, + ARMFILT_TOTAL, +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CArmoryPanel : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CArmoryPanel, vgui::EditablePanel ); +public: + CArmoryPanel(Panel *parent, const char *panelName); + virtual ~CArmoryPanel(); + + // Show the armory with one of the default filters set + void ShowPanel( int iItemDef, armory_filters_t nFilter = ARMFILT_ALL_ITEMS ); + + // Show the armory with a custom list of item definitions, and a custom filter string + void ShowPanel( const char *pszFilterString, CUtlVector<item_definition_index_t> *vecItems ); + + // Select the given item and jump to the appropriate page + void JumpToItem( int iItemDef, armory_filters_t nFilter ); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void ApplySettings( KeyValues *inResourceData ); + virtual void OnCommand( const char *command ); + virtual void PerformLayout( void ); + + bool ShouldShowExplanations( void ) { return true; } + void UpdateItemList( void ); + void UpdateSelectedItem( void ); + void AllowGotoStore( void ) { m_bAllowGotoStore = true; } + + MESSAGE_FUNC_PTR( OnItemPanelEntered, "ItemPanelEntered", panel ); + MESSAGE_FUNC_PTR( OnItemPanelExited, "ItemPanelExited", panel ); + MESSAGE_FUNC_PTR( OnItemPanelMouseReleased, "ItemPanelMouseReleased", panel ); + MESSAGE_FUNC_PARAMS( OnTextChanged, "TextChanged", data ); + MESSAGE_FUNC_PARAMS( OnItemLinkClicked, "URLClicked", pParams ); + + MESSAGE_FUNC( OnClosing, "Closing" ); + +private: + void OnShowPanel( void ); + void MoveItem( int iDelta ); + void UpdateDataBlock( void ); + bool CalculateDataText( void ); + + void SetFilterTo( int iItemDef, armory_filters_t nFilter ); + void SetBorderForItem( CItemModelPanel *pItemPanel, bool bMouseOver ); + bool DefPassesFilter( const CTFItemDefinition *pDef, armory_filters_t iFilter ); + + void SetupComboBox( const char *pszCustomAddition ); + + void SetSelectedItem( CEconItemView* newItem ); + void SetSelectedItem( int newIndex ); + +private: + float m_flStartExplanationsAt; + + CEconItemView m_SelectedItem; + CEconItemView m_PreviousItem; + CItemModelPanel *m_pSelectedItemModelPanel; + CItemModelPanel *m_pSelectedItemImageModelPanel; + + // Filters + vgui::ComboBox *m_pFilterComboBox; + armory_filters_t m_CurrentFilter; + armory_filters_t m_OldFilter; + int m_iFilterPage; + CExButton *m_pNextPageButton; + CExButton *m_pPrevPageButton; + CUtlVector<item_definition_index_t> m_FilteredItemList; + CUtlVector<item_definition_index_t> m_CustomFilteredList; + + // Thumbnails + KeyValues *m_pThumbnailModelPanelKVs; + bool m_bReapplyItemKVs; + CUtlVector<CItemModelPanel*> m_pThumbnailModelPanels; + CItemModelPanel *m_pMouseOverItemPanel; + CItemModelPanelToolTip *m_pMouseOverTooltip; + + bool m_bEventLogging; // Handles sending entered/exited stats messages. + + // Data display + vgui::EditablePanel *m_pDataPanel; + CEconItemDetailsRichText *m_pDataTextRichText; + CExButton *m_pViewSetButton; + + bool m_bAllowGotoStore; + CExButton *m_pStoreButton; + + CPanelAnimationVar( int, m_iThumbnailRows, "thumbnails_rows", "1" ); + CPanelAnimationVar( int, m_iThumbnailColumns, "thumbnails_columns", "1" ); + CPanelAnimationVarAliasType( int, m_iThumbnailX, "thumbnails_x", "0", "proportional_xpos" ); + CPanelAnimationVarAliasType( int, m_iThumbnailY, "thumbnails_y", "0", "proportional_ypos" ); + CPanelAnimationVarAliasType( int, m_iThumbnailDeltaX, "thumbnails_delta_x", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iThumbnailDeltaY, "thumbnails_delta_y", "0", "proportional_int" ); + Color m_colThumbnailBG; + Color m_colThumbnailBGMouseover; + Color m_colThumbnailBGSelected; + + Color m_colSetName; +}; + +#endif // CHARINFO_ARMORY_SUBPANEL_H diff --git a/game/client/tf/vgui/charinfo_loadout_subpanel.cpp b/game/client/tf/vgui/charinfo_loadout_subpanel.cpp new file mode 100644 index 0000000..42a7989 --- /dev/null +++ b/game/client/tf/vgui/charinfo_loadout_subpanel.cpp @@ -0,0 +1,1244 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "charinfo_loadout_subpanel.h" +#include "vgui/ISurface.h" +#include "vgui/IInput.h" +#include "vgui/ILocalize.h" +#include "c_tf_freeaccount.h" +#include "c_tf_player.h" +#include "confirm_dialog.h" +#include "gamestringpool.h" +#include "c_tf_objective_resource.h" +#include "tf_gamerules.h" +#include "tf_item_inventory.h" +#include "trading_start_dialog.h" +#include "gc_clientsystem.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + + +DECLARE_BUILD_FACTORY( CImageButton ); + + +ConVar tf_explanations_charinfopanel( "tf_explanations_charinfopanel", "0", FCVAR_ARCHIVE, "Whether the user has seen explanations for this panel." ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CImageButton::CImageButton( vgui::Panel *parent, const char *panelName ) : BaseClass( parent, panelName, "" ) +{ + m_pszActiveImageName = NULL; + m_pszInactiveImageName = NULL; + + m_pActiveImage = NULL; + m_pInactiveImage = NULL; + + m_ActiveDrawColor = Color(255,255,255,255); + m_InactiveDrawColor = Color(255,255,255,255); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CImageButton::ApplySettings( KeyValues *inResourceData ) +{ + m_bScaleImage = inResourceData->GetInt( "scaleImage", 0 ); + + // Active Image + delete [] m_pszActiveImageName; + m_pszActiveImageName = NULL; + + const char *activeImageName = inResourceData->GetString( "activeimage", "" ); + if ( *activeImageName ) + { + SetActiveImage( activeImageName ); + } + + // Inactive Image + delete [] m_pszInactiveImageName; + m_pszInactiveImageName = NULL; + + const char *inactiveImageName = inResourceData->GetString( "inactiveimage", "" ); + if ( *inactiveImageName ) + { + SetInactiveImage( inactiveImageName ); + } + + const char *pszDrawColor = inResourceData->GetString("activedrawcolor", ""); + if (*pszDrawColor) + { + int r = 0, g = 0, b = 0, a = 255; + if (sscanf(pszDrawColor, "%d %d %d %d", &r, &g, &b, &a) >= 3) + { + m_ActiveDrawColor = Color(r, g, b, a); + } + else + { + vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); + m_ActiveDrawColor = pScheme->GetColor(pszDrawColor, Color(0, 0, 0, 0)); + } + } + + pszDrawColor = inResourceData->GetString("inactivedrawcolor", ""); + if (*pszDrawColor) + { + int r = 0, g = 0, b = 0, a = 255; + if (sscanf(pszDrawColor, "%d %d %d %d", &r, &g, &b, &a) >= 3) + { + m_InactiveDrawColor = Color(r, g, b, a); + } + else + { + vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); + m_InactiveDrawColor = pScheme->GetColor(pszDrawColor, Color(0, 0, 0, 0)); + } + } + + BaseClass::ApplySettings( inResourceData ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CImageButton::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + if ( m_pszActiveImageName && strlen( m_pszActiveImageName ) > 0 ) + { + SetActiveImage(vgui::scheme()->GetImage( m_pszActiveImageName, m_bScaleImage ) ); + } + + if ( m_pszInactiveImageName && strlen( m_pszInactiveImageName ) > 0 ) + { + SetInactiveImage(vgui::scheme()->GetImage( m_pszInactiveImageName, m_bScaleImage ) ); + } + + vgui::IBorder *pBorder = pScheme->GetBorder( "NoBorder" ); + SetDefaultBorder( pBorder); + SetDepressedBorder( pBorder ); + SetKeyFocusBorder( pBorder ); + + Color defaultFgColor = GetSchemeColor( "Button.TextColor", Color(255, 255, 255, 255), pScheme ); + Color armedFgColor = GetSchemeColor( "Button.ArmedTextColor", Color(255, 255, 255, 255), pScheme ); + Color depressedFgColor = GetSchemeColor( "Button.DepressedTextColor", Color(255, 255, 255, 255), pScheme ); + + Color blank(0,0,0,0); + SetDefaultColor( defaultFgColor, blank ); + SetArmedColor( armedFgColor, blank ); + SetDepressedColor( depressedFgColor, blank ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CImageButton::SetActiveImage( const char *imagename ) +{ + int len = Q_strlen( imagename ) + 1; + m_pszActiveImageName = new char[ len ]; + Q_strncpy( m_pszActiveImageName, imagename, len ); + + SetActiveImage(vgui::scheme()->GetImage( m_pszActiveImageName, m_bScaleImage ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CImageButton::SetInactiveImage( const char *imagename ) +{ + int len = Q_strlen( imagename ) + 1; + m_pszInactiveImageName = new char[ len ]; + Q_strncpy( m_pszInactiveImageName, imagename, len ); + + SetInactiveImage(vgui::scheme()->GetImage( m_pszInactiveImageName, m_bScaleImage ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CImageButton::SetActiveImage( vgui::IImage *image ) +{ + m_pActiveImage = image; + + if ( m_pActiveImage ) + { + int wide, tall; + if ( m_bScaleImage ) + { + // scaling, force the image size to be our size + GetSize( wide, tall ); + m_pActiveImage->SetSize( wide, tall ); + } + else + { + // not scaling, so set our size to the image size + m_pActiveImage->GetSize( wide, tall ); + SetSize( wide, tall ); + } + } + + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CImageButton::SetInactiveImage( vgui::IImage *image ) +{ + m_pInactiveImage = image; + + if ( m_pInactiveImage ) + { + int wide, tall; + if ( m_bScaleImage) + { + // scaling, force the image size to be our size + GetSize( wide, tall ); + m_pInactiveImage->SetSize( wide, tall ); + } + else + { + // not scaling, so set our size to the image size + m_pInactiveImage->GetSize( wide, tall ); + SetSize( wide, tall ); + } + } + + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CImageButton::OnSizeChanged( int newWide, int newTall ) +{ + if ( m_bScaleImage ) + { + // scaling, force the image size to be our size + if ( m_pActiveImage ) + m_pActiveImage->SetSize( newWide, newTall ); + + if ( m_pInactiveImage ) + m_pInactiveImage->SetSize( newWide, newTall ); + } + BaseClass::OnSizeChanged( newWide, newTall ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CImageButton::Paint() +{ + if ( IsArmed() || _buttonFlags.IsFlagSet( FORCE_DEPRESSED ) ) + { + // draw the active image + if ( m_pActiveImage ) + { + m_pActiveImage->SetColor( m_ActiveDrawColor ); + m_pActiveImage->SetPos( 0, 0 ); + m_pActiveImage->Paint(); + } + } + else + { + // draw the inactive image + if ( m_pInactiveImage ) + { + m_pActiveImage->SetColor( m_InactiveDrawColor ); + m_pInactiveImage->SetPos( 0, 0 ); + m_pInactiveImage->Paint(); + } + } + + BaseClass::Paint(); +} + +const char *g_pszSubButtonNames[CHSB_NUM_BUTTONS] = +{ + "ShowBackpackButton", // CHSB_BACKPACK, + "ShowCraftingButton", // CHSB_CRAFTING, + "ShowArmoryButton", // CHSB_ARMORY, + "ShowTradeButton", // CHSB_TRADING, +}; +const char *g_pszSubButtonLabelNames[CHSB_NUM_BUTTONS] = +{ + "ShowBackpackLabel", // CHSB_BACKPACK, + "ShowCraftingLabel", // CHSB_CRAFTING, + "ShowArmoryLabel", // CHSB_ARMORY, + "ShowTradeLabel", // CHSB_TRADING, +}; + +int g_nLoadoutClassOrder[] = +{ + TF_CLASS_SCOUT, + TF_CLASS_SOLDIER, + TF_CLASS_PYRO, + TF_CLASS_DEMOMAN, + TF_CLASS_HEAVYWEAPONS, + TF_CLASS_ENGINEER, + TF_CLASS_MEDIC, + TF_CLASS_SNIPER, + TF_CLASS_SPY +}; + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCharInfoLoadoutSubPanel::CCharInfoLoadoutSubPanel(Panel *parent) : vgui::PropertyPage(parent, "CharInfoLoadoutSubPanel") +{ + m_iCurrentClassIndex = TF_CLASS_UNDEFINED; + m_iCurrentTeamIndex = TF_TEAM_RED; + m_iShowingPanel = CHAP_LOADOUT; + m_iPrevShowingPanel = CHAP_LOADOUT; + + memset( m_pClassButtons, 0, sizeof( m_pClassButtons ) ); + + m_pClassButtons[ TF_CLASS_SCOUT ] = new CImageButton( this, "scout" ); + m_pClassButtons[ TF_CLASS_SOLDIER ] = new CImageButton( this, "soldier" ); + m_pClassButtons[ TF_CLASS_PYRO ] = new CImageButton( this, "pyro" ); + m_pClassButtons[ TF_CLASS_DEMOMAN ] = new CImageButton( this, "demoman" ); + m_pClassButtons[ TF_CLASS_HEAVYWEAPONS ] = new CImageButton( this, "heavyweapons" ); + m_pClassButtons[ TF_CLASS_ENGINEER ] = new CImageButton( this, "engineer" ); + m_pClassButtons[ TF_CLASS_MEDIC ] = new CImageButton( this, "medic" ); + m_pClassButtons[ TF_CLASS_SNIPER ] = new CImageButton( this, "sniper" ); + m_pClassButtons[ TF_CLASS_SPY ] = new CImageButton( this, "spy" ); + + for( int i = 0; i < Q_ARRAYSIZE( m_pClassButtons ); i++ ) + { + if( m_pClassButtons[ i ] ) + m_pClassButtons[ i ]->SetParentNeedsCursorMoveEvents( true ); + } + + for ( int i = 0; i < CHSB_NUM_BUTTONS; i++ ) + { + m_pSubButtons[i] = new CImageButton( this, g_pszSubButtonNames[i] ); + m_pButtonLabels[i] = new CExLabel( this, g_pszSubButtonLabelNames[i], "" ); + } + m_iOverSubButton = -1; + + m_pClassLoadoutPanel = new CClassLoadoutPanel( this ); + m_pBackpackPanel = new CBackpackPanel( this, "backpack_panel" ); + m_pCraftingPanel = new CCraftingPanel( this, "crafting_panel" ); + m_pArmoryPanel = new CArmoryPanel( this, "armory_panel" ); + m_pArmoryPanel->AllowGotoStore(); + m_pSelectLabel = NULL; + m_pLoadoutChangesLabel = NULL; + m_pNoSteamLabel = NULL; + m_pNoGCLabel = NULL; + m_pClassLabel = NULL; + m_pItemsLabel = NULL; + m_bSnapClassLayout = false; + m_bClassLayoutDirty = false; + m_bRequestingInventoryRefresh = false; + m_flStartExplanationsAt = 0; + + vgui::ivgui()->AddTickSignal( GetVPanel() ); + + REGISTER_COLOR_AS_OVERRIDABLE( m_ItemColorNone, "itemcountcolor_noitems" ); + REGISTER_COLOR_AS_OVERRIDABLE( m_ItemColor, "itemcountcolor" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCharInfoLoadoutSubPanel::~CCharInfoLoadoutSubPanel() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharInfoLoadoutSubPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "Resource/UI/CharInfoLoadoutSubPanel.res" ); + + m_pSelectLabel = dynamic_cast<vgui::Label*>( FindChildByName("SelectLabel") ); + m_pLoadoutChangesLabel = dynamic_cast<vgui::Label*>( FindChildByName("LoadoutChangesLabel") ); + m_pNoSteamLabel = dynamic_cast<vgui::Label*>( FindChildByName("NoSteamLabel") ); + m_pNoGCLabel = dynamic_cast<vgui::Label*>( FindChildByName("NoGCLabel") ); + m_pClassLabel = dynamic_cast<vgui::Label*>( FindChildByName("ClassLabel") ); + int ignored; + if ( m_pClassLabel ) + { + m_pClassLabel->GetPos( ignored, m_iClassLabelYPos ); + } + m_pItemsLabel = dynamic_cast<CExLabel*>( FindChildByName("ItemsLabel") ); + if ( m_pItemsLabel ) + { + m_pItemsLabel->GetPos( ignored, m_iItemLabelYPos ); + } + + // Start classes sized as if the mouse is in the middle of the screen + m_iMouseXPos = -1; + m_bSnapClassLayout = true; + RecalculateTargetClassLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharInfoLoadoutSubPanel::OnPageShow( void ) +{ + SetVisible( true ); + + BaseClass::OnPageShow(); + + if( m_iCurrentClassIndex != TF_CLASS_UNDEFINED ) + { + m_pClassButtons[ m_iCurrentClassIndex ]->GetPos( m_iMouseXPos, m_iMouseYPos ); + m_bClassLayoutDirty = true; + InvalidateLayout(); + } + + // If this is the first time we've opened the loadout, start the loadout explanations + if ( !tf_explanations_charinfopanel.GetBool() && ShouldShowExplanations() ) + { + m_flStartExplanationsAt = engine->Time() + 0.5; + } + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharInfoLoadoutSubPanel::OnSelectionStarted( void ) +{ + PostActionSignal( new KeyValues("SelectionUpdate", "open", 1 ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharInfoLoadoutSubPanel::OnSelectionEnded( void ) +{ + PostActionSignal( new KeyValues("SelectionUpdate", "open", 0 ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharInfoLoadoutSubPanel::OnCancelSelection( void ) +{ + PostMessage( m_pClassLoadoutPanel, new KeyValues("CancelSelection") ); + PostMessage( m_pBackpackPanel, new KeyValues("CancelSelection") ); + PostMessage( m_pCraftingPanel, new KeyValues("CancelSelection") ); + PostMessage( m_pArmoryPanel, new KeyValues("CancelSelection") ); + RequestFocus(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharInfoLoadoutSubPanel::OnCharInfoClosing( void ) +{ + switch ( m_iShowingPanel ) + { + case CHAP_CRAFTING: + PostMessage( m_pCraftingPanel, new KeyValues("Closing") ); + break; + case CHAP_BACKPACK: + break; + case CHAP_ARMORY: + PostMessage( m_pArmoryPanel, new KeyValues("Closing") ); + break; + case CHAP_LOADOUT: + PostMessage( m_pClassLoadoutPanel, new KeyValues("Closing") ); + break; + default: // Class loadout. + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharInfoLoadoutSubPanel::OnOpenCrafting( void ) +{ + OpenToCrafting(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharInfoLoadoutSubPanel::OnCraftingClosed( void ) +{ + PostMessage( m_pCraftingPanel, new KeyValues("Closing") ); + m_iShowingPanel = CHAP_LOADOUT; + m_iPrevShowingPanel = CHAP_CRAFTING; + m_flStartExplanationsAt = 0; + m_iCurrentClassIndex = TF_CLASS_UNDEFINED; + UpdateModelPanels(); + RequestFocus(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharInfoLoadoutSubPanel::OnArmoryClosed( void ) +{ + // Return to whatever we were on before opening the armory + PostMessage( m_pArmoryPanel, new KeyValues("Closing") ); + m_iShowingPanel = m_iPrevShowingPanel; + m_iPrevShowingPanel = CHAP_ARMORY; + m_flStartExplanationsAt = 0; + m_iCurrentClassIndex = TF_CLASS_UNDEFINED; + UpdateModelPanels(); + RequestFocus(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharInfoLoadoutSubPanel::OnCommand( const char *command ) +{ + if ( !Q_strnicmp( command, "loadout ", 8 ) ) + { + // Ignore selection while we don't have a steam connection + if ( !TFInventoryManager()->GetLocalTFInventory()->RetrievedInventoryFromSteam() ) + return; + + m_flStartExplanationsAt = 0; + + const char *pszClass = command+8; + if ( pszClass[0] != '\0' ) + { + int nClassIndex = GetClassIndexFromString( pszClass, NUM_CLASSES_IN_LOADOUT_PANEL ); + if ( nClassIndex != TF_CLASS_UNDEFINED && m_iCurrentClassIndex != nClassIndex ) + { + SetClassIndex( nClassIndex, true ); + return; + } + } + } + else if ( !Q_strnicmp( command, "backpack", 8 ) ) + { + OpenToBackpack(); + } + else if ( !Q_strnicmp( command, "crafting", 8 ) ) + { + OpenToCrafting(); + } + else if ( !Q_strnicmp( command, "armory", 6 ) ) + { + OpenToArmory(); + } + else if ( !Q_strnicmp( command, "trading", 7 ) ) + { + OpenTradingStartDialog( this ); + } + else if ( !Q_stricmp( command, "show_explanations" ) ) + { + if ( !m_flStartExplanationsAt ) + { + m_flStartExplanationsAt = engine->Time(); + } + RequestFocus(); + } + else + { + engine->ClientCmd( const_cast<char *>( command ) ); + } + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharInfoLoadoutSubPanel::RequestInventoryRefresh() +{ + m_bRequestingInventoryRefresh = false; + + // Don't respond to the mouse if we don't have items + if ( !TFInventoryManager()->GetLocalTFInventory()->RetrievedInventoryFromSteam() ) + { + ShowWaitingDialog( new CGenericWaitingDialog(this), "#NoSteamNoItems_Refresh", true, true, 30.0f ); + if ( !m_bRequestingInventoryRefresh ) + { + // make sure the local inventory is added as a listener + TFInventoryManager()->UpdateLocalInventory(); + m_bRequestingInventoryRefresh = true; + // ask GC for refresh + GCSDK::CProtoBufMsg< CMsgRequestInventoryRefresh > msg( k_EMsgGCRequestInventoryRefresh ); + GCClientSystem()->BSendMessage( msg ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharInfoLoadoutSubPanel::SetClassIndex( int iClassIndex, bool bOpenClassLoadout ) +{ + Assert(iClassIndex >= TF_CLASS_UNDEFINED && iClassIndex <= NUM_CLASSES_IN_LOADOUT_PANEL); + m_iCurrentClassIndex = iClassIndex; + m_iShowingPanel = CHAP_LOADOUT; + UpdateModelPanels( bOpenClassLoadout ); + + RequestInventoryRefresh(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharInfoLoadoutSubPanel::SetTeamIndex( int iTeam ) +{ + Assert( IsValidTFTeam( iTeam ) ); + m_iCurrentTeamIndex = iTeam; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharInfoLoadoutSubPanel::OpenSubPanel( charinfo_activepanels_t iPanel ) +{ + m_flStartExplanationsAt = 0; + m_iCurrentClassIndex = TF_CLASS_UNDEFINED; + m_iPrevShowingPanel = m_iShowingPanel; + m_iShowingPanel = iPanel; + + UpdateModelPanels(); + + RequestInventoryRefresh(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharInfoLoadoutSubPanel::UpdateModelPanels( bool bOpenClassLoadout ) +{ + int iLabelClassToSet = -1; + int iClassIndexToSet = 0; + + if ( m_iShowingPanel == CHAP_CRAFTING ) + { + m_pClassLoadoutPanel->SetVisible( false ); + m_pBackpackPanel->SetVisible( false ); + m_pArmoryPanel->SetVisible( false ); + m_pCraftingPanel->ShowPanel( m_iCurrentClassIndex, true, (m_iPrevShowingPanel == CHAP_ARMORY) ); + } + else if ( m_iShowingPanel == CHAP_BACKPACK ) + { + m_pClassLoadoutPanel->SetVisible( false ); + m_pCraftingPanel->SetVisible( false ); + m_pArmoryPanel->SetVisible( false ); + m_pBackpackPanel->ShowPanel( m_iCurrentClassIndex, true, (m_iPrevShowingPanel == CHAP_ARMORY) ); + } + else if ( m_iShowingPanel == CHAP_ARMORY ) + { + m_pClassLoadoutPanel->SetVisible( false ); + m_pCraftingPanel->SetVisible( false ); + m_pBackpackPanel->SetVisible( false ); + m_pArmoryPanel->ShowPanel( m_iArmoryItemDef ); + } + else + { + iClassIndexToSet = bOpenClassLoadout ? m_iCurrentClassIndex : TF_CLASS_UNDEFINED; + m_pArmoryPanel->SetVisible( false ); + m_pBackpackPanel->SetVisible( false ); + m_pCraftingPanel->SetVisible( false ); + m_pClassLoadoutPanel->SetTeam( m_iCurrentTeamIndex ); + m_pClassLoadoutPanel->SetClass( iClassIndexToSet ); + m_pClassLoadoutPanel->ShowPanel( iClassIndexToSet, false, (m_iPrevShowingPanel == CHAP_ARMORY) ); + + iLabelClassToSet = m_iCurrentClassIndex; + } + + m_iCurrentClassIndex = iClassIndexToSet; + + if( bOpenClassLoadout ) + { + PostActionSignal( new KeyValues("ClassSelected", "class", m_iCurrentClassIndex ) ); + } + else + { + m_iLabelSetToClass = iLabelClassToSet; + m_bClassLayoutDirty = true; + InvalidateLayout(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharInfoLoadoutSubPanel::PerformLayout( void ) +{ + BaseClass::PerformLayout(); + + // Show our changes label if we're alive, and hence won't get the changes immediately + bool bChangesLabel = false; + if ( engine->IsInGame() ) + { + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( pLocalPlayer && pLocalPlayer->IsAlive() && pLocalPlayer->GetObserverMode() == OBS_MODE_NONE ) + { + bChangesLabel = true; + } + } + + if ( !TFInventoryManager()->GetLocalTFInventory()->RetrievedInventoryFromSteam() ) + { + bool bLoggedIntoSteam = steamapicontext && steamapicontext->SteamUser() && steamapicontext->SteamUser()->BLoggedOn(); + if ( m_pItemsLabel ) + m_pNoGCLabel->SetVisible( bLoggedIntoSteam ); + if ( m_pNoSteamLabel ) + m_pNoSteamLabel->SetVisible( !bLoggedIntoSteam ); + if ( m_pSelectLabel ) + m_pSelectLabel->SetVisible( false ); + if ( m_pLoadoutChangesLabel) + m_pLoadoutChangesLabel->SetVisible( false ); + + for ( int i = 0; i < CHSB_NUM_BUTTONS; i++ ) + { + m_pSubButtons[i]->SetVisible( false ); + m_pButtonLabels[i]->SetVisible( false ); + } + } + else + { + if ( m_pNoSteamLabel ) + m_pNoSteamLabel->SetVisible( false ); + if ( m_pNoGCLabel ) + m_pNoGCLabel->SetVisible( false ); + if ( m_pSelectLabel ) + m_pSelectLabel->SetVisible( true ); + + for ( int i = 0; i < CHSB_NUM_BUTTONS; i++ ) + { + m_pSubButtons[i]->SetVisible( true ); + m_pButtonLabels[i]->SetVisible( true ); + } + if ( !bChangesLabel ) + { + if ( m_pSelectLabel ) + m_pSelectLabel->SetPos( 0, m_iSelectLabelY ); + if ( m_pLoadoutChangesLabel ) + m_pLoadoutChangesLabel->SetVisible( false ); + } + else + { + if ( m_pSelectLabel ) + m_pSelectLabel->SetPos( 0, m_iSelectLabelOnChangesY ); + if ( m_pLoadoutChangesLabel ) + m_pLoadoutChangesLabel->SetVisible( true ); + } + } + + m_iOverSubButton = -1; + if ( m_pSelectLabel ) + m_pClassLabel->SetVisible( false ); + if ( m_pItemsLabel ) + m_pItemsLabel->SetVisible( false ); + + m_bClassLayoutDirty = false; + + // Now Layout the class images. + for ( int iPanel = 0; iPanel < ARRAYSIZE( g_nLoadoutClassOrder ); iPanel++ ) + { + int i = g_nLoadoutClassOrder[iPanel]; + + int iX = m_iClassLayout[i][0]; + int iY = m_iClassLayout[i][1]; + int iWide = m_iClassLayout[i][2]; + int iTall = m_iClassLayout[i][3]; + + if ( m_bSnapClassLayout ) + { + m_pClassButtons[i]->SetBounds( iX, iY, iWide, iTall ); + } + else + { + // Lerp towards the target + int iCurX, iCurY, iCurWide, iCurTall; + m_pClassButtons[i]->GetBounds( iCurX, iCurY, iCurWide, iCurTall ); + int iNewX = Lerp( 0.2, iCurX, iX ); + int iNewY = Lerp( 0.2, iCurY, iY ); + int iNewWide = Lerp( 0.2, iCurWide, iWide ); + int iNewTall = Lerp( 0.2, iCurTall, iTall ); + m_pClassButtons[i]->SetBounds( iNewX, iNewY, iNewWide, iNewTall ); + if ( abs(iNewX-iX) > 5 || abs(iNewY-iY) > 5 || abs(iNewWide-iWide) > 5 || abs(iNewTall-iTall) > 5 ) + { + m_bClassLayoutDirty = true; + } + } + } + + // We need to do our own management of cursor arming in the buttons, because the curserentered/exited code can't + // deal with the way we resize the buttons without the cursor moving. + int iBestButton = -1; + int iBestZ = 0; + int x = m_iMouseXPos, y = m_iMouseYPos; + + // only get the actual cursor pos if we don't have a cached cursor pos. THe + // cached pos might have come from the keyboard. + if( x < 0 ) + vgui::input()->GetCursorPos(x, y); + for ( int iPanel = 0; iPanel < ARRAYSIZE( g_nLoadoutClassOrder ); iPanel++ ) + { + int i = g_nLoadoutClassOrder[iPanel]; + m_pClassButtons[i]->SetArmed( false ); + + m_pClassButtons[i]->SetEnabled( TFInventoryManager()->GetLocalTFInventory()->RetrievedInventoryFromSteam() ); + + if ( m_pClassButtons[i]->IsWithin( x,y ) && iBestZ < m_pClassButtons[i]->GetZPos() ) + { + iBestButton = i; + iBestZ = m_pClassButtons[i]->GetZPos(); + } + } + + if ( iBestButton >= 0 && iBestButton < ARRAYSIZE( m_pClassButtons ) ) + { + m_pClassButtons[iBestButton]->SetArmed( true ); + + if ( m_iLabelSetToClass != iBestButton ) + { + m_iLabelSetToClass = iBestButton; + } + + UpdateLabelFromClass( m_iLabelSetToClass ); + } + + m_bSnapClassLayout = false; +} + +void CCharInfoLoadoutSubPanel::UpdateLabelFromClass( int nClass ) +{ + if ( nClass < 0 ) + return; + + const wchar_t *wszClassName = g_pVGuiLocalize->Find( g_aPlayerClassNames[nClass] ); + if ( m_pClassLabel ) + { + m_pClassLabel->SetText( wszClassName ); + m_pClassLabel->SetVisible( true ); + } + + if ( m_pItemsLabel ) + { + m_pItemsLabel->SetVisible( true ); + } + + CUtlVector<CEconItemView*> pList; + int iNumItems = TFInventoryManager()->GetAllUsableItemsForSlot( nClass, -1, &pList ); + + if ( !iNumItems ) + { + const wchar_t *wszItemsName = g_pVGuiLocalize->Find( "#NoItemsFoundShort" ); + m_pItemsLabel->SetText( wszItemsName ); + m_pItemsLabel->SetColorStr( m_ItemColorNone ); + } + else if ( iNumItems == 1 ) + { + const wchar_t *wszItemsName = g_pVGuiLocalize->Find( "#ItemsFoundShortOne" ); + m_pItemsLabel->SetText( wszItemsName ); + m_pItemsLabel->SetColorStr( m_ItemColor ); + } + else + { + wchar_t wzCount[10]; + _snwprintf( wzCount, ARRAYSIZE( wzCount ), L"%d", iNumItems ); + wchar_t wTemp[32]; + g_pVGuiLocalize->ConstructString_safe( wTemp, g_pVGuiLocalize->Find("ItemsFoundShort"), 1, wzCount ); + m_pItemsLabel->SetText( wTemp ); + m_pItemsLabel->SetColorStr( m_ItemColor ); + } + + int iPos = 0; + for ( int i = TF_FIRST_NORMAL_CLASS; i <= NUM_CLASSES_IN_LOADOUT_PANEL; i++ ) + { + if ( iRemapIndexToClass[i] == nClass ) + { + iPos = i; + break; + } + } + Assert(iPos != 0 ); + int iXLeft = (GetWide() - ((m_iClassWideMin * NUM_CLASSES_IN_LOADOUT_PANEL) + (m_iClassXDelta * (NUM_CLASSES_IN_LOADOUT_PANEL-1)))) * 0.5; + int iBaseX = iXLeft + ((m_iClassWideMin + m_iClassXDelta) * (iPos-1)); + int iCenterX = iBaseX + (m_iClassWideMin * 0.5); + + m_pClassLabel->SetVisible( true ); + m_pClassLabel->SetPos( iCenterX - (m_pClassLabel->GetWide() * 0.5), m_iClassLabelYPos ); + m_pItemsLabel->SetVisible( true ); + m_pItemsLabel->SetPos( iCenterX - (m_pItemsLabel->GetWide() * 0.5), m_iItemLabelYPos ); +} + +void CCharInfoLoadoutSubPanel::UpdateLabelFromSubButton( int nButton ) +{ + if( nButton < 0 ) + nButton = CHSB_NUM_BUTTONS - 1; + else if( nButton >= CHSB_NUM_BUTTONS ) + nButton = 0; + + if ( m_iOverSubButton == nButton ) + return; + + m_iOverSubButton = nButton; + + switch ( nButton ) + { + default: + case CHSB_BACKPACK: + { + int iNumItems = TFInventoryManager()->GetLocalTFInventory()->GetItemCount(); + if ( iNumItems == 1 ) + { + const wchar_t *wszItemsName = g_pVGuiLocalize->Find( "#Loadout_OpenBackpackDesc1" ); + m_pItemsLabel->SetText( wszItemsName ); + } + else + { + wchar_t wzCount[10]; + _snwprintf( wzCount, ARRAYSIZE( wzCount ), L"%d", iNumItems ); + wchar_t wTemp[32]; + g_pVGuiLocalize->ConstructString_safe( wTemp, g_pVGuiLocalize->Find("Loadout_OpenBackpackDesc"), 1, wzCount ); + m_pItemsLabel->SetText( wTemp ); + } + } + break; + case CHSB_CRAFTING: + m_pItemsLabel->SetText( g_pVGuiLocalize->Find( "Loadout_OpenCraftingDesc" ) ); + break; + case CHSB_ARMORY: + m_pItemsLabel->SetText( g_pVGuiLocalize->Find( "Loadout_OpenArmoryDesc" ) ); + break; + case CHSB_TRADING: + m_pItemsLabel->SetText( g_pVGuiLocalize->Find( "Loadout_OpenTradingDesc" ) ); + break; + } + + int iX, iY; + m_pSubButtons[nButton]->GetPos( iX, iY ); + iX += (m_pSubButtons[nButton]->GetWide() * 0.5); + iY += m_pSubButtons[nButton]->GetTall() + YRES(5); + + m_pItemsLabel->SetVisible( true ); + m_pItemsLabel->SetPos( iX - (m_pItemsLabel->GetWide() * 0.5), iY + (m_iItemLabelYPos - m_iClassLabelYPos) ); + m_pItemsLabel->SetColorStr( m_ItemColor ); + + for ( int i = 0; i < CHSB_NUM_BUTTONS; i++ ) + { + m_pSubButtons[i]->SetArmed( false ); + } + + m_pSubButtons[nButton]->SetArmed( true ); + m_pSubButtons[nButton]->RequestFocus(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharInfoLoadoutSubPanel::OnTick( void ) +{ + if ( m_iCurrentClassIndex != TF_CLASS_UNDEFINED ) + return; + if ( !IsVisible() ) + return; + + if ( m_bRequestingInventoryRefresh && TFInventoryManager()->GetLocalTFInventory()->RetrievedInventoryFromSteam() ) + { + m_bRequestingInventoryRefresh = false; + CloseWaitingDialog(); + return; + } + + // if the class layout is dirty, invalidate our layout so that + // we'll animate the class buttons. + if ( m_bClassLayoutDirty ) + { + InvalidateLayout(); + } + + if ( !HasFocus() ) + return; + + // Don't respond to the mouse if we don't have items + if ( !TFInventoryManager()->GetLocalTFInventory()->RetrievedInventoryFromSteam() ) + return; + + if ( m_flStartExplanationsAt && m_flStartExplanationsAt < engine->Time() ) + { + m_flStartExplanationsAt = 0; + + if ( ShouldShowExplanations() ) + { + tf_explanations_charinfopanel.SetValue( 1 ); + + CExplanationPopup *pPopup = dynamic_cast<CExplanationPopup*>( FindChildByName("StartExplanation") ); + if ( pPopup ) + { + pPopup->Popup(); + } + } + } + +} + + +//----------------------------------------------------------------------------- +// Purpose: Handles mousing over classes +//----------------------------------------------------------------------------- +void CCharInfoLoadoutSubPanel::OnCursorMoved( int x, int y ) +{ + RecalculateTargetClassLayoutAtPos( x, y ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Handles setting the highlighted class for both mouse and keyboard +//----------------------------------------------------------------------------- +void CCharInfoLoadoutSubPanel::RecalculateTargetClassLayoutAtPos( int x, int y ) +{ + // Ignore mouse movement outside the buttons + bool bWithin = false; + for ( int i = TF_FIRST_NORMAL_CLASS; i <= NUM_CLASSES_IN_LOADOUT_PANEL; i++ ) + { + if ( m_pClassButtons[i]->IsWithin(x,y) ) + { + bWithin = true; + break; + } + } + + if ( bWithin ) + { + m_iMouseXPos = x; + m_iMouseYPos = y; + RecalculateTargetClassLayout(); + m_bClassLayoutDirty = true; + } + else + { + // See if we're over a sub button + bool bOverSubButton = false; + for ( int i = 0; i < CHSB_NUM_BUTTONS; i++ ) + { + if ( m_pSubButtons[i]->IsWithin(x,y) ) + { + bOverSubButton = true; + UpdateLabelFromSubButton( i ); + } + } + + if ( !bOverSubButton && m_pClassLabel->IsVisible() ) + { + // Hide the class label + if ( m_iMouseXPos != -1 ) + { + m_iMouseXPos = -1; + RecalculateTargetClassLayout(); + m_bClassLayoutDirty = true; + } + + m_iOverSubButton = -1; + m_iLabelSetToClass = -1; + m_pClassLabel->SetVisible( false ); + m_pItemsLabel->SetVisible( false ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCharInfoLoadoutSubPanel::RecalculateTargetClassLayout( void ) +{ + // Now Layout the class images. + for ( int i = TF_FIRST_NORMAL_CLASS; i <= NUM_CLASSES_IN_LOADOUT_PANEL; i++ ) + { + int iIndex = GetRemappedMenuIndexForClass(i); + + // Figure out where we'd be unscaled + int iXLeft = (GetWide() - ((m_iClassWideMin * NUM_CLASSES_IN_LOADOUT_PANEL) + (m_iClassXDelta * (NUM_CLASSES_IN_LOADOUT_PANEL-1)))) * 0.5; + int iBaseX = iXLeft + ((m_iClassWideMin + m_iClassXDelta) * (iIndex-1)); + + // Scale based on distance from the mouse cursor. + int iCenterX = iBaseX + (m_iClassWideMin * 0.5); + + float flScale = 0.0; + if ( m_iMouseXPos >= 0 ) + { + flScale = RemapValClamped( abs(m_iMouseXPos - iCenterX), m_iClassDistanceMin, m_iClassDistanceMax, 1.0, 0.0 ); + } + + float iWide = RemapValClamped( flScale, 0.0, 1.0, m_iClassWideMin, m_iClassWideMax ); + float iTall = RemapValClamped( flScale, 0.0, 1.0, m_iClassTallMin, m_iClassTallMax ); + + int iY = m_iClassYPos - ((iTall - m_iClassTallMin) * 0.5); + int iX = iBaseX - ((iWide - m_iClassWideMin) * 0.5); + + m_pClassButtons[i]->SetZPos( flScale * 100 ); + + // Cache off the target bounds for this class button + m_iClassLayout[i][0] = iX; + m_iClassLayout[i][1] = iY; + m_iClassLayout[i][2] = iWide; + m_iClassLayout[i][3] = iTall; + } +} + +void CCharInfoLoadoutSubPanel::MoveCharacterSelection( int nDirection ) +{ + int nCurrent = 0; + + if ( m_iLabelSetToClass != -1 ) + { + for ( int i = 0; i < ARRAYSIZE( g_nLoadoutClassOrder ); i++ ) + { + if ( m_iLabelSetToClass == g_nLoadoutClassOrder[ i ] ) + { + nCurrent = i; + break; + } + } + + nCurrent += nDirection; + + if ( nCurrent < 0 ) + { + nCurrent = ARRAYSIZE( g_nLoadoutClassOrder ) - 1; + } + else if ( nCurrent >= ARRAYSIZE( g_nLoadoutClassOrder ) ) + { + nCurrent = 0; + } + } + + for ( int i = 0; i < ARRAYSIZE( g_nLoadoutClassOrder ); i++ ) + { + m_pClassButtons[ g_nLoadoutClassOrder[ i ] ]->SetArmed( false ); + } + + // animate the class buttons + CImageButton *pButton = m_pClassButtons[ g_nLoadoutClassOrder[ nCurrent ] ]; + int x, y, wide, tall; + pButton->GetBounds( x, y, wide, tall ); + RecalculateTargetClassLayoutAtPos( x + wide/2, y + tall/2 ); + + pButton->RequestFocus(); +} + +void CCharInfoLoadoutSubPanel::OnKeyCodeTyped(vgui::KeyCode code) +{ + // turn off key handling in this panel when we're showing a loadout + // for one class + if ( m_iCurrentClassIndex != TF_CLASS_UNDEFINED ) + { + // let escape and B (aka "go back") through so we + // can actually get out of the loadout screen + if ( code == KEY_ESCAPE ) + { + BaseClass::OnKeyCodePressed( code ); + } + return; + } + + BaseClass::OnKeyCodeTyped( code ); +} + +void CCharInfoLoadoutSubPanel::OnKeyCodePressed(vgui::KeyCode code) +{ + ButtonCode_t nButtonCode = GetBaseButtonCode( code ); + + // turn off key handling in this panel when we're showing a loadout + // for one class + if( m_iCurrentClassIndex != TF_CLASS_UNDEFINED ) + { + // let escape and B (aka "go back") through so we + // can actually get out of the loadout screen + if ( nButtonCode == KEY_XBUTTON_B ) + { + BaseClass::OnKeyCodePressed( code ); + } + return; + } + + if ( nButtonCode == KEY_XBUTTON_LEFT || + nButtonCode == KEY_XSTICK1_LEFT || + nButtonCode == KEY_XSTICK2_LEFT || + nButtonCode == STEAMCONTROLLER_DPAD_LEFT || + code == KEY_LEFT ) + { + if ( m_iLabelSetToClass != -1 ) + { + MoveCharacterSelection( -1 ); + } + else + { + UpdateLabelFromSubButton( m_iOverSubButton - 1 ); + } + return; + } + else if ( nButtonCode == KEY_XBUTTON_RIGHT || + nButtonCode == KEY_XSTICK1_RIGHT || + nButtonCode == KEY_XSTICK2_RIGHT || + nButtonCode == STEAMCONTROLLER_DPAD_RIGHT || + code == KEY_RIGHT ) + { + if ( m_iLabelSetToClass != -1 ) + { + MoveCharacterSelection( 1 ); + } + else + { + UpdateLabelFromSubButton( m_iOverSubButton + 1 ); + } + return; + } + else if ( nButtonCode == KEY_XBUTTON_UP || + nButtonCode == KEY_XSTICK1_UP || + nButtonCode == KEY_XSTICK2_UP || + nButtonCode == STEAMCONTROLLER_DPAD_UP || + code == KEY_UP ) + { + if ( m_iLabelSetToClass == -1 ) + { + m_iLabelSetToClass = g_nLoadoutClassOrder[ 0 ]; + CImageButton *pButton = m_pClassButtons[ m_iLabelSetToClass ]; + UpdateLabelFromClass( m_iLabelSetToClass ); + + int x, y, wide, tall; + pButton->GetBounds( x, y, wide, tall ); + RecalculateTargetClassLayoutAtPos( x + wide/2, y + tall/2 ); + pButton->RequestFocus(); + + } + return; + } + else if ( nButtonCode == KEY_XBUTTON_DOWN || + nButtonCode == KEY_XSTICK1_DOWN || + nButtonCode == KEY_XSTICK2_DOWN || + nButtonCode == STEAMCONTROLLER_DPAD_DOWN || + code == KEY_DOWN ) + { + if ( m_iLabelSetToClass != -1 ) + { + m_iLabelSetToClass = -1; + m_pClassLabel->SetVisible( false ); + m_pItemsLabel->SetVisible( false ); + + for ( int iPanel = 0; iPanel < ARRAYSIZE( g_nLoadoutClassOrder ); iPanel++ ) + { + int i = g_nLoadoutClassOrder[iPanel]; + m_pClassButtons[i]->SetArmed( false ); + } + + UpdateLabelFromSubButton( 0 ); + } + return; + } + + BaseClass::OnKeyCodePressed( code ); +}
\ No newline at end of file diff --git a/game/client/tf/vgui/charinfo_loadout_subpanel.h b/game/client/tf/vgui/charinfo_loadout_subpanel.h new file mode 100644 index 0000000..ec7581f --- /dev/null +++ b/game/client/tf/vgui/charinfo_loadout_subpanel.h @@ -0,0 +1,177 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef CHARINFO_LOADOUT_SUBPANEL_H +#define CHARINFO_LOADOUT_SUBPANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include <game/client/iviewport.h> +#include "vgui_controls/PropertyPage.h" +#include <vgui_controls/Button.h> +#include "tf_controls.h" +#include "tf_shareddefs.h" +#include "item_pickup_panel.h" +#include "backpack_panel.h" +#include "class_loadout_panel.h" +#include "crafting_panel.h" +#include "charinfo_armory_subpanel.h" + +#define NUM_CLASSES_IN_LOADOUT_PANEL (TF_LAST_NORMAL_CLASS-1) // We don't allow unlockables for the civilian + +class CImageButton : public vgui::Button +{ +private: + DECLARE_CLASS_SIMPLE( CImageButton, vgui::Button ); + +public: + CImageButton( vgui::Panel *parent, const char *panelName ); + + virtual void ApplySettings( KeyValues *inResourceData ); + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void OnSizeChanged( int newWide, int newTall ); + + void SetActiveImage( const char *imagename ); + void SetInactiveImage( const char *imagename ); + void SetActiveImage( vgui::IImage *image ); + void SetInactiveImage( vgui::IImage *image ); + +public: + virtual void Paint(); + +private: + vgui::IImage *m_pActiveImage; + char *m_pszActiveImageName; + + vgui::IImage *m_pInactiveImage; + char *m_pszInactiveImageName; + + bool m_bScaleImage; + Color m_ActiveDrawColor; + Color m_InactiveDrawColor; +}; + +enum charinfo_activepanels_t +{ + CHAP_LOADOUT, + CHAP_BACKPACK, + CHAP_CRAFTING, + CHAP_ARMORY, +}; + +enum charinfosubbuttons_t +{ + CHSB_BACKPACK, + CHSB_CRAFTING, + CHSB_ARMORY, + CHSB_TRADING, + + CHSB_NUM_BUTTONS +}; + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CCharInfoLoadoutSubPanel : public vgui::PropertyPage +{ + DECLARE_CLASS_SIMPLE( CCharInfoLoadoutSubPanel, vgui::PropertyPage ); +public: + CCharInfoLoadoutSubPanel(Panel *parent); + virtual ~CCharInfoLoadoutSubPanel(); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void OnCommand( const char *command ); + virtual void PerformLayout( void ); + virtual void OnCursorMoved( int x, int y ); + + void SetClassIndex( int iClassIndex, bool bOpenClassLoadout ); + void SetTeamIndex( int iTeamIndex ); + void OpenToBackpack( void ) { OpenSubPanel( CHAP_BACKPACK ); } + void OpenToCrafting( void ) { OpenSubPanel( CHAP_CRAFTING ); } + void OpenToArmory( int iItemDef = 0 ) { m_iArmoryItemDef = iItemDef; OpenSubPanel( CHAP_ARMORY ); } + void OpenSubPanel( charinfo_activepanels_t iPanel ); + void UpdateModelPanels( bool bOpenClassLoadout = true ); + + CClassLoadoutPanel *GetClassLoadoutPanel( void ) { return m_pClassLoadoutPanel; } + CBackpackPanel *GetBackpackPanel( void ) { return m_pBackpackPanel; } + CCraftingPanel *GetCraftingPanel( void ) { return m_pCraftingPanel; } + CArmoryPanel *GetArmoryPanel( void ) { return m_pArmoryPanel; } + + void UpdateLabelFromClass( int nClass ); + void UpdateLabelFromSubButton( int nButton ); + virtual void OnTick( void ); + void RecalculateTargetClassLayout( void ); + void RecalculateTargetClassLayoutAtPos( int x, int y ); + void MoveCharacterSelection( int nDirection ); + void OnKeyCodeTyped( vgui::KeyCode code ); + void OnKeyCodePressed( vgui::KeyCode code ); + + bool ShouldShowExplanations( void ) { return (m_iShowingPanel == CHAP_LOADOUT && m_iCurrentClassIndex == TF_CLASS_UNDEFINED); } + + charinfo_activepanels_t GetShowingPanel() const { return m_iShowingPanel; } + int GetCurrentClassIndex() const { return m_iCurrentClassIndex; } + + MESSAGE_FUNC( OnPageShow, "PageShow" ); + MESSAGE_FUNC( OnSelectionStarted, "SelectionStarted" ); + MESSAGE_FUNC( OnSelectionEnded, "SelectionEnded" ); + MESSAGE_FUNC( OnCancelSelection, "CancelSelection" ); + MESSAGE_FUNC( OnOpenCrafting, "OpenCrafting" ); + MESSAGE_FUNC( OnCraftingClosed, "CraftingClosed" ); + MESSAGE_FUNC( OnArmoryClosed, "ArmoryClosed" ); + MESSAGE_FUNC( OnCharInfoClosing, "CharInfoClosing" ); + +private: + void RequestInventoryRefresh(); + + CImageButton *m_pClassButtons[NUM_CLASSES_IN_LOADOUT_PANEL+1]; + CImageButton *m_pSubButtons[CHSB_NUM_BUTTONS]; + CExLabel *m_pButtonLabels[CHSB_NUM_BUTTONS]; + int m_iOverSubButton; + int m_iClassLayout[NUM_CLASSES_IN_LOADOUT_PANEL+1][4]; + bool m_bClassLayoutDirty; + bool m_bSnapClassLayout; + bool m_bRequestingInventoryRefresh; + int m_iCurrentClassIndex; + int m_iCurrentTeamIndex; + charinfo_activepanels_t m_iShowingPanel; + charinfo_activepanels_t m_iPrevShowingPanel; + CClassLoadoutPanel *m_pClassLoadoutPanel; + CBackpackPanel *m_pBackpackPanel; + CCraftingPanel *m_pCraftingPanel; + CArmoryPanel *m_pArmoryPanel; + vgui::Label *m_pSelectLabel; + vgui::Label *m_pLoadoutChangesLabel; + vgui::Label *m_pNoSteamLabel; + vgui::Label *m_pNoGCLabel; + vgui::Label *m_pClassLabel; + CExLabel *m_pItemsLabel; + int m_iMouseXPos; + int m_iMouseYPos; + int m_iLabelSetToClass; + int m_iClassLabelYPos; + int m_iItemLabelYPos; + Color m_ItemColorNone; + Color m_ItemColor; + float m_flStartExplanationsAt; + int m_iArmoryItemDef; + + CPanelAnimationVarAliasType( int, m_iSelectLabelY, "selectlabely_default", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iSelectLabelOnChangesY, "selectlabely_onchanges", "0", "proportional_int" ); + + CPanelAnimationVarAliasType( int, m_iClassYPos, "class_ypos", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iClassXDelta, "class_xdelta", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iClassWideMin, "class_wide_min", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iClassWideMax, "class_wide_max", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iClassTallMin, "class_tall_min", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iClassTallMax, "class_tall_max", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iClassDistanceMin, "class_distance_min", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iClassDistanceMax, "class_distance_max", "0", "proportional_int" ); +}; + +#endif // CHARINFO_LOADOUT_SUBPANEL_H diff --git a/game/client/tf/vgui/class_loadout_panel.cpp b/game/client/tf/vgui/class_loadout_panel.cpp new file mode 100644 index 0000000..5db5fba --- /dev/null +++ b/game/client/tf/vgui/class_loadout_panel.cpp @@ -0,0 +1,1539 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "class_loadout_panel.h" +#include "c_tf_player.h" +#include "vgui_controls/CheckButton.h" +#include "econ_gcmessages.h" +#include "gc_clientsystem.h" +#include "tf_item_system.h" +#include "loadout_preset_panel.h" +#include "econ_item_description.h" +#include "item_style_select_dialog.h" +#include "vgui/IInput.h" +#include "vgui_controls/PanelListPanel.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +extern ConVar tf_respawn_on_loadoutchanges; + +ConVar tf_show_preset_explanation_in_class_loadout( "tf_show_preset_explanation_in_class_loadout", "1", FCVAR_HIDDEN | FCVAR_CLIENTDLL | FCVAR_ARCHIVE ); +ConVar tf_show_taunt_explanation_in_class_loadout( "tf_show_taunt_explanation_in_class_loadout", "1", FCVAR_HIDDEN | FCVAR_CLIENTDLL | FCVAR_ARCHIVE ); + +void ParticleSlider_UpdateRequest( int iLoadoutPosition, float value ) +{ + CClassLoadoutPanel *pPanel = g_pClassLoadoutPanel; + if ( !pPanel ) + return; + + CEconItemView *pHat = pPanel->GetItemInSlot( iLoadoutPosition ); + if ( !pHat ) + return; + + // does this hat even have a particle effect + static CSchemaAttributeDefHandle pAttrDef_AttachParticleEffect( "attach particle effect" ); + uint32 iHasEffect = 0; + if ( !pHat->FindAttribute( pAttrDef_AttachParticleEffect, &iHasEffect ) ) + return; + + // Check for use head toggle + static CSchemaAttributeDefHandle pAttrDef_UseHeadOrigin( "particle effect use head origin" ); + uint32 iUseHead = 0; + if ( !pHat->FindAttribute( pAttrDef_UseHeadOrigin, &iUseHead ) || iUseHead == 0 ) + return; + + // Look for the attribute and request to change it + static CSchemaAttributeDefHandle pAttrDef_VerticalOffset( "particle effect vertical offset" ); + uint32 iOffSet = 0; + if ( !pHat->FindAttribute( pAttrDef_VerticalOffset, &iOffSet ) && value == 0 ) + { + return; + } + else + { + const float& flAttrValue = (float&)iOffSet; + if ( value == flAttrValue ) + return; // no change do nothing + } + + // Send a message to the GC to request a change + GCSDK::CProtoBufMsg<CMsgSetItemEffectVerticalOffset> msg( k_EMsgGCSetItemEffectVerticalOffset ); + msg.Body().set_item_id( pHat->GetItemID() ); + msg.Body().set_offset( value ); + GCClientSystem()->BSendMessage( msg ); +} + +void HatOffset_Callback( IConVar *pConVar, char const *pOldString, float flOldValue ) +{ + ConVarRef cVarRef( pConVar ); + ParticleSlider_UpdateRequest( LOADOUT_POSITION_HEAD, cVarRef.GetFloat() ); +} +ConVar tf_hat_effect_offset( "tf_hat_effect_offset", "0", FCVAR_DEVELOPMENTONLY, "Adjust the position of the unusual effect for your hat.", HatOffset_Callback ); + +void Misc1Offset_Callback( IConVar *pConVar, char const *pOldString, float flOldValue ) +{ + ConVarRef cVarRef( pConVar ); + ParticleSlider_UpdateRequest( LOADOUT_POSITION_MISC, cVarRef.GetFloat() ); +} +ConVar tf_misc1_effect_offset( "tf_misc1_effect_offset", "0", FCVAR_DEVELOPMENTONLY, "Adjust the position of the unusual effect for your hat.", Misc1Offset_Callback ); + +void Misc2Offset_Callback( IConVar *pConVar, char const *pOldString, float flOldValue ) +{ + ConVarRef cVarRef( pConVar ); + ParticleSlider_UpdateRequest( LOADOUT_POSITION_MISC2, cVarRef.GetFloat() ); +} +ConVar tf_misc2_effect_offset( "tf_misc2_effect_offset", "0", FCVAR_DEVELOPMENTONLY, "Adjust the position of the unusual effect for your hat.", Misc2Offset_Callback ); + + +// Hacky solution to different classes wanting different slots visible in their loadouts, and in different positions +struct LoadoutPanelPositioningInstance +{ + int m_iPos[NUM_ITEM_PANELS_IN_LOADOUT]; +}; + +bool IsTauntPanelPosition( int iButtonPos ) +{ + return iButtonPos >= 9 && iButtonPos <= 16; +} + +const LoadoutPanelPositioningInstance g_DefaultLoadoutPanelPositioning = +{ + { + 1, // LOADOUT_POSITION_PRIMARY = 0, + 2, // LOADOUT_POSITION_SECONDARY, + 3, // LOADOUT_POSITION_MELEE, + 0, // LOADOUT_POSITION_UTILITY, // STAGING ONLY + 0, // LOADOUT_POSITION_BUILDING, + 0, // LOADOUT_POSITION_PDA, + 0, // LOADOUT_POSITION_PDA2, + 5, // LOADOUT_POSITION_HEAD, + 6, // LOADOUT_POSITION_MISC, + 8, // LOADOUT_POSITION_ACTION, + 7, // LOADOUT_POSITION_MISC2, + 9, // LOADOUT_POSITION_TAUNT, + 10, // LOADOUT_POSITION_TAUNT2, + 11, // LOADOUT_POSITION_TAUNT3, + 12, // LOADOUT_POSITION_TAUNT4, + 13, // LOADOUT_POSITION_TAUNT5, + 14, // LOADOUT_POSITION_TAUNT6, + 15, // LOADOUT_POSITION_TAUNT7, + 16, // LOADOUT_POSITION_TAUNT8, + +#ifdef STAGING_ONLY + 0, // LOADOUT_POSITION_PDA_ADDON1, + 0, // LOADOUT_POSITION_PDA_ADDON2, + 0, // LOADOUT_POSITION_PDA3, + //9, // LOADOUT_POSITION_MISC3, + //10, // LOADOUT_POSITION_MISC4, + //11, // LOADOUT_POSITION_MISC5, + //12, // LOADOUT_POSITION_MISC6, + //13, // LOADOUT_POSITION_MISC7, + //14, // LOADOUT_POSITION_MISC8, + //15, // LOADOUT_POSITION_MISC9, + //16, // LOADOUT_POSITION_MISC10, + 0, // LOADOUT_POSITION_BUILDING2, +#endif // STAGING_ONLY + } +}; + +const LoadoutPanelPositioningInstance g_LoadoutPanelPositioning_Spy = +{ + { + 0, // LOADOUT_POSITION_PRIMARY = 0, + 1, // LOADOUT_POSITION_SECONDARY, + 2, // LOADOUT_POSITION_MELEE, + 0, // LOADOUT_POSITION_UTILITY, // STAGING ONLY + 4, // LOADOUT_POSITION_BUILDING, // sapper + 0, // LOADOUT_POSITION_PDA, // disguise kit (Hidden) + 3, // LOADOUT_POSITION_PDA2, // Watch + 5, // LOADOUT_POSITION_HEAD, + 6, // LOADOUT_POSITION_MISC, + 8, // LOADOUT_POSITION_ACTION, + 7, // LOADOUT_POSITION_MISC2, + 9, // LOADOUT_POSITION_TAUNT, + 10, // LOADOUT_POSITION_TAUNT2, + 11, // LOADOUT_POSITION_TAUNT3, + 12, // LOADOUT_POSITION_TAUNT4, + 13, // LOADOUT_POSITION_TAUNT5, + 14, // LOADOUT_POSITION_TAUNT6, + 15, // LOADOUT_POSITION_TAUNT7, + 16, // LOADOUT_POSITION_TAUNT8, +#ifdef STAGING_ONLY + 0, // LOADOUT_POSITION_PDA_ADDON1, + 0, // LOADOUT_POSITION_PDA_ADDON2, + 0, // LOADOUT_POSITION_PDA3, + //9, // LOADOUT_POSITION_MISC3, + //10, // LOADOUT_POSITION_MISC4, + //11, // LOADOUT_POSITION_MISC5, + //12, // LOADOUT_POSITION_MISC6, + //13, // LOADOUT_POSITION_MISC7, + //14, // LOADOUT_POSITION_MISC8, + //15, // LOADOUT_POSITION_MISC9, + //16, // LOADOUT_POSITION_MISC10, + 0, // LOADOUT_POSITION_BUILDING2, +#endif // STAGING_ONLY + } +}; + +const LoadoutPanelPositioningInstance g_LoadoutPanelPositioning_Engineer = +{ + { + 1, // LOADOUT_POSITION_PRIMARY = 0, + 2, // LOADOUT_POSITION_SECONDARY, + 3, // LOADOUT_POSITION_MELEE, + 0, // LOADOUT_POSITION_UTILITY, // STAGING ONLY + 0, // LOADOUT_POSITION_BUILDING, + 4, // LOADOUT_POSITION_PDA, + 0, // LOADOUT_POSITION_PDA2, + 5, // LOADOUT_POSITION_HEAD, + 6, // LOADOUT_POSITION_MISC, + 8, // LOADOUT_POSITION_ACTION, + 7, // LOADOUT_POSITION_MISC2, + 9, // LOADOUT_POSITION_TAUNT, + 10, // LOADOUT_POSITION_TAUNT2, + 11, // LOADOUT_POSITION_TAUNT3, + 12, // LOADOUT_POSITION_TAUNT4, + 13, // LOADOUT_POSITION_TAUNT5, + 14, // LOADOUT_POSITION_TAUNT6, + 15, // LOADOUT_POSITION_TAUNT7, + 16, // LOADOUT_POSITION_TAUNT8, +#ifdef STAGING_ONLY + 17, // LOADOUT_POSITION_PDA_ADDON1, + 18, // LOADOUT_POSITION_PDA_ADDON2, + 0, // LOADOUT_POSITION_PDA3, + //9, // LOADOUT_POSITION_MISC3, + //10, // LOADOUT_POSITION_MISC4, + //11, // LOADOUT_POSITION_MISC5, + //12, // LOADOUT_POSITION_MISC6, + //13, // LOADOUT_POSITION_MISC7, + //14, // LOADOUT_POSITION_MISC8, + //15, // LOADOUT_POSITION_MISC9, + //16, // LOADOUT_POSITION_MISC10, + 0, // LOADOUT_POSITION_BUILDING2, +#endif // STAGING_ONLY + } +}; + +const LoadoutPanelPositioningInstance *g_VisibleLoadoutSlotsPerClass[] = +{ + &g_DefaultLoadoutPanelPositioning, // TF_CLASS_UNDEFINED + &g_DefaultLoadoutPanelPositioning, // TF_CLASS_SCOUT + &g_DefaultLoadoutPanelPositioning, // TF_CLASS_SNIPER + &g_DefaultLoadoutPanelPositioning, // TF_CLASS_SOLDIER + &g_DefaultLoadoutPanelPositioning, // TF_CLASS_DEMOMAN + &g_DefaultLoadoutPanelPositioning, // TF_CLASS_MEDIC + &g_DefaultLoadoutPanelPositioning, // TF_CLASS_HEAVYWEAPONS + &g_DefaultLoadoutPanelPositioning, // TF_CLASS_PYRO + &g_LoadoutPanelPositioning_Spy, // TF_CLASS_SPY + &g_LoadoutPanelPositioning_Engineer, // TF_CLASS_ENGINEER +}; + +COMPILE_TIME_ASSERT( ARRAYSIZE( g_VisibleLoadoutSlotsPerClass ) == TF_LAST_NORMAL_CLASS ); + +//----------------------------------------------------------------------------- +// Particle Effect Slider +//----------------------------------------------------------------------------- +CLoadoutItemOptionsPanel::CLoadoutItemOptionsPanel( Panel *parent, const char *pName ) : vgui::EditablePanel( parent, pName ) +{ + m_pHatParticleSlider = NULL; + m_pHatParticleUseHeadButton = NULL; + + m_iCurrentClassIndex = -1; + m_eItemSlot = LOADOUT_POSITION_INVALID; + + m_pListPanel = new vgui::PanelListPanel( this, "PanelListPanel" ); + m_pListPanel->SetFirstColumnWidth( 0 ); + m_pHatParticleSlider = new CCvarSlider( m_pListPanel, "HatParticleSlider" ); + m_pHatParticleSlider->AddActionSignalTarget( this ); + m_pHatParticleUseHeadButton = new vgui::CheckButton( m_pListPanel, "HatUseHeadCheckButton", "#GameUI_ParticleHatUseHead" ); + m_pHatParticleUseHeadButton->AddActionSignalTarget( this ); + m_pSetStyleButton = new CExButton( m_pListPanel, "SetStyleButton", "#TF_Item_SelectStyle" ); + m_pSetStyleButton->AddActionSignalTarget( this ); +} + +//----------------------------------------------------------------------------- +void CLoadoutItemOptionsPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + LoadControlSettings( "resource/UI/ItemOptionsPanel.res" ); +} + +//----------------------------------------------------------------------------- +void CLoadoutItemOptionsPanel::PerformLayout( void ) +{ + BaseClass::PerformLayout(); + m_pHatParticleSlider->SetTickColor( Color( 235, 226, 202, 255 ) ); // tanlight +} + +//----------------------------------------------------------------------------- +void CLoadoutItemOptionsPanel::OnCommand( const char *command ) +{ + if ( FStrEq( command, "particle_button_clicked" ) ) + { + UpdateItemOptionsUI(); + return; + } + else if ( FStrEq( command, "particle_use_head_clicked" ) ) + { + // Grab current hat + CEconItemView *pHat = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, m_eItemSlot ); + if ( !pHat ) + return; + + // does this hat even have a particle effect + static CSchemaAttributeDefHandle pAttrDef_AttachParticleEffect( "attach particle effect" ); + uint32 iHasEffect = 0; + if ( !pHat->FindAttribute( pAttrDef_AttachParticleEffect, &iHasEffect ) ) + return; + + // Send a message to the GC to request a change + GCSDK::CProtoBufMsg<CMsgSetHatEffectUseHeadOrigin> msg( k_EMsgGCSetHatEffectUseHeadOrigin ); + msg.Body().set_item_id( pHat->GetItemID() ); + msg.Body().set_use_head( m_pHatParticleUseHeadButton->IsSelected() ); + GCClientSystem()->BSendMessage( msg ); + return; + } + else if ( FStrEq( command, "set_style" ) ) + { + CEconItemView *pHat = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, m_eItemSlot ); + CStyleSelectDialog *pStyle = vgui::SETUP_PANEL( new CStyleSelectDialog( GetParent(), pHat ) ); + if ( pStyle ) + { + pStyle->Show(); + } + } +} + +//----------------------------------------------------------------------------- +void CLoadoutItemOptionsPanel::OnMessage( const KeyValues* pParams, vgui::VPANEL hFromPanel ) +{ + if ( FStrEq( pParams->GetName(), "SliderDragEnd" ) ) + { + m_pHatParticleSlider->ApplyChanges(); + } + + BaseClass::OnMessage( pParams, hFromPanel ); +} + +//----------------------------------------------------------------------------- +void CLoadoutItemOptionsPanel::SetItemSlot( loadout_positions_t eItemSlot, int iClassIndex ) +{ + m_eItemSlot = eItemSlot; + m_iCurrentClassIndex = iClassIndex; + // Init the Slider based on the slot + const char * pszConVarName = NULL; + + switch ( eItemSlot ) + { + case LOADOUT_POSITION_HEAD : + pszConVarName = "tf_hat_effect_offset"; + break; + case LOADOUT_POSITION_MISC : + pszConVarName = "tf_misc1_effect_offset"; + break; + case LOADOUT_POSITION_MISC2 : + pszConVarName = "tf_misc2_effect_offset"; + break; + default: + break; + } + + if ( pszConVarName ) + { + m_pHatParticleSlider->SetupSlider( -8, 8, pszConVarName, false ); + } + + m_pHatParticleSlider->SetTickColor( Color( 235, 226, 202, 255 ) ); // tanlight + m_pHatParticleSlider->SetTickCaptions( "", "" ); + + UpdateItemOptionsUI(); +} + +//----------------------------------------------------------------------------- +void CLoadoutItemOptionsPanel::UpdateItemOptionsUI() +{ + if ( m_eItemSlot == LOADOUT_POSITION_INVALID ) + return; + + m_pListPanel->RemoveAll(); + + // Add controls for various item options + AddControlsParticleEffect(); + AddControlsSetStyle(); + + // Bail if no controls added + if ( m_pListPanel->GetItemCount() == 0 ) + { + // We should have some controls if we get to this point. + Assert( 0 ); + SetVisible( false ); + return; + } + + // Resize the background and list panel to contain all the controls + int nVertPixels = m_pListPanel->ComputeVPixelsNeeded(); + int nNewTall = Min( 200, nVertPixels ); + m_pListPanel->SetTall( nNewTall ); + SetTall( nNewTall ); + InvalidateLayout( true, false ); +} + +//----------------------------------------------------------------------------- +void CLoadoutItemOptionsPanel::AddControlsParticleEffect( void ) const +{ + m_pHatParticleUseHeadButton->SetVisible( false ); + m_pHatParticleSlider->SetVisible( false ); + + CEconItemView *pItem = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, m_eItemSlot ); + if ( pItem ) + { + // does this hat even have a particle effect + static CSchemaAttributeDefHandle pAttrDef_AttachParticleEffect( "attach particle effect" ); + uint32 iValue = 0; + if ( pItem->FindAttribute( pAttrDef_AttachParticleEffect, &iValue ) ) + { + m_pHatParticleUseHeadButton->SetVisible( true ); + m_pListPanel->AddItem( NULL, m_pHatParticleUseHeadButton ); + m_pListPanel->AddItem( NULL, m_pHatParticleSlider ); + + // Check for use head toggle + static CSchemaAttributeDefHandle pAttrDef_UseHeadOrigin( "particle effect use head origin" ); + uint32 iUseHead = 0; + if ( pItem->FindAttribute( pAttrDef_UseHeadOrigin, &iUseHead ) && iUseHead > 0 ) + { + m_pHatParticleSlider->SetVisible( true ); + + m_pHatParticleUseHeadButton->SetSelected( true ); + m_pHatParticleSlider->SetTickColor( Color( 235, 226, 202, 255 ) ); // tanlight + m_pHatParticleSlider->Repaint(); + + // Get offset if it exists + static CSchemaAttributeDefHandle pAttrDef_VerticalOffset( "particle effect vertical offset" ); + uint32 iOffset = 0; + if ( pItem->FindAttribute( pAttrDef_VerticalOffset, &iOffset ) ) + { + m_pHatParticleSlider->SetSliderValue( (float&)iOffset ); + } + } + else + { + m_pHatParticleUseHeadButton->SetSelected( false ); + } + } + } +} + +//----------------------------------------------------------------------------- +void CLoadoutItemOptionsPanel::AddControlsSetStyle( void ) const +{ + m_pSetStyleButton->SetVisible( false ); + + CEconItemView *pItem = GetItem(); + if ( pItem && pItem->GetStaticData()->GetNumStyles() ) + { + m_pSetStyleButton->SetVisible( true ); + m_pListPanel->AddItem( NULL, m_pSetStyleButton ); + } +} + +//----------------------------------------------------------------------------- +CEconItemView* CLoadoutItemOptionsPanel::GetItem( void ) const +{ + return TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, m_eItemSlot ); +} + +CClassLoadoutPanel *g_pClassLoadoutPanel = NULL; +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CClassLoadoutPanel::CClassLoadoutPanel( vgui::Panel *parent ) + : CBaseLoadoutPanel( parent, "class_loadout_panel" ) + , m_pItemOptionPanelKVs( NULL ) +{ + m_iCurrentClassIndex = TF_CLASS_UNDEFINED; + m_iCurrentTeamIndex = TF_TEAM_RED; + m_iCurrentSlotIndex = -1; + m_pPlayerModelPanel = NULL; + m_pSelectionPanel = NULL; + m_pTauntHintLabel = NULL; + m_pTauntLabel = NULL; + m_pTauntCaratLabel = NULL; + m_pPassiveAttribsLabel = NULL; + m_pLoadoutPresetPanel = NULL; + m_pPresetsExplanationPopup = NULL; + m_pTauntsExplanationPopup = NULL; + m_pBuildablesButton = NULL; + + m_pCharacterLoadoutButton = NULL; + m_pTauntLoadoutButton = NULL; + + m_bInTauntLoadoutMode = false; + + g_pClassLoadoutPanel = this; + + m_pItemOptionPanel = new CLoadoutItemOptionsPanel( this, "ItemOptionsPanel" ); +} + +CClassLoadoutPanel::~CClassLoadoutPanel() +{ + if ( m_pItemOptionPanelKVs ) + { + m_pItemOptionPanelKVs->deleteThis(); + m_pItemOptionPanelKVs = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + LoadControlSettings( "Resource/UI/ClassLoadoutPanel.res" ); + + BaseClass::ApplySchemeSettings( pScheme ); + + m_pPlayerModelPanel = dynamic_cast<CTFPlayerModelPanel*>( FindChildByName("classmodelpanel") ); + m_pTauntHintLabel = dynamic_cast<vgui::Label*>( FindChildByName("TauntHintLabel") ); + m_pTauntLabel = dynamic_cast<CExLabel*>( FindChildByName("TauntLabel") ); + m_pTauntCaratLabel = dynamic_cast<CExLabel*>( FindChildByName("TauntCaratLabel") ); + m_pBuildablesButton = dynamic_cast<CExButton*>( FindChildByName("BuildablesButton") ); + m_pCharacterLoadoutButton = dynamic_cast<CExImageButton*>( FindChildByName("CharacterLoadoutButton") ); + m_pTauntLoadoutButton = dynamic_cast<CExImageButton*>( FindChildByName("TauntLoadoutButton") ); + m_pPassiveAttribsLabel = dynamic_cast<CExLabel*>( FindChildByName("PassiveAttribsLabel") ); + m_pLoadoutPresetPanel = dynamic_cast<CLoadoutPresetPanel*>( FindChildByName( "loadout_preset_panel" ) ); + m_pPresetsExplanationPopup = dynamic_cast<CExplanationPopup*>( FindChildByName( "PresetsExplanation" ) ); + m_pTauntsExplanationPopup = dynamic_cast<CExplanationPopup*>( FindChildByName( "TauntsExplanation" ) ); + m_pTopLinePanel = FindChildByName( "TopLine" ); + if ( m_pPassiveAttribsLabel ) + { + m_pPassiveAttribsLabel->SetMouseInputEnabled( false ); + } + + m_pMouseOverTooltip->SetPositioningStrategy( IPTTP_BOTTOM_SIDE ); + + m_aDefaultColors[LOADED][FG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDefaultColorFg", Color( 255, 255, 255, 255 ) ); + m_aDefaultColors[LOADED][FG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetArmedColorFg", Color( 255, 255, 255, 255 ) ); + m_aDefaultColors[LOADED][FG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDepressedColorFg", Color( 255, 255, 255, 255 ) ); + + m_aDefaultColors[LOADED][BG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDefaultColorBg", Color( 255, 255, 255, 255 ) ); + m_aDefaultColors[LOADED][BG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetArmedColorBg", Color( 255, 255, 255, 255 ) ); + m_aDefaultColors[LOADED][BG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDepressedColorBg", Color( 255, 255, 255, 255 ) ); + + m_aDefaultColors[NOTLOADED][FG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.TextColor", Color( 255, 255, 255, 255 ) ); + m_aDefaultColors[NOTLOADED][FG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.ArmedTextColor", Color( 255, 255, 255, 255 ) ); + m_aDefaultColors[NOTLOADED][FG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.DepressedTextColor", Color( 255, 255, 255, 255 ) ); + + m_aDefaultColors[NOTLOADED][BG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.BgColor", Color( 255, 255, 255, 255 ) ); + m_aDefaultColors[NOTLOADED][BG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.ArmedBgColor", Color( 255, 255, 255, 255 ) ); + m_aDefaultColors[NOTLOADED][BG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.DepressedBgColor", Color( 255, 255, 255, 255 ) ); +} + + +void CClassLoadoutPanel::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + KeyValues *pItemKV = inResourceData->FindKey( "itemoptionpanels_kv" ); + if ( pItemKV ) + { + if ( m_pItemOptionPanelKVs ) + { + m_pItemOptionPanelKVs->deleteThis(); + } + m_pItemOptionPanelKVs = new KeyValues("itemoptionpanels_kv"); + pItemKV->CopySubkeys( m_pItemOptionPanelKVs ); + } + +#ifdef STAGING_ONLY + // PDA Panels + if ( m_pItemModelPanels.Count() > LOADOUT_POSITION_PDA_ADDON2 ) + { + for ( int i = LOADOUT_POSITION_PDA_ADDON1; i <= LOADOUT_POSITION_PDA_ADDON2; i++ ) + { + int wide = m_pItemModelPanels[i]->GetWide(); + int tall = m_pItemModelPanels[i]->GetTall(); + + m_pItemModelPanels[i]->SetSize( wide / 2, tall / 2 ); + m_pItemModelPanels[i]->InvalidateLayout(); + } + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::PerformLayout( void ) +{ + BaseClass::PerformLayout(); + + // This is disabled by default in res file. IF we turn it on again, uncomment this. + /*if ( m_pPassiveAttribsLabel ) + { + m_pPassiveAttribsLabel->SetVisible( !m_bInTauntLoadoutMode ); + }*/ + + if ( m_pTauntHintLabel ) + { + m_pTauntHintLabel->SetVisible( m_bInTauntLoadoutMode ); + + const char *key = engine->Key_LookupBinding( "taunt" ); + if ( !key ) + { + key = "< not bound >"; + } + SetDialogVariable( "taunt", key ); + } + + if ( m_pTauntLabel ) + { + m_pTauntLabel->SetVisible( m_bInTauntLoadoutMode ); + } + if ( m_pTauntCaratLabel ) + { + m_pTauntCaratLabel->SetVisible( m_bInTauntLoadoutMode ); + } + if ( m_pCharacterLoadoutButton ) + { + UpdatePageButtonColor( m_pCharacterLoadoutButton, !m_bInTauntLoadoutMode ); + } + if ( m_pTauntLoadoutButton ) + { + UpdatePageButtonColor( m_pTauntLoadoutButton, m_bInTauntLoadoutMode ); + } + + FOR_EACH_VEC( m_vecItemOptionButtons, i ) + { + m_vecItemOptionButtons[i]->SetVisible( false ); + } + + for ( int i = 0; i < m_pItemModelPanels.Count(); i++ ) + { + // Viewing a class loadout. Layout the buttons & the class image. + if ( i >= NUM_ITEM_PANELS_IN_LOADOUT ) + { + m_pItemModelPanels[i]->SetVisible( false ); + continue; + } + + int iButtonPos = 0; + if ( m_iCurrentClassIndex != TF_CLASS_UNDEFINED ) + { + iButtonPos = g_VisibleLoadoutSlotsPerClass[m_iCurrentClassIndex]->m_iPos[i]; + } + + bool bIsVisible = false; + if ( iButtonPos > 0 ) + { + bIsVisible = m_bInTauntLoadoutMode ? IsTauntPanelPosition( iButtonPos ) : !IsTauntPanelPosition( iButtonPos ); + } + m_pItemModelPanels[i]->SetVisible( bIsVisible ); + + if ( bIsVisible ) + { + if ( m_bInTauntLoadoutMode ) + { + iButtonPos -= g_VisibleLoadoutSlotsPerClass[m_iCurrentClassIndex]->m_iPos[LOADOUT_POSITION_TAUNT]; + } + else + { + iButtonPos--; + } + +#ifdef STAGING_ONLY + // Override for the PDA AddOnSlots + if ( i == LOADOUT_POSITION_PDA_ADDON1 ) + { + int iYPos, iXPos; + m_pItemModelPanels[ LOADOUT_POSITION_PDA ]->GetPos( iXPos, iYPos ); + int iWide, iTall; + m_pItemModelPanels[ LOADOUT_POSITION_PDA ]->GetSize( iWide, iTall ); + + m_pItemModelPanels[i]->SetPos( iXPos + iWide + XRES(1), iYPos ); + m_pItemModelPanels[i]->SetSize( iWide / 2.1, iTall / 2.1 ); + continue; + } + else if ( i == LOADOUT_POSITION_PDA_ADDON2 ) + { + int iYPos, iXPos; + m_pItemModelPanels[LOADOUT_POSITION_PDA]->GetPos( iXPos, iYPos ); + int iWide, iTall; + m_pItemModelPanels[LOADOUT_POSITION_PDA]->GetSize( iWide, iTall ); + + m_pItemModelPanels[i]->SetPos( iXPos + iWide + XRES(1), iYPos + iTall - (iTall / 2.1 ) ); + m_pItemModelPanels[i]->SetSize( iWide / 2.1, iTall / 2.1 ); + continue; + } +#endif + + m_pItemModelPanels[i]->SetNoItemText( ItemSystem()->GetItemSchema()->GetLoadoutStringsForDisplay( EEquipType_t::EQUIP_TYPE_CLASS )[i] ); + + int iCenter = GetWide() * 0.5; + int iColumnHeight = 4; + int iColumn = iButtonPos / iColumnHeight; + int iYButtonPos = iButtonPos % iColumnHeight; + + int iOffset = iColumn == 0 ? m_iItemXPosOffcenterA : m_iItemXPosOffcenterB + ((iColumn - 1) * 200); + int iXPos = iCenter + iOffset; + int iYPos = m_iItemYPos + (m_iItemYDelta * iYButtonPos); + m_pItemModelPanels[i]->SetPos( iXPos, iYPos ); + + // Update position and visibility of the item option buttons + if ( i < m_vecItemOptionButtons.Count() ) + { + // Place the button just inside the item model panel + CExButton* pItemOptionsPanel = m_vecItemOptionButtons[iButtonPos]; + int iButtonWide = m_pItemModelPanels[i]->GetWide(); + int iMyWide = pItemOptionsPanel->GetWide(); + int iOptionsXPos = iColumn == 0 + ? iXPos + iButtonWide - iMyWide + : iXPos; + pItemOptionsPanel->SetPos( iOptionsXPos, iYPos ); + + CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, i ); + // Enable or disable the item options button for this item model panel + pItemOptionsPanel->SetVisible( !m_bInTauntLoadoutMode && AnyOptionsAvailableForItem( pItemData ) ); + pItemOptionsPanel->SetCommand( CFmtStr( "options%d", i ) ); + } + } + + m_pItemModelPanels[ i ]->SetSelected( false ); + } + + if ( m_pLoadoutPresetPanel ) + { + m_pLoadoutPresetPanel->SetPos( ( ScreenWidth() - m_pLoadoutPresetPanel->GetWide() ) / 2, m_iItemYPos ); + } + + LinkModelPanelControllerNavigation( true ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::OnKeyCodePressed( vgui::KeyCode code ) +{ + // See if the preset control uses this key + if( m_pLoadoutPresetPanel->HandlePresetKeyPressed( code ) ) + { + return; + } + + ButtonCode_t nButtonCode = GetBaseButtonCode( code ); + + if (nButtonCode == KEY_XBUTTON_LEFT || + nButtonCode == KEY_XSTICK1_LEFT || + nButtonCode == KEY_XSTICK2_LEFT || + nButtonCode == STEAMCONTROLLER_DPAD_LEFT || + code == KEY_LEFT || + nButtonCode == KEY_XBUTTON_RIGHT || + nButtonCode == KEY_XSTICK1_RIGHT || + nButtonCode == KEY_XSTICK2_RIGHT || + nButtonCode == STEAMCONTROLLER_DPAD_RIGHT || + code == KEY_RIGHT || + nButtonCode == KEY_XBUTTON_UP || + nButtonCode == KEY_XSTICK1_UP || + nButtonCode == KEY_XSTICK2_UP || + nButtonCode == STEAMCONTROLLER_DPAD_UP || + code == KEY_UP || + nButtonCode == KEY_XBUTTON_DOWN || + nButtonCode == KEY_XSTICK1_DOWN || + nButtonCode == KEY_XSTICK2_DOWN || + nButtonCode == STEAMCONTROLLER_DPAD_DOWN || + code == KEY_DOWN ) + { + // just eat all navigation keys so we don't + // end up with undesirable navigation behavior bubbling from + // one item model panel to another + } + else if( nButtonCode == KEY_XBUTTON_A || code == KEY_ENTER || nButtonCode == STEAMCONTROLLER_A ) + { + // show the current loadout slot + int nSelected = GetFirstSelectedItemIndex( true ); + if( nSelected != -1 ) + { + OnCommand( VarArgs("change%d", nSelected ) ); + } + } + else + { + BaseClass::OnKeyCodePressed( code ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::OnNavigateTo( const char* panelName ) +{ + CItemModelPanel *pChild = dynamic_cast<CItemModelPanel *>( FindChildByName( panelName ) ); + if( !pChild ) + return; + + pChild->SetSelected( true ); + SetBorderForItem( pChild, false ); + pChild->RequestFocus(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::OnNavigateFrom( const char* panelName ) +{ + CItemModelPanel *pChild = dynamic_cast<CItemModelPanel *>( FindChildByName( panelName ) ); + if( !pChild ) + return; + + pChild->SetSelected( false ); + SetBorderForItem( pChild, false ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::OnShowPanel( bool bVisible, bool bReturningFromArmory ) +{ + if ( bVisible ) + { + // always start in character loadout page + SetLoadoutPage( CHARACTER_LOADOUT_PAGE ); + + if ( m_pSelectionPanel ) + { + m_pSelectionPanel->SetVisible( false ); + m_pSelectionPanel->MarkForDeletion(); + m_pSelectionPanel = NULL; + } + + m_iCurrentSlotIndex = TF_WPN_TYPE_PRIMARY; + if( m_pItemModelPanels.Count() && m_pItemModelPanels[0] ) + { + m_pItemModelPanels[0]->SetSelected( true ); + SetBorderForItem( m_pItemModelPanels[0], false ); + } + + m_bLoadoutHasChanged = false; + + if ( tf_show_preset_explanation_in_class_loadout.GetBool() && m_pPresetsExplanationPopup ) + { + m_pPresetsExplanationPopup->Popup(); + tf_show_preset_explanation_in_class_loadout.SetValue( 0 ); + } + else if ( tf_show_taunt_explanation_in_class_loadout.GetBool() && m_pTauntsExplanationPopup ) + { + m_pTauntsExplanationPopup->Popup(); + tf_show_taunt_explanation_in_class_loadout.SetValue( 0 ); + } + + ClearItemOptionsMenu(); + } + else + { + if ( m_pPlayerModelPanel ) + { + m_pPlayerModelPanel->ClearCarriedItems(); + } + } +} +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::PostShowPanel( bool bVisible ) +{ + if ( bVisible ) + { + if ( m_pPlayerModelPanel ) + { + m_pPlayerModelPanel->SetVisible( true ); + } + + if ( m_pBuildablesButton ) + { + m_pBuildablesButton->SetVisible( m_iCurrentClassIndex == TF_CLASS_ENGINEER ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::SetClass( int iClass ) +{ + m_iCurrentClassIndex = iClass; + + if ( m_pLoadoutPresetPanel ) + { + m_pLoadoutPresetPanel->SetClass( m_iCurrentClassIndex ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::SetTeam( int iTeam ) +{ + Assert( IsValidTFTeam( iTeam ) ); + m_iCurrentTeamIndex = iTeam; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CClassLoadoutPanel::GetNumRelevantSlots() const +{ + return m_pItemModelPanels.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconItemView *CClassLoadoutPanel::GetItemInSlot( int iSlot ) +{ + if( iSlot >= 0 && iSlot < m_pItemModelPanels.Count() ) + { + return m_pItemModelPanels[iSlot]->GetItem(); + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::FireGameEvent( IGameEvent *event ) +{ + // If we're not visible, ignore all events + if ( !IsVisible() ) + return; + + BaseClass::FireGameEvent( event ); + + // We need to update ourselves after the base has done it, so our item models have been updated + const char *type = event->GetName(); + if ( Q_strcmp( "inventory_updated", type ) == 0 ) + { + if ( m_pPlayerModelPanel ) + { + m_pPlayerModelPanel->HoldItemInSlot( m_iCurrentSlotIndex ); + } + } +} + +void CClassLoadoutPanel::AddNewItemPanel( int iPanelIndex ) +{ + BaseClass::AddNewItemPanel( iPanelIndex ); + + m_vecItemOptionButtons[ m_vecItemOptionButtons.AddToTail() ] = new CExButton( this, + CFmtStr( "item_options_button%d", iPanelIndex ), + "+", + this, + CFmtStr( "options%d", iPanelIndex ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::UpdateModelPanels( void ) +{ + // Search for a Robot Costume + bool bIsRobot = false; + static CSchemaAttributeDefHandle pAttrDef_PlayerRobot( "appear as mvm robot" ); + // For now, fill them out with the local player's currently wielded items + for ( int i = 0; i < m_pItemModelPanels.Count(); i++ ) + { + CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, i ); + if ( !pItemData ) + continue; + if ( FindAttribute( pItemData, pAttrDef_PlayerRobot ) ) + { + bIsRobot = true; + break; + } + } + + // We're showing the loadout for a specific class. + TFPlayerClassData_t *pData = GetPlayerClassData( m_iCurrentClassIndex ); + if ( m_pPlayerModelPanel ) + { + m_pPlayerModelPanel->ClearCarriedItems(); + m_pPlayerModelPanel->SetToPlayerClass( m_iCurrentClassIndex, bIsRobot ); + m_pPlayerModelPanel->SetTeam( m_iCurrentTeamIndex ); + } + + // For now, fill them out with the local player's currently wielded items + for ( int i = 0; i < m_pItemModelPanels.Count(); i++ ) + { + CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, i ); + m_pItemModelPanels[i]->SetItem( pItemData ); + m_pItemModelPanels[i]->SetShowQuantity( true ); + m_pItemModelPanels[i]->SetSelected( false ); + SetBorderForItem( m_pItemModelPanels[i], false ); + + if ( m_pPlayerModelPanel && pItemData && pItemData->IsValid() ) + { + m_pPlayerModelPanel->AddCarriedItem( pItemData ); + } + } + + if ( m_pPlayerModelPanel ) + { + m_pPlayerModelPanel->HoldItemInSlot( m_iCurrentSlotIndex ); + } + + SetDialogVariable( "loadoutclass", g_pVGuiLocalize->Find( pData->m_szLocalizableName ) ); + + UpdatePassiveAttributes(); + + // Now layout again to position our item buttons + InvalidateLayout(); + + if ( m_pItemOptionPanel->IsVisible() ) + { + m_pItemOptionPanel->UpdateItemOptionsUI(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::OnItemPanelMouseReleased( vgui::Panel *panel ) +{ + CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel ); + + if ( pItemPanel && IsVisible() ) + { + for ( int i = 0; i < m_pItemModelPanels.Count(); i++ ) + { + if ( m_pItemModelPanels[i] == pItemPanel ) + { + OnCommand( VarArgs("change%d", i) ); + return; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::OnSelectionReturned( KeyValues *data ) +{ + if ( data ) + { + uint64 ulIndex = data->GetUint64( "itemindex", INVALID_ITEM_ID ); + + // ulIndex implies do nothing (escape key) + if ( ulIndex != 0 ) + { + TFInventoryManager()->EquipItemInLoadout( m_iCurrentClassIndex, m_iCurrentSlotIndex, ulIndex ); + + m_bLoadoutHasChanged = true; + + UpdateModelPanels(); + + // Send the preset panel a msg so it can save the change + KeyValues *pLoadoutChangedMsg = new KeyValues( "LoadoutChanged" ); + pLoadoutChangedMsg->SetInt( "slot", m_iCurrentSlotIndex ); + pLoadoutChangedMsg->SetUint64( "itemid", ulIndex ); + PostMessage( m_pLoadoutPresetPanel, pLoadoutChangedMsg ); + } + } + + PostMessage( GetParent(), new KeyValues("SelectionEnded") ); + + // It'll have deleted itself, so we don't need to clean it up + m_pSelectionPanel = NULL; + OnCancelSelection(); + + // find the selected item and give it the focus + CItemModelPanel *pSelection = GetFirstSelectedItemModelPanel( true ); + if( !pSelection ) + { + m_pItemModelPanels[0]->SetSelected( true ); + pSelection = m_pItemModelPanels[0]; + } + + pSelection->RequestFocus(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::OnCancelSelection( void ) +{ + if ( m_pSelectionPanel ) + { + m_pSelectionPanel->SetVisible( false ); + m_pSelectionPanel->MarkForDeletion(); + m_pSelectionPanel = NULL; + } + + if ( m_pPlayerModelPanel ) + { + m_pPlayerModelPanel->SetVisible( true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::RespawnPlayer() +{ + if ( tf_respawn_on_loadoutchanges.GetBool() ) + { + // Tell the GC to tell server that we should respawn if we're in a respawn room + GCSDK::CGCMsg< MsgGCEmpty_t > msg( k_EMsgGCRespawnPostLoadoutChange ); + GCClientSystem()->BSendMessage( msg ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Apply KVs to the item option buttons +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::ApplyKVsToItemPanels( void ) +{ + BaseClass::ApplyKVsToItemPanels(); + + if ( m_pItemOptionPanelKVs ) + { + for ( int i = 0; i < m_vecItemOptionButtons.Count(); i++ ) + { + m_vecItemOptionButtons[i]->ApplySettings( m_pItemOptionPanelKVs ); + m_vecItemOptionButtons[i]->InvalidateLayout(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::OnClosing( void ) +{ + if ( m_pPlayerModelPanel ) + { + m_pPlayerModelPanel->ClearCarriedItems(); + } + + if ( m_bLoadoutHasChanged ) + { + RespawnPlayer(); + + m_bLoadoutHasChanged = false; + } +} + +extern const char *g_szItemBorders[AE_MAX_TYPES][5]; +extern ConVar cl_showbackpackrarities; +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::SetBorderForItem( CItemModelPanel *pItemPanel, bool bMouseOver ) +{ + if ( !pItemPanel ) + return; + + const char *pszBorder = NULL; + + if ( pItemPanel->IsGreyedOut() ) + { + pszBorder = "EconItemBorder"; + } + else + { + int iRarity = 0; + if ( pItemPanel->HasItem() && cl_showbackpackrarities.GetBool() ) + { + iRarity = pItemPanel->GetItem()->GetItemQuality(); + + uint8 nRarity = pItemPanel->GetItem()->GetItemDefinition()->GetRarity(); + if ( ( nRarity != k_unItemRarity_Any ) && ( iRarity != AE_SELFMADE ) ) + { + // translate this quality to rarity + iRarity = nRarity + AE_RARITY_DEFAULT; + } + + if ( iRarity > 0 ) + { + if ( bMouseOver || pItemPanel->IsSelected() ) + { + pszBorder = g_szItemBorders[iRarity][1]; + } + else + { + pszBorder = g_szItemBorders[iRarity][0]; + } + } + } + + + if ( iRarity == 0 ) + { + if ( bMouseOver || pItemPanel->IsSelected() ) + { + pszBorder = "LoadoutItemMouseOverBorder"; + } + else + { + pszBorder = "EconItemBorder"; + } + } + } + + vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); + pItemPanel->SetBorder( pScheme->GetBorder( pszBorder ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Clear the item options menu and reset the button that summoned it +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::ClearItemOptionsMenu( void ) +{ + SetOptionsButtonText( m_pItemOptionPanel->GetItemSlot(), "+" ); + m_pItemOptionPanel->SetItemSlot( LOADOUT_POSITION_INVALID, m_iCurrentClassIndex ); + m_pItemOptionPanel->SetVisible( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: Safely set the text for a button +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::SetOptionsButtonText( int nIndex, const char* pszText ) +{ + if ( nIndex >= 0 && nIndex < m_vecItemOptionButtons.Count() ) + { + m_vecItemOptionButtons[ m_pItemOptionPanel->GetItemSlot() ]->SetText( pszText ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Return if the passed in item has any options +//----------------------------------------------------------------------------- +bool CClassLoadoutPanel::AnyOptionsAvailableForItem( const CEconItemView *pItem ) +{ + if ( !pItem ) + return false; + + // Styles! + if ( pItem->GetStaticData()->GetNumSelectableStyles() > 1 ) + return true; + + // Unusual particle effect! For Cosmetics only + static CSchemaAttributeDefHandle pAttrDef_AttachParticleEffect( "attach particle effect" ); + if ( pItem->FindAttribute( pAttrDef_AttachParticleEffect ) && pItem->GetItemDefinition()->GetLoadoutSlot( 0 ) >= LOADOUT_POSITION_HEAD ) + return true; + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::SetLoadoutPage( classloadoutpage_t loadoutPage ) +{ + ClearItemOptionsMenu(); + switch ( loadoutPage ) + { + case CHARACTER_LOADOUT_PAGE: + { + m_bInTauntLoadoutMode = false; + } + break; + case TAUNT_LOADOUT_PAGE: + { + m_bInTauntLoadoutMode = true; + } + break; + default: + { + // Unhandled loadout page + Assert( 0 ); + } + } + InvalidateLayout(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::OnCommand( const char *command ) +{ + if ( FStrEq( command, "characterloadout" ) ) + { + SetLoadoutPage( CHARACTER_LOADOUT_PAGE ); + return; + } + else if ( FStrEq( command, "tauntloadout" ) ) + { + SetLoadoutPage( TAUNT_LOADOUT_PAGE ); + return; + } + else if ( !V_strnicmp( command, "change", 6 ) ) + { + const char *pszNum = command+6; + if ( pszNum && pszNum[0] ) + { + int iSlot = atoi(pszNum); + if ( iSlot >= 0 && iSlot < CLASS_LOADOUT_POSITION_COUNT && m_iCurrentClassIndex != TF_CLASS_UNDEFINED ) + { + if ( m_iCurrentSlotIndex != iSlot ) + { + m_iCurrentSlotIndex = iSlot; + } + + // Create the selection screen. It removes itself on close. + m_pSelectionPanel = new CEquipSlotItemSelectionPanel( this, m_iCurrentClassIndex, iSlot ); + m_pSelectionPanel->ShowPanel( 0, true ); + + if ( m_pPlayerModelPanel ) + { + m_pPlayerModelPanel->SetVisible( false ); + } + + ClearItemOptionsMenu(); + + PostMessage( GetParent(), new KeyValues("SelectionStarted") ); + } + } + + return; + } + else if ( !V_strnicmp( command, "options", 7 ) ) + { + const char *pszNum = command + 7; + if( pszNum && pszNum[0] ) + { + int iSlot = atoi( pszNum ); + //iSlot = g_VisibleLoadoutSlotsPerClass[m_iCurrentClassIndex]->m_iPos[iSlot - 1]; + if ( iSlot >= 0 && iSlot < m_vecItemOptionButtons.Count() && m_iCurrentClassIndex != TF_CLASS_UNDEFINED ) + { + // Change the button we're coming from to be a "+" + SetOptionsButtonText( m_pItemOptionPanel->GetItemSlot(), "+" ); + + // Update the current slot index for callback from the setstyle button. + // It will send us a message to change the item the player model is holding + // and we need this to be updated for that. + m_iCurrentSlotIndex = iSlot; + + + // Did they just toggle? + if ( m_pItemOptionPanel->GetItemSlot() == iSlot ) + { + m_pItemOptionPanel->SetVisible( !m_pItemOptionPanel->IsVisible() ); + } + else + { + // Set the options panel to have the data for this slot + m_pItemOptionPanel->SetItemSlot( (loadout_positions_t)iSlot, m_iCurrentClassIndex ); + m_pItemOptionPanel->SetVisible( true ); + // Figure out if this is on the left or right + int iColumnHeight = 4; + int iColumn = iSlot / iColumnHeight; + PinCorner_e myCornerToPin = iColumn == 0 ? PIN_TOPLEFT : PIN_TOPRIGHT; + PinCorner_e siblingCornerPinTo = iColumn == 0 ? PIN_TOPRIGHT : PIN_TOPLEFT; + // Pin to the appropriate side + int iButtonPos = g_VisibleLoadoutSlotsPerClass[m_iCurrentClassIndex]->m_iPos[ iSlot ] - 1; + m_pItemOptionPanel->PinToSibling( m_vecItemOptionButtons[ iButtonPos ]->GetName(), myCornerToPin, siblingCornerPinTo ); + m_pItemOptionPanel->UpdateItemOptionsUI(); + } + + // Change the button we're going to to be "-" if we're visible, "+" if we're not + SetOptionsButtonText( m_pItemOptionPanel->GetItemSlot(), m_pItemOptionPanel->IsVisible() ? "-" : "+" ); + } + return; + } + } + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::OnMessage( const KeyValues* pParams, vgui::VPANEL hFromPanel ) +{ + BaseClass::OnMessage( pParams, hFromPanel ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +struct passive_attrib_to_print_t +{ + const CEconItemAttributeDefinition *m_pAttrDef; + attrib_value_t m_value; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CAttributeIterator_AddPassiveAttribsToPassiveList : public CEconItemSpecificAttributeIterator +{ +public: + CAttributeIterator_AddPassiveAttribsToPassiveList( CUtlVector<passive_attrib_to_print_t> *pList, bool bForceAdd ) + : m_pList( pList ) + , m_bForceAdd( bForceAdd ) + { + Assert( m_pList ); + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, attrib_value_t value ) + { + Assert( pAttrDef ); + + if ( pAttrDef->IsHidden() ) + return true; + + if ( !m_bForceAdd ) + { + const char *pDesc = pAttrDef->GetArmoryDescString(); + if ( !pDesc || !pDesc[0] ) + return true; + + // If we have the "on_wearer" key, we're a passive attribute + if ( !Q_stristr(pDesc, "on_wearer") ) + return true; + } + + // Now see if we're already in the list + FOR_EACH_VEC( (*m_pList), i ) + { + passive_attrib_to_print_t& passiveAttr = (*m_pList)[i]; + + Assert( passiveAttr.m_pAttrDef ); + + // We match if our class is the same -- this is a case-sensitive compare! + if ( Q_strcmp( passiveAttr.m_pAttrDef->GetAttributeClass(), pAttrDef->GetAttributeClass() ) ) + continue; + + // We've found a matching attribute. Collate our values and stomp over the earlier value. + passiveAttr.m_value = CollateAttributeValues( passiveAttr.m_pAttrDef, passiveAttr.m_value, pAttrDef, value ); + + return true; + } + + // We didn't find it. Add it to the list. + passive_attrib_to_print_t newPassiveAttr = { pAttrDef, value }; + m_pList->AddToTail( newPassiveAttr ); + + return true; + } + + // Other types are ignored. + +private: + CUtlVector<passive_attrib_to_print_t> *m_pList; + bool m_bForceAdd; +}; + +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::UpdatePassiveAttributes( void ) +{ + if ( !m_pPassiveAttribsLabel ) + return; + + // We build a list of attributes & associated values by looping through all equipped items. + // This way we can identify & collate attributes based on the same definition index. + CUtlVector<passive_attrib_to_print_t> vecAttribsToPrint; + + // Loop through all equipped items + for ( int i = 0; i < m_pItemModelPanels.Count(); i++ ) + { + CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, i ); + if ( pItemData && pItemData->IsValid() ) + { + CAttributeIterator_AddPassiveAttribsToPassiveList attrItPassives( &vecAttribsToPrint, false ); + pItemData->IterateAttributes( &attrItPassives ); + } + } + + // Then add any set bonuses + if ( steamapicontext && steamapicontext->SteamUser() ) + { + CSteamID localSteamID = steamapicontext->SteamUser()->GetSteamID(); + CUtlVector<const CEconItemSetDefinition *> pActiveSets; + TFInventoryManager()->GetActiveSets( &pActiveSets, localSteamID, m_iCurrentClassIndex ); + + FOR_EACH_VEC( pActiveSets, set ) + { + CAttributeIterator_AddPassiveAttribsToPassiveList attrItSetPassives( &vecAttribsToPrint, true ); + pActiveSets[set]->IterateAttributes( &attrItSetPassives ); + } + } + + // Now build the text + wchar_t wszPassiveDesc[4096]; + wszPassiveDesc[0] = '\0'; + m_pPassiveAttribsLabel->GetTextImage()->ClearColorChangeStream(); + + wchar_t *pHeader = g_pVGuiLocalize->Find( "#TF_PassiveAttribs" ); + if ( pHeader ) + { + V_wcscpy_safe( wszPassiveDesc, pHeader ); + V_wcscat_safe( wszPassiveDesc, L"\n" ); + } + + if ( vecAttribsToPrint.Count() ) + { + FOR_EACH_VEC( vecAttribsToPrint, i ) + { + CEconAttributeDescription AttrDesc( GLocalizationProvider(), vecAttribsToPrint[i].m_pAttrDef, vecAttribsToPrint[i].m_value ); + AddAttribPassiveText( AttrDesc, wszPassiveDesc, ARRAYSIZE(wszPassiveDesc) ); + } + } + else + { + vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); + Color col = pScheme->GetColor( GetColorNameForAttribColor( ATTRIB_COL_NEUTRAL ), Color(255,255,255,255) ); + m_pPassiveAttribsLabel->GetTextImage()->AddColorChange( col, Q_wcslen( wszPassiveDesc ) ); + + wchar_t *pNone = g_pVGuiLocalize->Find( "#TF_PassiveAttribs_None" ); + if ( pNone ) + { + V_wcscat_safe( wszPassiveDesc, pNone ); + } + } + + m_pPassiveAttribsLabel->SetText( wszPassiveDesc ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::AddAttribPassiveText( const CEconAttributeDescription& AttrDesc, INOUT_Z_CAP(iNumPassiveChars) wchar_t *out_wszPassiveDesc, int iNumPassiveChars ) +{ + vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); + Assert( pScheme ); + + if ( !AttrDesc.GetDescription().IsEmpty() ) + { + // Insert the color change at the current position + Color col = pScheme->GetColor( GetColorNameForAttribColor( AttrDesc.GetDefaultColor() ), Color(255,255,255,255) ); + m_pPassiveAttribsLabel->GetTextImage()->AddColorChange( col, Q_wcslen( out_wszPassiveDesc ) ); + + // Now append the text of the attribute + V_wcsncat( out_wszPassiveDesc, AttrDesc.GetDescription().Get(), iNumPassiveChars ); + V_wcsncat( out_wszPassiveDesc, L"\n", iNumPassiveChars ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClassLoadoutPanel::UpdatePageButtonColor( CExImageButton *pPageButton, bool bIsActive ) +{ + if ( pPageButton ) + { + int iLoaded = bIsActive ? LOADED : NOTLOADED; + pPageButton->SetDefaultColor( m_aDefaultColors[iLoaded][FG][DEFAULT], m_aDefaultColors[iLoaded][BG][DEFAULT] ); + pPageButton->SetArmedColor( m_aDefaultColors[iLoaded][FG][ARMED], m_aDefaultColors[iLoaded][BG][ARMED] ); + pPageButton->SetDepressedColor( m_aDefaultColors[iLoaded][FG][DEPRESSED], m_aDefaultColors[iLoaded][BG][DEPRESSED] ); + } +} diff --git a/game/client/tf/vgui/class_loadout_panel.h b/game/client/tf/vgui/class_loadout_panel.h new file mode 100644 index 0000000..894b0e6 --- /dev/null +++ b/game/client/tf/vgui/class_loadout_panel.h @@ -0,0 +1,157 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef CLASS_LOADOUT_PANEL_H +#define CLASS_LOADOUT_PANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "base_loadout_panel.h" +#include "tf_playermodelpanel.h" +#include "item_selection_panel.h" +#include <../common/GameUI/cvarslider.h> +#include <vgui/VGUI.h> +#include "vgui_controls/CheckButton.h" + +#define NUM_ITEM_PANELS_IN_LOADOUT CLASS_LOADOUT_POSITION_COUNT + +class CLoadoutPresetPanel; + +class CLoadoutItemOptionsPanel : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CLoadoutItemOptionsPanel, vgui::EditablePanel ); +public: + CLoadoutItemOptionsPanel( Panel *parent, const char *pName ); + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void PerformLayout( void ); + + virtual void OnCommand( const char *command ); + virtual void OnMessage( const KeyValues* pParams, vgui::VPANEL hFromPanel ); + + void SetItemSlot( loadout_positions_t eItemSlot, int iClassIndex ); + loadout_positions_t GetItemSlot() const { return m_eItemSlot; } + void UpdateItemOptionsUI(); + +private: + + void AddControlsParticleEffect( void ) const; + void AddControlsSetStyle( void ) const; + CEconItemView* GetItem( void ) const; + + class vgui::PanelListPanel *m_pListPanel; + CCvarSlider *m_pHatParticleSlider; + CExButton *m_pSetStyleButton; + vgui::CheckButton *m_pHatParticleUseHeadButton; + + int m_iCurrentClassIndex; + loadout_positions_t m_eItemSlot; +}; + + +//----------------------------------------------------------------------------- +// A loadout screen that handles modifying the loadout of a specific class +//----------------------------------------------------------------------------- +class CClassLoadoutPanel : public CBaseLoadoutPanel +{ + DECLARE_CLASS_SIMPLE( CClassLoadoutPanel, CBaseLoadoutPanel ); +public: + CClassLoadoutPanel( vgui::Panel *parent ); + ~CClassLoadoutPanel(); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE; + virtual void PerformLayout( void ); + virtual void FireGameEvent( IGameEvent *event ); + + virtual void AddNewItemPanel( int iPanelIndex ) OVERRIDE; + virtual void UpdateModelPanels( void ); + virtual int GetNumItemPanels( void ) { return NUM_ITEM_PANELS_IN_LOADOUT; }; + virtual void OnShowPanel( bool bVisible, bool bReturningFromArmory ); + virtual void PostShowPanel( bool bVisible ); + virtual void OnKeyCodePressed( vgui::KeyCode code ) OVERRIDE; + virtual void OnNavigateTo( const char* panelName ) OVERRIDE; + virtual void OnNavigateFrom( const char* panelName ) OVERRIDE; + + void SetClass( int iClass ); + void SetTeam( int iTeam ); + + int GetNumRelevantSlots() const; + CEconItemView *GetItemInSlot( int iSlot ); + + MESSAGE_FUNC_PTR( OnItemPanelMouseReleased, "ItemPanelMouseReleased", panel ); + MESSAGE_FUNC_PARAMS( OnSelectionReturned, "SelectionReturned", data ); + MESSAGE_FUNC( OnCancelSelection, "CancelSelection" ); + MESSAGE_FUNC( OnClosing, "Closing" ); + virtual void OnCommand( const char *command ); + virtual void OnMessage( const KeyValues* pParams, vgui::VPANEL hFromPanel ); + + void SetSelectionPanel( CEquipSlotItemSelectionPanel *pPanel ) { m_pSelectionPanel = pPanel; } + void UpdatePassiveAttributes( void ); + + bool IsInSelectionPanel() const { return m_pSelectionPanel != NULL; } + CEquipSlotItemSelectionPanel *GetItemSelectionPanel() { return m_pSelectionPanel; } + + bool IsEditingTauntSlots() const { return m_bInTauntLoadoutMode; } + + enum classloadoutpage_t + { + CHARACTER_LOADOUT_PAGE, + TAUNT_LOADOUT_PAGE + }; + void SetLoadoutPage( classloadoutpage_t loadoutPage ); + +protected: + virtual void SetBorderForItem( CItemModelPanel *pItemPanel, bool bMouseOver ); + void AddAttribPassiveText( const class CEconAttributeDescription& AttrDesc, INOUT_Z_CAP(iNumPassiveChars) wchar_t *out_wszPassiveDesc, int iNumPassiveChars ); + void RespawnPlayer(); + virtual void ApplyKVsToItemPanels( void ) OVERRIDE; + void ClearItemOptionsMenu( void ); + void SetOptionsButtonText( int nIndex, const char* pszText ); + static bool AnyOptionsAvailableForItem( const CEconItemView *pItem ); + + int m_iCurrentClassIndex; + int m_iCurrentTeamIndex; + int m_iCurrentSlotIndex; + bool m_bLoadoutHasChanged; + bool m_bInTauntLoadoutMode; + CTFPlayerModelPanel *m_pPlayerModelPanel; + CEquipSlotItemSelectionPanel *m_pSelectionPanel; + vgui::Label *m_pTauntHintLabel; + CExLabel *m_pTauntLabel; + CExLabel *m_pTauntCaratLabel; + CExLabel *m_pPassiveAttribsLabel; + Panel *m_pTopLinePanel; + + CExButton *m_pBuildablesButton; + CExImageButton *m_pCharacterLoadoutButton; + CExImageButton *m_pTauntLoadoutButton; + + CLoadoutPresetPanel *m_pLoadoutPresetPanel; + + CExplanationPopup *m_pPresetsExplanationPopup; + CExplanationPopup *m_pTauntsExplanationPopup; + + KeyValues *m_pItemOptionPanelKVs; + CUtlVector< CExButton * > m_vecItemOptionButtons; + CLoadoutItemOptionsPanel *m_pItemOptionPanel; + +private: + void UpdatePageButtonColor( CExImageButton *pPageButton, bool bIsActive ); + + enum PageButtonColors_t + { + LOADED = 0, NOTLOADED, + FG = 0, BG, + DEFAULT = 0, ARMED, DEPRESSED + }; + Color m_aDefaultColors[2][2][3]; // [LOADED|NOTLOADED][FG|BG][DEFAULT|ARMED|DEPRESSED] +}; + +extern CClassLoadoutPanel *g_pClassLoadoutPanel; + +#endif // CLASS_LOADOUT_PANEL_H diff --git a/game/client/tf/vgui/collection_crafting_panel.cpp b/game/client/tf/vgui/collection_crafting_panel.cpp new file mode 100644 index 0000000..4794a4c --- /dev/null +++ b/game/client/tf/vgui/collection_crafting_panel.cpp @@ -0,0 +1,829 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "collection_crafting_panel.h" +#include "cdll_client_int.h" +#include "ienginevgui.h" +#include "econ_item_tools.h" +#include "econ_ui.h" +#include <vgui_controls/AnimationController.h> +#include "clientmode_tf.h" +#include "softline.h" +#include "drawing_panel.h" +#include "tf_item_inventory.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCollectionCraftingPanel::CCollectionCraftingPanel( vgui::Panel *parent, CItemModelPanelToolTip* pTooltip ) + : BaseClass( parent, "CollectionCraftingPanel" ) + , m_pKVItemPanels( NULL ) + , m_pModelPanel( NULL ) + , m_bWaitingForGCResponse( false ) + , m_bEnvelopeReadyToSend( false ) + , m_pMouseOverTooltip( pTooltip ) + , m_bShowing( false ) + , m_bShowImmediately( false ) +{ + ListenForGameEvent( "gameui_hidden" ); + + m_pSelectingItemModelPanel = NULL; + + m_pTradeUpContainer = new EditablePanel( this, "TradeUpContainer" ); + m_pInspectPanel = new CTFItemInspectionPanel( this, "NewItemPanel" ); + m_pCosmeticResultItemModelPanel = new CItemModelPanel( m_pInspectPanel, "CosmeticResultItemModelPanel" ); + m_pStampPanel = new ImagePanel( this, "Stamp" ); + m_pStampButton = new CExButton( this, "ApplyStampButton", "" ); + + EditablePanel* pPaperContainer = new EditablePanel( m_pTradeUpContainer, "PaperContainer" ); + + m_pOKButton = new CExButton( pPaperContainer, "OkButton", "" ); + m_pNextItemButton = new CExButton( this, "NextItemButton", "" ); + + m_pDrawingPanel = new CDrawingPanel( this, "drawingpanel" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCollectionCraftingPanel::~CCollectionCraftingPanel( void ) +{ + if ( m_hSelectionPanel ) + { + m_hSelectionPanel->MarkForDeletion(); + } + + if ( m_pKVItemPanels ) + { + m_pKVItemPanels->deleteThis(); + } +} +//----------------------------------------------------------------------------- +void CCollectionCraftingPanel::SetItemPanelCount( ) +{ + // only do this once + if ( m_vecItemContainers.Count() != 0 ) + return; + + const int nNumItems = GetInputItemCount(); + const int nNumOutput = GetOutputItemCount(); + + EditablePanel* pPaperContainer = dynamic_cast<vgui::EditablePanel*>( m_pTradeUpContainer->FindChildByName( "PaperContainer" ) ); + if ( pPaperContainer ) + { + m_vecItemContainers.SetCount( nNumItems ); + FOR_EACH_VEC( m_vecItemContainers, i ) + { + m_vecItemContainers[i] = new EditablePanel( pPaperContainer, "itemcontainer" ); + } + + m_vecOutputItemContainers.SetCount( nNumOutput ); + FOR_EACH_VEC( m_vecOutputItemContainers, i ) + { + m_vecOutputItemContainers[i] = new EditablePanel( pPaperContainer, "itemcontainer" ); + } + } +} +//----------------------------------------------------------------------------- +void CCollectionCraftingPanel::CreateSelectionPanel() +{ + m_hSelectionPanel = new CCollectionCraftingSelectionPanel( this ); +} +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCollectionCraftingPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( GetResFile() ); + + m_pModelPanel = FindControl< CBaseModelPanel >( "ReturnModel" ); + if ( m_pModelPanel ) + { + m_pModelPanel->SetLookAtCamera( false ); + } + + if ( m_pDrawingPanel ) + { + m_pDrawingPanel->SetType( DRAWING_PANEL_TYPE_CRAFTING ); + } + + m_pItemNamePanel = m_pInspectPanel->FindControl< CItemModelPanel >( "ItemName" ); + Assert( m_pItemNamePanel ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCollectionCraftingPanel::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + KeyValues *pItemKV = inResourceData->FindKey( "ItemContainerKV" ); + if ( pItemKV ) + { + if ( m_pKVItemPanels ) + { + m_pKVItemPanels->deleteThis(); + } + m_pKVItemPanels = new KeyValues("ItemContainerKV"); + pItemKV->CopySubkeys( m_pKVItemPanels ); + } + + m_vecImagePanels.Purge(); + m_vecItemPanels.Purge(); + + KeyValues *pBoxTopsKV = inResourceData->FindKey( "BoxTops" ); + if ( pBoxTopsKV ) + { + m_vecBoxTopNames.Purge(); + FOR_EACH_VALUE( pBoxTopsKV, pValue ) + { + m_vecBoxTopNames.AddToTail( pValue->GetString() ); + } + } + Assert( m_vecBoxTopNames.Count() ); + + KeyValues *pStampNames = inResourceData->FindKey( "stampimages" ); + if ( pStampNames ) + { + m_vecStampNames.Purge(); + FOR_EACH_VALUE( pStampNames, pValue ) + { + m_vecStampNames.AddToTail( pValue->GetString() ); + } + } + Assert( m_vecStampNames.Count() ); + + KeyValues *pResulStrings = inResourceData->FindKey( "resultstring" ); + if ( pResulStrings ) + { + m_vecResultStrings.Purge(); + FOR_EACH_VALUE( pResulStrings, pValue ) + { + m_vecResultStrings.AddToTail( pValue->GetString() ); + } + } + Assert( m_vecResultStrings.Count() ); + + KeyValues *pLocalizedPanelNames = inResourceData->FindKey( "localizedpanels" ); + if ( pLocalizedPanelNames ) + { + m_vecLocalizedPanels.Purge(); + FOR_EACH_TRUE_SUBKEY( pLocalizedPanelNames, pValue ) + { + m_vecLocalizedPanels.AddToTail( { pValue->GetString( "panelname" ), pValue->GetBool( "show_for_english", false ) } ); + } + } + + CreateItemPanels(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCollectionCraftingPanel::PerformLayout() +{ + BaseClass::PerformLayout(); + + if ( m_pModelPanel ) + { + m_pModelPanel->SetMDL( "models/player/items/crafting/mannco_crate_tradeup.mdl" ); + } + + FOR_EACH_VEC( m_vecItemContainers, i ) + { + m_vecItemContainers[ i ]->SetPos( m_iButtonsStartX + m_iButtonsStepX * ( i % 5 ) + , m_iButtonsStartY + m_iButtonsStepY * ( i / 5 ) ); + } + + FOR_EACH_VEC( m_vecOutputItemContainers, i ) + { + m_vecOutputItemContainers[i]->SetPos( m_iOutputItemStartX + m_iOutputItemStepX * ( i % 5 ) + , m_iOutputItemStartY + m_iOutputItemStepY * ( i / 5 ) ); + } + + if ( steamapicontext && steamapicontext->SteamApps() ) + { + char uilanguage[ 64 ]; + uilanguage[0] = 0; + engine->GetUILanguage( uilanguage, sizeof( uilanguage ) ); + ELanguage language = PchLanguageToELanguage( uilanguage ); + + FOR_EACH_VEC( m_vecLocalizedPanels, i ) + { + bool bShow = language == k_Lang_English && m_vecLocalizedPanels[ i ].m_bShowForEnglish; + Panel* pPanel = m_pTradeUpContainer->FindChildByName( m_vecLocalizedPanels[ i ].m_strPanel, true ); + if ( pPanel ) + { + pPanel->SetVisible( bShow ); + } + } + } + + UpdateOKButton(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCollectionCraftingPanel::CreateItemPanels() +{ + SetItemPanelCount(); + + m_vecImagePanels.SetCount( m_vecItemContainers.Count() ); + m_vecItemPanels.SetCount( m_vecItemContainers.Count() ); + + FOR_EACH_VEC( m_vecItemContainers, i ) + { + m_vecItemContainers[ i ]->ApplySettings( m_pKVItemPanels ); + m_vecImagePanels[ i ] = m_vecItemContainers[ i ]->FindControl< ImagePanel >( "imagepanel" ); + m_vecItemPanels[ i ] = m_vecItemContainers[ i ]->FindControl< CItemModelPanel >( "itempanel" ); + m_vecItemPanels[ i ]->SetActAsButton( true, true ); + m_vecItemPanels[ i ]->SetTooltip( m_pMouseOverTooltip, "" ); + + CExButton* pButton = m_vecItemContainers[ i ]->FindControl< CExButton >( "BackgroundButton" ); + if ( pButton ) + { + pButton->SetCommand( CFmtStr( "select%d", i ) ); + pButton->AddActionSignalTarget( this ); + } + } + + m_vecOutputImagePanels.SetCount( m_vecOutputItemContainers.Count() ); + m_vecOutputItemPanels.SetCount( m_vecOutputItemContainers.Count() ); + + FOR_EACH_VEC( m_vecOutputItemContainers, i ) + { + m_vecOutputItemContainers[i]->ApplySettings( m_pKVItemPanels ); + m_vecOutputImagePanels[i] = m_vecOutputItemContainers[i]->FindControl< ImagePanel >( "imagepanel" ); + m_vecOutputItemPanels[i] = m_vecOutputItemContainers[i]->FindControl< CItemModelPanel >( "itempanel" ); + m_vecOutputItemPanels[i]->SetTooltip( m_pMouseOverTooltip, "" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCollectionCraftingPanel::OnCommand( const char *command ) +{ + if ( !Q_stricmp( command, "reloadscheme" ) ) + { + InvalidateLayout( false, true ); + return; + } + + + if ( FStrEq( "doneselectingitems", command ) ) + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_LetterStart" ); + m_bEnvelopeReadyToSend = false; + + if ( m_vecStampNames.Count() ) + { + m_pStampPanel->SetImage( m_vecStampNames[ RandomInt( 0, m_vecStampNames.Count() - 1 ) ] ); + } + + return; + } + else if ( FStrEq( "cancel", command ) ) + { + SetVisible( false ); + return; + } + else if ( Q_strnicmp( "select", command, 6 ) == 0 ) + { + SelectPanel( atoi( command + 6 ) ); + return; + } + else if ( FStrEq( "envelopesend", command ) ) + { + GCSDK::CProtoBufMsg<CMsgCraftCollectionUpgrade> msg( k_EMsgGCCraftCollectionUpgrade ); + + // Construct message + FOR_EACH_VEC( m_vecItemPanels, i ) + { + if ( m_vecItemPanels[ i ]->GetItem() == NULL ) + return; + + msg.Body().add_item_id( m_vecItemPanels[ i ]->GetItem()->GetItemID() ); + } + // Send if off + GCClientSystem()->BSendMessage( msg ); + + m_bWaitingForGCResponse = true; + m_nFoundItemID.Purge(); + m_timerResponse.Start( 5.f ); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_LetterSend" ); + return; + } + else if ( FStrEq( "placestamp", command ) ) + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_PlaceStamp" ); + return; + } + else if( Q_strnicmp( "playcratesequence", command, 17 ) == 0 ) + { + m_pModelPanel->SetSequence( atoi( command + 17 ), true ); + return; + } + else if( FStrEq( "itemget", command ) ) + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_ItemRecieved" ); + wchar_t *pszLocalized = NULL; + + // + if ( m_eEconItemOrigin == kEconItemOrigin_FoundInCrate ) + { + pszLocalized = g_pVGuiLocalize->Find( "#NewItemMethod_FoundInCrate" ); + } + else if ( m_vecResultStrings.Count() ) + { + pszLocalized = g_pVGuiLocalize->Find( m_vecResultStrings[ RandomInt( 0, m_vecResultStrings.Count() - 1 ) ] ); + } + + m_pInspectPanel->SetDialogVariable( "resultstring", pszLocalized ); + return; + } + else if( Q_strnicmp( "playsound", command, 9 ) == 0 ) + { + vgui::surface()->PlaySound( command + 10 ); + return; + } + else if( FStrEq( "startexplanation1", command ) ) + { + CExplanationPopup *pPopup = dynamic_cast<CExplanationPopup*>( FindChildByName("StartExplanation") ); + if ( pPopup ) + { + pPopup->Popup(); + } + return; + } + else if( FStrEq( "startexplanation2", command ) ) + { + CExplanationPopup *pPopup = dynamic_cast<CExplanationPopup*>( FindChildByName("SigningExplanation") ); + if ( pPopup ) + { + pPopup->Popup(); + } + return; + } + else if ( FStrEq( "nextitem", command ) ) + { + if ( m_nFoundItemID.Count() > 1 ) + { + // Remove head, reset timer to drop next item + m_nFoundItemID.Remove( 0 ); + m_timerResponse.Start( 5.f ); + m_bShowImmediately = true; + } + } + else if( FStrEq( "reload", command ) ) + { + g_pVGuiLocalize->ReloadLocalizationFiles(); + InvalidateLayout( false, true ); + SetVisible( true ); + return; + } + + BaseClass::OnCommand( command ); +} + +void CCollectionCraftingPanel::SelectPanel( int nPanel ) +{ + m_pSelectingItemModelPanel = m_vecItemPanels[ nPanel ]; + + CCopyableUtlVector< const CEconItemView* > vecCurrentItems; + FOR_EACH_VEC( m_vecItemPanels, i ) + { + if ( m_vecItemPanels[ i ]->GetItem() ) + { + vecCurrentItems.AddToTail( m_vecItemPanels[ i ]->GetItem() ); + } + } + + if ( !m_hSelectionPanel ) + { + CreateSelectionPanel(); + m_hSelectionPanel->SetAutoDelete( false ); + } + + if ( m_hSelectionPanel ) + { + // Clicked on an item in the crafting area. Open up the selection panel. + m_hSelectionPanel->SetCorrespondingItems( vecCurrentItems ); + m_hSelectionPanel->ShowDuplicateCounts( true ); + m_hSelectionPanel->ShowPanel( 0, true ); + m_hSelectionPanel->SetCaller( this ); + m_hSelectionPanel->SetZPos( GetZPos() + 1 ); + } +} + +void CCollectionCraftingPanel::FireGameEvent( IGameEvent *event ) +{ + if ( FStrEq( event->GetName(), "gameui_hidden" ) ) + { + SetVisible( false ); + return; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCollectionCraftingPanel::OnItemPanelMousePressed( vgui::Panel *panel ) +{ + CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel ); + + if ( pItemPanel && IsVisible() && !pItemPanel->IsGreyedOut() ) + { + auto idx = m_vecItemPanels.Find( pItemPanel ); + if ( idx != m_vecItemPanels.InvalidIndex() ) + { + OnCommand( CFmtStr( "select%d", idx ) ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCollectionCraftingPanel::OnSelectionReturned( KeyValues *data ) +{ + Assert( m_pSelectingItemModelPanel ); + + m_hSelectionPanel->SetVisible( false ); + + if ( data && m_pSelectingItemModelPanel ) + { + uint64 ulIndex = data->GetUint64( "itemindex", INVALID_ITEM_ID ); + + CEconItemView* pSelectedItem = InventoryManager()->GetLocalInventory()->GetInventoryItemByItemID( ulIndex ); + + if ( pSelectedItem ) + { + vgui::surface()->PlaySound( "ui/trade_up_apply_sticker.wav" ); + } + + auto idx = m_vecItemPanels.Find( m_pSelectingItemModelPanel ); + SetItem( pSelectedItem, idx ); + } + + m_pSelectingItemModelPanel = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCollectionCraftingPanel::UpdateOKButton() +{ + bool bOKEnabled = true; + FOR_EACH_VEC( m_vecItemPanels, i ) + { + bOKEnabled &= m_vecItemPanels[ i ]->GetItem() != NULL; + } + + m_pOKButton->SetEnabled( bOKEnabled ); + + if ( bOKEnabled ) + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_pOKButton->GetParent(), "CollectionCrafting_OKBlink" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCollectionCraftingPanel::SetVisible( bool bVisible ) +{ + BaseClass::SetVisible( bVisible ); + + if ( bVisible ) + { + m_pInspectPanel->SetVisible( false ); + + EditablePanel* pDimmer = FindControl< EditablePanel >( "Dimmer" ); + if ( pDimmer ) + { + pDimmer->SetAlpha( 0 ); + } + + EditablePanel* pBG = FindControl< EditablePanel >( "BG" ); + if ( pBG ) + { + pBG->SetPos( pBG->GetXPos(), GetTall() ); + } + + m_pTradeUpContainer->SetVisible( true ); + m_pTradeUpContainer->SetPos( m_pTradeUpContainer->GetXPos(), -700 ); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_Intro" ); + vgui::surface()->PlaySound( "ui/trade_up_panel_slide.wav" ); + + m_pDrawingPanel->ClearLines( GetLocalPlayerIndex() ); + } + else + { + if ( m_hSelectionPanel ) + { + m_hSelectionPanel->SetVisible( false ); + } + + if ( m_bShowing ) + { + EconUI()->SetPreventClosure( false ); + } + m_bShowing = false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCollectionCraftingPanel::SOCreated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + if ( m_bWaitingForGCResponse ) + { + if( pObject->GetTypeID() != CEconItem::k_nTypeID ) + return; + + CEconItem *pItem = (CEconItem *)pObject; + + if ( IsUnacknowledged( pItem->GetInventoryToken() ) && ( pItem->GetOrigin() == m_eEconItemOrigin ) ) + { + //Assert( m_nFoundItemID == INVALID_ITEM_ID ); + //m_bWaitingForGCResponse = false; + m_nFoundItemID.AddToTail( pItem->GetItemID() ); + CEconItemView* pNewEconItemView = InventoryManager()->GetLocalInventory()->GetInventoryItemByItemID( pItem->GetItemID() ); + if ( pNewEconItemView ) + { + // Acknowledge the item + InventoryManager()->AcknowledgeItem( pNewEconItemView, true ); + InventoryManager()->SaveAckFile(); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCollectionCraftingPanel::Show( CUtlVector< const CEconItemView* >& vecStartingItems ) +{ + FOR_EACH_VEC( m_vecItemPanels, i ) + { + const CEconItemView* pItem = i < vecStartingItems.Count() ? vecStartingItems[ i ] : NULL; + SetItem( pItem, i ); + } + + m_bShowing = true; + EconUI()->SetPreventClosure( true ); + SetVisible( true ); + + m_eEconItemOrigin = kEconItemOrigin_TradeUp; +} +//----------------------------------------------------------------------------- +void CCollectionCraftingPanel::SetWaitingForItem( eEconItemOrigin eOrigin ) +{ + // Clear Panels + FOR_EACH_VEC( m_vecItemPanels, i ) + { + SetItem( NULL, i ); + } + + m_bShowing = true; + EconUI()->SetPreventClosure( true ); + + m_pInspectPanel->SetVisible( false ); + EditablePanel* pDimmer = FindControl< EditablePanel >( "Dimmer" ); + if ( pDimmer ) + { + pDimmer->SetAlpha( 0 ); + } + + EditablePanel* pBG = FindControl< EditablePanel >( "BG" ); + if ( pBG ) + { + pBG->SetPos( pBG->GetXPos(), GetTall() ); + } + + m_pTradeUpContainer->SetVisible( false ); + + // reset + m_pInspectPanel->SetItemCopy( NULL ); + m_pCosmeticResultItemModelPanel->SetItem( NULL ); + + // Do not use Derived SetVisible since it does extra animations we do not want here + BaseClass::SetVisible( true ); + + m_eEconItemOrigin = eOrigin; + + m_bWaitingForGCResponse = true; + m_nFoundItemID.Purge(); + m_timerResponse.Start( 5.f ); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_WaitForItemsOnly" ); + return; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCollectionCraftingPanel::SetItem( const CEconItemView* pItem, int nIndex ) +{ + if ( nIndex != m_vecItemPanels.InvalidIndex() ) + { + m_vecImagePanels[ nIndex ]->SetVisible( pItem != NULL ); + m_vecItemPanels[ nIndex ]->SetVisible( pItem != NULL ); + m_vecItemPanels[ nIndex ]->SetItem( pItem ); + + if ( pItem && m_vecBoxTopNames.Count() ) + { + CUniformRandomStream randomStream; + randomStream.SetSeed( pItem->GetItemID() ); + m_vecImagePanels[ nIndex ]->SetImage( m_vecBoxTopNames[ randomStream.RandomInt( 0, m_vecBoxTopNames.Count() - 1 ) ] ); + } + } + + UpdateOKButton(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCollectionCraftingPanel::OnThink() +{ + BaseClass::OnThink(); + const float flSoonestAirDropTime = 2.f; + + if ( m_timerResponse.HasStarted() ) + { + // Elapsed is bad. This means the item server didnt get back to us + if ( m_timerResponse.IsElapsed() ) + { + m_nFoundItemID.Purge(); + m_bWaitingForGCResponse = false; + m_timerResponse.Invalidate(); + + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_HideWaiting" ); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_ShowFailure" ); + } + else if ( m_timerResponse.GetElapsedTime() > flSoonestAirDropTime || m_bShowImmediately ) + { + m_bShowImmediately = false; + // At 2 seconds we want to either show that we're still waiting, or show the item + if ( m_nFoundItemID.Count() > 0 ) + { + OnCommand( "itemget" ); + m_timerResponse.Invalidate(); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_HideWaiting" ); + + // Setup the item in the panel + CEconItemView* pNewEconItemView = InventoryManager()->GetLocalInventory()->GetInventoryItemByItemID( m_nFoundItemID[0] ); + if ( pNewEconItemView ) + { + static CSchemaAttributeDefHandle pAttrib_WeaponAllowInspect( "weapon_allow_inspect" ); + if ( pNewEconItemView->FindAttribute( pAttrib_WeaponAllowInspect ) ) + { + m_pInspectPanel->SetItemCopy( pNewEconItemView ); + m_pInspectPanel->SetSpecialAttributesOnly( true ); + m_pCosmeticResultItemModelPanel->SetItem( NULL ); + } + else //( IsMiscSlot( pNewEconItemView->GetStaticData()->GetDefaultLoadoutSlot() ) ) + { + m_pCosmeticResultItemModelPanel->SetItem( pNewEconItemView ); + m_pCosmeticResultItemModelPanel->SetNameOnly( false ); + m_pInspectPanel->SetSpecialAttributesOnly( true ); + m_pInspectPanel->SetItemCopy( NULL ); + } + + // Acknowledge the item + InventoryManager()->AcknowledgeItem( pNewEconItemView, true ); + InventoryManager()->SaveAckFile(); + + if ( m_pItemNamePanel ) + { + m_pItemNamePanel->SetItem( pNewEconItemView ); + } + } + + m_bWaitingForGCResponse = false; + + // only show if more then 1 item in queue + m_pNextItemButton->SetVisible( m_nFoundItemID.Count() > 1 ); + } + else + { + // Say that we're waiting + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_ShowWaiting" ); + } + } + } + + bool bEnvelopReadyToSendThisFrame = true; + // They need to have drawn a little bit + bEnvelopReadyToSendThisFrame &= m_pDrawingPanel->GetLines( GetLocalPlayerIndex() ).Count() > 10; + // And placed a stamp + bEnvelopReadyToSendThisFrame &= m_pStampPanel->IsVisible(); + + // Show the send button? + if ( bEnvelopReadyToSendThisFrame && !m_bEnvelopeReadyToSend ) + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_ShowSendButton" ); + } + + m_bEnvelopeReadyToSend = bEnvelopReadyToSendThisFrame; +} + +//* ************************************************************************************************************************************** +// Stat Clock Crafting + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCraftCommonStatClockPanel::CCraftCommonStatClockPanel( vgui::Panel *parent, CItemModelPanelToolTip* pTooltip ) + : BaseClass( parent, pTooltip ) +{ + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCraftCommonStatClockPanel::~CCraftCommonStatClockPanel( void ) +{ + +} +//----------------------------------------------------------------------------- +void CCraftCommonStatClockPanel::Show( CUtlVector< const CEconItemView* >& vecStartingItems ) +{ + BaseClass::Show( vecStartingItems ); + + // Create output + static CSchemaItemDefHandle pItemDef_CommonStatClock( "Common Stat Clock" ); + m_outputItem.SetItemDefIndex( pItemDef_CommonStatClock->GetDefinitionIndex() ); + m_outputItem.SetItemQuality( AE_UNIQUE ); // Unique by default + m_outputItem.SetItemLevel( 0 ); // Hide this? + m_outputItem.SetItemID( 0 ); + m_outputItem.SetInitialized( true ); + + m_vecOutputImagePanels[0]->SetVisible( true ); + m_vecOutputItemPanels[0]->SetVisible( true ); + m_vecOutputItemPanels[0]->SetItem( &m_outputItem ); + + if ( m_vecBoxTopNames.Count() ) + { + CUniformRandomStream randomStream; + randomStream.SetSeed( 0 ); + m_vecOutputImagePanels[0]->SetImage( m_vecBoxTopNames[randomStream.RandomInt( 0, m_vecBoxTopNames.Count() - 1 )] ); + } +} +//----------------------------------------------------------------------------- +void CCraftCommonStatClockPanel::CreateSelectionPanel() +{ + CStatClockCraftingSelectionPanel *pSelectionPanel = new CStatClockCraftingSelectionPanel( this ); + m_hSelectionPanel = (CCollectionCraftingSelectionPanel*)pSelectionPanel; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftCommonStatClockPanel::OnCommand( const char *command ) +{ + if ( FStrEq( "envelopesend", command ) ) + { + GCSDK::CProtoBufMsg<CMsgCraftCommonStatClock> msg( k_EMsgGCCraftCommonStatClock ); + + // Find out if the user owns this item or not and place in the proper bucket + CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory(); + if ( !pLocalInv ) + return; + + FOR_EACH_VEC( m_vecItemPanels, i ) + { + if ( m_vecItemPanels[i]->GetItem() == NULL ) + return; + + msg.Body().add_item_id( m_vecItemPanels[i]->GetItem()->GetItemID() ); + } + // Send if off + GCClientSystem()->BSendMessage( msg ); + + m_bWaitingForGCResponse = true; + m_nFoundItemID.Purge(); + m_timerResponse.Start( 5.f ); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_LetterSend" ); + return; + } + + BaseClass::OnCommand( command ); +}
\ No newline at end of file diff --git a/game/client/tf/vgui/collection_crafting_panel.h b/game/client/tf/vgui/collection_crafting_panel.h new file mode 100644 index 0000000..491b40d --- /dev/null +++ b/game/client/tf/vgui/collection_crafting_panel.h @@ -0,0 +1,225 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef COLLECTION_CRAFTING_PANEL_H +#define COLLECTION_CRAFTING_PANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "backpack_panel.h" +#include "vgui_controls/ScrollableEditablePanel.h" +#include "tf_gcmessages.h" +#include "econ_gcmessages.h" +#include "tf_imagepanel.h" +#include "tf_controls.h" +#include "item_selection_panel.h" +#include "drawing_panel.h" +#include "local_steam_shared_object_listener.h" + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CCollectionCraftingSelectionPanel : public CItemCriteriaSelectionPanel +{ + DECLARE_CLASS_SIMPLE( CCollectionCraftingSelectionPanel, CItemCriteriaSelectionPanel ); +public: + CCollectionCraftingSelectionPanel( Panel *pParent ) : BaseClass( pParent, NULL ) {} + + void SetCorrespondingItems( CCopyableUtlVector< const CEconItemView* >& vecSelectedItems ) + { + m_vecCorrespondingItems = vecSelectedItems; + } + + void ApplySchemeSettings( vgui::IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + vgui::Label* pWeaponLabel = dynamic_cast<vgui::Label*>( FindChildByName( "ItemSlotLabel" ) ); + if ( pWeaponLabel ) + { + pWeaponLabel->SetVisible( false ); + } + } + + //----------------------------------------------------------------------------- + virtual const char *GetSelectionInvalidReason( const IEconItemInterface *pTestItem, const IEconItemInterface *pSourceItem ) const + { + return GetCollectionCraftingInvalidReason( pTestItem, pSourceItem ); + } + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + const char *GetItemNotSelectableReason( const CEconItemView *pItem ) const + { + if ( !pItem ) + return NULL; + + const CEconItemView* pSourceItem = m_vecCorrespondingItems.Count() ? m_vecCorrespondingItems[0] : NULL; + + FOR_EACH_VEC( m_vecCorrespondingItems, i ) + { + if ( pItem->GetItemID() == m_vecCorrespondingItems[i]->GetItemID() ) + { + return "#TF_StrangeCount_Transfer_Self"; + } + } + + return GetSelectionInvalidReason( pItem, pSourceItem ); + } + + virtual bool ShouldDeleteOnClose( void ) OVERRIDE{ return false; } + +protected: + const char * m_pszTitleToken; + CUtlVector< const CEconItemView* > m_vecCorrespondingItems; +}; + +//----------------------------------------------------------------------------- +// A panel to let users choose 10 weapons to craft up within collections +//----------------------------------------------------------------------------- +class CCollectionCraftingPanel : public vgui::EditablePanel, public CGameEventListener, public CLocalSteamSharedObjectListener +{ +public: + DECLARE_CLASS_SIMPLE( CCollectionCraftingPanel, vgui::EditablePanel ); + CCollectionCraftingPanel( vgui::Panel *parent, CItemModelPanelToolTip* pTooltip ); + ~CCollectionCraftingPanel( void ); + + virtual const char *GetResFile( void ) { return "Resource/UI/econ/CollectionCraftingDialog.res"; } + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE; + virtual void PerformLayout() OVERRIDE; + virtual void FireGameEvent( IGameEvent *event ) OVERRIDE; + virtual void OnCommand( const char *command ) OVERRIDE; + virtual void SetVisible( bool bVisible ) OVERRIDE; + + virtual void SOCreated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) OVERRIDE; + + virtual void Show( CUtlVector< const CEconItemView* >& vecStartingItems ); + void SetWaitingForItem( eEconItemOrigin eOrigin ); + + virtual int GetInputItemCount() { return COLLECTION_CRAFTING_ITEM_COUNT; } + virtual int GetOutputItemCount() { return 0; } // For Ui Display Purposes + + MESSAGE_FUNC_PTR( OnItemPanelMousePressed, "ItemPanelMousePressed", panel ); + MESSAGE_FUNC_PARAMS( OnSelectionReturned, "SelectionReturned", data ); + +protected: + + virtual void SetItemPanelCount( ); + virtual void CreateSelectionPanel(); + virtual void CreateItemPanels(); + + void SelectPanel( int nPanel ); + void UpdateOKButton(); + void SetItem( const CEconItemView* pItem, int nIndex ); + virtual void OnThink() OVERRIDE; + + CItemModelPanelToolTip *m_pMouseOverTooltip; + + DHANDLE<CCollectionCraftingSelectionPanel> m_hSelectionPanel; + + CExButton *m_pOKButton; + CExButton *m_pNextItemButton; + + EditablePanel* m_pTradeUpContainer; + CItemModelPanel* m_pSelectingItemModelPanel; + CUtlVector< EditablePanel* > m_vecItemContainers; + CUtlVector< ImagePanel* > m_vecImagePanels; + CUtlVector< CItemModelPanel* > m_vecItemPanels; + + CUtlVector< EditablePanel* > m_vecOutputItemContainers; + CUtlVector< ImagePanel* > m_vecOutputImagePanels; + CUtlVector< CItemModelPanel* > m_vecOutputItemPanels; + + CUtlVector< CUtlString > m_vecBoxTopNames; + CUtlVector< CUtlString > m_vecStampNames; + CUtlVector< CUtlString > m_vecResultStrings; + struct LocalizedPanelAction_t + { + CUtlString m_strPanel; + bool m_bShowForEnglish; + }; + CUtlVector< LocalizedPanelAction_t > m_vecLocalizedPanels; + CBaseModelPanel *m_pModelPanel; + ImagePanel* m_pStampPanel; + CExButton* m_pStampButton; + + CDrawingPanel *m_pDrawingPanel; + CTFItemInspectionPanel *m_pInspectPanel; + CItemModelPanel* m_pCosmeticResultItemModelPanel; + CItemModelPanel* m_pItemNamePanel; + + KeyValues* m_pKVItemPanels; + bool m_bWaitingForGCResponse; + RealTimeCountdownTimer m_timerResponse; + CUtlVector<itemid_t> m_nFoundItemID; + bool m_bEnvelopeReadyToSend; + bool m_bShowing; + bool m_bShowImmediately; + + eEconItemOrigin m_eEconItemOrigin; + + CPanelAnimationVarAliasType( int, m_iButtonsStartX, "buttons_start_x", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iButtonsStartY, "buttons_start_y", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iButtonsStepX, "buttons_step_x", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iButtonsStepY, "buttons_step_y", "0", "proportional_int" ); + + CPanelAnimationVarAliasType( int, m_iOutputItemStartX, "output_start_x", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iOutputItemStartY, "output_start_y", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iOutputItemStepX, "output_step_x", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iOutputItemStepY, "output_step_y", "0", "proportional_int" ); + + CPanelAnimationVarAliasType( float, m_flSlideInTime, "slide_in_time", "1.0", "float" ); + CPanelAnimationVarAliasType( int, m_iBGContainerTargetY, "bg_target_y", "0", "proportional_int" ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CStatClockCraftingSelectionPanel : public CCollectionCraftingSelectionPanel +{ + DECLARE_CLASS_SIMPLE( CStatClockCraftingSelectionPanel, CCollectionCraftingSelectionPanel ); +public: + CStatClockCraftingSelectionPanel( Panel *pParent ) : BaseClass( pParent ) {} + + //----------------------------------------------------------------------------- + virtual const char *GetSelectionInvalidReason( const IEconItemInterface *pTestItem, const IEconItemInterface *pSourceItem ) const + { + return GetCraftCommonStatClockInvalidReason( pTestItem, pSourceItem ); // FIX ME + } +}; + + +//----------------------------------------------------------------------------- +// A panel to let users choose 10 weapons to craft up within collections +//----------------------------------------------------------------------------- +class CCraftCommonStatClockPanel : public CCollectionCraftingPanel +{ +public: + DECLARE_CLASS_SIMPLE( CCraftCommonStatClockPanel, CCollectionCraftingPanel ); + CCraftCommonStatClockPanel( vgui::Panel *parent, CItemModelPanelToolTip* pTooltip ); + ~CCraftCommonStatClockPanel( void ); + + virtual const char *GetResFile( void ) { return "Resource/UI/econ/MannCoTrade_CommonStatClock.res"; } + virtual void OnCommand( const char *command ) OVERRIDE; + + virtual int GetInputItemCount() { return CRAFT_COMMON_STATCLOCK_ITEM_COUNT; } + virtual int GetOutputItemCount() { return 1; } + + virtual void Show( CUtlVector< const CEconItemView* >& vecStartingItems ); + +protected: + + virtual void CreateSelectionPanel(); + + CEconItemView m_outputItem; +}; + +#endif // COLLECTION_CRAFTING_PANEL_H diff --git a/game/client/tf/vgui/crafting_panel.cpp b/game/client/tf/vgui/crafting_panel.cpp new file mode 100644 index 0000000..9a503d7 --- /dev/null +++ b/game/client/tf/vgui/crafting_panel.cpp @@ -0,0 +1,1738 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "crafting_panel.h" +#include "vgui/ISurface.h" +#include "vgui/ISystem.h" +#include "c_tf_player.h" +#include "gamestringpool.h" +#include "iclientmode.h" +#include "tf_item_inventory.h" +#include "ienginevgui.h" +#include <vgui/ILocalize.h> +#include "vgui_controls/TextImage.h" +#include "vgui_controls/CheckButton.h" +#include "vgui_controls/ComboBox.h" +#include <vgui_controls/TextEntry.h> +#include "vgui/IInput.h" +#include "gcsdk/gcclient.h" +#include "gcsdk/gcclientjob.h" +#include "character_info_panel.h" +#include "charinfo_loadout_subpanel.h" +#include "econ_item_system.h" +#include "econ_item_constants.h" +#include "tf_hud_notification_panel.h" +#include "tf_hud_chat.h" +#include "c_tf_gamestats.h" +#include "confirm_dialog.h" +#include "econ_notifications.h" +#include "gc_clientsystem.h" +#include "charinfo_loadout_subpanel.h" +#include "item_selection_criteria.h" +#include "rtime.h" +#include "c_tf_freeaccount.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +ConVar tf_explanations_craftingpanel( "tf_explanations_craftingpanel", "0", FCVAR_ARCHIVE, "Whether the user has seen explanations for this panel." ); + +struct recipefilter_data_t +{ + const char *pszTooltipString; + const char *pszButtonImage; + const char *pszButtonImageMouseover; +}; +recipefilter_data_t g_RecipeFilters[NUM_RECIPE_CATEGORIES] = +{ + { "#RecipeFilter_Crafting", "crafticon_crafting_items", "crafticon_crafting_items_over" }, // RECIPE_CATEGORY_CRAFTINGITEMS, + { "#RecipeFilter_CommonItems", "crafticon_common_items", "crafticon_common_items_over" }, // RECIPE_CATEGORY_COMMONITEMS, + { "#RecipeFilter_RareItems", "crafticon_rare_items", "crafticon_rare_items_over" }, // RECIPE_CATEGORY_RAREITEMS, + { "#RecipeFilter_Special", "crafticon_special_blueprints", "crafticon_special_blueprints_over" } // RECIPE_CATEGORY_SPECIAL, +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +wchar_t *LocalizeRecipeStringPiece( const char *pszString, wchar_t *pszConverted, int nConvertedSizeInBytes ) +{ + if ( !pszString ) + return L""; + + if ( pszString[0] == '#' ) + return g_pVGuiLocalize->Find( pszString ); + + g_pVGuiLocalize->ConvertANSIToUnicode( pszString, pszConverted, nConvertedSizeInBytes ); + return pszConverted; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void SetItemPanelToRecipe( CItemModelPanel *pPanel, const CEconCraftingRecipeDefinition *pRecipeDef, bool bShowName ) +{ + wchar_t wcTmpName[512]; + wchar_t wcTmpDesc[512]; + int iNegAttribsBegin = 0; + + if ( !pRecipeDef ) + { + Q_wcsncpy( wcTmpName, g_pVGuiLocalize->Find( "#Craft_Recipe_Custom" ), sizeof( wcTmpName ) ); + Q_wcsncpy( wcTmpDesc, g_pVGuiLocalize->Find( "#Craft_Recipe_CustomDesc" ), sizeof( wcTmpDesc ) ); + iNegAttribsBegin = Q_wcslen( wcTmpDesc ); + } + else + { + if ( bShowName ) + { + wchar_t *pName_A = g_pVGuiLocalize->Find( pRecipeDef->GetName_A() ); + g_pVGuiLocalize->ConstructString_safe( wcTmpName, g_pVGuiLocalize->Find( pRecipeDef->GetName() ), 1, pName_A ); + } + else + { + wcTmpName[0] = '\0'; + } + + wchar_t wcTmpA[32]; + wchar_t wcTmpB[32]; + wchar_t wcTmpC[32]; + wchar_t wcTmp[512]; + + // Build the input string + wchar_t *pInp_A = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_A(), wcTmpA, sizeof( wcTmpA ) ); + wchar_t *pInp_B = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_B(), wcTmpB, sizeof( wcTmpB ) ); + wchar_t *pInp_C = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_C(), wcTmpC, sizeof( wcTmpC ) ); + g_pVGuiLocalize->ConstructString_safe( wcTmpDesc, g_pVGuiLocalize->Find( pRecipeDef->GetDescInputs() ), 3, pInp_A, pInp_B, pInp_C ); + iNegAttribsBegin = Q_wcslen(wcTmpDesc); + + // Build the output string + wchar_t *pOut_A = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_A(), wcTmpA, sizeof( wcTmpA ) ); + wchar_t *pOut_B = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_B(), wcTmpB, sizeof( wcTmpB ) ); + wchar_t *pOut_C = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_C(), wcTmpC, sizeof( wcTmpC ) ); + g_pVGuiLocalize->ConstructString_safe( wcTmp, g_pVGuiLocalize->Find( pRecipeDef->GetDescOutputs() ), 3, pOut_A, pOut_B, pOut_C ); + + // Concatenate, and mark the text changes + V_wcscat_safe( wcTmpDesc, L"\n" ); + V_wcscat_safe( wcTmpDesc, wcTmp ); + } + + pPanel->SetAttribOnly( !bShowName ); + pPanel->SetTextYPos( 0 ); + pPanel->SetItem( NULL ); + pPanel->SetNoItemText( wcTmpName, wcTmpDesc, iNegAttribsBegin ); + pPanel->InvalidateLayout(true); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PositionMouseOverPanelForRecipe( vgui::Panel *pScissorPanel, vgui::Panel *pRecipePanel, vgui::ScrollableEditablePanel *pRecipeScroller, CItemModelPanel *pMouseOverItemPanel ) +{ + int x,y; + vgui::ipanel()->GetAbsPos( pRecipePanel->GetVPanel(), x, y ); + int xs,ys; + vgui::ipanel()->GetAbsPos( pMouseOverItemPanel->GetParent()->GetVPanel(), xs, ys ); + x -= xs; + y -= ys; + + int iXPos = (x + (pRecipePanel->GetWide() * 0.5)) - (pMouseOverItemPanel->GetWide() * 0.5); + int iYPos = (y + pRecipePanel->GetTall()); + + // Make sure the popup stays onscreen. + if ( iXPos < 0 ) + { + iXPos = 0; + } + else if ( (iXPos + pMouseOverItemPanel->GetWide()) > pMouseOverItemPanel->GetParent()->GetWide() ) + { + iXPos = pMouseOverItemPanel->GetParent()->GetWide() - pMouseOverItemPanel->GetWide(); + } + + if ( iYPos < 0 ) + { + iYPos = 0; + } + else if ( (iYPos + pMouseOverItemPanel->GetTall() + YRES(32)) > pMouseOverItemPanel->GetParent()->GetTall() ) + { + // Move it up above our item + iYPos = y - pMouseOverItemPanel->GetTall() - YRES(4); + } + + pMouseOverItemPanel->SetPos( iXPos, iYPos ); + pMouseOverItemPanel->SetVisible( true ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCraftingPanel::CCraftingPanel( vgui::Panel *parent, const char *panelName ) : CBaseLoadoutPanel( parent, panelName ) +{ + m_pRecipeListContainer = new vgui::EditablePanel( this, "recipecontainer" ); + m_pRecipeListContainerScroller = new vgui::ScrollableEditablePanel( this, m_pRecipeListContainer, "recipecontainerscroller" ); + m_pSelectedRecipeContainer = new vgui::EditablePanel( this, "selectedrecipecontainer" ); + m_pRecipeButtonsKV = NULL; + m_pRecipeFilterButtonsKV = NULL; + m_bEventLogging = false; + m_iCraftingAttempts = 0; + m_iRecipeCategoryFilter = RECIPE_CATEGORY_CRAFTINGITEMS; + m_iCurrentlySelectedRecipe = -1; + CleanupPostCraft( true ); + + m_pToolTip = new CTFTextToolTip( this ); + m_pToolTipEmbeddedPanel = new vgui::EditablePanel( this, "TooltipPanel" ); + m_pToolTipEmbeddedPanel->SetKeyBoardInputEnabled( false ); + m_pToolTipEmbeddedPanel->SetMouseInputEnabled( false ); + m_pToolTip->SetEmbeddedPanel( m_pToolTipEmbeddedPanel ); + m_pToolTip->SetTooltipDelay( 0 ); + + m_pSelectionPanel = NULL; + m_iSelectingForSlot = 0; + + m_pCraftButton = NULL; + m_pUpgradeButton = NULL; + m_pFreeAccountLabel = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCraftingPanel::~CCraftingPanel( void ) +{ + if ( m_pRecipeButtonsKV ) + { + m_pRecipeButtonsKV->deleteThis(); + m_pRecipeButtonsKV = NULL; + } + if ( m_pRecipeFilterButtonsKV ) + { + m_pRecipeFilterButtonsKV->deleteThis(); + m_pRecipeFilterButtonsKV = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + LoadControlSettings( GetResFile() ); + + BaseClass::ApplySchemeSettings( pScheme ); + + m_pRecipeListContainerScroller->GetScrollbar()->SetAutohideButtons( true ); + m_pCraftButton = dynamic_cast<CExButton*>( m_pSelectedRecipeContainer->FindChildByName("CraftButton") ); + if ( m_pCraftButton ) + { + m_pCraftButton->AddActionSignalTarget( this ); + } + m_pUpgradeButton = dynamic_cast<CExButton*>( m_pSelectedRecipeContainer->FindChildByName("UpgradeButton") ); + if ( m_pUpgradeButton ) + { + m_pUpgradeButton->AddActionSignalTarget( this ); + } + m_pFreeAccountLabel = dynamic_cast<CExLabel*>( m_pSelectedRecipeContainer->FindChildByName("FreeAccountLabel") ); + + CreateRecipeFilterButtons(); + UpdateRecipeFilter(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + KeyValues *pItemKV = inResourceData->FindKey( "recipebuttons_kv" ); + if ( pItemKV ) + { + if ( m_pRecipeButtonsKV ) + { + m_pRecipeButtonsKV->deleteThis(); + } + m_pRecipeButtonsKV = new KeyValues("recipebuttons_kv"); + pItemKV->CopySubkeys( m_pRecipeButtonsKV ); + } + + KeyValues *pButtonKV = inResourceData->FindKey( "recipefilterbuttons_kv" ); + if ( pButtonKV ) + { + if ( m_pRecipeFilterButtonsKV ) + { + m_pRecipeFilterButtonsKV->deleteThis(); + } + m_pRecipeFilterButtonsKV = new KeyValues("recipefilterbuttons_kv"); + pButtonKV->CopySubkeys( m_pRecipeFilterButtonsKV ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::PerformLayout( void ) +{ + BaseClass::PerformLayout(); + + // Need to lay these out before we start making item panels inside them + m_pRecipeListContainer->InvalidateLayout( true ); + m_pRecipeListContainerScroller->InvalidateLayout( true ); + + // Position the recipe filters + FOR_EACH_VEC( m_pRecipeFilterButtons, i ) + { + if ( m_pRecipeFilterButtonsKV ) + { + m_pRecipeFilterButtons[i]->ApplySettings( m_pRecipeFilterButtonsKV ); + m_pRecipeFilterButtons[i]->InvalidateLayout(); + } + + int iButtonW, iButtonH; + m_pRecipeFilterButtons[i]->GetSize( iButtonW, iButtonH ); + + int iXPos = (GetWide() * 0.5) + m_iFilterOffcenterX + ((iButtonW + m_iFilterDeltaX) * i); + int iYPos = m_iFilterYPos;// + ((iButtonH + m_iFilterDeltaY) * i); + m_pRecipeFilterButtons[i]->SetPos( iXPos, iYPos ); + } + + // Position the recipe buttons + for ( int i = 0; i < m_pRecipeButtons.Count(); i++ ) + { + if ( m_pRecipeButtonsKV ) + { + m_pRecipeButtons[i]->ApplySettings( m_pRecipeButtonsKV ); + m_pRecipeButtons[i]->InvalidateLayout(); + } + + int iYDelta = m_pRecipeButtons[0]->GetTall() + YRES(2); + + // Once we've setup our first item, we know how large to make the container + if ( i == 0 ) + { + m_pRecipeListContainer->SetSize( m_pRecipeListContainer->GetWide(), iYDelta * m_pRecipeButtons.Count() ); + } + + int x,y; + m_pRecipeButtons[i]->GetPos( x,y ); + m_pRecipeButtons[i]->SetPos( x, (iYDelta * i) ); + } + + // Now that the container has been sized, tell the scroller to re-evaluate + m_pRecipeListContainerScroller->InvalidateLayout(); + m_pRecipeListContainerScroller->GetScrollbar()->InvalidateLayout(); + + // Then position all our item panels + for ( int i = 0; i < m_pItemModelPanels.Count(); i++ ) + { + PositionItemPanel( m_pItemModelPanels[i], i ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::CreateRecipeFilterButtons( void ) +{ + for ( int i = 0; i < NUM_RECIPE_CATEGORIES; i++ ) + { + if ( m_pRecipeFilterButtons.Count() <= i ) + { + CImageButton *pNewButton = new CImageButton( this, g_RecipeFilters[i].pszTooltipString ); + m_pRecipeFilterButtons.AddToTail( pNewButton ); + } + + m_pRecipeFilterButtons[i]->SetInactiveImage( g_RecipeFilters[i].pszButtonImage ); + m_pRecipeFilterButtons[i]->SetActiveImage( g_RecipeFilters[i].pszButtonImageMouseover ); + m_pRecipeFilterButtons[i]->SetTooltip( m_pToolTip, g_RecipeFilters[i].pszTooltipString ); + const char *pszCommand = VarArgs("selectfilter%d", i ); + m_pRecipeFilterButtons[i]->SetCommand( pszCommand ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::UpdateRecipeFilter( void ) +{ + int iMatchingRecipes = 0; + m_iCurrentlySelectedRecipe = -1; + m_iCurrentRecipeTotalInputs = 0; + m_iCurrentRecipeTotalOutputs = 0; + + FOR_EACH_VEC( m_pRecipeFilterButtons, i ) + { + bool bForceDepressed = ( i == m_iRecipeCategoryFilter ); + m_pRecipeFilterButtons[i]->ForceDepressed( bForceDepressed ); + } + + // Loop through the known recipes, and see which ones match our category filter + for ( int i = 0; i < TFInventoryManager()->GetLocalTFInventory()->GetRecipeCount(); i++ ) + { + const CEconCraftingRecipeDefinition *pRecipeDef = TFInventoryManager()->GetLocalTFInventory()->GetRecipeDef(i); + if ( !pRecipeDef ) + continue; + + if ( pRecipeDef->IsDisabled() ) + continue; + + if ( pRecipeDef->GetCategory() != m_iRecipeCategoryFilter ) + continue; + + wchar_t wTemp[256]; + wchar_t *pName_A = g_pVGuiLocalize->Find( pRecipeDef->GetName_A() ); + g_pVGuiLocalize->ConstructString_safe( wTemp, g_pVGuiLocalize->Find( pRecipeDef->GetName() ), 1, pName_A ); + SetButtonToRecipe( iMatchingRecipes, pRecipeDef->GetDefinitionIndex(), wTemp ); + + iMatchingRecipes++; + } + + // Add a "Custom" option to the bottom of the Special recipe list + if ( m_iRecipeCategoryFilter == RECIPE_CATEGORY_SPECIAL ) + { + SetButtonToRecipe( iMatchingRecipes, RECIPE_CUSTOM, g_pVGuiLocalize->Find("#Craft_Recipe_Custom") ); + iMatchingRecipes++; + } + + // Delete excess buttons + for ( int i = m_pRecipeButtons.Count() - 1; i >= iMatchingRecipes; i-- ) + { + m_pRecipeButtons[i]->MarkForDeletion(); + m_pRecipeButtons.Remove( i ); + } + + // Move the scrollbar to the top + m_pRecipeListContainerScroller->GetScrollbar()->SetValue( 0 ); + + UpdateSelectedRecipe( true ); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::OnCancelSelection( void ) +{ + if ( m_pSelectionPanel ) + { + m_pSelectionPanel->SetVisible( false ); + } + + CloseCraftingStatusDialog(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::OnSelectionReturned( KeyValues *data ) +{ + if ( data ) + { + uint64 ulIndex = data->GetUint64( "itemindex", INVALID_ITEM_ID ); + if ( ulIndex == INVALID_ITEM_ID ) + { + // should this be INVALID_ITEM_ID? + m_InputItems[m_iSelectingForSlot] = 0; + } + else + { + m_InputItems[m_iSelectingForSlot] = ulIndex; + } + + UpdateModelPanels(); + UpdateCraftButton(); + } + + // It'll have deleted itself, so we don't need to clean it up + OnCancelSelection(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::OnShowPanel( bool bVisible, bool bReturningFromArmory ) +{ + if ( bVisible ) + { + if ( m_pSelectionPanel ) + { + m_pSelectionPanel->SetVisible( false ); + } + + memset( m_InputItems, 0, sizeof(m_InputItems) ); + memset( m_ItemPanelCriteria, 0, sizeof(m_ItemPanelCriteria) ); + m_iCurrentlySelectedRecipe = -1; + m_iCurrentRecipeTotalInputs = 0; + m_iCurrentRecipeTotalOutputs = 0; + UpdateRecipeFilter(); + + if ( !m_bEventLogging ) + { + m_bEventLogging = true; + C_CTF_GameStats.Event_Crafting( IE_CRAFTING_ENTERED ); + } + } + else + { + CloseCraftingStatusDialog(); + vgui::ivgui()->RemoveTickSignal( GetVPanel() ); + } + + BaseClass::OnShowPanel( bVisible, bReturningFromArmory ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::OnClosing() +{ + if ( m_bEventLogging ) + { + C_CTF_GameStats.Event_Crafting( IE_CRAFTING_EXITED ); + m_bEventLogging = false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::PositionItemPanel( CItemModelPanel *pPanel, int iIndex ) +{ + int iCenter = 0; + int iButtonX, iButtonY, iXPos, iYPos; + + if ( IsInputItemPanel(iIndex) ) + { + iButtonX = (iIndex % CRAFTING_SLOTS_INPUT_COLUMNS); + iButtonY = (iIndex / CRAFTING_SLOTS_INPUT_COLUMNS); + iXPos = (iCenter + m_iItemCraftingOffcenterX) + (iButtonX * m_pItemModelPanels[iIndex]->GetWide()) + (m_iItemBackpackXDelta * iButtonX); + iYPos = m_iItemYPos + (iButtonY * m_pItemModelPanels[iIndex]->GetTall() ) + (m_iItemBackpackYDelta * iButtonY); + } + else + { + int iButtonIndex = iIndex - CRAFTING_SLOTS_INPUTPANELS; + iButtonX = (iButtonIndex % CRAFTING_SLOTS_OUTPUT_COLUMNS); + iButtonY = (iButtonIndex / CRAFTING_SLOTS_OUTPUT_COLUMNS); + iXPos = (iCenter + m_iItemCraftingOffcenterX) + (iButtonX * m_pItemModelPanels[iIndex]->GetWide()) + (m_iItemBackpackXDelta * iButtonX); + iYPos = m_iOutputItemYPos + (iButtonY * m_pItemModelPanels[iIndex]->GetTall() ) + (m_iItemBackpackYDelta * iButtonY); + } + + m_pItemModelPanels[iIndex]->SetPos( iXPos, iYPos ); + return; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::UpdateRecipeItems( bool bClearInputItems ) +{ + if ( bClearInputItems ) + { + memset( m_InputItems, 0, sizeof(m_InputItems) ); + } + + memset( m_ItemPanelCriteria, 0, sizeof(m_ItemPanelCriteria) ); + m_iCurrentRecipeTotalInputs = 0; + m_iCurrentRecipeTotalOutputs = 0; + + if ( m_iCurrentlySelectedRecipe == -1 ) + return; + + /* + // Build lists of items divided by class & loadout slot, so recipes can quickly test themselves + CUtlVector<CEconItem*> vecAllItems; + CUtlVector<CEconItem*> vecItemsByClass[ LOADOUT_COUNT ]; + CUtlVector<CEconItem*> vecItemsBySlot[ LOADOUT_POSITION_COUNT ]; + + for ( int i = 1; i <= TFInventoryManager()->GetLocalTFInventory()->GetMaxItemCount(); i++ ) + { + CEconItemView *pItemData = TFInventoryManager()->GetItemByBackpackPosition(i); + if ( pItemData && pItemData->IsValid() ) + { + CEconItem *pSOCData = pItemData->GetSOCData(); + vecAllItems.AddToTail( pSOCData ); + + CTFItemDefinition *pItemDef = pItemData->GetStaticData(); + + // Put it in class lists for any class that can use it. Use the zeroth list as all-class items. + if ( pItemDef->CanBeUsedByAllClasses() ) + { + vecItemsByClass[0].AddToTail( pSOCData ); + } + for (int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ ) + { + if ( pItemDef->CanBeUsedByClass(iClass) ) + { + vecItemsByClass[iClass].AddToTail( pSOCData ); + } + } + + // Put it in the slot lists for any slot that it can be equipped in + for (int iSlot = 0; iSlot < LOADOUT_POSITION_COUNT; iSlot++ ) + { + if ( pItemDef->CanBePlacedInSlot( iSlot ) ) + { + vecItemsBySlot[iSlot].AddToTail( pSOCData ); + } + } + } + } + */ + + // Find the items needed for the specified recipe + if ( m_iCurrentlySelectedRecipe == RECIPE_CUSTOM ) + { + // Custom recipe. Show all open buttons, and let them put anything in there. + m_iCurrentRecipeTotalInputs = CRAFTING_SLOTS_INPUTPANELS; + m_iCurrentRecipeTotalOutputs = 0; + + FOR_EACH_VEC( m_pItemModelPanels, i ) + { + m_pItemModelPanels[i]->SetNoItemText( "" ); + } + } + else + { + const CTFCraftingRecipeDefinition *pRecipeDef = (CTFCraftingRecipeDefinition*)TFInventoryManager()->GetLocalTFInventory()->GetRecipeDefByDefIndex( m_iCurrentlySelectedRecipe ); + if ( pRecipeDef ) + { + m_iCurrentRecipeTotalInputs = pRecipeDef->GetTotalInputItemsRequired(); + m_iCurrentRecipeTotalOutputs = pRecipeDef->GetTotalOutputItems(); + + CUtlVector<itemid_t> vecItemsUsed; + + // Set the text in each of the item panels + const CUtlVector<CItemSelectionCriteria> *vecInputCriteria; + vecInputCriteria = pRecipeDef->GetInputItems(); + CUtlVector<uint32> vecInputDupes; + vecInputDupes = pRecipeDef->GetInputItemDupeCounts(); + + int iModelPanel = 0; + FOR_EACH_VEC( *vecInputCriteria, i ) + { + const char *pszNoItemText = GetItemTextForCriteria( &(*vecInputCriteria)[i] ); + + int iNumPanels = vecInputDupes[i] ? vecInputDupes[i] : 1; + for ( int iPanel = 0; iPanel < iNumPanels; iPanel++ ) + { + m_ItemPanelCriteria[iModelPanel] = &(*vecInputCriteria)[i]; + if ( m_pItemModelPanels[iModelPanel] ) + { + m_pItemModelPanels[iModelPanel]->SetNoItemText( pszNoItemText ); + } + iModelPanel++; + } + } + + // Set the output items as well + CUtlVector<CItemSelectionCriteria> vecOutputCriteria; + vecOutputCriteria = pRecipeDef->GetOutputItems(); + FOR_EACH_VEC( vecOutputCriteria, i ) + { + int iOutputPanel = CRAFTING_SLOTS_INPUTPANELS + i; + + CEconItemDefinition *pDef = GetItemDefFromCriteria( &vecOutputCriteria[i] ); + if ( pDef ) + { + //m_pItemModelPanels[iOutputPanel]->SetNoItemText( pszNoItemText ); + CEconItemView *pItemData = new CEconItemView(); + pItemData->Init( pDef->GetDefinitionIndex(), AE_UNIQUE, AE_USE_SCRIPT_VALUE, true ); + if ( m_pItemModelPanels[iOutputPanel] ) + { + m_pItemModelPanels[iOutputPanel]->SetItem( pItemData ); + } + delete pItemData; + continue; + } + + // If we didn't manage to extract an output, just use the recipe output string + wchar_t wcTmpA[32]; + wchar_t wcTmpB[32]; + wchar_t wcTmpC[32]; + wchar_t wcTmp[512]; + wchar_t *pOut_A = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_A(), wcTmpA, sizeof( wcTmpA ) ); + wchar_t *pOut_B = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_B(), wcTmpB, sizeof( wcTmpB ) ); + wcTmp[0] = '\0'; + V_wcscat_safe( wcTmp, pOut_A ); + V_wcscat_safe( wcTmp, L" " ); + V_wcscat_safe( wcTmp, pOut_B ); + if ( Q_strnicmp( pRecipeDef->GetDescOutputs(), "#RDO_ABC", 8 ) == 0 ) + { + wchar_t *pOut_C = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_C(), wcTmpC, sizeof( wcTmpC ) ); + V_wcscat_safe( wcTmp, L" " ); + V_wcscat_safe( wcTmp, pOut_C ); + } + + if ( m_pItemModelPanels[iOutputPanel] ) + { + m_pItemModelPanels[iOutputPanel]->SetItem( NULL ); + m_pItemModelPanels[iOutputPanel]->SetNoItemText( wcTmp ); + } + } + } + } + + // Now check to see if they've got the right items in there + UpdateCraftButton(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::UpdateCraftButton( void ) +{ + if ( m_iCurrentlySelectedRecipe == -1 ) + return; + + bool bAllowedToUse = true; + + const CEconCraftingRecipeDefinition *pRecipeDef = NULL; + if ( m_iCurrentlySelectedRecipe != RECIPE_CUSTOM ) + { + pRecipeDef = (CTFCraftingRecipeDefinition*)TFInventoryManager()->GetLocalTFInventory()->GetRecipeDefByDefIndex( m_iCurrentlySelectedRecipe ); + if ( !pRecipeDef ) + return; + + bAllowedToUse = ( !IsFreeTrialAccount() || !pRecipeDef->IsPremiumAccountOnly() ); + } + + if ( m_pCraftButton ) + { + m_pCraftButton->SetVisible( bAllowedToUse ); + } + if ( m_pUpgradeButton ) + { + m_pUpgradeButton->SetVisible( !bAllowedToUse ); + } + if ( m_pFreeAccountLabel ) + { + m_pFreeAccountLabel->SetVisible( !bAllowedToUse ); + } + + if ( !bAllowedToUse ) + return; + + bool bCraftButtonActive = false; + if ( m_iCurrentlySelectedRecipe == RECIPE_CUSTOM ) + { + // Need at least one item in a slot + for ( int i = 0; i < CRAFTING_SLOTS_INPUTPANELS; i++ ) + { + CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( m_InputItems[i] ); + if ( pItemData ) + { + bCraftButtonActive = true; + break; + } + } + } + else + { + CUtlVector<CEconItem*> vecAllItems; + for ( int i = 0; i < CRAFTING_SLOTS_INPUTPANELS; i++ ) + { + CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( m_InputItems[i] ); + if ( pItemData ) + { + vecAllItems.AddToTail( pItemData->GetSOCData() ); + } + } + + bCraftButtonActive = pRecipeDef->ItemListMatchesInputs( &vecAllItems, NULL, false, NULL ); + } + + if ( m_pCraftButton ) + { + m_pCraftButton->SetEnabled( bCraftButtonActive ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CCraftingPanel::GetItemTextForCriteria( const CItemSelectionCriteria *pCriteria ) +{ + // Otherwise, look at the first condition, and see if we can determine what the item is + const char *pszVal = pCriteria->GetValueForFirstConditionOfType( k_EOperator_String_EQ ); + if ( pszVal && pszVal[0] ) + { + // Is it a loadout slot? + int iSlot = StringFieldToInt( pszVal, ItemSystem()->GetItemSchema()->GetLoadoutStrings( EEquipType_t::EQUIP_TYPE_CLASS ), true ); + if ( iSlot != -1 ) + return ItemSystem()->GetItemSchema()->GetLoadoutStringsForDisplay( EEquipType_t::EQUIP_TYPE_CLASS )[iSlot]; + + // Is it a craft material type? + if ( V_stricmp( pszVal, "weapon" ) == 0 ) + { + return "#RI_W"; + } + else if ( V_stricmp( pszVal, "hat" ) == 0 ) + { + return "#RI_Hg"; + } + else if ( V_stricmp( pszVal, "craft_token" ) == 0 ) + { + return "#RI_T"; + } + else if ( V_stricmp( pszVal, "class_token" ) == 0 ) + { + return "#CI_T_C"; + } + else if ( V_stricmp( pszVal, "slot_token" ) == 0 ) + { + return "#CI_T_S"; + } + + // Is it an item name? + CEconItemDefinition *pDef = ItemSystem()->GetItemSchema()->GetItemDefinitionByName(pszVal); + if ( pDef ) + return pDef->GetItemBaseName(); + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconItemDefinition *CCraftingPanel::GetItemDefFromCriteria( const CItemSelectionCriteria *pCriteria ) +{ + // Otherwise, look at the first condition, and see if we can determine what the item is + const char *pszVal = pCriteria->GetValueForFirstConditionOfType( k_EOperator_String_EQ ); + if ( pszVal && pszVal[0] ) + return ItemSystem()->GetItemSchema()->GetItemDefinitionByName(pszVal); + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::AddNewItemPanel( int iPanelIndex ) +{ + BaseClass::AddNewItemPanel( iPanelIndex ); + + // Move the model panels to our selected recipe container + m_pItemModelPanels[iPanelIndex]->SetParent( m_pSelectedRecipeContainer ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::UpdateModelPanels( void ) +{ + BaseClass::UpdateModelPanels(); + + for ( int i = 0; i < m_pItemModelPanels.Count(); i++ ) + { + if ( IsInputItemPanel(i) ) + { + if ( m_InputItems[i] != 0 ) + { + CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( m_InputItems[i] ); + m_pItemModelPanels[i]->SetItem( pItemData ); + m_pItemModelPanels[i]->SetVisible( true ); + m_pItemModelPanels[i]->SetShowEquipped( true ); + SetBorderForItem( m_pItemModelPanels[i], false ); + } + else + { + m_pItemModelPanels[i]->SetItem( NULL ); + + // Always show the number of slots that the recipe uses + bool bVisible = (m_iCurrentRecipeTotalInputs > i); + m_pItemModelPanels[i]->SetVisible( bVisible ); + } + } + else + { + bool bVisible = ((m_iCurrentRecipeTotalOutputs + CRAFTING_SLOTS_INPUTPANELS) > i); + m_pItemModelPanels[i]->SetVisible( bVisible ); + } + } + + vgui::Panel *pLabel = m_pSelectedRecipeContainer->FindChildByName("OutputLabel"); + if ( pLabel ) + { + pLabel->SetVisible( m_iCurrentRecipeTotalOutputs > 0 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::SetButtonToRecipe( int iButton, int iDefIndex, wchar_t *pszText ) +{ + // Re-use existing buttons, or make new ones if we need more + CRecipeButton *pRecipeButton = NULL; + if ( iButton < m_pRecipeButtons.Count() ) + { + pRecipeButton = m_pRecipeButtons[iButton]; + } + else + { + pRecipeButton = new CRecipeButton( m_pRecipeListContainer, "selectrecipe", "", this, "selectrecipe" ); + if ( m_pRecipeButtonsKV ) + { + pRecipeButton->ApplySettings( m_pRecipeButtonsKV ); + } + pRecipeButton->MakeReadyForUse(); + m_pRecipeButtons.AddToTail( pRecipeButton ); + } + + const char *pszCommand = VarArgs("selectrecipe%d", iDefIndex ); + pRecipeButton->SetCommand( pszCommand ); + pRecipeButton->SetText( pszText ); + pRecipeButton->SetDefIndex( iDefIndex ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::UpdateSelectedRecipe( bool bClearInputItems ) +{ + for ( int i = 0; i < m_pRecipeButtons.Count(); i++ ) + { + bool bSelected = m_pRecipeButtons[i]->m_iRecipeDefIndex == m_iCurrentlySelectedRecipe; + m_pRecipeButtons[i]->ForceDepressed( bSelected ); + m_pRecipeButtons[i]->RecalculateDepressedState(); + + if ( bSelected ) + { + wchar_t wszText[1024]; + m_pRecipeButtons[i]->GetText( wszText, ARRAYSIZE( wszText ) ); + m_pSelectedRecipeContainer->SetDialogVariable( "recipetitle", wszText ); + + if ( m_iCurrentlySelectedRecipe == RECIPE_CUSTOM ) + { + m_pSelectedRecipeContainer->SetDialogVariable( "recipeinputstring", g_pVGuiLocalize->Find("#Craft_Recipe_CustomDesc") ); + } + else + { + const CTFCraftingRecipeDefinition *pRecipeDef = (CTFCraftingRecipeDefinition*)TFInventoryManager()->GetLocalTFInventory()->GetRecipeDefByDefIndex( m_iCurrentlySelectedRecipe ); + if ( pRecipeDef ) + { + // Build the input string + wchar_t wcTmpA[32]; + wchar_t wcTmpB[32]; + wchar_t wcTmpC[32]; + wchar_t wcTmpDesc[512]; + wchar_t *pInp_A = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_A(), wcTmpA, sizeof( wcTmpA ) ); + wchar_t *pInp_B = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_B(), wcTmpB, sizeof( wcTmpB ) ); + wchar_t *pInp_C = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_C(), wcTmpC, sizeof( wcTmpC ) ); + g_pVGuiLocalize->ConstructString_safe( wcTmpDesc, g_pVGuiLocalize->Find( pRecipeDef->GetDescInputs() ), 3, pInp_A, pInp_B, pInp_C ); + m_pSelectedRecipeContainer->SetDialogVariable( "recipeinputstring", wcTmpDesc ); + } + } + } + } + + m_pSelectedRecipeContainer->SetVisible( m_iCurrentlySelectedRecipe != -1 ); + + UpdateRecipeItems( bClearInputItems ); + UpdateModelPanels(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::OnCommand( const char *command ) +{ + if ( !Q_strnicmp( command, "selectrecipe", 12 ) ) + { + const char *pszNum = command+12; + if ( pszNum && pszNum[0] ) + { + m_iCurrentlySelectedRecipe = atoi(pszNum); + UpdateSelectedRecipe( true ); + } + + return; + } + if ( !Q_strnicmp( command, "selectfilter", 12 ) ) + { + const char *pszNum = command+12; + if ( pszNum && pszNum[0] ) + { + m_iRecipeCategoryFilter = (recipecategories_t)atoi(pszNum); + UpdateRecipeFilter(); + } + + return; + } + else if ( !Q_strnicmp( command, "back", 4 ) ) + { + PostMessage( GetParent(), new KeyValues("CraftingClosed") ); + return; + } + else if ( !Q_strnicmp( command, "craft", 5 ) ) + { + if ( CheckForUntradableItems() ) + { + Craft(); + } + return; + } + else if ( !Q_stricmp( command, "upgrade" ) ) + { + EconUI()->CloseEconUI(); + EconUI()->OpenStorePanel( STOREPANEL_SHOW_UPGRADESTEPS, false ); + return; + } + else if ( !Q_stricmp( command, "reloadscheme" ) ) + { + InvalidateLayout( true, true ); + } + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::OnRecipePanelEntered( vgui::Panel *panel ) +{ + CRecipeButton *pRecipePanel = dynamic_cast < CRecipeButton * > ( panel ); + + if ( pRecipePanel && IsVisible() && !IsIgnoringItemPanelEnters() ) + { + const CEconCraftingRecipeDefinition *pRecipeDef = NULL; + if ( pRecipePanel->m_iRecipeDefIndex != RECIPE_CUSTOM ) + { + pRecipeDef = TFInventoryManager()->GetLocalTFInventory()->GetRecipeDefByDefIndex( pRecipePanel->m_iRecipeDefIndex ); + } + + SetItemPanelToRecipe( GetMouseOverPanel(), pRecipeDef, false ); + PositionMouseOverPanelForRecipe( this, pRecipePanel, m_pRecipeListContainerScroller, GetMouseOverPanel() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::OnRecipePanelExited( vgui::Panel *panel ) +{ + GetMouseOverPanel()->SetAttribOnly( false ); + GetMouseOverPanel()->SetTextYPos( YRES(20) ); + GetMouseOverPanel()->SetVisible( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CCraftingPanel::GetItemPanelIndex( CItemModelPanel *pItemPanel ) +{ + for ( int i = 0; i < m_pItemModelPanels.Count(); i++ ) + { + if ( m_pItemModelPanels[i] == pItemPanel ) + return i; + } + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::OnItemPanelMousePressed( vgui::Panel *panel ) +{ + CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel ); + + if ( pItemPanel && IsVisible() && !pItemPanel->IsGreyedOut() ) + { + int iPos = GetItemPanelIndex(pItemPanel); + if ( IsInputItemPanel(iPos) ) + { + m_iSelectingForSlot = iPos; + + // Create it the first time around + if ( !m_pSelectionPanel ) + { + m_pSelectionPanel = new CCraftingItemSelectionPanel( this ); + } + + if ( m_iCurrentlySelectedRecipe == RECIPE_CUSTOM ) + { + m_pSelectionPanel->UpdateOnShow( NULL, true, m_InputItems, ARRAYSIZE(m_InputItems) ); + } + else + { + // Clicked on an item in the crafting area. Open up the selection panel. + m_pSelectionPanel->UpdateOnShow( m_ItemPanelCriteria[iPos], false, m_InputItems, ARRAYSIZE(m_InputItems) ); + } + + m_pSelectionPanel->ShowDuplicateCounts( true ); + m_pSelectionPanel->ShowPanel( 0, true ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +static void ConfirmCraft( bool bConfirmed, void* pContext ) +{ + CCraftingPanel *pCraftingPanel = ( CCraftingPanel* )pContext; + if ( bConfirmed ) + { + pCraftingPanel->Craft(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CCraftingPanel::CheckForUntradableItems( void ) +{ + bool bHasUntradable = false; + for ( int i = 0; i < CRAFTING_SLOTS_INPUTPANELS; i++ ) + { + if ( m_InputItems[i] != 0 ) + { + CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( m_InputItems[i] ); + if ( pItemData->IsTradable() == false ) + { + bHasUntradable = true; + break; + } + } + } + + if ( bHasUntradable ) + { + CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#Craft_Untradable_Title", "#Craft_Untradable_Text", "#GameUI_OK", "#Cancel", &ConfirmCraft ); + pDialog->SetContext( this ); + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::Craft( void ) +{ + // Build our list of items that we're trying to craft + ++m_iCraftingAttempts; + CUtlVector<itemid_t> vecCraftingItems; + for ( int i = 0; i < CRAFTING_SLOTS_INPUTPANELS; i++ ) + { + if ( m_InputItems[i] != 0 ) + { + CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( m_InputItems[i] ); + C_CTF_GameStats.Event_Crafting( IE_CRAFTING_ATTEMPT, pItemData, m_iCraftingAttempts ); + vecCraftingItems.AddToTail( m_InputItems[i] ); + } + } + + if ( !vecCraftingItems.Count() ) + return; + + GCSDK::CGCMsg<MsgGCCraft_t> msg( k_EMsgGCCraft ); + msg.Body().m_nRecipeDefIndex = m_iCurrentlySelectedRecipe; + msg.Body().m_nItemCount = vecCraftingItems.Count(); + for ( int i = 0; i < vecCraftingItems.Count(); i++ ) + { + msg.AddUint64Data( vecCraftingItems[i] ); + } + GCClientSystem()->BSendMessage( msg ); + + OpenCraftingStatusDialog( this, "#CraftUpdate_Start", true, false, false ); + + // Start ticking so we can give up waiting if we don't get a response from the GC + // We use the VGUI time, because we may not be in a game at all. + m_flAbortCraftingAt = vgui::system()->GetCurrentTime() + 10; + m_bWaitingForCraftItems = false; + m_iRecipeIndexTried = m_iCurrentlySelectedRecipe; + + vgui::ivgui()->AddTickSignal( GetVPanel(), 100 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::OnCraftResponse( EGCMsgResponse eResponse, CUtlVector<uint64> *vecCraftedIndices, int iRecipeUsed ) +{ + switch ( eResponse ) + { + case k_EGCMsgResponseNoMatch: + { + C_CTF_GameStats.Event_Crafting( IE_CRAFTING_NO_RECIPE_MATCH, NULL, m_iCraftingAttempts ); + CleanupPostCraft( m_iCurrentlySelectedRecipe != RECIPE_CUSTOM ); + OpenCraftingStatusDialog( this, "#CraftUpdate_NoMatch", false, true, false ); + } + break; + + case k_EGCMsgResponseDenied: + { + // Craft denied. + C_CTF_GameStats.Event_Crafting( IE_CRAFTING_FAILURE, NULL, m_iCraftingAttempts ); + CleanupPostCraft( m_iCurrentlySelectedRecipe != RECIPE_CUSTOM ); + OpenCraftingStatusDialog( this, "#CraftUpdate_Denied", false, true, false ); + } + break; + + // We've got the list of items crafted. We save off the item list until our item cache has all the items. + case k_EGCMsgResponseOK: + { + // Start ticking, and wait until the cache contains all the items in the list. + m_bWaitingForCraftItems = true; + m_vecNewlyCraftedItems = *vecCraftedIndices; + + if ( iRecipeUsed != m_iRecipeIndexTried && iRecipeUsed != -1 ) + { + m_iNewRecipeIndex = iRecipeUsed; + } + } + break; + + default: + { + // Craft failed in some way. + C_CTF_GameStats.Event_Crafting( IE_CRAFTING_FAILURE, NULL, m_iCraftingAttempts ); + OpenCraftingStatusDialog( this, "#CraftUpdate_Failed", false, true, false ); + CleanupPostCraft( m_iCurrentlySelectedRecipe != RECIPE_CUSTOM ); + } + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::ShowCraftFinish( void ) +{ + TFInventoryManager()->ShowItemsCrafted( &m_vecNewlyCraftedItems ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::OnTick( void ) +{ + BaseClass::OnTick(); + + if ( IsVisible() ) + { + if ( m_flAbortCraftingAt ) + { + if ( m_flAbortCraftingAt < vgui::system()->GetCurrentTime() ) + { + C_CTF_GameStats.Event_Crafting( IE_CRAFTING_TIMEOUT, NULL, m_iCraftingAttempts ); + CleanupPostCraft( m_iCurrentlySelectedRecipe != RECIPE_CUSTOM ); + OpenCraftingStatusDialog( this, "#CraftUpdate_Failed", false, true, false ); + return; + } + } + + if ( m_bWaitingForCraftItems ) + { + // If all the items in our newly crafted list are in the cache, we can show the pickup. + FOR_EACH_VEC_BACK( m_vecNewlyCraftedItems, i ) + { + CEconItemView* pNewItem = InventoryManager()->GetLocalInventory()->GetInventoryItemByItemID( m_vecNewlyCraftedItems[i] ); + if ( pNewItem == NULL ) + return; + C_CTF_GameStats.Event_Crafting( IE_CRAFTING_SUCCESS, pNewItem, m_iCraftingAttempts ); + } + + m_bWaitingForCraftItems = false; + + // We have all the new items, show the pickup + OpenCraftingStatusDialog( this, "#CraftUpdate_Success", false, true, true ); + CleanupPostCraft( true ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::CleanupPostCraft( bool bClearInputItems ) +{ + m_flAbortCraftingAt = 0; + m_bWaitingForCraftItems = false; + + UpdateSelectedRecipe( bClearInputItems ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ConVar *CCraftingPanel::GetExplanationConVar( void ) +{ + return &tf_explanations_craftingpanel; +} + +//================================================================================================================================ +// NOT CONNECTED TO STEAM WARNING DIALOG +//================================================================================================================================ +static vgui::DHANDLE<CCraftingStatusDialog> g_CraftingStatusPanel; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCraftingStatusDialog::CCraftingStatusDialog( vgui::Panel *pParent, const char *pElementName ) : BaseClass( pParent, "CraftingStatusDialog" ) +{ + m_pRecipePanel = vgui::SETUP_PANEL( new CItemModelPanel( this, "RecipeItemModelPanel" ) ); + m_bShowNewRecipe = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingStatusDialog::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + if ( m_bShowNewRecipe ) + { + LoadControlSettings( "resource/UI/NewRecipeFoundDialog.res" ); + } + else + { + LoadControlSettings( "resource/UI/CraftingStatusDialog.res" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingStatusDialog::OnCommand( const char *command ) +{ + bool bClose = false; + + if ( !Q_stricmp( command, "close" ) ) + { + // If we were a success, show the player their new crafted items + if ( m_bShowOnExit ) + { + if ( EconUI()->GetCraftingPanel() ) + { + EconUI()->GetCraftingPanel()->ShowCraftFinish(); + } + + m_bShowOnExit = false; + } + + bClose = true; + } + else if ( !Q_stricmp( command, "forceclose" ) ) + { + bClose = true; + } + + if ( bClose ) + { + m_bShowOnExit = false; + TFModalStack()->PopModal( this ); + SetVisible( false ); + MarkForDeletion(); + + EconUI()->SetPreventClosure( false ); + return; + } + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingStatusDialog::OnTick( void ) +{ + if ( !m_bAnimateEllipses || !IsVisible() ) + { + vgui::ivgui()->RemoveTickSignal( GetVPanel() ); + } + else + { + m_iNumEllipses = ((m_iNumEllipses+1) % 4); + } + + switch ( m_iNumEllipses ) + { + case 3: SetDialogVariable( "ellipses", L"..." ); break; + case 2: SetDialogVariable( "ellipses", L".." ); break; + case 1: SetDialogVariable( "ellipses", L"." ); break; + default: SetDialogVariable( "ellipses", L"" ); break; + } + + BaseClass::OnTick(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingStatusDialog::UpdateSchemeForVersion( bool bRecipe ) +{ + m_bShowNewRecipe = bRecipe; + InvalidateLayout( false, true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingStatusDialog::ShowStatusUpdate( bool bAnimateEllipses, bool bAllowClose, bool bShowOnExit ) +{ + m_bShowNewRecipe = false; + + CExButton *pButton = dynamic_cast<CExButton*>( FindChildByName("CloseButton") ); + if ( pButton ) + { + pButton->SetVisible( bAllowClose ); + pButton->SetEnabled( bAllowClose ); + } + + m_bAnimateEllipses = bAnimateEllipses; + if ( m_bAnimateEllipses ) + { + vgui::ivgui()->AddTickSignal( GetVPanel(), 500 ); + SetDialogVariable( "ellipses", L"" ); + m_iNumEllipses = 0; + } + else + { + vgui::ivgui()->RemoveTickSignal( GetVPanel() ); + SetDialogVariable( "ellipses", L"" ); + } + + m_bShowOnExit = bShowOnExit; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void SetupCraftingStatusDialog( vgui::Panel *pParent ) +{ + if (!g_CraftingStatusPanel.Get()) + { + g_CraftingStatusPanel = vgui::SETUP_PANEL( new CCraftingStatusDialog( pParent, NULL ) ); + } + g_CraftingStatusPanel->SetVisible( true ); + g_CraftingStatusPanel->MakePopup(); + g_CraftingStatusPanel->MoveToFront(); + g_CraftingStatusPanel->SetKeyBoardInputEnabled(true); + g_CraftingStatusPanel->SetMouseInputEnabled(true); + TFModalStack()->PushModal( g_CraftingStatusPanel ); + + EconUI()->SetPreventClosure( true ); +} + +CCraftingStatusDialog *OpenCraftingStatusDialog( vgui::Panel *pParent, const char *pszText, bool bAnimateEllipses, bool bAllowClose, bool bShowOnExit ) +{ + SetupCraftingStatusDialog( pParent ); + g_CraftingStatusPanel->UpdateSchemeForVersion( false ); + g_CraftingStatusPanel->SetDialogVariable( "updatetext", g_pVGuiLocalize->Find( pszText ) ); + g_CraftingStatusPanel->ShowStatusUpdate( bAnimateEllipses, bAllowClose, bShowOnExit ); + return g_CraftingStatusPanel; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CloseCraftingStatusDialog( void ) +{ + if ( g_CraftingStatusPanel ) + { + g_CraftingStatusPanel->OnCommand( "forceclose" ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: GC Msg handler to receive the craft response +//----------------------------------------------------------------------------- +class CGCCraftResponse : public GCSDK::CGCClientJob +{ +public: + CGCCraftResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket ); + + CUtlVector<uint64> vecCraftedIndices; + uint16 iItems = 0; + if ( !msg.BReadUint16Data( &iItems ) ) + return true; + vecCraftedIndices.SetSize( iItems ); + for ( int i = 0; i < iItems; i++ ) + { + if( !msg.BReadUint64Data( &vecCraftedIndices[i] ) ) + return true; + } + + if ( EconUI()->GetCraftingPanel() ) + { + EconUI()->GetCraftingPanel()->OnCraftResponse( (EGCMsgResponse)msg.Body().m_eResponse, &vecCraftedIndices, msg.Body().m_nResponseIndex ); + } + + //Msg("RECEIVED CGCCraftResponse: %d\n", msg.Body().m_eResponse ); + return true; + } + +}; + +GC_REG_JOB( GCSDK::CGCClient, CGCCraftResponse, "CGCCraftResponse", k_EMsgGCCraftResponse, GCSDK::k_EServerTypeGCClient ); + + + +//----------------------------------------------------------------------------- +// Purpose: GC Msg handler to receive the Golden Wrench broadcast message +//----------------------------------------------------------------------------- +class CGCGoldenWrenchBroadcast : public GCSDK::CGCClientJob +{ +public: + CGCGoldenWrenchBroadcast( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + GCSDK::CProtoBufMsg<CMsgTFGoldenWrenchBroadcast> msg( pNetPacket ); + + // @todo Tom Bui: should we display this in some other manner? This gets covered up by the crafting panel. + CHudNotificationPanel *pNotifyPanel = GET_HUDELEMENT( CHudNotificationPanel ); + if ( pNotifyPanel ) + { + bool bDeleted = msg.Body().deleted(); + wchar_t szPlayerName[1024]; + g_pVGuiLocalize->ConvertANSIToUnicode( msg.Body().user_name().c_str(), szPlayerName, sizeof(szPlayerName) ); + wchar_t szWrenchNumber[16]=L""; + _snwprintf( szWrenchNumber, ARRAYSIZE( szWrenchNumber ), L"%i", msg.Body().wrench_number() ); + wchar_t szNotification[1024]=L""; + g_pVGuiLocalize->ConstructString_safe( szNotification, + g_pVGuiLocalize->Find( bDeleted ? "#TF_HUD_Event_GoldenWrench_D": "#TF_HUD_Event_GoldenWrench_C" ), + 2, szPlayerName, szWrenchNumber ); + pNotifyPanel->SetupNotifyCustom( szNotification, HUD_NOTIFY_GOLDEN_WRENCH, 10.0f ); + + // echo to chat + CBaseHudChat *pHUDChat = (CBaseHudChat *)GET_HUDELEMENT( CHudChat ); + if ( pHUDChat ) + { + char szAnsi[1024]; + g_pVGuiLocalize->ConvertUnicodeToANSI( szNotification, szAnsi, sizeof(szAnsi) ); + + pHUDChat->Printf( CHAT_FILTER_NONE, "%s", szAnsi ); + } + + // play a sound + vgui::surface()->PlaySound( bDeleted ? "vo/announcer_failure.mp3" : "vo/announcer_success.mp3" ); + } + + //Msg("RECEIVED CGCCraftResponse: %d\n", msg.Body().m_eResponse ); + return true; + } + +}; + +GC_REG_JOB( GCSDK::CGCClient, CGCGoldenWrenchBroadcast, "CGCGoldenWrenchBroadcast", k_EMsgGCGoldenWrenchBroadcast, GCSDK::k_EServerTypeGCClient ); + + +//----------------------------------------------------------------------------- +// Purpose: GC Msg handler to receive the Saxxy broadcast message +//----------------------------------------------------------------------------- +class CGSaxxyBroadcast : public GCSDK::CGCClientJob +{ +public: + CGSaxxyBroadcast( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + GCSDK::CProtoBufMsg<CMsgTFSaxxyBroadcast> msg( pNetPacket ); + + CEconNotification *pNotification = new CEconNotification(); + pNotification->SetText( "#TF_Event_Saxxy_Deleted" ); + pNotification->SetLifetime( 30.0f ); + + { + // Who deleted this? + wchar_t wszPlayerName[ MAX_PLAYER_NAME_LENGTH ]; + g_pVGuiLocalize->ConvertANSIToUnicode( msg.Body().has_user_name() ? msg.Body().user_name().c_str() : NULL, wszPlayerName, sizeof( wszPlayerName ) ); + pNotification->AddStringToken( "owner", wszPlayerName ); + + // What category was the Saxxy for? + char szCategory[MAX_ATTRIBUTE_DESCRIPTION_LENGTH]; + Q_snprintf( szCategory, sizeof( szCategory ), "Replay_Contest_Category%d", msg.Body().category_number() ); + + pNotification->AddStringToken( "category", g_pVGuiLocalize->Find( szCategory ) ); + } + + NotificationQueue_Add( pNotification ); + + return true; + } + +}; + +GC_REG_JOB( GCSDK::CGCClient, CGSaxxyBroadcast, "CGSaxxyBroadcast", k_EMsgGCSaxxyBroadcast, GCSDK::k_EServerTypeGCClient ); + +//----------------------------------------------------------------------------- +// Purpose: GC Msg handler to receive any generic item deletion notification +//----------------------------------------------------------------------------- +class CClientItemBroadcastNotificationJob : public GCSDK::CGCClientJob +{ +public: + CClientItemBroadcastNotificationJob( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + GCSDK::CProtoBufMsg<CMsgGCTFSpecificItemBroadcast> msg( pNetPacket ); + + CEconNotification *pNotification = new CEconNotification(); + pNotification->SetText( msg.Body().was_destruction() ? "#TF_Event_Item_Deleted" : "#TF_Event_Item_Created" ); + pNotification->SetLifetime( 30.0f ); + + // Who deleted this? + wchar_t wszPlayerName[ MAX_PLAYER_NAME_LENGTH ]; + g_pVGuiLocalize->ConvertANSIToUnicode( msg.Body().has_user_name() ? msg.Body().user_name().c_str() : NULL, wszPlayerName, sizeof( wszPlayerName ) ); + pNotification->AddStringToken( "owner", wszPlayerName ); + + // What type of item was this? + const CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinition( msg.Body().item_def_index() ); + if ( pItemDef ) + { + pNotification->AddStringToken( "item_name", g_pVGuiLocalize->Find( pItemDef->GetItemBaseName() ) ); + + NotificationQueue_Add( pNotification ); + } + + return true; + } + +}; + +GC_REG_JOB( GCSDK::CGCClient, CClientItemBroadcastNotificationJob, "CClientItemBroadcastNotificationJob", k_EMsgGCTFSpecificItemBroadcast, GCSDK::k_EServerTypeGCClient ); + +//----------------------------------------------------------------------------- +// Purpose: GC Msg handler to receive the Saxxy Awarded broadcast message +//----------------------------------------------------------------------------- +class CGSaxxyAwardedBroadcast : public GCSDK::CGCClientJob +{ +private: + // embedded notification for custom trigger + class CSaxxyAwardedNotification : public CEconNotification + { + public: + CSaxxyAwardedNotification() + { + SetSoundFilename( "vo/announcer_success.mp3" ); + } + + virtual EType NotificationType() { return eType_Trigger; } + + virtual void Trigger() + { + if ( steamapicontext && steamapicontext->SteamFriends() ) + { + steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( "http://www.teamfortress.com/saxxyawards/winners.php" ); + } + MarkForDeletion(); + } + }; + +public: + + CGSaxxyAwardedBroadcast( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + GCSDK::CProtoBufMsg< CMsgSaxxyAwarded > msg( pNetPacket ); + + CEconNotification *pNotification = new CSaxxyAwardedNotification(); + pNotification->SetText( "#TF_Event_Saxxy_Awarded" ); + pNotification->SetLifetime( 30.0f ); + + { + // Winners + CFmtStr1024 strWinners; + for ( int i = 0; i < msg.Body().winner_names_size(); ++i ) + { + strWinners.Append( msg.Body().winner_names( i ).c_str() ); + if ( i + 1 < msg.Body().winner_names_size() ) + { + strWinners.Append( "\n" ); + } + } + wchar_t wszPlayerNames[ 1024 ]; + g_pVGuiLocalize->ConvertANSIToUnicode( strWinners.Access(), wszPlayerNames, sizeof( wszPlayerNames ) ); + pNotification->AddStringToken( "winners", wszPlayerNames ); + + // year + CRTime cTime; + cTime.SetToCurrentTime(); + cTime.SetToGMT( false ); + locchar_t wszYear[10]; + loc_sprintf_safe( wszYear, LOCCHAR( "%04u" ), cTime.GetYear() ); + pNotification->AddStringToken( "year", wszYear ); + + // What category was the Saxxy for? + char szCategory[MAX_ATTRIBUTE_DESCRIPTION_LENGTH]; + Q_snprintf( szCategory, sizeof( szCategory ), "Replay_Contest_Category%d", msg.Body().category() ); + pNotification->AddStringToken( "category", g_pVGuiLocalize->Find( szCategory ) ); + } + + NotificationQueue_Add( pNotification ); + + return true; + } + +}; + +GC_REG_JOB( GCSDK::CGCClient, CGSaxxyAwardedBroadcast, "CGSaxxyAwardedBroadcast", k_EMsgGCSaxxy_Awarded, GCSDK::k_EServerTypeGCClient ); + +//----------------------------------------------------------------------------- +// Purpose: GC Msg handler to receive a generic system broadcast message +//----------------------------------------------------------------------------- +class CGCSystemMessageBroadcast : public GCSDK::CGCClientJob +{ +public: + CGCSystemMessageBroadcast( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + CBaseHudChat *pHUDChat = (CBaseHudChat *)GET_HUDELEMENT( CHudChat ); + if ( !pHUDChat ) + return false; + + GCSDK::CProtoBufMsg<CMsgSystemBroadcast> msg( pNetPacket ); + + // retrieve the text + const char *pchMessage = msg.Body().message().c_str(); + wchar_t *pwMessage = g_pVGuiLocalize->Find( pchMessage ); + wchar_t wszConvertedText[2048] = L""; + if ( pwMessage == NULL ) + { + g_pVGuiLocalize->ConvertANSIToUnicode( pchMessage, wszConvertedText, sizeof( wszConvertedText ) ); + pwMessage = wszConvertedText; + } + + Color color( 0xff, 0xcc, 0x33, 255 ); + KeyValuesAD keyValues( "System Message" ); + keyValues->SetWString( "message", pwMessage ); + keyValues->SetColor( "custom_color", color ); + + // print to chat log + wchar_t wszLocalizedString[2048] = L""; + g_pVGuiLocalize->ConstructString_safe( wszLocalizedString, "#Notification_System_Message", keyValues ); + pHUDChat->SetCustomColor( color ); + pHUDChat->Printf( CHAT_FILTER_NONE, "%ls", wszLocalizedString ); + + // send to notification + CEconNotification* pNotification = new CEconNotification(); + pNotification->SetText( "#Notification_System_Message" ); + pNotification->SetKeyValues( keyValues ); + pNotification->SetLifetime( 30.0f ); + pNotification->SetSoundFilename( "ui/system_message_alert.wav" ); + NotificationQueue_Add( pNotification ); + + return true; + } + +}; + +GC_REG_JOB( GCSDK::CGCClient, CGCSystemMessageBroadcast, "CGCSystemMessageBroadcast", k_EMsgGCSystemMessage, GCSDK::k_EServerTypeGCClient ); diff --git a/game/client/tf/vgui/crafting_panel.h b/game/client/tf/vgui/crafting_panel.h new file mode 100644 index 0000000..5730615 --- /dev/null +++ b/game/client/tf/vgui/crafting_panel.h @@ -0,0 +1,202 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef CRAFTING_PANEL_H +#define CRAFTING_PANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "backpack_panel.h" +#include "vgui_controls/ScrollableEditablePanel.h" +#include "tf_gcmessages.h" +#include "econ_gcmessages.h" +#include "tf_imagepanel.h" +#include "tf_controls.h" +#include "item_selection_panel.h" + +class CImageButton; + +// Crafting slots on crafting page +#define CRAFTING_SLOTS_INPUT_ROWS 3 +#define CRAFTING_SLOTS_INPUT_COLUMNS 4 +#define CRAFTING_SLOTS_INPUTPANELS (CRAFTING_SLOTS_INPUT_ROWS * CRAFTING_SLOTS_INPUT_COLUMNS) +#define CRAFTING_SLOTS_OUTPUT_ROWS 1 +#define CRAFTING_SLOTS_OUTPUT_COLUMNS 4 +#define CRAFTING_SLOTS_COUNT (CRAFTING_SLOTS_INPUTPANELS + (CRAFTING_SLOTS_OUTPUT_ROWS * CRAFTING_SLOTS_OUTPUT_COLUMNS)) + +#define RECIPE_CUSTOM -2 + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CRecipeButton : public CExButton +{ +private: + DECLARE_CLASS_SIMPLE( CRecipeButton, CExButton ); + +public: + CRecipeButton( vgui::Panel *parent, const char *name, const char *text, vgui::Panel *pActionSignalTarget = NULL, const char *cmd = NULL ) + : CExButton( parent, name, text, pActionSignalTarget, cmd ) + { + } + + virtual void ApplySettings( KeyValues *inResourceData ) + { + BaseClass::ApplySettings( inResourceData ); + SetEnabled( m_iRecipeDefIndex != -1 ); + } + + void SetDefIndex( int iIndex ) + { + m_iRecipeDefIndex = iIndex; + SetEnabled( m_iRecipeDefIndex != -1 ); + } + + void OnCursorEntered( void ) + { + PostActionSignal( new KeyValues("RecipePanelEntered") ); + BaseClass::OnCursorEntered(); + } + + void OnCursorExited( void ) + { + PostActionSignal( new KeyValues("RecipePanelExited") ); + BaseClass::OnCursorExited(); + } + + +public: + int m_iRecipeDefIndex; +}; + +//----------------------------------------------------------------------------- +// An inventory screen that handles displaying the crafting screen +//----------------------------------------------------------------------------- +class CCraftingPanel : public CBaseLoadoutPanel +{ + DECLARE_CLASS_SIMPLE( CCraftingPanel, CBaseLoadoutPanel ); +public: + CCraftingPanel( vgui::Panel *parent, const char *panelName ); + ~CCraftingPanel( void ); + + virtual const char *GetResFile( void ) { return "Resource/UI/CraftingPanel.res"; } + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void ApplySettings( KeyValues *inResourceData ); + virtual void PerformLayout( void ); + virtual void OnShowPanel( bool bVisible, bool bReturningFromArmory ); + virtual void OnCommand( const char *command ); + + void CreateRecipeFilterButtons( void ); + void UpdateRecipeFilter( void ); + + virtual int GetNumItemPanels( void ) { return CRAFTING_SLOTS_COUNT; }; + + bool IsInputItemPanel( int iSlot ) { return (iSlot < CRAFTING_SLOTS_INPUTPANELS); } + virtual void PositionItemPanel( CItemModelPanel *pPanel, int iIndex ); + int GetItemPanelIndex( CItemModelPanel *pItemPanel ); + + void UpdateSelectedRecipe( bool bClearInputItems ); + void UpdateRecipeItems( bool bClearInputItems ); + void UpdateCraftButton( void ); + + const char *GetItemTextForCriteria( const CItemSelectionCriteria *pCriteria ); + CEconItemDefinition *GetItemDefFromCriteria( const CItemSelectionCriteria *pCriteria ); + virtual void AddNewItemPanel( int iPanelIndex ); + virtual void UpdateModelPanels( void ); + void SetButtonToRecipe( int iButton, int iDefIndex, wchar_t *pszText ); + + bool CheckForUntradableItems( void ); + void Craft( void ); + void OnCraftResponse( EGCMsgResponse eResponse, CUtlVector<uint64> *vecCraftedIndices, int iRecipeUsed ); + void ShowCraftFinish( void ); + virtual void OnTick( void ); + void CleanupPostCraft( bool bClearInputItems ); + + MESSAGE_FUNC_PTR( OnItemPanelMousePressed, "ItemPanelMousePressed", panel ); + MESSAGE_FUNC_PTR( OnRecipePanelEntered, "RecipePanelEntered", panel ); + MESSAGE_FUNC_PTR( OnRecipePanelExited, "RecipePanelExited", panel ); + MESSAGE_FUNC( OnCancelSelection, "CancelSelection" ); + MESSAGE_FUNC_PARAMS( OnSelectionReturned, "SelectionReturned", data ); + + MESSAGE_FUNC( OnClosing, "Closing" ); + + virtual ConVar *GetExplanationConVar( void ); + +private: + // Items in the input model panels + itemid_t m_InputItems[CRAFTING_SLOTS_INPUTPANELS]; + const CItemSelectionCriteria *m_ItemPanelCriteria[CRAFTING_SLOTS_INPUTPANELS]; + + CExButton *m_pCraftButton; + CExButton *m_pUpgradeButton; + CExLabel *m_pFreeAccountLabel; + vgui::EditablePanel *m_pRecipeListContainer; + vgui::ScrollableEditablePanel *m_pRecipeListContainerScroller; + vgui::EditablePanel *m_pSelectedRecipeContainer; + + KeyValues *m_pRecipeButtonsKV; + CUtlVector<CRecipeButton*> m_pRecipeButtons; + KeyValues *m_pRecipeFilterButtonsKV; + CUtlVector<CImageButton*> m_pRecipeFilterButtons; + + int m_iCurrentlySelectedRecipe; + int m_iCurrentRecipeTotalInputs; + int m_iCurrentRecipeTotalOutputs; + recipecategories_t m_iRecipeCategoryFilter; + + CUtlVector<itemid_t> m_vecNewlyCraftedItems; + double m_flAbortCraftingAt; + bool m_bWaitingForCraftItems; + int m_iRecipeIndexTried; + int m_iNewRecipeIndex; + bool m_bEventLogging; + int m_iCraftingAttempts; + + CTFTextToolTip *m_pToolTip; + vgui::EditablePanel *m_pToolTipEmbeddedPanel; + + CCraftingItemSelectionPanel *m_pSelectionPanel; + int m_iSelectingForSlot; + + CPanelAnimationVarAliasType( int, m_iItemCraftingOffcenterX, "item_crafting_offcenter_x", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iFilterOffcenterX, "filter_xoffset", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iFilterYPos, "filter_ypos", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iFilterDeltaX, "filter_xdelta", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iFilterDeltaY, "filter_ydelta", "0", "proportional_int" ); + + CPanelAnimationVarAliasType( int, m_iOutputItemYPos, "output_item_ypos", "0", "proportional_int" ); +}; + +//----------------------------------------------------------------------------- +// Purpose: A dialog used to show the current state of a crafting request. +//----------------------------------------------------------------------------- +class CCraftingStatusDialog : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CCraftingStatusDialog, vgui::EditablePanel ); + +public: + CCraftingStatusDialog( vgui::Panel *pParent, const char *pElementName ); + + virtual void ApplySchemeSettings( vgui::IScheme *scheme ); + virtual void OnCommand( const char *command ); + virtual void OnTick( void ); + void UpdateSchemeForVersion( bool bRecipe ); + void ShowStatusUpdate( bool bAnimateEllipses, bool bAllowed, bool bShowOnExit ); + +private: + bool m_bShowOnExit; + bool m_bAnimateEllipses; + int m_iNumEllipses; + bool m_bShowNewRecipe; + CItemModelPanel *m_pRecipePanel; +}; +CCraftingStatusDialog *OpenCraftingStatusDialog( vgui::Panel *pParent, const char *pszText, bool bAnimateEllipses, bool bAllowClose, bool bShowOnExit ); +CCraftingStatusDialog *OpenNewRecipeFoundDialog( vgui::Panel *pParent, const CEconCraftingRecipeDefinition *pRecipeDef ); +void CloseCraftingStatusDialog( void ); + +#endif // CRAFTING_PANEL_H diff --git a/game/client/tf/vgui/crate_detail_panels.cpp b/game/client/tf/vgui/crate_detail_panels.cpp new file mode 100644 index 0000000..dcde85d --- /dev/null +++ b/game/client/tf/vgui/crate_detail_panels.cpp @@ -0,0 +1,388 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "crate_detail_panels.h" +#include "vgui_controls/TextImage.h" +#include "econ_gcmessages.h" +#include "gc_clientsystem.h" +#include "econ_ui.h" +#include <vgui/ISurface.h> +#include "econ_item_inventory.h" +#include "econ/tool_items/tool_items.h" + +#define SHUFFLE_TIME 5.f + +float CInputStringForItemBackpackOverlayDialog::m_sflNextShuffleTime = 0.f; +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CInputStringForItemBackpackOverlayDialog::CInputStringForItemBackpackOverlayDialog( vgui::Panel *pParent, CEconItemView *pItem, CEconItemView *pChosenKey ) + : vgui::EditablePanel( pParent, "InputStringForItemBackpackOverlayDialog" ) + , m_Item( *pItem ) + , m_pPreviewModelPanel( NULL ) + , m_pTextEntry( NULL ) + , m_pItemModelPanelKVs( NULL ) + , m_bUpdateRecieved( false ) +{ + if ( pChosenKey ) + { + m_UseableKey = *pChosenKey; + } + m_pPreviewModelPanel = new CItemModelPanel( this, "preview_model" ); + m_pTextEntry = new vgui::TextEntry( this, "TextEntryControl" ); + m_pShuffleButton = new CExButton( this, "ShuffleButton", "Shuffle" ); + m_pRareLootLabel = new CExLabel( this, "RareLootLabel", "#Econ_Revolving_Loot_List_Rare_Item" ); + m_pProgressBar = new vgui::ProgressBar( this, "ShuffleProgress" ); + m_pGetKeyButton = new CExButton( this, "GetKeyButton", "getkey" ); + m_pUseKeyButton = new CExButton( this, "UseKeyButton", "usekey" ); + + m_pMouseOverItemPanel = vgui::SETUP_PANEL( new CItemModelPanel( this, "mouseoveritempanel" ) ); + m_pMouseOverTooltip = new CItemModelPanelToolTip( this ); + m_pMouseOverTooltip->SetupPanels( this, m_pMouseOverItemPanel ); + + ListenForGameEvent( "inventory_updated" ); +} + +CInputStringForItemBackpackOverlayDialog::~CInputStringForItemBackpackOverlayDialog() +{ + if ( m_pItemModelPanelKVs ) + { + m_pItemModelPanelKVs->deleteThis(); + m_pItemModelPanelKVs = NULL; + } + + m_vecContentsPanels.PurgeAndDeleteElements(); +} + +void CInputStringForItemBackpackOverlayDialog::FireGameEvent( IGameEvent *event ) +{ + // If we're not visible, ignore all events + if ( !IsVisible() ) + return; + + // Something caused our inventory to update. Assuming it was from our shuffle + // then we need to update ourselves. + const char *type = event->GetName(); + if ( Q_strcmp( "inventory_updated", type ) == 0 ) + { + m_bUpdateRecieved = true; + } +} + +void CInputStringForItemBackpackOverlayDialog::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "Resource/UI/econ/InputStringForItemBackpackOverlayDialog.res" ); + + CCrateLootListWrapper itemWrapper( &m_Item ); + const IEconLootList *pLootList = itemWrapper.GetEconLootList(); + // Set the crate footer text. The crate itself specifies what to use. + if ( pLootList->GetLootListFooterLocalizationKey() ) + { + m_pRareLootLabel->SetText( pLootList->GetLootListFooterLocalizationKey() ); + } + else + { + const char *pszRareLootListFooterLocalizationKey = m_Item.GetItemDefinition()->GetDefinitionString( "loot_list_rare_item_footer", "#Econ_Revolving_Loot_List_Rare_Item" ); + m_pRareLootLabel->SetText( pszRareLootListFooterLocalizationKey ); + } + + // Use the gradient border for the tooltip + m_pMouseOverItemPanel->SetBorder( pScheme->GetBorder("LoadoutItemPopupBorder") ); + + m_pPreviewModelPanel->SetItem( &m_Item ); + m_pPreviewModelPanel->SetActAsButton( false, false ); // Dont mess around with the mouse + + m_pTextEntry->RequestFocus(); +} + +void CInputStringForItemBackpackOverlayDialog::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + // Pull out the model panel KVs for this panel + KeyValues *pItemKV = inResourceData->FindKey( "modelpanels_kv" ); + if ( pItemKV ) + { + if ( m_pItemModelPanelKVs ) + { + m_pItemModelPanelKVs->deleteThis(); + } + m_pItemModelPanelKVs = new KeyValues( "modelpanels_kv" ); + pItemKV->CopySubkeys( m_pItemModelPanelKVs ); + } + + CreateItemPanels(); +} + +void CInputStringForItemBackpackOverlayDialog::CreateItemPanels() +{ + CCrateLootListWrapper itemWrapper( &m_Item ); + const IEconLootList *pLootList = itemWrapper.GetEconLootList(); + + class CItemDefLootListIterator : public IEconLootList::IEconLootListIterator + { + public: + CItemDefLootListIterator( CUtlVector< item_definition_index_t > *pVecItemDefs ) + : m_pVecItemDefs( pVecItemDefs ) + {} + + virtual void OnIterate( item_definition_index_t unItemDefIndex ) OVERRIDE + { + const CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinition( unItemDefIndex ); + if ( pItemDef && pItemDef->BValidForShuffle() ) + { + m_pVecItemDefs->AddToTail( unItemDefIndex ); + } + } + + private: + CUtlVector< item_definition_index_t > * const m_pVecItemDefs; + }; + + // Get the drops from the item + CUtlVector< item_definition_index_t > vecItemDefs; + CItemDefLootListIterator it( &vecItemDefs ); + pLootList->EnumerateUserFacingPotentialDrops( &it ); + + if ( !m_pItemModelPanelKVs ) + return; + + if ( m_vecContentsPanels.Count() != vecItemDefs.Count() ) + { + m_vecContentsPanels.PurgeAndDeleteElements(); + + FOR_EACH_VEC( vecItemDefs, i ) + { + // Create new panel + CItemModelPanel* pItemPanel = m_vecContentsPanels[ m_vecContentsPanels.AddToTail( new CItemModelPanel( this, CFmtStr( "item_preview_%d", i ) ) ) ]; + pItemPanel->ApplySettings( m_pItemModelPanelKVs ); + pItemPanel->InvalidateLayout( true ); + pItemPanel->SetActAsButton( false, true ); // Lets us get mouse enter/exit evens for tooltips + pItemPanel->SetTooltip( m_pMouseOverTooltip, "" ); // Tooltip panel to use + } + } + + // Create the panels and set the items into them + FOR_EACH_VEC( vecItemDefs, i ) + { + const item_definition_index_t &itemDef = vecItemDefs[i]; + + CItemModelPanel* pItemPanel = m_vecContentsPanels[i]; + + CEconItemView item; + item.SetItemDefIndex( itemDef ); + item.SetItemQuality( AE_UNIQUE ); // Unique by default + item.SetItemLevel( 0 ); // Hide this? + item.SetInitialized( true ); + item.SetItemOriginOverride( kEconItemOrigin_Invalid ); + + pItemPanel->SetItem( &item ); + } +} + +void CInputStringForItemBackpackOverlayDialog::PerformLayout( void ) +{ + BaseClass::PerformLayout(); + + // Find out how wide these panels will be side by side + const int nBuffer = 5; + int nTotalWide = 0; + const int nCount = m_vecContentsPanels.Count(); + if ( nCount ) + { + const int nWide = m_vecContentsPanels.Head()->GetWide(); + nTotalWide = (nCount * nWide) + ( (nCount - 1) * nBuffer ); + } + + // Find out how much space the panels take up within the parent + int nParentWide = GetWide(); + int nDiff = nParentWide - nTotalWide; + // How far we need to offset from the left edge + int nStartOffset = nDiff / 2; + + // Place all the panels side by side + FOR_EACH_VEC( m_vecContentsPanels, i ) + { + CItemModelPanel* pItemPanel = m_vecContentsPanels[ i ]; + + const int nWide = pItemPanel->GetWide(); + + pItemPanel->SetPos( nStartOffset + i * (nWide + nBuffer), YRES(150) ); + pItemPanel->SetVisible( true ); + } + + // Which button to show + m_pUseKeyButton->SetVisible( m_UseableKey.IsValid() ); + m_pGetKeyButton->SetVisible( !m_UseableKey.IsValid() ); +} + +void CInputStringForItemBackpackOverlayDialog::OnCommand( const char *command ) +{ + if ( !Q_strnicmp( command, "cancel", 6 ) ) + { + TFModalStack()->PopModal( this ); + + SetVisible( false ); + MarkForDeletion(); + } + else if ( !Q_strnicmp( command, "shuffle", 7 ) ) + { + // let the GC know + if ( m_pTextEntry && Plat_FloatTime() >= m_sflNextShuffleTime ) + { + // Set the next time they can send a request to shuffle + m_sflNextShuffleTime = Plat_FloatTime() + SHUFFLE_TIME; + + enum { kMaxCodeStringSize = 32 }; + char szText[ kMaxCodeStringSize ] = { 0 }; + m_pTextEntry->GetText( &szText[0], sizeof( szText ) ); + + GCSDK::CProtoBufMsg<CMsgGCShuffleCrateContents> msg( k_EMsgGCShuffleCrateContents ); + + msg.Body().set_crate_item_id( m_Item.GetID() ); + msg.Body().set_user_code_string( szText ); + + GCClientSystem()->BSendMessage( msg ); + + m_pProgressBar->SetProgress( 0.f ); + m_pProgressBar->SetVisible( true ); + m_pTextEntry->SetVisible( false ); + + vgui::surface()->PlaySound( "ui/itemcrate_shuffle.wav" ); + } + } + else if ( !Q_strnicmp( command, "getkey", 6 ) ) + { + static CSchemaAttributeDefHandle pAttrDef_DecodedBy( "decoded by itemdefindex" ); + + uint32 iDecodableItemDef = 0; + if ( m_Item.FindAttribute( pAttrDef_DecodedBy, &iDecodableItemDef ) ) + { + // casting to the proper type since our econ system is dumb + const float& value_as_float = (float&)iDecodableItemDef; + EconUI()->CloseEconUI(); + EconUI()->OpenStorePanel( (int)value_as_float, false ); + + // close ourselves + TFModalStack()->PopModal( this ); + SetVisible( false ); + MarkForDeletion(); + } + } + else if ( !Q_strnicmp( command, "usekey", 6 ) ) + { + if ( m_UseableKey.IsValid() ) + { + // Use the key + ApplyTool( GetParent(), &m_UseableKey, &m_Item ); + // close ourselves + TFModalStack()->PopModal( this ); + SetVisible( false ); + MarkForDeletion(); + } + } +} + +void CInputStringForItemBackpackOverlayDialog::FindUsableKey() +{ + static CSchemaAttributeDefHandle pAttrDef_DecodedBy( "decoded by itemdefindex" ); + + uint32 iDecodableItemDef = 0; + if ( m_Item.FindAttribute( pAttrDef_DecodedBy, &iDecodableItemDef ) ) + { + const float& value_as_float = (float&)iDecodableItemDef; + iDecodableItemDef = (float)value_as_float; + CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory(); + if ( !pInventory ) + return; + + for ( int i = 0; i < pInventory->GetItemCount(); i++ ) + { + CEconItemView *pItem = pInventory->GetItem(i); + if ( pItem->GetItemDefIndex() == iDecodableItemDef ) + { + m_UseableKey = *pItem; + } + } + } +} + +void CInputStringForItemBackpackOverlayDialog::OnThink() +{ + float flDelta = m_sflNextShuffleTime - Plat_FloatTime(); + + // If we're ready, show "Shuffle" + if ( flDelta < 0 ) + { + // Show the text entry, show the progress bar + m_pProgressBar->SetVisible( false ); + m_pTextEntry->SetVisible( true ); + + // Re-enable the shuffle/use buttons + m_pShuffleButton->SetEnabled( m_pTextEntry->GetTextLength() != 0 ); + m_pUseKeyButton->SetEnabled( true ); + // Say "Shuffle" + m_pShuffleButton->SetText( "#ShuffleContents" ); + + // We got a inventory update message, update + if ( m_bUpdateRecieved ) + { + CreateItemPanels(); + m_bUpdateRecieved = false; + } + } + else + { + // Show the progress bar, hide the text field + m_pProgressBar->SetVisible( true ); + m_pTextEntry->SetVisible( false ); + + // Dont allow clicking the shuffle or use key button + m_pShuffleButton->SetEnabled( false ); + m_pUseKeyButton->SetEnabled( false ); + // Say "Shuffling..." + m_pShuffleButton->SetText( "#ShufflingContents" ); + + // Set progress + float flProgress = ( SHUFFLE_TIME - flDelta ) / SHUFFLE_TIME; + m_pProgressBar->SetProgress( flProgress ); + } +} + +void CInputStringForItemBackpackOverlayDialog::Show() +{ + SetVisible( true ); + MakePopup(); + MoveToFront(); + SetKeyBoardInputEnabled( true ); + SetMouseInputEnabled( true ); + TFModalStack()->PushModal( this ); + + // If a key wasnt passed in, find the first one in the + // player's inventory + if ( !m_UseableKey.IsValid() ) + { + FindUsableKey(); + } + + // Which button to show + m_pUseKeyButton->SetVisible( m_UseableKey.IsValid() ); + m_pGetKeyButton->SetVisible( !m_UseableKey.IsValid() ); + + // Put the current gen code of the crate into the text field + static CSchemaAttributeDefHandle pAttrDef_DecodedBy( "crate generation code" ); + const char *pszAttrGenCode; + if ( FindAttribute_UnsafeBitwiseCast<CAttribute_String>( &m_Item, pAttrDef_DecodedBy, &pszAttrGenCode ) ) + { + m_pTextEntry->SetText( pszAttrGenCode ); + } +} + + diff --git a/game/client/tf/vgui/crate_detail_panels.h b/game/client/tf/vgui/crate_detail_panels.h new file mode 100644 index 0000000..9d10c57 --- /dev/null +++ b/game/client/tf/vgui/crate_detail_panels.h @@ -0,0 +1,62 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef CRATE_DETAIL_PANELS_H +#define CRATE_DETAIL_PANELS_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_controls.h" +#include "item_model_panel.h" +#include "econ_item_view.h" +#include <vgui_controls/TextEntry.h> +#include <vgui_controls/ProgressBar.h> + +class CInputStringForItemBackpackOverlayDialog : public vgui::EditablePanel, public CGameEventListener +{ + DECLARE_CLASS_SIMPLE( CInputStringForItemBackpackOverlayDialog, vgui::EditablePanel ); + +public: + CInputStringForItemBackpackOverlayDialog( vgui::Panel *pParent, CEconItemView *pItem, CEconItemView *pChosenKey = NULL ); + ~CInputStringForItemBackpackOverlayDialog(); + + virtual void FireGameEvent( IGameEvent *event ) OVERRIDE; + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE; + virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE; + virtual void PerformLayout( void ) OVERRIDE; + virtual void OnCommand( const char *command ) OVERRIDE; + virtual void OnThink() OVERRIDE; + + void Show(); + +protected: + CItemModelPanel *GetPreviewModelPanel() { return m_pPreviewModelPanel; } + + CEconItemView m_Item; + +private: + void CreateItemPanels(); + void FindUsableKey(); + + vgui::ProgressBar *m_pProgressBar; + CExLabel *m_pRareLootLabel; + CExButton *m_pUseKeyButton; + CExButton *m_pGetKeyButton; + CExButton *m_pShuffleButton; + CItemModelPanel *m_pPreviewModelPanel; + vgui::TextEntry *m_pTextEntry; + CUtlVector< CItemModelPanel* > m_vecContentsPanels; + KeyValues *m_pItemModelPanelKVs; + CItemModelPanelToolTip *m_pMouseOverTooltip; + CItemModelPanel *m_pMouseOverItemPanel; + static float m_sflNextShuffleTime; + bool m_bUpdateRecieved; + CEconItemView m_UseableKey; +}; + +#endif // CRATE_DETAIL_PANELS_H diff --git a/game/client/tf/vgui/drawing_panel.cpp b/game/client/tf/vgui/drawing_panel.cpp new file mode 100644 index 0000000..f6e764a --- /dev/null +++ b/game/client/tf/vgui/drawing_panel.cpp @@ -0,0 +1,335 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "drawing_panel.h" +#include "softline.h" +#include <vgui/IScheme.h> +#include <vgui/IVGui.h> +#include "voice_status.h" +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +extern ConVar cl_mute_all_comms; + +Color g_DrawPanel_TeamColors[TF_TEAM_COUNT] = +{ + COLOR_TF_SPECTATOR, // unassigned + COLOR_TF_SPECTATOR, // spectator + COLOR_TF_RED, // red + COLOR_TF_BLUE, // blue +}; + +DECLARE_BUILD_FACTORY( CDrawingPanel ); + +CDrawingPanel::CDrawingPanel( Panel *parent, const char*name ) : Panel( parent, name ) +{ + m_bDrawingLines = false; + m_fLastMapLine = 0; + m_iMouseX = 0; + m_iMouseY = 0; + m_iPanelType = DRAWING_PANEL_TYPE_NONE; + m_bTeamColors = false; + + m_nWhiteTexture = vgui::surface()->CreateNewTextureID(); + vgui::surface()->DrawSetTextureFile( m_nWhiteTexture, "vgui/white", true, false ); + + ListenForGameEvent( "cl_drawline" ); +} + +void CDrawingPanel::SetVisible( bool bState ) +{ + ClearAllLines(); + + BaseClass::SetVisible( bState ); +} + +void CDrawingPanel::ReadColor( const char* pszToken, Color& color ) +{ + if ( pszToken && *pszToken ) + { + int r = 0, g = 0, b = 0, a = 255; + if ( sscanf( pszToken, "%d %d %d %d", &r, &g, &b, &a ) >= 3 ) + { + // it's a direct color + color = Color( r, g, b, a ); + } + else + { + vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); + color = pScheme->GetColor( pszToken, Color( 0, 0, 0, 0 ) ); + } + } +} + +void CDrawingPanel::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + ReadColor( inResourceData->GetString( "linecolor", "" ), m_colorLine ); + m_bTeamColors = inResourceData->GetBool( "team_colors", false ); +} + +void CDrawingPanel::Paint() +{ + for ( int iIndex = 0; iIndex < ARRAYSIZE( m_vecDrawnLines ); iIndex++ ) + { + //Draw the lines + for ( int i = 0; i < m_vecDrawnLines[iIndex].Count(); i++ ) + { + if ( !m_vecDrawnLines[iIndex][i].bSetBlipCentre ) + { + Vector vecBlipPos; + vecBlipPos.x = m_vecDrawnLines[iIndex][i].worldpos.x; + vecBlipPos.y = m_vecDrawnLines[iIndex][i].worldpos.y; + vecBlipPos.z = 0; + //Msg("drawing line with blippos %f, %f\n", vecBlipPos.x, vecBlipPos.y); + m_vecDrawnLines[iIndex][i].blipcentre = m_vecDrawnLines[iIndex][i].worldpos; + //Msg(" which is blipcentre=%f, %f\n", m_MapLines[i].blipcentre.x, m_MapLines[i].blipcentre.y); + m_vecDrawnLines[iIndex][i].bSetBlipCentre = true; + } + + float x = m_vecDrawnLines[iIndex][i].blipcentre.x; + float y = m_vecDrawnLines[iIndex][i].blipcentre.y; + + if ( m_vecDrawnLines[iIndex][i].bLink ) + { + if ( i > 1 ) + { + m_vecDrawnLines[iIndex][i].linkpos = m_vecDrawnLines[iIndex][i-1].worldpos; + + if ( !m_vecDrawnLines[iIndex][i].bSetLinkBlipCentre ) + { + Vector vecBlipPos2; + vecBlipPos2.x = m_vecDrawnLines[iIndex][i].linkpos.x; + vecBlipPos2.y = m_vecDrawnLines[iIndex][i].linkpos.y; + vecBlipPos2.z = 0; + m_vecDrawnLines[iIndex][i].linkblipcentre = m_vecDrawnLines[iIndex][i].linkpos; + m_vecDrawnLines[iIndex][i].bSetLinkBlipCentre = true; + } + + float x2 = m_vecDrawnLines[iIndex][i].linkblipcentre.x; + float y2 = m_vecDrawnLines[iIndex][i].linkblipcentre.y; + + int alpha = 255; + if ( m_iPanelType == DRAWING_PANEL_TYPE_MATCH_SUMMARY ) + { + float t = gpGlobals->curtime - m_vecDrawnLines[iIndex][i].created_time; + + if ( t < DRAWN_LINE_SOLID_TIME ) + { + + } + else if ( t < DRAWN_LINE_SOLID_TIME + DRAWN_LINE_FADE_TIME ) + { + alpha = 255 - ( ( t - DRAWN_LINE_SOLID_TIME ) / DRAWN_LINE_FADE_TIME ) * 255.0f; + } + else + { + continue; + } + } + + //Msg("drawing line from %f,%f to %f,%f\n", x, y, x2, y2); + + vgui::surface()->DrawSetTexture( m_nWhiteTexture ); + vgui::Vertex_t start, end; + + Color drawColor = m_colorLine; + if ( m_bTeamColors ) + { + C_BasePlayer *pPlayer = UTIL_PlayerByIndex( iIndex ); + if ( pPlayer ) + { + drawColor = g_DrawPanel_TeamColors[ pPlayer->GetTeamNumber() ]; + } + } + + // draw main line + vgui::surface()->DrawSetColor( Color( drawColor.r(), drawColor.g(), drawColor.b(), alpha ) ); + start.Init( Vector2D( x, y ), Vector2D( 0, 0 ) ); + end.Init( Vector2D( x2, y2 ), Vector2D( 1, 1 ) ); + SoftLine::DrawPolygonLine( start, end ); + + // draw translucent ones around it to give it some softness + vgui::surface()->DrawSetColor( Color( drawColor.r(), drawColor.g(), drawColor.b(), 0.5f * alpha ) ); + + start.Init( Vector2D( x - 0.50f, y - 0.50f ), Vector2D( 0, 0 ) ); + end.Init( Vector2D( x2 - 0.50f, y2 - 0.50f ), Vector2D( 1, 1 ) ); + SoftLine::DrawPolygonLine( start, end ); + + start.Init( Vector2D( x + 0.50f, y - 0.50f ), Vector2D( 0, 0 ) ); + end.Init( Vector2D( x2 + 0.50f, y2 - 0.50f ), Vector2D( 1, 1 ) ); + SoftLine::DrawPolygonLine( start, end ); + + start.Init( Vector2D( x - 0.50f, y + 0.50f ), Vector2D( 0, 0 ) ); + end.Init( Vector2D( x2 - 0.50f, y2 + 0.50f ), Vector2D( 1, 1 ) ); + SoftLine::DrawPolygonLine( start, end ); + + start.Init( Vector2D( x + 0.50f, y + 0.50f ), Vector2D( 0, 0 ) ); + end.Init( Vector2D( x2 + 0.50f, y2 + 0.50f ), Vector2D( 1, 1 ) ); + SoftLine::DrawPolygonLine( start, end ); + } + } + } + } +} + +void CDrawingPanel::OnMousePressed( vgui::MouseCode code ) +{ + if ( code != MOUSE_LEFT ) + return; + + SendMapLine( m_iMouseX, m_iMouseY, true ); + m_bDrawingLines = true; +} + +void CDrawingPanel::OnMouseReleased( vgui::MouseCode code ) +{ + if ( code != MOUSE_LEFT ) + return; + + m_bDrawingLines = false; +} + +void CDrawingPanel::OnCursorExited() +{ + //Msg("CDrawingPanel::OnCursorExited\n"); + m_bDrawingLines = false; +} + +void CDrawingPanel::OnThink() +{ + if ( m_iPanelType == DRAWING_PANEL_TYPE_MATCH_SUMMARY ) + { + // clean up any segments that have faded out + for ( int iIndex = 0; iIndex < ARRAYSIZE( m_vecDrawnLines ); iIndex++ ) + { + for ( int i = m_vecDrawnLines[iIndex].Count() - 1; i >= 0; i-- ) + { + if ( gpGlobals->curtime - m_vecDrawnLines[iIndex][i].created_time > DRAWN_LINE_SOLID_TIME + DRAWN_LINE_FADE_TIME ) + { + m_vecDrawnLines[iIndex].Remove( i ); + } + } + } + } +} + +void CDrawingPanel::OnCursorMoved( int x, int y ) +{ + //Msg("CDrawingPanel::OnCursorMoved %d,%d\n", x, y); + m_iMouseX = x; + m_iMouseY = y; + + const float flLineInterval = 1.f / 60.f; + + if ( m_bDrawingLines && gpGlobals->curtime >= m_fLastMapLine + flLineInterval ) + { + SendMapLine( x, y, false ); + } + +} + +void CDrawingPanel::SendMapLine( int x, int y, bool bInitial ) +{ + if ( engine->IsPlayingDemo() ) + return; + + int iIndex = GetLocalPlayerIndex(); + int nMaxLines = 750; // 12.5 seconds of drawing at 60fps + if ( m_iPanelType == DRAWING_PANEL_TYPE_MATCH_SUMMARY ) + { + nMaxLines = 120; // 2 seconds of drawing at 60fps + } + + // Stop adding lines after this much + if ( m_vecDrawnLines[iIndex].Count() >= nMaxLines ) + return; + + int linetype = bInitial ? 0 : 1; + + m_fLastMapLine = gpGlobals->curtime; + + // short circuit add it to your own list + MapLine line; + line.worldpos.x = x; + line.worldpos.y = y; + line.created_time = gpGlobals->curtime; + if ( linetype == 1 ) // links to a previous + { + line.bLink = true; + } + + m_vecDrawnLines[iIndex].AddToTail( line ); + + if ( m_iPanelType == DRAWING_PANEL_TYPE_MATCH_SUMMARY ) + { + // notify the server of this! + KeyValues *kv = new KeyValues( "cl_drawline" ); + kv->SetInt( "panel", m_iPanelType ); + kv->SetInt( "line", linetype ); + kv->SetFloat( "x", (float)x / (float)GetWide() ); + kv->SetFloat( "y", (float)y / (float)GetTall() ); + engine->ServerCmdKeyValues( kv ); + } +} + +void CDrawingPanel::ClearLines( int iIndex ) +{ + m_vecDrawnLines[iIndex].Purge(); +} + +void CDrawingPanel::ClearAllLines() +{ + for ( int iIndex = 0; iIndex < ARRAYSIZE( m_vecDrawnLines ); iIndex++ ) + { + m_vecDrawnLines[iIndex].Purge(); + } +} + +void CDrawingPanel::FireGameEvent( IGameEvent *event ) +{ + if ( FStrEq( event->GetName(), "cl_drawline" ) ) + { + int iIndex = event->GetInt( "player" ); + + // if this is NOT the local player (we've already stored our own data) + if ( ( iIndex != GetLocalPlayerIndex() ) || engine->IsPlayingDemo() ) + { + // If a player is muted for voice, also mute them for lines because jerks gonna jerk. + if ( cl_mute_all_comms.GetBool() && ( iIndex != 0 ) ) + { + if ( GetClientVoiceMgr() && GetClientVoiceMgr()->IsPlayerBlocked( iIndex ) ) + { + ClearLines( iIndex ); + return; + } + } + + int iPanelType = event->GetInt( "panel" ); + + // if this message is about our panel type + if ( iPanelType == m_iPanelType ) + { + int iLineType = event->GetInt( "line" ); + float x = event->GetFloat( "x" ); + float y = event->GetFloat( "y" ); + + MapLine line; + line.worldpos.x = (int)( x * GetWide() ); + line.worldpos.y = (int)( y * GetTall() ); + line.created_time = gpGlobals->curtime; + if ( iLineType == 1 ) // links to a previous + { + line.bLink = true; + } + + m_vecDrawnLines[iIndex].AddToTail( line ); + } + } + } +}
\ No newline at end of file diff --git a/game/client/tf/vgui/drawing_panel.h b/game/client/tf/vgui/drawing_panel.h new file mode 100644 index 0000000..3cfe991 --- /dev/null +++ b/game/client/tf/vgui/drawing_panel.h @@ -0,0 +1,84 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef DRAWING_PANEL_H +#define DRAWING_PANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include <vgui_controls/Panel.h> + +#define DRAWN_LINE_SOLID_TIME 15.0f +#define DRAWN_LINE_FADE_TIME 3.0f + +class MapLine +{ +public: + MapLine() + { + worldpos.Init( 0, 0 ); + linkpos.Init( 0, 0 ); + created_time = 0; + bSetLinkBlipCentre = false; + bSetBlipCentre = false; + blipcentre.Init( 0, 0 ); + linkblipcentre.Init( 0, 0 ); + bLink = false; + } + + Vector2D worldpos; // blip in world space + Vector2D blipcentre; // blip in map texture space + Vector2D linkpos; // link blip in world space + Vector2D linkblipcentre; // link blip in map texture space + bool bLink; + bool bSetBlipCentre; // have we calculated the blip in map texture space yet? + bool bSetLinkBlipCentre; // have we calculated the link blip in map texture space yet? + float created_time; +}; + + +class CDrawingPanel : public vgui::Panel, public CGameEventListener +{ + DECLARE_CLASS_SIMPLE( CDrawingPanel, vgui::Panel ); +public: + CDrawingPanel( Panel *parent, const char *name ); + + virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE; + + void SendMapLine( int x, int y, bool bInitial ); + virtual void OnMouseReleased( vgui::MouseCode code ); + virtual void OnMousePressed( vgui::MouseCode code ); + virtual void OnCursorExited(); + virtual void OnCursorMoved( int x, int y ); + virtual void Paint(); + virtual void OnThink(); + virtual void SetVisible( bool bState ) OVERRIDE; + void ClearLines( int iIndex ); + void ClearAllLines(); + const CUtlVector<MapLine>& GetLines( int iIndex ) const { return m_vecDrawnLines[iIndex]; } + void SetType( int iPanelType ){ m_iPanelType = iPanelType; } + + virtual void FireGameEvent( IGameEvent *event ); + +private: + void ReadColor( const char* pszToken, Color& color ); + + bool m_bDrawingLines; + float m_fLastMapLine; + int m_iMouseX, m_iMouseY; + int m_nWhiteTexture; + + Color m_colorLine; + + CUtlVector<MapLine> m_vecDrawnLines[MAX_PLAYERS+1]; + + int m_iPanelType; + bool m_bTeamColors; +}; + +#endif // DRAWING_PANEL_H diff --git a/game/client/tf/vgui/dynamic_recipe_subpanel.cpp b/game/client/tf/vgui/dynamic_recipe_subpanel.cpp new file mode 100644 index 0000000..35ad19a --- /dev/null +++ b/game/client/tf/vgui/dynamic_recipe_subpanel.cpp @@ -0,0 +1,2013 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "crafting_panel.h" +#include "dynamic_recipe_subpanel.h" +#include "vgui/ISurface.h" +#include "vgui/ISystem.h" +#include "c_tf_player.h" +#include "gamestringpool.h" +#include "iclientmode.h" +#include "tf_item_inventory.h" +#include "ienginevgui.h" +#include <vgui/ILocalize.h> +#include "vgui_controls/TextImage.h" +#include "vgui_controls/CheckButton.h" +#include "vgui_controls/ComboBox.h" +#include <vgui_controls/TextEntry.h> +#include "vgui/IInput.h" +#include "gcsdk/gcclient.h" +#include "gcsdk/gcclientjob.h" +#include "character_info_panel.h" +#include "charinfo_loadout_subpanel.h" +#include "econ_item_system.h" +#include "econ_item_constants.h" +#include "tf_hud_notification_panel.h" +#include "tf_hud_chat.h" +#include "c_tf_gamestats.h" +#include "confirm_dialog.h" +#include "econ_notifications.h" +#include "gc_clientsystem.h" +#include "charinfo_loadout_subpanel.h" +#include "item_selection_criteria.h" +#include "rtime.h" +#include "c_tf_freeaccount.h" +#include "econ_dynamic_recipe.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +static CDynamicRecipePanel* g_DynamicRecipePanel = NULL; +extern const char *g_szItemBorders[AE_MAX_TYPES][5]; + +//----------------------------------------------------------------------------- +// Purpose: Default to NULL item +//----------------------------------------------------------------------------- +CRecipeComponentItemModelPanel::CRecipeComponentItemModelPanel( vgui::Panel *parent, const char *name ) + : CItemModelPanel( parent, name ) + , m_nPageNumber( 0 ) +{ + SetItem( NULL ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Add recipe to our list of recipes. Each call to this function +// effectively adds an item to the next page +//----------------------------------------------------------------------------- +void CRecipeComponentItemModelPanel::AddRecipe( itemid_t nRecipe ) +{ + RecipeItem_t& recipeItem = m_vecRecipes[m_vecRecipes.AddToTail()]; + recipeItem.m_nRecipeIndex = nRecipe; + UpdateRecipeItem( &recipeItem ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Wipe all recipes from all pages +//----------------------------------------------------------------------------- +void CRecipeComponentItemModelPanel::DeleteRecipes() +{ + m_vecDefaultItems.Purge(); + m_vecRecipes.Purge(); + SetItem( NULL ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Override to set the item to be our default item if NULL is passed in. +// Also handles greying out the panels +//----------------------------------------------------------------------------- +void CRecipeComponentItemModelPanel::SetItem( const CEconItemView *pItem ) +{ + // Use the default item if they set NULL + if( pItem == NULL ) + { + SetBlankState(); + } + else + { + BaseClass::SetItem( pItem ); + SetGreyedOut( NULL ); + } + + InvalidateLayout( true ); +} + + +void CRecipeComponentItemModelPanel::SetBlankState() +{ + CEconItemView* pDefaultItem = NULL; + if( m_nPageNumber < m_vecDefaultItems.Count() ) + pDefaultItem = m_vecDefaultItems[ m_nPageNumber ]; + BaseClass::SetItem( pDefaultItem ); + SetGreyedOut( "" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Move a recipe item to a specific page +//----------------------------------------------------------------------------- +void CRecipeComponentItemModelPanel::SetRecipeItem( itemid_t nRecipeItem, int nPageNumber ) +{ + Assert( nPageNumber < m_vecRecipes.Count() ); + m_vecRecipes[ nPageNumber ].m_nRecipeIndex = nRecipeItem; + + UpdateRecipeItem( &m_vecRecipes[ nPageNumber ] ); + + // Use the item that this item ID maps to + SetItem( m_vecRecipes[ nPageNumber ].m_pRecipeItem ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Add a default item to a page +//----------------------------------------------------------------------------- +void CRecipeComponentItemModelPanel::AddDefaultItem( CEconItemView *pItem ) +{ + m_vecDefaultItems[ m_vecDefaultItems.AddToTail() ] = pItem; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get a recipe item on a given page +//----------------------------------------------------------------------------- +CEconItemView* CRecipeComponentItemModelPanel::GetRecipeItem( int nPageNumber ) const +{ + if( nPageNumber < m_vecRecipes.Count() ) + { + return m_vecRecipes[nPageNumber].m_pRecipeItem; + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get the recipe index from a specific page +//----------------------------------------------------------------------------- +itemid_t CRecipeComponentItemModelPanel::GetRecipeIndex( int nPageNumber ) const +{ + if( nPageNumber < m_vecRecipes.Count() ) + { + return m_vecRecipes[nPageNumber].m_nRecipeIndex; + } + + return INVALID_ITEM_ID; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Iterate through all attributes on a item and turn recipe attributes +// into input and output items. Store those items in vectors for inputs +// and outputs. Inputs are sorted from least common to most common +// so that the later pages are filled with more repeats than the early pages +//----------------------------------------------------------------------------- +bool CDynamicRecipePanel::CRecipeComponentAttributeCounter::OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_DynamicRecipeComponent& value ) +{ + static CSchemaAttributeDefHandle pAttrib_CannotTrade( "cannot trade" ); + Assert( pAttrib_CannotTrade ); + + unsigned nCount = value.num_required() - value.num_fulfilled(); + + if( value.component_flags() & DYNAMIC_RECIPE_FLAG_IS_OUTPUT ) + { + CEconItem* pItem = m_vecTempEconItems[m_vecTempEconItems.AddToTail( new CEconItem() )]; + DecodeItemFromEncodedAttributeString( value, pItem ); + + for( unsigned i=0; i < nCount; ++i ) + { + CEconItemView& item = m_vecOutputItems[ m_vecOutputItems.AddToTail() ]; + + item.SetItemDefIndex( pItem->GetDefinitionIndex() ); + item.SetItemQuality( pItem->GetQuality() ); + item.SetItemLevel( pItem->GetItemLevel() ); + item.SetItemID( pItem->GetItemID() ); + item.SetNonSOEconItem( pItem ); // Set the item into the econ item view. + item.SetInitialized( true ); + item.SetItemOriginOverride( kEconItemOrigin_RecipeOutput ); // Spoof where we came from + + // Set the untradable flag if the attribute says so + if( value.component_flags() & DYNAMIC_RECIPE_FLAG_IS_UNTRADABLE ) + { + item.GetAttributeList()->SetRuntimeAttributeValue( pAttrib_CannotTrade, 0.0f ); // value doesn't matter -- we only check for presence/absence + } + } + } + else + { + m_nInputCount += nCount; + + CEconItem* pItem = m_vecTempEconItems[m_vecTempEconItems.AddToTail( new CEconItem() )]; + DecodeItemFromEncodedAttributeString( value, pItem ); + + CUtlVector<InputComponent_t>& inputSeries = m_vecInputItems[ m_vecInputItems.AddToTail() ]; + for( unsigned i=0; i < nCount; ++i ) + { + InputComponent_t& item = inputSeries[ inputSeries.AddToTail() ]; + item.m_ItemView.SetItemDefIndex( pItem->GetDefinitionIndex() ); + item.m_ItemView.SetItemQuality( pItem->GetQuality() ); + item.m_ItemView.SetItemLevel( 0 ); + item.m_ItemView.SetItemID( pItem->GetItemID() ); + item.m_ItemView.SetNonSOEconItem( pItem ); // Set the item into the econ item view. + item.m_ItemView.SetInitialized( true ); + item.m_ItemView.SetItemOriginOverride( kEconItemOrigin_RecipeOutput ); // Spoof where we came from + item.m_pAttrib = pAttrDef; + } + + m_vecInputItems.Sort( LeastCommonInputSortFunc ); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the output item on a given page +//----------------------------------------------------------------------------- +CEconItemView* CDynamicRecipePanel::CRecipeComponentAttributeCounter::GetOutputItem( int i ) +{ + if( i >= 0 && i < m_vecOutputItems.Count() ) + return &m_vecOutputItems[i]; + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the input item on a given page +//----------------------------------------------------------------------------- +CEconItemView* CDynamicRecipePanel::CRecipeComponentAttributeCounter::GetInputItem( int i ) +{ + InputComponent_t* pInputComponent = GetInputComponent( i ); + if( pInputComponent ) + { + return &pInputComponent->m_ItemView; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the attribute that the item in a given panel maps to +//----------------------------------------------------------------------------- +const CEconItemAttributeDefinition* CDynamicRecipePanel::CRecipeComponentAttributeCounter::GetInputAttrib( int i ) +{ + InputComponent_t* pInputComponent = GetInputComponent( i ); + if( pInputComponent ) + { + return pInputComponent->m_pAttrib; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Sort input vectors based on count. Fewer first. +//----------------------------------------------------------------------------- +int CDynamicRecipePanel::CRecipeComponentAttributeCounter::LeastCommonInputSortFunc( const CCopyableUtlVector<InputComponent_t> *p1, const CCopyableUtlVector<InputComponent_t> *p2 ) +{ + return p1->Count() > p2->Count(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Find input component i from our 2D vector of input items +//----------------------------------------------------------------------------- +CDynamicRecipePanel::CRecipeComponentAttributeCounter::InputComponent_t* CDynamicRecipePanel::CRecipeComponentAttributeCounter::GetInputComponent( int i ) +{ + int nAccum = 0; + FOR_EACH_VEC( m_vecInputItems, nIndex ) + { + int nCount = m_vecInputItems[ nIndex ].Count(); + + if( i < nAccum + nCount ) + { + return &m_vecInputItems[ nIndex ][ i - nAccum ]; + } + + nAccum += nCount; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Reset all data in the iterator +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::CRecipeComponentAttributeCounter::Reset() +{ + m_vecInputItems.Purge(); + m_vecOutputItems.Purge(); + m_vecTempEconItems.PurgeAndDeleteElements(); + m_nInputCount = 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: Compare Check if m_pItemToMatch passes the criteria of any of the +// attributes on m_pSourceItem. +//----------------------------------------------------------------------------- +bool CDynamicRecipePanel::CDynamicRecipeItemMatchFind::OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_DynamicRecipeComponent& value ) +{ + // Can't match ourself + if( m_pSourceItem && m_pItemToMatch && m_pSourceItem->GetID() == m_pItemToMatch->GetID() ) + return true; + + if( value.component_flags() & DYNAMIC_RECIPE_FLAG_IS_OUTPUT ) + return true; + + if( !DefinedItemAttribMatch( value, m_pItemToMatch ) ) + return true; + + // Must be useable in crafting. Expensive -- do this last. + if( !m_pItemToMatch || !m_pItemToMatch->IsUsableInCrafting() ) + return true; + + // A match! + m_bMatchesAny = true; + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Delete all recipe data +//----------------------------------------------------------------------------- +void CInputPanelItemModelPanel::DeleteRecipes() +{ + CRecipeComponentItemModelPanel::DeleteRecipes(); + m_vecAttrDef.Purge(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Add component info to a new page +//----------------------------------------------------------------------------- +void CInputPanelItemModelPanel::AddComponentInfo( const CEconItemAttributeDefinition *pComponentAttrib ) +{ + m_vecAttrDef[ m_vecAttrDef.AddToTail() ] = pComponentAttrib; + AddRecipe( NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Check if a passed in database item matches the desired item in this +// item panel. Default to the current page +//----------------------------------------------------------------------------- +bool CInputPanelItemModelPanel::MatchesAttribCriteria( itemid_t itemID ) const +{ + return MatchesAttribCriteria( itemID, m_nPageNumber ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Check if a passed in database item matches the desired item in this +// item panel on the specified page. +//----------------------------------------------------------------------------- +bool CInputPanelItemModelPanel::MatchesAttribCriteria( itemid_t itemID, int nPageNumber ) const +{ + if( !m_pDynamicRecipeItem ) + return false; + + CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory(); + if ( !pLocalInv ) + return false; + + const CEconItemView* pItem = pLocalInv->GetInventoryItemByItemID( itemID ); + + if( !pItem || !pItem->IsUsableInCrafting() ) + return false; + + const CEconItemAttributeDefinition* pAttrDef = GetAttrib( nPageNumber ); + CAttribute_DynamicRecipeComponent attribValue; + if( m_pDynamicRecipeItem->FindAttribute<CAttribute_DynamicRecipeComponent >( pAttrDef, &attribValue ) ) + { + return DefinedItemAttribMatch( attribValue, pItem ); + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get the attribute that this panel represents +//----------------------------------------------------------------------------- +const CEconItemAttributeDefinition* CInputPanelItemModelPanel::GetAttrib( int nPageNumber ) const +{ + if( nPageNumber >= 0 && nPageNumber < m_vecAttrDef.Count() ) + return m_vecAttrDef[ nPageNumber ]; + + return NULL; +} + + +void CInputPanelItemModelPanel::SetBlankState() +{ + // Get the default item + CEconItemView* pDefaultItem = NULL; + if( m_nPageNumber < m_vecDefaultItems.Count() ) + pDefaultItem = m_vecDefaultItems[ m_nPageNumber ]; + + // Grey out + SetGreyedOut( "" ); + + // Check for the "item name text override" attribute on the default item + static CSchemaAttributeDefHandle pAttrDef_ItemNameTextOverride( "item name text override" ); + CAttribute_String attrItemNameTextOverride; + if ( pDefaultItem ) + { + pDefaultItem->FindAttribute( pAttrDef_ItemNameTextOverride, &attrItemNameTextOverride ); + + if ( FStrEq( attrItemNameTextOverride.value().c_str(), "#TF_ItemName_Item" ) ) + { + // This is a dummy item. Dont display an icon. Just the name of the item + CItemModelPanel::SetItem( NULL ); + SetAttribOnly( true ); + SetTextYPos( 0 ); + //SetNoItemText( pDefaultItem->GetItemName(), NULL, NULL ); + const wchar_t *pszItemname = pDefaultItem->GetItemName(); + if ( V_wcscmp( pszItemname, g_pVGuiLocalize->Find( "#TF_ItemName_Item" ) ) == 0 && pDefaultItem->GetQuality() == AE_PAINTKITWEAPON ) + { + SetNoItemText( g_pVGuiLocalize->Find( "paintkitweapon" ), NULL, NULL ); + } + else + { + SetNoItemText( pDefaultItem->GetItemName(), NULL, NULL ); + } + } + else + { + BaseClass::SetItem( pDefaultItem ); + } + } + else + { + // Construct an item from the attribute that describes this input + CEconItem tempItem; + const CEconItemAttributeDefinition* pAttrDef = GetAttrib( m_nPageNumber ); + CAttribute_DynamicRecipeComponent attribValue; + if( m_pDynamicRecipeItem->FindAttribute<CAttribute_DynamicRecipeComponent >( pAttrDef, &attribValue ) ) + { + DecodeItemFromEncodedAttributeString( attribValue, &tempItem ); + } + + // Shove it into an econitemview + CEconItemView tempView; + tempView.Init( tempItem.GetItemDefIndex(), tempItem.GetQuality(), 0 ); + tempView.SetNonSOEconItem( &tempItem ); + + // Set its name as the text for this item model panel + CItemModelPanel::SetItem( NULL ); + SetAttribOnly( true ); + SetTextYPos( 0 ); + SetNoItemText( tempView.GetItemName(), NULL, NULL ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Is this in play +//----------------------------------------------------------------------------- +bool CRecipeComponentItemModelPanel::IsSlotAvailable( int nPageNumber ) +{ + return nPageNumber < m_vecRecipes.Count(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Sets the passed in recipe item and changes the item we show +//----------------------------------------------------------------------------- +void CRecipeComponentItemModelPanel::UpdateRecipeItem( RecipeItem_t* pRecipeItem ) +{ + Assert( pRecipeItem ); + + CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory(); + if ( pLocalInv == NULL ) + return; + + pRecipeItem->m_pRecipeItem = pLocalInv->GetInventoryItemByItemID( pRecipeItem->m_nRecipeIndex ); + SetItem( pRecipeItem->m_pRecipeItem ); +} + +//----------------------------------------------------------------------------- +// Purpose: Update the item we show for the current page +//----------------------------------------------------------------------------- +void CRecipeComponentItemModelPanel::UpdateDisplayItem() +{ + if( m_nPageNumber < m_vecRecipes.Count() ) + { + UpdateRecipeItem( &m_vecRecipes[ m_nPageNumber ] ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set the current page +//----------------------------------------------------------------------------- +void CRecipeComponentItemModelPanel::SetPageNumber( int nPageNumber ) +{ + Assert( nPageNumber >= 0 ); + m_nPageNumber = nPageNumber; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CDynamicRecipePanel::CDynamicRecipePanel( vgui::Panel *parent, const char *panelName, CEconItemView* pRecipeItem ) + : CBackpackPanel( parent, panelName ) + , m_pDynamicRecipeItem( pRecipeItem ) + , m_pRecipeCraftButton( NULL ) + , m_nNumRecipeItems( 0 ) + , m_bAllRecipePanelsFilled( false ) + , m_bInputPanelsDirty( false ) + , m_nInputPage( 0 ) + , m_nOutputPage( 0 ) + , m_pCurInputPageLabel( NULL ) + , m_pNextInputPageButton( NULL ) + , m_pPrevInputPageButton( NULL ) + , m_flAbortCraftingAt( 0 ) + , m_pMouseOverItemPanel( NULL ) + , m_pNoMatchesLabel( NULL ) + , m_pUntradableOutputsLabel( NULL ) + , m_bShowUntradable( false ) + +{ + g_DynamicRecipePanel = this; + + m_pRecipeContainer = new vgui::EditablePanel( this, "recipecontainer" ); + m_pInventoryContainer = new vgui::EditablePanel( this, "inventorycontainer" ); + m_pShowUntradableItemsCheckbox = new vgui::CheckButton( m_pInventoryContainer, "untradablecheckbox", "#Dynamic_Recipe_Untradable_Checkbox" ); + m_pShowUntradableItemsCheckbox->AddActionSignalTarget( this ); + m_pInputsLabel = new CExLabel( m_pRecipeContainer, "InputLabel", "#Craft_Recipe_Inputs" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CDynamicRecipePanel::~CDynamicRecipePanel( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Get all our controls +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + LoadControlSettings( GetResFile() ); + + // This calls AddNewItemPanel + BaseClass::ApplySchemeSettings( pScheme ); + + CExButton* pCancelButton = dynamic_cast<CExButton*>( m_pInventoryContainer->FindChildByName("CancelButton") ); + if ( pCancelButton ) + pCancelButton->AddActionSignalTarget( this ); + + m_pRecipeCraftButton = dynamic_cast<CExButton*>( m_pRecipeContainer->FindChildByName("CraftButton") ); + if ( m_pRecipeCraftButton ) + m_pRecipeCraftButton->AddActionSignalTarget( this ); + + m_pNextPageButton = dynamic_cast<CExButton*>( m_pInventoryContainer->FindChildByName("NextPageButton") ); + if( m_pNextPageButton ) + m_pNextPageButton->AddActionSignalTarget( this ); + + m_pPrevPageButton = dynamic_cast<CExButton*>( m_pInventoryContainer->FindChildByName("PrevPageButton") ); + if( m_pPrevPageButton ) + m_pPrevPageButton->AddActionSignalTarget( this ); + + m_pCurPageLabel = dynamic_cast<vgui::Label*>( m_pInventoryContainer->FindChildByName("CurPageLabel") ); + Assert( m_pCurPageLabel ); + + m_pNoMatchesLabel = dynamic_cast<CExLabel*>( m_pInventoryContainer->FindChildByName( "NoMatches" ) ); + Assert( m_pNoMatchesLabel ); + + m_pUntradableOutputsLabel = dynamic_cast<CExLabel*>( m_pRecipeContainer->FindChildByName( "UntradableLabel" ) ); + Assert( m_pUntradableOutputsLabel ); + + m_pOutputsLabel = dynamic_cast<CExLabel*>( m_pRecipeContainer->FindChildByName( "OutputLabel" ) ); + Assert( m_pOutputsLabel ); + + m_pCurInputPageLabel = dynamic_cast<CExLabel*>( m_pRecipeContainer->FindChildByName( "CurInputPageLabel" ) ); + + m_pNextInputPageButton = dynamic_cast<CExButton*>( m_pRecipeContainer->FindChildByName( "NextInputPageButton" ) ); + if( m_pNextInputPageButton ) + m_pNextInputPageButton->AddActionSignalTarget( this ); + + m_pPrevInputPageButton = dynamic_cast<CExButton*>( m_pRecipeContainer->FindChildByName( "PrevInputPageButton" ) ); + if( m_pPrevInputPageButton ) + m_pPrevInputPageButton->AddActionSignalTarget( this ); + +#ifdef STAGING_ONLY + m_pDevGiveInputsButton = new CExButton( m_pInventoryContainer, "dev_giveinputsbutton", "[Debug] Give Inputs", this, "dev_giveinputs" ); + m_pDevGiveInputsButton->SetEnabled( true ); + m_pDevGiveInputsButton->SetVisible( true ); +#endif + + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); +} + +//----------------------------------------------------------------------------- +// Purpose: Update all the item panels and page buttons and labels +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::PerformLayout( void ) +{ + BaseClass::PerformLayout(); + +#ifdef STAGING_ONLY + if( m_pDevGiveInputsButton ) + { + m_pDevGiveInputsButton->SetPos( XRES(0) , YRES(290) ); + m_pDevGiveInputsButton->SetWide( 210 ); + m_pDevGiveInputsButton->SetTall( 40 ); + m_pDevGiveInputsButton->MoveToFront(); + m_pDevGiveInputsButton->SetEnabled( true ); + m_pDevGiveInputsButton->SetVisible( true ); + } +#endif + + if( m_pSortByComboBox ) + { + m_pSortByComboBox->SetVisible( false ); + } + + UpdateModelPanels(); + + bool bNoMatches = m_nNumRecipeItems == 0; + bool bMultiplePagesOfMatches = m_nNumRecipeItems > (unsigned)GetNumBackpackPanelsPerPage(); + // Some panels show and hide based on the number of matches + if( m_pNoMatchesLabel) + m_pNoMatchesLabel->SetVisible( bNoMatches ); + if( m_pNextPageButton ) + m_pNextPageButton->SetVisible( bMultiplePagesOfMatches ); + if( m_pPrevPageButton ) + m_pPrevPageButton->SetVisible( bMultiplePagesOfMatches ); + if( m_pCurPageLabel ) + m_pCurPageLabel->SetVisible( bMultiplePagesOfMatches ); + + bool bMultiplePagesOfInputs = m_RecipeIterator.GetInputCount() > GetNumInputPanelsPerPage(); + if( m_pNextInputPageButton ) + m_pNextInputPageButton->SetVisible( bMultiplePagesOfInputs ); + if( m_pNextInputPageButton ) + m_pNextInputPageButton->SetEnabled( m_nInputPage < GetNumInputPages() - 1 ); + if( m_pCurInputPageLabel ) + m_pCurInputPageLabel->SetVisible( bMultiplePagesOfInputs ); + if( m_pPrevInputPageButton ) + m_pPrevInputPageButton->SetVisible( bMultiplePagesOfInputs ); + if( m_pPrevInputPageButton ) + m_pPrevInputPageButton->SetEnabled( m_nInputPage > 0 ); + + if( m_pUntradableOutputsLabel ) + m_pUntradableOutputsLabel->SetVisible( m_pDynamicRecipeItem ? !m_pDynamicRecipeItem->IsTradable() : false ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Handle commands +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::OnCommand( const char *command ) +{ + if ( !Q_strnicmp( command, "back", 4 ) ) + { + PostMessage( GetParent(), new KeyValues("CraftingClosed") ); + return; + } + else if ( !Q_strnicmp( command, "craft", 5 ) ) + { + // Check if we should warn about partial completion + if( WarnAboutPartialCompletion() ) + { + if( CheckForUntradableItems() ) + { + Craft(); + } + } + + return; + } + else if ( !Q_stricmp( command, "reloadscheme" ) ) + { + InvalidateLayout( true, true ); // deliberatly fallthrough to baseclass + } + else if( !Q_stricmp( command, "cancel" ) ) + { + SetVisible( false ); + return; + } + else if ( !Q_strnicmp( command, "nextpage", 8 ) ) + { + InvalidateLayout(); // deliberatly fallthrough to baseclass + } + else if ( !Q_strnicmp( command, "prevpage", 8 ) ) + { + InvalidateLayout(); // deliberatly fallthrough to baseclass + } + else if( !Q_strnicmp( command, "nextinputpage", 13 ) ) + { + if( m_nInputPage < GetNumInputPages() ) + m_nInputPage++; + InvalidateLayout(); + return; + } + else if( !Q_strnicmp( command, "previnputpage", 13 ) ) + { + if( m_nInputPage > 0 ) + m_nInputPage--; + InvalidateLayout(); + return; + } + else if( !Q_strnicmp( command, "deleteitem", 10 ) || + !Q_strnicmp( command, "useitem", 7 ) ) + { + // Gobble up these commands + return; + } +#ifdef STAGING_ONLY + else if( !Q_strnicmp( command, "dev_giveinputs", 14 ) ) + { + Debug_GiveRequiredInputs(); + if( m_pDevGiveInputsButton ) + m_pDevGiveInputsButton->SetEnabled( false ); + } +#endif + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: Gobble up all keyboard input for now! +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::OnKeyCodePressed( vgui::KeyCode /*code*/ ) +{ + return; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::OnButtonChecked( KeyValues *pData ) +{ + Panel *pPanel = reinterpret_cast<vgui::Panel *>( pData->GetPtr("panel") ); + + if ( m_pShowUntradableItemsCheckbox == pPanel ) + { + if ( m_bShowUntradable != m_pShowUntradableItemsCheckbox->IsSelected() ) + { + m_bShowUntradable = m_pShowUntradableItemsCheckbox->IsSelected(); + InitItemPanels(); + UpdateModelPanels(); + } + } +} + + +int CDynamicRecipePanel::GetNumItemPanels( void ) +{ + return DYNAMIC_RECIPE_INPUT_COUNT + DYNAMIC_RECIPE_OUTPUT_COUNT + DYNAMIC_RECIPE_PACKPACK_COUNT_PER_PAGE; +} + +//----------------------------------------------------------------------------- +// Purpose: Check to see that each and every input panel has a recipe item in it +//----------------------------------------------------------------------------- +bool CDynamicRecipePanel::AllRecipePanelsFilled( void ) +{ + // Need to recalculate + if( m_bInputPanelsDirty ) + { + // Assume all filled, and go through and try to find one that's empty + m_bAllRecipePanelsFilled = true; + FOR_EACH_VEC( m_vecRecipeInputModelPanels, i ) + { + CInputPanelItemModelPanel* pInputPanel = m_vecRecipeInputModelPanels[i]; + for( int j=0; j < GetNumInputPages(); ++j ) + { + if( pInputPanel->GetRecipeItem( j ) == NULL && pInputPanel->GetAttrib( j ) != NULL ) + { + m_bAllRecipePanelsFilled = false; + break; + } + } + } + } + + return m_bAllRecipePanelsFilled; +} + + +//----------------------------------------------------------------------------- +// Purpose: Callback for the confirm partial completion dialog +//----------------------------------------------------------------------------- +void ConfirmDestroyItems( bool bConfirmed, void* pContext ) +{ + CDynamicRecipePanel *pRecipePanel = ( CDynamicRecipePanel* )pContext; + if ( pRecipePanel && bConfirmed ) + { + if( pRecipePanel->CheckForUntradableItems() ) + { + pRecipePanel->Craft(); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Callback for the conrim untradable dialog +//----------------------------------------------------------------------------- +static void ConfirmUntradableCraft( bool bConfirmed, void* pContext ) +{ + CDynamicRecipePanel *pRecipePanel = ( CDynamicRecipePanel* )pContext; + if ( pRecipePanel && bConfirmed ) + { + pRecipePanel->Craft(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Go through each input panel and find out if any of them contain an +// item that is untradeable +//----------------------------------------------------------------------------- +bool CDynamicRecipePanel::CheckForUntradableItems( void ) +{ + // We dont care if the recipe itself is already not tradable + if( !m_pDynamicRecipeItem->IsTradable() ) + { + return true; + } + + bool bHasUntradable = false; + for ( int i = 0; i < CRAFTING_SLOTS_INPUTPANELS; ++i ) + { + CInputPanelItemModelPanel* pPanel = m_vecRecipeInputModelPanels[i]; + for( int j = 0; j < GetNumInputPages(); ++j ) + { + itemid_t nRecipeItemID = pPanel->GetRecipeIndex( j ); + + if ( nRecipeItemID != 0 && nRecipeItemID != INVALID_ITEM_ID ) + { + CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( nRecipeItemID ); + if ( pItemData->IsTradable() == false ) + { + bHasUntradable = true; + break; + } + } + } + } + + if ( bHasUntradable ) + { + enum { kWarningLength = 512 }; + locchar_t wszWarning[ kWarningLength ] = LOCCHAR(""); + + loc_scpy_safe( wszWarning, + CConstructLocalizedString( GLocalizationProvider()->Find( "Dynamic_Recipe_Untradable_Text" ), + m_pDynamicRecipeItem->GetItemName() ) ); + + CTFGenericConfirmDialog *pDialog = new CTFGenericConfirmDialog( "#Craft_Untradable_Title", wszWarning, "#GameUI_OK", "#Cancel", &ConfirmUntradableCraft, NULL ); + + if ( pDialog ) + { + pDialog->SetContext( this ); + pDialog->Show(); + } + + return false; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Check if not all of the inputs have items set into them. Put up +// a prompt and return false if so. +//----------------------------------------------------------------------------- +bool CDynamicRecipePanel::WarnAboutPartialCompletion( void ) +{ + if( !AllRecipePanelsFilled() ) + { + enum { kWarningLength = 512 }; + locchar_t wszWarning[ kWarningLength ] = LOCCHAR(""); + + loc_scpy_safe( wszWarning, + CConstructLocalizedString( GLocalizationProvider()->Find( "Dynamic_Recipe_Partial_Completion_Warning" ), + m_pDynamicRecipeItem->GetItemName() ) ); + + CTFGenericConfirmDialog *pDialog = new CTFGenericConfirmDialog( "#Craft_Untradable_Title", wszWarning, "#GameUI_OK", "#Cancel", &ConfirmDestroyItems, NULL ); + if ( pDialog ) + { + pDialog->SetContext( this ); + pDialog->Show(); + } + + + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: They hit the craft button! Cook up a message that we're going to send +// to the GC that contains all of the item_ids and attributes they are +// to apply to. Open a crafting status dialog when after we send the message. +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::Craft() +{ + GCSDK::CProtoBufMsg<CMsgFulfillDynamicRecipeComponent> msg( k_EMsgGCFulfillDynamicRecipeComponent ); + msg.Body().set_tool_item_id( m_pDynamicRecipeItem->GetItemID() ); + + FOR_EACH_VEC( m_vecRecipeInputModelPanels, i ) + { + CInputPanelItemModelPanel* pPanel = m_vecRecipeInputModelPanels[i]; + + for( int j=0; j < GetNumInputPages(); ++j ) + { + itemid_t nRecipeItemID = pPanel->GetRecipeIndex( j ); + + if( nRecipeItemID != 0 && nRecipeItemID != INVALID_ITEM_ID ) + { + if( !pPanel->MatchesAttribCriteria( nRecipeItemID, j ) ) + { + AssertMsg( 0, "Input panel has recipe item that does not pass its criteria" ); + // Something bad happened. Return this item to the backpack. + ReturnRecipeItemToBackpack( nRecipeItemID, pPanel, j ); + continue; + } + + // Add component + CMsgRecipeComponent* pComponent = msg.Body().add_consumption_components(); + pComponent->set_subject_item_id( nRecipeItemID ); + + const CEconItemAttributeDefinition* pAttribute = pPanel->GetAttrib( j ); + if( !pAttribute ) + { + AssertMsg( 0, "NULL attribute in panel what attempting to craft" ); + // Something bad happened. Return this item to the backpack. + ReturnRecipeItemToBackpack( nRecipeItemID, pPanel, j ); + continue; + } + pComponent->set_attribute_index( pAttribute->GetDefinitionIndex() ); + } + } + } + + EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pDynamicRecipeItem, "consumed_item" ); + GCClientSystem()->BSendMessage( msg ); + + // Open a craft status window to take focus away and let the user know something is happening + OpenCraftingStatusDialog( this, "#CraftUpdate_Start", true, false, false ); + m_flAbortCraftingAt = vgui::system()->GetCurrentTime() + 10; + vgui::ivgui()->AddTickSignal( GetVPanel(), 100 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Think when we're waiting for a craft response +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::OnTick( void ) +{ + BaseClass::OnTick(); + + if( IsVisible() ) + { + if( m_flAbortCraftingAt != 0.f ) + { + // Timeout for crafting. Let them know we failed. + if( m_flAbortCraftingAt < vgui::system()->GetCurrentTime() ) + { + OpenCraftingStatusDialog( this, "#CraftUpdate_Failed", false, true, false ); + m_flAbortCraftingAt = 0; + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Close the craft status window if we close +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::OnShowPanel( bool bVisible, bool bReturningFromArmory ) +{ + if ( !bVisible ) + { + CloseCraftingStatusDialog(); + vgui::ivgui()->RemoveTickSignal( GetVPanel() ); + } + + BaseClass::OnShowPanel( bVisible, bReturningFromArmory ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Add the new panels into vectors for each group, and set parents to +// appropriate frames +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::AddNewItemPanel( int iPanelIndex ) +{ + if( IsInputPanel( iPanelIndex ) ) + { + // Input item + int nIndex = m_vecRecipeInputModelPanels.AddToTail(); + CInputPanelItemModelPanel* pPanel = vgui::SETUP_PANEL( new CInputPanelItemModelPanel( this, VarArgs("modelpanel%d", iPanelIndex), m_pDynamicRecipeItem )); + m_pItemModelPanels.AddToTail( pPanel ); + pPanel->SetParent( m_pRecipeContainer ); + m_vecRecipeInputModelPanels[nIndex] = pPanel; + } + else if( IsOutputPanel( iPanelIndex ) ) + { + // Output item + CItemModelPanel* pPanel = vgui::SETUP_PANEL( new CItemModelPanel( this, VarArgs("modelpanel%d", iPanelIndex) ) ); + m_vecRecipeOutputModelPanels.AddToTail( pPanel ); + m_pItemModelPanels.AddToTail( pPanel ); + pPanel->SetParent( m_pRecipeContainer ); + } + else + { + // Inventory item + int nIndex = m_vecBackpackModelPanels.AddToTail(); + CRecipeComponentItemModelPanel* pPanel = vgui::SETUP_PANEL( new CRecipeComponentItemModelPanel( this, VarArgs("modelpanel%d", iPanelIndex) ) ); + m_vecBackpackModelPanels[nIndex] = pPanel; + m_pItemModelPanels.AddToTail( pPanel ); + pPanel->SetParent( m_pInventoryContainer ); + } + + CItemModelPanel* pPanel = m_pItemModelPanels.Tail(); + pPanel->SetActAsButton( true, true ); + pPanel->SetTooltip( m_pMouseOverTooltip, "" ); + pPanel->SetShowGreyedOutTooltip( true ); + pPanel->SetShowEquipped( true ); + + // Store a position for our new panel + m_ItemModelPanelPos.AddToTail(); + m_ItemModelPanelPos[iPanelIndex].x = m_ItemModelPanelPos[iPanelIndex].y = 0; + + Assert( iPanelIndex == (m_pItemModelPanels.Count()-1) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Is this index an input panel +//----------------------------------------------------------------------------- +bool CDynamicRecipePanel::IsInputPanel( int iPanelIndex ) const +{ + return iPanelIndex < DYNAMIC_RECIPE_INPUT_COUNT; +} + +//----------------------------------------------------------------------------- +// Purpose: Is this index an output panel +//----------------------------------------------------------------------------- +bool CDynamicRecipePanel::IsOutputPanel( int iPanelIndex) const +{ + return iPanelIndex < ( DYNAMIC_RECIPE_OUTPUT_COUNT + DYNAMIC_RECIPE_INPUT_COUNT ) && !IsInputPanel(iPanelIndex); +} + +//----------------------------------------------------------------------------- +// Purpose: Is this index a backpack panel +//----------------------------------------------------------------------------- +bool CDynamicRecipePanel::IsBackpackPanel( int iPanelIndex ) const +{ + return ( iPanelIndex >= ( DYNAMIC_RECIPE_INPUT_COUNT + DYNAMIC_RECIPE_OUTPUT_COUNT ) + && !IsInputPanel( iPanelIndex ) + && !IsOutputPanel( iPanelIndex ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Is this backpack panel on this page +//----------------------------------------------------------------------------- +bool CDynamicRecipePanel::IsInvPanelOnThisPage( unsigned nIndex ) const +{ + unsigned nNumPerPage = DYNAMIC_RECIPE_BACKPACK_ROWS * DYNAMIC_RECIPE_BACKPACK_COLS; + unsigned nMinIndex = nNumPerPage * GetCurrentPage(); + unsigned nMaxIndex = nNumPerPage * (GetCurrentPage() + 1); + + return nIndex >= nMinIndex && nIndex < nMaxIndex; +} + +//----------------------------------------------------------------------------- +// Purpose: Given the number of items that match the recipe, how many pages +// do we need to show +//----------------------------------------------------------------------------- +int CDynamicRecipePanel::GetNumPages() +{ + return ceil( float(m_nNumRecipeItems) / float(GetNumBackpackPanelsPerPage()) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the page of the backpack panels +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::SetCurrentPage( int nNewPage ) +{ + if ( nNewPage < 0 ) + { + nNewPage = GetNumPages() - 1; + } + else if ( nNewPage >= GetNumPages() ) + { + nNewPage = 0; + } + + FOR_EACH_VEC( m_vecBackpackModelPanels, i ) + { + m_vecBackpackModelPanels[i]->SetPageNumber( nNewPage ); + } + + BaseClass::SetCurrentPage( nNewPage ); +} + +//----------------------------------------------------------------------------- +// Purpose: Chance the current page for all input panels +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::SetCurrentInputPage( int nNewPage ) +{ + m_nInputPage = nNewPage; + + FOR_EACH_VEC( m_vecRecipeInputModelPanels, i ) + { + m_vecRecipeInputModelPanels[i]->SetPageNumber( nNewPage ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Given the number of input items, how many input pages do we need to show +//----------------------------------------------------------------------------- +int CDynamicRecipePanel::GetNumInputPages() const +{ + return ceil( float(m_RecipeIterator.GetInputCount()) / float(GetNumInputPanelsPerPage()) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Given the number of output items, how many output pages do we need to show +//----------------------------------------------------------------------------- +int CDynamicRecipePanel::GetNumOutputPage() const +{ + return ceil( float(m_RecipeIterator.GetOutputCount()) / float(GetNumOutputPanelsPerPage()) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Reset all of the item panels, their pages, their info and re-evaluate +// the recipe item for attributes and the backpack for matching items +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::InitItemPanels() +{ + // Clear out every panel's items + FOR_EACH_VEC( m_pItemModelPanels, i ) + { + m_pItemModelPanels[i]->SetItem( NULL ); + } + + // Go through and set the item to all inventory panels to NULL + FOR_EACH_VEC( m_vecBackpackModelPanels, i ) + { + m_vecBackpackModelPanels[i]->DeleteRecipes(); + } + + // Go through our backpack and repopulate our backpack and matching components + FindPossibleBackpackItems(); + + // Reset to the first page + SetCurrentInputPage( 0 ); + SetCurrentPage( 0 ); + + // Go through and set default items on input panels + FOR_EACH_VEC( m_vecRecipeInputModelPanels, i ) + { + CInputPanelItemModelPanel* pPanel = m_vecRecipeInputModelPanels[i]; + + // Clear out any recipes we have + pPanel->DeleteRecipes(); + // Clear out stale info + pPanel->SetDynamicRecipeItem( m_pDynamicRecipeItem ); + } + + for( int i=0; i < m_RecipeIterator.GetInputCount(); ++i ) + { + CInputPanelItemModelPanel* pPanel = m_vecRecipeInputModelPanels[ i % m_vecRecipeInputModelPanels.Count() ]; + + pPanel->AddDefaultItem( m_RecipeIterator.GetInputItem( i ) ); + pPanel->AddComponentInfo( m_RecipeIterator.GetInputAttrib( i ) ); + } + + // Go through and set items into output panels + FOR_EACH_VEC( m_vecRecipeOutputModelPanels, i ) + { + CItemModelPanel* pPanel = m_vecRecipeOutputModelPanels[i]; + + // Set appropriate item for output panel + if( i < m_RecipeIterator.GetOutputCount() ) + { + int nOutputIndex = (m_nOutputPage * DYNAMIC_RECIPE_OUTPUT_COUNT) + i; + pPanel->SetItem( m_RecipeIterator.GetOutputItem( nOutputIndex ) ); + } + } + + // Go through all the recipe items and set them into inventory panels + PopulatePanelsForCurrentPage(); + + UpdateModelPanels(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Fills in the backpack slots with the items for the current page +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::PopulatePanelsForCurrentPage() +{ + // Go through all the recipe items and set them into inventory panels + FOR_EACH_VEC( m_vecBackpackModelPanels, i ) + { + CRecipeComponentItemModelPanel* pPanel = m_vecBackpackModelPanels[i]; + // Update the item we display. Our inventory might have shifted around + pPanel->UpdateDisplayItem(); + + bool bIsSlotOpen = pPanel->IsSlotAvailable( GetCurrentPage() ); + pPanel->SetVisible( bIsSlotOpen ); + pPanel->SetEnabled( bIsSlotOpen ); + pPanel->InvalidateLayout(); + SetBorderForItem( pPanel, false ); + } + + FOR_EACH_VEC( m_vecRecipeInputModelPanels, i ) + { + CInputPanelItemModelPanel* pPanel = m_vecRecipeInputModelPanels[i]; + pPanel->SetPageNumber( m_nInputPage ); + pPanel->UpdateDisplayItem(); + + bool bIsSlotOpen = pPanel->IsSlotAvailable( m_nInputPage ); + pPanel->SetVisible( bIsSlotOpen ); + pPanel->SetEnabled( bIsSlotOpen ); + pPanel->InvalidateLayout(); + SetBorderForItem( pPanel, false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Go through the player's entire backpack and check if each of them +// passes any of the input panel's attributes criteria +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::FindPossibleBackpackItems() +{ + Assert( m_pDynamicRecipeItem->GetSOCData() ); + // Iterate through the attributes on our recipe item + // and create all of our input and output items. + m_RecipeIterator.Reset(); + CEconItem* pEconItem = m_pDynamicRecipeItem->GetSOCData() ; + pEconItem->IterateAttributes( &m_RecipeIterator ); + + CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory(); + if ( pLocalInv == NULL ) + return; + + m_nNumRecipeItems = 0; + + // Go through our backpack and filter items that match our input criteria + for ( int i = 0 ; i < pLocalInv->GetItemCount() ; ++i ) + { + CEconItemView *pItem = pLocalInv->GetItem( i ); + Assert( pItem ); + + // If we're not showing untradable items, and this item is untradeable + // then we just skip it + if ( !pItem->IsTradable() && !m_bShowUntradable ) + continue; + + CDynamicRecipeItemMatchFind matchingIterator( m_pDynamicRecipeItem, pItem ); + m_pDynamicRecipeItem->IterateAttributes( &matchingIterator ); + if( matchingIterator.MatchesAnyAttributes() ) + { + // Set in the recipe + m_vecBackpackModelPanels[ m_nNumRecipeItems % GetNumBackpackPanelsPerPage() ]->AddRecipe( pItem->GetItemID() ); + ++m_nNumRecipeItems; + } + } + + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Layout the item panels +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::PositionItemPanel( CItemModelPanel *pPanel, int iIndex ) +{ + int iCenter = 0; + int iButtonX = 0, iButtonY = 0, iXPos = 0, iYPos = 0; + + // Position all of the panels + if( IsInputPanel( iIndex ) ) + { + iButtonX = (iIndex % CRAFTING_SLOTS_INPUT_COLUMNS); + iButtonY = (iIndex / CRAFTING_SLOTS_INPUT_COLUMNS); + iXPos = (iCenter + m_iItemCraftingOffcenterX) + (iButtonX * pPanel->GetWide()) + (m_iItemBackpackXDelta * iButtonX); + iYPos = m_iItemYPos + (iButtonY * pPanel->GetTall() ) + (m_iItemBackpackYDelta * iButtonY); + + pPanel->SetPos( iXPos, iYPos ); + } + else if( IsOutputPanel( iIndex ) ) + { + int iButtonIndex = iIndex - DYNAMIC_RECIPE_INPUT_COUNT; + iButtonX = (iButtonIndex % CRAFTING_SLOTS_OUTPUT_COLUMNS); + iButtonY = (iButtonIndex / CRAFTING_SLOTS_OUTPUT_COLUMNS); + iXPos = (iCenter + m_iItemCraftingOffcenterX) + (iButtonX * pPanel->GetWide()) + (m_iItemBackpackXDelta * iButtonX); + iYPos = m_iOutputItemYPos + (iButtonY * pPanel->GetTall() ) + (m_iItemBackpackYDelta * iButtonY); + + pPanel->SetPos( iXPos, iYPos ); + pPanel->SetItem( m_RecipeIterator.GetOutputItem( iButtonIndex ) ); + } + else if( IsBackpackPanel( iIndex ) ) + { + int iButtonIndex = iIndex - DYNAMIC_RECIPE_INPUT_COUNT - DYNAMIC_RECIPE_OUTPUT_COUNT; + iButtonX = (iButtonIndex % DYNAMIC_RECIPE_BACKPACK_COLS); + iButtonY = (iButtonIndex / DYNAMIC_RECIPE_BACKPACK_COLS); + iXPos = (iCenter + m_iInventoryXPos) + (iButtonX * pPanel->GetWide()) + (m_iItemBackpackXDelta * iButtonX); + iYPos = m_iInventoryYPos + (iButtonY * pPanel->GetTall() ) + (m_iItemBackpackYDelta * iButtonY); + + pPanel->SetPos( iXPos, iYPos ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Update each panel to see if it should show, what item to show, and if +// it's greyed out. Update the page labels here too. +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::UpdateModelPanels( void ) +{ + // Check if any input panels have recipe items in them + bool bAnyInputHasRecipe = false; + FOR_EACH_VEC( m_vecRecipeInputModelPanels, i ) + { + CInputPanelItemModelPanel* pPanel = m_vecRecipeInputModelPanels[i]; + + // Update the item we display. Our inventory might have shifted around + pPanel->UpdateDisplayItem(); + + SetBorderForItem( pPanel, false ); + // Check for a recipe + for( int j=0; j < GetNumInputPages(); ++j ) + { + CEconItemView* pRecipe = pPanel->GetRecipeItem( j ); + bAnyInputHasRecipe |= pRecipe != NULL; + } + // Input panels are visible and enabled if their recipe slot is available + pPanel->SetEnabled( pPanel->GetDefaultItem() != NULL ); + pPanel->SetVisible( pPanel->GetDefaultItem() != NULL ); + } + + static CSchemaAttributeDefHandle pAttrib_NoPartialComplete( "recipe no partial complete" ); + bool bPartialCompletionAllowed = !m_pDynamicRecipeItem->FindAttribute( pAttrib_NoPartialComplete ); + + m_pInputsLabel->SetText( bPartialCompletionAllowed ? "#Craft_Recipe_Inputs" : "#Dynamic_Recipe_Outputs_No_Partial_Complete" ); + + // If any of the input panels have a recipe in them, then crafting is available + if ( m_pRecipeCraftButton ) + { + bool bCraftEnabled = ( bPartialCompletionAllowed && bAnyInputHasRecipe ) || AllRecipePanelsFilled(); + m_pRecipeCraftButton->SetEnabled( bCraftEnabled ); + } + if ( m_pRecipeCraftButton ) + { + m_pRecipeCraftButton->SetText( AllRecipePanelsFilled() ? "#CraftConfirm" : "#ToolCustomizeTextureOKButton" ); + } + if ( m_pOutputsLabel ) + { + m_pOutputsLabel->SetText( AllRecipePanelsFilled() ? "#Craft_Recipe_Outputs" : "#Dynamic_Recipe_Outputs_Not_Complete"); + m_pOutputsLabel->SetFgColor( AllRecipePanelsFilled() ? Color( 200, 80, 60, 255 ) : Color( 117, 107, 94, 255 ) ); + } + + FOR_EACH_VEC( m_vecRecipeOutputModelPanels, i ) + { + CItemModelPanel* pPanel = m_vecRecipeOutputModelPanels[i]; + SetBorderForItem( pPanel, false ); + + // Output panels are visible and enabled if they have an item in them + pPanel->SetVisible( pPanel->GetItem() != NULL ); + pPanel->SetEnabled( pPanel->GetItem() != NULL ); + } + + PopulatePanelsForCurrentPage(); + + // Update the current backpack page numbers + char szTmp[16]; + Q_snprintf(szTmp, 16, "%d/%d", GetCurrentPage() + 1, GetNumPages() ); + m_pInventoryContainer->SetDialogVariable( "backpackpage", szTmp ); + + // Update the current input page numbers + Q_snprintf(szTmp, 16, "%d/%d", m_nInputPage + 1, GetNumInputPages() ); + m_pRecipeContainer->SetDialogVariable( "inputpage", szTmp ); + + DeSelectAllBackpackItemPanels(); +} + +//----------------------------------------------------------------------------- +// Purpose: If this is a backpack panel, send the item into the first accepting +// input pane, if one exists, or else do nothing. If this is a input +// panel with a backpack item in it, send the backpack item back to the +// backpack. +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::OnItemPanelMouseDoublePressed( vgui::Panel *panel ) +{ + CRecipeComponentItemModelPanel *pSrcPanel = dynamic_cast < CRecipeComponentItemModelPanel * > ( panel ); + if( !pSrcPanel ) + return; + + int iIndex = GetBackpackPositionForPanel( pSrcPanel ); + + // Send from an input panel to the first open backpack panel, even on a different page + if( IsInputPanel( iIndex ) ) + { + CInputPanelItemModelPanel *pInputPanel = assert_cast<CInputPanelItemModelPanel*>( pSrcPanel ); + Assert( pInputPanel ); + + int nSrcRecipeIndex = pSrcPanel->GetRecipeIndex( pInputPanel->GetPageNumber() ); + CEconItemView* pRecipeItem = pInputPanel->GetRecipeItem( pInputPanel->GetPageNumber() ); + if( pRecipeItem ) + { + // Just put it in the first open slot. + ReturnRecipeItemToBackpack( nSrcRecipeIndex, pInputPanel, pInputPanel->GetPageNumber() ); + } + } + else if( IsBackpackPanel( iIndex ) ) + { + // Sending from the backpack panel to the first matching input panel that's on the current input page. + int nSrcRecipeIndex = pSrcPanel->GetRecipeIndex( GetCurrentPage() ); + FOR_EACH_VEC( m_vecRecipeInputModelPanels, i ) + { + CInputPanelItemModelPanel *pDstPanel = m_vecRecipeInputModelPanels[i]; + if( pDstPanel->GetRecipeItem( pDstPanel->GetPageNumber() ) == NULL && pDstPanel->MatchesAttribCriteria( nSrcRecipeIndex ) ) + { + // Next check if we just want to move the item + SetRecipeComponentIntoPanel( nSrcRecipeIndex, pSrcPanel, pSrcPanel->GetPageNumber(), pDstPanel, pDstPanel->GetPageNumber() ); + break; + } + } + } + + m_bInputPanelsDirty = true; + // Force panels to update + UpdateModelPanels(); +} + +//----------------------------------------------------------------------------- +// Purpose: Border highlight if the panel has an item in it +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::OnItemPanelEntered( vgui::Panel *panel ) +{ + CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel ); + m_pMouseOverItemPanel = pItemPanel; + + CEconItemView *pItem = pItemPanel->GetItem(); + + // Recalc the borders on item panels + FOR_EACH_VEC( m_vecRecipeInputModelPanels, i ) + { + SetBorderForItem( m_vecRecipeInputModelPanels[i], false ); + } + + if ( pItemPanel && IsVisible() ) + { + SetBorderForItem( pItemPanel, pItem != NULL ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Turn of border highlights +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::OnItemPanelExited( vgui::Panel *panel ) +{ + CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel ); + m_pMouseOverItemPanel = NULL; + + // Recalc the borders on item panels + FOR_EACH_VEC( m_vecRecipeInputModelPanels, i ) + { + SetBorderForItem( m_vecRecipeInputModelPanels[i], false ); + } + + if ( pItemPanel && !pItemPanel->IsSelected() ) + { + SetBorderForItem( pItemPanel, false ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: We got a craft response! Close out this window because we're going +// to show the user their loot +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::OnRecipeCompleted() +{ + SetVisible( false ); + m_flAbortCraftingAt = 0.f; + +} + + +//----------------------------------------------------------------------------- +// Purpose: Set the border for the item. Input panels highlight when a dragged +// item matches its criteria. Output items highlight when all inputs +// are fulfilled. +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::SetBorderForItem( CItemModelPanel *pItemPanel, bool bMouseOver ) +{ + if ( !pItemPanel || !pItemPanel->IsVisible() ) + return; + + int iIndex = GetBackpackPositionForPanel( pItemPanel ); + if( IsInputPanel( iIndex ) ) + { + // Special case for input panels. They need to highlight when a dragged item + // matches their criteria. + CItemModelPanel* pPanel = m_bDragging ? m_pMouseDragItemPanel : m_pMouseOverItemPanel; + CEconItemView* pPanelItem = pPanel ? pPanel->GetItem() : NULL; + + CInputPanelItemModelPanel* pInputPanel = dynamic_cast<CInputPanelItemModelPanel*>( pItemPanel ); + Assert( pInputPanel ); + // If this panel doesnt have a recipe, then we want some sort of greyed out look + if( pInputPanel->GetRecipeItem( pInputPanel->GetPageNumber() ) == NULL ) + { + const char *pszBorder = NULL; + int iRarity = GetItemQualityForBorder( pItemPanel ); + + // We only want backpack panels and the drag panel to be highlight sources + bool bValidHighlightSourcePanel = pPanel == m_pMouseDragItemPanel + || m_vecBackpackModelPanels.Find( static_cast<CRecipeComponentItemModelPanel*>(pPanel) ) != m_vecBackpackModelPanels.InvalidIndex(); + + // If this panel can accept whatever the source panel is, we want to highlight a bit + if( bValidHighlightSourcePanel && pPanelItem && pInputPanel->MatchesAttribCriteria( pPanelItem->GetItemID() ) ) + { + if ( pItemPanel->GetItem() ) + { + pszBorder = g_szItemBorders[iRarity][3]; + pItemPanel->SetGreyedOut( NULL ); + } + else + { + pszBorder = g_szItemBorders[iRarity][0]; + } + } + else // Look gloomy and uninviting if not + { + pszBorder = g_szItemBorders[iRarity][3]; + pItemPanel->SetGreyedOut( "" ); + } + + vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); + pItemPanel->SetBorder( pScheme->GetBorder( pszBorder ) ); + return; + } + + } + else if( IsOutputPanel( iIndex ) ) + { + const char *pszBorder = NULL; + int iRarity = GetItemQualityForBorder( pItemPanel ); + + // The output panel greys out when any of the input panels are not fulfilled, + // and lights up when they are all fulfilled + if ( iRarity >= 0 && iRarity < ARRAYSIZE( g_szItemBorders ) ) + { + if( AllRecipePanelsFilled() ) + { + pszBorder = g_szItemBorders[iRarity][0]; + pItemPanel->SetGreyedOut( NULL ); + } + else + { + pszBorder = g_szItemBorders[iRarity][3]; + pItemPanel->SetGreyedOut( NULL ); + } + } + + vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); + pItemPanel->SetBorder( pScheme->GetBorder( pszBorder ) ); + return; + } + + // Backpack and output panels get default treatment + BaseClass::SetBorderForItem( pItemPanel, bMouseOver ); +} + +//----------------------------------------------------------------------------- +// Purpose: Swap recipe items in srcpanel and dstpanel +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::SetRecipeComponentIntoPanel( itemid_t nSrcRecipeIndex, CRecipeComponentItemModelPanel* pSrcPanel, int nSrcPage, CRecipeComponentItemModelPanel* pDstPanel, int nDstPage ) +{ + Assert( nSrcRecipeIndex != 0 ); + Assert( pSrcPanel ); + Assert( pDstPanel ); + Assert( pSrcPanel->GetRecipeItem( nSrcPage ) ); + + // Get the recipe from the destination panel + itemid_t pDstRecipeIndex = pDstPanel->GetRecipeIndex( nDstPage ); + // Swap recipes + pSrcPanel->SetRecipeItem( pDstRecipeIndex, nSrcPage ); + pDstPanel->SetRecipeItem( nSrcRecipeIndex, nDstPage ); +} + +//----------------------------------------------------------------------------- +// Purpose: Check if a given panel is allowed to be dragged. +//----------------------------------------------------------------------------- +bool CDynamicRecipePanel::AllowDragging( CItemModelPanel *pPanel ) +{ + int iIndex = GetBackpackPositionForPanel( pPanel ); + + // If this isn't an input or backpack panel, abort + if( !IsInputPanel( iIndex ) && !IsBackpackPanel( iIndex ) ) + return false; + + CRecipeComponentItemModelPanel* pRecipePanel = dynamic_cast< CRecipeComponentItemModelPanel* >( pPanel ); + Assert( pRecipePanel ); + if( !pRecipePanel ) + return false; + + int nPageNumber = 0; + if( IsBackpackPanel( iIndex ) ) + { + nPageNumber = GetCurrentPage(); + } + else if ( IsInputPanel( iIndex ) ) + { + nPageNumber = m_nInputPage; + } + // Get the recipe item out of this panel + CEconItemView* pRecipe = pRecipePanel->GetRecipeItem( nPageNumber ); + + // If no recipe, abort + if( !pRecipe ) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Start dragging! +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::StartDrag( int x, int y ) +{ + BaseClass::StartDrag( x, y ); + + // Recalc the borders on item panels + FOR_EACH_VEC( m_pItemModelPanels, i ) + { + SetBorderForItem( m_pItemModelPanels[i], false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Stop dragging +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::StopDrag( bool bSucceeded ) +{ + BaseClass::StopDrag( bSucceeded ); + + // Recalc the borders on item panels + FOR_EACH_VEC( m_pItemModelPanels, i ) + { + SetBorderForItem( m_pItemModelPanels[i], false ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Helper function to find out if an input panel can accept an item +//----------------------------------------------------------------------------- +bool CDynamicRecipePanel::InputPanelCanAcceptItem( CItemModelPanel* pPanel, itemid_t nItemID ) +{ + Assert( IsInputPanel( GetBackpackPositionForPanel( pPanel ) ) ); + + CInputPanelItemModelPanel* pInputPanel = dynamic_cast<CInputPanelItemModelPanel*>( pPanel ); + Assert( pInputPanel ); + + return pInputPanel->MatchesAttribCriteria( nItemID ); +} + +//----------------------------------------------------------------------------- +// Purpose: Check if the dragged item can be dropped into a given panel +//----------------------------------------------------------------------------- +bool CDynamicRecipePanel::CanDragTo( CItemModelPanel *pItemPanel, int iPanelIndex ) +{ + // From input to backpack panel + int iDraggedFromPos = GetBackpackPositionForPanel(m_pItemDraggedFromPanel); + if( IsInputPanel( iDraggedFromPos ) && IsBackpackPanel( iPanelIndex ) ) + return true; + + itemid_t itemID = m_pMouseDragItemPanel->GetItem() + ? m_pMouseDragItemPanel->GetItem()->GetItemID() + : 0; + + // From backpack to input panel that can accept the item + if( IsBackpackPanel( iDraggedFromPos ) + && IsInputPanel( iPanelIndex ) + && InputPanelCanAcceptItem( pItemPanel,itemID ) ) + return true; + + // From inoput to other input that can also accept the item + if( IsInputPanel( iDraggedFromPos ) + && IsInputPanel( iPanelIndex ) + && InputPanelCanAcceptItem( pItemPanel, itemID ) ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Handle the user releasing their drag on a given panel. Only valid +// from backpack<->backpack, input<->input, backpack<->input +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::HandleDragTo( CItemModelPanel * /*pItemPanel*/, int iPanelIndex ) +{ + int iDraggedFromPos = GetBackpackPositionForPanel(m_pItemDraggedFromPanel); + + // The destination panel needs to exist, be enabled and be visible or else we dont consider it. + CItemModelPanel* pDestinationPanel = m_pItemModelPanels[ iPanelIndex ]; + if( !pDestinationPanel || !pDestinationPanel->IsEnabled() || !pDestinationPanel->IsVisible() ) + { + return; + } + + m_bInputPanelsDirty = true; + + // If we dragged from a inventory panel onto an input panel + if( IsInputPanel( iPanelIndex ) && IsBackpackPanel( iDraggedFromPos ) ) + { + CRecipeComponentItemModelPanel* pSrcPanel = dynamic_cast<CRecipeComponentItemModelPanel*>( m_pItemModelPanels[iDraggedFromPos] ); + CInputPanelItemModelPanel* pDstPanel = dynamic_cast<CInputPanelItemModelPanel*>( m_pItemModelPanels[iPanelIndex] ); + + if( pSrcPanel && pDstPanel ) + { + // They dragged an item from the backpack into an input slot. Check if this is ok. + itemid_t nSrcRecipeIndex = pSrcPanel->GetRecipeIndex( GetCurrentPage() ); + Assert( nSrcRecipeIndex != 0 ); + if( nSrcRecipeIndex != 0 && pDstPanel->MatchesAttribCriteria( nSrcRecipeIndex ) ) + { + SetRecipeComponentIntoPanel( nSrcRecipeIndex, pSrcPanel, GetCurrentPage(), pDstPanel, pDstPanel->GetPageNumber() ); + } + } + } + else if( IsInputPanel( iDraggedFromPos ) && IsBackpackPanel( iPanelIndex ) ) + { + CRecipeComponentItemModelPanel* pSrcPanel = dynamic_cast<CRecipeComponentItemModelPanel*>( m_pItemModelPanels[iDraggedFromPos] ); + CRecipeComponentItemModelPanel* pDstPanel = dynamic_cast<CRecipeComponentItemModelPanel*>( m_pItemModelPanels[iPanelIndex] ); + + // If we dragged from an input panel to a backpack panel, return the item + // to its original backpack slot regardless of where they dragged it to + if( pSrcPanel && pDstPanel ) + { + // They dragged from an input panel to inventory panel. Check if there's something in + // the inventory slot already. If so, just move to the first open spot. + itemid_t pSrcRecipeIndex = pSrcPanel->GetRecipeIndex( pSrcPanel->GetPageNumber() ); + itemid_t pDstRecipeIndex = pDstPanel->GetRecipeIndex( GetCurrentPage() ); + + Assert( pSrcRecipeIndex != 0 ); + if( pSrcRecipeIndex != 0 && pDstRecipeIndex != 0 ) + { + // There's already a recipe item in there. Just put it in the first open slot. + ReturnRecipeItemToBackpack( pSrcRecipeIndex, pSrcPanel, 0 ); + } + else if( pSrcRecipeIndex ) + { + // It's open! Place in there + SetRecipeComponentIntoPanel( pSrcRecipeIndex, pSrcPanel, pSrcPanel->GetPageNumber(), pDstPanel, GetCurrentPage() ); + } + } + } + else if( IsInputPanel( iDraggedFromPos ) && IsInputPanel( iPanelIndex ) ) + { + CInputPanelItemModelPanel* pSrcPanel = dynamic_cast<CInputPanelItemModelPanel*>( m_pItemModelPanels[iDraggedFromPos] ); + CInputPanelItemModelPanel* pDstPanel = dynamic_cast<CInputPanelItemModelPanel*>( m_pItemModelPanels[iPanelIndex] ); + + // Dragged from an input panel to an input panel. + if( pSrcPanel && pDstPanel ) + { + itemid_t nSrcRecipeIndex = pSrcPanel->GetRecipeIndex( pSrcPanel->GetPageNumber() ); + itemid_t nDstRecipeIndex = pDstPanel->GetRecipeIndex( pDstPanel->GetPageNumber() ); + + // First see if we can swap our inputs + if( nSrcRecipeIndex != 0 && nDstRecipeIndex != 0 ) + { + if( pSrcPanel->MatchesAttribCriteria( nDstRecipeIndex ) && + pDstPanel->MatchesAttribCriteria( nSrcRecipeIndex ) ) + { + SetRecipeComponentIntoPanel( nDstRecipeIndex, pSrcPanel, 0, pDstPanel, 0 ); + } + } + else if( nSrcRecipeIndex != 0 && nDstRecipeIndex == 0 && pDstPanel->MatchesAttribCriteria( nSrcRecipeIndex ) ) + { + // Next check if we just want to move the item + SetRecipeComponentIntoPanel( nSrcRecipeIndex, pSrcPanel, pSrcPanel->GetPageNumber(), pDstPanel, pDstPanel->GetPageNumber() ); + } + } + } + else + { + AssertMsg( 0, "Unhandled drag case!" ); + } + + UpdateModelPanels(); +} + +//----------------------------------------------------------------------------- +// Purpose: Return a given item to the first open slot in out backpack +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::ReturnRecipeItemToBackpack( itemid_t nItemID, CRecipeComponentItemModelPanel* pSrcPanel, int nSrcPage ) +{ + // For each page, check each recipe slot, on each panel for an opening + for( int i=0; i < GetNumPages(); ++i ) + { + FOR_EACH_VEC( m_vecBackpackModelPanels, j ) + { + CRecipeComponentItemModelPanel* pDstPanel = m_vecBackpackModelPanels[j]; + CEconItemView* pDstRecipe = pDstPanel->GetRecipeItem( i ); + bool bSlotIsOpen = pDstPanel->IsSlotAvailable( i ); + if( bSlotIsOpen && pDstRecipe == NULL ) + { + SetRecipeComponentIntoPanel( nItemID, pSrcPanel, nSrcPage, pDstPanel, i ); + return; + } + } + } + + AssertMsg( 0, "No open backpack slot found when returning item to backpack!" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Set in a new recipe item. Update labels and reset panels. +//----------------------------------------------------------------------------- +void CDynamicRecipePanel::SetNewRecipe( CEconItemView* pNewRecipeItem ) +{ + m_flAbortCraftingAt = 0.f; + m_pDynamicRecipeItem = pNewRecipeItem; + // Update recipe title + m_pRecipeContainer->SetDialogVariable( "recipetitle", m_pDynamicRecipeItem->GetItemName() ); + // By default, dont show untradable items + m_pShowUntradableItemsCheckbox->SetSelected( false ); + // Repopulate the panels + InitItemPanels(); + UpdateModelPanels(); +} + + +class CWaitForConsumeDialog : public CGenericWaitingDialog +{ +public: + CWaitForConsumeDialog( vgui::Panel *pParent ) : CGenericWaitingDialog( pParent ) + { + } + +protected: + virtual void OnTimeout() + { + // Play an exciting sound! + vgui::surface()->PlaySound( "misc/achievement_earned.wav" ); + + // Show them their loot! + InventoryManager()->ShowItemsPickedUp( true ); + } +}; + +void CDynamicRecipePanel::OnCraftResponse( itemid_t nNewToolID, EGCMsgResponse eResponse ) +{ + // We got a response. We dont need to time-out + m_flAbortCraftingAt = 0; + + switch( eResponse ) + { + case k_EGCMsgResponseOK: + { + // If a new tool id comes back, that means we only partially completed the recipe + if( nNewToolID != 0 ) + { + OpenCraftingStatusDialog( this, "#Dynamic_Recipe_Response_Success", false, true, false ); + + // Play a sound letting them know the item is gone + const char *pszSoundFilename = m_pDynamicRecipeItem->GetDefinitionString( "recipe_partial_complete_sound", "ui/chem_set_add_element.wav" ); + vgui::surface()->PlaySound( pszSoundFilename ); + + CEconItemView* pNewRecipe = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( nNewToolID ); + Assert( pNewRecipe ); + // Set the new recipe into the panel. This resets all the item panels within. + SetNewRecipe( pNewRecipe ); + } + else + { + CloseCraftingStatusDialog(); + + // No new tool, so we completed the recipe! + const char *pszSoundFilename = m_pDynamicRecipeItem->GetDefinitionString( "recipe_complete_sound", "ui/chem_set_creation.wav" ); + vgui::surface()->PlaySound( pszSoundFilename ); + // Show the "Completing consumption" dialog for 5 seconds + ShowWaitingDialog( new CWaitForConsumeDialog( NULL ), "#ToolConsumptionInProgress", true, false, 5.0f ); + + // Hide the dynamic recipe panel after 6 seconds + g_DynamicRecipePanel->PostMessage( g_DynamicRecipePanel, new KeyValues("RecipeCompleted"), 6.f ); + } + } + break; + case k_EGCMsgResponseInvalid: + { + // Something was bad in the request. + OpenCraftingStatusDialog( this, "#Dynamic_Recipe_Response_Invalid", false, true, false ); + + InitItemPanels(); + UpdateModelPanels(); + } + break; + case k_EGCMsgResponseNoMatch: + { + // One or more of the items sent in didnt match + OpenCraftingStatusDialog( this, "#Dynamic_Recipe_Response_NoMatch", false, true, false ); + + InitItemPanels(); + UpdateModelPanels(); + } + break; + default: + { + OpenCraftingStatusDialog( this, "#Dynamic_Recipe_Response_Default", false, true, false ); + + InitItemPanels(); + UpdateModelPanels(); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: GC Msg handler to receive the dynamic recipe +//----------------------------------------------------------------------------- +class CGCCompleteDynamicRecipeResponse : public GCSDK::CGCClientJob +{ +public: + CGCCompleteDynamicRecipeResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket ); + + itemid_t nNewToolID = INVALID_ITEM_ID; + if( !msg.BReadUint64Data( &nNewToolID ) ) + return true; + + g_DynamicRecipePanel->OnCraftResponse( nNewToolID, (EGCMsgResponse)msg.Body().m_eResponse ); + + return true; + } + +}; + +GC_REG_JOB( GCSDK::CGCClient, CGCCompleteDynamicRecipeResponse, "CGCCompleteDynamicRecipeResponse", k_EMsgGCFulfillDynamicRecipeComponentResponse, GCSDK::k_EServerTypeGCClient ); + +#ifdef STAGING_ONLY +void CDynamicRecipePanel::Debug_GiveRequiredInputs() const +{ + if ( !steamapicontext || !steamapicontext->SteamUser() ) + { + Msg("Not connected to Steam.\n"); + return; + } + CSteamID steamIDForPlayer = steamapicontext->SteamUser()->GetSteamID(); + if ( !steamIDForPlayer.IsValid() ) + { + Msg("Failed to find a valid steamID for the local player.\n"); + return; + } + + FOR_EACH_VEC( m_vecRecipeInputModelPanels, i ) + { + CInputPanelItemModelPanel *pInputPanel = m_vecRecipeInputModelPanels[i]; + + int nPage = 0; + for( const CEconItemAttributeDefinition *pAttrDef = pInputPanel->GetAttrib( nPage ); pAttrDef != NULL; pAttrDef = pInputPanel->GetAttrib( ++nPage ) ) + { + CAttribute_DynamicRecipeComponent attribValue; + if( m_pDynamicRecipeItem->FindAttribute<CAttribute_DynamicRecipeComponent >( pAttrDef, &attribValue ) ) + { + GCSDK::CProtoBufMsg<CMsgDevNewItemRequest> msg( k_EMsgGCDev_NewItemRequest ); + msg.Body().set_receiver( steamIDForPlayer.ConvertToUint64() ); + CItemSelectionCriteria criteria; + + if( attribValue.component_flags() & DYNAMIC_RECIPE_FLAG_PARAM_ITEM_DEF_SET ) + { + criteria.BAddCondition( "name", k_EOperator_String_EQ, GetItemSchema()->GetItemDefinition( attribValue.def_index() )->GetDefinitionName(), true ); + } + + if( attribValue.component_flags() & DYNAMIC_RECIPE_FLAG_PARAM_QUALITY_SET ) + { + criteria.SetQuality( attribValue.item_quality() ); + } + + criteria.SetIgnoreEnabledFlag( true ); + criteria.BSerializeToMsg( *msg.Body().mutable_criteria() ); + GCClientSystem()->BSendMessage( msg ); + } + } + } +} +#endif diff --git a/game/client/tf/vgui/dynamic_recipe_subpanel.h b/game/client/tf/vgui/dynamic_recipe_subpanel.h new file mode 100644 index 0000000..f0bddaa --- /dev/null +++ b/game/client/tf/vgui/dynamic_recipe_subpanel.h @@ -0,0 +1,267 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef DYNAMIC_RECIPE_SUBPANEL_H +#define DYNAMIC_RECIPE_SUBPANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "backpack_panel.h" +#include "vgui_controls/ScrollableEditablePanel.h" +#include "tf_gcmessages.h" +#include "econ_gcmessages.h" +#include "tf_imagepanel.h" +#include "tf_controls.h" +#include "item_selection_panel.h" +#include "econ_dynamic_recipe.h" + +class CImageButton; + +#define DYNAMIC_RECIPE_INPUT_ROWS 4 +#define DYNAMIC_RECIPE_INPUT_COLS 3 +#define DYNAMIC_RECIPE_INPUT_COUNT ( DYNAMIC_RECIPE_INPUT_ROWS * DYNAMIC_RECIPE_INPUT_COLS ) +#define DYNAMIC_RECIPE_OUTPUT_ROWS 4 +#define DYNAMIC_RECIPE_OUTPUT_COLS 3 +#define DYNAMIC_RECIPE_OUTPUT_COUNT ( DYNAMIC_RECIPE_OUTPUT_ROWS * DYNAMIC_RECIPE_OUTPUT_COLS ) + +#define DYNAMIC_RECIPE_BACKPACK_ROWS 4 +#define DYNAMIC_RECIPE_BACKPACK_COLS 4 +#define DYNAMIC_RECIPE_PACKPACK_COUNT_PER_PAGE ( DYNAMIC_RECIPE_BACKPACK_ROWS * DYNAMIC_RECIPE_BACKPACK_COLS ) + +class CRecipeComponentItemModelPanel; + +class CRecipeComponentItemModelPanel : public CItemModelPanel +{ +public: + DECLARE_CLASS_SIMPLE( CRecipeComponentItemModelPanel, CItemModelPanel ); + CRecipeComponentItemModelPanel( vgui::Panel *parent, const char *name ); + + void AddRecipe( itemid_t nRecipe ); + virtual void DeleteRecipes(); + virtual void SetItem( const CEconItemView *pItem ) OVERRIDE; + void SetRecipeItem( itemid_t nRecipeItem, int nPageNumber ); + void AddDefaultItem( CEconItemView *pItem ); + CEconItemView* GetRecipeItem( int nPageNumber ) const; + itemid_t GetRecipeIndex( int nPageNumber ) const; + bool IsSlotAvailable( int nPageNumber ); + CEconItemView* GetDefaultItem() const { return m_nPageNumber < m_vecDefaultItems.Count() ? m_vecDefaultItems[ m_nPageNumber ] : NULL; } + void UpdateDisplayItem(); + + void SetPageNumber( int nPageNumber ); + int GetPageNumber() const { return m_nPageNumber; } +protected: + struct RecipeItem_t + { + itemid_t m_nRecipeIndex; + CEconItemView* m_pRecipeItem; + }; + + void UpdateRecipeItem( RecipeItem_t* pRecipeItem ); + virtual void SetBlankState(); + + CUtlVector< CEconItemView* > m_vecDefaultItems; + CUtlVector< RecipeItem_t > m_vecRecipes; + int m_nPageNumber; +}; + +class CInputPanelItemModelPanel : public CRecipeComponentItemModelPanel +{ +public: + CInputPanelItemModelPanel( vgui::Panel *parent, const char *name, const CEconItemView* pDynamicRecipeItem ) + : CRecipeComponentItemModelPanel( parent, name ) + , m_pDynamicRecipeItem( pDynamicRecipeItem ) + {} + + virtual void DeleteRecipes(); + void AddComponentInfo( const CEconItemAttributeDefinition *pComponentAttrib ); + bool MatchesAttribCriteria( itemid_t itemID ) const; + bool MatchesAttribCriteria( itemid_t itemID, int nPageNumber ) const; + const CEconItemAttributeDefinition * GetAttrib( int nPageNumber ) const; + void SetDynamicRecipeItem( const CEconItemView* pDynamicRecipeItem ) { m_pDynamicRecipeItem = pDynamicRecipeItem; } + +protected: + virtual void SetBlankState() OVERRIDE; + +private: + CUtlVector< const CEconItemAttributeDefinition* > m_vecAttrDef; + const CEconItemView* m_pDynamicRecipeItem; +}; + + +//----------------------------------------------------------------------------- +// An inventory screen that handles displaying the crafting screen +//----------------------------------------------------------------------------- +class CDynamicRecipePanel : public CBackpackPanel +{ + DECLARE_CLASS_SIMPLE( CDynamicRecipePanel, CBackpackPanel ); +public: + +#ifdef STAGING_ONLY + void Debug_GiveRequiredInputs() const; + CExButton *m_pDevGiveInputsButton; +#endif + + CDynamicRecipePanel( vgui::Panel *parent, const char *panelName, CEconItemView* pRecipeItem ); + ~CDynamicRecipePanel( void ); + + void SetNewRecipe( CEconItemView* pNewRecipeItem ); + void ConsumeItem( ); + void InitItemPanels(); + virtual const char *GetResFile( void ) { return "Resource/UI/DynamicRecipePanel.res"; } + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE; + virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE; + virtual void PerformLayout( void ) OVERRIDE; + virtual void OnCommand( const char *command ) OVERRIDE; + virtual void OnKeyCodePressed( vgui::KeyCode code ) OVERRIDE; + void OnButtonChecked( KeyValues *pData ) OVERRIDE; + + virtual void OpenContextMenu() OVERRIDE {} + virtual int GetNumItemPanels( void ) OVERRIDE; + virtual void AddNewItemPanel( int iPanelIndex ) OVERRIDE; + void Craft(); + virtual void OnTick( void ) OVERRIDE; + virtual void OnShowPanel( bool bVisible, bool bReturningFromArmory ) OVERRIDE; + void OnCraftResponse( itemid_t nNewToolID, EGCMsgResponse eResponse ); +private: + + bool IsInputPanel( int iPanelIndex ) const; + bool IsOutputPanel( int iPanelIndex) const; + bool IsBackpackPanel( int iPanelIndex) const; + bool IsInvPanelOnThisPage( unsigned nIndex ) const; + int GetNumBackpackPanelsPerPage() const { return DYNAMIC_RECIPE_BACKPACK_ROWS * DYNAMIC_RECIPE_BACKPACK_COLS; } + virtual int GetNumPages() OVERRIDE; + virtual void SetCurrentPage( int nNewPage ) OVERRIDE; + int GetFirstBackpackIndex() const { return DYNAMIC_RECIPE_INPUT_COUNT + DYNAMIC_RECIPE_OUTPUT_COUNT; } + + void SetCurrentInputPage( int nNewPage ); + int GetNumInputPages() const; + int GetNumInputPanelsPerPage() const { return DYNAMIC_RECIPE_INPUT_COUNT; } + + int GetNumOutputPage() const; + int GetNumOutputPanelsPerPage() const { return DYNAMIC_RECIPE_OUTPUT_COUNT; } + + class CRecipeComponentAttributeCounter : public CEconItemSpecificAttributeIterator + { + public: + CRecipeComponentAttributeCounter() + : m_nInputCount( 0 ) + {} + ~CRecipeComponentAttributeCounter() { Reset(); } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_DynamicRecipeComponent& value ) OVERRIDE; + int GetInputCount() const { return m_nInputCount; } + int GetOutputCount() const { return m_vecOutputItems.Count(); } + CEconItemView* GetOutputItem( int i ); + CEconItemView* GetInputItem( int i ); + const CEconItemAttributeDefinition* GetInputAttrib( int i ); + + void Reset(); + + private: + + struct InputComponent_t + { + CEconItemView m_ItemView; + const CEconItemAttributeDefinition* m_pAttrib; + }; + + typedef CUtlVector< CCopyableUtlVector<InputComponent_t> > InputComponentVec; + + static int LeastCommonInputSortFunc( const CCopyableUtlVector<InputComponent_t> *p1, const CCopyableUtlVector<InputComponent_t> *p2 ); + InputComponent_t* GetInputComponent( int i ); + + InputComponentVec m_vecInputItems; + CUtlVector< CEconItemView > m_vecOutputItems; + CUtlVector< CEconItem* > m_vecTempEconItems; + int m_nInputCount; + }; + + class CDynamicRecipeItemMatchFind : public CEconItemSpecificAttributeIterator + { + public: + CDynamicRecipeItemMatchFind( const CEconItemView* pSourceItem, const CEconItemView* pItemTomatch ) + : m_bMatchesAny( false ) + , m_pSourceItem( pSourceItem ) + , m_pItemToMatch( pItemTomatch ) + {} + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_DynamicRecipeComponent& value ) OVERRIDE; + bool MatchesAnyAttributes() const { return m_bMatchesAny; } + private: + const CEconItemView* m_pSourceItem; + const CEconItemView* m_pItemToMatch; + bool m_bMatchesAny; + }; + + CEconItemView* m_pDynamicRecipeItem; + CRecipeComponentAttributeCounter m_RecipeIterator; + + bool AllRecipePanelsFilled( void ); + bool CheckForUntradableItems( void ); + bool WarnAboutPartialCompletion( void ); + void FindPossibleBackpackItems(); + virtual void PositionItemPanel( CItemModelPanel *pPanel, int iIndex ); + void PopulatePanelsForCurrentPage(); + virtual void UpdateModelPanels( void ); + virtual void SetBorderForItem( CItemModelPanel *pItemPanel, bool bMouseOver ); + void SetRecipeComponentIntoPanel( itemid_t nSrcRecipeIndex, CRecipeComponentItemModelPanel* pSrcPanel, int nSrcPage, CRecipeComponentItemModelPanel* pDstPanel, int nDstPage ); + bool InputPanelCanAcceptItem( CItemModelPanel* pPanel, itemid_t nItemID ); + + CTFTextToolTip *m_pToolTip; + vgui::EditablePanel *m_pToolTipEmbeddedPanel; + CExButton *m_pRecipeCraftButton; + CExLabel *m_pNoMatchesLabel; + CExLabel *m_pUntradableOutputsLabel; + CExLabel *m_pInputsLabel; + CExLabel *m_pOutputsLabel; + vgui::Label *m_pCurInputPageLabel; + CExButton *m_pNextInputPageButton; + CExButton *m_pPrevInputPageButton; + CItemModelPanel *m_pMouseOverItemPanel; + vgui::CheckButton *m_pShowUntradableItemsCheckbox; + + CUtlVector<CInputPanelItemModelPanel*> m_vecRecipeInputModelPanels; + CUtlVector<CRecipeComponentItemModelPanel*> m_vecBackpackModelPanels; + CUtlVector<CItemModelPanel*> m_vecRecipeOutputModelPanels; + + vgui::EditablePanel *m_pRecipeContainer; + vgui::EditablePanel *m_pInventoryContainer; + + unsigned m_nNumRecipeItems; + bool m_bAllRecipePanelsFilled; + bool m_bInputPanelsDirty; + bool m_bShowUntradable; + + int m_nInputPage; + int m_nOutputPage; + + float m_flAbortCraftingAt; + + MESSAGE_FUNC_PTR( OnItemPanelMouseDoublePressed, "ItemPanelMouseDoublePressed", panel ); + MESSAGE_FUNC_PTR( OnItemPanelEntered, "ItemPanelEntered", panel ); + MESSAGE_FUNC_PTR( OnItemPanelExited, "ItemPanelExited", panel ); + MESSAGE_FUNC( OnRecipeCompleted, "RecipeCompleted" ); + + virtual bool AllowDragging( CItemModelPanel *panel ) OVERRIDE; + virtual void StartDrag( int x, int y ) OVERRIDE; + virtual void StopDrag( bool bSucceeded ) OVERRIDE; + virtual bool CanDragTo( CItemModelPanel *pItemPanel, int iPanelIndex ) OVERRIDE; + virtual void HandleDragTo( CItemModelPanel *pItemPanel, int iPanelIndex ) OVERRIDE; + + void ReturnRecipeItemToBackpack( itemid_t nItemID, CRecipeComponentItemModelPanel* pSrcPanel, int nSrcPage ); + + CPanelAnimationVarAliasType( int, m_iItemCraftingOffcenterX, "item_crafting_offcenter_x", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iOutputItemYPos, "output_item_ypos", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iInventoryXPos, "inventory_xpos", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iInventoryYPos, "inventory_ypos", "0", "proportional_int" ); + + friend void ConfirmDestroyItems( bool bConfirmed, void* pContext ); +}; + +#endif // DYNAMIC_RECIPE_SUBPANEL_H diff --git a/game/client/tf/vgui/halloween_offering_panel.cpp b/game/client/tf/vgui/halloween_offering_panel.cpp new file mode 100644 index 0000000..9cc3d1c --- /dev/null +++ b/game/client/tf/vgui/halloween_offering_panel.cpp @@ -0,0 +1,88 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "halloween_offering_panel.h" +#include "cdll_client_int.h" +#include "ienginevgui.h" +#include "econ_item_tools.h" +#include "econ_ui.h" +#include <vgui_controls/AnimationController.h> +#include "clientmode_tf.h" +#include "softline.h" +#include "drawing_panel.h" +#include "tf_item_inventory.h" +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CHalloweenOfferingPanel::CHalloweenOfferingPanel( vgui::Panel *parent, CItemModelPanelToolTip* pTooltip ) + : BaseClass( parent, pTooltip ) +{ + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CHalloweenOfferingPanel::~CHalloweenOfferingPanel( void ) +{ + +} + +//----------------------------------------------------------------------------- +void CHalloweenOfferingPanel::CreateSelectionPanel() +{ + CHalloweenOfferingSelectionPanel *pSelectionPanel = new CHalloweenOfferingSelectionPanel( this ); + m_hSelectionPanel = (CCollectionCraftingSelectionPanel*)pSelectionPanel; +} +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHalloweenOfferingPanel::OnCommand( const char *command ) +{ + if ( FStrEq( "envelopesend", command ) ) + { + GCSDK::CProtoBufMsg<CMsgCraftHalloweenOffering> msg( k_EMsgGCCraftHalloweenOffering ); + + // Find the Garygoyle 'tool' item for this + static CSchemaItemDefHandle pItemDef_Gargoyle( "Activated Halloween Pass" ); + Assert( pItemDef_Gargoyle ); + if ( !pItemDef_Gargoyle ) + return; + // Find out if the user owns this item or not and place in the proper bucket + CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory(); + if ( !pLocalInv ) + return; + + const CEconItemView *pRefItem = pLocalInv->FindFirstItembyItemDef( pItemDef_Gargoyle->GetDefinitionIndex() ); + if ( !pRefItem ) + return; + + msg.Body().set_tool_id( pRefItem->GetItemID() ); + + FOR_EACH_VEC( m_vecItemPanels, i ) + { + if ( m_vecItemPanels[ i ]->GetItem() == NULL ) + return; + + msg.Body().add_item_id( m_vecItemPanels[ i ]->GetItem()->GetItemID() ); + } + // Send if off + GCClientSystem()->BSendMessage( msg ); + + m_bWaitingForGCResponse = true; + m_nFoundItemID.Purge(); + m_timerResponse.Start( 5.f ); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_LetterSend" ); + return; + } + + BaseClass::OnCommand( command ); +}
\ No newline at end of file diff --git a/game/client/tf/vgui/halloween_offering_panel.h b/game/client/tf/vgui/halloween_offering_panel.h new file mode 100644 index 0000000..cc4c092 --- /dev/null +++ b/game/client/tf/vgui/halloween_offering_panel.h @@ -0,0 +1,54 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef HALLOWEEN_OFFERING_PANEL_H +#define HALLOWEEN_OFFERING_PANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "collection_crafting_panel.h" + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CHalloweenOfferingSelectionPanel : public CCollectionCraftingSelectionPanel +{ + DECLARE_CLASS_SIMPLE( CHalloweenOfferingSelectionPanel, CCollectionCraftingSelectionPanel ); +public: + CHalloweenOfferingSelectionPanel( Panel *pParent ) : BaseClass( pParent ) {} + + //----------------------------------------------------------------------------- + virtual const char *GetSelectionInvalidReason( const IEconItemInterface *pTestItem, const IEconItemInterface *pSourceItem ) const + { + return GetHalloweenOfferingInvalidReason( pTestItem, pSourceItem ); + } +}; + + +//----------------------------------------------------------------------------- +// A panel to let users choose 10 weapons to craft up within collections +//----------------------------------------------------------------------------- +class CHalloweenOfferingPanel : public CCollectionCraftingPanel +{ +public: + DECLARE_CLASS_SIMPLE( CHalloweenOfferingPanel, CCollectionCraftingPanel ); + CHalloweenOfferingPanel( vgui::Panel *parent, CItemModelPanelToolTip* pTooltip ); + ~CHalloweenOfferingPanel( void ); + + virtual const char *GetResFile( void ) { return "Resource/UI/econ/HalloweenOfferingDialog.res"; } + virtual void OnCommand( const char *command ) OVERRIDE; + + virtual int GetInputItemCount() { return HALLOWEEN_OFFERING_ITEM_COUNT; } + +protected: + + virtual void CreateSelectionPanel(); +}; + +#endif // HALLOWEEN_OFFERING_PANEL_H diff --git a/game/client/tf/vgui/item_ad_panel.cpp b/game/client/tf/vgui/item_ad_panel.cpp new file mode 100644 index 0000000..75206cc --- /dev/null +++ b/game/client/tf/vgui/item_ad_panel.cpp @@ -0,0 +1,489 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "item_ad_panel.h" +#include "econ_item_system.h" +#include "item_model_panel.h" +#include "econ_store.h" +#include "econ_ui.h" +#include "store/store_panel.h" +#include "tf_controls.h" +#include "econ_item_description.h" +#include "vgui/IInput.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseAdPanel::CBaseAdPanel( Panel *parent, const char *panelName ) + : BaseClass( parent, panelName ) +{} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseAdPanel::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + m_flPresentTime = inResourceData->GetFloat( "present_time", 10.f ); +} + +bool CBaseAdPanel::CheckForRequiredSteamComponents( const char* pszSteamRequried, const char* pszOverlayRequired ) +{ + // Make sure we've got the appropriate connections to Steam + if ( !steamapicontext || !steamapicontext->SteamUtils() ) + { + OpenStoreStatusDialog( NULL, pszSteamRequried, true, false ); + return false; + } + + if ( !steamapicontext->SteamUtils()->IsOverlayEnabled() ) + { + OpenStoreStatusDialog( NULL, pszOverlayRequired, true, false ); + return false; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CItemAdPanel::CItemAdPanel( Panel *parent, const char *panelName, item_definition_index_t itemDefIndex ) + : BaseClass( parent, panelName ) + , m_ItemDefIndex( itemDefIndex ) + , m_bShowMarketButton( true ) +{ + SetDialogVariable( "price", "..." ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemAdPanel::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( GetItemDef()->GetAdResFile() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemAdPanel::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + m_bShowMarketButton = inResourceData->GetBool( "show_market", true ); // Default to showing market + + if ( !m_bShowMarketButton ) + { + // Tick every second as we try to get our price from the store + vgui::ivgui()->AddTickSignal( GetVPanel(), 1000 ); + } + + const CTFItemDefinition* pItemDef = GetItemDef(); + CItemModelPanel* pItemImage = FindControl< CItemModelPanel >( "ItemIcon" ); + if ( pItemImage ) + { + CEconItemView adItem; + adItem.Init( pItemDef->GetDefinitionIndex(), AE_UNIQUE, 1, 1 ); + pItemImage->InvalidateLayout( true, true ); + pItemImage->SetItem( &adItem ); + + KeyValuesAD modelpanelKV( "modelpanel_kv" ); + KeyValues *itemKV = new KeyValues( "itemmodelpanel" ); + itemKV->SetBool( "inventory_image_type", true ); + itemKV->SetBool( "use_item_rendertarget", false ); + itemKV->SetBool( "allow_rot", false ); + + modelpanelKV->AddSubKey( itemKV ); + pItemImage->ApplySettings( modelpanelKV ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemAdPanel::PerformLayout() +{ + BaseClass::PerformLayout(); + + const CTFItemDefinition* pItemDef = GetItemDef(); + + // Get the ad text for the item. If it's not there, juse use the description text. + SetDialogVariable( "item_name", g_pVGuiLocalize->Find( pItemDef->GetItemBaseName() ) ); + const char* pszAdtext = pItemDef->GetAdTextToken() ? pItemDef->GetAdTextToken() : pItemDef->GetItemDesc(); + CExScrollingEditablePanel* pScrollableItemText = FindControl< CExScrollingEditablePanel >( "ScrollableItemText", true ); + if ( pszAdtext && pScrollableItemText ) + { + pScrollableItemText->SetDialogVariable( "item_ad_text", g_pVGuiLocalize->Find( pszAdtext ) ); + + Label* pAdLabel = pScrollableItemText->FindControl< Label >( "ItemAdText", true ); + if ( pAdLabel ) + { + int nWide, nTall; + pAdLabel->GetContentSize( nWide, nTall ); + pAdLabel->SetTall( nTall ); + } + + pScrollableItemText->InvalidateLayout( true ); + } + + CExButton* pBuyButton = FindControl< CExButton >( "BuyButton", true ); + CExButton* pMarketButton = FindControl< CExButton >( "MarketButton", true ); + if ( pBuyButton && pMarketButton ) + { + + pBuyButton->SetVisible( !m_bShowMarketButton ); + pMarketButton->SetVisible( m_bShowMarketButton ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemAdPanel::OnTick() +{ + const CTFItemDefinition* pItemDef = GetItemDef(); + bool bStoreIsReady = EconUI()->GetStorePanel() && EconUI()->GetStorePanel()->GetPriceSheet() && EconUI()->GetStorePanel()->GetCart() && steamapicontext && steamapicontext->SteamUser() && pItemDef; + if ( bStoreIsReady ) + { + // Get the price of the item + const ECurrency eCurrency = EconUI()->GetStorePanel()->GetCurrency(); + const econ_store_entry_t *pEntry = EconUI()->GetStorePanel()->GetPriceSheet()->GetEntry( pItemDef->GetDefinitionIndex() ); + if ( pEntry ) + { + item_price_t unPrice = pEntry->GetCurrentPrice( eCurrency ); + // Set that price into the button + wchar_t wzLocalizedPrice[ kLocalizedPriceSizeInChararacters ]; + MakeMoneyString( wzLocalizedPrice, ARRAYSIZE( wzLocalizedPrice ), unPrice, eCurrency ); + SetDialogVariable( "price", wzLocalizedPrice ); + + // Don't need to tick anymore + vgui::ivgui()->RemoveTickSignal( GetVPanel() ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const CTFItemDefinition* CItemAdPanel::GetItemDef() const +{ + return (CTFItemDefinition*)ItemSystem()->GetItemSchema()->GetItemDefinition( m_ItemDefIndex ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemAdPanel::OnCommand( const char *command ) +{ + if ( FStrEq( "purchase", command ) ) + { + if ( !CheckForRequiredSteamComponents( "#StoreUpdate_SteamRequired", "#MMenu_OverlayRequired" ) ) + return; + + const CTFItemDefinition* pItemDef = GetItemDef(); + if ( pItemDef ) + { + if ( EconUI()->GetStorePanel() && EconUI()->GetStorePanel()->GetPriceSheet() && EconUI()->GetStorePanel()->GetCart() && steamapicontext && steamapicontext->SteamUser() ) + { + // Add a the item to the users cart and checkout + EconUI()->GetStorePanel()->GetCart()->EmptyCart(); + AddItemToCartHelper( NULL, pItemDef->GetDefinitionIndex(), kCartItem_Purchase ); + EconUI()->GetStorePanel()->InitiateCheckout( true ); + } + } + } + else if ( FStrEq( "market", command ) ) + { + if ( !CheckForRequiredSteamComponents( "#StoreUpdate_SteamRequired", "#MMenu_OverlayRequired" ) ) + return; + + const CTFItemDefinition* pItemDef = GetItemDef(); + if ( pItemDef && steamapicontext && steamapicontext->SteamFriends() ) + { + const char *pszPrefix = ""; + if ( GetUniverse() == k_EUniverseBeta ) + { + pszPrefix = "beta."; + } + + static char pszItemName[256]; + g_pVGuiLocalize->ConvertUnicodeToANSI( g_pVGuiLocalize->Find ( pItemDef->GetItemBaseName() ) , pszItemName, sizeof(pszItemName) ); + + char szURL[512]; + V_snprintf( szURL, sizeof(szURL), "http://%ssteamcommunity.com/market/listings/%d/%s", pszPrefix, engine->GetAppID(), pszItemName ); + steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( szURL ); + } + } +} + + + +DECLARE_BUILD_FACTORY( CCyclingAdContainerPanel ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCyclingAdContainerPanel::CCyclingAdContainerPanel( Panel *parent, const char *panelName ) + : BaseClass( parent, panelName ) + , m_pAdsContainer( NULL ) + , m_pKVItems( NULL ) + , m_nCurrentIndex( 0 ) + , m_nXPos( 0 ) + , m_nTargetIndex( 0 ) + , m_nTransitionStartOffsetX( 0 ) + , m_bTransitionRight( true ) + , m_bSettingsApplied( false ) + , m_bNeedsToCreatePanels( false ) +{ + m_pAdsContainer = new EditablePanel( this, "AdsContainer" ); + m_pFadePanel = new EditablePanel( this, "FadeTransition" ); + m_pNextButton = new CExButton( this, "NextButton", ">", this, "next" ); + m_pPrevButton = new CExButton( this, "PrevButton", "<", this, "prev" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCyclingAdContainerPanel::~CCyclingAdContainerPanel() +{ + if ( m_pKVItems ) + { + m_pKVItems->deleteThis(); + m_pKVItems = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCyclingAdContainerPanel::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "Resource/UI/econ/CyclingAdContainer.res" ); + + m_bSettingsApplied = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCyclingAdContainerPanel::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + KeyValues* pKVItems = inResourceData->FindKey( "items" ); + if ( pKVItems ) + { + SetItemKVs( pKVItems ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCyclingAdContainerPanel::CreatePanels() +{ + if ( ItemSystem()->GetItemSchema()->GetVersion() == 0 ) + return; + + m_vecPossibleAds.Purge(); + + FOR_EACH_TRUE_SUBKEY( m_pKVItems, pKVItem ) + { + const char* pszItemName = pKVItem->GetString( "item" ); + const CEconItemDefinition *pDef = ItemSystem()->GetItemSchema()->GetItemDefinitionByName( pszItemName ); + if ( pDef ) + { + AdData_t& adData = m_vecPossibleAds[ m_vecPossibleAds.AddToTail() ]; + adData.m_pAdPanel = new CItemAdPanel( m_pAdsContainer, "ad", pDef->GetDefinitionIndex() ); + + adData.m_pAdPanel->InvalidateLayout( true, true ); // Default settings + adData.m_pAdPanel->ApplySettings( pKVItem ); + adData.m_pAdPanel->InvalidateLayout(); + } + else + { + AssertMsg( 0, "Invalid item def '%s'!", pszItemName ); + } + } + + m_bNeedsToCreatePanels = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCyclingAdContainerPanel::PerformLayout() +{ + BaseClass::PerformLayout(); + + PresentIndex( 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCyclingAdContainerPanel::OnThink() +{ + BaseClass::OnThink(); + + if ( m_bNeedsToCreatePanels && m_bSettingsApplied ) + { + CreatePanels(); + PresentIndex( 0 ); + } + + UpdateAdPanelPositions(); + + // See if it's time to auto-cycle to the next ad + if ( m_ShowTimer.HasStarted() && m_ShowTimer.IsElapsed() && m_vecPossibleAds.Count() > 1 ) + { + m_ShowTimer.Invalidate(); + PresentIndex( m_nTargetIndex + 1 ); + } + + int nMouseX, nMouseY; + vgui::input()->GetCursorPos( nMouseX, nMouseY ); + bool bControlsVisible = IsWithin( nMouseX, nMouseY ) && m_vecPossibleAds.Count() > 1; + m_pPrevButton->SetVisible( bControlsVisible ); + m_pNextButton->SetVisible( bControlsVisible ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCyclingAdContainerPanel::SetItemKVs( KeyValues* pKVItems ) +{ + if ( pKVItems ) + { + if ( m_pKVItems ) + { + m_pKVItems->deleteThis(); + m_pKVItems = NULL; + } + m_pKVItems = pKVItems->MakeCopy(); + } + + m_bNeedsToCreatePanels = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCyclingAdContainerPanel::OnCommand( const char *command ) +{ + if ( FStrEq( "next", command ) ) + { + PresentIndex( m_nTargetIndex + 1 ); + } + else if ( FStrEq( "prev", command ) ) + { + PresentIndex( m_nTargetIndex - 1 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCyclingAdContainerPanel::PresentIndex( int nIndex ) +{ + if ( m_vecPossibleAds.IsEmpty() ) + return; + + if ( m_nCurrentIndex == nIndex ) + return; + + // Figure out which way we want to ransition + m_bTransitionRight = nIndex > m_nCurrentIndex; + + // Wrap if needed + if ( nIndex >= m_vecPossibleAds.Count() ) + { + nIndex = 0; + } + else if ( nIndex < 0 ) + { + nIndex = m_vecPossibleAds.Count() - 1; + } + + m_nTargetIndex = nIndex; + + // If they click more times while transitioning out, just change the target. If we're + // into transitioning in to the next panel, then we need to start the whole thing over. + if ( !IsTransitioningOut() ) + { + m_nTransitionStartOffsetX = m_nXPos; + float flTransitionTime = 1.f; + m_TransitionTimer.Start( flTransitionTime ); + + m_ShowTimer.Start( flTransitionTime + m_vecPossibleAds[ m_nCurrentIndex ].m_pAdPanel->GetPresentTime() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCyclingAdContainerPanel::UpdateAdPanelPositions() +{ + // Figure out how far along a transition we are + float flPercent = Clamp( m_TransitionTimer.GetElapsedTime() / m_TransitionTimer.GetCountdownDuration(), 0.f, 1.f ); + flPercent = Gain( flPercent, 0.8f ); + + // At a certain point, we're no longer transitioning out the old -- we're transitioning in the new + const float flTransitionCutOff = m_TransitionTimer.GetCountdownDuration() / 2.f; + bool bTransitionOut = flPercent < flTransitionCutOff; + + int nStartX = 0; + int nTargetX = 0; + float flFadeAmount = 0.f; + + if ( bTransitionOut ) + { + nStartX = m_nTransitionStartOffsetX; + nTargetX = m_bTransitionRight ? -100 : 100; + flFadeAmount = RemapValClamped( flPercent, 0.f, flTransitionCutOff * 0.75f, 0.f, 255.f ); + } + else + { + // Once we've passed the middle, show the target + m_nCurrentIndex = m_nTargetIndex; + nStartX = m_bTransitionRight ? 100 : -100; + nTargetX = 0; + flFadeAmount = RemapValClamped( flPercent, flTransitionCutOff * 1.25f, 1.f, 255.f, 0.f ); + } + + // Alpha fades up entirely near the middle to cover the swap + m_pFadePanel->SetAlpha( flFadeAmount ); + + m_nXPos = RemapVal( flPercent, 0.f, 1.f, nStartX, nTargetX ); + FOR_EACH_VEC( m_vecPossibleAds, i ) + { + m_vecPossibleAds[i].m_pAdPanel->SetPos( m_nXPos, m_vecPossibleAds[i].m_pAdPanel->GetYPos() ); + m_vecPossibleAds[i].m_pAdPanel->SetVisible( i == m_nCurrentIndex ); + } +} + +float CCyclingAdContainerPanel::GetTransitionProgress() const +{ + float flPercent = Clamp( m_TransitionTimer.GetElapsedTime() / m_TransitionTimer.GetCountdownDuration(), 0.f, 1.f ); + return Gain( flPercent, 0.8f ); +} + +bool CCyclingAdContainerPanel::IsTransitioningOut() const +{ + const float flTransitionCutOff = m_TransitionTimer.GetCountdownDuration() / 2.f; + return GetTransitionProgress() < flTransitionCutOff; +}
\ No newline at end of file diff --git a/game/client/tf/vgui/item_ad_panel.h b/game/client/tf/vgui/item_ad_panel.h new file mode 100644 index 0000000..795d469 --- /dev/null +++ b/game/client/tf/vgui/item_ad_panel.h @@ -0,0 +1,113 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ITEM_AD_PANEL_H +#define ITEM_AD_PANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include <vgui/VGUI.h> +#include "vgui_controls/EditablePanel.h" + +using namespace vgui; +class CExButton; + +class CBaseAdPanel : public EditablePanel +{ + DECLARE_CLASS_SIMPLE( CBaseAdPanel, EditablePanel ); + +public: + CBaseAdPanel( Panel *parent, const char *panelName ); + virtual ~CBaseAdPanel() {} + + virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE; + + float GetPresentTime() const { return m_flPresentTime; } + + static bool CheckForRequiredSteamComponents( const char* pszSteamRequried, const char* pszOverlayRequired ); + +private: + + float m_flPresentTime; +}; + +class CItemAdPanel : public CBaseAdPanel +{ + DECLARE_CLASS_SIMPLE( CItemAdPanel, CBaseAdPanel ); +public: + CItemAdPanel( Panel *parent, const char *panelName, item_definition_index_t itemDefIndex ); + virtual ~CItemAdPanel() {} + + virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE; + virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE; + virtual void PerformLayout() OVERRIDE; + virtual void OnTick() OVERRIDE; + virtual void OnCommand( const char *command ) OVERRIDE; + +private: + + const CTFItemDefinition* GetItemDef() const; + bool m_bShowMarketButton; + + item_definition_index_t m_ItemDefIndex; +}; + +class CCyclingAdContainerPanel : public EditablePanel +{ + DECLARE_CLASS_SIMPLE( CCyclingAdContainerPanel, EditablePanel ); +public: + CCyclingAdContainerPanel( Panel *parent, const char *panelName ); + virtual ~CCyclingAdContainerPanel(); + + virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE; + virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE; + virtual void PerformLayout() OVERRIDE; + virtual void OnCommand( const char *command ) OVERRIDE; + virtual void OnThink() OVERRIDE; + + void SetItemKVs( KeyValues* pKVItems ); + +private: + + void CreatePanels(); + void PresentIndex( int nIndex ); + void UpdateAdPanelPositions(); + float GetTransitionProgress() const; + bool IsTransitioningOut() const; + + EditablePanel* m_pAdsContainer; + EditablePanel* m_pFadePanel; + CExButton* m_pPrevButton; + CExButton* m_pNextButton; + bool m_bNeedsToCreatePanels; + bool m_bSettingsApplied; + + struct AdData_t + { + ~AdData_t() + { + delete m_pAdPanel; + } + CBaseAdPanel* m_pAdPanel; + KeyValues* m_pSettingsKVs; + }; + + KeyValues *m_pKVItems; + + CUtlVector< AdData_t > m_vecPossibleAds; + int m_nTargetIndex; + int m_nCurrentIndex; + int m_nTransitionStartOffsetX; + bool m_bTransitionRight; + int m_nXPos; + + RealTimeCountdownTimer m_TransitionTimer; + RealTimeCountdownTimer m_ShowTimer; +}; + +#endif // ITEM_AD_PANEL_H diff --git a/game/client/tf/vgui/item_quickswitch.cpp b/game/client/tf/vgui/item_quickswitch.cpp new file mode 100644 index 0000000..e7e119f --- /dev/null +++ b/game/client/tf/vgui/item_quickswitch.cpp @@ -0,0 +1,792 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "vgui_controls/EditablePanel.h" +#include "tf_controls.h" +#include "tf_gamerules.h" +#include "tf_shareddefs.h" +#include "vgui/ISurface.h" +#include "c_tf_player.h" +#include "gamestringpool.h" +#include "iclientmode.h" +#include "tf_item_inventory.h" +#include "ienginevgui.h" +#include <vgui/ILocalize.h> +#include "vgui_controls/TextImage.h" +#include "vgui_controls/ComboBox.h" +#include "vgui/IInput.h" +#include "item_model_panel.h" +#include "hudelement.h" +#include "item_quickswitch.h" +#include "econ_gcmessages.h" +#include "gc_clientsystem.h" +#include "loadout_preset_panel.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +#define MAX_QUICKSWITCH_SLOTS 11 + +extern ConVar tf_respawn_on_loadoutchanges; + +extern const char *g_szEquipSlotHeader[CLASS_LOADOUT_POSITION_COUNT]; +int g_SlotsToLoadoutSlotsPerClass[TF_LAST_NORMAL_CLASS][MAX_QUICKSWITCH_SLOTS] = +{ + //TF_CLASS_UNDEFINED = 0, + { + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + }, + + // TF_CLASS_SCOUT, + { + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_PRIMARY, + LOADOUT_POSITION_SECONDARY, + LOADOUT_POSITION_MELEE, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_HEAD, + LOADOUT_POSITION_MISC, + LOADOUT_POSITION_ACTION, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + }, + + // TF_CLASS_SNIPER, + { + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_PRIMARY, + LOADOUT_POSITION_SECONDARY, + LOADOUT_POSITION_MELEE, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_HEAD, + LOADOUT_POSITION_MISC, + LOADOUT_POSITION_ACTION, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + }, + + // TF_CLASS_SOLDIER, + { + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_PRIMARY, + LOADOUT_POSITION_SECONDARY, + LOADOUT_POSITION_MELEE, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_HEAD, + LOADOUT_POSITION_MISC, + LOADOUT_POSITION_ACTION, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + }, + + // TF_CLASS_DEMOMAN, + { + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_PRIMARY, + LOADOUT_POSITION_SECONDARY, + LOADOUT_POSITION_MELEE, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_HEAD, + LOADOUT_POSITION_MISC, + LOADOUT_POSITION_ACTION, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + }, + + // TF_CLASS_MEDIC, + { + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_PRIMARY, + LOADOUT_POSITION_SECONDARY, + LOADOUT_POSITION_MELEE, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_HEAD, + LOADOUT_POSITION_MISC, + LOADOUT_POSITION_ACTION, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + }, + + // TF_CLASS_HEAVYWEAPONS, + { + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_PRIMARY, + LOADOUT_POSITION_SECONDARY, + LOADOUT_POSITION_MELEE, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_HEAD, + LOADOUT_POSITION_MISC, + LOADOUT_POSITION_ACTION, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + }, + + // TF_CLASS_PYRO, + { + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_PRIMARY, + LOADOUT_POSITION_SECONDARY, + LOADOUT_POSITION_MELEE, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_HEAD, + LOADOUT_POSITION_MISC, + LOADOUT_POSITION_ACTION, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + }, + +#ifdef STAGING_ONLY + // TF_CLASS_SPY, + { + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_SECONDARY, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_MELEE, + LOADOUT_POSITION_PDA, + LOADOUT_POSITION_PDA2, + LOADOUT_POSITION_PDA3, + LOADOUT_POSITION_HEAD, + LOADOUT_POSITION_MISC, + LOADOUT_POSITION_ACTION, + LOADOUT_POSITION_INVALID, + }, +#else + // TF_CLASS_SPY, + { + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_SECONDARY, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_MELEE, + LOADOUT_POSITION_PDA, + LOADOUT_POSITION_PDA2, + LOADOUT_POSITION_HEAD, + LOADOUT_POSITION_MISC, + LOADOUT_POSITION_ACTION, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + }, +#endif + + // TF_CLASS_ENGINEER, + { + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_PRIMARY, + LOADOUT_POSITION_SECONDARY, + LOADOUT_POSITION_MELEE, + LOADOUT_POSITION_PDA, + LOADOUT_POSITION_PDA2, + LOADOUT_POSITION_HEAD, + LOADOUT_POSITION_MISC, + LOADOUT_POSITION_ACTION, + LOADOUT_POSITION_INVALID, + LOADOUT_POSITION_INVALID, + }, +}; + +DECLARE_HUDELEMENT( CItemQuickSwitchPanel ); + +void IN_QuickSwitchDown( const CCommand &args ) +{ + // quickswitch disabled in training + if ( TFGameRules() && TFGameRules()->IsInTraining() ) + { + return; + } + + CItemQuickSwitchPanel *pQSPanel = GET_HUDELEMENT( CItemQuickSwitchPanel ); + if ( pQSPanel ) + { + pQSPanel->OpenQS(); + } +} + +void IN_QuickSwitchUp( const CCommand &args ) +{ + // quickswitch disabled in training + if ( TFGameRules() && TFGameRules()->IsInTraining() ) + { + return; + } + + CItemQuickSwitchPanel *pQSPanel = GET_HUDELEMENT( CItemQuickSwitchPanel ); + if ( pQSPanel ) + { + pQSPanel->CloseQS(); + } +} + +static ConCommand openquickswitch( "+quickswitch", IN_QuickSwitchDown ); +static ConCommand closequickswitch( "-quickswitch", IN_QuickSwitchUp ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CItemQuickSwitchPanel::CItemQuickSwitchPanel( const char *pElementName ) : CHudElement( pElementName ), BaseClass( NULL, "ItemQuickSwitchPanel" ) +{ + Panel *pParent = g_pClientMode->GetViewport(); + SetParent( pParent ); + + SetMouseInputEnabled( true ); + SetKeyBoardInputEnabled( false ); + + m_pItemContainer = vgui::SETUP_PANEL( new vgui::EditablePanel( this, "itemcontainer" ) ); + m_pItemContainerScroller = vgui::SETUP_PANEL( new vgui::ScrollableEditablePanel( this, m_pItemContainer, "itemcontainerscroller" ) ); + m_pItemKV = NULL; + m_pWeaponLabel = NULL; + m_pEquipYourClassLabel = NULL; + m_pLoadoutPresetPanel = NULL; + m_iClass = TF_CLASS_UNDEFINED; + m_iSlot = 0; + + SetVisible( false ); + + ListenForGameEvent( "inventory_updated" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CItemQuickSwitchPanel::~CItemQuickSwitchPanel() +{ + if ( m_pItemKV ) + { + m_pItemKV->deleteThis(); + m_pItemKV = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CItemQuickSwitchPanel::IsValid( void ) +{ + return ( m_iClass >= TF_FIRST_NORMAL_CLASS && m_iClass < TF_LAST_NORMAL_CLASS ) && + ( m_iSlot > LOADOUT_POSITION_INVALID && m_iSlot < CLASS_LOADOUT_POSITION_COUNT ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CItemQuickSwitchPanel::HudElementKeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding ) +{ + if ( !IsVisible() ) + return 1; // key not handled + + if ( !down ) + return 1; // key not handled + + int iSlot = 0; + + // convert slot1, slot2 etc to 1,2,3,4 + if ( pszCurrentBinding && ( !Q_strncmp( pszCurrentBinding, "slot", 4 ) && Q_strlen( pszCurrentBinding ) > 4 ) ) + { + const char *pszNum = pszCurrentBinding + 4; + iSlot = atoi( pszNum ); + + if ( ( iSlot < 1 ) || ( iSlot >= MAX_QUICKSWITCH_SLOTS ) ) + { + // invalid bind + iSlot = 0; + } + } + + if ( iSlot > 0 ) + { + int iLoadoutSlot = g_SlotsToLoadoutSlotsPerClass[m_iClass][iSlot]; + if ( iLoadoutSlot != LOADOUT_POSITION_INVALID ) + { + // is it the slot we're already viewing? + if ( iLoadoutSlot != m_iSlot ) + { + m_iSlot = iLoadoutSlot; + + if ( IsValid() ) + { + UpdateModelPanels(); + InvalidateLayout( true ); + } + } + } + else + { + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( pLocalPlayer ) + { + pLocalPlayer->EmitSound( "Player.DenyWeaponSelection" ); + } + } + + return 0; + } + + return 1; // key not handled +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CItemQuickSwitchPanel::CalculateClassAndSlot( void ) +{ + C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( !pPlayer ) + return false; + + // Get the current class + m_iClass = pPlayer->GetPlayerClass()->GetClassIndex(); + if ( m_iClass < TF_FIRST_NORMAL_CLASS || m_iClass >= TF_LAST_NORMAL_CLASS ) + return false; + + if ( m_pLoadoutPresetPanel ) + { + m_pLoadoutPresetPanel->SetClass( m_iClass ); + } + + if ( pPlayer->IsAlive() ) + { + // Get the current weapon slot + CTFWeaponBase *pWpn = pPlayer->GetActiveTFWeapon(); + if ( !pWpn ) + return false; + + m_iSlot = pWpn->GetAttributeContainer()->GetItem()->GetStaticData()->GetLoadoutSlot( m_iClass ); + if ( m_iSlot == LOADOUT_POSITION_INVALID ) + return false; + } + else + { + m_iClass = pPlayer->m_Shared.GetDesiredPlayerClassIndex(); + m_iSlot = g_SlotsToLoadoutSlotsPerClass[m_iClass][1]; // use the first slot if we're dead + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemQuickSwitchPanel::OpenQS( void ) +{ + if ( !CalculateClassAndSlot() ) + return; + + m_bLoadoutHasChanged = false; + + UpdateModelPanels(); + SetVisible( true ); + RequestFocus(); + MakePopup(); + SetKeyBoardInputEnabled( false ); + + // Force layout now so that we can position the cursor properly + InvalidateLayout( true ); + + // It takes a few frames before this panel appears the first time, which means + // we need to delay until it appears before we can pop the mouse to the right spot. + vgui::ivgui()->AddTickSignal( GetVPanel() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemQuickSwitchPanel::OnTick( void ) +{ + BaseClass::OnTick(); + + // Move the mouse cursor onto the first entry in the panel that's not our active weapon + if ( vgui::surface()->IsCursorVisible() ) + { + vgui::ivgui()->RemoveTickSignal( GetVPanel() ); + + int x,y,w,h; + if ( m_pItemPanels.Count() > 1 ) + { + vgui::ipanel()->GetAbsPos( m_pItemPanels[1]->GetVPanel(), x, y ); + m_pItemPanels[1]->GetSize( w, h ); + } + else + { + vgui::ipanel()->GetAbsPos( GetVPanel(), x, y ); + GetSize( w, h ); + } + ::input->SetFullscreenMousePos( x + (w * 0.5), y + (h * 0.5) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemQuickSwitchPanel::CloseQS( void ) +{ + vgui::ivgui()->RemoveTickSignal( GetVPanel() ); + SetVisible( false ); + + if ( m_bLoadoutHasChanged ) + { + if ( tf_respawn_on_loadoutchanges.GetBool() ) + { + // Tell the GC to tell server that we should respawn if we're in a respawn room + GCSDK::CGCMsg< GCSDK::MsgGCEmpty_t > msg( k_EMsgGCRespawnPostLoadoutChange ); + GCClientSystem()->BSendMessage( msg ); + } + + // Send the preset panel a msg so it can save the change + CEconItemView *pCurItemData = TFInventoryManager()->GetItemInLoadoutForClass( m_iClass, m_iSlot ); + if ( pCurItemData ) + { + KeyValues *pLoadoutChangedMsg = new KeyValues( "LoadoutChanged" ); + pLoadoutChangedMsg->SetInt( "slot", m_iSlot ); + pLoadoutChangedMsg->SetUint64( "itemid", pCurItemData->GetItemID() ); + PostMessage( m_pLoadoutPresetPanel, pLoadoutChangedMsg ); + } + + m_bLoadoutHasChanged = false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemQuickSwitchPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "Resource/UI/ItemQuickSwitch.res" ); + + m_pWeaponLabel = dynamic_cast<vgui::Label*>( FindChildByName("ItemSlotLabel") ); + m_pEquipYourClassLabel = dynamic_cast<vgui::Label*>( FindChildByName("EquipLabel") ); + m_pNoItemsToEquipLabel = dynamic_cast<vgui::Label*>( FindChildByName("NoItemsLabel") ); + m_pEquippedLabel = dynamic_cast<CExLabel*>( m_pItemContainer->FindChildByName("CurrentlyEquippedBackground") ); + m_pLoadoutPresetPanel = dynamic_cast<CLoadoutPresetPanel*>( FindChildByName( "loadout_preset_panel" ) ); + + if ( m_pEquippedLabel ) + { + m_pEquippedLabel->SetMouseInputEnabled( false ); + } + + if ( m_pLoadoutPresetPanel ) + { + m_pLoadoutPresetPanel->EnableVerticalDisplay( true ); + } + + m_pItemContainerScroller->GetScrollbar()->SetAutohideButtons( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemQuickSwitchPanel::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + KeyValues *pItemKV = inResourceData->FindKey( "itemskv" ); + if ( pItemKV ) + { + if ( m_pItemKV ) + { + m_pItemKV->deleteThis(); + } + m_pItemKV = new KeyValues( "itemkv" ); + pItemKV->CopySubkeys( m_pItemKV ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemQuickSwitchPanel::PerformLayout( void ) +{ + BaseClass::PerformLayout(); + + if ( !IsValid() ) + return; + + // Need to lay these out before we start making item panels inside them + m_pItemContainer->InvalidateLayout( true ); + m_pItemContainerScroller->InvalidateLayout( true ); + + // Position the item panels + for ( int i = 0; i < m_pItemPanels.Count(); i++ ) + { + if ( m_pItemKV ) + { + m_pItemPanels[i]->ApplySettings( m_pItemKV ); + m_pItemPanels[i]->InvalidateLayout(); + } + + int iYDelta = m_pItemPanels[0]->GetTall() + m_iItemPanelYDelta; + + // Once we've setup our first item, we know how large to make the container + if ( i == 0 ) + { + m_pItemContainer->SetSize( m_pItemContainer->GetWide(), iYDelta * m_pItemPanels.Count() ); + } + + // Always indent the top one to make it look better. + m_pItemPanels[i]->SetPos( m_iItemPanelXPos, m_iItemPanelYDelta + (iYDelta * i) ); + } + + // Now that the container has been sized, tell the scroller to re-evaluate + m_pItemContainerScroller->InvalidateLayout(); + m_pItemContainerScroller->GetScrollbar()->InvalidateLayout(); + + // Force the class label to layout & resize, so we can align our title + if ( m_pEquipYourClassLabel && m_pWeaponLabel ) + { + m_pWeaponLabel->InvalidateLayout( true ); + m_pWeaponLabel->SizeToContents(); + int iXPos, iYPos; + m_pWeaponLabel->GetPos( iXPos, iYPos ); + iXPos = ( GetWide() - m_pWeaponLabel->GetWide() ) * 0.5; + m_pWeaponLabel->SetPos( iXPos, m_pWeaponLabel->GetTall() ); + m_pEquipYourClassLabel->SetPos( iXPos, iYPos - m_pEquipYourClassLabel->GetTall() ); + } + + // If it's visible, put the no items to equip at the bottom + if ( m_pNoItemsToEquipLabel->IsVisible() ) + { + int iYDelta = 0; + if ( m_pItemPanels.Count() ) + { + iYDelta = m_pItemPanels[0]->GetTall() + m_iItemPanelYDelta; + } + m_pNoItemsToEquipLabel->SetPos( m_iItemPanelXPos, m_iItemPanelYDelta + (iYDelta * (m_pItemPanels.Count()+1)) ); + } + + UpdateEquippedItem(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemQuickSwitchPanel::UpdateEquippedItem( void ) +{ + if ( !m_pEquippedLabel ) + return; + + bool bEquipped = false; + + CEconItemView *pCurItemData = TFInventoryManager()->GetItemInLoadoutForClass( m_iClass, m_iSlot ); + if ( pCurItemData ) + { + if ( pCurItemData->IsValid() ) + { + for ( int i = 0; i < m_pItemPanels.Count(); i++ ) + { + CEconItemView *pItem = m_pItemPanels[i]->GetItem(); + if ( pItem && ( *pItem == *pCurItemData ) ) + { + int x,y; + m_pItemPanels[i]->GetPos( x, y ); + m_pEquippedLabel->SetPos( x + XRES(3), y + YRES(2) ); + + bEquipped = true; + } + } + } + else if ( !TFInventoryManager()->SlotContainsBaseItems( GEconItemSchema().GetEquipTypeFromClassIndex( m_iClass ), m_iSlot ) ) + { + for ( int i = 0; i < m_pItemPanels.Count(); i++ ) + { + CEconItemView *pItem = m_pItemPanels[i]->GetItem(); + if ( !pItem ) + { + int x,y; + m_pItemPanels[i]->GetPos( x, y ); + m_pEquippedLabel->SetPos( x + XRES(3), y + YRES(2) ); + + bEquipped = true; + } + } + } + } + + if ( m_pEquippedLabel->IsVisible() != bEquipped ) + { + m_pEquippedLabel->SetVisible( bEquipped ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemQuickSwitchPanel::UpdateModelPanels( void ) +{ + if ( !IsValid() ) + return; + + TFPlayerClassData_t *pData = GetPlayerClassData( m_iClass ); + SetDialogVariable( "loadoutclass", g_pVGuiLocalize->Find( pData->m_szLocalizableName ) ); + + if ( m_pWeaponLabel ) + { + m_pWeaponLabel->SetText( g_szEquipSlotHeader[m_iSlot] ); + } + + // What items can go in this slot? + extern equip_region_mask_t GenerateEquipRegionConflictMask( int iClass, int iUpToSlot, int iIgnoreSlot ); + const equip_region_mask_t unUsedEquipRegionMask = GenerateEquipRegionConflictMask( m_iClass, m_iSlot, LOADOUT_POSITION_INVALID ); + + CEquippableItemsForSlotGenerator equippableItems( m_iClass, m_iSlot, unUsedEquipRegionMask, CEquippableItemsForSlotGenerator::kSlotGenerator_None ); + + int iButton = 0; + FOR_EACH_VEC( equippableItems.GetDisplayItems(), i ) + { + // For quick-switch, only show items that are equippable and would show up as such in our regular + // loadout. + if ( equippableItems.GetDisplayItems()[i].m_eDisplayType == CEquippableItemsForSlotGenerator::kSlotDisplay_Normal ) + { + SetButtonToItem( iButton++, equippableItems.GetDisplayItems()[i].m_pEconItemView ); + } + } + + if ( !TFInventoryManager()->SlotContainsBaseItems( GEconItemSchema().GetEquipTypeFromClassIndex( m_iClass ), m_iSlot ) ) + { + SetButtonToItem( iButton++, NULL ); + } + + if ( m_pNoItemsToEquipLabel ) + { + m_pNoItemsToEquipLabel->SetVisible( iButton == 0 ); + } + + // Delete excess items + for ( int i = m_pItemPanels.Count() - 1; i >= iButton; i-- ) + { + m_pItemPanels[i]->MarkForDeletion(); + m_pItemPanels.Remove( i ); + } + + InvalidateLayout(); + + // Move the scrollbar to the top + m_pItemContainerScroller->GetScrollbar()->SetValue( 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemQuickSwitchPanel::SetButtonToItem( int iButton, CEconItemView *pItem ) +{ + CItemModelPanel *pItemPanel; + if ( iButton < m_pItemPanels.Count() ) + { + pItemPanel = m_pItemPanels[iButton]; + } + else + { + const char *pszCommand = VarArgs( "itempanel%d", iButton ); + pItemPanel = new CItemModelPanel( m_pItemContainer, pszCommand ); + if ( m_pItemKV ) + { + pItemPanel->ApplySettings( m_pItemKV ); + } + pItemPanel->MakeReadyForUse(); + pItemPanel->SetActAsButton( true, true ); + pItemPanel->SendPanelEnterExits( true ); + + vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); + pItemPanel->SetBorder( pScheme->GetBorder( "EconItemBorder" ) ); + + m_pItemPanels.AddToTail( pItemPanel ); + } + + pItemPanel->SetNoItemText( "#SelectNoItemSlot" ); + pItemPanel->SetItem( pItem ); + pItemPanel->AddActionSignalTarget( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemQuickSwitchPanel::OnItemPanelEntered( vgui::Panel *panel ) +{ + CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel ); + if ( pItemPanel ) + { + pItemPanel->SetPaintBorderEnabled( true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemQuickSwitchPanel::OnItemPanelExited( vgui::Panel *panel ) +{ + CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel ); + if ( pItemPanel ) + { + pItemPanel->SetPaintBorderEnabled( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemQuickSwitchPanel::OnIPMouseReleased( vgui::Panel *panel ) +{ + CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel ); + if ( !pItemPanel ) + return; + + itemid_t iIndex = INVALID_ITEM_ID; + + CEconItemView *pItemData = pItemPanel->GetItem(); + if ( pItemData && pItemData->IsValid() ) + { + iIndex = pItemData->GetItemID(); + } + + TFInventoryManager()->EquipItemInLoadout( m_iClass, m_iSlot, iIndex ); + + m_bLoadoutHasChanged = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemQuickSwitchPanel::FireGameEvent( IGameEvent *event ) +{ + if ( !IsVisible() ) + return; + + const char * type = event->GetName(); + + if ( Q_strcmp( type, "inventory_updated" ) == 0 ) + { + UpdateEquippedItem(); + } + else + { + CHudElement::FireGameEvent( event ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemQuickSwitchPanel::OnItemPresetLoaded() +{ + m_bLoadoutHasChanged = true; +} diff --git a/game/client/tf/vgui/item_quickswitch.h b/game/client/tf/vgui/item_quickswitch.h new file mode 100644 index 0000000..5cf8bd8 --- /dev/null +++ b/game/client/tf/vgui/item_quickswitch.h @@ -0,0 +1,72 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#ifndef ITEM_QUICKSWITCH_H +#define ITEM_QUICKSWITCH_H +#ifdef _WIN32 +#pragma once +#endif + +#include "vgui_controls/ScrollableEditablePanel.h" + +class CLoadoutPresetPanel; + +class CItemQuickSwitchPanel : public vgui::EditablePanel, public CHudElement +{ + DECLARE_CLASS_SIMPLE( CItemQuickSwitchPanel, vgui::EditablePanel ); +public: + CItemQuickSwitchPanel( const char *pElementName ); + virtual ~CItemQuickSwitchPanel(); + + void OpenQS( void ); + void CloseQS( void ); + bool ShouldDraw( void ) { return IsVisible(); } + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void ApplySettings( KeyValues *inResourceData ); + virtual void PerformLayout( void ); + virtual void OnTick( void ); + + void UpdateEquippedItem( void ); + bool CalculateClassAndSlot(); + void UpdateModelPanels( void ); + void SetButtonToItem( int iButton, CEconItemView *pItem ); + + bool IsValid( void ); + + virtual void FireGameEvent( IGameEvent *event ); + + int HudElementKeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding ); + + MESSAGE_FUNC( OnItemPresetLoaded, "ItemPresetLoaded" ); + + MESSAGE_FUNC_PTR( OnIPMouseReleased, "ItemPanelMouseReleased", panel ); + MESSAGE_FUNC_PTR( OnItemPanelEntered, "ItemPanelEntered", panel ); + MESSAGE_FUNC_PTR( OnItemPanelExited, "ItemPanelExited", panel ); + +private: + int m_iClass; // Class of the player we're selecting an item for + int m_iSlot; // Slot on the player that we're selecting an item for + bool m_bLoadoutHasChanged; + + vgui::EditablePanel *m_pItemContainer; + vgui::ScrollableEditablePanel *m_pItemContainerScroller; + vgui::Label *m_pWeaponLabel; + vgui::Label *m_pEquipYourClassLabel; + vgui::Label *m_pNoItemsToEquipLabel; + vgui::Label *m_pEquippedLabel; + + CLoadoutPresetPanel *m_pLoadoutPresetPanel; + + KeyValues *m_pItemKV; + CUtlVector<CItemModelPanel *> m_pItemPanels; + + CPanelAnimationVarAliasType( int, m_iItemPanelXPos, "itempanel_xpos", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iItemPanelYDelta, "itempanel_ydelta", "0", "proportional_int" ); +}; + +#endif // ITEM_QUICKSWITCH_H diff --git a/game/client/tf/vgui/item_slot_panel.cpp b/game/client/tf/vgui/item_slot_panel.cpp new file mode 100644 index 0000000..d09852f --- /dev/null +++ b/game/client/tf/vgui/item_slot_panel.cpp @@ -0,0 +1,301 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "item_slot_panel.h" +#include "tf_item_inventory.h" +#include "item_selection_panel.h" +#include "tf_gcmessages.h" +#include "gc_clientsystem.h" + +#define NUM_MAX_SLOTS 1 + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CItemSlotPanel::CItemSlotPanel( vgui::Panel *parent ) + : CBaseLoadoutPanel( parent, "item_slot_panel" ) +{ + m_pItem = NULL; + m_pSelectionPanel = NULL; + m_iCurrentSlotIndex = 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CItemSlotPanel::~CItemSlotPanel() +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemSlotPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + LoadControlSettings( "Resource/UI/ItemSlotPanel.res" ); + + BaseClass::ApplySchemeSettings( pScheme ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemSlotPanel::PerformLayout( void ) +{ + BaseClass::PerformLayout(); + + for ( int i = 0; i < m_pItemModelPanels.Count(); i++ ) + { + if ( !m_itemSlots[i].m_bHasSlot ) + { + m_pItemModelPanels[i]->SetVisible( false ); + continue; + } + + int iCenter = GetWide() * 0.5; + int iButtonX = (i % GetNumColumns()); + int iButtonY = (i / GetNumColumns()); + int iXPos = (iCenter + m_iItemBackpackOffcenterX) + (iButtonX * m_pItemModelPanels[i]->GetWide()) + (m_iItemBackpackXDelta * iButtonX); + int iYPos = m_iItemYPos + (iButtonY * m_pItemModelPanels[i]->GetTall() ) + (m_iItemBackpackYDelta * iButtonY); + + m_pItemModelPanels[i]->SetPos( iXPos, iYPos ); + m_pItemModelPanels[i]->SetVisible( true ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemSlotPanel::OnItemPanelMouseReleased( vgui::Panel *panel ) +{ + CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel ); + + if ( pItemPanel && IsVisible() ) + { + for ( int i = 0; i < m_pItemModelPanels.Count(); i++ ) + { + if ( m_pItemModelPanels[i] == pItemPanel ) + { + OnCommand( VarArgs("change%d", i) ); + return; + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemSlotPanel::OnSelectionReturned( KeyValues *data ) +{ + if ( data ) + { + uint64 ulIndex = data->GetUint64( "itemindex", INVALID_ITEM_ID ); + if ( ulIndex != INVALID_ITEM_ID ) + { + CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( ulIndex ); + if ( pItemData ) + { + m_pItemModelPanels[ m_iCurrentSlotIndex ]->SetItem( pItemData ); + + itemid_t ulOriginalID = pItemData->GetSOCData()->GetOriginalID(); + + m_itemSlots[ m_iCurrentSlotIndex ].m_ulOriginalID = ulOriginalID; + + // tell GC to update the slot attribute + GCSDK::CProtoBufMsg<CMsgSetItemSlotAttribute> msg( k_EMsgGC_ClientSetItemSlotAttribute ); + + msg.Body().set_item_id( m_pItem->GetItemID() ); + msg.Body().set_slot_item_original_id( ulOriginalID ); + msg.Body().set_slot_index( m_iCurrentSlotIndex + 1 ); + + //EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "applied_upgrade_card", m_pToolModelPanel->GetItem()->GetItemDefIndex() ); + + GCClientSystem()->BSendMessage( msg ); + } + } + } + + PostMessage( GetParent(), new KeyValues("SelectionEnded") ); + + // It'll have deleted itself, so we don't need to clean it up + m_pSelectionPanel = NULL; + OnCancelSelection(); + + // find the selected item and give it the focus + CItemModelPanel *pSelection = GetFirstSelectedItemModelPanel( true ); + if( !pSelection ) + { + m_pItemModelPanels[0]->SetSelected( true ); + pSelection = m_pItemModelPanels[0]; + } + + pSelection->RequestFocus(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemSlotPanel::OnCancelSelection( void ) +{ + if ( m_pSelectionPanel ) + { + m_pSelectionPanel->SetVisible( false ); + m_pSelectionPanel->MarkForDeletion(); + m_pSelectionPanel = NULL; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemSlotPanel::OnCommand( const char *command ) +{ + if ( !V_stricmp( command, "ok" ) ) + { + SetVisible( false ); + return; + } + else if ( V_stristr( command, "change" ) ) + { + const char *pszNum = command+6; + if ( pszNum && pszNum[0] ) + { + int iSlot = atoi(pszNum); + if ( iSlot >= 0 && iSlot < m_itemSlots.Count() ) + { + if ( m_iCurrentSlotIndex != iSlot ) + { + m_iCurrentSlotIndex = iSlot; + } + + m_selectionCriteria = CItemSelectionCriteria(); + m_selectionCriteria.SetTags( m_itemSlots[m_iCurrentSlotIndex].m_slotCriteriaAttribute.tags().c_str() ); + m_selectionCriteria.SetIgnoreEnabledFlag( true ); + + // Create the selection screen. It removes itself on close. + m_pSelectionPanel = new CItemCriteriaSelectionPanel( this, &m_selectionCriteria ); + m_pSelectionPanel->InvalidateLayout( false, true ); // need to ApplySchemeSettings now so it doesn't override our SetDialogVariable below later + m_pSelectionPanel->ShowPanel( 0, true ); + m_pSelectionPanel->SetDialogVariable( "loadoutclass", g_pVGuiLocalize->Find( "#EditSlots_SelectItemPanel" ) ); + + PostMessage( GetParent(), new KeyValues("SelectionStarted") ); + } + } + + return; + } + + BaseClass::OnCommand( command ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemSlotPanel::UpdateModelPanels( void ) +{ + // For now, fill them out with the local player's currently wielded items + for ( int i = 0; i < m_pItemModelPanels.Count(); i++ ) + { + CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByOriginalID( m_itemSlots[i].m_ulOriginalID ); + m_pItemModelPanels[i]->SetItem( pItemData ); + m_pItemModelPanels[i]->SetShowQuantity( true ); + m_pItemModelPanels[i]->SetSelected( false ); + SetBorderForItem( m_pItemModelPanels[i], false ); + } + + // Now layout again to position our item buttons + InvalidateLayout(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CItemSlotPanel::GetNumItemPanels( void ) +{ + return NUM_MAX_SLOTS; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemSlotPanel::OnShowPanel( bool bVisible, bool bReturningFromArmory ) +{ + if ( bVisible ) + { + if ( m_pSelectionPanel ) + { + m_pSelectionPanel->SetVisible( false ); + m_pSelectionPanel->MarkForDeletion(); + m_pSelectionPanel = NULL; + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemSlotPanel::AddNewItemPanel( int iPanelIndex ) +{ + BaseClass::AddNewItemPanel( iPanelIndex ); + + m_itemSlots.AddToTail(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemSlotPanel::SetItem( CEconItem* pItem ) +{ + if ( !pItem ) + { + SetVisible( false ); + return; + } + + OnCancelSelection(); + + m_pItem = pItem; + + static CSchemaAttributeDefHandle s_itemSlotCriteriaAttributes[] = + { + CSchemaAttributeDefHandle( "item slot criteria 1" ), + }; + COMPILE_TIME_ASSERT( ARRAYSIZE( s_itemSlotCriteriaAttributes ) == NUM_MAX_SLOTS ); + + static CSchemaAttributeDefHandle s_itemInSlotAttributes[] = + { + CSchemaAttributeDefHandle( "item in slot 1" ), + }; + COMPILE_TIME_ASSERT( ARRAYSIZE( s_itemInSlotAttributes ) == NUM_MAX_SLOTS ); + + for ( int i=0; i<ARRAYSIZE( s_itemSlotCriteriaAttributes ); ++i ) + { + m_itemSlots[i].m_bHasSlot = false; + if ( m_pItem->FindAttribute( s_itemSlotCriteriaAttributes[i], &m_itemSlots[i].m_slotCriteriaAttribute ) ) + { + m_itemSlots[i].m_bHasSlot = true; + m_itemSlots[i].m_ulOriginalID = INVALID_ITEM_ID; + m_pItem->FindAttribute( s_itemInSlotAttributes[i], &m_itemSlots[i].m_ulOriginalID ); + } + } + + UpdateModelPanels(); +} diff --git a/game/client/tf/vgui/item_slot_panel.h b/game/client/tf/vgui/item_slot_panel.h new file mode 100644 index 0000000..357ce87 --- /dev/null +++ b/game/client/tf/vgui/item_slot_panel.h @@ -0,0 +1,54 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#ifndef ITEM_SLOT_PANEL_H +#define ITEM_SLOT_PANEL_H +#include "base_loadout_panel.h" + +class CItemCriteriaSelectionPanel; + +//----------------------------------------------------------------------------- +// A loadout screen that handles modifying the loadout of a specific item +//----------------------------------------------------------------------------- +class CItemSlotPanel : public CBaseLoadoutPanel +{ + DECLARE_CLASS_SIMPLE( CItemSlotPanel, CBaseLoadoutPanel ); +public: + CItemSlotPanel( vgui::Panel *parent ); + ~CItemSlotPanel(); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void PerformLayout( void ); + + virtual void AddNewItemPanel( int iPanelIndex ) OVERRIDE; + virtual void UpdateModelPanels( void ) OVERRIDE; + virtual int GetNumItemPanels( void ) OVERRIDE; + virtual void OnShowPanel( bool bVisible, bool bReturningFromArmory ) OVERRIDE; + + MESSAGE_FUNC_PTR( OnItemPanelMouseReleased, "ItemPanelMouseReleased", panel ); + MESSAGE_FUNC_PARAMS( OnSelectionReturned, "SelectionReturned", data ); + MESSAGE_FUNC( OnCancelSelection, "CancelSelection" ); + virtual void OnCommand( const char *command ); + + void SetItem( CEconItem* pItem ); + +private: + CEconItem *m_pItem; + + struct ItemSlot_t + { + CAttribute_ItemSlotCriteria m_slotCriteriaAttribute; + itemid_t m_ulOriginalID; + bool m_bHasSlot; + }; + CUtlVector< ItemSlot_t > m_itemSlots; + + int m_iCurrentSlotIndex; + CItemSelectionCriteria m_selectionCriteria; + CItemCriteriaSelectionPanel *m_pSelectionPanel; +}; + +#endif // ITEM_SLOT_PANEL_H diff --git a/game/client/tf/vgui/loadout_preset_panel.cpp b/game/client/tf/vgui/loadout_preset_panel.cpp new file mode 100644 index 0000000..b9998ce --- /dev/null +++ b/game/client/tf/vgui/loadout_preset_panel.cpp @@ -0,0 +1,250 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +//=======================================================================================// + +#include "cbase.h" +#include "loadout_preset_panel.h" +#include "tf_item_inventory.h" +#include "econ/econ_item_preset.h" +#include "econ/econ_item_system.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +//----------------------------------------------------------------------------- + +using namespace vgui; + +//----------------------------------------------------------------------------- + +DECLARE_BUILD_FACTORY( CLoadoutPresetPanel ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CLoadoutPresetPanel::CLoadoutPresetPanel( vgui::Panel *pParent, const char *pName ) +: EditablePanel( pParent, "loadout_preset_panel" ) +{ + V_memset( m_pPresetButtons, 0, sizeof( m_pPresetButtons ) ); + + m_iClass = TF_CLASS_UNDEFINED; + m_pPresetButtonKv = NULL; + m_bDisplayVertical = false; + + // Create all buttons + for ( int i = 0; i < MAX_PRESETS; ++i ) + { + CFmtStr fmtTokenName( "TF_ItemPresetName%i", i ); + CFmtStr fmtButtonName( "LoadPresetButton%i", i ); + wchar_t *pwszPresetName = g_pVGuiLocalize->Find( fmtTokenName.Access() ); + m_pPresetButtons[i] = new CExButton( this, fmtButtonName.Access(), pwszPresetName, this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLoadoutPresetPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "Resource/UI/LoadoutPresetPanel.res" ); + + m_aDefaultColors[LOADED][FG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDefaultColorFg", Color( 255, 255, 255, 255 ) ); + m_aDefaultColors[LOADED][FG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetArmedColorFg", Color( 255, 255, 255, 255 ) ); + m_aDefaultColors[LOADED][FG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDepressedColorFg", Color( 255, 255, 255, 255 ) ); + + m_aDefaultColors[LOADED][BG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDefaultColorBg", Color( 255, 255, 255, 255 ) ); + m_aDefaultColors[LOADED][BG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetArmedColorBg", Color( 255, 255, 255, 255 ) ); + m_aDefaultColors[LOADED][BG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDepressedColorBg", Color( 255, 255, 255, 255 ) ); + + m_aDefaultColors[NOTLOADED][FG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.TextColor", Color( 255, 255, 255, 255 ) ); + m_aDefaultColors[NOTLOADED][FG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.ArmedTextColor", Color( 255, 255, 255, 255 ) ); + m_aDefaultColors[NOTLOADED][FG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.DepressedTextColor", Color( 255, 255, 255, 255 ) ); + + m_aDefaultColors[NOTLOADED][BG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.BgColor", Color( 255, 255, 255, 255 ) ); + m_aDefaultColors[NOTLOADED][BG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.ArmedBgColor", Color( 255, 255, 255, 255 ) ); + m_aDefaultColors[NOTLOADED][BG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.DepressedBgColor", Color( 255, 255, 255, 255 ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLoadoutPresetPanel::ApplySettings( KeyValues *pInResourceData ) +{ + BaseClass::ApplySettings( pInResourceData ); + + KeyValues *pPresetButtonKv = pInResourceData->FindKey( "presetbutton_kv" ); + if ( pPresetButtonKv && !m_pPresetButtonKv ) + { + m_pPresetButtonKv = new KeyValues( "presetbutton_kv" ); + pPresetButtonKv->CopySubkeys( m_pPresetButtonKv ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLoadoutPresetPanel::PerformLayout() +{ + BaseClass::PerformLayout(); + + if ( !m_pPresetButtons[0] ) + return; + + const int nBuffer = XRES( 2 ); + + for ( int i = 0; i < MAX_PRESETS; ++i ) + { + if ( m_pPresetButtonKv ) + { + m_pPresetButtons[i]->ApplySettings( m_pPresetButtonKv ); + } + + // Display buttons vertically or horizontally? + // NOTE: Button width and height will be valid here, since we've just applied settings + if ( m_bDisplayVertical ) + { + const int nButtonHeight = m_pPresetButtons[0]->GetTall(); + m_pPresetButtons[i]->SetPos( 0, i * ( nButtonHeight + nBuffer ) ); + } + else + { + const int nButtonWidth = m_pPresetButtons[0]->GetWide(); + const int nStartX = 0.5f * ( GetWide() - MAX_PRESETS * ( nButtonWidth + nBuffer ) ); + m_pPresetButtons[i]->SetPos( nStartX + i * ( nButtonWidth + nBuffer ), 0 ); + } + m_pPresetButtons[i]->SetVisible( true ); + } + + vgui::ivgui()->AddTickSignal( GetVPanel(), 150 ); + + UpdatePresetButtonStates(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLoadoutPresetPanel::SetClass( int iClass ) +{ + m_iClass = iClass; + + if ( iClass != TF_CLASS_UNDEFINED ) + { + UpdatePresetButtonStates(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLoadoutPresetPanel::EnableVerticalDisplay( bool bVertical ) +{ + m_bDisplayVertical = bVertical; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLoadoutPresetPanel::LoadPreset( int iPresetIndex ) +{ + TFInventoryManager()->LoadPreset( m_iClass, iPresetIndex ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLoadoutPresetPanel::OnCommand( const char *command ) +{ + if ( !V_strnicmp( command, "loadpreset_", 11 ) ) + { + const int iPresetIndex = atoi( command + 11 ); + LoadPreset( iPresetIndex ); + } + else + { + BaseClass::OnCommand( command ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLoadoutPresetPanel::OnTick() +{ + UpdatePresetButtonStates(); +} + +//----------------------------------------------------------------------------- +// Purpose: Processes some keypresses for the loadout panel +//----------------------------------------------------------------------------- +bool CLoadoutPresetPanel::HandlePresetKeyPressed( vgui::KeyCode code ) +{ + ButtonCode_t nButtonCode = GetBaseButtonCode( code ); + + if( nButtonCode == KEY_XBUTTON_LEFT_SHOULDER ) + { + if( GetSelectedPresetID() > 0 ) + LoadPreset( GetSelectedPresetID() - 1 ); + return true; + } + else if( nButtonCode == KEY_XBUTTON_RIGHT_SHOULDER ) + { + if( GetSelectedPresetID() < MAX_PRESETS - 1 ) + LoadPreset( GetSelectedPresetID() + 1 ); + return true; + } + else + { + return false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +equipped_preset_t CLoadoutPresetPanel::GetSelectedPresetID() const +{ + if ( !InventoryManager()->GetLocalInventory() ) + return INVALID_PRESET_INDEX; + + const uint32 unAccountID = InventoryManager()->GetLocalInventory()->GetOwner().GetAccountID(); + const CEconItemPerClassPresetData soSearch( unAccountID, m_iClass ); + + GCSDK::CSharedObjectCache *pSOCache = InventoryManager()->GetLocalInventory()->GetSOC(); + if ( !pSOCache ) + return INVALID_PRESET_INDEX; + + const CEconItemPerClassPresetData *pExistingPerClassData = assert_cast<CEconItemPerClassPresetData *>( pSOCache->FindSharedObject( soSearch ) ); + if ( !pExistingPerClassData ) + return INVALID_PRESET_INDEX; + + return pExistingPerClassData->GetActivePreset(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLoadoutPresetPanel::UpdatePresetButtonStates() +{ + const equipped_preset_t unEquippedPresetID = GetSelectedPresetID(); + + for ( int i = 0; i < MAX_PRESETS; ++i ) + { + if ( i == unEquippedPresetID ) + { + m_pPresetButtons[i]->SetDefaultColor( m_aDefaultColors[LOADED][FG][DEFAULT], m_aDefaultColors[LOADED][BG][DEFAULT] ); + m_pPresetButtons[i]->SetArmedColor( m_aDefaultColors[LOADED][FG][ARMED], m_aDefaultColors[LOADED][BG][ARMED] ); + m_pPresetButtons[i]->SetDepressedColor( m_aDefaultColors[LOADED][FG][DEPRESSED], m_aDefaultColors[LOADED][BG][DEPRESSED] ); + } + else + { + m_pPresetButtons[i]->SetDefaultColor( m_aDefaultColors[NOTLOADED][FG][DEFAULT], m_aDefaultColors[NOTLOADED][BG][DEFAULT] ); + m_pPresetButtons[i]->SetArmedColor( m_aDefaultColors[NOTLOADED][FG][ARMED], m_aDefaultColors[NOTLOADED][BG][ARMED] ); + m_pPresetButtons[i]->SetDepressedColor( m_aDefaultColors[NOTLOADED][FG][DEPRESSED], m_aDefaultColors[NOTLOADED][BG][DEPRESSED] ); + } + + CFmtStr fmtCmd( "loadpreset_%i", i ); + m_pPresetButtons[i]->SetCommand( fmtCmd.Access() ); + } +} diff --git a/game/client/tf/vgui/loadout_preset_panel.h b/game/client/tf/vgui/loadout_preset_panel.h new file mode 100644 index 0000000..7004832 --- /dev/null +++ b/game/client/tf/vgui/loadout_preset_panel.h @@ -0,0 +1,64 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +//=======================================================================================// + +#ifndef LOADOUT_PRESET_PANEL_H +#define LOADOUT_PRESET_PANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "vgui_controls/EditablePanel.h" +#include "vgui_controls/PHandle.h" +#include "class_loadout_panel.h" + +class CExButton; +class CSelectedItemPreset; + +//----------------------------------------------------------------------------- +// A loadout preset panel, which allows combinations of items to be saved and +// restored via the GC. +//----------------------------------------------------------------------------- +class CLoadoutPresetPanel : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CLoadoutPresetPanel, vgui::EditablePanel ); +public: + CLoadoutPresetPanel( vgui::Panel *pParent, const char *pName ); // name is ignored but needed for DECLARE_BUILD_FACTORY() + + void SetClass( int iClass ); + void EnableVerticalDisplay( bool bVertical ); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void ApplySettings( KeyValues *pInResourceData ); + virtual void PerformLayout(); + virtual void OnCommand( const char *command ); + virtual void OnTick() OVERRIDE; + + bool HandlePresetKeyPressed( vgui::KeyCode code ); + +private: + equipped_preset_t GetSelectedPresetID() const; + void UpdatePresetButtonStates(); + void LoadPreset( int iPresetIndex ); + + enum PresetsConsts_t + { + MAX_PRESETS = 4, + }; + + int m_iClass; + KeyValues *m_pPresetButtonKv; + CExButton *m_pPresetButtons[ MAX_PRESETS ]; + bool m_bDisplayVertical; + + enum PresetButtonColors_t + { + LOADED = 0, NOTLOADED, + FG = 0, BG, + DEFAULT = 0, ARMED, DEPRESSED + }; + Color m_aDefaultColors[2][2][3]; // [LOADED|NOTLOADED][FG|BG][DEFAULT|ARMED|DEPRESSED] +}; + + +#endif // LOADOUT_PRESET_PANEL_H diff --git a/game/client/tf/vgui/modelimagepanel.cpp b/game/client/tf/vgui/modelimagepanel.cpp new file mode 100644 index 0000000..e322282 --- /dev/null +++ b/game/client/tf/vgui/modelimagepanel.cpp @@ -0,0 +1,214 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "cbase.h" +#include "modelimagepanel.h" +#include "iconrenderreceiver.h" +#include "materialsystem/imaterialvar.h" +#include "VGuiMatSurface/IMatSystemSurface.h" +#include "renderparm.h" + + +using namespace vgui; + +const char *g_pszModelImagePanelRTName = "_rt_ModelImagePanel"; +static vgui::DHANDLE<CModelImagePanel> s_hModelImageLockPanel; + +#ifdef STAGING_ONLY +ConVar tf_modelimagepanel_ignore_cache( "tf_modelimagepanel_ignore_cache", "0" ); +#endif + +DECLARE_BUILD_FACTORY( CModelImagePanel ); + +CModelImagePanel::CModelImagePanel( vgui::Panel *pParent, const char *pName ) + : BaseClass( pParent, pName ) +{ + m_pCachedIcon = NULL; + m_pCachedMaterial = NULL; + m_iCachedTextureID = -1; +} + +CModelImagePanel::~CModelImagePanel() +{ + InvalidateImage(); +} + +void CModelImagePanel::PerformLayout() +{ + BaseClass::PerformLayout(); + + InvalidateImage(); +} + +void CModelImagePanel::OnSizeChanged( int wide, int tall ) +{ + BaseClass::OnSizeChanged( wide, tall ); + + InvalidateImage(); +} + +void CModelImagePanel::Paint() +{ + // don't do anything for invalid model + if ( m_RootMDL.m_MDL.GetMDL() == MDLHANDLE_INVALID ) + { + return; + } + + // check lock panel + if ( s_hModelImageLockPanel ) + { + // waiting for async copy to finish + if ( s_hModelImageLockPanel->m_pCachedIcon && s_hModelImageLockPanel->m_pCachedIcon->GetTexture() ) + { + s_hModelImageLockPanel = NULL; + } + } + + if ( m_pCachedIcon ) + { + if ( m_pCachedIcon->GetTexture() ) + { + if ( !m_pCachedMaterial && g_pMaterialSystem ) + { + const char *pszTextureName = m_pCachedIcon->GetTexture()->GetName(); + KeyValues *pVMTKeyValues = new KeyValues( "UnlitGeneric" ); + pVMTKeyValues->SetString( "$basetexture", pszTextureName ); + pVMTKeyValues->SetInt( "$translucent", 1 ); + pVMTKeyValues->SetInt( "$vertexcolor", 1 ); + IMaterial *pMaterial = g_pMaterialSystem->FindProceduralMaterial( pszTextureName, TEXTURE_GROUP_VGUI, pVMTKeyValues ); + SafeAssign( &m_pCachedMaterial, pMaterial ); + + bool bFound = false; + IMaterialVar *pVar = m_pCachedMaterial->FindVar( "$basetexture", &bFound ); + if ( bFound && pVar ) + { + pVar->SetTextureValue( m_pCachedIcon->GetTexture() ); + m_pCachedMaterial->RefreshPreservingMaterialVars(); + } + } + + if ( m_iCachedTextureID == -1 ) + { + m_iCachedTextureID = g_pMatSystemSurface->DrawGetTextureId( m_pCachedIcon->GetTexture() ); + g_pMatSystemSurface->DrawSetTextureMaterial( m_iCachedTextureID, m_pCachedMaterial ); + } + } + else + { + // still waiting for texture + BaseClass::Paint(); + return; + } + } + + // just draw the texture if we got one. + if ( m_iCachedTextureID != -1 ) + { + surface()->DrawSetTexture( m_iCachedTextureID ); + surface()->DrawSetColor( 255, 255, 255, 255 ); + const int iWidth = GetWide(); + const int iHeight = GetTall(); + const int iMappingWitdh = m_pCachedMaterial->GetMappingWidth(); + const int iMappingHeight = m_pCachedMaterial->GetMappingHeight(); + float flTexW, flTexH; + if ( iWidth > iMappingWitdh || iHeight > iMappingHeight ) + { + float flScale = iWidth > iHeight ? (float)iMappingWitdh / iWidth : (float)iMappingHeight / iHeight; + flTexW = ( flScale * iWidth ) / iMappingWitdh; + flTexH = ( flScale * iHeight ) / iMappingHeight; + } + else + { + flTexW = (float)( iWidth - 1 ) / iMappingWitdh; + flTexH = (float)( iHeight - 1 ) / iMappingHeight; + } + surface()->DrawTexturedSubRect( 0, 0, iWidth, iHeight, 0.f, 0.f, flTexW, flTexH ); + return; + } + + // can't find available cache render target, don't do anything + if ( s_hModelImageLockPanel != NULL && s_hModelImageLockPanel != this ) + { + BaseClass::Paint(); + return; + } + + CMatRenderContextPtr pRenderContext( materials ); + + // Turn off depth-write to dest alpha so that we get white there instead. The code that uses + // the render target needs a mask of where stuff was rendered. + pRenderContext->SetIntRenderingParameter( INT_RENDERPARM_WRITE_DEPTH_TO_DESTALPHA, false ); + + g_pMatSystemSurface->Set3DPaintTempRenderTarget( g_pszModelImagePanelRTName ); + + BaseClass::Paint(); + + // copy the rendered weapon skin from the render target + Assert( m_pCachedIcon == NULL ); + CStudioHdr studioHdr( g_pMDLCache->GetStudioHdr( m_RootMDL.m_MDL.GetMDL() ), g_pMDLCache ); + char buffer[_MAX_PATH]; + CUtlString strMDLName = V_GetFileName( studioHdr.pszName() ); + V_sprintf_safe( buffer, "proc/icon/mdl_%s_body%d_skin%d_w%d_h%d", strMDLName.StripExtension().Get(), m_RootMDL.m_MDL.m_nBody, m_RootMDL.m_MDL.m_nSkin, GetWide(), GetTall() ); + SafeAssign( &m_pCachedIcon, new CIconRenderReceiver() ); + + // If the icon still exists in the material system, don't bother regenerating it. + if ( materials->IsTextureLoaded( buffer ) +#ifdef STAGING_ONLY + && !tf_modelimagepanel_ignore_cache.GetBool() +#endif + ) + { + ITexture* resTexture = materials->FindTexture( buffer, TEXTURE_GROUP_RUNTIME_COMPOSITE, false, 0 ); + if ( resTexture && resTexture->IsError() == false ) + { + m_pCachedIcon->OnAsyncCreateComplete( resTexture, NULL ); + } + } + else + { + // No icon available yet, need to create it. + ITexture *pRenderTarget = g_pMaterialSystem->FindTexture( g_pszModelImagePanelRTName, TEXTURE_GROUP_RENDER_TARGET ); + if ( pRenderTarget ) + { + pRenderContext->AsyncCreateTextureFromRenderTarget( pRenderTarget, buffer, IMAGE_FORMAT_RGBA8888, false, TEXTUREFLAGS_IMMEDIATE_CLEANUP, m_pCachedIcon, NULL ); + + // make this panel lock the render target + s_hModelImageLockPanel = this; + } + } + + g_pMatSystemSurface->Reset3DPaintTempRenderTarget(); +} + +void CModelImagePanel::SetMDL( MDLHandle_t handle, void *pProxyData /*= NULL*/ ) +{ + BaseClass::SetMDL( handle, pProxyData ); + InvalidateImage(); +} + +void CModelImagePanel::SetMDL( const char *pMDLName, void *pProxyData /*= NULL*/ ) +{ + BaseClass::SetMDL( pMDLName, pProxyData ); +} + +void CModelImagePanel::SetMDLBody( unsigned int nBody ) +{ + SetBody( nBody ); + InvalidateImage(); +} + +void CModelImagePanel::SetMDLSkin( int nSkin ) +{ + SetSkin( nSkin ); + InvalidateImage(); +} + +void CModelImagePanel::InvalidateImage() +{ + SafeRelease( &m_pCachedIcon ); + SafeRelease( &m_pCachedMaterial ); + m_iCachedTextureID = -1; +} diff --git a/game/client/tf/vgui/modelimagepanel.h b/game/client/tf/vgui/modelimagepanel.h new file mode 100644 index 0000000..4c2ede0 --- /dev/null +++ b/game/client/tf/vgui/modelimagepanel.h @@ -0,0 +1,43 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#ifndef MODELIMAGEPANEL_H +#define MODELIMAGEPANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "basemodel_panel.h" + +class CIconRenderReceiver; + +class CModelImagePanel : public CBaseModelPanel +{ +DECLARE_CLASS_SIMPLE( CModelImagePanel, CBaseModelPanel ); + +public: + + // Constructor, Destructor. + CModelImagePanel( vgui::Panel *pParent, const char *pName ); + virtual ~CModelImagePanel(); + + virtual void PerformLayout() OVERRIDE; + virtual void Paint() OVERRIDE; + virtual void OnSizeChanged( int wide, int tall ) OVERRIDE; + + virtual void SetMDL( MDLHandle_t handle, void *pProxyData = NULL ) OVERRIDE; + virtual void SetMDL( const char *pMDLName, void *pProxyData = NULL ) OVERRIDE; + void SetMDLBody( unsigned int nBody ); + void SetMDLSkin( int nSkin ); + + void InvalidateImage(); + +private: + CIconRenderReceiver *m_pCachedIcon; + IMaterial *m_pCachedMaterial; + int m_iCachedTextureID; +}; + +#endif // MODELIMAGEPANEL_H diff --git a/game/client/tf/vgui/quest_item_panel.cpp b/game/client/tf/vgui/quest_item_panel.cpp new file mode 100644 index 0000000..cfa05fe --- /dev/null +++ b/game/client/tf/vgui/quest_item_panel.cpp @@ -0,0 +1,1625 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "quest_item_panel.h" +#include "tf_hud_item_progress_tracker.h" +#include "quest_log_panel.h" +#include "c_tf_player.h" +#include "econ_item_description.h" +#include "clientmode_tf.h" +#include <vgui_controls/AnimationController.h> +#include "quest_objective_manager.h" +#include "econ_quests.h" +#include "confirm_dialog.h" +#include "tf_quest_restriction.h" +#include "item_model_panel.h" +#include "tf_gc_client.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +void AddSubKeyNamed( KeyValues *pKeys, const char *pszName ); + +const float k_flQuestDecodeTime = 2.f; +const float k_flQuestTurnInTime = 5.f; +ConVar tf_quest_turn_in_confirm_opt_out( "tf_quest_turn_in_confirm_opt_out", "0", FCVAR_ARCHIVE, "If nonzero, don't confirm submitting a contract that does not have all of the bonus points" ); + +extern CQuestLogPanel *GetQuestLog(); +extern CQuestTooltip* g_spTextTooltip; + +extern const char *s_pszMMTypes[kMatchmakingTypeCount]; +extern const char *s_pszGameModes[eNumGameCategories]; + +void SelectGroup( EMatchmakingGroupType eGroup, bool bSelected ); +void SelectCategory( EGameCategory eCategory, bool bSelected ); + +void PromptOrFireCommand( const char* pszCommand ); + +static void ConfirmDiscardQuest( bool bConfirmed, void* pContext ) +{ + CQuestItemPanel *pQuestItemPanel = ( CQuestItemPanel* )pContext; + if ( pQuestItemPanel ) + { + pQuestItemPanel->OnConfirmDelete( bConfirmed ); + } +} + +static void ConfirmEquipLoaners( bool bConfirmed, void* pContext ) +{ + CQuestItemPanel *pQuestItemPanel = ( CQuestItemPanel* )pContext; + if ( pQuestItemPanel ) + { + pQuestItemPanel->OnConfirmEquipLoaners( bConfirmed ); + } +} + +static void ConfirmTurnInQuest( bool bConfirmed, void* pContext ) +{ + if ( bConfirmed ) + { + CQuestItemPanel *pQuestItemPanel = (CQuestItemPanel*)pContext; + if ( pQuestItemPanel ) + { + pQuestItemPanel->OnCompleteQuest(); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: fill vecLoanerItems with loaners def indices from pQuest +//----------------------------------------------------------------------------- +static int GetLoanerListFromQuest( const CEconItemView *pQuest, CUtlVector< item_definition_index_t >& vecLoanerItems ) +{ + if ( !pQuest ) + return 0; + + // loaners from the quest + const CUtlVector< CTFRequiredQuestItemsSet >& vecQuestRequiredItems = pQuest->GetItemDefinition()->GetQuestDef()->GetRequiredItemSets(); + FOR_EACH_VEC( vecQuestRequiredItems, i ) + { + // don't add dups + if ( vecLoanerItems.Find( vecQuestRequiredItems[i].GetLoanerItemDef() ) == vecLoanerItems.InvalidIndex() ) + { + vecLoanerItems.AddToTail( vecQuestRequiredItems[i].GetLoanerItemDef() ); + } + } + + // loaners from the objectives + //{ + // // Get all the objectives + // QuestObjectiveDefVec_t vecChosenObjectives; + // pQuest->GetItemDefinition()->GetQuestDef()->GetRolledObjectivesForItem( vecChosenObjectives, pQuest ); + + // // Get all the items we need to give as loaners from the objectives + // FOR_EACH_VEC( vecChosenObjectives, i ) + // { + // const CUtlVector< CTFRequiredQuestItemsSet >& vecObjectiveRequiredItems = vecChosenObjectives[ i ]->GetConditions()->GetRequiredItemSets(); + // FOR_EACH_VEC( vecObjectiveRequiredItems, iRequired ) + // { + // // don't add dups + // if ( vecLoanerItems.Find( vecObjectiveRequiredItems[ iRequired ].GetLoanerItemDef() ) == vecLoanerItems.InvalidIndex() ) + // { + // vecLoanerItems.AddToTail( vecObjectiveRequiredItems[ iRequired ].GetLoanerItemDef() ); + // } + // } + // } + //} + + return vecLoanerItems.Count(); +} + + +//----------------------------------------------------------------------------- +// Purpose: fill vecGrantedLoaners with granted loaners from specific quest ID +//----------------------------------------------------------------------------- +static int GetLoanersFromLocalInventory( const itemid_t& questID, const CUtlVector< item_definition_index_t >& vecLoanerItems, CUtlVector< CEconItemView* >& vecGrantedLoaners ) +{ + if ( vecLoanerItems.Count() > 0 ) + { + CPlayerInventory *pLocalInv = InventoryManager()->GetLocalInventory(); + int nCount = pLocalInv->GetItemCount(); + for ( int i = 0; i < nCount; ++i ) + { + CEconItemView* pItem = pLocalInv->GetItem( i ); + + bool bIsLoaner = false; + // check if the item is a loaner and is associated with this quest + FOR_EACH_VEC( vecLoanerItems, iLoaner ) + { + if ( vecLoanerItems[iLoaner] == pItem->GetItemDefIndex() && GetAssociatedQuestItemID( pItem ) == questID ) + { + bIsLoaner = true; + break; + } + } + + // already granted this loaner, remove from the list to give + if ( bIsLoaner ) + { + vecGrantedLoaners.AddToTail( pItem ); + } + + // found all given loaners + if ( vecLoanerItems.Count() == vecGrantedLoaners.Count() ) + { + break; + } + } + } + + return vecGrantedLoaners.Count(); +} + + +DECLARE_BUILD_FACTORY( CInputProxyPanel ) +CInputProxyPanel::CInputProxyPanel( Panel *parent, const char *pszPanelName ) + : BaseClass( parent, pszPanelName ) +{} + +void CInputProxyPanel::AddPanelForCommand( EInputTypes eInputType, Panel* pPanel, const char* pszCommand ) +{ + m_vecRedirectPanels[ eInputType ].AddToTail( { pPanel, pszCommand } ); +} + +void CInputProxyPanel::OnCursorMoved( int x, int y ) +{ + FOR_EACH_VEC( m_vecRedirectPanels[ INPUT_MOUSE_MOVE ], i ) + { + PostMessage( m_vecRedirectPanels[ INPUT_MOUSE_MOVE ][ i ].m_pPanel, new KeyValues( m_vecRedirectPanels[ INPUT_MOUSE_MOVE ][ i ].m_pszCommand, "x", x, "y", y ) ); + } +} + +void CInputProxyPanel::OnCursorEntered() +{ + FOR_EACH_VEC( m_vecRedirectPanels[ INPUT_MOUSE_ENTER ], i ) + { + PostMessage( m_vecRedirectPanels[ INPUT_MOUSE_ENTER ][ i ].m_pPanel, new KeyValues( m_vecRedirectPanels[ INPUT_MOUSE_ENTER ][ i ].m_pszCommand ) ); + } +} + +void CInputProxyPanel::OnCursorExited() +{ + FOR_EACH_VEC( m_vecRedirectPanels[ INPUT_MOUSE_EXIT ], i ) + { + PostMessage( m_vecRedirectPanels[ INPUT_MOUSE_EXIT ][ i ].m_pPanel, new KeyValues( m_vecRedirectPanels[ INPUT_MOUSE_EXIT ][ i ].m_pszCommand ) ); + } +} + +void CInputProxyPanel::OnMousePressed(MouseCode code) +{ + FOR_EACH_VEC( m_vecRedirectPanels[ INPUT_MOUSE_PRESS ], i ) + { + PostMessage( m_vecRedirectPanels[ INPUT_MOUSE_PRESS ][ i ].m_pPanel, new KeyValues( m_vecRedirectPanels[ INPUT_MOUSE_PRESS ][ i ].m_pszCommand, "code", code ) ); + } +} + +void CInputProxyPanel::OnMouseDoublePressed(MouseCode code) +{ + FOR_EACH_VEC( m_vecRedirectPanels[ INPUT_MOUSE_DOUBLE_PRESS ], i ) + { + PostMessage( m_vecRedirectPanels[ INPUT_MOUSE_DOUBLE_PRESS ][ i ].m_pPanel, new KeyValues( m_vecRedirectPanels[ INPUT_MOUSE_DOUBLE_PRESS ][ i ].m_pszCommand, "code", code ) ); + } +} + +void CInputProxyPanel::OnMouseReleased(MouseCode code) +{ + FOR_EACH_VEC( m_vecRedirectPanels[ INPUT_MOUSE_RELEASED ], i ) + { + PostMessage( m_vecRedirectPanels[ INPUT_MOUSE_RELEASED ][ i ].m_pPanel, new KeyValues( m_vecRedirectPanels[ INPUT_MOUSE_RELEASED ][ i ].m_pszCommand, "code", code ) ); + } +} + +void CInputProxyPanel::OnMouseWheeled(int delta) +{ + FOR_EACH_VEC( m_vecRedirectPanels[ INPUT_MOUSE_WHEEL ], i ) + { + PostMessage( m_vecRedirectPanels[ INPUT_MOUSE_WHEEL ][ i ].m_pPanel, new KeyValues( m_vecRedirectPanels[ INPUT_MOUSE_WHEEL ][ i ].m_pszCommand, "delta", delta ) ); + } +} + + +DECLARE_BUILD_FACTORY( CQuestStatusPanel ) + +CQuestStatusPanel::CQuestStatusPanel( Panel *parent, const char *pszPanelName ) + : EditablePanel( parent, pszPanelName ) + , m_pMovingContainer( NULL ) + , m_bShouldBeVisible( false ) +{ + m_pMovingContainer = new EditablePanel( this, "movingcontainer" ); + m_transitionTimer.Invalidate(); +} + +void CQuestStatusPanel::SetShow( bool bShow ) +{ + if ( bShow != m_bShouldBeVisible ) + { + m_transitionTimer.Start( 0.6f ); + } + m_bShouldBeVisible = bShow; + SetVisible( m_bShouldBeVisible ); +} + +void CQuestStatusPanel::OnThink() +{ + BaseClass::OnThink(); + + const int nStartY = m_bShouldBeVisible ? m_iHiddenY : m_iVisibleY; + const int nEndY = m_bShouldBeVisible ? m_iVisibleY : m_iHiddenY; + + float flProgress = 1.f; + if ( !m_transitionTimer.IsElapsed() ) + { + flProgress = Bias( RemapValClamped( m_transitionTimer.GetElapsedTime(), 0.f , m_transitionTimer.GetCountdownDuration(), 0.f, 1.f ), 0.7f ); + } + flProgress = RemapVal( flProgress, 0.f, 1.f, (float)nStartY, (float)nEndY ); + + m_pMovingContainer->SetPos( m_pMovingContainer->GetXPos(), flProgress ); + SetVisible( m_bShouldBeVisible || flProgress > 0.f ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CQuestItemPanel::CQuestItemPanel( Panel *parent, const char *pszPanelName, CEconItemView* pQuestItem, CScrollableQuestList* pQuestList ) + : EditablePanel( parent, pszPanelName ) + , m_hQuestItem( NULL ) + , m_eState( STATE_NORMAL ) + , m_pTurnInContainer( NULL ) + , m_pTurnInDimmer( NULL ) + , m_pszCompleteSound( NULL ) + , m_pFrontFolderContainer( NULL ) + , m_pBackFolderContainer( NULL ) + , m_bCollapsed( true ) + , m_pQuestList( pQuestList ) + , m_pQuestPaperContainer( NULL ) + , m_pTitleButton( NULL ) + , m_pIdentifyContainer( NULL ) + , m_pIdentifyDimmer( NULL ) + , m_pKVCipherStrings( NULL ) + , m_pPhotoStatic( NULL ) + , m_pFlavorScrollingContainer( NULL ) + , m_pTurningInLabel( NULL ) + , m_pFindServerButton( NULL ) + , m_pLoanerContainerPanel( NULL ) + , m_pRequestLoanerItemsButton( NULL ) + , m_pEquipLoanerItemsButton( NULL ) + , m_pItemTrackerPanel( NULL ) + , m_pKVItemTracker( NULL ) + , m_pObjectiveExplanationLabel( NULL ) + , m_pEncodedStatus( NULL ) + , m_pInactiveStatus( NULL ) + , m_pReadyToTurnInStatus( NULL ) + , m_pExpirationLabel( NULL ) + , m_pTurnInButton( NULL ) + , m_bHasAllControls( false ) + , m_pDiscardButton( NULL ) +{ + SetItem( pQuestItem ); + m_StateTimer.Invalidate(); + + ListenForGameEvent( "quest_objective_completed" ); + ListenForGameEvent( "player_spawn" ); + ListenForGameEvent( "client_disconnect" ); + ListenForGameEvent( "inventory_updated" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CQuestItemPanel::~CQuestItemPanel() +{ + if ( m_pItemTrackerPanel ) + { + m_pItemTrackerPanel->MarkForDeletion(); + m_pItemTrackerPanel = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestItemPanel::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings ( pScheme ); + AddActionSignalTarget( GetQuestLog() ); + LoadResFileForCurrentItem(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestItemPanel::LoadResFileForCurrentItem() +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + const char *pszResFile = "Resource/UI/quests/QuestItemPanel_Base.res"; + + if ( m_hQuestItem ) + { + const GameItemDefinition_t *pItemDef = m_hQuestItem->GetItemDefinition(); + // Get our quest theme + const CQuestThemeDefinition *pTheme = pItemDef->GetQuestDef()->GetQuestTheme(); + if ( pTheme ) + { + pszResFile = pTheme->GetQuestItemResFile(); + } + } + + KeyValues *pConditions = new KeyValues( "conditions" ); + if ( pConditions ) + { + char uilanguage[64]; + uilanguage[0] = 0; + engine->GetUILanguage( uilanguage, sizeof( uilanguage ) ); + char szCondition[64]; + Q_snprintf( szCondition, sizeof( szCondition ), "if_%s", uilanguage ); + AddSubKeyNamed( pConditions, szCondition ); + } + + SetMouseInputEnabled( true ); // Slam this to true. When panels get created, they'll inherit their parents' mouse enabled state + // and if we've been fiddling with it, we might accidently create all child panels with mouse input disabled. + // Setting this to true just before the controls are made gives them a chance to be mouse enabled if they want. + LoadControlSettings( pszResFile, NULL, NULL, pConditions ); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, m_strReset ); + + m_pMainContainer = FindControl<EditablePanel>( "MainContainer" ); + if ( m_pMainContainer ) + { + m_pMainContainer->AddActionSignalTarget( this ); + } + + m_pQuestPaperContainer = FindControl<EditablePanel>( "QuestPaperContainer", true ); + m_pFrontFolderContainer = FindControl<EditablePanel>( "FrontFolderContainer", true ); + Assert( m_pFrontFolderContainer ); + if ( m_pFrontFolderContainer ) + { + m_pFrontFolderImage = m_pFrontFolderContainer->FindControl<ImagePanel>( "FrontFolderImage", true ); + Assert( m_pFrontFolderImage ); + + m_pEncodedStatus = m_pFrontFolderContainer->FindControl< CQuestStatusPanel >( "EncodedStatus", true ); + m_pInactiveStatus = m_pFrontFolderContainer->FindControl< CQuestStatusPanel >( "InactiveStatus", true ); + m_pReadyToTurnInStatus = m_pFrontFolderContainer->FindControl< CQuestStatusPanel >( "ReadyToTurnInStatus", true ); + } + m_pBackFolderContainer = FindControl<EditablePanel>( "BackFolderContainer", true ); + Assert( m_pBackFolderContainer ); + if ( m_pBackFolderContainer ) + { + m_pBackFolderImage = m_pBackFolderContainer->FindControl<ImagePanel>( "BackFolderImage", true ); + Assert( m_pBackFolderImage ); + } + + if ( m_pQuestPaperContainer ) + { +#if defined( STAGING_ONLY ) || defined( DEBUG ) + // don't do this in public + m_pDiscardButton = new CExButton( m_pQuestPaperContainer, "Discard", "Discard", this, "discard_quest" ); + m_pDiscardButton->SetEnabled( true ); + m_pDiscardButton->SizeToContents(); + m_pDiscardButton->SetZPos( 101 ); + m_pDiscardButton->SetPos( 70, 40 ); + m_pDiscardButton->SetVisible( false ); +#endif // STAGING_ONLY || DEBUG + + m_pFindServerButton = m_pQuestPaperContainer->FindControl< CExButton >( "FindServerButton", true ); + + m_pLoanerContainerPanel = m_pQuestPaperContainer->FindControl< EditablePanel >( "LoanerContainerPanel", true ); + if ( m_pLoanerContainerPanel ) + { + m_pRequestLoanerItemsButton = m_pLoanerContainerPanel->FindControl< CExButton >( "RequestLoanerItemsButton", true ); + m_pEquipLoanerItemsButton = m_pLoanerContainerPanel->FindControl< CExButton >( "EquipLoanerItemsButton", true ); + for ( int i = 0; i < ARRAYSIZE( m_pLoanerItemModelPanel ); ++i ) + { + m_pLoanerItemModelPanel[i] = m_pLoanerContainerPanel->FindControl< CItemModelPanel >( CFmtStr( "Loaner%dItemModelPanel", i + 1 ), true ); + } + } + + m_pTitleButton = m_pQuestPaperContainer->FindControl<CExButton>( "TitleButton", true ); + m_pIdentifyContainer = m_pQuestPaperContainer->FindControl<EditablePanel>( "IdentifyButtonContainer", true ); + if ( m_pIdentifyContainer ) + { + m_pIdentifyDimmer = m_pIdentifyContainer->FindControl<EditablePanel>( "Dimmer", true ); + m_pIdentifyButton = m_pIdentifyContainer->FindControl<CExButton>( "IdentifyButton", true ); + } + Assert( m_pIdentifyContainer ); + + m_pEncodedImage = m_pQuestPaperContainer->FindControl<ImagePanel>( "EncodedImage", true ); + + m_pPhotoStatic = m_pQuestPaperContainer->FindControl<ImagePanel>( "StaticPhoto", true ); + Assert( m_pPhotoStatic ); + + m_pFlavorScrollingContainer = m_pQuestPaperContainer->FindControl<CExScrollingEditablePanel>( "ScrollableBottomContainer", true ); + Assert( m_pFlavorScrollingContainer ); + + if ( m_pFlavorScrollingContainer ) + { + m_pObjectiveExplanationLabel = m_pFlavorScrollingContainer->FindControl< Label >( "QuestObjectiveExplanation", true ); + } + + CInputProxyPanel* pInputProxy = m_pQuestPaperContainer->FindControl< CInputProxyPanel >( "PaperInputProxyPanel", true ); + if ( pInputProxy ) + { + // Make the scroller scroll + pInputProxy->AddPanelForCommand( CInputProxyPanel::INPUT_MOUSE_WHEEL, m_pFlavorScrollingContainer, "MouseWheeled" ); + + // Make the title glow + pInputProxy->AddPanelForCommand( CInputProxyPanel::INPUT_MOUSE_ENTER, m_pTitleButton, "CursorEntered" ); + pInputProxy->AddPanelForCommand( CInputProxyPanel::INPUT_MOUSE_EXIT, m_pTitleButton, "CursorExited" ); + + // Capture clicks to expand/contract + pInputProxy->AddPanelForCommand( CInputProxyPanel::INPUT_MOUSE_RELEASED, this, "MouseReleased" ); + } + + m_pTurnInContainer = m_pQuestPaperContainer->FindControl< EditablePanel >( "TurnInContainer" ); + Assert( m_pTurnInContainer ); + if ( m_pTurnInContainer ) + { + m_pTurnInDimmer = m_pTurnInContainer->FindControl< EditablePanel >( "Dimmer", true ); + Assert( m_pTurnInContainer ); + + m_pTurnInButton = m_pTurnInContainer->FindControl< Button >( "TurnInButton", true ); + Assert( m_pTurnInButton ); + + m_pTurnInSpinnerContainer = m_pTurnInContainer->FindControl< EditablePanel>( "TurnInSpinnerContainer", true ); + Assert( m_pTurnInSpinnerContainer ); + + if ( m_pTurnInSpinnerContainer ) + { + m_pTurningInLabel = m_pTurnInSpinnerContainer->FindControl< Label >( "TurningInLabel", true ); + Assert( m_pTurningInLabel ); + } + } + + m_pAcceptedImage = m_pQuestPaperContainer->FindControl< ImagePanel >( "AcceptedImage", true ); + Assert( m_pAcceptedImage ); + } + + if ( m_pFrontFolderContainer ) + { + CInputProxyPanel* pInputProxy = m_pFrontFolderContainer->FindControl< CInputProxyPanel >( "FrontInputProxyPanel", true ); + if ( pInputProxy ) + { + pInputProxy->AddPanelForCommand( CInputProxyPanel::INPUT_MOUSE_ENTER, m_pInactiveStatus, "CursorEntered" ); + pInputProxy->AddPanelForCommand( CInputProxyPanel::INPUT_MOUSE_EXIT, m_pInactiveStatus, "CursorExited" ); + + // Make the title glow + pInputProxy->AddPanelForCommand( CInputProxyPanel::INPUT_MOUSE_ENTER, m_pTitleButton, "CursorEntered" ); + pInputProxy->AddPanelForCommand( CInputProxyPanel::INPUT_MOUSE_EXIT, m_pTitleButton, "CursorExited" ); + + // Make the backdrop highlight + pInputProxy->AddPanelForCommand( CInputProxyPanel::INPUT_MOUSE_ENTER, this, "CollapsedGlowStart" ); + pInputProxy->AddPanelForCommand( CInputProxyPanel::INPUT_MOUSE_EXIT, this, "CollapsedGlowEnd" ); + + // Capture clicks to expand/contract + pInputProxy->AddPanelForCommand( CInputProxyPanel::INPUT_MOUSE_RELEASED, this, "MouseReleased" ); + } + } + + m_pExpirationLabel = FindControl<Label>( "QuestExpirationWarning", true ); + m_pFlavorText = FindControl<Label>( "QuestFlavorText", true ); + + SetupObjectivesPanels( true ); + + if ( pConditions ) + { + pConditions->deleteThis(); + } + + m_bHasAllControls = m_pQuestPaperContainer + && m_pFrontFolderContainer + && m_pFrontFolderImage + && m_pBackFolderContainer + && m_pBackFolderImage + && m_pEncodedStatus + && m_pInactiveStatus + && m_pReadyToTurnInStatus + && m_pFlavorText + && m_pObjectiveExplanationLabel + && m_pExpirationLabel + && m_pTurnInContainer + && m_pTurnInDimmer + && m_pTurnInButton + && m_pIdentifyButton + && m_pTurnInSpinnerContainer + && m_pTitleButton + && m_pIdentifyDimmer + && m_pIdentifyContainer + && m_pPhotoStatic + && m_pAcceptedImage + && m_pTurningInLabel + && m_pFlavorScrollingContainer + && m_pItemTrackerPanel + && m_pEncodedImage + && m_pMainContainer; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestItemPanel::ApplySettings( KeyValues *inResourceData ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + BaseClass::ApplySettings( inResourceData ); + + if ( m_hQuestItem ) + { + m_vecFoldersImages.Purge(); + KeyValues *pKVFoldersBlock = inResourceData->FindKey( "folders" ); + Assert( pKVFoldersBlock ); + if ( pKVFoldersBlock ) + { + FOR_EACH_TRUE_SUBKEY( pKVFoldersBlock, pKVFolder ) + { + auto& folder = m_vecFoldersImages[ m_vecFoldersImages.AddToTail() ]; + folder.m_strFront = pKVFolder->GetString( "front", NULL); + folder.m_strBack = pKVFolder->GetString( "back", NULL ); + } + } + else + { + const GameItemDefinition_t *pItemDef = m_hQuestItem->GetItemDefinition(); + // Get our quest theme + const CQuestThemeDefinition *pTheme = pItemDef->GetQuestDef()->GetQuestTheme(); + if ( pTheme ) + { + Warning( "%s %s is missing 'folders' data\n", pItemDef->GetQuestDef()->GetCorrespondingOperationName(), pTheme->GetQuestItemResFile() ); + } + } + } + + if ( 1/*m_pKVItemTracker == NULL*/ ) + { + KeyValues *pTrackerKV = inResourceData->FindKey( "tracker_kv" ); + + if ( pTrackerKV ) + { + m_pKVItemTracker = pTrackerKV->MakeCopy(); + } + } + + m_strEncodedText = inResourceData->GetString( "encoded_text", NULL ); + m_strExpireText = inResourceData->GetString( "expire_text", NULL ); + m_strItemTrackerResFile = inResourceData->GetString( "TrackerPanelResFile", NULL ); + // Sound effects + m_strTurnInSound = inResourceData->GetString( "turn_in_sound", NULL ); + m_strTurnInSuccessSound = inResourceData->GetString( "turn_in_success_sound", NULL ); + m_strDecodeSound = inResourceData->GetString( "decode_sound", NULL ); + m_strExpandSound = inResourceData->GetString( "expand_sound", NULL ); + m_strCollapseSound = inResourceData->GetString( "collapse_sound", NULL ); + + // Animations + m_strReset = inResourceData->GetString( "anim_reset", NULL ); + m_strAnimExpand = inResourceData->GetString( "anim_expand", NULL ); + m_strAnimCollapse = inResourceData->GetString( "anim_collapse", NULL ); + m_strTurningIn = inResourceData->GetString( "anim_turning_in", NULL ); + m_strHighlightOn = inResourceData->GetString( "anim_highlight_on", NULL ); + m_strHighlightOff = inResourceData->GetString( "anim_highlight_off", NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestItemPanel::PerformLayout( void ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + BaseClass::PerformLayout(); + + if ( !HasAllControls() ) + return; + + m_pIdentifyContainer->SetVisible( m_eState == STATE_UNIDENTIFIED ); + m_pTurnInContainer->SetVisible( m_eState == STATE_COMPLETED || m_eState == STATE_TURNING_IN__GC_RESPONDED || m_eState == STATE_TURNING_IN__WAITING_FOR_GC); + m_pTurnInButton->SetVisible( m_eState == STATE_COMPLETED ); + m_pTurnInSpinnerContainer->SetVisible( m_eState == STATE_TURNING_IN__GC_RESPONDED || m_eState == STATE_TURNING_IN__WAITING_FOR_GC ); + m_pPhotoStatic->SetVisible( m_eState == STATE_UNIDENTIFIED || m_eState == STATE_IDENTIFYING ); + m_pFindServerButton->SetVisible( m_eState == STATE_NORMAL ); + + // only exist in non public build + if ( m_pDiscardButton ) + { + m_pDiscardButton->SetVisible( m_eState == STATE_NORMAL ); + } + + // loaners + if ( m_eState == STATE_NORMAL || m_eState == STATE_COMPLETED ) + { + // get all loaners required from quest + CUtlVector< item_definition_index_t > vecLoanerItems; + bool bRequiredLoaners = GetLoanerListFromQuest( m_hQuestItem, vecLoanerItems ) > 0; + + // get all granted loaners from this quest + CUtlVector< CEconItemView* > vecGrantedLoaners; + if ( bRequiredLoaners ) + { + GetLoanersFromLocalInventory( m_hQuestItem->GetItemID(), vecLoanerItems, vecGrantedLoaners ); + } + + if ( bRequiredLoaners ) + { + m_pLoanerContainerPanel->SetVisible( true ); + bool bAllGranted = vecLoanerItems.Count() == vecGrantedLoaners.Count(); + m_pRequestLoanerItemsButton->SetVisible( !bAllGranted ); + m_pEquipLoanerItemsButton->SetVisible( bAllGranted ); + + for ( int i = 0; i < ARRAYSIZE( m_pLoanerItemModelPanel ); ++i ) + { + // try to use the granted items first + if ( i < vecGrantedLoaners.Count() ) + { + m_pLoanerItemModelPanel[i]->SetItem( vecGrantedLoaners[i] ); + m_pLoanerItemModelPanel[i]->SetVisible( true ); + } + // In case we don't get all the loaner items, use fake items as second option + else if ( i < vecLoanerItems.Count() ) + { + CEconItemView tempItem; + tempItem.Init( vecLoanerItems[i], AE_UNIQUE, AE_USE_SCRIPT_VALUE, true ); + + m_pLoanerItemModelPanel[i]->SetItem( &tempItem ); + m_pLoanerItemModelPanel[i]->SetVisible( true ); + } + else + { + m_pLoanerItemModelPanel[i]->SetVisible( false ); + } + } + } + else + { + m_pLoanerContainerPanel->SetVisible( false ); + } + } + else + { + m_pLoanerContainerPanel->SetVisible( false ); + } + + + + m_pEncodedStatus->SetShow( m_eState == STATE_UNIDENTIFIED ); + m_pReadyToTurnInStatus->SetShow( m_eState == STATE_COMPLETED || m_eState == STATE_TURNING_IN__GC_RESPONDED || m_eState == STATE_TURNING_IN__WAITING_FOR_GC ); + + float flDecodeAmount = 1.f; + // Only cypher-style decoding needs to decode + if ( m_eDecodeStyle == DECODE_STYLE_CYPHER && m_eState == STATE_UNIDENTIFIED ) + { + flDecodeAmount = 0.f; + } + + m_pEncodedImage->SetAlpha( m_eState == STATE_UNIDENTIFIED ? 255 : 0 ); + + if ( m_hQuestItem ) + { + m_pTitleButton->SetText( GetDecodedString( "name", flDecodeAmount ) ); + + int nScrollableYOffset = 0; + // Check if the quest is going to expire soon (within a week). If so, show a "This is going to be destroyed" message. + const CRTime nExpirationTime = m_hQuestItem->GetExpirationDate(); + const CRTime nOneWeekFromNow = CRTime::RTime32DateAdd( CRTime::RTime32TimeCur(), 1, k_ETimeUnitWeek ); + const bool bExpiringSoon = nExpirationTime.GetRTime32() != RTime32(0) && nExpirationTime < nOneWeekFromNow; + m_pExpirationLabel->SetVisible( bExpiringSoon ); + if ( bExpiringSoon ) + { + CLocalizedRTime32 locTime = { nExpirationTime.GetRTime32(), false, GLocalizationProvider(), NULL }; + m_pExpirationLabel->SetText( CConstructLocalizedString( g_pVGuiLocalize->Find( m_strExpireText ), locTime ) ); + m_pExpirationLabel->InvalidateLayout( true ); + m_pExpirationLabel->SizeToContents(); + nScrollableYOffset += m_pExpirationLabel->GetTall(); + } + + m_pObjectiveExplanationLabel->SetPos( 0, nScrollableYOffset ); + m_pObjectiveExplanationLabel->SetText( GetDecodedString( "explanation", flDecodeAmount ) ); + m_pObjectiveExplanationLabel->InvalidateLayout( true ); // So we get the right height when we do SizeToContents below + m_pObjectiveExplanationLabel->SizeToContents(); + nScrollableYOffset += m_pObjectiveExplanationLabel->GetTall(); + + m_pFlavorText->SetText( GetDecodedString( "desc", flDecodeAmount ) ); + int nWide, nTall; + m_pFlavorText->GetTextImage()->GetContentSize( nWide, nTall ); + m_pFlavorText->SetTall( nTall + 20 ); + + m_pItemTrackerPanel->SetPos( m_pItemTrackerPanel->GetXPos(), nScrollableYOffset ); + nScrollableYOffset += m_pItemTrackerPanel->GetTall(); + + // Put the flavor text below the obectives + m_pFlavorText->SetPos( m_pFlavorText->GetXPos(), nScrollableYOffset ); + + m_pFlavorScrollingContainer->InvalidateLayout( true ); + m_pFlavorScrollingContainer->InvalidateLayout(); + m_pFlavorScrollingContainer->ResetScrollAmount(); + + + // Randomize our folder images based on original ID + if ( m_vecFoldersImages.Count() ) + { + RandomSeed( m_hQuestItem->GetSOCData() ? m_hQuestItem->GetSOCData()->GetOriginalID() : m_hQuestItem->GetItemDefIndex() ); + int idx = RandomInt( 0, m_vecFoldersImages.Count() - 1 ); + + m_pFrontFolderImage->SetImage( m_vecFoldersImages[ idx ].m_strFront ); + m_pBackFolderImage->SetImage( m_vecFoldersImages[ idx ].m_strBack ); + } + } + + UpdateInvalidReasons(); + + if ( m_pItemTrackerPanel ) + { + auto& vecObjectives = m_pItemTrackerPanel->GetAttributePanels(); + FOR_EACH_VEC( vecObjectives, i ) + { + // Only do this when unidentified. The panel updates its own string, and we don't want to stomp it + if ( m_eState == STATE_UNIDENTIFIED ) + { + const wchar_t *pszString = GetDecodedString( CFmtStr( "objective%d", i ), flDecodeAmount ); + vecObjectives[ i ]->SetDialogVariable( "attr_desc", pszString ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CQuestItemPanel::IsCursorOverMainContainer() const +{ + return m_pMainContainer ? m_pMainContainer->IsCursorOver() : false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestItemPanel::SetupObjectivesPanels( bool bRecreate ) +{ + if ( m_pItemTrackerPanel && bRecreate ) + { + m_pItemTrackerPanel->MarkForDeletion(); + m_pItemTrackerPanel = NULL; + } + + if ( !m_hQuestItem ) + return; + + if ( !m_pItemTrackerPanel ) + { + m_pItemTrackerPanel = new CItemTrackerPanel( m_pFlavorScrollingContainer, "ItemTrackerPanel", m_hQuestItem->GetSOCData(), m_strItemTrackerResFile ); + m_pItemTrackerPanel->SetAutoDelete( false ); + SETUP_PANEL( m_pItemTrackerPanel ); + } + else + { + // Get all the panels created + m_pItemTrackerPanel->SetItem( m_hQuestItem->GetSOCData() ); + m_pItemTrackerPanel->InvalidateLayout( true ); + } + + m_pItemTrackerPanel->SetTall( m_pItemTrackerPanel->GetContentTall() ); + + // Need to re-layout so the flavor text gets properly positioned under + // all of the objectives + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestItemPanel::SetItem( CEconItemView* pItem ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + if ( pItem == m_hQuestItem ) + { + return; + } + + m_bCollapsed = true; + m_hQuestItem.SetItem( pItem ); + + if ( m_pItemTrackerPanel && pItem ) + { + m_pItemTrackerPanel->SetItem( pItem->GetSOCData() ); + } + + // By default + SetState( STATE_NORMAL ); + + if ( m_hQuestItem ) + { + if ( IsQuestItemReadyToTurnIn( m_hQuestItem ) ) + { + SetState( STATE_COMPLETED ); + } + else if ( IsUnacknowledged() ) + { + SetState( STATE_UNIDENTIFIED ); + } + + // Snag the quickplay map (if there is one) + m_strQuickPlayMap = m_hQuestItem->GetItemDefinition()->GetQuestDef()->GetQuickplayMapName(); + + m_strMatchmakingGroupName = m_hQuestItem->GetItemDefinition()->GetQuestDef()->GetMatchmakingGroupName(); + m_strMatchmakingCategoryName = m_hQuestItem->GetItemDefinition()->GetQuestDef()->GetMatchmakingCategoryName(); + m_strMatchmakingMapName = m_hQuestItem->GetItemDefinition()->GetQuestDef()->GetMatchmakingMapName(); + } + + // Reload res file so we get the right art + LoadResFileForCurrentItem(); + + // Capture strings after controls are created + CaptureAndEncodeStrings(); + + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns if the character is one that we don't want to re-encode +// as another, or one that we don't want to encode another to in +// order to maintain line breaks so that the decoding sequence is +// easier for the user to follow. +//----------------------------------------------------------------------------- +bool IsNonEncodeCharacter( const wchar_t& wch) +{ + switch ( wch ) + { + case L'\x000A': + case L'\x000B': + case L'\x000C': + case L'\x000D': + case L'\x0085': + case L'\x2028': + case L'\x2029': + case L' ': + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestItemPanel::CaptureAndEncodeStrings() +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + + if ( !m_hQuestItem ) + return; + + // Clean up any existing values + if ( m_pKVCipherStrings ) + { + m_pKVCipherStrings->deleteThis(); + m_pKVCipherStrings = NULL; + } + + m_pKVCipherStrings = new KeyValues( "cipherstrings" ); + KeyValues *pKVDecoded = m_pKVCipherStrings->CreateNewKey(); + pKVDecoded->SetName( "decoded" ); + + { + // Capture the description/flavor string + const char *pszLocToken = m_hQuestItem->GetItemDefinition()->GetQuestDef()->GetRolledDescriptionForItem( m_hQuestItem->GetSOCData() ); + pKVDecoded->SetWString( "desc", g_pVGuiLocalize->Find( pszLocToken ) ); + } + + if ( m_pObjectiveExplanationLabel ) + { + wchar_t wszBuff[512]; + m_pObjectiveExplanationLabel->GetText( wszBuff, ARRAYSIZE( wszBuff ) ); + pKVDecoded->SetWString( "explanation", wszBuff ); + } + + auto& vecObjectives = m_pItemTrackerPanel->GetAttributePanels(); + // Capture objective strings + FOR_EACH_VEC( vecObjectives, i ) + { + CItemAttributeProgressPanel *pObjective = vecObjectives[ i ]; + KeyValues *pKV = pObjective->GetDialogVariables(); + pKVDecoded->SetWString( CFmtStr( "objective%d", i ), pKV->GetWString( "attr_desc" ) ); + } + + // Create encoded strings from the decoded strings + KeyValues *pKVEncoded = pKVDecoded->MakeCopy(); + pKVEncoded->SetName( "encoded" ); + + m_pKVCipherStrings->AddSubKey( pKVEncoded ); + + RandomSeed( m_hQuestItem->GetSOCData() ? m_hQuestItem->GetSOCData()->GetOriginalID() : m_hQuestItem->GetItemDefIndex() ); + + // "encode" each string by scrambling + FOR_EACH_VALUE( pKVEncoded, pKVString ) + { + const wchar_t *pWString = pKVString->GetWString(); + wchar wszBuff[4096]; + loc_scpy_safe( wszBuff, pWString ); + int nStrLen = Q_wcslen( wszBuff ); + + // Go through the entire string and swap each character + // with another random character in the string + int i=0; + while( wszBuff[i] != 0 && i < ARRAYSIZE( wszBuff ) ) + { + // Dont scramble spaces to maintain line breaks + if ( !IsNonEncodeCharacter( wszBuff[i] ) ) + { + // Scramble, but keep trying if we scramble to a space + do + { + wszBuff[i] = *(pWString + RandomInt( 0, nStrLen - 1 ) ); + } while ( IsNonEncodeCharacter( wszBuff[i] ) ); + + } + + i++; + } + + pKVEncoded->SetWString( pKVString->GetName(), wszBuff ); + } + + const char *pszLocToken = m_hQuestItem->GetItemDefinition()->GetQuestDef()->GetRolledNameForItem( m_hQuestItem->GetSOCData() ); + const wchar_t* pwszName = g_pVGuiLocalize->Find( pszLocToken ); + // Force the encrypted version of the quest title to be "<Encrypted>". + pKVEncoded->SetWString( "name", g_pVGuiLocalize->Find( m_strEncodedText ) ); + pKVDecoded->SetWString( "name", pwszName ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestItemPanel::OnCommand( const char *command ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + BaseClass::OnCommand( command ); + + if ( FStrEq( command, "discard_quest" ) ) + { + OnDiscardQuest(); + } + else if ( FStrEq( command, "select" ) ) + { + m_pQuestList->SetSelected( this, false ); + } + else if ( FStrEq( command, "turnin" ) ) + { + if ( m_hQuestItem && m_hQuestItem->GetItemDefinition() && m_hQuestItem->GetItemDefinition()->GetQuestDef() ) + { + if ( !tf_quest_turn_in_confirm_opt_out.GetBool() && ( GetEarnedBonusPoints( m_hQuestItem ) != m_hQuestItem->GetItemDefinition()->GetQuestDef()->GetMaxBonusPoints() ) ) + { + CTFGenericConfirmOptOutDialog *pPanel = ShowConfirmOptOutDialog( "#TF_Quest_TurnIn_Title", "#TF_Quest_TurnIn_Text", + "#TF_Quest_TurnIn_Yes", "#TF_Quest_TurnIn_No", + "#TF_Quest_TurnIn_Ask_Opt_Out", "tf_quest_turn_in_confirm_opt_out", + ConfirmTurnInQuest ); + if ( pPanel ) + { + pPanel->SetContext( this ); + return; + } + } + else + { + OnCompleteQuest(); + } + } + } + else if ( FStrEq( command, "identify" ) ) + { + OnIdentify(); + } + else if ( FStrEq( command, "request_loaner_items" ) ) + { + GCSDK::CProtoBufMsg< CMsgGCQuestObjective_RequestLoanerItems > msg( k_EMsgGCQuestObjective_RequestLoanerItems ); + msg.Body().set_quest_item_id( m_hQuestItem->GetItemID() ); + GCClientSystem()->BSendMessage( msg ); + } + else if ( FStrEq( command, "equip_loaner_items" ) ) + { + OnEquipLoaners(); + } + else if( Q_strnicmp( "playsound", command, 9 ) == 0 ) + { + vgui::surface()->PlaySound( command + 10 ); + } + else if ( FStrEq( "mm_casual_open", command ) ) + { + if ( GTFGCClientSystem() ) + { + if ( ( m_strMatchmakingGroupName != 0 ) || ( m_strMatchmakingCategoryName != 0 ) || ( m_strMatchmakingMapName != 0 ) ) + { + GTFGCClientSystem()->ClearCasualSearchCriteria(); + + if ( m_strMatchmakingGroupName != 0 ) + { + int iGroupType = StringFieldToInt( m_strMatchmakingGroupName.Get(), s_pszMMTypes, ARRAYSIZE( s_pszMMTypes ) ); + if ( iGroupType > -1 ) + { + SelectGroup( (EMatchmakingGroupType)iGroupType, true ); + } + } + + if ( m_strMatchmakingCategoryName != 0 ) + { + int iCategoryType = StringFieldToInt( m_strMatchmakingCategoryName.Get(), s_pszGameModes, ARRAYSIZE( s_pszGameModes ) ); + if ( iCategoryType > -1 ) + { + SelectCategory( (EGameCategory)iCategoryType, true ); + } + } + + if ( m_strMatchmakingMapName != 0 ) + { + if ( GetItemSchema() ) + { + const MapDef_t *pMap = GetItemSchema()->GetMasterMapDefByName( m_strMatchmakingMapName.Get() ); + if ( pMap ) + { + GTFGCClientSystem()->SelectCasualMap( pMap->m_nDefIndex, true ); + } + } + } + } + } + + // Defaulting to 12v12 + GTFGCClientSystem()->SetLadderType( k_nMatchGroup_Casual_12v12 ); + PromptOrFireCommand( "OpenMatchmakingLobby casual" ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const wchar_t* CQuestItemPanel::GetDecodedString( const char* pszKeyName, float flPercentDecoded ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + static wchar_t wszBuff[4096]; + KeyValues *pKVEncoded = m_pKVCipherStrings->FindKey( "encoded" ); + KeyValues *pKVDecoded = m_pKVCipherStrings->FindKey( "decoded" ); + + // Trivial work? + if ( flPercentDecoded <= 0.f ) + { + return pKVEncoded->GetWString( pszKeyName ); + } + else if ( flPercentDecoded >= 1.f ) + { + return pKVDecoded->GetWString( pszKeyName ); + } + + loc_scpy_safe( wszBuff, pKVEncoded->GetWString( pszKeyName ) ); + const locchar_t* pwszDecoded = pKVDecoded->GetWString( pszKeyName ); + int nLength = loc_strlen( pwszDecoded ); + int nMaxCopy = nLength * flPercentDecoded; + // Not using V_wcsncpy because it null terminates. + wcsncpy( wszBuff, pwszDecoded, nMaxCopy ); + + return wszBuff; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestItemPanel::OnThink() +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + + switch ( m_eState ) + { + case STATE_IDENTIFYING: + { + // Have we finished? + if ( m_StateTimer.HasStarted() && m_StateTimer.IsElapsed() ) + { + m_pItemTrackerPanel->InvalidateLayout(); + m_StateTimer.Invalidate(); + SetState( STATE_NORMAL ); + + // Play a reveal sound? + const GameItemDefinition_t *pItemDef = m_hQuestItem->GetItemDefinition(); + const CQuestThemeDefinition *pTheme = pItemDef->GetQuestDef()->GetQuestTheme(); + if ( pTheme ) + { + const char *pszRevealSound = pTheme->GetRevealSound(); + if ( pszRevealSound && pszRevealSound[0] ) + { + vgui::surface()->PlaySound( pszRevealSound ); + } + } + } + + float flPercent = m_StateTimer.GetElapsedTime() / m_StateTimer.GetCountdownDuration(); + + switch( m_eDecodeStyle ) + { + case DECODE_STYLE_CYPHER: + { + // Slowly "decode" the text in the lables + m_pTitleButton->SetText( GetDecodedString( "name", flPercent ) ); + m_pFlavorText->SetText( GetDecodedString( "desc", flPercent ) ); + m_pObjectiveExplanationLabel->SetText( GetDecodedString( "explanation", flPercent ) ); + + auto& vecObjectives = m_pItemTrackerPanel->GetAttributePanels(); + FOR_EACH_VEC( vecObjectives, i ) + { + const wchar_t *pszString = GetDecodedString( CFmtStr( "objective%d", i ), flPercent ); + vecObjectives[ i ]->SetDialogVariable( "attr_desc", pszString ); + } + break; + } + case DECODE_STYLE_PANEL_FADE: + { + // Slowly fade out the encode image + m_pEncodedImage->SetAlpha( 255 * ( 1.f - flPercent ) ); + break; + } + default: + Assert( 0 ); + } + + break; + } + case STATE_TURNING_IN__WAITING_FOR_GC: + { + // Have we finished? + if ( m_StateTimer.HasStarted() && m_StateTimer.IsElapsed() ) + { + m_pQuestList->SetCompletingPanel( NULL ); + m_StateTimer.Invalidate(); + + // Bring up confirm dialog + CTFGenericConfirmDialog *pDialog = new CTFGenericConfirmDialog( "#TF_Trading_Timeout_Title", "#TF_Trading_Timeout_Text", "#TF_OK", NULL, NULL, NULL ); + + if ( pDialog ) + { + pDialog->SetContext( this ); + pDialog->Show(); + } + + SetState( STATE_COMPLETED ); + } + + // Intentionally fall through + } + case STATE_TURNING_IN__GC_RESPONDED: + { + // Have we finished? + if ( m_StateTimer.HasStarted() && m_StateTimer.IsElapsed() ) + { + SetState( STATE_SHOW_ACCEPTED ); + m_StateTimer.Start( 3.f ); + m_pAcceptedImage->SetVisible( true ); + + vgui::surface()->PlaySound( m_strTurnInSuccessSound ); + } + + if ( m_pTurningInLabel ) + { + int nPeriods = m_StateTimer.GetElapsedTime() / 0.3f; + nPeriods %= 4; // Only do up to 3 periods + + wchar_t wszTurningInText[64]; + char szTurningInLocToken[128]; + m_pTurningInLabel->GetTextImage()->GetUnlocalizedText( szTurningInLocToken, ARRAYSIZE( szTurningInLocToken ) ); + V_snwprintf( wszTurningInText, ARRAYSIZE( wszTurningInText ), L"%ls", g_pVGuiLocalize->Find( szTurningInLocToken ) ); + + while ( nPeriods > 0 ) + { + V_wcsncat( wszTurningInText, L".", ARRAYSIZE( wszTurningInText ) ); + --nPeriods; + } + + m_pTurningInLabel->SetText( wszTurningInText ); + } + + break; + } + case STATE_SHOW_ACCEPTED: + { + if ( m_StateTimer.HasStarted() && m_StateTimer.IsElapsed() ) + { + m_pQuestList->SetCompletingPanel( NULL ); + m_StateTimer.Invalidate(); + + engine->ClientCmd_Unrestricted( "gameui_allowescapetoshow\n" ); + + InventoryManager()->ShowItemsPickedUp( true, false ); + GetQuestLog()->AttachToGameUI(); + GetQuestLog()->MarkQuestsDirty(); + m_pQuestList->PopulateQuestLists(); + + engine->ClientCmd_Unrestricted( "gameui_preventescapetoshow\n" ); + + if ( m_pszCompleteSound ) + { + vgui::surface()->PlaySound( m_pszCompleteSound ); + } + } + else + { + float flPercent = Clamp( m_StateTimer.GetElapsedTime() / 0.2f, 0.f, 1.f ); + m_nPaperXShakePos = sin( m_StateTimer.GetElapsedTime() * 200.f ) * ( 1.f - flPercent ) * 8.f; + m_nPaperYShakePos = sin( m_StateTimer.GetElapsedTime() * 200.f ) * ( 1.f - flPercent ) * 8.f; + m_pQuestPaperContainer->SetPos( m_nPaperXPos + m_nPaperXShakePos, m_nPaperYPos + m_nPaperYShakePos ); + } + + break; + } + case STATE_UNIDENTIFIED: + { + // Do nothing + break; + } + case STATE_COMPLETED: + { + // Do nothing + break; + } + default: + // Do nothing + break; + } + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestItemPanel::FireGameEvent( IGameEvent *event ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + if( FStrEq( event->GetName(), "quest_objective_completed" ) ) + { + itemid_t nIDLow = 0x00000000FFFFFFFF & (itemid_t)event->GetInt( "quest_item_id_low" ); + itemid_t nIDHi = 0xFFFFFFFF00000000 & (itemid_t)event->GetInt( "quest_item_id_hi" ) << 32; + itemid_t nID = nIDLow | nIDHi; + if ( m_hQuestItem && nID == m_hQuestItem->GetID() ) + { + SetupObjectivesPanels( false ); + + if ( IsQuestItemReadyToTurnIn( m_hQuestItem ) ) + { + SetState( STATE_COMPLETED ); + } + + PerformLayout(); + } + } + else if ( FStrEq( event->GetName(), "player_spawn" ) + || FStrEq( event->GetName(), "client_disconnect" ) ) + { + InvalidateLayout(); + } + else if ( FStrEq( "inventory_updated", event->GetName() ) ) + { + // InvalidateLayout(); + } +} + +void CQuestItemPanel::OnMouseReleased( MouseCode code ) +{ + OnCommand( "select" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Update our invalid reasons +//----------------------------------------------------------------------------- +void CQuestItemPanel::UpdateInvalidReasons() +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + InvalidReasonsContainer_t invalidReasons; + bool bAllAreInvalid = false; + + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( pLocalPlayer && m_hQuestItem ) + { + // Get the tracker for the items + const CQuestItemTracker* pItemTracker = QuestObjectiveManager()->GetTypedTracker< CQuestItemTracker* >( m_hQuestItem->GetItemID() ); + // Get invalid reasons + if ( pItemTracker ) + { + int nNumInvalid = pItemTracker->IsValidForPlayer( pLocalPlayer, invalidReasons ); + bAllAreInvalid = pItemTracker->GetTrackers().Count() == nNumInvalid; + } + + // Build a string describing why the current quest can't be worked on + if ( !invalidReasons.IsValid() ) + { + CUtlVector< CUtlString > vecStrings; + // Get the strings that explain each reasons + GetInvalidReasonsNames( invalidReasons, vecStrings ); + + wchar_t wszBuff[ 1024 ]; + + // Start with the explanation + V_swprintf_safe( wszBuff, L"%ls", g_pVGuiLocalize->Find( "#TF_QuestInvalid_Explanation" ) ); + + // Add in each reason why the quest is invalid + for( int i = 0; i < vecStrings.Count(); ++ i ) + { + V_wcscat_safe( wszBuff, L"\n\n" ); + V_wcscat_safe( wszBuff, g_pVGuiLocalize->Find( vecStrings[i] ) ); + } + + // This gets snagged by CQuestTooltip + m_pInactiveStatus->SetDialogVariable( "tiptext", wszBuff ); + } + } + + // Visible if there's a reason why we're invalid + bool bShow = bAllAreInvalid && m_eState == STATE_NORMAL; + m_pInactiveStatus->SetShow( bShow ); + m_pInactiveStatus->SetMouseInputEnabled( bShow ); + m_pInactiveStatus->SetTooltip( g_spTextTooltip, NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Start a glow +//----------------------------------------------------------------------------- +void CQuestItemPanel::OnCollapsedGlowStart( void ) +{ + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, m_strHighlightOn ); +} + +//----------------------------------------------------------------------------- +// Purpose: Stop the glow +//----------------------------------------------------------------------------- +void CQuestItemPanel::OnCollapsedGlowEnd( void ) +{ + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, m_strHighlightOff ); +} + +//----------------------------------------------------------------------------- +// Purpose: Delete the quest. +//----------------------------------------------------------------------------- +void CQuestItemPanel::OnDiscardQuest( void ) +{ +#if !defined(STAGING_ONLY) && !defined(DEBUG) + // Not in public! + return; +#endif + + if ( m_pQuestList->GetCompletingPanel() == NULL ) + { + // Bring up confirm dialog + CTFGenericConfirmDialog *pDialog = new CTFGenericConfirmDialog( "#QuestConfirmDiscard_Title", "#QuestConfirmDiscard_Body", "#X_DiscardItem", "#Cancel", &ConfirmDiscardQuest, NULL ); + if ( pDialog ) + { + pDialog->SetContext( this ); + pDialog->Show(); + } + + const GameItemDefinition_t *pItemDef = m_hQuestItem->GetItemDefinition(); + // Get our quest theme + const CQuestThemeDefinition *pTheme = pItemDef->GetQuestDef()->GetQuestTheme(); + if ( pTheme ) + { + const char *pszDiscardSound = pTheme->GetDiscardSound(); + if ( pszDiscardSound && pszDiscardSound[0] ) + { + vgui::surface()->PlaySound( pszDiscardSound ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Equip loaners for local player +//----------------------------------------------------------------------------- +void CQuestItemPanel::OnEquipLoaners( void ) +{ + if ( !m_hQuestItem ) + return; + + if ( m_pQuestList->GetCompletingPanel() == NULL ) + { + // Bring up confirm dialog + CTFGenericConfirmDialog *pDialog = new CTFGenericConfirmDialog( "#QuestConfirmEquipLoaners_Title", "#QuestConfirmEquipLoaners_Body", "#Equip", "#Cancel", &ConfirmEquipLoaners, NULL ); + if ( pDialog ) + { + pDialog->SetContext( this ); + pDialog->Show(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Send a message to the GC to evaluate completion of this quest +//----------------------------------------------------------------------------- +void CQuestItemPanel::OnCompleteQuest( void ) +{ + if ( !m_hQuestItem ) + return; + + // Double check that they're not just forcing the command + if ( IsQuestItemReadyToTurnIn( m_hQuestItem ) && m_pQuestList->GetCompletingPanel() == NULL ) + { + m_pQuestList->SetCompletingPanel( this ); + + SetState( STATE_TURNING_IN__WAITING_FOR_GC ); + + // Use the timer for turning in the quest + m_StateTimer.Start( k_flQuestTurnInTime ); + vgui::surface()->PlaySound( m_strTurnInSound ); + + GCSDK::CProtoBufMsg< CMsgGCQuestComplete_Request > msg( k_EMsgGCQuestComplete_Request ); + + msg.Body().set_quest_item_id( m_hQuestItem->GetItemID() ); + + GCClientSystem()->BSendMessage( msg ); + + PostActionSignal( new KeyValues("CompleteQuest") ); + + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, m_strTurningIn ); + + const GameItemDefinition_t *pItemDef = m_hQuestItem->GetItemDefinition(); + // Get our quest theme + const CQuestThemeDefinition *pTheme = pItemDef->GetQuestDef()->GetQuestTheme(); + if ( pTheme ) + { + m_pszCompleteSound = pTheme->GetRewardSound(); + } + } +} + +void CQuestItemPanel::OnIdentify() +{ + if ( IsUnacknowledged() ) + { + SetState( STATE_IDENTIFYING ); + + // Use the timer for identifying progress + m_StateTimer.Start( k_flQuestDecodeTime ); + vgui::surface()->PlaySound( m_strDecodeSound ); + + // ack item + CEconItemView *pModifyItem = m_hQuestItem; + TFInventoryManager()->AcknowledgeItem( pModifyItem, false ); + TFInventoryManager()->SetItemBackpackPosition( pModifyItem, (uint32)-1, false, true ); + + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_pQuestPaperContainer, "QuestItem_StaticPhoto_Reveal" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestItemPanel::OnConfirmDelete( bool bConfirm ) +{ + // Delete the quest + if ( bConfirm && m_hQuestItem ) + { + GCSDK::CProtoBufMsg< CMsgGCQuestDiscard_Request > msg( k_EMsgGCQuestDiscard_Request ); + + msg.Body().set_quest_item_id( m_hQuestItem->GetItemID() ); + + GCClientSystem()->BSendMessage( msg ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestItemPanel::OnConfirmEquipLoaners( bool bConfirm ) +{ + // equip loaners + if ( bConfirm && m_hQuestItem ) + { + // get all loaners required from quest + CUtlVector< item_definition_index_t > vecLoanerItems; + bool bRequiredLoaners = GetLoanerListFromQuest( m_hQuestItem, vecLoanerItems ); + + // get all granted loaners from this quest + CUtlVector< CEconItemView* > vecGrantedLoaners; + if ( bRequiredLoaners ) + { + GetLoanersFromLocalInventory( m_hQuestItem->GetItemID(), vecLoanerItems, vecGrantedLoaners ); + } + + for ( int i=0; i<vecGrantedLoaners.Count(); ++i ) + { + CEconItemView *pItem = vecGrantedLoaners[i]; + if ( pItem ) + { + // do it for first class that can equip + for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; ++iClass ) + { + if ( pItem->GetStaticData()->CanBeUsedByClass( iClass ) ) + { + int iSlot = pItem->GetStaticData()->GetLoadoutSlot( iClass ); + TFInventoryManager()->EquipItemInLoadout( iClass, iSlot, pItem->GetItemID() ); + + // take the player to character loadout page + engine->ClientCmd_Unrestricted( CFmtStr( "open_charinfo_direct %d", iClass ) ); + break; + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestItemPanel::QuestCompletedResponse() +{ + // If we werent the one listening, dont bother + if ( m_eState != STATE_TURNING_IN__WAITING_FOR_GC ) + return; + + m_pQuestPaperContainer->GetPos( m_nPaperXPos, m_nPaperYPos ); + m_nPaperXShakePos = m_nPaperYShakePos = 0; + + SetState( STATE_TURNING_IN__GC_RESPONDED ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestItemPanel::SetSelected( bool bSelected, bool bImmediate ) +{ + bool bPrevCollapsedSide = m_bCollapsed; + m_bCollapsed = ( !m_bCollapsed && bSelected ) || ( !bSelected ); + + if ( !bImmediate && bPrevCollapsedSide != m_bCollapsed ) + { + if ( m_bCollapsed ) + { + vgui::surface()->PlaySound( m_strCollapseSound ); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, m_strAnimCollapse ); + } + else + { + vgui::surface()->PlaySound( m_strExpandSound ); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, m_strAnimExpand ); + } + } + else + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, m_strReset ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CQuestItemPanel::IsUnacknowledged() +{ + if ( !m_hQuestItem ) + return false; + + return IsQuestItemUnidentified( m_hQuestItem->GetSOCData() ); +} + +void CQuestItemPanel::SetState( EItemPanelState_t eState ) +{ + m_eState = eState; + InvalidateLayout(); +} + + + +//----------------------------------------------------------------------------- +// Purpose: GC Msg handler to handle a loaner item response +//----------------------------------------------------------------------------- +class CGCLoanerRequestResponse : public GCSDK::CGCClientJob +{ +public: + CGCLoanerRequestResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + GCSDK::CProtoBufMsg<CMsgGCQuestObjective_RequestLoanerResponse> msg( pNetPacket ); + + // Show them the items they just got loaned! + InventoryManager()->ShowItemsPickedUp( true, false ); + + return true; + } +}; + +GC_REG_JOB( GCSDK::CGCClient, CGCLoanerRequestResponse, "CGCLoanerRequestResponse", k_EMsgGCQuestObjective_RequestLoanerResponse, GCSDK::k_EServerTypeGCClient ); diff --git a/game/client/tf/vgui/quest_item_panel.h b/game/client/tf/vgui/quest_item_panel.h new file mode 100644 index 0000000..dfdb5a8 --- /dev/null +++ b/game/client/tf/vgui/quest_item_panel.h @@ -0,0 +1,265 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef QUEST_ITEM_PANEL_H +#define QUEST_ITEM_PANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "econ_item_inventory.h" +#include "tf_controls.h" + +using namespace vgui; + +class CScrollableQuestList; +class CItemModelPanel; + +//----------------------------------------------------------------------------- +// Simple tooltip class that looks into the moused-over panel's dialog variables +// for "tiptext" and uses that value as its string to present. +//----------------------------------------------------------------------------- +class CQuestTooltip : public CTFTextToolTip +{ + DECLARE_CLASS_SIMPLE( CQuestTooltip, CTFTextToolTip ); +public: + CQuestTooltip( vgui::Panel *parent, const char *text = NULL ) + : BaseClass( parent, text ) + {} + + virtual void ShowTooltip( Panel *pCurrentPanel ) OVERRIDE; + virtual void PositionWindow( Panel *pTipPanel ) OVERRIDE; +private: +}; + +//----------------------------------------------------------------------------- +// Can pass various input events to other panels +//----------------------------------------------------------------------------- +class CInputProxyPanel : public EditablePanel +{ +public: + + enum EInputTypes + { + INPUT_MOUSE_ENTER = 0, + INPUT_MOUSE_EXIT, + INPUT_MOUSE_PRESS, + INPUT_MOUSE_DOUBLE_PRESS, + INPUT_MOUSE_RELEASED, + INPUT_MOUSE_WHEEL, + INPUT_MOUSE_MOVE, + NUM_INPUT_TYPES, + }; + + DECLARE_CLASS_SIMPLE( CInputProxyPanel, EditablePanel ); + CInputProxyPanel( Panel *parent, const char *pszPanelName ); + + void AddPanelForCommand( EInputTypes eInputType, Panel* pPanel, const char* pszCommand ); + + MESSAGE_FUNC_INT_INT( OnCursorMoved, "OnCursorMoved", x, y ); + virtual void OnCursorEntered(); + virtual void OnCursorExited(); + virtual void OnMousePressed(MouseCode code); + virtual void OnMouseDoublePressed(MouseCode code); + virtual void OnMouseReleased(MouseCode code); + virtual void OnMouseWheeled(int delta); + +private: + + struct CommandPair_t + { + Panel* m_pPanel; + const char* m_pszCommand; + }; + CUtlVector< CommandPair_t > m_vecRedirectPanels[NUM_INPUT_TYPES]; +}; + +//----------------------------------------------------------------------------- +// Contains a panel that animates into place when it needs to show or hide +//----------------------------------------------------------------------------- +class CQuestStatusPanel : public EditablePanel +{ +public: + DECLARE_CLASS_SIMPLE( CQuestStatusPanel, EditablePanel ); + CQuestStatusPanel( Panel *parent, const char *pszPanelName ); + + void SetShow( bool bShow ); + virtual void OnThink() OVERRIDE; + +private: + EditablePanel* m_pMovingContainer; + RealTimeCountdownTimer m_transitionTimer; + bool m_bShouldBeVisible; + + CPanelAnimationVarAliasType( int, m_iVisibleY, "visible_y", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iHiddenY, "hidden_y", "40", "proportional_int" ); +}; + +//----------------------------------------------------------------------------- +// An representation of a single quest +//----------------------------------------------------------------------------- +class CQuestItemPanel : public EditablePanel, CGameEventListener +{ +public: + enum EItemPanelState_t + { + STATE_NORMAL = 0, + STATE_UNIDENTIFIED, + STATE_IDENTIFYING, + STATE_COMPLETED, + STATE_TURNING_IN__WAITING_FOR_GC, + STATE_TURNING_IN__GC_RESPONDED, + STATE_SHOW_ACCEPTED, + + NUM_STATES, + }; + + DECLARE_CLASS_SIMPLE( CQuestItemPanel, EditablePanel ); + + CQuestItemPanel( Panel *parent, const char *pszPanelName, CEconItemView* pQuestItem, CScrollableQuestList* pQuestList ); + virtual ~CQuestItemPanel(); + + virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE; + virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE; + virtual void PerformLayout( void ) OVERRIDE; + virtual void OnCommand( const char *command ) OVERRIDE; + virtual void OnThink() OVERRIDE; + virtual void FireGameEvent( IGameEvent *event ) OVERRIDE; + virtual void OnSizeChanged(int wide, int tall) OVERRIDE {} + virtual void OnMouseReleased(MouseCode code) OVERRIDE; + + const CEconItemView* GetItem() { return m_hQuestItem; } + void SetItem( CEconItemView* pItem ); + void QuestCompletedResponse(); + EItemPanelState_t GetState() const { return m_eState; } + void SetSelected( bool bSelected, bool bImmediate ); + bool IsSelected() const { return !m_bCollapsed; } + bool IsCursorOverMainContainer() const; + + MESSAGE_FUNC( OnCollapsedGlowStart, "CollapsedGlowStart" ); + MESSAGE_FUNC( OnCollapsedGlowEnd, "CollapsedGlowEnd" ); + MESSAGE_FUNC( OnDiscardQuest, "DiscardQuest" ); + MESSAGE_FUNC( OnEquipLoaners, "EquipLoaners" ); + void OnCompleteQuest(); + void OnConfirmDelete( bool bConfirm ); + void OnConfirmEquipLoaners( bool bConfirm ); + +protected: + + bool HasAllControls() const { return m_bHasAllControls; } + + void LoadResFileForCurrentItem(); + void OnIdentify(); + void SetupObjectivesPanels( bool bRecreate ); + bool IsUnacknowledged(); + void SetState( EItemPanelState_t eState ); + void CaptureAndEncodeStrings(); + const wchar_t* GetDecodedString( const char* pszKeyName, float flPercentDecoded ); + void UpdateInvalidReasons(); + + EItemPanelState_t m_eState; + CEconItemViewHandle m_hQuestItem; + + EditablePanel *m_pQuestPaperContainer; + EditablePanel *m_pFrontFolderContainer; + ImagePanel *m_pFrontFolderImage; + EditablePanel *m_pBackFolderContainer; + ImagePanel *m_pBackFolderImage; + ImagePanel *m_pEncodedImage; + EditablePanel *m_pMainContainer; + + CQuestStatusPanel *m_pEncodedStatus; + CQuestStatusPanel *m_pInactiveStatus; + CQuestStatusPanel *m_pReadyToTurnInStatus; + Label *m_pFlavorText; + Label *m_pObjectiveExplanationLabel; + Label *m_pExpirationLabel; + EditablePanel *m_pTurnInContainer; + EditablePanel *m_pTurnInDimmer; + Button *m_pTurnInButton; + EditablePanel *m_pTurnInSpinnerContainer; + CExButton *m_pTitleButton; + EditablePanel *m_pIdentifyDimmer; + EditablePanel *m_pIdentifyContainer; + CExButton *m_pIdentifyButton; + ImagePanel *m_pPhotoStatic; + ImagePanel *m_pAcceptedImage; + Label *m_pTurningInLabel; + class CExScrollingEditablePanel *m_pFlavorScrollingContainer; + CExButton *m_pFindServerButton; + + // loaners + EditablePanel *m_pLoanerContainerPanel; + CExButton *m_pRequestLoanerItemsButton; + CExButton *m_pEquipLoanerItemsButton; + CItemModelPanel *m_pLoanerItemModelPanel[2]; + + CExButton *m_pDiscardButton; + + + int m_nPaperXPos; + int m_nPaperYPos; + int m_nPaperXShakePos; + int m_nPaperYShakePos; + bool m_bHasAllControls; + CUtlString m_strItemTrackerResFile; + CUtlString m_strQuickPlayMap; + + CUtlString m_strMatchmakingGroupName; + CUtlString m_strMatchmakingCategoryName; + CUtlString m_strMatchmakingMapName; + + // Sound effects + CUtlString m_strExpandSound; + CUtlString m_strCollapseSound; + CUtlString m_strTurnInSound; + CUtlString m_strTurnInSuccessSound; + CUtlString m_strDecodeSound; + + // Animation + CUtlString m_strReset; + CUtlString m_strAnimExpand; + CUtlString m_strAnimCollapse; + CUtlString m_strTurningIn; + CUtlString m_strHighlightOn; + CUtlString m_strHighlightOff; + + class CItemTrackerPanel *m_pItemTrackerPanel; + + CScrollableQuestList *m_pQuestList; + + RealTimeCountdownTimer m_StateTimer; + KeyValues *m_pKVItemTracker; + + struct FolderPair_t + { + CUtlString m_strFront; + CUtlString m_strBack; + }; + CUtlVector< FolderPair_t > m_vecFoldersImages; + + CUtlString m_strEncodedText; + CUtlString m_strExpireText; + const char *m_pszCompleteSound; + bool m_bCollapsed; + + KeyValues *m_pKVCipherStrings; + + CPanelAnimationVarAliasType( int, m_iFrontPaperHideHeight, "front_paper_hide_height", "1000", "proportional_int" ); // Default to a large value so it wont be visible + CPanelAnimationVarAliasType( int, m_iUnidentifiedHeight, "unidentified_height", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iObjectiveInset, "objective_inset", "200", "proportional_int" ); + //CPanelAnimationVarAliasType( int, m_iScrollingContainerHeight, "scrolling_container_height", "200", "proportional_int" ); + + enum EDecodeStyle + { + DECODE_STYLE_CYPHER = 0, + DECODE_STYLE_PANEL_FADE, + }; + CPanelAnimationVarAliasType( EDecodeStyle, m_eDecodeStyle, "decode_style", "0", "int" ); +}; + +#endif // QUEST_ITEM_PANEL_H diff --git a/game/client/tf/vgui/quest_log_panel.cpp b/game/client/tf/vgui/quest_log_panel.cpp new file mode 100644 index 0000000..e65c7b0 --- /dev/null +++ b/game/client/tf/vgui/quest_log_panel.cpp @@ -0,0 +1,1182 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "quest_log_panel.h" +#include "ienginevgui.h" +#include "c_tf_gamestats.h" +#include "store/store_panel.h" +#include "econ/econ_ui.h" +#include "clientmode_tf.h" +#include "tf_hud_mainmenuoverride.h" +#include "vgui_int.h" +#include "IGameUIFuncs.h" // for key bindings +#include <vgui_controls/AnimationController.h> +#include "tf_item_inventory.h" +#include "vgui/IInput.h" +#include "item_ad_panel.h" +#include "vgui_controls/ProgressBar.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +void AddSubKeyNamed( KeyValues *pKeys, const char *pszName ); + +static CItemModelPanelToolTip* g_spItemTooltip = NULL; +CQuestTooltip* g_spTextTooltip = NULL; + +CQuestLogPanel *GetQuestLog() +{ + CQuestLogPanel *pQuestLogPanel = (CQuestLogPanel*)gViewPortInterface->FindPanelByName( PANEL_QUEST_LOG ); + return pQuestLogPanel; +} + +//------------------------------------------------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CScrollableQuestList::CScrollableQuestList( vgui::Panel *parent, const char *pszPanelName ) + : EditablePanel( parent, pszPanelName ) + , m_pCompletingPanel( NULL ) + , m_bQuestsLayoutDirty( false ) + , m_pszNoQuests( NULL ) + , m_pszNeedAPass( NULL ) + , m_pszNotPossible( NULL ) +{ + m_pContainer = new EditablePanel( this, "Container" ); + m_vecQuestItemPanels.SetSize( 2 ); + + FOR_EACH_VEC( m_vecQuestItemPanels, i ) + { + m_vecQuestItemPanels[ i ] = new CQuestItemPanel( m_pContainer, "QuestItemPanel", NULL, this ); + } +} + +CScrollableQuestList::~CScrollableQuestList() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CScrollableQuestList::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + BaseClass::ApplySchemeSettings( pScheme ); + + const char *pszResFile = "Resource/UI/econ/ScrollableQuestList.res"; + + // Check if the operation wants to override our default res file + const auto& mapOperations = GetItemSchema()->GetOperationDefinitions(); + FOR_EACH_MAP_FAST( mapOperations, i ) + { + CEconOperationDefinition* pCurrentOperation = mapOperations[i]; + // Take the first active operation's res file that's different than default + if ( pCurrentOperation->IsActive() && pCurrentOperation->GetQuestListOverrideResFile() ) + { + // Use the first found for now + pszResFile = pCurrentOperation->GetQuestListOverrideResFile(); + break; + } + } + + LoadControlSettings( pszResFile ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CScrollableQuestList::ApplySettings( KeyValues *inResourceData ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + BaseClass::ApplySettings( inResourceData ); + + m_pszNoQuests = inResourceData->GetString( "no_quests", "#QuestLog_NoQuests" ); + m_pszNeedAPass = inResourceData->GetString( "need_a_pass", "#QuestLog_NeedPassForContracts" ); + m_pszNotPossible = inResourceData->GetString( "not_possible", "#QuestLog_NoContractsPossible" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CScrollableQuestList::PerformLayout( void ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + BaseClass::PerformLayout(); + + m_pContainer->InvalidateLayout( true ); + + FOR_EACH_VEC( m_vecQuestItemPanels, i ) + { + m_vecQuestItemPanels[ i ]->InvalidateLayout( true, true ); + m_vecQuestItemPanels[ i ]->SetZPos( 1 + i ); + } + + PositionQuestItemPanels(); + + UpdateEmptyMessage(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CScrollableQuestList::OnThink() +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + if ( m_bQuestsLayoutDirty ) + { + m_bQuestsLayoutDirty = false; + + PositionQuestItemPanels(); + } + + // Conditionally turn mouse input on/off based on where the mouse is + FOR_EACH_VEC( m_vecQuestItemPanels, i ) + { + m_vecQuestItemPanels[i]->SetMouseInputEnabled( m_vecQuestItemPanels[i]->IsCursorOverMainContainer() ); + } +} + +void CScrollableQuestList::OnCommand( const char *command ) +{ + if ( FStrEq( command, "deselect_all" ) ) + { + SetSelected( NULL, false ); + } + + BaseClass::OnCommand( command ); +} + +int QuestSort_AcquiredTime( CQuestItemPanel* const* p1, CQuestItemPanel* const* p2 ) +{ + if ( !(*p1)->GetItem() ) + return -1; + + if ( !(*p2)->GetItem() ) + return 1; + + // Newest items first + return (*p1)->GetItem()->GetID() - (*p2)->GetItem()->GetID(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CScrollableQuestList::PositionQuestItemPanels() +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + // We dont do anything when a quest is completing + if ( m_pCompletingPanel != NULL ) + return; + + int nVisible = 0; + FOR_EACH_VEC( m_vecQuestItemPanels, i ) + { + CQuestItemPanel* pPanel = m_vecQuestItemPanels[i]; + if ( pPanel ) + { + pPanel->SetVisible( pPanel->GetItem() ); + + if ( pPanel->GetItem() ) + { + ++nVisible; + } + } + } + + CExLabel *pLabel = FindControl<CExLabel>( "EmptyLabel", true ); + if ( pLabel ) + { + pLabel->SetVisible( nVisible == 0 ); + } + + // Check for a selected panel + const CQuestItemPanel* pSelected = NULL; + FOR_EACH_VEC( m_vecQuestItemPanels, i ) + { + if ( m_vecQuestItemPanels[i]->IsSelected() ) + { + Assert( pSelected == NULL ); + pSelected = m_vecQuestItemPanels[i]; + } + } + + struct FolderCommands_t + { + const char* m_pszSelected; + const char* m_pszOtherIsSelected; + const char* m_pszNoneSelected; + }; + + const FolderCommands_t folderCommands[] = { { "QuestItem_Back_Selected", "QuestItem_Back_OtherSelected", "QuestItem_Back_NoneSelected" } + , { "QuestItem_Front_Selected", "QuestItem_Front_OtherSelected", "QuestItem_Front_NoneSelected" } }; + + // Update the positions + FOR_EACH_VEC( m_vecQuestItemPanels, i ) + { + // This is the selected panel + if ( pSelected == m_vecQuestItemPanels[ i ] ) + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_vecQuestItemPanels[ i ], folderCommands[i].m_pszSelected ); + } + else if ( pSelected ) // Some other panel is selected + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_vecQuestItemPanels[ i ], folderCommands[i].m_pszOtherIsSelected ); + } + else // No panel is selected + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_vecQuestItemPanels[ i ], folderCommands[i].m_pszNoneSelected ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CScrollableQuestList::SetSelected( CQuestItemPanel *pItem, bool bImmediately ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + FOR_EACH_VEC( m_vecQuestItemPanels, i ) + { + m_vecQuestItemPanels[ i ]->SetSelected( m_vecQuestItemPanels[ i ] == pItem, bImmediately ); + } + + PositionQuestItemPanels(); +} + +bool DoesLootlistDropQuests( const CEconLootListDefinition* pLootList ) +{ + FOR_EACH_VEC( pLootList->GetLootListContents(), j ) + { + const CEconLootListDefinition::drop_item_t& item = pLootList->GetLootListContents()[j]; + // 0 and greater means item. Less than 0 means nested lootlist + if( item.m_iItemOrLootlistDef >= 0 ) + { + const GameItemDefinition_t* pItemDef = assert_cast<const GameItemDefinition_t *>( GetItemSchema()->GetItemDefinition( item.m_iItemOrLootlistDef ) ); + if( pItemDef ) + { + return pItemDef->GetQuestDef(); + } + } + else + { + // Get the nested lootlist + int iLLIndex = (item.m_iItemOrLootlistDef * -1) - 1; + const CEconLootListDefinition *pNestedLootList = GetItemSchema()->GetLootListByIndex( iLLIndex ); + Assert( pNestedLootList ); + if ( !pNestedLootList ) + continue; + + // Dig through all of this lootlist's entries + return DoesLootlistDropQuests( pNestedLootList ); + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Update what message we show when we have no quests +//----------------------------------------------------------------------------- +void CScrollableQuestList::UpdateEmptyMessage() +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + + CCyclingAdContainerPanel* pPassStoreAd = FindControl< CCyclingAdContainerPanel >( "ItemAd", true ); + if ( !pPassStoreAd ) + return; + + pPassStoreAd->SetVisible( false ); + SetDialogVariable( "noquests", "" ); + + FOR_EACH_VEC( m_vecQuestItemPanels, i ) + { + // Case 1 above + if ( m_vecQuestItemPanels[ i ]->GetItem() ) + return; + } + + // By default, there's no operations going on, and there's no ad to show + const char *pszNoQuestsText = m_pszNotPossible; + + // Find any active quest-dropping operations + const auto& mapOperations = GetItemSchema()->GetOperationDefinitions(); + FOR_EACH_MAP_FAST( mapOperations, iOperation ) + { + CEconOperationDefinition *pOperation = mapOperations[ iOperation ]; + const CSchemaLootListDefHandle pOperationLootlist( pOperation->GetOperationLootlist() ); + // Must still be dropping, and be dropping quests + if ( CRTime::RTime32TimeCur() < pOperation->GetStopGivingToPlayerDate() && pOperationLootlist && DoesLootlistDropQuests( pOperationLootlist ) ) + { + // If there's a required item and a gateway item + if ( pOperation->GetRequiredItemDefIndex() != INVALID_ITEM_DEF_INDEX && pOperation->GetGatewayItemDefIndex() != INVALID_ITEM_DEF_INDEX ) + { + // And the user doesn't have the required item + if ( TFInventoryManager()->GetLocalTFInventory()->FindFirstItembyItemDef( pOperation->GetRequiredItemDefIndex() ) == NULL ) + { + // The user needs to get the item + pszNoQuestsText = m_pszNeedAPass; + + bool bStoreIsReady = EconUI()->GetStorePanel() && EconUI()->GetStorePanel()->GetPriceSheet() && EconUI()->GetStorePanel()->GetCart() && steamapicontext && steamapicontext->SteamUser(); + bool bGatewayItemInStore = false; + // Check if the gateway item is in the Mann Co Store + if ( bStoreIsReady ) + { + bGatewayItemInStore = EconUI()->GetStorePanel()->GetPriceSheet()->GetEntry( pOperation->GetGatewayItemDefIndex() ) != NULL; + } + + CEconItemDefinition* pGatewayItemDef = GetItemSchema()->GetItemDefinition( pOperation->GetGatewayItemDefIndex() ); + Assert( pGatewayItemDef ); + if ( !pGatewayItemDef ) + return; + + // Cook up KVs for this item ad + KeyValuesAD pKVItemAd( "items" ); // The panel will copy these + KeyValues* pKVItem = pKVItemAd->CreateNewKey(); + pKVItem->SetName( "0" ); + pKVItem->SetString( "item", pGatewayItemDef->GetDefinitionName() ); + pKVItem->SetInt( "show_market", bGatewayItemInStore ? 0 : 1 ); + + pPassStoreAd->SetVisible( true ); + pPassStoreAd->SetItemKVs( pKVItemAd ); + + // This is the most important thing to communicate. Don't let other operations stomp it. + break; + } + } + + pszNoQuestsText = m_pszNoQuests; + // Don't break. Give more important operations a chance to present + } + } + + SetDialogVariable( "noquests", g_pVGuiLocalize->Find( pszNoQuestsText ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CScrollableQuestList::PopulateQuestLists() +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + // We dont do anything when a quest is completing + if ( m_pCompletingPanel != NULL ) + return; + + DirtyQuestLayout(); + + CUtlVector< CEconItemView * > vecQuestItems; + TFInventoryManager()->GetAllQuestItems( &vecQuestItems ); + + CUtlVector< CQuestItemPanel* > vecAvailablePanels; + + FOR_EACH_VEC( m_vecQuestItemPanels, i ) + { + bool bFound = false; + if ( m_vecQuestItemPanels[ i ]->GetItem() ) + { + FOR_EACH_VEC_BACK( vecQuestItems, j ) + { + // See if the item in the panel is still in the list of items we own + if ( m_vecQuestItemPanels[ i ]->GetItem()->GetOriginalID() == vecQuestItems[ j ]->GetOriginalID() ) + { + // Refresh it + m_vecQuestItemPanels[ i ]->InvalidateLayout(); + vecQuestItems.Remove( j ); + bFound = true; + break; + } + } + } + + // Didn't find it. Clear it out + if ( !bFound ) + { + if ( m_vecQuestItemPanels[ i ]->GetItem() ) + { + m_vecQuestItemPanels[ i ]->SetItem( NULL ); + } + + if ( m_vecQuestItemPanels[ i ]->IsSelected() ) + { + SetSelected( m_vecQuestItemPanels[ i ], false ); + } + + vecAvailablePanels.AddToTail( m_vecQuestItemPanels[ i ] ); + } + } + + for ( int i = 0 ; i < vecQuestItems.Count(); ++i ) + { + CEconItemView *pItem = vecQuestItems[i]; + + if ( i < vecAvailablePanels.Count() ) + { + vecAvailablePanels[ i ]->SetItem( pItem ); + } + else if ( i >= m_vecQuestItemPanels.Count() ) + { + Assert( !"Ran out of quest panels!" ); + } + } + + // Sort the panels to make sure they're in order + m_vecQuestItemPanels.Sort( &QuestSort_AcquiredTime ); + + // Sorting is done manually + FOR_EACH_VEC( m_vecQuestItemPanels, i ) + { + m_vecQuestItemPanels[ i ]->SetZPos( i + 1 ); + } + + PositionQuestItemPanels(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CScrollableQuestList::QuestCompletedResponse() +{ + FOR_EACH_VEC( m_vecQuestItemPanels, i ) + { + m_vecQuestItemPanels[i]->QuestCompletedResponse(); + } + +// PopulateQuestLists(); +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if any quest item panels are in the passed in state +//----------------------------------------------------------------------------- +bool CScrollableQuestList::AnyQuestItemPanelsInState( CQuestItemPanel::EItemPanelState_t eState ) const +{ + FOR_EACH_VEC( m_vecQuestItemPanels, i ) + { + if ( m_vecQuestItemPanels[i]->GetState() == eState ) + return true; + } + + return false; +} + +//------------------------------------------------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CQuestLogPanel::CQuestLogPanel( IViewPort *pViewPort ) + : EditablePanel( NULL, PANEL_QUEST_LOG ) + , m_bWaitingForComplete( false ) + , m_pQuestList( NULL ) + , m_bInventoryDirty( true ) + , m_iQuestLogKey( BUTTON_CODE_INVALID ) +{ + if (g_pVGuiLocalize) + { + g_pVGuiLocalize->AddFile( "resource/tf_quests_%language%.txt" ); + } + + vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme"); + SetScheme(scheme); + SetProportional( true ); + + ListenForGameEvent( "inventory_updated" ); + ListenForGameEvent( "gameui_hidden" ); + ListenForGameEvent( "gc_connected" ); + + // Create the item model panel tooltip + m_pMouseOverItemPanel = new CItemModelPanel( this, "mouseoveritempanel" ); + m_pMouseOverTooltip = new CItemModelPanelToolTip( this ); + m_pMouseOverTooltip->SetupPanels( this, m_pMouseOverItemPanel ); + + // Create the text tooltip + m_pToolTip = new CQuestTooltip( this ); + m_pToolTipEmbeddedPanel = new vgui::EditablePanel( this, "TooltipPanel" ); + m_pToolTipEmbeddedPanel->SetKeyBoardInputEnabled( false ); + m_pToolTipEmbeddedPanel->SetMouseInputEnabled( false ); +// m_pToolTipEmbeddedPanel->MakePopup(); + m_pToolTip->SetEmbeddedPanel( m_pToolTipEmbeddedPanel ); + m_pToolTip->SetTooltipDelay( 0 ); + + EditablePanel *pMainContainer = new EditablePanel( this, "MainContainer" ); + m_pQuestList = new CScrollableQuestList( pMainContainer, "QuestList" ); + + m_pProgressPanel = new EditablePanel( this, "ProgressPanel" ); + + m_pDebugButton = new CExButton( pMainContainer, "Options", "Options", this, "open_debug_menu" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Look into the moused-over panel and take "tiptext" from its dialog +// variables and set it as our own. +//----------------------------------------------------------------------------- +void CQuestTooltip::ShowTooltip( Panel *pCurrentPanel ) +{ + EditablePanel* pEditableCurrentPanel = dynamic_cast< EditablePanel* >( pCurrentPanel ); + if ( pEditableCurrentPanel ) + { + KeyValues* pKVVariables = pEditableCurrentPanel->GetDialogVariables(); + const wchar_t *pwszTipText = pKVVariables->GetWString( "tiptext", L"" ); + m_pEmbeddedPanel->SetDialogVariable( "tiptext", pwszTipText ); + } + + BaseClass::ShowTooltip( pCurrentPanel ); +} + +//----------------------------------------------------------------------------- +// Purpose: Position ourselves down and to the right as far as posible +//----------------------------------------------------------------------------- +void CQuestTooltip::PositionWindow( Panel *pTipPanel ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + int iTipW, iTipH; + pTipPanel->GetSize( iTipW, iTipH ); + + int cursorX, cursorY; + vgui::input()->GetCursorPos(cursorX, cursorY); + + int px, py, wide, tall; + ipanel()->GetAbsPos( m_pEmbeddedPanel->GetParent()->GetVPanel(), px, py ); + m_pEmbeddedPanel->GetParent()->GetSize(wide, tall); + + if ( !m_pEmbeddedPanel->IsPopup() ) + { + // Move the cursor into our parent space + cursorX -= px; + cursorY -= py; + } + + // Dangle as far down and as far right as possible + int nXPos = cursorX - Max( 0, ( ( iTipW + cursorX ) - wide ) ); + int nYPos = ( cursorY + 20 )- Max( 0, ( ( iTipH + cursorY + 20 ) - tall ) ) ; + + pTipPanel->SetPos( nXPos, nYPos ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CQuestLogPanel::~CQuestLogPanel() +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::AttachToGameUI( void ) +{ + C_CTFGameStats::ImmediateWriteInterfaceEvent( "interface_open", "quest_log_panel" ); + + if ( GetClientModeTFNormal()->GameUI() ) + { + GetClientModeTFNormal()->GameUI()->SetMainMenuOverride( GetVPanel() ); + } + + SetKeyBoardInputEnabled( true ); + SetMouseInputEnabled( true ); + SetCursor(dc_arrow); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CQuestLogPanel::GetName( void ) +{ + return PANEL_QUEST_LOG; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::ApplySchemeSettings( IScheme *pScheme ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + BaseClass::ApplySchemeSettings( pScheme ); + + const char *pszResFile = "Resource/UI/econ/QuestLogPanel.res"; + + // Check if the operation wants to override our default res file + const auto& mapOperations = GetItemSchema()->GetOperationDefinitions(); + FOR_EACH_MAP_FAST( mapOperations, i ) + { + CEconOperationDefinition* pOperation = mapOperations[i]; + if ( pOperation->IsActive() && pOperation->IsCampaign() ) + { + // Use the first found for now + if ( pOperation->GetQuestLogOverrideResFile() ) + { + pszResFile = pOperation->GetQuestLogOverrideResFile(); + } + break; + } + } + + LoadControlSettings( pszResFile ); + + g_spItemTooltip = m_pMouseOverTooltip; + g_spTextTooltip = m_pToolTip; + + // The outer dim / close button + { + Button* pButton = FindControl<Button>( "OutsideCloseButton" ); + if ( pButton ) + { + pButton->AddActionSignalTarget( this ); + pButton->SetPaintBackgroundEnabled( false ); + } + } + + m_pQuestList->InvalidateLayout( false, true ); + + Assert( m_pQuestList ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::PerformLayout() +{ + BaseClass::PerformLayout(); + + if ( GetUniverse() != k_EUniversePublic ) + { + int x, y; + m_pDebugButton->GetParent()->GetPos( x, y ); + int w, h; + m_pDebugButton->GetParent()->GetSize( w, h ); + m_pDebugButton->SizeToContents(); + m_pDebugButton->SetVisible( true ); + m_pDebugButton->SetPos( x + w - m_pDebugButton->GetWide() - 60, y + 15 ); + m_pDebugButton->SetZPos( 1000 ); + } + else + { + m_pDebugButton->SetVisible( false ); + } + + UpdateQuestsItemPanels(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::OnCommand( const char *pCommand ) +{ + if ( FStrEq( pCommand, "close" ) ) + { + if ( enginevgui->IsGameUIVisible() ) + { + ShowPanel( false ); + } + else + { + IViewPortPanel *pQuestLog = ( gViewPortInterface->FindPanelByName( PANEL_QUEST_LOG ) ); + if ( pQuestLog ) + { + gViewPortInterface->ShowPanel( pQuestLog, false ); + } + } + } + else if ( Q_stricmp( "open_debug_menu", pCommand ) == 0 ) + { + if ( GetUniverse() == k_EUniverseBeta || GetUniverse() == k_EUniverseDev ) + { + const char *pszContextMenuBorder = "NotificationDefault"; + const char *pszContextMenuFont = "HudFontMediumSecondary"; + + Menu *pContextMenu = new Menu( this, "ContextMenu" ); + pContextMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) ); + pContextMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) ); + + + MenuBuilder contextMenuBuilder( pContextMenu, this ); + + const auto& mapOperations = GetItemSchema()->GetOperationDefinitions(); + FOR_EACH_MAP_FAST( mapOperations, iOperation ) + { + bool bHasAnyQuests = false; + Menu* pOperationSubMenu = NULL; + + CEconOperationDefinition *pOperation = mapOperations[ iOperation ]; + const CEconLootListDefinition *pLootListDef = GetItemSchema()->GetLootListByName( pOperation->GetOperationLootlist() ); + if ( pLootListDef ) + { + auto& vecContents = pLootListDef->GetLootListContents(); + FOR_EACH_VEC( vecContents, i ) + { + if ( vecContents[i].m_iItemOrLootlistDef > 0 ) + { + const GameItemDefinition_t* pItemDef = (GameItemDefinition_t*)GetItemSchema()->GetItemDefinition( vecContents[i].m_iItemOrLootlistDef ); + if ( pItemDef && pItemDef->GetQuestDef() ) + { + if ( !bHasAnyQuests ) + { + bHasAnyQuests = true; + + pOperationSubMenu = new Menu( this, "OperationSubMenu" ); + pOperationSubMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) ); + pOperationSubMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) ); + + contextMenuBuilder.AddCascadingMenuItem( pOperation->GetName(), pOperationSubMenu, "operations" ); + } + + pOperationSubMenu->AddMenuItem( pItemDef->GetItemBaseName(), CFmtStr( "give%s", pItemDef->GetDefinitionName() ), this ); + } + } + } + } + } + + bool bHasAnyQuests = false; + Menu* pAllSubMenu = NULL; + + FOR_EACH_MAP_FAST( GetItemSchema()->GetItemDefinitionMap(), i ) + { + const GameItemDefinition_t* pItemDef = (GameItemDefinition_t*)GetItemSchema()->GetItemDefinitionMap()[ i ]; + if ( pItemDef->GetQuestDef() ) + { + if ( !bHasAnyQuests ) + { + bHasAnyQuests = true; + + pAllSubMenu = new Menu( this, "AllSubMenu" ); + pAllSubMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) ); + pAllSubMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) ); + + contextMenuBuilder.AddCascadingMenuItem( "all", pAllSubMenu, "all" ); + } + + pAllSubMenu->AddMenuItem( pItemDef->GetItemBaseName(), CFmtStr( "give%s", pItemDef->GetDefinitionName() ), this ); + } + } + + // Position to the cursor's position + int nX, nY; + g_pVGuiInput->GetCursorPosition( nX, nY ); + pContextMenu->SetPos( nX - 1, nY - 1 ); + + pContextMenu->SetVisible(true); + pContextMenu->AddActionSignalTarget(this); + } + } + else if ( Q_strnicmp( "give", pCommand, 4 ) == 0 ) + { + if ( GetUniverse() != k_EUniversePublic ) + { + if ( !steamapicontext || !steamapicontext->SteamUser() ) + { + Msg("Not connected to Steam.\n"); + return; + } + + CSteamID steamIDForPlayer = steamapicontext->SteamUser()->GetSteamID(); + if ( !steamIDForPlayer.IsValid() ) + { + Msg("Failed to find a valid steamID for the local player.\n"); + return; + } + + const char* pszItemToGive = pCommand + 4; + Msg( "Sending request to generate '%s' for Local Player (%llu)\n", pszItemToGive, steamIDForPlayer.ConvertToUint64() ); + + CItemSelectionCriteria criteria; + + GCSDK::CProtoBufMsg<CMsgDevNewItemRequest> msg( k_EMsgGCDev_NewItemRequest ); + msg.Body().set_receiver( steamIDForPlayer.ConvertToUint64() ); + + criteria.SetIgnoreEnabledFlag( true ); + if ( !criteria.BAddCondition( "name", k_EOperator_String_EQ, pszItemToGive, true ) || + !criteria.BSerializeToMsg( *msg.Body().mutable_criteria() ) ) + { + Msg( "Failed to add condition and/or serialize item grant request. This is probably caused by having a string that's too long.\n" ); + return; + } + GCClientSystem()->BSendMessage( msg ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::FireGameEvent( IGameEvent *event ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + // Listen for inventory updates in case our item gets changed while the user + // is looking at us. We want to re-do our entire layout since a quest might + // have been equipped / destroyed / completed and we need to re-categorize + // all the user's quests. + if ( FStrEq( event->GetName(), "inventory_updated" ) && !m_bWaitingForComplete ) + { + m_bInventoryDirty = true; + + if ( IsVisible() ) + { + if ( m_pQuestList ) + { + m_pQuestList->PopulateQuestLists(); + m_pQuestList->UpdateEmptyMessage(); + } + + UpdateQuestsItemPanels(); + } + } + else if ( FStrEq( event->GetName(), "gameui_hidden" ) ) + { + ShowPanel( false ); + return; + } + else if ( FStrEq( event->GetName(), "gc_connected" ) ) + { + m_bInventoryDirty = true; + InvalidateLayout( false, true ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::ShowPanel( bool bShow ) +{ + // Snag this so we know what to listen for + m_iQuestLogKey = gameuifuncs->GetButtonCodeForBind( "show_quest_log" ); + + if ( m_pQuestList && bShow ) + { + m_pQuestList->SetSelected( NULL, true ); + m_pQuestList->DirtyQuestLayout(); + } + + SetVisible( bShow ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::OnKeyCodePressed( KeyCode code ) +{ + if ( code == m_iQuestLogKey || code == STEAMCONTROLLER_B ) + { + ShowPanel( false ); + return; + } + + BaseClass::OnKeyCodePressed( code ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::OnKeyCodeTyped( KeyCode code ) +{ + if ( code == KEY_ESCAPE ) + { + if ( IsVisible() ) + { + SetVisible( false ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::SetVisible( bool bState ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + // Default to showing the active quests upon opening + if ( bState == true ) + { + UpdateQuestsItemPanels(); + + IGameEvent *event = gameeventmanager->CreateEvent( "questlog_opened" ); + if ( event ) + { + gameeventmanager->FireEventClientSide( event ); + } + + if ( enginevgui->IsGameUIVisible() ) + { + AttachToGameUI(); + } + else + { + ipanel()->SetParent( GetVPanel(), VGui_GetClientDLLRootPanel() ); + + MakePopup( false, true ); + SetKeyBoardInputEnabled( true ); + SetMouseInputEnabled( true ); + MoveToFront(); + } + + engine->ClientCmd_Unrestricted( "gameui_preventescapetoshow\n" ); + vgui::surface()->PlaySound( "ui/panel_open.wav" ); + } + else if ( IsVisible() ) + { + // Detach from the GameUI when we hide + IViewPortPanel *pMMOverride = gViewPortInterface->FindPanelByName( PANEL_MAINMENUOVERRIDE ); + if ( pMMOverride ) + { + ((CHudMainMenuOverride*)pMMOverride)->AttachToGameUI(); + } + + engine->ClientCmd_Unrestricted( "gameui_allowescapetoshow\n" ); + vgui::surface()->PlaySound( "ui/panel_close.wav" ); + } + + BaseClass::SetVisible( bState ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::QuestCompletedResponse() +{ + m_bWaitingForComplete = false; + m_bInventoryDirty = true; + + if ( m_pQuestList ) + m_pQuestList->QuestCompletedResponse(); + + //UpdateQuestsItemPanels(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::UpdateQuestsItemPanels() +{ + UpdateBadgeProgressPanels(); + + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + if ( m_bInventoryDirty ) + { + m_pQuestList->QuestCompletedResponse(); + + if ( m_pQuestList ) + { + m_pQuestList->PopulateQuestLists(); + m_pQuestList->UpdateEmptyMessage(); + } + } + + m_bInventoryDirty = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::MarkQuestsDirty() +{ + m_bInventoryDirty = true; + + if ( IsVisible() ) + { + UpdateQuestsItemPanels(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if any quest item panels are in the passed in state +//----------------------------------------------------------------------------- +bool CQuestLogPanel::AnyQuestItemPanelsInState( CQuestItemPanel::EItemPanelState_t eState ) const +{ + return m_pQuestList->AnyQuestItemPanelsInState( eState ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::OnCompleteQuest( void ) +{ + m_bWaitingForComplete = true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::UpdateBadgeProgressPanels() +{ + CEconOperationDefinition *pCurrentOperation = NULL; + const auto& mapOperations = GetItemSchema()->GetOperationDefinitions(); + FOR_EACH_MAP_FAST( mapOperations, i ) + { + CEconOperationDefinition* pOperation = mapOperations[i]; + if ( pOperation->IsActive() && pOperation->IsCampaign() ) + { + pCurrentOperation = pOperation; + break; + } + } + + if ( pCurrentOperation ) + { + CEconItemView *pCoin = TFInventoryManager()->GetLocalTFInventory()->FindFirstItembyItemDef( pCurrentOperation->GetRequiredItemDefIndex() ); + if ( pCoin ) + { + m_pProgressPanel->SetVisible( true ); + CItemModelPanel *pCoinPanel = m_pProgressPanel->FindControl< CItemModelPanel >( "CoinModelPanel" ); + if ( pCoinPanel ) + { + pCoinPanel->SetItem( pCoin ); + } + + uint32 nNumCompletedContracts = 0; + { + uint32 nCompletedContractsTemp = 0; + GetKilleaterValueByEvent( pCoin, kKillEaterEvent_CosmeticOperationContractsCompleted, nCompletedContractsTemp ); + nNumCompletedContracts = Max( nNumCompletedContracts, nCompletedContractsTemp ); + // Halloween has it's own thing for whatever reason + GetKilleaterValueByEvent( pCoin, kKillEaterEvent_HalloweenContractsCompleted, nCompletedContractsTemp ); + nNumCompletedContracts = Max( nNumCompletedContracts, nCompletedContractsTemp ); + Assert( pCurrentOperation->GetMaxDropCount() > 0 ); + } + + uint32 nNumContractPoints = 0; + { + uint32 nContractsPointsTemp = 0; + GetKilleaterValueByEvent( pCoin, kKillEaterEvent_CosmeticOperationContractsPoints, nContractsPointsTemp ); + nNumContractPoints = Max( nContractsPointsTemp, nNumContractPoints ); + // Halloween has it's own thing for whatever reason + GetKilleaterValueByEvent( pCoin, kKillEaterEvent_HalloweenSouls, nContractsPointsTemp ); + nNumContractPoints = Max( nContractsPointsTemp, nNumContractPoints ); + } + + EditablePanel *pBadgeContainer = m_pProgressPanel->FindControl< EditablePanel >( "BadgeMeterContainer" ); + if ( pBadgeContainer ) + { + ContinuousProgressBar *pBadgeProgressBar = pBadgeContainer->FindControl< ContinuousProgressBar >( "BadgeProgressMeter" ); + if ( pBadgeProgressBar ) + { + const char *pszLevelingDataName = GetItemSchema()->GetKillEaterScoreTypeLevelingDataName( kKillEaterEvent_HalloweenSouls ); + Assert( pszLevelingDataName ); + + const CUtlVector<CItemLevelingDefinition> *pLevelingData = GetItemSchema()->GetItemLevelingData( pszLevelingDataName ); + Assert( pLevelingData ); + + int nRequiredPointsToNextRank = 0; + int nRequiredPointsToCurrentRank = 0; + const char *pszCurrentLevelName = NULL; + FOR_EACH_VEC( (*pLevelingData), i ) + { + pszCurrentLevelName = (*pLevelingData)[i].GetNameLocalizationKey(); + + const uint32 nRank = (*pLevelingData)[i].GetRequiredScore(); + if ( nNumContractPoints < nRank ) + { + nRequiredPointsToNextRank = nRank; + break; + } + else + { + nRequiredPointsToCurrentRank = nRank; + } + } + + // if no next level, just use max points + if ( nRequiredPointsToNextRank == 0 ) + { + // assuming each contract's worth 130 (100 point + 30 bonus) + nRequiredPointsToNextRank = pCurrentOperation->GetMaxDropCount() * 130; + } + + float flProgress = (float)( nNumContractPoints - nRequiredPointsToCurrentRank ) / (float)( nRequiredPointsToNextRank - nRequiredPointsToCurrentRank ); + pBadgeProgressBar->SetProgress( flProgress ); + + CExLabel *pLabel = m_pProgressPanel->FindControl< CExLabel >( "BadgeProgressLabel" ); + if ( pLabel ) + { + pLabel->SetText( CConstructLocalizedString( g_pVGuiLocalize->Find( "QuestLog_BadgeProgress" ), g_pVGuiLocalize->Find( pszCurrentLevelName ) ) ); + } + + CExLabel *pScoreLabel = pBadgeContainer->FindControl< CExLabel >( "BadgeProgressMeterText" ); + if ( pScoreLabel ) + { + pScoreLabel->SetText( CFmtStr( "%d/%d", nNumContractPoints, nRequiredPointsToNextRank ) ); + } + } + } + + EditablePanel *pContractContainer = m_pProgressPanel->FindControl< EditablePanel >( "ContractMeterContainer" ); + if ( pContractContainer ) + { + ContinuousProgressBar *pContractProgressBar = pContractContainer->FindControl< ContinuousProgressBar >( "ContractsCompletedProgressMeter" ); + if ( pContractProgressBar ) + { + pContractProgressBar->SetProgress( (float)nNumCompletedContracts / (float)pCurrentOperation->GetMaxDropCount() ); + + CExLabel *pLabel = pContractContainer->FindControl< CExLabel >( "ContractsCompletedProgressMeterText" ); + if ( pLabel ) + { + pLabel->SetText( CFmtStr( "%d", nNumCompletedContracts ) ); + } + } + } + } + else + { + m_pProgressPanel->SetVisible( false ); + } + } + else + { + m_pProgressPanel->SetVisible( false ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: GC Msg handler for when a quest has been completed +//----------------------------------------------------------------------------- +class CGCCompleteQuestCompleteResponse : public GCSDK::CGCClientJob +{ +public: + CGCCompleteQuestCompleteResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket ); + + itemid_t nNewToolID = 0; + if( !msg.BReadUint64Data( &nNewToolID ) ) + return true; + + CQuestLogPanel *pQuestLog = GetQuestLog(); + if ( pQuestLog ) + { + pQuestLog->QuestCompletedResponse(); + } + + return true; + } +}; + +GC_REG_JOB( GCSDK::CGCClient, CGCCompleteQuestCompleteResponse, "CGCCompleteQuestCompleteResponse", k_EMsgGCQuestCompleted, GCSDK::k_EServerTypeGCClient ); + + +#ifdef STAGING_ONLY +static void cc_tf_quest_log_reload() +{ + CQuestLogPanel *pQuestLog = GetQuestLog(); + if ( pQuestLog ) + { + pQuestLog->MarkQuestsDirty(); + pQuestLog->InvalidateLayout( true, true ); + gViewPortInterface->ShowPanel( pQuestLog, true ); + } +} +ConCommand tf_quest_log_reload( "tf_quest_log_reload", cc_tf_quest_log_reload ); +#endif diff --git a/game/client/tf/vgui/quest_log_panel.h b/game/client/tf/vgui/quest_log_panel.h new file mode 100644 index 0000000..1c8fa39 --- /dev/null +++ b/game/client/tf/vgui/quest_log_panel.h @@ -0,0 +1,132 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef QUEST_LOG_PANEL_H +#define QUEST_LOG_PANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "vgui_controls/EditablePanel.h" +#include <game/client/iviewport.h> +#include "quest_item_panel.h" + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Creates the quest log if it doesnt exists, and gives you a pointer to it +//----------------------------------------------------------------------------- +class CQuestLogPanel *GetQuestLog(); + +//----------------------------------------------------------------------------- +// A scrollable list of quest items +//----------------------------------------------------------------------------- +class CScrollableQuestList : public EditablePanel +{ + DECLARE_CLASS_SIMPLE( CScrollableQuestList, EditablePanel ); +public: + CScrollableQuestList( Panel *parent, const char *pszPanelName ); + virtual ~CScrollableQuestList(); + + virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE; + virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE; + virtual void PerformLayout( void ) OVERRIDE; + virtual void OnThink() OVERRIDE; + virtual void OnCommand( const char *command ) OVERRIDE; + + void DirtyQuestLayout() { m_bQuestsLayoutDirty = true; } + void PopulateQuestLists(); + void QuestCompletedResponse(); + bool AnyQuestItemPanelsInState( CQuestItemPanel::EItemPanelState_t eState ) const; + void PositionQuestItemPanels(); + + void SetSelected( CQuestItemPanel *pItem, bool bImmediately ); + void SetCompletingPanel( const CQuestItemPanel *pItem ) { m_pCompletingPanel = pItem; } + const CQuestItemPanel *GetCompletingPanel() const { return m_pCompletingPanel; } + void UpdateEmptyMessage(); + +protected: + + bool m_bQuestsLayoutDirty; + EditablePanel *m_pContainer; + CUtlVector< CQuestItemPanel* > m_vecQuestItemPanels; + CQuestItemPanel* m_spCompletingPanel; + const CQuestItemPanel* m_pCompletingPanel; + + CUtlString m_pszNoQuests; + CUtlString m_pszNeedAPass; + CUtlString m_pszNotPossible; + + CPanelAnimationVarAliasType( int, m_iEntryStep, "entry_step", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iEntryStartingX, "entry_x", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iEntryStartingY, "entry_y", "0", "proportional_int" ); +}; + +//----------------------------------------------------------------------------- +// The default quest log panel +//----------------------------------------------------------------------------- +class CQuestLogPanel : public EditablePanel, public IViewPortPanel, public CGameEventListener +{ + DECLARE_CLASS_SIMPLE( CQuestLogPanel, EditablePanel ); +public: + CQuestLogPanel( IViewPort *pViewPort ); + virtual ~CQuestLogPanel(); + + void AttachToGameUI(); + virtual const char *GetName( void ) OVERRIDE; + virtual void SetData( KeyValues *data ) OVERRIDE {} + virtual void Reset() OVERRIDE { Update(); SetVisible( true ); } + virtual void Update() OVERRIDE { return; } + virtual bool NeedsUpdate( void ) OVERRIDE { return false; } + virtual bool HasInputElements( void ) OVERRIDE { return true; } + virtual void ShowPanel( bool bShow ) OVERRIDE; + + // both vgui::Frame and IViewPortPanel define these, so explicitly define them here as passthroughs to vgui + vgui::VPANEL GetVPanel( void ){ return BaseClass::GetVPanel(); } + virtual bool IsVisible() OVERRIDE { return BaseClass::IsVisible(); } + virtual void SetParent( vgui::VPANEL parent ) OVERRIDE { BaseClass::SetParent( parent ); } + + virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE; + virtual void PerformLayout() OVERRIDE; + virtual void OnCommand( const char *pCommand ) OVERRIDE; + virtual void FireGameEvent( IGameEvent *event ) OVERRIDE; + virtual void SetVisible( bool bState ) OVERRIDE; + virtual void OnKeyCodePressed( KeyCode code ) OVERRIDE; + virtual void OnKeyCodeTyped(KeyCode code) OVERRIDE; + + virtual GameActionSet_t GetPreferredActionSet() { return GAME_ACTION_SET_NONE; } + + void QuestCompletedResponse(); + void UpdateQuestsItemPanels(); + void MarkQuestsDirty(); + + void UpdateBadgeProgressPanels(); + + bool AnyQuestItemPanelsInState( CQuestItemPanel::EItemPanelState_t eState ) const; + + MESSAGE_FUNC( OnCompleteQuest, "CompleteQuest" ); + +private: + + CScrollableQuestList *m_pQuestList; + + class CItemModelPanel *m_pMouseOverItemPanel; + class CItemModelPanelToolTip *m_pMouseOverTooltip; + + EditablePanel *m_pProgressPanel; + + class CQuestTooltip *m_pToolTip; + EditablePanel *m_pToolTipEmbeddedPanel; + + ButtonCode_t m_iQuestLogKey; + bool m_bWaitingForComplete; + bool m_bInventoryDirty; + + Button *m_pDebugButton; +}; + +#endif // QUEST_LOG_PANEL_H diff --git a/game/client/tf/vgui/quest_notification_panel.cpp b/game/client/tf/vgui/quest_notification_panel.cpp new file mode 100644 index 0000000..c3c03aa --- /dev/null +++ b/game/client/tf/vgui/quest_notification_panel.cpp @@ -0,0 +1,533 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "quest_notification_panel.h" +#include "vgui/ISurface.h" +#include "ienginevgui.h" +#include "hudelement.h" +#include "iclientmode.h" +#include "basemodel_panel.h" +#include "tf_item_inventory.h" +#include "quest_log_panel.h" +#include "econ_controls.h" +#include "c_tf_player.h" +#include <vgui_controls/AnimationController.h> +#include "engine/IEngineSound.h" +#include "econ_item_system.h" +#include "tf_hud_item_progress_tracker.h" +#include "tf_spectatorgui.h" +#include "econ_quests.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +ConVar tf_quest_notification_line_delay( "tf_quest_notification_line_delay", "1.2", FCVAR_ARCHIVE ); + +extern ISoundEmitterSystemBase *soundemitterbase; +CQuestNotificationPanel *g_pQuestNotificationPanel = NULL; + +DECLARE_HUDELEMENT( CQuestNotificationPanel ); + +CQuestNotification::CQuestNotification( CEconItem *pItem ) + : m_hItem( pItem ) +{} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CQuestNotification::Present( CQuestNotificationPanel* pNotificationPanel ) +{ + m_timerDialog.Start( tf_quest_notification_line_delay.GetFloat() ); + + return 0.f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CQuestNotification_Speaking::CQuestNotification_Speaking( CEconItem *pItem ) + : CQuestNotification( pItem ) +{ + m_pszSoundToSpeak = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CQuestNotification_Speaking::Present( CQuestNotificationPanel* pNotificationPanel ) +{ + CQuestNotification::Present( pNotificationPanel ); + + if ( m_hItem ) + { + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if ( !pPlayer ) + return 0.f; + + CTFPlayer* pTFPlayer = ToTFPlayer( pPlayer ); + if ( !pTFPlayer ) + return 0.f; + + const GameItemDefinition_t *pItemDef = m_hItem->GetItemDefinition(); + // Get our quest theme + const CQuestThemeDefinition *pTheme = pItemDef->GetQuestDef()->GetQuestTheme(); + if ( pTheme ) + { + // Get the sound we need to speak + m_pszSoundToSpeak = GetSoundEntry( pTheme, pTFPlayer->GetPlayerClass()->GetClassIndex() ); + float flPresentTime = 0.f; + if ( m_pszSoundToSpeak ) + { + flPresentTime = enginesound->GetSoundDuration( m_pszSoundToSpeak ) + m_timerDialog.GetCountdownDuration() + 1.f; + m_timerShow.Start( enginesound->GetSoundDuration( m_pszSoundToSpeak ) + m_timerDialog.GetCountdownDuration() + 1.f ); + } + + return flPresentTime; + } + } + + return 0.f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestNotification_Speaking::Update( CQuestNotificationPanel* pNotificationPanel ) +{ + if ( m_timerDialog.IsElapsed() && m_timerDialog.HasStarted() && m_hItem ) + { + m_timerDialog.Invalidate(); + + // Play it! + if ( m_pszSoundToSpeak ) + { + vgui::surface()->PlaySound( m_pszSoundToSpeak ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CQuestNotification_Speaking::IsDone() const +{ + return m_timerShow.IsElapsed() && m_timerShow.HasStarted(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CQuestNotification_NewQuest::GetSoundEntry( const CQuestThemeDefinition* pTheme, int nClassIndex ) +{ + return pTheme->GetGiveSoundForClass( nClassIndex ); +} + +bool CQuestNotification_NewQuest::ShouldPresent() const +{ + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if ( !pPlayer ) + return false; + + CTFPlayer* pTFPlayer = ToTFPlayer( pPlayer ); + if ( !pTFPlayer ) + return false; + + IViewPortPanel* pSpecGuiPanel = gViewPortInterface->FindPanelByName( PANEL_SPECGUI ); + if ( !pTFPlayer->IsAlive() ) + { + if ( !pSpecGuiPanel || !pSpecGuiPanel->IsVisible() ) + return false; + } + else + { + // Local player is in a spawn room + if ( pTFPlayer->m_Shared.GetRespawnTouchCount() <= 0 ) + return false; + } + + return true; +} + +CQuestNotification_CompletedQuest::CQuestNotification_CompletedQuest( CEconItem *pItem ) + : CQuestNotification_Speaking( pItem ) +{ + const char *pszSoundName = UTIL_GetRandomSoundFromEntry( "Quest.StatusTickComplete" ); + m_PresentTimer.Start( enginesound->GetSoundDuration( pszSoundName ) - 2.f ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CQuestNotification_CompletedQuest::GetSoundEntry( const CQuestThemeDefinition* pTheme, int nClassIndex ) +{ + return pTheme->GetCompleteSoundForClass( nClassIndex ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CQuestNotification_CompletedQuest::ShouldPresent() const +{ + return m_PresentTimer.IsElapsed() && m_PresentTimer.HasStarted(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CQuestNotification_FullyCompletedQuest::GetSoundEntry( const CQuestThemeDefinition* pTheme, int nClassIndex ) +{ + return pTheme->GetFullyCompleteSoundForClass( nClassIndex ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CQuestNotificationPanel::CQuestNotificationPanel( const char *pszElementName ) + : CHudElement( pszElementName ) + , EditablePanel( NULL, "QuestNotificationPanel" ) + , m_flTimeSinceLastShown( 0.f ) + , m_bIsPresenting( false ) + , m_mapNotifiedItemIDs( DefLessFunc( itemid_t ) ) + , m_bInitialized( false ) + , m_pMainContainer( NULL ) +{ + Panel *pParent = g_pClientMode->GetViewport(); + SetParent( pParent ); + + g_pQuestNotificationPanel = this; + + ListenForGameEvent( "player_death" ); + ListenForGameEvent( "inventory_updated" ); + ListenForGameEvent( "player_initial_spawn" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CQuestNotificationPanel::~CQuestNotificationPanel() +{} + + + + +void CQuestNotificationPanel::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + // Default, load pauling + LoadControlSettings( "Resource/UI/econ/QuestNotificationPanel_Pauling_standard.res" ); + + m_pMainContainer = FindControl< EditablePanel >( "MainContainer", true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestNotificationPanel::PerformLayout() +{ + BaseClass::PerformLayout(); + + CExLabel* pNewQuestLabel = FindControl< CExLabel >( "NewQuestText", true ); + if ( pNewQuestLabel ) + { + const wchar_t *pszText = NULL; + const char *pszTextKey = "#QuestNotification_Accept"; + if ( pszTextKey ) + { + pszText = g_pVGuiLocalize->Find( pszTextKey ); + } + if ( pszText ) + { + wchar_t wzFinal[512] = L""; + UTIL_ReplaceKeyBindings( pszText, 0, wzFinal, sizeof( wzFinal ) ); + pNewQuestLabel->SetText( wzFinal ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestNotificationPanel::FireGameEvent( IGameEvent * event ) +{ + const char *pszName = event->GetName(); + + if ( FStrEq( pszName, "inventory_updated" ) || FStrEq( pszName, "player_death" ) ) + { + CheckForNotificationOpportunities(); + } + else if ( FStrEq( pszName, "player_initial_spawn" ) ) + { + CTFPlayer *pNewPlayer = ToTFPlayer( UTIL_PlayerByIndex( event->GetInt( "index" ) ) ); + if ( pNewPlayer == C_BasePlayer::GetLocalPlayer() ) + { + // Reset every round + m_mapNotifiedItemIDs.Purge(); + m_vecNotifications.PurgeAndDeleteElements(); + m_timerNotificationCooldown.Start( 0 ); + m_bInitialized = false; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestNotificationPanel::Reset() +{ + CheckForNotificationOpportunities(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestNotificationPanel::CheckForNotificationOpportunities() +{ + // Suppress making new notifications while in competitive play + if ( TFGameRules() && TFGameRules()->IsCompetitiveMode() ) + return; + + FOR_EACH_VEC_BACK( m_vecNotifications, i ) + { + // Clean up old entires for items that are now gone + if ( m_vecNotifications[i]->GetItemHandle() == NULL ) + { + delete m_vecNotifications[i]; + m_vecNotifications.Remove( i ); + } + } + + CPlayerInventory *pInv = InventoryManager()->GetLocalInventory(); + Assert( pInv ); + if ( pInv ) + { + for ( int i = 0 ; i < pInv->GetItemCount(); ++i ) + { + CEconItemView *pItem = pInv->GetItem( i ); + + // Check if this is a quest at all + if ( pItem->GetItemDefinition()->GetQuestDef() == NULL ) + continue; + + CQuestNotification* pNotification = NULL; + if ( IsUnacknowledged( pItem->GetInventoryPosition() ) ) + { + pNotification = new CQuestNotification_NewQuest( pItem->GetSOCData() ); + } + else if ( IsQuestItemFullyCompleted( pItem ) ) // Fully completed + { + pNotification = new CQuestNotification_FullyCompletedQuest( pItem->GetSOCData() ); + } + else if ( IsQuestItemReadyToTurnIn( pItem ) ) // Ready to turn in + { + pNotification = new CQuestNotification_CompletedQuest( pItem->GetSOCData() ); + } + else + { + // Clean up any pending notifications for normal quests + FOR_EACH_VEC_BACK( m_vecNotifications, j ) + { + if ( m_vecNotifications[j]->GetItemHandle() == pItem->GetSOCData() ) + { + delete m_vecNotifications[j]; + m_vecNotifications.Remove( j ); + } + } + } + + if ( pNotification && !AddNotificationForItem( pItem, pNotification ) ) + { + delete pNotification; + pNotification = NULL; + } + } + + m_bInitialized = pInv->GetOwner().IsValid(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CQuestNotificationPanel::AddNotificationForItem( const CEconItemView *pItem, CQuestNotification* pNotification ) +{ + bool bTypeAlreadyInQueue = false; + // Check if there's already a notification of this type + FOR_EACH_VEC_BACK( m_vecNotifications, i ) + { + // There's already a quest of this type in queue, no need to add another + if ( m_vecNotifications[i]->GetType() == pNotification->GetType() ) + { + bTypeAlreadyInQueue = true; + break; + } + } + + // Find the notified bits + auto idx = m_mapNotifiedItemIDs.Find( pItem->GetItemID() ); + if ( idx == m_mapNotifiedItemIDs.InvalidIndex() ) + { + // Create if missing + idx = m_mapNotifiedItemIDs.Insert( pItem->GetItemID() ); + m_mapNotifiedItemIDs[ idx ].SetSize( CQuestNotification::NUM_NOTIFICATION_TYPES ); + FOR_EACH_VEC( m_mapNotifiedItemIDs[ idx ], i ) + { + m_mapNotifiedItemIDs[ idx ][ i ] = 0.f; + } + } + + // Check if we've already done a notification for this type recently + if ( Plat_FloatTime() < m_mapNotifiedItemIDs[ idx ][ pNotification->GetType() ] || m_mapNotifiedItemIDs[ idx ][ pNotification->GetType() ] == NEVER_REPEAT ) + { + return false; + } + + bool bNotificationUsed = false; + // Don't play completed notifications unless they happen mid-play + if ( !bTypeAlreadyInQueue && ( m_bInitialized || pNotification->GetType() == CQuestNotification::NOTIFICATION_TYPE_NEW_QUEST ) ) + { + // Add notification + m_vecNotifications.AddToTail( pNotification ); + bNotificationUsed = true; + } + + // Mark that we've created a notification of this type for this item + m_mapNotifiedItemIDs[ idx ][ pNotification->GetType() ] = pNotification->GetReplayTime() == NEVER_REPEAT ? NEVER_REPEAT : Plat_FloatTime() + pNotification->GetReplayTime(); + + return bNotificationUsed; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CQuestNotificationPanel::ShouldDraw() +{ + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if ( !pPlayer ) + return false; + + CTFPlayer* pTFPlayer = ToTFPlayer( pPlayer ); + if ( !pTFPlayer ) + return false; + + // Not selected a class, so they haven't joined in + if ( pTFPlayer->IsPlayerClass( 0 ) ) + return false; + + if ( !CHudElement::ShouldDraw() ) + return false; + + if ( TFGameRules() && TFGameRules()->IsCompetitiveMode() ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestNotificationPanel::OnThink() +{ + if ( !ShouldDraw() ) + return; + + bool bHasStarted = m_animTimer.HasStarted(); + float flShowProgress = bHasStarted ? 1.f : 0.f; + const float flTransitionTime = 0.5f; + + Update(); + + if ( bHasStarted ) + { + // Transitions + if ( m_animTimer.GetElapsedTime() < flTransitionTime ) + { + flShowProgress = Bias( m_animTimer.GetElapsedTime() / flTransitionTime, 0.75f ); + } + else if ( ( m_animTimer.GetRemainingTime() + 1.f ) < flTransitionTime ) + { + flShowProgress = Bias( Max( 0.0f, m_animTimer.GetRemainingTime() + 1.f ) / flTransitionTime, 0.25f ); + } + } + + // Move the main container around + if ( m_pMainContainer ) + { + int nY = g_pSpectatorGUI && g_pSpectatorGUI->IsVisible() ? g_pSpectatorGUI->GetTopBarHeight() : 0; + + float flXPos = RemapValClamped( flShowProgress, 0.f, 1.f, 0.f, m_pMainContainer->GetWide() + XRES( 4 ) ); + m_pMainContainer->SetPos( GetWide() - (int)flXPos, nY ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CQuestNotificationPanel::ShouldPresent() +{ + if ( !m_timerNotificationCooldown.IsElapsed() ) + return false; + + // We need notifications! + if ( m_vecNotifications.IsEmpty() ) + return false; + + // It's been a few seconds since we were last shown + if ( ( Plat_FloatTime() - m_flTimeSinceLastShown ) < 1.5f ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestNotificationPanel::Update() +{ + bool bAllowedToShow = ShouldPresent(); + + if ( bAllowedToShow && !m_bIsPresenting ) + { + if ( m_vecNotifications.Head()->ShouldPresent() ) + { + float flPresentTime = m_vecNotifications.Head()->Present( this ); + m_animTimer.Start( flPresentTime ); + + m_timerHoldUp.Start( 3.f ); + + // Notification sound + vgui::surface()->PlaySound( "ui/quest_alert.wav" ); + m_bIsPresenting = true; + } + } + else if ( !bAllowedToShow && m_bIsPresenting && m_timerHoldUp.IsElapsed() ) + { + m_flTimeSinceLastShown = Plat_FloatTime(); + // Play the slide-out animation + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "QuestNotification_Hide" ); + m_bIsPresenting = false; + } + else if ( m_bIsPresenting ) // We are presenting a notification + { + if ( m_vecNotifications.Count() ) + { + m_vecNotifications.Head()->Update( this ); + // Check if the notification is done + if ( m_vecNotifications.Head()->IsDone() ) + { + // Start our cooldown + m_timerNotificationCooldown.Start( 1.f ); + // We're done with this notification + delete m_vecNotifications.Head(); + m_vecNotifications.Remove( 0 ); + } + } + } +} diff --git a/game/client/tf/vgui/quest_notification_panel.h b/game/client/tf/vgui/quest_notification_panel.h new file mode 100644 index 0000000..1fcdb38 --- /dev/null +++ b/game/client/tf/vgui/quest_notification_panel.h @@ -0,0 +1,180 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef QUEST_NOTIFICATION_PANEL_H +#define QUEST_NOTIFICATION_PANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include <vgui/VGUI.h> +#include "hudelement.h" +#include "vgui_controls/EditablePanel.h" +#include <../common/GameUI/cvarslider.h> +#include "vgui_controls/CheckButton.h" +#include "vgui_controls/ScrollableEditablePanel.h" +#include "econ_item_inventory.h" + +using namespace vgui; +#define NEVER_REPEAT -1.f + +class CQuestNotificationPanel; + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +class CQuestNotification +{ +public: + CQuestNotification( CEconItem *pItem ); + + enum ENotificationType_t + { + NOTIFICATION_TYPE_NEW_QUEST = 0, + NOTIFICATION_TYPE_COMPLETED, + NOTIFICATION_TYPE_FULLY_COMPLETED, + + NUM_NOTIFICATION_TYPES + }; + + virtual ~CQuestNotification() {} + + virtual float Present( CQuestNotificationPanel* pNotificationPanel ); + virtual void Update( CQuestNotificationPanel* pNotificationPanel ) = 0; + virtual bool IsDone() const = 0; + virtual bool ShouldPresent() const = 0; + virtual ENotificationType_t GetType() const = 0; + virtual float GetReplayTime() const = 0; + + CEconItemHandle& GetItemHandle() { return m_hItem; } + +protected: + CEconItemHandle m_hItem; + RealTimeCountdownTimer m_timerDialog; + RealTimeCountdownTimer m_timerShow; +}; + +//----------------------------------------------------------------------------- +// Notifications where we'll speak +//----------------------------------------------------------------------------- +class CQuestNotification_Speaking : public CQuestNotification +{ +public: + + CQuestNotification_Speaking( CEconItem *pItem ); + virtual ~CQuestNotification_Speaking() {} + + virtual float Present( CQuestNotificationPanel* pNotificationPanel ) OVERRIDE; + virtual void Update( CQuestNotificationPanel* pNotificationPanel ) OVERRIDE; + virtual bool IsDone() const OVERRIDE; + +protected: + virtual const char *GetSoundEntry( const CQuestThemeDefinition* pTheme, int nClassIndex ) = 0; + + const char *m_pszSoundToSpeak; +}; + +//----------------------------------------------------------------------------- +// New quest notification +//----------------------------------------------------------------------------- +class CQuestNotification_NewQuest : public CQuestNotification_Speaking +{ + DECLARE_CLASS_SIMPLE( CQuestNotification_NewQuest, CQuestNotification_Speaking ); +public: + CQuestNotification_NewQuest( CEconItem *pItem ) + : CQuestNotification_Speaking( pItem ) + {} + + virtual ~CQuestNotification_NewQuest() {} + + virtual bool ShouldPresent() const OVERRIDE; + ENotificationType_t GetType() const { return NOTIFICATION_TYPE_NEW_QUEST; } + virtual float GetReplayTime() const { return 300.f; } + static float k_flReplayTime; + +protected: + virtual const char *GetSoundEntry( const CQuestThemeDefinition* pTheme, int nClassIndex ) OVERRIDE; + + static CUtlVector< itemid_t > m_vecNotifiedItemIDs; +}; + +//----------------------------------------------------------------------------- +// Quest complete notification +//----------------------------------------------------------------------------- +class CQuestNotification_CompletedQuest : public CQuestNotification_Speaking +{ + DECLARE_CLASS_SIMPLE( CQuestNotification_CompletedQuest, CQuestNotification_Speaking ); +public: + CQuestNotification_CompletedQuest( CEconItem *pItem ); + + virtual ~CQuestNotification_CompletedQuest() {} + + virtual bool ShouldPresent() const; + ENotificationType_t GetType() const { return NOTIFICATION_TYPE_COMPLETED; } + virtual float GetReplayTime() const { return NEVER_REPEAT; } + +protected: + virtual const char *GetSoundEntry( const CQuestThemeDefinition* pTheme, int nClassIndex ) OVERRIDE; + + RealTimeCountdownTimer m_PresentTimer; +}; + +class CQuestNotification_FullyCompletedQuest : public CQuestNotification_CompletedQuest +{ + DECLARE_CLASS_SIMPLE( CQuestNotification_FullyCompletedQuest, CQuestNotification_CompletedQuest ); +public: + CQuestNotification_FullyCompletedQuest( CEconItem *pItem ) : CQuestNotification_CompletedQuest( pItem ) + { + } + + virtual ~CQuestNotification_FullyCompletedQuest() {} + + ENotificationType_t GetType() const { return NOTIFICATION_TYPE_FULLY_COMPLETED; } +protected: + virtual const char *GetSoundEntry( const CQuestThemeDefinition* pTheme, int nClassIndex ) OVERRIDE; +}; + +//----------------------------------------------------------------------------- +// The quest notification panel where a character tells the user about quest state +//----------------------------------------------------------------------------- +class CQuestNotificationPanel : public CHudElement, public EditablePanel +{ + DECLARE_CLASS_SIMPLE( CQuestNotificationPanel, EditablePanel ); +public: + CQuestNotificationPanel( const char *pszElementName ); + virtual ~CQuestNotificationPanel(); + + virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE; + virtual void PerformLayout() OVERRIDE; + virtual void FireGameEvent( IGameEvent * event ) OVERRIDE; + virtual void Reset() OVERRIDE; + virtual bool ShouldDraw() OVERRIDE; + virtual void OnThink() OVERRIDE; +private: + + bool ShouldPresent(); + + void Update(); + void CheckForNotificationOpportunities(); + + bool AddNotificationForItem( const CEconItemView *pItem, CQuestNotification* pNotification ); + void SetCharacterImage( const char *pszImageName ); + + CUtlVector< CQuestNotification* > m_vecNotifications; + + float m_flTimeSinceLastShown; + bool m_bIsPresenting; + RealTimeCountdownTimer m_timerHoldUp; + RealTimeCountdownTimer m_timerNotificationCooldown; + RealTimeCountdownTimer m_animTimer; + EditablePanel *m_pMainContainer; + bool m_bInitialized; + + CUtlMap< itemid_t, CCopyableUtlVector< float > > m_mapNotifiedItemIDs; +}; + +#endif // QUEST_NOTIFICATION_PANEL_H diff --git a/game/client/tf/vgui/report_player_dialog.cpp b/game/client/tf/vgui/report_player_dialog.cpp new file mode 100644 index 0000000..62eb9b8 --- /dev/null +++ b/game/client/tf/vgui/report_player_dialog.cpp @@ -0,0 +1,305 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "report_player_dialog.h" +#include "gc_clientsystem.h" +#include "ienginevgui.h" + +using namespace vgui; + +// in seconds +static const float MIN_REPORT_INTERVAL = 300.f; + +struct ReportedPlayer_t +{ + CSteamID steamID; + float flReportedTime; +}; +CUtlVector< ReportedPlayer_t > vecReportedPlayers; + +bool CanReportPlayer( CSteamID steamID, bool bVerbose ) +{ + bool bCanReport = true; + for (int i = 0; i < vecReportedPlayers.Count(); ++i) + { + if ( vecReportedPlayers[i].steamID == steamID ) + { + float flTimeSinceLastReported = gpGlobals->curtime - vecReportedPlayers[i].flReportedTime; + bCanReport = flTimeSinceLastReported >= MIN_REPORT_INTERVAL; + if ( !bCanReport && bVerbose ) + { + float flCooldownTime = MIN_REPORT_INTERVAL - flTimeSinceLastReported; + ConMsg( "Already reported this player. You can report this player again in %.2f seconds\n", flCooldownTime ); + } + break; + } + } + + return bCanReport; +} + +bool ReportPlayerAccount( CSteamID steamID, int nReason ) +{ + if ( !steamID.IsValid() ) + { + Warning( "Reporting an invalid steam ID\n" ); + return false; + } + + if ( !CanReportPlayer( steamID, true ) ) + { + return false; + } + + if ( nReason <= CMsgGC_ReportPlayer_EReason_kReason_INVALID || nReason >= CMsgGC_ReportPlayer_EReason_kReason_COUNT ) + { + Assert( !"Invalid report reason" ); + return false; + } + + GCSDK::CProtoBufMsg< CMsgGC_ReportPlayer > msg( k_EMsgGC_ReportPlayer ); + msg.Body().set_account_id_target( steamID.GetAccountID() ); + msg.Body().set_reason( (CMsgGC_ReportPlayer_EReason)nReason ); + GCClientSystem()->BSendMessage( msg ); + ConMsg( "Report sent. Thank you.\n" ); + + ReportedPlayer_t reportedPlayer; + reportedPlayer.steamID = steamID; + reportedPlayer.flReportedTime = gpGlobals->curtime; + vecReportedPlayers.AddToTail( reportedPlayer ); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CReportPlayerDialog::CReportPlayerDialog( vgui::Panel *parent ) : BaseClass( parent, "ReportPlayerDialog" ) +{ + vgui::VPANEL gameuiPanel = enginevgui->GetPanel( PANEL_GAMEUIDLL ); + SetParent( gameuiPanel ); + + vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFile("resource/SourceScheme.res", "Client"); + SetScheme(scheme); + + SetSize( 320, 270 ); + SetTitle( "#GameUI_ReportPlayerCaps", true ); + + m_pReportButton = new Button( this, "ReportButton", "" ); + m_pPlayerList = new ListPanel( this, "PlayerList" ); + m_pPlayerList->AddColumnHeader( 0, "Name", "#GameUI_PlayerName", 180 ); + m_pPlayerList->AddColumnHeader( 1, "Properties", "#GameUI_Properties", 80 ); + m_pPlayerList->SetEmptyListText( "#GameUI_NoOtherPlayersInGame" ); + m_pReasonBox = new ComboBox( this, "ReasonBox", 5, false ); + + LoadControlSettings( "Resource/ReportPlayerDialog.res" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CReportPlayerDialog::~CReportPlayerDialog() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CReportPlayerDialog::Activate() +{ + BaseClass::Activate(); + + m_pPlayerList->DeleteAllItems(); + + static EUniverse universe = steamapicontext->SteamUtils()->GetConnectedUniverse(); + + for ( int i = 1; i <= engine->GetMaxClients(); i++ ) + { + player_info_t pi; + if ( !engine->GetPlayerInfo( i, &pi ) ) + continue; + + // no need to add local player + if ( engine->GetLocalPlayer() == i ) + continue; + + // Already reported + CSteamID steamID( pi.friendsID, universe, k_EAccountTypeIndividual ); + if ( !CanReportPlayer( steamID, false ) ) + { + continue; + } + + char szPlayerIndex[32]; + Q_snprintf( szPlayerIndex, sizeof( szPlayerIndex ), "%d", i ); + + KeyValues *pData = new KeyValues( szPlayerIndex ); + pData->SetString( "Name", pi.name ); + pData->SetInt( "index", i ); + m_pPlayerList->AddItem( pData, 0, false, false ); + } + + m_pReasonBox->RemoveAll(); + KeyValues *pKeyValues = new KeyValues( "data" ); + SetDialogVariable( "combo_label", g_pVGuiLocalize->Find( "#GameUI_ReportPlayerReason" ) ); + pKeyValues->SetInt( "reason", 0 ); + m_pReasonBox->AddItem( g_pVGuiLocalize->Find( "GameUI_ReportPlayer_Choose" ), pKeyValues ); + pKeyValues->SetInt( "reason", 1 ); + m_pReasonBox->AddItem( g_pVGuiLocalize->Find( "GameUI_ReportPlayer_Cheating" ), pKeyValues ); + pKeyValues->SetInt( "reason", 2 ); + m_pReasonBox->AddItem( g_pVGuiLocalize->Find( "GameUI_ReportPlayer_Idle" ), pKeyValues ); + pKeyValues->SetInt( "reason", 3 ); + m_pReasonBox->AddItem( g_pVGuiLocalize->Find( "GameUI_ReportPlayer_Harassment" ), pKeyValues ); + pKeyValues->SetInt( "reason", 4 ); + m_pReasonBox->AddItem( g_pVGuiLocalize->Find( "GameUI_ReportPlayer_Griefing" ), pKeyValues ); + m_pReasonBox->SilentActivateItemByRow( 0 ); + pKeyValues->deleteThis(); + + RefreshPlayerProperties(); + m_pPlayerList->SetSingleSelectedItem( m_pPlayerList->GetItemIDFromRow( 0 ) ); + OnItemSelected(); +} + +//----------------------------------------------------------------------------- +// Purpose: walks the players and sets their info display in the list +//----------------------------------------------------------------------------- +void CReportPlayerDialog::RefreshPlayerProperties() +{ + for ( int i = 0; i <= m_pPlayerList->GetItemCount(); i++ ) + { + KeyValues *pData = m_pPlayerList->GetItem( i ); + if ( !pData ) + continue; + + int playerIndex = pData->GetInt( "index" ); + + player_info_t pi; + if ( !engine->GetPlayerInfo( playerIndex, &pi ) ) + { + pData->SetString( "properties", "Disconnected" ); + continue; + } + + pData->SetString( "name", pi.name ); + + if ( pi.fakeplayer ) + { + pData->SetString( "properties", "CPU Player" ); + } + else + { + pData->SetString( "properties", "" ); + } + } + m_pPlayerList->RereadAllItems(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CReportPlayerDialog::IsValidPlayerSelected() +{ + bool bIsValidPlayer = false; + + if ( m_pPlayerList->GetSelectedItemsCount() > 0 ) + { + KeyValues *pData = m_pPlayerList->GetItem( m_pPlayerList->GetSelectedItem( 0 ) ); + player_info_t pi; + bIsValidPlayer = engine->GetPlayerInfo( pData->GetInt( "index" ), &pi ); +#ifdef _DEBUG + bIsValidPlayer = bIsValidPlayer && pData->GetInt( "index" ) != engine->GetLocalPlayer(); +#else + bIsValidPlayer = bIsValidPlayer && !pi.fakeplayer && pData->GetInt( "index" ) != engine->GetLocalPlayer(); +#endif + } + + return bIsValidPlayer; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CReportPlayerDialog::OnCommand( const char *command ) +{ + if ( !stricmp( command, "Report" ) ) + { + ReportPlayer(); + } + else + { + BaseClass::OnCommand( command ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CReportPlayerDialog::ReportPlayer() +{ + for ( int iSelectedItem = 0; iSelectedItem < m_pPlayerList->GetSelectedItemsCount(); iSelectedItem++ ) + { + KeyValues *pPlayerData = m_pPlayerList->GetItem( m_pPlayerList->GetSelectedItem( iSelectedItem ) ); + if ( !pPlayerData ) + return; + + Assert( pPlayerData->GetInt( "index" ) ); + + // INVALID = 0; + // CHEATING = 1; + // IDLE = 2; + // HARASSMENT = 3; + // GRIEFING = 4; + + player_info_t pi; + if ( !engine->GetPlayerInfo( pPlayerData->GetInt( "index" ), &pi ) ) + return; + + CSteamID steamID( pi.friendsID, GetUniverse(), k_EAccountTypeIndividual ); + KeyValues *pReasonData = m_pReasonBox->GetActiveItemUserData(); + int nReason = ( pReasonData ) ? pReasonData->GetInt( "reason", 0 ) : 0; + ReportPlayerAccount( steamID, nReason ); + Close(); + return; + } + + RefreshPlayerProperties(); + OnItemSelected(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CReportPlayerDialog::OnItemSelected() +{ + RefreshPlayerProperties(); + + bool bReportButtonEnabled = IsValidPlayerSelected(); + if ( !bReportButtonEnabled ) + { + m_pReportButton->SetText( "#GameUI_ReportPlayer" ); + } + + // Reason selected? + KeyValues *pUserData = m_pReasonBox->GetActiveItemUserData(); + bReportButtonEnabled = bReportButtonEnabled && pUserData && pUserData->GetInt( "reason", 0 ) > 0; + m_pReportButton->SetEnabled( bReportButtonEnabled ); +} + +//----------------------------------------------------------------------------- +// Purpose: Called when text changes in combo box +//----------------------------------------------------------------------------- +void CReportPlayerDialog::OnTextChanged( KeyValues *data ) +{ + Panel *pPanel = reinterpret_cast< vgui::Panel* >( data->GetPtr( "panel" ) ); + vgui::ComboBox *pComboBox = dynamic_cast< vgui::ComboBox* >( pPanel ); + if ( pComboBox && pComboBox == m_pReasonBox ) + { + bool bReportButtonEnabled = IsValidPlayerSelected(); + KeyValues *pReasonData = m_pReasonBox->GetActiveItemUserData(); + bReportButtonEnabled = bReportButtonEnabled && pReasonData && pReasonData->GetInt( "reason", 0 ) > 0; + m_pReportButton->SetEnabled( bReportButtonEnabled ); + } +} diff --git a/game/client/tf/vgui/report_player_dialog.h b/game/client/tf/vgui/report_player_dialog.h new file mode 100644 index 0000000..52b8307 --- /dev/null +++ b/game/client/tf/vgui/report_player_dialog.h @@ -0,0 +1,56 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#ifndef REPORT_PLAYER_DIALOG_H +#define REPORT_PLAYER_DIALOG_H +#ifdef _WIN32 +#pragma once +#endif + +#include "vgui_controls/Frame.h" +#include "vgui_controls/ListPanel.h" +#include "vgui_controls/Button.h" +#include "vgui_controls/ComboBox.h" + +class CReportPlayerDialog : public vgui::Frame +{ + DECLARE_CLASS_SIMPLE( CReportPlayerDialog, vgui::Frame ); + +public: + CReportPlayerDialog( vgui::Panel *parent ); + ~CReportPlayerDialog(); + + virtual void Activate(); + +private: + MESSAGE_FUNC( OnItemSelected, "ItemSelected" ); + MESSAGE_FUNC_PARAMS( OnTextChanged, "TextChanged", data ); + virtual void OnCommand( const char *command ); + + void ReportPlayer(); + void RefreshPlayerProperties(); + bool IsValidPlayerSelected(); + + void OnKeyCodePressed( vgui::KeyCode code ) + { + if ( code == KEY_XBUTTON_B ) + { + Close(); + } + else + { + BaseClass::OnKeyCodePressed( code ); + } + } + + vgui::ListPanel *m_pPlayerList; + vgui::Button *m_pReportButton; + vgui::ComboBox *m_pReasonBox; +}; + +bool ReportPlayerAccount( CSteamID steamID, int nReason ); + +#endif // REPORT_PLAYER_DIALOG_H diff --git a/game/client/tf/vgui/sc_hinticon.cpp b/game/client/tf/vgui/sc_hinticon.cpp new file mode 100644 index 0000000..703c121 --- /dev/null +++ b/game/client/tf/vgui/sc_hinticon.cpp @@ -0,0 +1,70 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "sc_hinticon.h" +#include <vgui/IVGui.h> +#include "inputsystem/iinputsystem.h" + +using namespace vgui; + +DECLARE_BUILD_FACTORY( CSCHintIcon ); + +//----------------------------------------------------------------------------- +CSCHintIcon::CSCHintIcon( vgui::Panel *parent, const char* panelName ) : + vgui::Label( parent, panelName, L"" ) + , m_bIsActionMapped( false ) + , m_actionSetHandle( 0 ) +{ + m_szActionName[0] = '\0'; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSCHintIcon::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + auto szActionName = inResourceData->GetString( "actionName", "" ); + Q_strncpy( m_szActionName, szActionName, nMaxActionNameLength ); + + auto szActionSet = inResourceData->GetString( "actionSet", nullptr ); + if ( szActionSet ) + { + m_actionSetHandle = g_pInputSystem->GetActionSetHandle( szActionSet ); + } + else + { + m_actionSetHandle = 0; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSCHintIcon::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + const wchar_t* iconText = L""; + m_bIsActionMapped = false; + if ( m_actionSetHandle ) + { + auto origin = g_pInputSystem->GetSteamControllerActionOrigin( m_szActionName, m_actionSetHandle ); + if ( origin != k_EControllerActionOrigin_None ) + { + iconText = g_pInputSystem->GetSteamControllerFontCharacterForActionOrigin( origin ); + if ( iconText && iconText[0] ) + { + m_bIsActionMapped = true; + } + } + } + + SetText( iconText ); +}
\ No newline at end of file diff --git a/game/client/tf/vgui/sc_hinticon.h b/game/client/tf/vgui/sc_hinticon.h new file mode 100644 index 0000000..337ece2 --- /dev/null +++ b/game/client/tf/vgui/sc_hinticon.h @@ -0,0 +1,39 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Control for displaying a Steam Controller hint icon +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef SC_HINTICON_H +#define SC_HINTICON_H +#ifdef _WIN32 +#pragma once +#endif + +#include <vgui/IScheme.h> +#include <vgui/KeyCode.h> +#include <KeyValues.h> +#include <vgui/IVGui.h> +#include <vgui_controls/Label.h> + +class CSCHintIcon : public vgui::Label +{ +public: + DECLARE_CLASS_SIMPLE( CSCHintIcon, vgui::Label ); + + CSCHintIcon( vgui::Panel *parent, const char *panelName ); + + virtual void ApplySettings( KeyValues *inResourceData ); + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + + bool IsActionMapped() const { return m_bIsActionMapped; } + +private: + bool m_bIsActionMapped; + static const int nMaxActionNameLength = 63; + char m_szActionName[nMaxActionNameLength+1]; + ControllerActionSetHandle_t m_actionSetHandle; +}; + +#endif // SC_HINTICON_H
\ No newline at end of file diff --git a/game/client/tf/vgui/select_player_dialog.cpp b/game/client/tf/vgui/select_player_dialog.cpp new file mode 100644 index 0000000..fd8c072 --- /dev/null +++ b/game/client/tf/vgui/select_player_dialog.cpp @@ -0,0 +1,446 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include <vgui/ILocalize.h> +#include "vgui_controls/TextEntry.h" +#include "select_player_dialog.h" +#include "tf_controls.h" +#include "c_playerresource.h" +#include "ienginevgui.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +int CSelectPlayerDialog::SortPartnerInfoFunc( const partner_info_t *pA, const partner_info_t *pB ) +{ + return Q_stricmp( pA->m_name.Get(), pB->m_name.Get() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CSelectPlayerDialog::CSelectPlayerDialog( vgui::Panel *parent ) + : vgui::EditablePanel( parent, "SelectPlayerDialog" ) + , m_bAllowSameTeam( true ) + , m_bAllowOutsideServer( true ) +{ + if ( parent == NULL ) + { + vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme"); + SetScheme(scheme); + SetProportional( true ); + } + + m_pSelectFromServerButton = NULL; + m_pCancelButton = NULL; + m_pButtonKV = NULL; + m_bReapplyButtonKVs = false; + + for ( int i = 0; i < SPDS_NUM_STATES; i++ ) + { + m_pStatePanels[i] = new vgui::EditablePanel( this, VarArgs("StatePanel%d",i) ); + } + + m_pPlayerList = new vgui::EditablePanel( this, "PlayerList" ); + m_pPlayerListScroller = new vgui::ScrollableEditablePanel( this, m_pPlayerList, "PlayerListScroller" ); + + m_iCurrentState = SPDS_SELECTING_PLAYER; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CSelectPlayerDialog::~CSelectPlayerDialog( void ) +{ + if ( m_pButtonKV ) + { + m_pButtonKV->deleteThis(); + m_pButtonKV = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSelectPlayerDialog::Reset( void ) +{ + m_iCurrentState = SPDS_SELECTING_PLAYER; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSelectPlayerDialog::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + KeyValues *pItemKV = inResourceData->FindKey( "button_kv" ); + if ( pItemKV ) + { + if ( m_pButtonKV ) + { + m_pButtonKV->deleteThis(); + } + m_pButtonKV = new KeyValues("button_kv"); + pItemKV->CopySubkeys( m_pButtonKV ); + + m_bReapplyButtonKVs = true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSelectPlayerDialog::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( GetResFile() ); + + m_pCancelButton = dynamic_cast<CExButton*>( FindChildByName( "CancelButton" ) ); + + // Find all the sub buttons, and set their action signals to point to this panel + for ( int i = 0; i < SPDS_NUM_STATES; i++ ) + { + int iButton = 0; + CExButton *pButton = NULL; + do + { + pButton = dynamic_cast<CExButton*>( m_pStatePanels[i]->FindChildByName( VarArgs("subbutton%d",iButton)) ); + if ( pButton ) + { + pButton->AddActionSignalTarget( this ); + + // The second button on the first state is the server button + if ( iButton == 1 ) + { + m_pSelectFromServerButton = pButton; + } + + iButton++; + } + } while (pButton); + } + + UpdateState(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSelectPlayerDialog::PerformLayout( void ) +{ + BaseClass::PerformLayout(); + + // Layout the player list buttons + if ( m_pPlayerPanels.Count() ) + { + int iButtonH = m_pPlayerPanels[0]->GetTall() + YRES(2); + m_pPlayerList->SetSize( m_pPlayerList->GetWide(), YRES(2) + (iButtonH * m_pPlayerPanels.Count()) ); + + // These need to all be layout-complete before we can position the player panels, + // because the scrollbar will cause the playerlist entries to move when it lays out. + m_pPlayerList->InvalidateLayout( true ); + m_pPlayerListScroller->InvalidateLayout( true ); + m_pPlayerListScroller->GetScrollbar()->InvalidateLayout( true ); + + for ( int i = 0; i < m_pPlayerPanels.Count(); i++ ) + { + m_pPlayerPanels[i]->SetPos( 0, YRES(2) + (iButtonH * i) ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSelectPlayerDialog::OnCommand( const char *command ) +{ + if ( !Q_stricmp( command, "cancel" ) ) + { + if ( m_iCurrentState != SPDS_SELECTING_PLAYER ) + { + m_iCurrentState = SPDS_SELECTING_PLAYER; + UpdateState(); + return; + } + + TFModalStack()->PopModal( this ); + SetVisible( false ); + MarkForDeletion(); + if ( GetParent() ) + { + PostMessage( GetParent(), new KeyValues("CancelSelection") ); + } + return; + } + else if ( !Q_stricmp( command, "friends" ) ) + { + m_iCurrentState = SPDS_SELECTING_FROM_FRIENDS; + UpdateState(); + return; + } + else if ( !Q_stricmp( command, "server" ) ) + { + m_iCurrentState = SPDS_SELECTING_FROM_SERVER; + UpdateState(); + return; + } + else if ( !Q_strnicmp( command, "select_player", 13 ) ) + { + int iPlayer = atoi( command + 13 ) - 1; + if ( iPlayer >= 0 && iPlayer < m_PlayerInfoList.Count() ) + { + m_iCurrentState = SPDS_SELECTING_PLAYER; + OnCommand( "cancel" ); + OnSelectPlayer( m_PlayerInfoList[iPlayer].m_steamID ); + } + return; + } + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSelectPlayerDialog::UpdateState( void ) +{ + for ( int i = 0; i < SPDS_NUM_STATES; i++ ) + { + if ( !m_pStatePanels[i] ) + continue; + + m_pStatePanels[i]->SetVisible( m_iCurrentState == i ); + } + + if ( m_pSelectFromServerButton ) + { + m_pSelectFromServerButton->SetEnabled( engine->IsInGame() ); + } + + if ( m_iCurrentState == SPDS_SELECTING_PLAYER ) + { + m_pCancelButton->SetText( g_pVGuiLocalize->Find( "#Cancel" ) ); + } + else + { + m_pCancelButton->SetText( g_pVGuiLocalize->Find( "#TF_Back" ) ); + } + + switch ( m_iCurrentState ) + { + case SPDS_SELECTING_FROM_FRIENDS: + SetupSelectFriends(); + break; + case SPDS_SELECTING_FROM_SERVER: + SetupSelectServer( false ); + break; + case SPDS_SELECTING_PLAYER: + default: + m_pPlayerListScroller->SetVisible( false ); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSelectPlayerDialog::SetupSelectFriends( void ) +{ + // @todo optional check to see if friend is on my server + if ( m_bAllowOutsideServer == false ) + { + SetupSelectServer( true ); + return; + } + + m_PlayerInfoList.Purge(); + + if ( steamapicontext && steamapicontext->SteamFriends() ) + { + // Get our game info so we can use that to test if our friends are connected to the same game as us + FriendGameInfo_t myGameInfo; + CSteamID mySteamID = steamapicontext->SteamUser()->GetSteamID(); + steamapicontext->SteamFriends()->GetFriendGamePlayed( mySteamID, &myGameInfo ); + + int iFriends = steamapicontext->SteamFriends()->GetFriendCount( k_EFriendFlagImmediate ); + for ( int i = 0; i < iFriends; i++ ) + { + CSteamID friendSteamID = steamapicontext->SteamFriends()->GetFriendByIndex( i, k_EFriendFlagImmediate ); + + FriendGameInfo_t gameInfo; + if ( !AllowOutOfGameFriends() && !steamapicontext->SteamFriends()->GetFriendGamePlayed( friendSteamID, &gameInfo ) ) + continue; + + // Friends is in-game. Make sure it's TF2. + if ( AllowOutOfGameFriends() || (gameInfo.m_gameID.IsValid() && gameInfo.m_gameID == myGameInfo.m_gameID) ) + { + const char *pszName = steamapicontext->SteamFriends()->GetFriendPersonaName( friendSteamID ); + int idx = m_PlayerInfoList.AddToTail(); + partner_info_t &info = m_PlayerInfoList[idx]; + info.m_steamID = friendSteamID; + info.m_name = pszName; + } + } + } + + UpdatePlayerList(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSelectPlayerDialog::SetupSelectServer( bool bFriendsOnly ) +{ + m_PlayerInfoList.Purge(); + + if ( steamapicontext && steamapicontext->SteamUtils() ) + { + for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ ) + { + // find all players who are on the local player's team + int iLocalPlayerIndex = GetLocalPlayerIndex(); + if( ( iPlayerIndex != iLocalPlayerIndex ) && ( g_PR->IsConnected( iPlayerIndex ) ) ) + { + player_info_t pi; + if ( !engine->GetPlayerInfo( iPlayerIndex, &pi ) ) + continue; + if ( !pi.friendsID ) + continue; + + CSteamID steamID( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual ); + + if ( bFriendsOnly ) + { + EFriendRelationship eRelationship = steamapicontext->SteamFriends()->GetFriendRelationship( steamID ); + if ( eRelationship != k_EFriendRelationshipFriend ) + { + continue; + } + } + + if ( g_PR->GetTeam( iPlayerIndex ) != TF_TEAM_RED && g_PR->GetTeam( iPlayerIndex ) != TF_TEAM_BLUE ) + continue; + + if ( m_bAllowSameTeam == false ) + { + if ( GetLocalPlayerTeam() == g_PR->GetTeam( iPlayerIndex ) ) + { + continue; + } + } + + int idx = m_PlayerInfoList.AddToTail(); + partner_info_t &info = m_PlayerInfoList[idx]; + info.m_steamID = steamID; + info.m_name = pi.name; + } + } + } + + UpdatePlayerList(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSelectPlayerDialog::UpdatePlayerList( void ) +{ + vgui::Label *pLabelEmpty = dynamic_cast<vgui::Label*>( m_pStatePanels[m_iCurrentState]->FindChildByName("EmptyPlayerListLabel") ); + vgui::Label *pLabelQuery = dynamic_cast<vgui::Label*>( m_pStatePanels[m_iCurrentState]->FindChildByName("QueryLabel") ); + + // If we have no players in our list, show the no-player label. + if ( m_PlayerInfoList.Count() == 0 ) + { + if ( pLabelEmpty ) + { + pLabelEmpty->SetVisible( true ); + } + if ( pLabelQuery ) + { + pLabelQuery->SetVisible( false ); + } + return; + } + + // First, reapply any KVs we have to reapply + if ( m_bReapplyButtonKVs ) + { + m_bReapplyButtonKVs = false; + + if ( m_pButtonKV ) + { + FOR_EACH_VEC( m_pPlayerPanels, i ) + { + m_pPlayerPanels[i]->ApplySettings( m_pButtonKV ); + } + } + } + + // sort by name + m_PlayerInfoList.Sort( &SortPartnerInfoFunc ); + + // Otherwise, build the player panels from the list of steam IDs + for ( int i = 0; i < m_PlayerInfoList.Count(); i++ ) + { + if ( m_pPlayerPanels.Count() <= i ) + { + m_pPlayerPanels.AddToTail(); + m_pPlayerPanels[i] = new CSelectPlayerTargetPanel( m_pPlayerList, VarArgs("player%d",i) ); + m_pPlayerPanels[i]->GetButton()->SetCommand( VarArgs("select_player%d",i+1) ); + m_pPlayerPanels[i]->GetButton()->AddActionSignalTarget( this ); + m_pPlayerPanels[i]->GetAvatar()->SetShouldDrawFriendIcon( false ); + m_pPlayerPanels[i]->GetAvatar()->SetMouseInputEnabled( false ); + + if ( m_pButtonKV ) + { + m_pPlayerPanels[i]->ApplySettings( m_pButtonKV ); + m_pPlayerPanels[i]->InvalidateLayout( true ); + } + } + + m_pPlayerPanels[i]->SetInfo( m_PlayerInfoList[i].m_steamID, m_PlayerInfoList[i].m_name ); + } + + m_pPlayerListScroller->GetScrollbar()->SetAutohideButtons( true ); + m_pPlayerListScroller->GetScrollbar()->SetValue( 0 ); + + // Remove any extra player panels + for ( int i = m_pPlayerPanels.Count()-1; i >= m_PlayerInfoList.Count(); i-- ) + { + m_pPlayerPanels[i]->MarkForDeletion(); + m_pPlayerPanels.Remove(i); + } + + if ( pLabelEmpty ) + { + pLabelEmpty->SetVisible( false ); + } + if ( pLabelQuery ) + { + pLabelQuery->SetVisible( true ); + } + m_pPlayerListScroller->SetVisible( true ); + + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSelectPlayerTargetPanel::SetInfo( const CSteamID &steamID, const char *pszName ) +{ + if ( !steamapicontext || !steamapicontext->SteamFriends() ) + return; + + m_pAvatar->SetPlayer( steamID, k_EAvatarSize64x64 ); + + m_pButton->SetText( pszName ); +} + diff --git a/game/client/tf/vgui/select_player_dialog.h b/game/client/tf/vgui/select_player_dialog.h new file mode 100644 index 0000000..64ef206 --- /dev/null +++ b/game/client/tf/vgui/select_player_dialog.h @@ -0,0 +1,107 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef SELECT_PLAYER_DIALOG_H +#define SELECT_PLAYER_DIALOG_H +#ifdef _WIN32 +#pragma once +#endif + +#include "vgui_controls/EditablePanel.h" +#include "vgui_controls/ScrollableEditablePanel.h" +#include "tf_controls.h" +#include "vgui_avatarimage.h" + +// Select Player Dialog states +enum +{ + SPDS_SELECTING_PLAYER, + SPDS_SELECTING_FROM_FRIENDS, + SPDS_SELECTING_FROM_SERVER, + + SPDS_NUM_STATES, +}; + +// Button that displays the name & avatar image of a potential target +class CSelectPlayerTargetPanel : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CSelectPlayerTargetPanel, vgui::EditablePanel ); +public: + CSelectPlayerTargetPanel( vgui::Panel *parent, const char *name ) : vgui::EditablePanel( parent, name ) + { + m_pAvatar = new CAvatarImagePanel( this, "avatar" ); + m_pButton = new CExButton( this, "button", "", parent ); + } + ~CSelectPlayerTargetPanel( void ) + { + m_pAvatar->MarkForDeletion(); + m_pButton->MarkForDeletion(); + } + + void SetInfo( const CSteamID &steamID, const char *pszName ); + + CAvatarImagePanel *GetAvatar( void ) { return m_pAvatar; } + CExButton *GetButton( void ) { return m_pButton; } + +private: + // Embedded panels + CAvatarImagePanel *m_pAvatar; + CExButton *m_pButton; +}; + +//----------------------------------------------------------------------------- +// A dialog that allows users to select who they want to do something with +//----------------------------------------------------------------------------- +class CSelectPlayerDialog : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CSelectPlayerDialog, vgui::EditablePanel ); +public: + CSelectPlayerDialog( vgui::Panel *parent ); + ~CSelectPlayerDialog( void ); + + virtual void ApplySettings( KeyValues *inResourceData ); + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void PerformLayout( void ); + virtual void OnCommand( const char *command ); + + void UpdateState( void ); + virtual void UpdatePlayerList( void ); + virtual void Reset( void ); + virtual void SetupSelectFriends( void ); + virtual void SetupSelectServer( bool bFriendsOnly ); + + virtual bool AllowOutOfGameFriends() { return false; } + + virtual void OnSelectPlayer( const CSteamID &steamID ) = 0; + +protected: + virtual const char *GetResFile() { return "resource/ui/SelectPlayerDialog.res"; } + + struct partner_info_t + { + CSteamID m_steamID; + CUtlString m_name; + }; + + static int SortPartnerInfoFunc( const partner_info_t *pA, const partner_info_t *pB ); + + vgui::EditablePanel *m_pStatePanels[SPDS_NUM_STATES]; + int m_iCurrentState; + CExButton *m_pSelectFromServerButton; + CExButton *m_pCancelButton; + + vgui::EditablePanel *m_pPlayerList; + vgui::ScrollableEditablePanel *m_pPlayerListScroller; + CUtlVector<partner_info_t> m_PlayerInfoList; + CUtlVector<CSelectPlayerTargetPanel*> m_pPlayerPanels; + KeyValues *m_pButtonKV; + bool m_bReapplyButtonKVs; + bool m_bAllowSameTeam; + bool m_bAllowOutsideServer; +}; + +#endif // SELECT_PLAYER_DIALOG_H diff --git a/game/client/tf/vgui/softline.cpp b/game/client/tf/vgui/softline.cpp new file mode 100644 index 0000000..173c983 --- /dev/null +++ b/game/client/tf/vgui/softline.cpp @@ -0,0 +1,96 @@ +#include "cbase.h" +#include "softline.h" +#include <KeyValues.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + + +SoftLine::SoftLine(vgui::Panel *parent, const char *panelName, Color col) : + vgui::Panel(parent, panelName) +{ + m_Color = col; + m_iCornerType = 0; +} + +void SoftLine::Paint() +{ + if (m_iCornerType == 1) + DrawSoftLine(1,GetTall() - 2, GetWide() - 2, 1, m_Color); + else + DrawSoftLine(1,1, GetWide() - 2, GetTall() - 2, m_Color); +} + +int SoftLine::s_nWhiteTexture = -1; + +void SoftLine::DrawSoftLine(float x, float y, float x2, float y2, Color c) +{ + vgui::Vertex_t start, end; + + if (s_nWhiteTexture == -1) + { + s_nWhiteTexture = vgui::surface()->CreateNewTextureID(); + vgui::surface()->DrawSetTextureFile( s_nWhiteTexture, "vgui/white" , true, false); + if (s_nWhiteTexture == -1) + return; + return; + } + + // draw main line + vgui::surface()->DrawSetTexture(s_nWhiteTexture); + vgui::surface()->DrawSetColor(c); + + //vgui::surface()->DrawLine(x,y,x2,y2); + start.Init(Vector2D(x,y), Vector2D(0,0)); + end.Init(Vector2D(x2,y2), Vector2D(1,1)); + DrawPolygonLine(start, end); + + // draw translucent ones around it to give it some softness + vgui::surface()->DrawSetColor(c); + + start.Init(Vector2D(x - 0.50f,y - 0.50f), Vector2D(0,0)); + end.Init(Vector2D(x2 - 0.50f,y2 - 0.50f), Vector2D(1,1)); + DrawPolygonLine(start, end); + + start.Init(Vector2D(x + 0.50f,y - 0.50f), Vector2D(0,0)); + end.Init(Vector2D(x2 + 0.50f,y2 - 0.50f), Vector2D(1,1)); + DrawPolygonLine(start, end); + + start.Init(Vector2D(x - 0.50f,y + 0.50f), Vector2D(0,0)); + end.Init(Vector2D(x2 - 0.50f,y2 + 0.50f), Vector2D(1,1)); + DrawPolygonLine(start, end); + + start.Init(Vector2D(x + 0.50f,y + 0.50f), Vector2D(0,0)); + end.Init(Vector2D(x2 + 0.50f,y2 + 0.50f), Vector2D(1,1)); + DrawPolygonLine(start, end); +} + +// draws a line using polygon calls +void SoftLine::DrawPolygonLine(vgui::Vertex_t start, vgui::Vertex_t end, float width) +{ + DrawPolygonLine(start.m_Position.x, start.m_Position.y, end.m_Position.x, end.m_Position.y, width); +} + +void SoftLine::DrawPolygonLine(float x, float y, float x2, float y2, float width) +{ + // find long edge + Vector2D start(x, y); + Vector2D end(x2, y2); + Vector2D long_edge = end - start; + // normalize and rotate 90 degrees to get our short edge + Vector2D short_edge = long_edge; + short_edge.NormalizeInPlace(); + float newx = cos(1.5708f) * short_edge.x - sin(1.5708f) * short_edge.y; + float newy = sin(1.5708f) * short_edge.x - cos(1.5708f) * short_edge.y; + short_edge.x = newx; + short_edge.y = newy; + short_edge *= width; + vgui::Vertex_t points[4] = + { + vgui::Vertex_t( Vector2D(x, y) - short_edge * 0.5f, Vector2D(0,0) ), + vgui::Vertex_t( Vector2D(x, y) - short_edge * 0.5f + long_edge, Vector2D(1,0) ), + vgui::Vertex_t( Vector2D(x, y) + short_edge * 0.5f + long_edge, Vector2D(1,1) ), + vgui::Vertex_t( Vector2D(x, y) + short_edge * 0.5f, Vector2D(0,1) ) + }; + vgui::surface()->DrawTexturedPolygon(4, points); +}
\ No newline at end of file diff --git a/game/client/tf/vgui/softline.h b/game/client/tf/vgui/softline.h new file mode 100644 index 0000000..cbc9ec9 --- /dev/null +++ b/game/client/tf/vgui/softline.h @@ -0,0 +1,35 @@ +#ifndef _INCLUDED_SOFT_LINE_H +#define _INCLUDED_SOFT_LINE_H + +#ifdef _WIN32 +#pragma once +#endif + +#include <vgui/VGUI.h> +#include <vgui_controls/Panel.h> +#include <vgui/ISurface.h> + +// this is a vgui panel that draws a line between opposite corners +// the line is softened with translucent lines around it + +class SoftLine : public vgui::Panel +{ + DECLARE_CLASS_SIMPLE( SoftLine, vgui::Panel ); +public: + SoftLine(vgui::Panel *parent, const char *panelName, Color col); + virtual void Paint(); + void DrawSoftLine(float x, float y, float x2, float y2, Color c); + void SetCornerType(int i) { m_iCornerType = i; } + + Color m_Color; + int m_iCornerType; + + static int s_nWhiteTexture; + + // draws a line between two points using polygon rather than line drawing functions (since line doesn't work sometimes) + static void DrawPolygonLine(float x, float y, float x2, float y2, float width=1.0f); + static void DrawPolygonLine(vgui::Vertex_t start, vgui::Vertex_t end, float width=1.0f); +}; + + +#endif // _INCLUDED_SOFT_LINE_H
\ No newline at end of file diff --git a/game/client/tf/vgui/store/tf_store.cpp b/game/client/tf/vgui/store/tf_store.cpp new file mode 100644 index 0000000..d1a827b --- /dev/null +++ b/game/client/tf/vgui/store/tf_store.cpp @@ -0,0 +1,7 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +//=======================================================================================// + + +#include "cbase.h" +#include "store/tf_store.h" diff --git a/game/client/tf/vgui/store/tf_store.h b/game/client/tf/vgui/store/tf_store.h new file mode 100644 index 0000000..67c83d2 --- /dev/null +++ b/game/client/tf/vgui/store/tf_store.h @@ -0,0 +1,12 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +//=======================================================================================// + +#ifndef TF_STORE_H +#define TF_STORE_H +#ifdef _WIN32 +#pragma once +#endif + + +#endif // TF_STORE_H diff --git a/game/client/tf/vgui/store/tf_store_page_base.cpp b/game/client/tf/vgui/store/tf_store_page_base.cpp new file mode 100644 index 0000000..ee867e8 --- /dev/null +++ b/game/client/tf/vgui/store/tf_store_page_base.cpp @@ -0,0 +1,278 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "store/tf_store_page_base.h" +//#include "store/v1/tf_store_preview_item.h" +#include "econ_item_inventory.h" +#include "store/store_viewcart.h" +#include "c_tf_freeaccount.h" +#include "rtime.h" +#include "econ_ui.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +extern const char *g_aPlayerClassNames[TF_CLASS_MENU_BUTTONS]; + +const char *g_szClassFilterStrings[] = +{ + "", // Undefined + "#Store_Items_Scout", + "#Store_Items_Sniper", + "#Store_Items_Soldier", + "#Store_Items_Demoman", + "#Store_Items_Medic", + "#Store_Items_HWGuy", + "#Store_Items_Pyro", + "#Store_Items_Spy", + "#Store_Items_Engineer" +}; + +DECLARE_BUILD_FACTORY( CStorePreviewClassIcon ); + +ConVar tf_explanations_store( "tf_explanations_store", "0", FCVAR_ARCHIVE, "Whether the user has seen explanations for this panel." ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFStorePageBase::CTFStorePageBase(Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData, const char *pPreviewItemResFile ) : CStorePage(parent, pPageData, pPreviewItemResFile) +{ + m_flStartExplanationsAt = 0; + + // TF has an option for each class, all class items, all items, and an unowned item option. Let's make sure they all fit. + if ( m_pFilterComboBox ) + { + m_pFilterComboBox->SetNumberOfEditLines( 12 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePageBase::OnPageShow( void ) +{ + BaseClass::OnPageShow(); + + // If this is the first time we've opened the store, start the armory explanations + if ( !tf_explanations_store.GetBool() && m_pPageData ) + { + m_flStartExplanationsAt = engine->Time() + 0.5; + vgui::ivgui()->AddTickSignal( GetVPanel() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePageBase::OnCommand( const char *command ) +{ + if ( !Q_stricmp( command, "show_explanations" ) ) + { + if ( !m_flStartExplanationsAt ) + { + m_flStartExplanationsAt = engine->Time(); + vgui::ivgui()->AddTickSignal( GetVPanel() ); + } + RequestFocus(); + } + else + { + BaseClass::OnCommand( command ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePageBase::GetFiltersForDef( GameItemDefinition_t *pDef, CUtlVector<int> *pVecFilters ) +{ + pVecFilters->AddToTail( FILTER_ALL_ITEMS ); + + // Add item to unowned filter only if it doesn't belong to these categories. + const econ_store_entry_t *pEntry = EconUI()->GetStorePanel()->GetPriceSheet()->GetEntry( pDef->GetDefinitionIndex() ); + if( !pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Tools ) && + !pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Maps ) && + !pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Bundles ) && + !pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Collections ) ) + { + bool bItemOwned = false; + int iCount = InventoryManager()->GetLocalInventory()->GetItemCount(); + for ( int i = 0; i < iCount; i++ ) + { + if ( InventoryManager()->GetLocalInventory()->GetItem( i )->GetItemDefIndex() == pDef->GetDefinitionIndex() ) + { + bItemOwned = true; + break; + } + } + + if ( !bItemOwned ) + { + pVecFilters->AddToTail( FILTER_UNOWNED_ITEMS ); + } + } + + if ( pDef->CanBeUsedByAllClasses() ) + pVecFilters->AddToTail( FILTER_ALLCLASS_ITEMS ); + + for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ ) + { + if ( pDef->CanBeUsedByClass( iClass ) ) + pVecFilters->AddToTail( iClass ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePageBase::OnItemDetails( vgui::Panel *panel ) +{ + CStoreItemControlsPanel *pControlsPanel = dynamic_cast< CStoreItemControlsPanel * >( panel ); + if ( pControlsPanel ) + { + const econ_store_entry_t *pEntry = pControlsPanel->GetItem(); + if ( pEntry ) + { + SelectItemPanel( pControlsPanel->GetItemModelPanel() ); + PostMessage( EconUI()->GetStorePanel(), new KeyValues("ArmoryOpened", "itemdef", pEntry->GetItemDefinitionIndex() ) ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePageBase::ShowPreview( int iClass, const econ_store_entry_t* pEntry ) +{ + if ( iClass < TF_FIRST_NORMAL_CLASS || iClass >= TF_LAST_NORMAL_CLASS ) + { + iClass = TF_CLASS_SCOUT; + } + + BaseClass::ShowPreview( iClass, pEntry ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePageBase::UpdateFilterComboBox( void ) +{ + if ( !m_pFilterComboBox ) + return; + + wchar_t wzLocalized[256]; + wchar_t wszCount[16]; + + m_pFilterComboBox->RemoveAll(); + + // All items + KeyValues *pKeyValues = new KeyValues( "data" ); + pKeyValues->SetInt( "filter", FILTER_ALL_ITEMS ); + m_pFilterComboBox->AddItem( "#Store_ClassFilter_None", pKeyValues ); + +#if NEWFILTER + // All classes + int nCount = m_pPrimaryFilter->GetCountForFilterItem( FILTER_ALLCLASS_ITEMS ); + if ( nCount ) + { + pKeyValues->SetInt( "filter", FILTER_ALLCLASS_ITEMS ); + + _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", nCount ); + g_pVGuiLocalize->ConstructString_safe( wzLocalized, g_pVGuiLocalize->Find( "#Store_ClassFilter_AllClasses" ), 1, wszCount ); + m_pFilterComboBox->AddItem( wzLocalized, pKeyValues ); + } + + // Individual classes + for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ ) + { + nCount = m_pPrimaryFilter->GetCountForFilterItem( iClass ); + if ( !nCount ) + continue; + + pKeyValues->SetInt( "filter", iClass ); + + _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", nCount ); + g_pVGuiLocalize->ConstructString_safe( wzLocalized, g_pVGuiLocalize->Find( g_szClassFilterStrings[iClass] ), 1, wszCount ); + m_pFilterComboBox->AddItem( wzLocalized, pKeyValues ); + } + + // Unowned item filter + nCount = m_pPrimaryFilter->GetCountForFilterItem( FILTER_UNOWNED_ITEMS ); + if ( nCount ) + { + pKeyValues->SetInt( "filter", FILTER_UNOWNED_ITEMS ); + + _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", nCount ); + g_pVGuiLocalize->ConstructString_safe( wzLocalized, g_pVGuiLocalize->Find( "#Store_Items_Unowned" ), 1, wszCount ); + m_pFilterComboBox->AddItem( wzLocalized, pKeyValues ); + } + +#else + // All classes + if ( m_vecFilterCounts[FILTER_ALLCLASS_ITEMS] ) + { + pKeyValues->SetInt( "filter", FILTER_ALLCLASS_ITEMS ); + + _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", m_vecFilterCounts[FILTER_ALLCLASS_ITEMS] ); + g_pVGuiLocalize->ConstructString_safe( wzLocalized, g_pVGuiLocalize->Find( "#Store_ClassFilter_AllClasses" ), 1, wszCount ); + m_pFilterComboBox->AddItem( wzLocalized, pKeyValues ); + } + + // Individual classes + for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ ) + { + if ( m_vecFilterCounts[iClass] == 0 ) + continue; + + pKeyValues->SetInt( "filter", iClass ); + + _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", m_vecFilterCounts[iClass] ); + g_pVGuiLocalize->ConstructString_safe( wzLocalized, g_pVGuiLocalize->Find( g_szClassFilterStrings[iClass] ), 1, wszCount ); + m_pFilterComboBox->AddItem( wzLocalized, pKeyValues ); + } + + // Unowned item filter + if ( m_vecFilterCounts[FILTER_UNOWNED_ITEMS] ) + { + pKeyValues->SetInt( "filter", FILTER_UNOWNED_ITEMS ); + + _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", m_vecFilterCounts[FILTER_UNOWNED_ITEMS] ); + g_pVGuiLocalize->ConstructString_safe( wzLocalized, g_pVGuiLocalize->Find( "#Store_Items_Unowned" ), 1, wszCount ); + m_pFilterComboBox->AddItem( wzLocalized, pKeyValues ); + } +#endif + + pKeyValues->deleteThis(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePageBase::OnTick( void ) +{ + BaseClass::OnTick(); + + if ( m_flStartExplanationsAt && m_flStartExplanationsAt < engine->Time() ) + { + m_flStartExplanationsAt = 0; + + tf_explanations_store.SetValue( 1 ); + + CExplanationPopup *pPopup = dynamic_cast<CExplanationPopup*>( FindChildByName("StartExplanation") ); + if ( pPopup ) + { + pPopup->Popup(); + } + } + + if ( !m_flStartExplanationsAt ) + { + vgui::ivgui()->RemoveTickSignal( GetVPanel() ); + } +} + diff --git a/game/client/tf/vgui/store/tf_store_page_base.h b/game/client/tf/vgui/store/tf_store_page_base.h new file mode 100644 index 0000000..9a4bcbe --- /dev/null +++ b/game/client/tf/vgui/store/tf_store_page_base.h @@ -0,0 +1,121 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_STORE_PAGE_H +#define TF_STORE_PAGE_H +#ifdef _WIN32 +#pragma once +#endif + +#include <game/client/iviewport.h> +#include "vgui_controls/PropertyPage.h" +#include <vgui_controls/Button.h> +#include <vgui_controls/ComboBox.h> +#include <vgui_controls/ImagePanel.h> +#include "econ_controls.h" +#include "econ_ui.h" +#include "econ_store.h" +#include "item_model_panel.h" +#include "store/store_page.h" +#include "tf_shareddefs.h" + +class CItemModelPanel; +class CItemModelPanelToolTip; +class CTFPlayerModelPanel; +class CStorePreviewItemPanel; +class CStoreItemControlsPanel; + +extern const char *g_pszTipsClassImages[]; + +#define FILTER_ALLCLASS_ITEMS TF_LAST_NORMAL_CLASS +#define FILTER_UNOWNED_ITEMS (TF_LAST_NORMAL_CLASS + 1) + +//----------------------------------------------------------------------------- +// Purpose: A player class preview icon in the store's item preview panel +//----------------------------------------------------------------------------- +class CStorePreviewClassIcon : public CBaseStorePreviewIcon +{ + DECLARE_CLASS_SIMPLE( CStorePreviewClassIcon, CBaseStorePreviewIcon ); +public: + CStorePreviewClassIcon( vgui::Panel *parent, const char *name ) : CBaseStorePreviewIcon(parent,name) + { + m_pImagePanel = new vgui::ImagePanel( this, "classimage" ); + m_pImagePanel->SetShouldScaleImage( true ); + m_pImagePanel->SetMouseInputEnabled( false ); + m_pImagePanel->SetKeyBoardInputEnabled( false ); + m_iClass = 0; + } + + virtual void OnCursorEntered() + { + BaseClass::OnCursorEntered(); + PostActionSignal(new KeyValues("ShowClassIconMouseover", "class", m_iClass)); + } + virtual void OnCursorExited() + { + BaseClass::OnCursorExited(); + PostActionSignal(new KeyValues("HideClassIconMouseover")); + } + virtual void OnMouseReleased(vgui::MouseCode code) + { + BaseClass::OnMouseReleased(code); + PostActionSignal(new KeyValues("ClassIconSelected", "class", m_iClass)); + } + + virtual void SetInternalImageBounds( int iX, int iY, int iWide, int iTall ) + { + m_pImagePanel->SetBounds( iX, iY, iWide, iTall ); + } + + void SetClass( int iClass ) + { + if ( iClass >= TF_FIRST_NORMAL_CLASS && iClass < TF_LAST_NORMAL_CLASS ) + { + m_pImagePanel->SetImage( g_pszTipsClassImages[iClass] ); + } + else + { + m_pImagePanel->SetImage( "class_portraits/all_class" ); + } + m_iClass = iClass; + } + + int GetClass( void ) { return m_iClass; } + +private: + vgui::ImagePanel *m_pImagePanel; + int m_iClass; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CTFStorePageBase : public CStorePage +{ + DECLARE_CLASS_SIMPLE( CTFStorePageBase, CStorePage ); +protected: + // CTFStorePageBase should not be instantiated directly + CTFStorePageBase( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData, const char *pPreviewItemResFile = NULL ); + +public: + + virtual void OnCommand( const char *command ); + virtual void ShowPreview( int iClass, const econ_store_entry_t* pEntry ); + + MESSAGE_FUNC( OnPageShow, "PageShow" ); + MESSAGE_FUNC_PTR( OnItemDetails, "ItemDetails", panel ); + + virtual void UpdateFilterComboBox( void ); + virtual void GetFiltersForDef( GameItemDefinition_t *pDef, CUtlVector<int> *pVecFilters ); + virtual void OnTick( void ); + virtual int GetNumPrimaryFilters( void ) { return FILTER_UNOWNED_ITEMS+1; } + +protected: + float m_flStartExplanationsAt; +}; + +#endif // TF_STORE_PAGE_H diff --git a/game/client/tf/vgui/store/tf_store_panel_base.cpp b/game/client/tf/vgui/store/tf_store_panel_base.cpp new file mode 100644 index 0000000..5a80fbb --- /dev/null +++ b/game/client/tf/vgui/store/tf_store_panel_base.cpp @@ -0,0 +1,133 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "store/tf_store_panel_base.h" +#include "vgui/IInput.h" +#include "iclientmode.h" +#include "econ_item_system.h" +#include "econ_notifications.h" +#include "c_tf_freeaccount.h" +#include <vgui_controls/AnimationController.h> +#include "charinfo_armory_subpanel.h" +#include "backpack_panel.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +class CServerNotConnectedToSteamDialog; +CServerNotConnectedToSteamDialog *OpenServerNotConnectedToSteamDialog( vgui::Panel *pParent ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFBaseStorePanel::CTFBaseStorePanel( Panel *parent ) : CStorePanel(parent) +{ + m_pArmoryPanel = new CArmoryPanel( this, "armory_panel" ); + m_pNotificationsPresentPanel = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFBaseStorePanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + m_pNotificationsPresentPanel = FindChildByName( "NotificationsPresentPanel" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFBaseStorePanel::OnArmoryOpened( KeyValues *data ) +{ + int iItemDef = data->GetInt( "itemdef", 0 ); + + // If it's a bundle, open the armory to a custom page showing all the items in the bundle + CEconItemDefinition *pDef = ItemSystem()->GetStaticDataForItemByDefIndex( iItemDef ); + if ( pDef ) + { + const bundleinfo_t *pBundleInfo = pDef->GetBundleInfo(); + if ( pBundleInfo ) + { + CUtlVector<item_definition_index_t> vecItems; + FOR_EACH_VEC( pBundleInfo->vecItemDefs, j ) + { + if ( pBundleInfo->vecItemDefs[j] ) + { + vecItems.AddToTail( pBundleInfo->vecItemDefs[j]->GetDefinitionIndex() ); + } + } + + m_pArmoryPanel->ShowPanel( pDef->GetItemBaseName(), &vecItems ); + m_pArmoryPanel->MoveToFront(); + return; + } + } + + m_pArmoryPanel->ShowPanel( iItemDef ); + m_pArmoryPanel->MoveToFront(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFBaseStorePanel::OnArmoryClosed( void ) +{ + PostMessage( m_pArmoryPanel, new KeyValues("Closing") ); + m_pArmoryPanel->SetVisible( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFBaseStorePanel::OnThink() +{ + bool bShouldBeVisible = NotificationQueue_GetNumNotifications() != 0; + if ( m_pNotificationsPresentPanel != NULL && m_pNotificationsPresentPanel->IsVisible() != bShouldBeVisible ) + { + m_pNotificationsPresentPanel->SetVisible( bShouldBeVisible ); + if ( bShouldBeVisible ) + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "NotificationsPresentBlink" ); + } + else + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "NotificationsPresentBlinkStop" ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFBaseStorePanel::PostTransactionCompleted( void ) +{ + // pop this dialog up + if ( NeedsToChooseMostHelpfulFriend() ) + { + // update main menu + IGameEvent *event = gameeventmanager->CreateEvent( "store_pricesheet_updated" ); + if ( event ) + { + gameeventmanager->FireEventClientSide( event ); + } + } + + EconUI()->GetBackpackPanel()->CheckForQuickOpenKey(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFBaseStorePanel::SetTransactionID( uint64 inID ) +{ + BaseClass::SetTransactionID( inID ); + + EconUI()->GetBackpackPanel()->SetCurrentTransactionID( inID ); +}
\ No newline at end of file diff --git a/game/client/tf/vgui/store/tf_store_panel_base.h b/game/client/tf/vgui/store/tf_store_panel_base.h new file mode 100644 index 0000000..33a7082 --- /dev/null +++ b/game/client/tf/vgui/store/tf_store_panel_base.h @@ -0,0 +1,54 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_STORE_PANEL_BASE_H +#define TF_STORE_PANEL_BASE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "store/store_panel.h" + +class CArmoryPanel; +class CStorePage; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CTFBaseStorePanel : public CStorePanel +{ + DECLARE_CLASS_SIMPLE( CTFBaseStorePanel, CStorePanel ); +protected: + // CTFBaseStorePanel should not be instantiated directly + CTFBaseStorePanel( Panel *parent ); + +public: + // UI Layout + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void OnThink(); + + // GC Management + virtual void PostTransactionCompleted( void ); + + // Cart Management + CStoreCart *GetCart( void ) { return &m_Cart; } + void ShowStorePanel( void ); + void InitiateCheckout( void ); + void CheckoutCancel( void ); + + virtual void SetTransactionID( uint64 inID ) OVERRIDE; + + // Armory management + MESSAGE_FUNC_PARAMS( OnArmoryOpened, "ArmoryOpened", data ); + MESSAGE_FUNC( OnArmoryClosed, "ArmoryClosed" ); + +private: + CArmoryPanel *m_pArmoryPanel; + vgui::Panel *m_pNotificationsPresentPanel; +}; + +#endif // TF_STORE_PANEL_BASE_H diff --git a/game/client/tf/vgui/store/tf_store_preview_item_base.cpp b/game/client/tf/vgui/store/tf_store_preview_item_base.cpp new file mode 100644 index 0000000..9f6e2b3 --- /dev/null +++ b/game/client/tf/vgui/store/tf_store_preview_item_base.cpp @@ -0,0 +1,1183 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "store/tf_store_preview_item_base.h" +#include "store/store_page.h" +#include "vgui/ISurface.h" +#include "vgui/IInput.h" +#include "vgui/ILocalize.h" +#include "gamestringpool.h" +#include "tf_item_inventory.h" +#include "tf_playermodelpanel.h" +#include "econ_item_system.h" +#include "item_model_panel.h" +#include "c_tf_gamestats.h" +#include "econ_ui.h" +#include "econ_item_tools.h" +#include "vgui_controls/MenuItem.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFStorePreviewItemPanelBase::CTFStorePreviewItemPanelBase( vgui::Panel *pParent, const char *pResFile, const char *pPanelName, CStorePage *pOwner ) +: CStorePreviewItemPanel( pParent, pResFile, "storepreviewitem", pOwner ) +{ + ResetHandles(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::ResetHandles( void ) +{ + m_pPlayerModelPanel = NULL; + m_iCurrentClass = TF_CLASS_SCOUT; + m_iCurrentHeldItem = 0; + m_pClassIconMouseoverLabel = NULL; + m_pRotRightButton = NULL; + m_pRotLeftButton = NULL; + m_pNextWeaponButton = NULL; + m_pZoomButton = NULL; + m_pOptionsButton = NULL; + m_pTeamButton = NULL; + m_pDataTextRichText = NULL; + m_iCurrentIconPosition = 0; + m_iState = PS_ITEM; + m_unPaintDef = 0; + m_unPaintRGB0 = 0; + m_unPaintRGB1 = 0; + m_pPaintNameLabel = NULL; + m_pStyleNameLabel = NULL; + m_pCustomizeMenu = NULL; + m_pClassIcons.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::SetCycleLabelText( vgui::Label *pTargetLabel, const char *pCycleText ) +{ + if ( pTargetLabel ) + { + pTargetLabel->SetText( pCycleText ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + ResetHandles(); + + BaseClass::ApplySchemeSettings( pScheme ); + + m_pPlayerModelPanel = dynamic_cast<CTFPlayerModelPanel*>( FindChildByName("classmodelpanel") ); + m_pClassIconMouseoverLabel = dynamic_cast<vgui::Label*>( FindChildByName("ClassUsageMouseoverLabel") ); + m_pRotRightButton = dynamic_cast<CExButton*>( FindChildByName("RotRightButton") ); + m_pRotLeftButton = dynamic_cast<CExButton*>( FindChildByName("RotLeftButton") ); + m_pNextWeaponButton = dynamic_cast<CExButton*>( FindChildByName("NextWeaponButton") ); + m_pZoomButton = dynamic_cast<CExButton*>( FindChildByName("ZoomButton") ); + m_pOptionsButton = dynamic_cast<CExButton*>( FindChildByName("OptionsButton") ); + m_pTeamButton = dynamic_cast<CExButton*>( FindChildByName("TeamButton") ); + m_pPaintNameLabel = dynamic_cast<vgui::Label*>( FindChildByName("PaintNameLabel") ); + m_pStyleNameLabel = dynamic_cast<vgui::Label*>( FindChildByName("StyleNameLabel") ); + + if ( m_pClassIconMouseoverLabel ) + { + m_pClassIconMouseoverLabel->SetVisible( false ); + } + + // Find all the class images + CStorePreviewClassIcon *pClassImage = NULL; + int iIcon = 1; + do + { + pClassImage = dynamic_cast<CStorePreviewClassIcon*>( FindChildByName( VarArgs("ClassUsageImage%d",iIcon)) ); + if ( pClassImage ) + { + m_pClassIcons.AddToTail( pClassImage ); + } + iIcon++; + } while ( pClassImage ); + + // Update our class icons. Hide them all first. The code below will unhide ones used. + for ( int i = 0; i < m_pClassIcons.Count(); i++ ) + { + m_pClassIcons[i]->SetVisible( false ); + } + + SetState( PS_ITEM ); + + m_vecPaintCans.Purge(); + const CEconItemSchema::ToolsItemDefinitionMap_t &toolDefs = GetItemSchema()->GetToolsItemDefinitionMap(); + + // Store all of the active paint can item defs + FOR_EACH_MAP_FAST( toolDefs, i ) + { + const CEconItemDefinition *pItemDef = toolDefs[i]; + + // ignore everything that is not a paint can tool + const IEconTool *pEconTool = pItemDef->GetEconTool(); + if ( pEconTool && !V_strcmp( pEconTool->GetTypeName(), "paint_can" ) ) + { + m_vecPaintCans.AddToTail( pItemDef->GetDefinitionIndex() ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::PerformLayout( void ) +{ + BaseClass::PerformLayout(); + + // center the icons (we need to redo some of the work of CStorePreviewItemPanel, because we + // center the base item icons along with our TF specific class ones) + int iNumItemIcons = 0; + FOR_EACH_VEC( m_pItemIcons, i ) + { + if ( m_pItemIcons[i]->IsVisible() ) + { + ++iNumItemIcons; + } + } + int iNumClassIcons = 0; + FOR_EACH_VEC( m_pClassIcons, i ) + { + if ( m_pClassIcons[i]->IsVisible() ) + { + ++iNumClassIcons; + } + } + if ( iNumItemIcons || iNumClassIcons ) + { + int iCenterX = GetWide() / 2; + int interval = XRES(2); + int totalWidth = (iNumItemIcons * m_pItemIcons[0]->GetWide()) + (iNumClassIcons * m_pClassIcons[0]->GetWide()) + (interval * (iNumItemIcons + iNumClassIcons - 1)); + int iX = iCenterX - ( totalWidth / 2 ); + + int posX, posY; + m_pItemIcons[0]->GetPos( posX, posY ); + + int iButton = 0; + for ( int i = 0; i < m_pItemIcons.Count(); i++ ) + { + if ( m_pItemIcons[i]->IsVisible() ) + { + m_pItemIcons[i]->SetPos( iX, posY ); + iX += m_pItemIcons[i]->GetWide() + interval; + + iButton++; + } + } + + for ( int i = 0; i < m_pClassIcons.Count(); i++ ) + { + if ( m_pClassIcons[i]->IsVisible() ) + { + m_pClassIcons[i]->SetPos( iX, posY ); + iX += m_pClassIcons[i]->GetWide() + interval; + + iButton++; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +static bool IsAnythingPaintable( const CUtlVector<CEconItemView*>& vecItems ) +{ + FOR_EACH_VEC( vecItems, i ) + { + if ( vecItems[i]->GetStaticData()->GetCapabilities() & ITEM_CAP_PAINTABLE ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +static void UpdatePaintColorsForTeam( CTFPlayerModelPanel *pPlayerModelPanel, uint32 unRGB0, uint32 unRGB1 ) +{ + Assert( pPlayerModelPanel ); + + static CSchemaAttributeDefHandle pAttrDef_ItemTintRGB( "set item tint RGB" ); + + if ( !pAttrDef_ItemTintRGB ) + return; + + const CUtlVector<CEconItemView*> &items = pPlayerModelPanel->GetCarriedItems(); + if ( !IsAnythingPaintable( items ) ) + return; + + for ( int i=0; i<items.Count(); ++i ) + { + if ( items[i]->GetStaticData()->GetCapabilities() & ITEM_CAP_PAINTABLE ) + { + items[i]->GetAttributeList()->SetRuntimeAttributeValue( pAttrDef_ItemTintRGB, pPlayerModelPanel->GetTeam() == TF_TEAM_RED ? unRGB0 : unRGB1 ); + items[i]->InvalidateColor(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::OnCommand( const char *command ) +{ + if ( !Q_strnicmp( command, "team_toggle", 11 ) ) + { + if ( m_pPlayerModelPanel ) + { + m_pPlayerModelPanel->SetTeam( m_pPlayerModelPanel->GetTeam() == TF_TEAM_RED ? TF_TEAM_BLUE : TF_TEAM_RED ); + } + + // Also toggle team paint color if necessary. + if ( m_unPaintRGB0 != m_unPaintRGB1 ) + { + UpdatePaintColorsForTeam( m_pPlayerModelPanel, m_unPaintRGB0, m_unPaintRGB1 ); + } + } + else if ( !Q_strnicmp( command, "zoom_toggle", 11 ) ) + { + if ( m_pPlayerModelPanel ) + { + m_pPlayerModelPanel->ToggleZoom(); + } + } + else if ( !Q_strnicmp( command, "paint_toggle", 12 ) ) + { + CyclePaint(); + } + else if ( !Q_strnicmp( command, "set_red", 7 ) ) + { + if ( m_pPlayerModelPanel ) + { + m_pPlayerModelPanel->SetSkin( 0 ); + } + return; + } + else if ( !Q_strnicmp( command, "set_blu", 7 ) ) + { + if ( m_pPlayerModelPanel ) + { + m_pPlayerModelPanel->SetSkin( 1 ); + } + return; + } + else if ( !Q_strnicmp( command, "next_weapon", 11 ) ) + { + if ( m_pPlayerModelPanel ) + { + const CUtlVector<CEconItemView*> &items = m_pPlayerModelPanel->GetCarriedItems(); + int iLastItem = m_iCurrentHeldItem; + do + { + m_iCurrentHeldItem = ( m_iCurrentHeldItem + 1 ) % items.Count(); + } while ( m_iCurrentHeldItem != iLastItem && m_pPlayerModelPanel->HoldItem( m_iCurrentHeldItem ) == false ); + } + m_pPlayerModelPanel->SetTeam( m_pPlayerModelPanel->GetTeam() ); + return; + } + else if ( !Q_strnicmp( command, "next_style", 10 ) ) + { + CycleStyle(); + return; + } + else if ( V_strncasecmp( command, "SetPaint", V_strlen( "SetPaint" ) ) == 0 ) + { + item_definition_index_t iItemDef = V_atoi( &command[ V_strlen( "SetPaint" ) ] ); + SetPaint( iItemDef ); + } + else if ( V_strncasecmp( command, "SetStyle", V_strlen( "SetStyle" ) ) == 0 ) + { + style_index_t unStyle = V_atoi( &command[ V_strlen( "SetStyle" ) ] ); + SetStyle( unStyle ); + } + else if ( V_strncasecmp( command, "SetUnusual", V_strlen( "SetUnusual" ) ) == 0 ) + { + int iUnusual = V_atoi( &command[ V_strlen( "SetUnusual" ) ] ); + SetUnusual( iUnusual ); + } + else if ( FStrEq( command, "options" ) ) + { + UpdateCustomizeMenu(); + } + else + { + BaseClass::OnCommand( command ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::OnClassIconSelected( KeyValues *data ) +{ + int iClass = data->GetInt( "class", 0 ); + if ( iClass < TF_FIRST_NORMAL_CLASS || iClass >= TF_LAST_NORMAL_CLASS ) + { + iClass = TF_CLASS_SCOUT; + } + m_iCurrentClass = iClass; + UpdateModelPanel(); + + SetState( PS_PLAYER ); + +// C_CTF_GameStats.Event_Store( IE_STORE_ITEM_PREVIEWED, NULL, NULL, m_iCurrentClass ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::SetPlayerModelVisible( bool bVisible ) +{ + if( m_pPlayerModelPanel ) + { + m_pPlayerModelPanel->SetVisible( bVisible ); + if ( m_pRotRightButton ) + { + m_pRotRightButton->SetVisible( bVisible ); + } + if ( m_pRotLeftButton ) + { + m_pRotLeftButton->SetVisible( bVisible ); + } + UpdatePlayerModelButtons(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::PreviewItem( int iClass, CEconItemView *pItem, const econ_store_entry_t* pEntry ) +{ + m_iCurrentClass = 0; + BaseClass::PreviewItem( iClass, pItem, pEntry ); + + UpdateModelPanel(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::SetState( preview_state_t iState ) +{ + BaseClass::SetState( iState ); + SetPlayerModelVisible( m_iState == PS_PLAYER ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CTFStorePreviewItemPanelBase::GetPreviewTeam() const +{ + return m_pPlayerModelPanel + ? m_pPlayerModelPanel->GetTeam() + : TF_TEAM_RED; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::UpdateIcons( void ) +{ + // Don't bother calling back to the base class UpdateIcons, because + // we'd need to redo it all with the class icons factored in. + + bool bAdditionalIcons = false; + + // Do the item icons first + if ( m_iState == PS_DETAILS ) + { + // Show as many of the items in the bundle as possible + const CEconItemDefinition *pItemData = m_item.GetItemDefinition(); + if ( pItemData ) + { + const bundleinfo_t *pBundleInfo = pItemData->GetBundleInfo(); + if ( pBundleInfo ) + { + FOR_EACH_VEC( m_pItemIcons, i ) + { + // If we haven't scrolled, the first item is the bundle itself + if ( m_iCurrentIconPosition == 0 && i == 0 ) + { + m_pItemIcons[0]->SetItem( 0, &m_item ); + continue; + } + + int iItemPos = (i - 1 + m_iCurrentIconPosition); + if ( pBundleInfo->vecItemDefs.Count() > iItemPos && pBundleInfo->vecItemDefs[iItemPos] ) + { + m_pItemIcons[i]->SetItem( i, pBundleInfo->vecItemDefs[iItemPos]->GetDefinitionIndex() ); + m_pItemIcons[i]->SetVisible( true ); + } + else + { + m_pItemIcons[i]->SetVisible( false ); + } + } + + bAdditionalIcons = (m_iCurrentIconPosition + m_pItemIcons.Count()) <= pBundleInfo->vecItemDefs.Count(); + } + else if ( m_pItemIcons.Count() ) + { + m_pItemIcons[0]->SetVisible( true ); + m_pItemIcons[0]->SetItem( 0, &m_item ); + FOR_EACH_VEC( m_pItemIcons, i ) + { + if ( i != 0 ) + { + m_pItemIcons[i]->SetVisible( false ); + } + } + } + } + + // Hide all the class icons + for ( int i = 0; i < m_pClassIcons.Count(); i++ ) + { + m_pClassIcons[i]->SetVisible( false ); + } + } + else + { + // Hide all item icons first (but not the first if we haven't scrolled) + FOR_EACH_VEC( m_pItemIcons, i ) + { + m_pItemIcons[i]->SetVisible( m_iCurrentIconPosition == 0 && i == 0 ); + } + + // First icon is always the store entry (item/bundle), if we haven't scrolled right + if ( m_iCurrentIconPosition == 0 && m_pItemIcons.Count() ) + { + m_pItemIcons[0]->SetItem( 0, &m_item ); + } + + // Then do the class icons + const CTFItemDefinition *pItemData = m_item.GetItemDefinition(); + if ( pItemData ) + { + int iButton = 0; + int iMaxButtons = m_pClassIcons.Count(); + int iNumClasses = (TF_LAST_NORMAL_CLASS - TF_FIRST_NORMAL_CLASS); + // we show one less class icon when the item is visible + if ( iMaxButtons < iNumClasses && m_iCurrentIconPosition == 0 ) + { + iMaxButtons -= 1; + } + for ( int iClass = TF_FIRST_NORMAL_CLASS + m_iCurrentIconPosition; iClass < TF_LAST_NORMAL_CLASS; iClass++ ) + { + if ( !pItemData->CanBeUsedByClass(iClass) ) + continue; + + // Run out of buttons? + if ( iButton >= iMaxButtons ) + { + bAdditionalIcons = true; + break; + } + + m_pClassIcons[iButton]->SetVisible( true ); + m_pClassIcons[iButton]->SetClass(iClass); + iButton++; + + if ( !m_iCurrentClass ) + { + m_iCurrentClass = iClass; + } + } + for ( ; iButton < m_pClassIcons.Count(); ++iButton ) + { + m_pClassIcons[iButton]->SetVisible( false ); + } + } + } + + if( m_pIconsMoveLeftButton ) + m_pIconsMoveLeftButton->SetVisible( (m_iCurrentIconPosition > 0) ); + if( m_pIconsMoveRightButton ) + m_pIconsMoveRightButton->SetVisible( bAdditionalIcons ); + + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::OnTick( void ) +{ + BaseClass::OnTick(); + + if ( !IsVisible() ) + { + vgui::ivgui()->RemoveTickSignal( GetVPanel() ); + return; + } + + if ( m_iCurrentRotation ) + { + m_pPlayerModelPanel->RotateYaw( m_iCurrentRotation ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::UpdateModelPanel() +{ + if ( m_pPlayerModelPanel ) + { + m_iCurrentHeldItem = 0; + m_pPlayerModelPanel->SetToPlayerClass( m_iCurrentClass, false ); + m_pPlayerModelPanel->ClearCarriedItems(); + + + if ( m_item.IsValid() ) + { + CTFItemDefinition *pItemDef = m_item.GetStaticData(); + if ( pItemDef->GetBundleInfo() != NULL ) + { + const bundleinfo_t *pBundleInfo = pItemDef->GetBundleInfo(); + FOR_EACH_VEC( pBundleInfo->vecItemDefs, i ) + { + CTFItemDefinition *pBundledItem = dynamic_cast<CTFItemDefinition *>( pBundleInfo->vecItemDefs[i] ); + if ( pBundledItem && pBundledItem->CanBeUsedByClass( m_iCurrentClass ) ) + { + CEconItemView bundleItemData; + bundleItemData.Init( pBundledItem->GetDefinitionIndex(), AE_UNIQUE, AE_USE_SCRIPT_VALUE, true ); + bundleItemData.SetClientItemFlags( kEconItemFlagClient_Preview ); + int iItemIdx = m_pPlayerModelPanel->AddCarriedItem( &bundleItemData ); + // try to hold it + if ( m_pPlayerModelPanel->HoldItem( iItemIdx ) ) + { + m_iCurrentHeldItem = iItemIdx; + } + } + } + } + else + { + m_pPlayerModelPanel->AddCarriedItem( &m_item ); + + // Now make sure we're holding it if it's a non-wearable + int iLoadoutSlot = m_item.GetStaticData()->GetLoadoutSlot( m_iCurrentClass ); + m_pPlayerModelPanel->HoldItemInSlot( iLoadoutSlot ); + } + } + + UpdatePlayerModelButtons(); + + // Fix a problem where changing the class would change the preview mesh but wouldn't + // update the paint. This is a hack and won't work if we have multi-class styles or + // just about anything else. + CyclePaint( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::UpdatePlayerModelButtons() +{ + UpdateOptionsButton(); + UpdateNextWeaponButton(); + UpdateZoomButton(); + UpdateTeamButton(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::UpdateCustomizeMenu( void ) +{ + if ( m_pCustomizeMenu ) + { + delete m_pCustomizeMenu; + m_pCustomizeMenu = NULL; + } + + if ( !m_pPlayerModelPanel->IsVisible() ) + return; + + if ( !m_pOptionsButton || !m_pOptionsButton->IsVisible() ) + return; + + if ( !m_item.IsValid() ) + return; + + m_pCustomizeMenu = new Menu( this, "CustomizeMenu" ); + MenuBuilder contextMenuBuilder( m_pCustomizeMenu, this ); + const char *pszContextMenuBorder = "NotificationDefault"; + const char *pszContextMenuFont = "HudFontMediumSecondary"; + m_pCustomizeMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) ); + m_pCustomizeMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) ); + + // Add paint options sub menu + { + Menu *pPaintSubMenu = NULL; + FOR_EACH_VEC( m_vecPaintCans, i ) + { + item_definition_index_t paintItemDefIndex = m_vecPaintCans[ i ]; + GameItemDefinition_t * pPaintCanDef = dynamic_cast<GameItemDefinition_t*>( GEconItemSchema().GetItemDefinition( paintItemDefIndex ) ); + if ( !CEconSharedToolSupport::ToolCanApplyToDefinition( dynamic_cast<const GameItemDefinition_t *>( pPaintCanDef ), m_item.GetStaticData() ) ) + continue; + + if ( pPaintSubMenu == NULL ) + { + pPaintSubMenu = new Menu( this, "PaintSubMenu" ); + pPaintSubMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) ); + pPaintSubMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) ); + contextMenuBuilder.AddCascadingMenuItem( "#Context_Paint", pPaintSubMenu, "customization" ); + } + + wchar_t wBuff[256] = { 0 }; + V_swprintf_safe( wBuff, L" %ls", g_pVGuiLocalize->Find( pPaintCanDef->GetItemBaseName() ) ); + int nIndex = pPaintSubMenu->AddMenuItem( "", new KeyValues( "Command", "command", CFmtStr( "SetPaint%d", paintItemDefIndex ) ), this ); + vgui::MenuItem *pMenuItem = pPaintSubMenu->GetMenuItem( nIndex ); + pMenuItem->SetText( wBuff ); + pMenuItem->InvalidateLayout( true, false ); + + uint32 unPaintRGB0 = 0; + uint32 unPaintRGB1 = 0; + + static CSchemaAttributeDefHandle pAttrDef_PaintRGB( "set item tint RGB" ); + static CSchemaAttributeDefHandle pAttrDef_PaintRGB2( "set item tint RGB 2" ); + + float fRGB = 0.0f; + if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pPaintCanDef, pAttrDef_PaintRGB, &fRGB ) && fRGB != 0.0f ) + { + unPaintRGB0 = fRGB; + + // We may or may not have a secondary paint color as well. If we don't, we just use the primary + // paint color to fill both slots. + if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pPaintCanDef, pAttrDef_PaintRGB2, &fRGB ) ) + { + unPaintRGB1 = fRGB; + } + else + { + unPaintRGB1 = unPaintRGB0; + } + } + + CItemMaterialCustomizationIconPanel *pCustomPanel = new CItemMaterialCustomizationIconPanel( pMenuItem, "paint" ); + pCustomPanel->SetZPos( -100 ); + pCustomPanel->SetTall( 30 ); + pCustomPanel->SetWide( 30 ); + pCustomPanel->m_colPaintColors.AddToTail( Color( clamp( (unPaintRGB0 & 0xFF0000) >> 16, 0, 255 ), clamp( (unPaintRGB0 & 0xFF00) >> 8, 0, 255 ), clamp( (unPaintRGB0 & 0xFF), 0, 255 ), 255 ) ); + pCustomPanel->m_colPaintColors.AddToTail( Color( clamp( (unPaintRGB1 & 0xFF0000) >> 16, 0, 255 ), clamp( (unPaintRGB1 & 0xFF00) >> 8, 0, 255 ), clamp( (unPaintRGB1 & 0xFF), 0, 255 ), 255 ) ); + } + } + + // Add style + { + Menu *pStyleSubMenu = NULL; + if ( m_item.GetStaticData()->GetNumSelectableStyles() > 1 ) + { + for ( style_index_t unStyle=0; unStyle<m_item.GetStaticData()->GetNumStyles(); ++unStyle ) + { + const CEconStyleInfo *pStyle = m_item.GetStaticData()->GetStyleInfo( unStyle ); + if ( !pStyle ) + continue; + + if ( !pStyle->IsSelectable() ) + continue; + + if ( pStyleSubMenu == NULL ) + { + pStyleSubMenu = new Menu( this, "StyleSubMenu" ); + pStyleSubMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) ); + pStyleSubMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) ); + contextMenuBuilder.AddCascadingMenuItem( "#Context_Style", pStyleSubMenu, "customization" ); + } + + int nIndex = pStyleSubMenu->AddMenuItem( "", new KeyValues( "Command", "command", CFmtStr( "SetStyle%d", unStyle ) ), this ); + vgui::MenuItem *pMenuItem = pStyleSubMenu->GetMenuItem( nIndex ); + pMenuItem->SetText( pStyle->GetName() ); + pMenuItem->InvalidateLayout( true, false ); + } + } + } + + // Add unusual + { + const CUtlVector< int > *pUnusualList = GetUnusualList(); + if ( pUnusualList ) + { + Menu *pUnusualSubMenu = NULL; + for ( int i=0; i<pUnusualList->Count(); ++i ) + { + if ( pUnusualSubMenu == NULL ) + { + pUnusualSubMenu = new Menu( this, "UnusualSubMenu" ); + pUnusualSubMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) ); + pUnusualSubMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) ); + contextMenuBuilder.AddCascadingMenuItem( "#Context_Unusual", pUnusualSubMenu, "customization" ); + } + + int iParticleIndex = pUnusualList->Element( i ); + int nIndex = pUnusualSubMenu->AddMenuItem( "", new KeyValues( "Command", "command", CFmtStr( "SetUnusual%d", iParticleIndex ) ), this ); + vgui::MenuItem *pMenuItem = pUnusualSubMenu->GetMenuItem( nIndex ); + pMenuItem->SetText( GetItemSchema()->GetParticleSystemLocalizedName( iParticleIndex ) ); + pMenuItem->InvalidateLayout( true, false ); + } + } + } + + int nX, nY; + g_pVGuiInput->GetCursorPosition( nX, nY ); + m_pCustomizeMenu->SetPos( nX - 1, nY - 1 ); + + m_pCustomizeMenu->SetVisible(true); + m_pCustomizeMenu->AddActionSignalTarget(this); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::UpdateOptionsButton( void ) +{ + if ( !m_pOptionsButton ) + return; + + m_pOptionsButton->SetVisible( false ); + + if ( !m_pPlayerModelPanel->IsVisible() ) + return; + + if ( !m_item.IsValid() ) + return; + + const CEconItemDefinition *pItemData = m_item.GetItemDefinition(); + if ( !pItemData ) + return; + + bool bVisible = false; + + // Is the selected item paintable + if ( pItemData->GetCapabilities() & ITEM_CAP_PAINTABLE ) + { + bVisible = true; + } + // has multiple styles? + else if ( pItemData->GetNumSelectableStyles() > 1 ) + { + bVisible = true; + } + // can have unusual? + else if ( GetUnusualList() != NULL ) + { + bVisible = true; + } + + m_pOptionsButton->SetVisible( bVisible ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::UpdateZoomButton( void ) +{ + if ( !m_pZoomButton ) + return; + + m_pZoomButton->SetVisible( m_pPlayerModelPanel->IsVisible() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::UpdateTeamButton( void ) +{ + if ( !m_pTeamButton ) + return; + + m_pTeamButton->SetVisible( m_pPlayerModelPanel->IsVisible() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::UpdateNextWeaponButton( void ) +{ + if ( !m_pNextWeaponButton ) + return; + + if ( !m_pPlayerModelPanel->IsVisible() ) + { + m_pNextWeaponButton->SetVisible( false ); + return; + } + + bool bShowNextWeaponsButton = false; + const CUtlVector<CEconItemView*> &items = m_pPlayerModelPanel->GetCarriedItems(); + int iNumItemsArray[CLASS_LOADOUT_POSITION_COUNT]; + memset( iNumItemsArray, 0, sizeof( iNumItemsArray ) ); + FOR_EACH_VEC( items, i ) + { + CEconItemView *pItem = items[i]; + int iLoadoutSlot = pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClass ); + if ( iLoadoutSlot >= 0 && iLoadoutSlot < CLASS_LOADOUT_POSITION_COUNT ) + { + ++iNumItemsArray[iLoadoutSlot]; + } + } + bShowNextWeaponsButton |= iNumItemsArray[LOADOUT_POSITION_PRIMARY] + iNumItemsArray[LOADOUT_POSITION_SECONDARY] + iNumItemsArray[LOADOUT_POSITION_MELEE] > 1; + + m_pNextWeaponButton->SetVisible( bShowNextWeaponsButton ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::OnHideClassIconMouseover( void ) +{ + if ( m_pClassIconMouseoverLabel ) + { + m_pClassIconMouseoverLabel->SetVisible( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::OnShowClassIconMouseover( KeyValues *data ) +{ + if ( m_pClassIconMouseoverLabel ) + { + const CEconItemDefinition *pItemData = m_item.GetItemDefinition(); + bool bIsABundle = pItemData ? (pItemData->GetBundleInfo() != NULL) : false; + + // Set the text to the correct string + int iClass = data->GetInt( "class", 0 ); + if ( iClass >= TF_FIRST_NORMAL_CLASS && iClass < TF_LAST_NORMAL_CLASS ) + { + wchar_t wzLocalized[256]; + const char *pszLocString = bIsABundle ? "#Store_ClassImageMouseoverBundle" : "#Store_ClassImageMouseover"; + g_pVGuiLocalize->ConstructString_safe( wzLocalized, g_pVGuiLocalize->Find( pszLocString ), 1, g_pVGuiLocalize->Find( g_aPlayerClassNames[iClass] ) ); + m_pClassIconMouseoverLabel->SetText( wzLocalized ); + } + else + { + const char *pszLocString = bIsABundle ? "#Store_ClassImageMouseoverAllBundle" : "#Store_ClassImageMouseoverAll"; + m_pClassIconMouseoverLabel->SetText( pszLocString ); + } + + m_pClassIconMouseoverLabel->SetVisible( true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::CycleStyle( void ) +{ + if ( !m_pPlayerModelPanel ) + return; + + // Find and cycle the style based on the first item we're previewing that has + // styles. If we are previewing multiple items at the same time where more than + // one of them has styles, we'll only cycle through the names/options of the + // first one in the list. + CEconItemView *pPreviewItemView = NULL; + + const CUtlVector<CEconItemView*> &vecItems = m_pPlayerModelPanel->GetCarriedItems(); + FOR_EACH_VEC( vecItems, i ) + { + CEconItemView *pItem = vecItems[i]; + if ( pItem->GetStaticData()->GetNumStyles() && ( pItem->GetFlags() & kEconItemFlagClient_Preview ) ) + { + pPreviewItemView = pItem; + break; + } + } + + if ( !pPreviewItemView ) + return; + + // Cycle. + style_index_t unStyleCount = pPreviewItemView->GetStaticData()->GetNumStyles(); + Assert( unStyleCount >= 1 ); + + style_index_t unStyle = pPreviewItemView->GetItemStyle(); + // Default to style 0 if we're getting an invalid index + unStyle = unStyle == INVALID_STYLE_INDEX ? 0 : unStyle; + + // Try to find the next selectable style + const CEconStyleInfo *pStyle = NULL; + style_index_t unStartingStyle = unStyle; + do + { + unStyle = (unStyle + 1) % unStyleCount; + pStyle = pPreviewItemView->GetStaticData()->GetStyleInfo( unStyle ); + Assert( pStyle ); + } + while ( unStyle != unStartingStyle && ( !pStyle || !pStyle->IsSelectable() ) ); + + pPreviewItemView->SetItemStyleOverride( unStyle ); + SetCycleLabelText( m_pStyleNameLabel, pStyle->GetName() ); + + // Re-equip our held item. This causes all of our equipped items to get reloaded, and thus + // their styles updated + m_pPlayerModelPanel->SwitchHeldItemTo( m_pPlayerModelPanel->GetHeldItem() ); + m_pPlayerModelPanel->UpdatePreviewVisuals(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::SetPaint( item_definition_index_t iItemDef ) +{ + if ( !m_pPlayerModelPanel ) + return; + + if ( !IsAnythingPaintable( m_pPlayerModelPanel->GetCarriedItems() ) ) + return; + + static CSchemaAttributeDefHandle pAttribDef_Paint( "set item tint RGB" ); + static CSchemaAttributeDefHandle pAttribDef_Paint2( "set item tint RGB 2" ); + + // Find the next paint color. + const CEconItemSchema::SortedItemDefinitionMap_t &mapDefs = GetItemSchema()->GetSortedItemDefinitionMap(); + + m_unPaintRGB0 = 0; + m_unPaintRGB1 = 0; + + if ( iItemDef != INVALID_ITEM_DEF_INDEX ) + { + int iteratorName = mapDefs.FirstInorder(); + while ( iteratorName != mapDefs.InvalidIndex() ) + { + // Find the next sub + int iIndex = mapDefs[iteratorName]->GetDefinitionIndex(); + if ( iIndex == iItemDef ) + { + // Is this definition something that has paint attributes on it? + const CEconItemDefinition *pData = mapDefs[iteratorName]; + + float fRGB = 0.0f; + if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pData, pAttribDef_Paint, &fRGB ) && fRGB != 0.0f ) + { + m_unPaintRGB0 = fRGB; + + // We may or may not have a secondary paint color as well. If we don't, we just use the primary + // paint color to fill both slots. + if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pData, pAttribDef_Paint2, &fRGB ) ) + { + m_unPaintRGB1 = fRGB; + } + else + { + m_unPaintRGB1 = m_unPaintRGB0; + } + + m_unPaintDef = pData->GetDefinitionIndex(); + SetCycleLabelText( m_pPaintNameLabel, pData->GetItemBaseName() ); + } + else + { + Warning( "CTFStorePreviewItemPanelBase::SetPaint iItemDef[%d] is not paint item", iItemDef ); + } + + break; + } + + iteratorName = mapDefs.NextInorder( iteratorName ); + } + } + + if ( m_unPaintRGB0 == 0 ) + { + m_unPaintDef = 0; + SetCycleLabelText( m_pPaintNameLabel, "#Store_NoPaint" ); + } + + UpdatePaintColorsForTeam( m_pPlayerModelPanel, m_unPaintRGB0, m_unPaintRGB1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::SetStyle( style_index_t unStyle ) +{ + if ( !m_pPlayerModelPanel ) + return; + + // Find and cycle the style based on the first item we're previewing that has + // styles. If we are previewing multiple items at the same time where more than + // one of them has styles, we'll only cycle through the names/options of the + // first one in the list. + CEconItemView *pPreviewItemView = NULL; + + const CUtlVector<CEconItemView*> &vecItems = m_pPlayerModelPanel->GetCarriedItems(); + FOR_EACH_VEC( vecItems, i ) + { + CEconItemView *pItem = vecItems[i]; + if ( pItem->GetStaticData()->GetNumSelectableStyles() > 1 ) + { + pPreviewItemView = pItem; + break; + } + } + + if ( !pPreviewItemView ) + return; + + pPreviewItemView->SetItemStyleOverride( unStyle ); + const CEconStyleInfo *pStyle = pPreviewItemView->GetStaticData()->GetStyleInfo( unStyle ); + Assert( pStyle && pStyle->IsSelectable() ); + if ( pStyle ) + { + SetCycleLabelText( m_pStyleNameLabel, pStyle->GetName() ); + } + + // Re-equip our held item. This causes all of our equipped items to get reloaded, and thus + // their styles updated + m_pPlayerModelPanel->SwitchHeldItemTo( m_pPlayerModelPanel->GetHeldItem() ); + m_pPlayerModelPanel->UpdatePreviewVisuals(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::SetUnusual( uint32 iUnusualIndex ) +{ + if ( !m_pPlayerModelPanel ) + return; + + static CSchemaAttributeDefHandle pAttrDef_AttachParticleEffect( "attach particle effect" ); + static CSchemaAttributeDefHandle pAttrDef_OnTauntAttachParticleIndex( "on taunt attach particle index" ); + const CUtlVector<CEconItemView*> &vecItems = m_pPlayerModelPanel->GetCarriedItems(); + + FOR_EACH_VEC( vecItems, i ) + { + CEconItemView *pItem = vecItems[i]; + if ( pItem->GetStaticData()->GetTauntData() ) + { + const float& value_as_float = (float&)iUnusualIndex; + pItem->GetAttributeList()->SetRuntimeAttributeValue( pAttrDef_OnTauntAttachParticleIndex, value_as_float ); + } + else + { + pItem->GetAttributeList()->SetRuntimeAttributeValue( pAttrDef_AttachParticleEffect, iUnusualIndex ); + } + } + m_pPlayerModelPanel->InvalidateParticleEffects(); + m_pPlayerModelPanel->SwitchHeldItemTo( m_pPlayerModelPanel->GetHeldItem() ); +} + + +const CUtlVector< int > *CTFStorePreviewItemPanelBase::GetUnusualList() const +{ + if ( !AllowUnusualPreview() ) + return NULL; + + int iLoadoutSlot = m_item.GetStaticData()->GetLoadoutSlot( m_iCurrentClass ); + if ( IsValidPickupWeaponSlot( iLoadoutSlot ) ) + { + return GetItemSchema()->GetWeaponUnusualParticleIndexes(); + } + // is hat or whole head? + else if ( m_item.GetItemDefinition()->GetEquipRegionMask() & GetItemSchema()->GetEquipRegionBitMaskByName( "hat" ) || m_item.GetItemDefinition()->GetEquipRegionMask() & GetItemSchema()->GetEquipRegionBitMaskByName( "whole_head" ) ) + { + return GetItemSchema()->GetCosmeticUnusualParticleIndexes(); + } + else if ( IsTauntSlot( iLoadoutSlot ) ) + { + return GetItemSchema()->GetTauntUnusualParticleIndexes(); + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanelBase::CyclePaint( bool bActuallyCycle ) +{ + if ( !m_pPlayerModelPanel ) + return; + + if ( !IsAnythingPaintable( m_pPlayerModelPanel->GetCarriedItems() ) ) + return; + + static CSchemaAttributeDefHandle pAttribDef_Paint( "set item tint RGB" ); + static CSchemaAttributeDefHandle pAttribDef_Paint2( "set item tint RGB 2" ); + + // Find the next paint color. + const CEconItemSchema::SortedItemDefinitionMap_t &mapDefs = GetItemSchema()->GetSortedItemDefinitionMap(); + + m_unPaintRGB0 = 0; + m_unPaintRGB1 = 0; + + if ( bActuallyCycle || m_unPaintDef > 0 ) + { + int iteratorName = mapDefs.FirstInorder(); + while ( iteratorName != mapDefs.InvalidIndex() ) + { + // Find the next sub + int iIndex = mapDefs[iteratorName]->GetDefinitionIndex(); + if ( bActuallyCycle ? iIndex > m_unPaintDef : iIndex >= m_unPaintDef ) + { + // Is this definition something that has paint attributes on it? + const CEconItemDefinition *pData = mapDefs[iteratorName]; + + float fRGB = 0.0f; + if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pData, pAttribDef_Paint, &fRGB ) && fRGB != 0.0f ) + { + if ( V_strstr( pData->GetItemBaseName(), "Halloween" ) ) + { + iteratorName = mapDefs.NextInorder( iteratorName ); + continue; + } + + m_unPaintRGB0 = fRGB; + + // We may or may not have a secondary paint color as well. If we don't, we just use the primary + // paint color to fill both slots. + if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pData, pAttribDef_Paint2, &fRGB ) ) + { + m_unPaintRGB1 = fRGB; + } + else + { + m_unPaintRGB1 = m_unPaintRGB0; + } + + m_unPaintDef = pData->GetDefinitionIndex(); + SetCycleLabelText( m_pPaintNameLabel, pData->GetItemBaseName() ); + break; + } + } + + iteratorName = mapDefs.NextInorder( iteratorName ); + } + } + + if ( m_unPaintRGB0 == 0 ) + { + m_unPaintDef = 0; + SetCycleLabelText( m_pPaintNameLabel, "#Store_NoPaint" ); + } + + UpdatePaintColorsForTeam( m_pPlayerModelPanel, m_unPaintRGB0, m_unPaintRGB1 ); +} diff --git a/game/client/tf/vgui/store/tf_store_preview_item_base.h b/game/client/tf/vgui/store/tf_store_preview_item_base.h new file mode 100644 index 0000000..cd382a0 --- /dev/null +++ b/game/client/tf/vgui/store/tf_store_preview_item_base.h @@ -0,0 +1,102 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_STORE_PREVIEW_ITEM_BASE_H +#define TF_STORE_PREVIEW_ITEM_BASE_H +#ifdef _WIN32 +#pragma once +#endif + +#include <vgui_controls/Panel.h> +#include "store/store_preview_item.h" +#include "store/v1/tf_store_page.h" +#include "tf_shareddefs.h" +#include "tf_hud_mainmenuoverride.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CTFStorePreviewItemPanelBase : public CStorePreviewItemPanel +{ + DECLARE_CLASS_SIMPLE( CTFStorePreviewItemPanelBase, CStorePreviewItemPanel ); +protected: + // CTFStorePreviewItemPanelBase should not be intantiated directly + CTFStorePreviewItemPanelBase( vgui::Panel *pParent, const char *pResFile, const char *pPanelName, CStorePage *pOwner ); + +public: + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void OnCommand( const char *command ); + virtual void PerformLayout( void ); + virtual void OnTick( void ); + + virtual void PreviewItem( int iClass, CEconItemView *pItem, const econ_store_entry_t* pEntry=NULL ) OVERRIDE; + virtual void SetState( preview_state_t iState ); + + virtual int GetPreviewTeam() const; + + MESSAGE_FUNC_PARAMS( OnClassIconSelected, "ClassIconSelected", data ); + MESSAGE_FUNC( OnHideClassIconMouseover, "HideClassIconMouseover" ); + MESSAGE_FUNC_PARAMS( OnShowClassIconMouseover, "ShowClassIconMouseover", data ); + +protected: + void UpdateModelPanel(); + virtual void SetPlayerModelVisible( bool bVisible ); + virtual void UpdatePlayerModelButtons( void ); + virtual void UpdateCustomizeMenu( void ); + void UpdateOptionsButton( void ); + void UpdateNextWeaponButton( void ); + void UpdateZoomButton( void ); + void UpdateTeamButton( void ); + virtual void UpdateIcons( void ); + + void SetPaint( item_definition_index_t iItemDef ); + void SetStyle( style_index_t unStyle ); + void SetUnusual( uint32 iUnusualIndex ); + const CUtlVector< int > *GetUnusualList() const; + virtual bool AllowUnusualPreview() const + { +#ifdef STAGING_ONLY + // we want to be able to use this everywhere in staging for testing purpose + return true; +#else + return false; +#endif + } + + void CyclePaint( bool bActuallyCycle = true ); + void CycleStyle( void ); + void ResetHandles( void ); + + // This can be overridden to capture *any* "cycle text" that is being set, so one generic label can be used + // by a derived class if needed. Base version just sets the label's text. + virtual void SetCycleLabelText( vgui::Label *pTargetLabel, const char *pCycleText ); + + vgui::Label *m_pClassIconMouseoverLabel; + CTFPlayerModelPanel *m_pPlayerModelPanel; + CUtlVector<CStorePreviewClassIcon*> m_pClassIcons; + + int m_iCurrentClass; + int m_iCurrentHeldItem; + + item_definition_index_t m_unPaintDef; + uint32 m_unPaintRGB0; + uint32 m_unPaintRGB1; + + CExButton *m_pRotRightButton; + CExButton *m_pRotLeftButton; + CExButton *m_pNextWeaponButton; + CExButton *m_pZoomButton; + CExButton *m_pOptionsButton; + CExButton *m_pTeamButton; + vgui::Label *m_pPaintNameLabel; + vgui::Label *m_pStyleNameLabel; + + Menu *m_pCustomizeMenu; + CUtlVector< item_definition_index_t > m_vecPaintCans; +}; + +#endif // TF_STORE_PREVIEW_ITEM_H diff --git a/game/client/tf/vgui/store/v1/tf_store_page.cpp b/game/client/tf/vgui/store/v1/tf_store_page.cpp new file mode 100644 index 0000000..9d55a9c --- /dev/null +++ b/game/client/tf/vgui/store/v1/tf_store_page.cpp @@ -0,0 +1,96 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "store/v1/tf_store_page.h" +#include "store/v1/tf_store_preview_item.h" +#include "c_tf_freeaccount.h" +#include "store/store_panel.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFStorePage1::CTFStorePage1(Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData, const char *pPreviewItemResFile ) : BaseClass(parent, pPageData, pPreviewItemResFile) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CTFStorePage1::GetPageResFile( void ) +{ + Assert( !"No code should currently reference the old store!" ); + + return m_pPageData->m_pchPageRes; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage1::OnPageShow( void ) +{ + BaseClass::OnPageShow(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage1::OnCommand( const char *command ) +{ + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage1::OnItemDetails( vgui::Panel *panel ) +{ + BaseClass::OnItemDetails( panel ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage1::ShowPreview( int iClass, const econ_store_entry_t* pEntry ) +{ + BaseClass::ShowPreview( iClass, pEntry ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage1::UpdateFilterComboBox( void ) +{ + BaseClass::UpdateFilterComboBox(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage1::GetFiltersForDef( GameItemDefinition_t *pDef, CUtlVector<int> *pVecFilters ) +{ + return BaseClass::GetFiltersForDef( pDef, pVecFilters ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage1::OnTick( void ) +{ + BaseClass::OnTick(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CStorePreviewItemPanel *CTFStorePage1::CreatePreviewPanel( void ) +{ + return new CTFStorePreviewItemPanel1( this, m_pPreviewItemResFile, "storepreviewitem", this ); +} diff --git a/game/client/tf/vgui/store/v1/tf_store_page.h b/game/client/tf/vgui/store/v1/tf_store_page.h new file mode 100644 index 0000000..814bc01 --- /dev/null +++ b/game/client/tf/vgui/store/v1/tf_store_page.h @@ -0,0 +1,39 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_STORE_PAGE1_H +#define TF_STORE_PAGE1_H +#ifdef _WIN32 +#pragma once +#endif + +#include "store/tf_store_page_base.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CTFStorePage1 : public CTFStorePageBase +{ + DECLARE_CLASS_SIMPLE( CTFStorePage1, CTFStorePageBase ); +public: + CTFStorePage1( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData, const char *pPreviewItemResFile = NULL ); + + virtual const char *GetPageResFile( void ); + virtual void OnCommand( const char *command ); + virtual void ShowPreview( int iClass, const econ_store_entry_t* pEntry ); + + MESSAGE_FUNC( OnPageShow, "PageShow" ); + MESSAGE_FUNC_PTR( OnItemDetails, "ItemDetails", panel ); + + virtual void UpdateFilterComboBox( void ); + virtual void GetFiltersForDef( GameItemDefinition_t *pDef, CUtlVector<int> *pVecFilters ); + virtual void OnTick( void ); + + virtual CStorePreviewItemPanel *CreatePreviewPanel( void ); +}; + +#endif // TF_STORE_PAGE1_H diff --git a/game/client/tf/vgui/store/v1/tf_store_page_maps.cpp b/game/client/tf/vgui/store/v1/tf_store_page_maps.cpp new file mode 100644 index 0000000..4b8a895 --- /dev/null +++ b/game/client/tf/vgui/store/v1/tf_store_page_maps.cpp @@ -0,0 +1,30 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "store/v1/tf_store_page_maps.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFStorePage_Maps::CTFStorePage_Maps( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData ) +: BaseClass( parent, pPageData, "Resource/UI/econ/store/v1/StorePreviewItemPanel_Maps.res" ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage_Maps::OnPageShow() +{ + BaseClass::OnPageShow(); + + SetDetailsVisible( false ); +}
\ No newline at end of file diff --git a/game/client/tf/vgui/store/v1/tf_store_page_maps.h b/game/client/tf/vgui/store/v1/tf_store_page_maps.h new file mode 100644 index 0000000..30fc0dc --- /dev/null +++ b/game/client/tf/vgui/store/v1/tf_store_page_maps.h @@ -0,0 +1,33 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef STORE_PAGE_MAPS_H +#define STORE_PAGE_MAPS_H +#ifdef _WIN32 +#pragma once +#endif + +#include "store/v1/tf_store_page.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CTFStorePage_Maps : public CTFStorePage1 +{ + DECLARE_CLASS_SIMPLE( CTFStorePage_Maps, CTFStorePage1 ); +public: + CTFStorePage_Maps( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData ); + virtual ~CTFStorePage_Maps() {} + + virtual const char* GetPageResFile() { return "Resource/UI/econ/store/v1/StorePage_Maps.res"; } + + virtual void OnPageShow( void ); + +protected: +}; + +#endif // STORE_PAGE_MAPS_H diff --git a/game/client/tf/vgui/store/v1/tf_store_panel.cpp b/game/client/tf/vgui/store/v1/tf_store_panel.cpp new file mode 100644 index 0000000..4daf037 --- /dev/null +++ b/game/client/tf/vgui/store/v1/tf_store_panel.cpp @@ -0,0 +1,69 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "store/v1/tf_store_page.h" +#include "store/v1/tf_store_panel.h" +#include "store/store_page_halloween.h" +#include "store/store_page_new.h" +#include "store/v1/tf_store_page_maps.h" +#include "store/store_viewcart.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFStorePanel1::CTFStorePanel1( vgui::Panel *parent ) : CTFBaseStorePanel(parent) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePanel1::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePanel1::OnThink() +{ + BaseClass::OnThink(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePanel1::PostTransactionCompleted( void ) +{ + BaseClass::PostTransactionCompleted(); +} + +//----------------------------------------------------------------------------- +// Purpose: Static store page factory. +//----------------------------------------------------------------------------- +CStorePage *CTFStorePanel1::CreateStorePage( const CEconStoreCategoryManager::StoreCategory_t *pPageData ) +{ + if ( pPageData ) + { + if ( !Q_strcmp( pPageData->m_pchPageClass, "CStorePage_SpecialPromo" ) ) + return new CTFStorePage_SpecialPromo( this, pPageData ); + + if ( !Q_strcmp( pPageData->m_pchPageClass, "CStorePage_Maps" ) ) + return new CTFStorePage_Maps( this, pPageData ); + + if ( !Q_strcmp( pPageData->m_pchPageClass, "CStorePage_Popular" ) ) + return new CTFStorePage_Popular( this, pPageData ); + } + + // Default, standard store page. + return new CTFStorePage1( this, pPageData ); +} diff --git a/game/client/tf/vgui/store/v1/tf_store_panel.h b/game/client/tf/vgui/store/v1/tf_store_panel.h new file mode 100644 index 0000000..cac6b00 --- /dev/null +++ b/game/client/tf/vgui/store/v1/tf_store_panel.h @@ -0,0 +1,38 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_STORE_PANEL1_H +#define TF_STORE_PANEL1_H +#ifdef _WIN32 +#pragma once +#endif + +#include "store/tf_store_panel_base.h" + +class CStorePage; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CTFStorePanel1 : public CTFBaseStorePanel +{ + DECLARE_CLASS_SIMPLE( CTFStorePanel1, CTFBaseStorePanel ); +public: + CTFStorePanel1( vgui::Panel *parent ); + + // UI Layout + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void OnThink(); + + // GC Management + virtual void PostTransactionCompleted( void ); + +private: + virtual CStorePage *CreateStorePage( const CEconStoreCategoryManager::StoreCategory_t *pPageData ); +}; + +#endif // TF_STORE_PANEL1_H diff --git a/game/client/tf/vgui/store/v1/tf_store_preview_item.cpp b/game/client/tf/vgui/store/v1/tf_store_preview_item.cpp new file mode 100644 index 0000000..7f23513 --- /dev/null +++ b/game/client/tf/vgui/store/v1/tf_store_preview_item.cpp @@ -0,0 +1,100 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "store/v1/tf_store_preview_item.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFStorePreviewItemPanel1::CTFStorePreviewItemPanel1( vgui::Panel *pParent, const char *pResFile, const char *pPanelName, CStorePage *pOwner ) +: BaseClass( pParent, pResFile, "storepreviewitem", pOwner ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel1::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel1::PerformLayout( void ) +{ + BaseClass::PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel1::OnCommand( const char *command ) +{ + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel1::OnClassIconSelected( KeyValues *data ) +{ + BaseClass::OnClassIconSelected( data ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel1::OnHideClassIconMouseover( void ) +{ + BaseClass::OnHideClassIconMouseover(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel1::OnShowClassIconMouseover( KeyValues *data ) +{ + BaseClass::OnShowClassIconMouseover( data ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel1::PreviewItem( int iClass, CEconItemView *pItem, const econ_store_entry_t* pEntry ) +{ + BaseClass::PreviewItem( iClass, pItem, pEntry ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel1::SetState( preview_state_t iState ) +{ + BaseClass::SetState( iState ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel1::UpdateIcons( void ) +{ + BaseClass::UpdateIcons(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel1::OnTick( void ) +{ + BaseClass::OnTick(); +} diff --git a/game/client/tf/vgui/store/v1/tf_store_preview_item.h b/game/client/tf/vgui/store/v1/tf_store_preview_item.h new file mode 100644 index 0000000..02994c4 --- /dev/null +++ b/game/client/tf/vgui/store/v1/tf_store_preview_item.h @@ -0,0 +1,41 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_STORE_PREVIEW_ITEM1_H +#define TF_STORE_PREVIEW_ITEM1_H +#ifdef _WIN32 +#pragma once +#endif + +#include "store/tf_store_preview_item_base.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CTFStorePreviewItemPanel1 : public CTFStorePreviewItemPanelBase +{ + DECLARE_CLASS_SIMPLE( CTFStorePreviewItemPanel1, CTFStorePreviewItemPanelBase ); +public: + CTFStorePreviewItemPanel1( vgui::Panel *pParent, const char *pResFile, const char *pPanelName, CStorePage *pOwner ); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void OnCommand( const char *command ); + virtual void PerformLayout( void ); + virtual void OnTick( void ); + + virtual void PreviewItem( int iClass, CEconItemView *pItem, const econ_store_entry_t* pEntry=NULL ) OVERRIDE; + virtual void SetState( preview_state_t iState ); + + MESSAGE_FUNC_PARAMS( OnClassIconSelected, "ClassIconSelected", data ); + MESSAGE_FUNC( OnHideClassIconMouseover, "HideClassIconMouseover" ); + MESSAGE_FUNC_PARAMS( OnShowClassIconMouseover, "ShowClassIconMouseover", data ); + +private: + virtual void UpdateIcons( void ); +}; + +#endif // TF_STORE_PREVIEW_ITEM1_H diff --git a/game/client/tf/vgui/store/v2/tf_store_mapstamps_info_dialog.cpp b/game/client/tf/vgui/store/v2/tf_store_mapstamps_info_dialog.cpp new file mode 100644 index 0000000..1ee55a8 --- /dev/null +++ b/game/client/tf/vgui/store/v2/tf_store_mapstamps_info_dialog.cpp @@ -0,0 +1,83 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "store/v2/tf_store_mapstamps_info_dialog.h" +#include "tf_mouseforwardingpanel.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +DECLARE_BUILD_FACTORY( CTFMapStampsInfoDialog ); + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFMapStampsInfoDialog::CTFMapStampsInfoDialog( vgui::Panel *pParent, const char *pName ) +: BaseClass( pParent, "MapStampsInfoDialog" ) +{ + m_pBgPanel = new CMouseMessageForwardingPanel( this, "BgPanel" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapStampsInfoDialog::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "Resource/UI/econ/store/v2/StoreMapStampsInfoDialog.res" ); + + m_pDlgFrame = dynamic_cast<EditablePanel *>( FindChildByName( "DialogFrame" ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapStampsInfoDialog::PerformLayout( void ) +{ + BaseClass::PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapStampsInfoDialog::OnCommand( const char *command ) +{ + if ( !V_strnicmp( command, "close", 5 ) ) + { + DoClose(); + } + else + { + BaseClass::OnCommand( command ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapStampsInfoDialog::OnMouseReleased(MouseCode code) +{ + BaseClass::OnMouseReleased( code ); + + if ( m_pDlgFrame && !m_pDlgFrame->IsCursorOver() ) + { + DoClose(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapStampsInfoDialog::DoClose() +{ + SetVisible( false ); + MarkForDeletion(); +}
\ No newline at end of file diff --git a/game/client/tf/vgui/store/v2/tf_store_mapstamps_info_dialog.h b/game/client/tf/vgui/store/v2/tf_store_mapstamps_info_dialog.h new file mode 100644 index 0000000..7486e8c --- /dev/null +++ b/game/client/tf/vgui/store/v2/tf_store_mapstamps_info_dialog.h @@ -0,0 +1,36 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_MAPS_INFO_DIALOG_H +#define TF_MAPS_INFO_DIALOG_H +#ifdef _WIN32 +#pragma once +#endif + +#include "vgui_controls/EditablePanel.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CTFMapStampsInfoDialog : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CTFMapStampsInfoDialog, vgui::EditablePanel ); +public: + CTFMapStampsInfoDialog( vgui::Panel *pParent, const char *pName = "" ); + + void DoClose(); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void OnCommand( const char *command ); + virtual void PerformLayout( void ); + virtual void OnMouseReleased(vgui::MouseCode code); + + Panel *m_pBgPanel; + EditablePanel *m_pDlgFrame; +}; + +#endif // TF_MAPS_INFO_DIALOG_H diff --git a/game/client/tf/vgui/store/v2/tf_store_page2.cpp b/game/client/tf/vgui/store/v2/tf_store_page2.cpp new file mode 100644 index 0000000..c7ac2f2 --- /dev/null +++ b/game/client/tf/vgui/store/v2/tf_store_page2.cpp @@ -0,0 +1,849 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "store/v2/tf_store_page2.h" +#include "store/v2/tf_store_preview_item2.h" +#include "c_tf_freeaccount.h" +#include "store/store_panel.h" +#include "store/tf_store.h" +#include "navigationpanel.h" +#include "econ/store/store_page_new.h" +#include "econ_item_system.h" +#include "c_tf_gamestats.h" +#include "vgui_controls/TextImage.h" +#include "econ_item_description.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +static const int kNumSortTypes = 5; +ItemSortTypeData_t g_StoreSortTypes[ kNumSortTypes ] = +{ + { "#Store_SortType_DateNewest", kEconStoreSortType_DateNewest }, + { "#Store_SortType_DateOldest", kEconStoreSortType_DateOldest }, + { "#Store_SortType_HighestPrice", kEconStoreSortType_Price_HighestToLowest }, + { "#Store_SortType_LowestPrice", kEconStoreSortType_Price_LowestToHighest }, + { "#Store_SortType_Alphabetical", kEconStoreSortType_Name_AToZ }, +}; + +class CClassFilterTooltip : public vgui::BaseTooltip +{ +public: + CClassFilterTooltip( CTFStorePage2 *pStorePage ) + : BaseTooltip( pStorePage ) + , m_pStorePage( pStorePage ) + { + } + + CTFStorePage2 *m_pStorePage; + + virtual void SetText(const char *text) + { + m_pStorePage->m_pClassFilterTooltipLabel->SetText( text ); + } + + virtual void ShowTooltip(Panel *currentPanel) + { + int x = 0; + int y = currentPanel->GetTall(); + currentPanel->LocalToScreen( x, y ); + m_pStorePage->ScreenToLocal( x, y ); + + // The tooltip wants to be centered around the given panel, but it's constrained by the left and right boundaries + // of the navigation panel. + if ( m_pStorePage->m_pClassFilterButtons ) + { + // Right side of tooltip should not pass right boundary of nav panel + int aClassFilterNavPos[2] = { 0, 0 }; + m_pStorePage->m_pClassFilterButtons->GetPos( aClassFilterNavPos[0], aClassFilterNavPos[1] ); + const int nTipWide = m_pStorePage->m_pClassFilterTooltipLabel->GetWide(); + x = clamp( + x + ( currentPanel->GetWide() - nTipWide ) / 2, + aClassFilterNavPos[0], + aClassFilterNavPos[0] + m_pStorePage->m_pClassFilterButtons->GetWide() - nTipWide ); + } + + m_pStorePage->m_pClassFilterTooltipLabel->SetPos( x, y ); + m_pStorePage->m_pClassFilterTooltipLabel->SetVisible( true ); + } + virtual void HideTooltip() + { + m_pStorePage->m_pClassFilterTooltipLabel->SetVisible( false ); + } +}; + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFStorePage2::CTFStorePage2(Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData, const char *pPreviewItemResFile ) +: BaseClass( parent, pPageData, pPreviewItemResFile ), + m_pSubcategoriesFilterCombo( NULL ), + m_pSortByCombo( NULL ), + m_pHomeCategoryTabs( NULL ), + m_pClassFilterButtons( NULL ), + m_pNameFilterTextEntry( NULL ), + m_pSubcategoriesFilterLabel( NULL ), + m_iCurrentSubcategory( 2 ), // Switch to Featured items tab on home page when store launches; BRETT SAID I COULD DO THIS + m_pClassFilterTooltipLabel( NULL ), + m_pClassFilterTooltip( NULL ), + m_flFilterItemTime( 0.0f ) +{ + const CEconStorePriceSheet *pPriceSheet = EconUI()->GetStorePanel()->GetPriceSheet(); + + if ( IsHomePage() ) + { + bool bAtLeastOneItemIsOnSale = false; + if ( pPriceSheet ) + { + const CEconStorePriceSheet::StoreEntryMap_t& mapStoreEntries = pPriceSheet->GetEntries(); + FOR_EACH_MAP_FAST( mapStoreEntries, i ) + { + if ( mapStoreEntries[i].IsOnSale( EconUI()->GetStorePanel()->GetCurrency() ) ) + { + bAtLeastOneItemIsOnSale = true; + break; + } + } + } + + m_pHomeCategoryTabs = new CNavigationPanel( this, "ItemCategoryTabs" ); + + FOR_EACH_VEC( m_pPageData->m_vecSubcategories, i ) + { + // Skip over adding the "On Sale!" tab if no items are currently on sale. + const char *pchName = m_pPageData->m_vecSubcategories[i]->m_pchName; + bool bIsOnSaleCategory = m_pPageData->m_vecSubcategories[i]->m_unID == CEconStoreCategoryManager::k_CategoryID_OnSale; + + // Skip over the "Top Sellers" tab as we're replacing it with the 'Starter Packs' tab for now + bool bIsPopularCategory = m_pPageData->m_vecSubcategories[i]->m_unID == CEconStoreCategoryManager::k_CategoryID_Popular; + + // Skip over the "New" tab as we're replacing it with the 'Featured' tab for now + bool bIsNew = m_pPageData->m_vecSubcategories[i]->m_unID == CEconStoreCategoryManager::k_CategoryID_New; + + // We include all of the sale items in the Featured tab currently, so we don't need to add these categories back in at the moment + bAtLeastOneItemIsOnSale = false; + + if ( ( !bIsPopularCategory && !bIsOnSaleCategory && !bIsNew ) || ( bAtLeastOneItemIsOnSale && bIsOnSaleCategory ) ) + { + m_pHomeCategoryTabs->AddButton( i, pchName ); + } + } + } +} + +//----------------------------------------------------------------------------- +CTFStorePage2::~CTFStorePage2() +{ + delete m_pClassFilterTooltip; + m_pClassFilterTooltip = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage2::OnPostCreate() +{ + BaseClass::OnPostCreate(); + + m_bShouldDeletePreviewPanel = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFStorePage2::HasSubcategories() const +{ + return m_pPageData && m_pPageData->HasSubcategories(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage2::PerformLayout() +{ + BaseClass::PerformLayout(); + + if ( m_pSubcategoriesFilterCombo ) + { + m_pSubcategoriesFilterCombo->SetVisible( HasSubcategories() ); + } + + if ( m_pSubcategoriesFilterLabel ) + { + m_pSubcategoriesFilterLabel->SetVisible( HasSubcategories() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage2::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + m_pNameFilterTextEntry = FindControl<vgui::TextEntry>( "NameFilterTextEntry" ); + if ( m_pNameFilterTextEntry ) + { + m_pNameFilterTextEntry->AddActionSignalTarget( this ); + } + + m_pSortByCombo = dynamic_cast< ComboBox * >( FindChildByName( "SortFilterComboBox" ) ); + if ( m_pSortByCombo ) + { + m_pSortByCombo->RemoveAll(); + vgui::HFont hFont = pScheme->GetFont( "HudFontSmallestBold", true ); + m_pSortByCombo->SetFont( hFont ); + KeyValues *pKeyValues = new KeyValues( "data" ); + for ( int i = 0; i < ARRAYSIZE(g_StoreSortTypes); i++ ) + { + pKeyValues->SetInt( "sortby", i ); + m_pSortByCombo->AddItem( g_StoreSortTypes[i].szSortDesc, pKeyValues ); + } + pKeyValues->deleteThis(); + m_pSortByCombo->ActivateItemByRow( 0 ); + } + + m_pSubcategoriesFilterCombo = dynamic_cast< ComboBox * >( FindChildByName( "SubcategoryFilterComboBox" ) ); + if ( m_pSubcategoriesFilterCombo ) + { + vgui::HFont hFont = pScheme->GetFont( "HudFontSmallestBold", true ); + m_pSubcategoriesFilterCombo->SetFont( hFont ); + + m_pSubcategoriesFilterCombo->RemoveAll(); + + if ( m_pPageData ) + { + // Add "all items" explicitly + KeyValuesAD kvAllItems( "data" ); + kvAllItems->SetInt( "index", GetNumSubcategories() ); + m_pSubcategoriesFilterCombo->AddItem( "#Store_ClassFilter_None", kvAllItems ); + + FOR_EACH_VEC( m_pPageData->m_vecSubcategories, i ) + { + KeyValues *pData = new KeyValues( "data" ); + pData->SetInt( "index", i ); + + m_pSubcategoriesFilterCombo->AddItem( m_pPageData->m_vecSubcategories[i]->m_pchName, pData ); + + pData->deleteThis(); + } + } + + // Move to "All items" selected + m_pSubcategoriesFilterCombo->ActivateItemByRow( 0 ); + + m_pSubcategoriesFilterCombo->GetComboButton()->SetFgColor( Color( 117,107,94,255 ) ); + m_pSubcategoriesFilterCombo->GetComboButton()->SetDefaultColor( Color( 117,107,94,255), Color( 0,0,0,0) ); + m_pSubcategoriesFilterCombo->GetComboButton()->SetArmedColor( Color( 117,107,94,255), Color( 0,0,0,0) ); + m_pSubcategoriesFilterCombo->GetComboButton()->SetDepressedColor( Color( 117,107,94,255), Color( 0,0,0,0) ); + } + + m_pClassFilterButtons = dynamic_cast< CNavigationPanel * >( FindChildByName( "ClassFilterNavPanel" ) ); + m_pSubcategoriesFilterLabel = dynamic_cast< CExLabel * >( FindChildByName( "SubcategoryFiltersLabel" ) ); + + m_pClassFilterTooltipLabel = dynamic_cast< CExLabel * >( FindChildByName( "ClassFilterTooltipLabel" ) ); + if ( m_pClassFilterTooltipLabel && m_pClassFilterButtons ) + { + m_pClassFilterTooltip = new CClassFilterTooltip(this); + for ( int i = 0 ; i < m_pClassFilterButtons->NumButtons() ; ++i ) + { + CExButton *pButton = m_pClassFilterButtons->GetButton( i ); + CUtlString sSaveText = pButton->GetEffectiveTooltipText(); + pButton->SetTooltip( m_pClassFilterTooltip, sSaveText ); + } + } + + // Setup title text in home page + if ( IsHomePage() && g_pVGuiLocalize ) + { + CExLabel *pTitleLabel = dynamic_cast<CExLabel *>( FindChildByName( "TitleLabel" ) ); + wchar_t *pHomePageTitle = g_pVGuiLocalize->Find( "#Store_HomePageTitle" ); + wchar_t *pRedText = g_pVGuiLocalize->Find( "#Store_HomePageTitleRedText" ); + + if ( pTitleLabel && pHomePageTitle && pRedText ) + { + const store_promotion_spend_for_free_item_t *pPromotion = EconUI()->GetStorePanel()->GetPriceSheet()->GetStorePromotion_SpendForFreeItem(); + + ECurrency eCurrency = EconUI()->GetStorePanel()->GetCurrency(); + AssertMsg( eCurrency >= k_ECurrencyUSD && eCurrency < k_ECurrencyMax, "Invalid currency!" ); + int iPriceThreshold = pPromotion->m_rgusPriceThreshold[ eCurrency ]; + wchar_t wszPriceThreshold[ kLocalizedPriceSizeInChararacters ]; + MakeMoneyString( wszPriceThreshold, ARRAYSIZE( wszPriceThreshold ), iPriceThreshold, EconUI()->GetStorePanel()->GetCurrency() ); + + static wchar_t wszText[512]; + g_pVGuiLocalize->ConstructString_safe( wszText, pHomePageTitle, 2, pRedText, wszPriceThreshold ); + + pTitleLabel->SetText( wszText ); + TextImage *pTextImage = pTitleLabel->GetTextImage(); + const wchar_t *pFound = wcsstr( wszText, pRedText ); + if ( pTextImage && pFound ) + { + const int iRedTextPos = pFound - wszText; + const int nRedTextLen = wcslen( pRedText ); + pTextImage->ClearColorChangeStream(); + pTextImage->AddColorChange( Color(200,80,60,255), iRedTextPos ); + pTextImage->AddColorChange( pTitleLabel->GetFgColor(), iRedTextPos + nRedTextLen ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CTFStorePage2::GetPageResFile( void ) +{ + if ( IsHomePage() ) + { + Assert( ShouldUseNewStore() ); + + return "Resource/UI/econ/store/v2/StoreHome_Premium.res"; + } + + return m_pPageData->m_pchPageRes; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage2::OnPageShow( void ) +{ + BaseClass::OnPageShow(); + + if ( m_pClassFilterButtons ) + { + m_pClassFilterButtons->UpdateButtonSelectionStates( 0 ); + } + + ClearNameFilter( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage2::OnCommand( const char *command ) +{ + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage2::OnItemDetails( vgui::Panel *panel ) +{ + CStoreItemControlsPanel *pControlsPanel = dynamic_cast< CStoreItemControlsPanel * >( panel ); + if ( pControlsPanel ) + { + const econ_store_entry_t *pEntry = pControlsPanel->GetItem(); + if ( pEntry && m_pPreviewPanel ) + { + ShowPreviewWindow( pEntry->GetItemDefinitionIndex() ); + } + } +} + +//----------------------------------------------------------------------------- +void CTFStorePage2::OnItemDefDetails( KeyValues *pData ) +{ + ShowPreviewWindow( pData->GetInt("ItemDefIndex", -1) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage2::ShowPreviewWindow( item_definition_index_t usDefIndex ) +{ + if ( m_pPreviewPanel ) + { + CEconItemView itemData; + itemData.Init( usDefIndex, AE_UNIQUE, AE_USE_SCRIPT_VALUE, true ); + itemData.SetClientItemFlags( kEconItemFlagClient_Preview ); + + m_pPreviewPanel->PreviewItem( 0, &itemData ); + m_pPreviewPanel->SetState( PS_ITEM ); // Adding this, since without it, only the item icon shows up + m_pPreviewPanel->SetVisible( true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage2::OnNavButtonSelected( KeyValues *pData ) +{ + Panel *pPanel = (Panel *)pData->GetPtr( "panel" ); + + if ( pPanel == m_pClassFilterButtons ) + { + const int iFilter = pData->GetInt( "userdata", -1 ); AssertMsg( iFilter >= 0, "Bad filter" ); + if ( iFilter < 0 ) + return; + + SetFilter( iFilter ); + m_iCurrentPage = 0; + UpdateModelPanels(); + + if ( m_pCheckoutButton ) + { + m_pCheckoutButton->RequestFocus(); + } + + C_CTFGameStats::ImmediateWriteInterfaceEvent( "store_page_2_nav(class)", CFmtStr( "%i", iFilter ).Access() ); + } + else if ( pPanel == m_pHomeCategoryTabs ) + { + int iSelectedTab = pData->GetInt( "userdata", -1 ); + if ( iSelectedTab < 0 ) + return; + + if ( !m_pPageData || !m_pPageData->m_vecSubcategories.IsValidIndex( iSelectedTab ) ) + return; + + m_iCurrentSubcategory = iSelectedTab; + + FOR_EACH_VEC( m_vecItemPanels, i ) + { + // Delete the old one + m_vecItemPanels[i].m_pStorePricePanel->MarkForDeletion(); + + // Create a new one and cache it + CStorePricePanel *pPricePanel = CreatePricePanel( i ); + pPricePanel->InvalidateLayout(); + pPricePanel->SetMouseInputEnabled( false ); + pPricePanel->SetKeyBoardInputEnabled( false ); + + m_vecItemPanels[i].m_pStorePricePanel = pPricePanel; + + // Setup the mouse handler + m_vecItemPanels[i].m_pItemControlsPanel->SetMouseHoverHandler( pPricePanel ); + } + + m_iCurrentPage = 0; + + UpdateFilteredItems(); + UpdateModelPanels(); + + C_CTFGameStats::ImmediateWriteInterfaceEvent( "store_page_2_nav(category)", CFmtStr( "%i", iSelectedTab ).Access() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called when text changes in combo box +//----------------------------------------------------------------------------- +void CTFStorePage2::OnTextChanged( KeyValues *data ) +{ + Panel *pPanel = reinterpret_cast<vgui::Panel *>( data->GetPtr("panel") ); + + vgui::TextEntry *pTextEntry = dynamic_cast<vgui::TextEntry *>( pPanel ); + + if ( pTextEntry ) + { + if ( pTextEntry == m_pNameFilterTextEntry ) + { + m_wNameFilter.RemoveAll(); + if ( m_pNameFilterTextEntry->GetTextLength() ) + { + m_wNameFilter.EnsureCount( m_pNameFilterTextEntry->GetTextLength() + 1 ); + m_pNameFilterTextEntry->GetText( m_wNameFilter.Base(), m_wNameFilter.Count() * sizeof(wchar_t) ); + V_wcslower( m_wNameFilter.Base() ); + } + m_flFilterItemTime = gpGlobals->curtime + 0.5f; + return; + } + } + + vgui::ComboBox *pComboBox = dynamic_cast<vgui::ComboBox *>( pPanel ); + + if ( pComboBox ) + { + if ( pComboBox == m_pSubcategoriesFilterCombo ) + { + // the class selection combo box changed, update class details + KeyValues *pUserData = m_pSubcategoriesFilterCombo->GetActiveItemUserData(); + if ( !pUserData ) + return; + + // Update current subcategory filter + m_iCurrentSubcategory = pUserData->GetInt( "index", 0 ); + m_bFilterDirty = true; + + m_iCurrentPage = 0; + UpdateModelPanels(); + + if ( m_pCheckoutButton ) + { + m_pCheckoutButton->RequestFocus(); + } + + C_CTFGameStats::ImmediateWriteInterfaceEvent( "store_page_2_nav(subcategories)", CFmtStr( "%i", m_iCurrentSubcategory ).Access() ); + } + + else if ( pComboBox == m_pSortByCombo ) + { + // the class selection combo box changed, update class details + KeyValues *pUserData = m_pSortByCombo->GetActiveItemUserData(); + if ( !pUserData ) + return; + + int iSortTypeSelectionIndex = pUserData->GetInt( "sortby", -1 ); + m_bFilterDirty = true; + if ( iSortTypeSelectionIndex >= 0 ) + { + eEconStoreSortType iSortType = (eEconStoreSortType)g_StoreSortTypes[iSortTypeSelectionIndex].iSortType; + + UpdateFilteredItems(); + UpdateModelPanels(); + + C_CTFGameStats::ImmediateWriteInterfaceEvent( "store_page_2_nav(sort_by)", CFmtStr( "%i", iSortType ).Access() ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage2::GetFiltersForDef( GameItemDefinition_t *pDef, CUtlVector<int> *pVecFilters ) +{ + BaseClass::GetFiltersForDef( pDef, pVecFilters ); +} + +bool CTFStorePage2::FindAndSelectEntry( const econ_store_entry_t *pEntry ) +{ + m_iCurrentSubcategory = GetAllSubcategoriesIndex(); + m_bFilterDirty = true; + if ( BaseClass::FindAndSelectEntry( pEntry ) ) + { + ivgui()->PostMessage( GetVPanel(), new KeyValues( "ItemDefDetails", "ItemDefIndex", pEntry->GetItemDefinitionIndex() ), GetVPanel() ); + return true; + } + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage2::ClearNameFilter( bool bUpdateModelPanels ) +{ + // don't do anything if we don't have any filter + if ( m_wNameFilter.Count() == 0 ) + return; + + m_wNameFilter.RemoveAll(); + if( m_pNameFilterTextEntry ) + { + m_pNameFilterTextEntry->SetText( "" ); + } + + if ( bUpdateModelPanels ) + { + m_flFilterItemTime = gpGlobals->curtime + 0.1f; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CTFStorePage2::GetAllSubcategoriesIndex() const +{ + return m_pPageData ? m_pPageData->GetNumSubcategories() : 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage2::UpdateFilteredItems() +{ + if ( m_bFilterDirty ) + { + // Make sure list of unfiltered items is sorted + m_pSortByCombo = dynamic_cast< ComboBox * >( FindChildByName( "SortFilterComboBox" ) ); + if ( m_pSortByCombo ) + { + KeyValues *pUserData = m_pSortByCombo->GetActiveItemUserData(); + if ( pUserData ) + { + int iSortTypeSelectionIndex = pUserData->GetInt( "sortby", -1 ); + if ( iSortTypeSelectionIndex >= 0 ) + { + eEconStoreSortType iSortType = (eEconStoreSortType)g_StoreSortTypes[iSortTypeSelectionIndex].iSortType; + CEconStorePriceSheet *pPriceSheet = EconUI()->GetStorePanel()->GetPriceSheetForEdit(); + pPriceSheet->SetEconStoreSortType( iSortType ); + + CEconStoreCategoryManager::StoreCategory_t *pPageData = const_cast< CEconStoreCategoryManager::StoreCategory_t * >( m_pPageData ); + pPageData->m_vecEntries.SetLessContext( pPriceSheet ); + pPageData->m_vecEntries.RedoSort( true ); + } + } + } + } + + if ( !IsHomePage() ) + { + BaseClass::UpdateFilteredItems(); + return; + } + + m_FilteredEntries.Purge(); + + // Subcategories on the home page are special cases, as they aren't based on + // an item's tags. + const StoreCategoryID_t unSubcategoryID = m_pPageData->m_vecSubcategories[ m_iCurrentSubcategory ]->m_unID; + + CStorePanel *pStorePanel = EconUI()->GetStorePanel(); + if ( !pStorePanel ) + return; + + FOR_EACH_VEC( m_vecItemPanels, idx ) + { + m_vecItemPanels[idx].m_pItemModelPanel->SetShowQuantity( unSubcategoryID != CEconStoreCategoryManager::k_CategoryID_Popular ); + } + + if ( unSubcategoryID == CEconStoreCategoryManager::k_CategoryID_Popular ) + { + const CUtlVector<uint32>& popularItems = pStorePanel->GetPopularItems(); + for ( int i = 0; i < MIN( m_vecItemPanels.Count(), popularItems.Count() ); ++i ) + { + const econ_store_entry_t *pEntry = pStorePanel->GetPriceSheet()->GetEntry( popularItems[i] ); + m_FilteredEntries.AddToTail( pEntry ); + } + } + else if ( unSubcategoryID == CEconStoreCategoryManager::k_CategoryID_New ) + { + // Add all new items + const CUtlMap< uint16, econ_store_entry_t > &mapEntries = pStorePanel->GetPriceSheet()->GetEntries(); + FOR_EACH_MAP_FAST( mapEntries, i ) + { + const econ_store_entry_t *pCurEntry = &mapEntries[i]; + if ( pCurEntry->m_bNew ) + { + m_FilteredEntries.AddToTail( pCurEntry ); + } + } + } + else if ( unSubcategoryID == CEconStoreCategoryManager::k_CategoryID_OnSale ) + { + ECurrency eCurrency = EconUI()->GetStorePanel()->GetCurrency(); + + // Add all entries that are on sale + const CEconStorePriceSheet::StoreEntryMap_t &mapEntries = pStorePanel->GetPriceSheet()->GetEntries(); + FOR_EACH_MAP_FAST( mapEntries, i ) + { + const econ_store_entry_t *pCurEntry = &mapEntries[i]; + + if ( pCurEntry->IsOnSale( eCurrency ) ) + { + m_FilteredEntries.AddToTail( pCurEntry ); + } + } + } + else if ( unSubcategoryID == CEconStoreCategoryManager::k_CategoryID_Featured ) + { + const CEconStorePriceSheet::FeaturedItems_t& vecFeaturedItems = pStorePanel->GetPriceSheet()->GetFeaturedItems(); + FOR_EACH_VEC( vecFeaturedItems, i ) + { + const econ_store_entry_t *pEntry = pStorePanel->GetPriceSheet()->GetEntry( vecFeaturedItems[i] ); + if ( pEntry ) + { + m_FilteredEntries.AddToTail( pEntry ); + } + else + { + AssertMsg( 0, "trying to add featured item that's not in the store price sheet.\n" ); + } + } + } + else if ( unSubcategoryID == CEconStoreCategoryManager::k_CategoryID_ClassBundles ) + { + // Let's find the class bundles + const CEconStorePriceSheet::StoreEntryMap_t &mapEntries = pStorePanel->GetPriceSheet()->GetEntries(); + FOR_EACH_MAP_FAST( mapEntries, i ) + { + const econ_store_entry_t *pCurEntry = &mapEntries[i]; + + if ( pCurEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_ClassBundles ) ) + { + m_FilteredEntries.AddToTail( pCurEntry ); + } + } + + // Sort by date to get the weapon bundles before the keyless crates + //extern int ItemNameSortComparator( const econ_store_entry_t *const *ppEntryA, const econ_store_entry_t *const *ppEntryB ); + extern int FirstSaleDateSortComparator( const econ_store_entry_t *const *ppItemA, const econ_store_entry_t *const *ppItemB ); + + m_FilteredEntries.Sort( &FirstSaleDateSortComparator ); + + } + else if ( unSubcategoryID == CEconStoreCategoryManager::k_CategoryID_Taunts ) + { + const CEconStorePriceSheet::StoreEntryMap_t &mapEntries = pStorePanel->GetPriceSheet()->GetEntries(); + FOR_EACH_MAP_FAST( mapEntries, i ) + { + const econ_store_entry_t *pCurEntry = &mapEntries[i]; + + if ( pCurEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Taunts ) ) + { + m_FilteredEntries.AddToTail( pCurEntry ); + } + } + } + else + { + AssertMsg( 0, "Subcategory has no defined behavior in code" ); + } + + // If we're either "New" category or the "On Sale" category or the "Taunts" category, sort our contents + // by sale date. + if ( unSubcategoryID == CEconStoreCategoryManager::k_CategoryID_New || unSubcategoryID == CEconStoreCategoryManager::k_CategoryID_OnSale || unSubcategoryID == CEconStoreCategoryManager::k_CategoryID_Taunts ) + { + extern int FirstSaleDateSortComparator( const econ_store_entry_t *const *ppItemA, const econ_store_entry_t *const *ppItemB ); + + m_FilteredEntries.Sort( &FirstSaleDateSortComparator ); + } + + m_bFilterDirty = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFStorePage2::DoesEntryFilterPassSecondaryFilter( const econ_store_entry_t *pEntry ) +{ + if ( !DoesEntryFilterPassSubcategoryFilter( pEntry ) ) + { + return false; + } + + if ( m_wNameFilter.Count() > 0 ) + { + CEconItemView itemData; + itemData.Init( pEntry->GetItemDefinitionIndex(), AE_UNIQUE, AE_USE_SCRIPT_VALUE, true ); + itemData.SetClientItemFlags( kEconItemFlagClient_Preview | kEconItemFlagClient_StoreItem ); + return DoesItemPassSearchFilter( itemData.GetDescription(), m_wNameFilter.Base() ); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFStorePage2::DoesEntryFilterPassSubcategoryFilter( const econ_store_entry_t *pEntry ) +{ + Assert( pEntry ); + Assert( m_pPageData ); + + // Make sure pages without subcategories can still function + if ( !HasSubcategories() ) + return true; + + // "All subcategories" item selected? + if ( m_iCurrentSubcategory == GetAllSubcategoriesIndex() ) + return true; + + if ( !m_pPageData->m_vecSubcategories.IsValidIndex( m_iCurrentSubcategory ) ) + return false; + + // Get the subcategory ID + const StoreCategoryID_t unSubCategoryID = m_pPageData->m_vecSubcategories[ m_iCurrentSubcategory ]->m_unID; + + // If the store entry is covered by the currently selected category, return true. +// return pEntry->m_vecTagIds.Find( unSubCategoryID ) != pEntry->m_vecTagIds.InvalidIndex(); + return pEntry->IsListedInCategory( unSubCategoryID ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage2::UpdateFilterComboBox( void ) +{ + BaseClass::UpdateFilterComboBox(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage2::OnThink( void ) +{ + BaseClass::OnThink(); + + if ( m_flFilterItemTime && gpGlobals->curtime >= m_flFilterItemTime ) + { + m_bFilterDirty = true; + UpdateFilteredItems(); + UpdateModelPanels(); + m_flFilterItemTime = 0.0f; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CStorePreviewItemPanel *CTFStorePage2::CreatePreviewPanel( void ) +{ + return new CTFStorePreviewItemPanel2( EconUI()->GetStorePanel(), m_pPreviewItemResFile, "storepreviewitem", this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CStorePricePanel* CTFStorePage2::CreatePricePanel( int iIndex ) +{ + if ( m_pPageData && + m_pPageData->m_bIsHome && + HasSubcategories() && + m_pPageData->m_vecSubcategories.IsValidIndex( m_iCurrentSubcategory ) && + m_pPageData->m_vecSubcategories[ m_iCurrentSubcategory ]->m_unID == CEconStoreCategoryManager::k_CategoryID_Popular ) + { + return vgui::SETUP_PANEL( new CStorePricePanel_Popular( this, "StorePrice", iIndex + 1 ) ); + } + + return BaseClass::CreatePricePanel( iIndex ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage2::OnAddItemToCart( KeyValues *pData ) +{ + item_definition_index_t iItemDef = (item_definition_index_t)pData->GetInt( "item_def", INVALID_ITEM_DEF_INDEX ); + + AddItemToCartHelper( GetPageName(), iItemDef, (ECartItemType)pData->GetInt( "cart_add_type", kCartItem_Purchase ) ); + UpdateCart(); + + // Turn the free slots indicator red if we can't fit everything. + UpdateBackpackLabel(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage2::OnItemPanelMouseReleased( vgui::Panel *panel ) +{ + CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel ); + if ( pItemPanel && IsVisible() && pItemPanel->HasItem() ) + { + FOR_EACH_VEC( m_vecItemPanels, i ) + { + if ( m_vecItemPanels[i].m_pItemModelPanel == pItemPanel ) + { + ShowPreviewWindow( m_vecItemPanels[i].m_pItemModelPanel->GetItem()->GetItemDefIndex() ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage2::OnItemPanelMouseDoublePressed( vgui::Panel *panel ) +{ + // Do nothing +} diff --git a/game/client/tf/vgui/store/v2/tf_store_page2.h b/game/client/tf/vgui/store/v2/tf_store_page2.h new file mode 100644 index 0000000..8c25c92 --- /dev/null +++ b/game/client/tf/vgui/store/v2/tf_store_page2.h @@ -0,0 +1,82 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_STORE_PAGE2_H +#define TF_STORE_PAGE2_H +#ifdef _WIN32 +#pragma once +#endif + +#include "store/tf_store_page_base.h" + +class CNavigationPanel; +class CClassFilterTooltip; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CTFStorePage2 : public CTFStorePageBase +{ + DECLARE_CLASS_SIMPLE( CTFStorePage2, CTFStorePageBase ); +public: + CTFStorePage2( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData, const char *pPreviewItemResFile = NULL ); + ~CTFStorePage2(); + + virtual void OnPostCreate(); + + bool HasSubcategories() const; + int GetNumSubcategories() const { return m_pPageData ? m_pPageData->m_vecSubcategories.Count() : 0; } + + virtual void PerformLayout(); + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + + virtual const char *GetPageResFile( void ); + virtual void OnCommand( const char *command ); + + MESSAGE_FUNC( OnPageShow, "PageShow" ); + MESSAGE_FUNC_PTR( OnItemDetails, "ItemDetails", panel ); + MESSAGE_FUNC_PARAMS( OnItemDefDetails, "ItemDefDetails", pData ); + MESSAGE_FUNC_PARAMS( OnTextChanged, "TextChanged", pData ); + MESSAGE_FUNC_PARAMS( OnNavButtonSelected, "NavButtonSelected", pData ); + MESSAGE_FUNC_PARAMS( OnAddItemToCart, "AddItemToCart", data ); // Comes from preview panel + MESSAGE_FUNC_PTR( OnItemPanelMouseDoublePressed, "ItemPanelMouseDoublePressed", panel ); + MESSAGE_FUNC_PTR( OnItemPanelMouseReleased, "ItemPanelMouseReleased", panel ); // Comes from CStoreItemControlsPanel + + virtual bool DoesEntryFilterPassSecondaryFilter( const econ_store_entry_t *pEntry ); + bool DoesEntryFilterPassSubcategoryFilter( const econ_store_entry_t *pEntry ); + + virtual void UpdateFilteredItems( void ); + virtual void UpdateFilterComboBox( void ); + virtual void GetFiltersForDef( GameItemDefinition_t *pDef, CUtlVector<int> *pVecFilters ); + virtual void OnThink( void ); + virtual bool FindAndSelectEntry( const econ_store_entry_t *pEntry ); + + void ClearNameFilter( bool bUpdateModelPanels ); + + virtual CStorePreviewItemPanel *CreatePreviewPanel( void ); + virtual CStorePricePanel* CreatePricePanel( int iIndex ); + + void ShowPreviewWindow( item_definition_index_t usDefIndex ); + int GetAllSubcategoriesIndex() const; + + vgui::TextEntry *m_pNameFilterTextEntry; + CExLabel *m_pSubcategoriesFilterLabel; + vgui::ComboBox *m_pSubcategoriesFilterCombo; + vgui::ComboBox *m_pSortByCombo; + CNavigationPanel *m_pHomeCategoryTabs; + CNavigationPanel *m_pClassFilterButtons; + CExLabel *m_pClassFilterTooltipLabel; + CClassFilterTooltip *m_pClassFilterTooltip; + + int m_iCurrentSubcategory; + CUtlVector<wchar_t> m_wNameFilter; + float m_flFilterItemTime; + + friend class CClassFilterTooltip; +}; + +#endif // TF_STORE_PAGE2_H diff --git a/game/client/tf/vgui/store/v2/tf_store_page_maps2.cpp b/game/client/tf/vgui/store/v2/tf_store_page_maps2.cpp new file mode 100644 index 0000000..3fb0ccd --- /dev/null +++ b/game/client/tf/vgui/store/v2/tf_store_page_maps2.cpp @@ -0,0 +1,59 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "store/v2/tf_store_page_maps2.h" +#include "store/v2/tf_store_mapstamps_info_dialog.h" +#include "store/store_panel.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFStorePage_Maps2::CTFStorePage_Maps2( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData ) +: BaseClass( parent, pPageData, "Resource/UI/econ/store/v2/StorePreviewItemPanel_Maps.res" ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage_Maps2::OnPageShow() +{ + BaseClass::OnPageShow(); + + SetDetailsVisible( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage_Maps2::OnCommand( const char *command ) +{ + if ( !V_strnicmp( command, "maps_learnmore", 14 ) ) + { + DisplayMapStampsDialog(); + } + else + { + BaseClass::OnCommand( command ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePage_Maps2::DisplayMapStampsDialog() +{ + CTFMapStampsInfoDialog *pDlg = vgui::SETUP_PANEL( new CTFMapStampsInfoDialog( EconUI()->GetStorePanel() ) ); + pDlg->SetVisible( true ); + pDlg->InvalidateLayout( true, true ); + pDlg->SetKeyBoardInputEnabled(true); + pDlg->SetMouseInputEnabled(true); +} diff --git a/game/client/tf/vgui/store/v2/tf_store_page_maps2.h b/game/client/tf/vgui/store/v2/tf_store_page_maps2.h new file mode 100644 index 0000000..567ea5d --- /dev/null +++ b/game/client/tf/vgui/store/v2/tf_store_page_maps2.h @@ -0,0 +1,35 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef STORE_PAGE_MAPS2_H +#define STORE_PAGE_MAPS2_H +#ifdef _WIN32 +#pragma once +#endif + +#include "store/v2/tf_store_page2.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CTFStorePage_Maps2 : public CTFStorePage2 +{ + DECLARE_CLASS_SIMPLE( CTFStorePage_Maps2, CTFStorePage2 ); +public: + CTFStorePage_Maps2( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData ); + virtual ~CTFStorePage_Maps2() {} + + virtual const char* GetPageResFile() { return "Resource/UI/econ/store/v2/StorePage_Maps.res"; } + +protected: + virtual void OnCommand( const char *command ); + virtual void OnPageShow( void ); + + void DisplayMapStampsDialog(); +}; + +#endif // STORE_PAGE_MAPS2_H diff --git a/game/client/tf/vgui/store/v2/tf_store_panel2.cpp b/game/client/tf/vgui/store/v2/tf_store_panel2.cpp new file mode 100644 index 0000000..89fa857 --- /dev/null +++ b/game/client/tf/vgui/store/v2/tf_store_panel2.cpp @@ -0,0 +1,109 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "store/v2/tf_store_panel2.h" +#include "store/v2/tf_store_page2.h" +#include "store/v2/tf_store_page_maps2.h" +#include "store/store_page_halloween.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFStorePanel2::CTFStorePanel2( vgui::Panel *parent ) : CTFBaseStorePanel(parent) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePanel2::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePanel2::ShowPanel( bool bShow ) +{ + BaseClass::ShowPanel( bShow ); + + // Base class should turn this on + if ( bShow && m_bOGSLogging ) + { + EconUI()->Gamestats_Store( IE_STORE2_ENTERED ); + } + + Panel *pCheckOutButton = FindChildByName( "CheckOutButton" ); + if ( pCheckOutButton ) + { + pCheckOutButton->RequestFocus(); + } +} + +void CTFStorePanel2::OnAddToCart( void ) +{ + Panel *pCheckOutButton = FindChildByName( "CheckOutButton" ); + if ( pCheckOutButton ) + { + pCheckOutButton->RequestFocus(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePanel2::OnThink() +{ + BaseClass::OnThink(); +} + +void CTFStorePanel2::OnKeyCodePressed( vgui::KeyCode code ) +{ + // ESC cancels + if ( code == KEY_XBUTTON_B ) + { + OnCommand( "close" ); + } + else + { + BaseClass::OnKeyCodePressed( code ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePanel2::PostTransactionCompleted( void ) +{ + BaseClass::PostTransactionCompleted(); +} + +//----------------------------------------------------------------------------- +// Purpose: Static store page factory. +//----------------------------------------------------------------------------- +CStorePage *CTFStorePanel2::CreateStorePage( const CEconStoreCategoryManager::StoreCategory_t *pPageData ) +{ + if ( pPageData ) + { + if ( !Q_strcmp( pPageData->m_pchPageClass, "CStorePage_SpecialPromo" ) ) + return new CTFStorePage_SpecialPromo( this, pPageData ); + + if ( !Q_strcmp( pPageData->m_pchPageClass, "CStorePage_Maps" ) ) + return new CTFStorePage_Maps2( this, pPageData ); + + if ( !Q_strcmp( pPageData->m_pchPageClass, "CStorePage_Popular" ) ) + return new CTFStorePage_Popular( this, pPageData ); + } + + // Default, standard store page. + return new CTFStorePage2( this, pPageData ); +} diff --git a/game/client/tf/vgui/store/v2/tf_store_panel2.h b/game/client/tf/vgui/store/v2/tf_store_panel2.h new file mode 100644 index 0000000..4364706 --- /dev/null +++ b/game/client/tf/vgui/store/v2/tf_store_panel2.h @@ -0,0 +1,41 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_STORE_PANEL2_H +#define TF_STORE_PANEL2_H +#ifdef _WIN32 +#pragma once +#endif + +#include "store/tf_store_panel_base.h" + +class CStorePage; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CTFStorePanel2 : public CTFBaseStorePanel +{ + DECLARE_CLASS_SIMPLE( CTFStorePanel2, CTFBaseStorePanel ); +public: + CTFStorePanel2( vgui::Panel *parent ); + + // UI Layout + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void OnThink(); + virtual void OnKeyCodePressed( vgui::KeyCode code ); + virtual void ShowPanel( bool bShow ); + virtual void OnAddToCart( void ); + + // GC Management + virtual void PostTransactionCompleted( void ); + +private: + virtual CStorePage *CreateStorePage( const CEconStoreCategoryManager::StoreCategory_t *pPageData ); +}; + +#endif // TF_STORE_PANEL2_H diff --git a/game/client/tf/vgui/store/v2/tf_store_preview_item2.cpp b/game/client/tf/vgui/store/v2/tf_store_preview_item2.cpp new file mode 100644 index 0000000..c213e10 --- /dev/null +++ b/game/client/tf/vgui/store/v2/tf_store_preview_item2.cpp @@ -0,0 +1,1367 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "store/v2/tf_store_preview_item2.h" +#include "econ_item_description.h" +#include "vgui_controls/TextImage.h" +#include "vgui_controls/ScrollBar.h" +#include "vgui_controls/ScrollBarSlider.h" +#include "vgui/IInput.h" +#include "tf_item_schema.h" +#include "econ_item_system.h" +#include "store/store_panel.h" +#include "c_tf_gamestats.h" +#include "tf_playermodelpanel.h" +#include "navigationpanel.h" +#include "tf_mouseforwardingpanel.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline float LerpScale( float flIn, float flInMin, float flInMax, float flOutMin, float flOutMax ) +{ + float flDenom = flInMax - flInMin; + if ( flDenom == 0.0f ) + return 0.0f; + + float t = clamp( ( flIn - flInMin ) / flDenom, 0.0f, 1.0f ); + return Lerp( t, flOutMin, flOutMax ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline float SCurve( float t ) +{ + t = clamp( t, 0.0f, 1.0f ); + return t * t * (3 - 2*t); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CFullscreenStorePreviewItem::CFullscreenStorePreviewItem( vgui::Panel *pParent, EditablePanel *pOwner ) +: BaseClass( pParent, "FullscreenStorePreview" ), + m_iItemDef( INVALID_ITEM_ID ), + m_pCycleTextLabel( NULL ), + m_pTeamNavPanel( NULL ), + m_pPreviewButton( NULL ), + m_pRotLeftButton( NULL ), + m_pRotRightButton( NULL ), + m_pZoomButton( NULL ), + m_pOverlayPanel( NULL ), + m_flGoFullscreenStartTime( 0.0f ), + m_flLastMouseMoveTime( 0.0f ), + m_bIsHalloweenOrFullmoonOnlyItem( false ) +{ + m_hOwner = pOwner; +} + +void CFullscreenStorePreviewItem::SetItemDef( itemid_t iItemDef ) +{ + m_iItemDef = iItemDef; + + CStorePanel *pStorePanel = EconUI()->GetStorePanel(); + if ( pStorePanel ) + { + const CEconStorePriceSheet *pPriceSheet = pStorePanel->GetPriceSheet(); + CExButton *pPreviewButton = dynamic_cast<CExButton *>( FindChildByName( "TryItOutButton" ) ); + if ( pPriceSheet && pPreviewButton ) + { + const econ_store_entry_t *pStoreEntry = pPriceSheet->GetEntry( m_iItemDef ); + pPreviewButton->SetVisible( pStoreEntry && pStoreEntry->CanPreview() ); + } + } +} + +void CFullscreenStorePreviewItem::GoFullscreen( CTFPlayerModelPanel *pPlayerModelPanel ) +{ + m_Stats.Clear(); + + m_flGoFullscreenStartTime = gpGlobals->realtime; + + SetAlpha( 0 ); + SetVisible( true ); + MoveToFront(); + + m_pPlayerModelPanel = pPlayerModelPanel; + + if ( !m_pPlayerModelPanel.Get() ) + return; + + // Cache old player model panel bounds and such + m_pPlayerModelPanel->GetBounds( m_OldModelState.m_aPlayerModelPanelBounds[0], m_OldModelState.m_aPlayerModelPanelBounds[1], m_OldModelState.m_aPlayerModelPanelBounds[2], m_OldModelState.m_aPlayerModelPanelBounds[3] ); + m_OldModelState.m_vecPlayerPos = m_pPlayerModelPanel->m_vecPlayerPos; + m_OldModelState.m_bZoomed = m_pPlayerModelPanel->IsZoomed(); + + // Fullscreen panel is new parent + m_pPlayerModelPanel->SetParent( this ); + + // Get team state + if ( m_pTeamNavPanel ) + { + m_pTeamNavPanel->UpdateButtonSelectionStates( m_pPlayerModelPanel->GetTeam() == TF_TEAM_RED ? 0 : 1 ); + } +} + +void CFullscreenStorePreviewItem::ExitFullscreen() +{ + if ( !m_hOwner.Get() ) + return; + + if ( m_pPlayerModelPanel.Get() ) + { + m_pPlayerModelPanel->SetParent( m_hOwner.Get() ); + m_pPlayerModelPanel->SetBounds( m_OldModelState.m_aPlayerModelPanelBounds[0], m_OldModelState.m_aPlayerModelPanelBounds[1], m_OldModelState.m_aPlayerModelPanelBounds[2], m_OldModelState.m_aPlayerModelPanelBounds[3] ); + + // Reset the player position to it's pre-fullscreen location, but add on any zoom delta if needed. + const Vector vecZoomOffset = m_OldModelState.m_bZoomed != m_pPlayerModelPanel->IsZoomed() ? m_pPlayerModelPanel->GetZoomOffset() : vec3_origin; + m_pPlayerModelPanel->m_vecPlayerPos = m_OldModelState.m_vecPlayerPos + vecZoomOffset; + } + + m_flGoFullscreenStartTime = 0.0f; + SetVisible( false ); + + PostMessage( m_hOwner.Get(), new KeyValues( "ExitFullscreen" ) ); +} + +bool CFullscreenStorePreviewItem::IsFullscreenMode() +{ + return IsVisible(); +} + +void CFullscreenStorePreviewItem::OnNavButtonSelected( KeyValues *pData ) +{ + const int iTeam = pData->GetInt( "userdata", -1 ); AssertMsg( iTeam >= 0, "Bad filter" ); + if ( iTeam < 0 ) + return; + + if ( !m_pPlayerModelPanel.Get() ) + return; + + m_pPlayerModelPanel->SetTeam( iTeam ); + + C_CTFGameStats::ImmediateWriteInterfaceEvent( "team_switch_%s(store_preview_item_panel_fullscreen)", iTeam == TF_TEAM_RED ? "red" : "blu" ); +} + +void CFullscreenStorePreviewItem::OnThink() +{ + BaseClass::OnThink(); + + if ( m_flGoFullscreenStartTime == 0.0f ) + { + SetVisible( false ); + return; + } + + // We are fading, or already faded in + SetVisible( true ); + + // Keep track of mouse movement - if the mouse button is down, force the mouse-moving state so we + // don't end up fading out while the player is rotating or clicking-and-holding anywhere else + int nMouseX, nMouseY; + vgui::input()->GetCursorPos( nMouseX, nMouseY ); + bool bMouseMoved = false; + const bool bMouseButtonDown = vgui::input()->IsMouseDown( MOUSE_LEFT ); + const bool bForceMouseMoving = bMouseButtonDown; + if ( bForceMouseMoving || nMouseX != m_nLastMouseX || nMouseY != m_nLastMouseY ) + { + bMouseMoved = true; + m_nLastMouseX = nMouseX; + m_nLastMouseY = nMouseY; + m_flLastMouseMoveTime = gpGlobals->realtime; + } + + // Fade in the button blocker if the mouse has been idle for some period of time + if ( m_pOverlayPanel ) + { + const float flButtonBlockerFade = SCurve( + LerpScale( + gpGlobals->realtime, + m_flLastMouseMoveTime + m_flUiFadeoutTime, + m_flLastMouseMoveTime + m_flUiFadeoutTime + m_flUiFadeoutDuration, + 0.0f, + 1.0f + ) + ); + + m_pOverlayPanel->SetVisible( flButtonBlockerFade > 0.0f ); + + m_pOverlayPanel->SetAlpha( (int)( 255 * flButtonBlockerFade ) ); + + // Set to layer above overlay panel, so it will always be visible + m_pPlayerModelPanel->SetZPos( flButtonBlockerFade > 0.0f ? ( m_pOverlayPanel->GetZPos() + 1 ) : 0 ); + } + + const float flFade = SCurve( + LerpScale( + gpGlobals->realtime, + m_flGoFullscreenStartTime, + m_flGoFullscreenStartTime + m_flFullscreenFadeToBlackDuration, + 0.0f, + 1.0f + ) + ); + + SetAlpha( (int)( 255 * flFade ) ); + + if ( !m_pPlayerModelPanel.Get() ) + return; + + // Resize 3D model panel + const int aDstBounds[4] = { 0, 0, ScreenWidth(), ScreenHeight() }; + const int aBounds[4] = { + Lerp( flFade, m_OldModelState.m_aPlayerModelPanelBounds[0], aDstBounds[0] ), + Lerp( flFade, m_OldModelState.m_aPlayerModelPanelBounds[1], aDstBounds[1] ), + Lerp( flFade, m_OldModelState.m_aPlayerModelPanelBounds[2], aDstBounds[2] ), + Lerp( flFade, m_OldModelState.m_aPlayerModelPanelBounds[3], aDstBounds[3] ) + }; + m_pPlayerModelPanel->SetBounds( aBounds[0], aBounds[1], aBounds[2], aBounds[3] ); + + if ( flFade < 0.999f ) + { + const Vector vecZoomOffset = m_pPlayerModelPanel->IsZoomed() ? m_pPlayerModelPanel->GetZoomOffset() : vec3_origin; + const Vector vecFullscreenOrigin( m_flModelPanelOriginX, m_flModelPanelOriginY, m_flModelPanelOriginZ ); + m_pPlayerModelPanel->m_vecPlayerPos = Lerp( flFade, m_OldModelState.m_vecPlayerPos, vecFullscreenOrigin + vecZoomOffset ); + } + + if ( m_pZoomButton ) + { + m_pZoomButton->SetEnabled( flFade == 1.0f ); + } + + if ( bMouseButtonDown && m_pRotLeftButton && m_pRotRightButton ) + { + float flDeltaAngle = 0; + const float kScale = 100.0f; + if ( m_pRotLeftButton->IsWithin( nMouseX, nMouseY ) ) + { + flDeltaAngle = -gpGlobals->frametime * kScale; + } + else if ( m_pRotRightButton->IsWithin( nMouseX, nMouseY ) ) + { + flDeltaAngle = gpGlobals->frametime * kScale; + } + + m_pPlayerModelPanel->RotateYaw( flDeltaAngle ); + + // Accumulate time rotation buttons are being pressed for stat tracking + m_Stats.m_flRotationTime += gpGlobals->frametime; + } +} + +void CFullscreenStorePreviewItem::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "Resource/UI/econ/store/v2/StorePreviewItemPanel_Fullscreen.res" ); + + m_pOverlayPanel = dynamic_cast<EditablePanel *>( FindChildByName( "OverlayPanel" ) ); + m_pRotLeftButton = dynamic_cast<CExButton *>( FindChildByName( "RotateLeftButton" ) ); + m_pRotRightButton = dynamic_cast<CExButton *>( FindChildByName( "RotateRightButton" ) ); + m_pZoomButton = dynamic_cast<CExButton *>( FindChildByName( "ZoomButton" ) ); + m_pTeamNavPanel = dynamic_cast<CNavigationPanel *>( FindChildByName( "TeamNavPanel" ) ); +} + +void CFullscreenStorePreviewItem::OnCommand( const char *command ) +{ + C_CTFGameStats::ImmediateWriteInterfaceEvent( "on_command(store_preview_item_panel_fullscreen)", command ); + + if ( !V_strnicmp( command, "close", 6 ) ) + { + ExitFullscreen(); + return; + } + + if ( m_hOwner.Get() ) + { + // Let the owner handle the command + m_hOwner->OnCommand( command ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFStorePreviewItemPanel2::CTFStorePreviewItemPanel2( vgui::Panel *pParent, const char *pResFile, const char *pPanelName, CStorePage *pOwner ) +: BaseClass( pParent, pResFile, "storepreviewitem", pOwner ) +{ + m_pScrollBar = new ScrollBar( this, "ScrollBar", true ); + m_pScrollBar->AddActionSignalTarget( this ); + + m_pFullscreenPanel = new CFullscreenStorePreviewItem( this, this ); + m_pFullscreenPanel->AddActionSignalTarget( this ); + + m_pMouseOverItemPanel = vgui::SETUP_PANEL( new CItemModelPanel( this, "mouseoveritempanel" ) ); + m_pMouseOverTooltip = new CItemModelPanelToolTip( this ); + m_pMouseOverTooltip->SetupPanels( this, m_pMouseOverItemPanel ); + m_pMouseOverTooltip->SetPositioningStrategy( IPTTP_BOTTOM_SIDE ); + m_pMouseOverItemPanel->MoveToFront(); + + m_pItemViewData = NULL; + m_pSOEconItemData = NULL; + Clear(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel2::Clear() +{ + m_pPlayerModelPanel = NULL; + + m_pPreviewButton = NULL; + m_pDialogFrame = NULL; + m_pPreviewViewportBg = NULL; + m_pItemNameLabel = NULL; + m_pAttributesLabel = NULL; + m_pItemCollectionHighlight = NULL; + m_pDetailsView = NULL; + m_pDetailsViewChild = NULL; // Scrollable + m_pItemWikiPageButton = NULL; + m_pTeamNavPanel = NULL; + m_pCycleTextLabel = NULL; + m_pScrollableChild = NULL; + m_pGoFullscreenButton = NULL; + m_nNumAttribLinesAdded = 0; + m_bArmoryTextAdded = false; + m_nNumAttribLinesAdded = 0; + m_iSliderPos = 0; + m_aClickPos[0] = m_aClickPos[1] = 0; + m_bCloseOnUp = false; + m_bMouseWasDown = false; + m_bIsHalloweenOrFullmoonOnlyItem = false; + + for ( int i = 0; i < ARRAYSIZE( m_pAddRentalToCartButtons ); i++ ) + { + m_pAddRentalToCartButtons[i] = NULL; + } + + m_vecReferenceItemPanels.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel2::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + Clear(); + + BaseClass::ApplySchemeSettings( pScheme ); + + FOR_EACH_VEC( m_pItemIcons, i ) + { + // Strip tooltips. + // We have all the same data already + m_pItemIcons[i]->GetItemPanel()->SetTooltip( NULL, NULL ); + } + + m_pDialogFrame = dynamic_cast<EditablePanel *>( FindChildByName( "DialogFrame" ) ); + m_pPreviewButton = dynamic_cast<CExButton *>( FindChildByName( "TryItOutButton" ) ); + m_pCycleTextLabel = dynamic_cast<CExLabel *>( FindChildByName( "CycleTextLabel" ) ); + m_pGoFullscreenButton = dynamic_cast<CExImageButton *>( FindChildByName( "GoFullscreenButton" ) ); + + COMPILE_TIME_ASSERT( ARRAYSIZE( m_pAddRentalToCartButtons ) == 3 ); +#ifdef ENABLE_STORE_RENTAL_BACKEND + m_pAddRentalToCartButtons[0] = dynamic_cast<CExButton *>( FindChildByName( "AddRentalToCartButton_1Day" ) ); + m_pAddRentalToCartButtons[1] = dynamic_cast<CExButton *>( FindChildByName( "AddRentalToCartButton_3Day" ) ); + m_pAddRentalToCartButtons[2] = dynamic_cast<CExButton *>( FindChildByName( "AddRentalToCartButton_7Day" ) ); +#endif + + if ( m_pDialogFrame ) + { + m_pPreviewViewportBg = dynamic_cast<EditablePanel *>( m_pDialogFrame->FindChildByName( "PreviewViewportBg" ) ); + m_pItemNameLabel = dynamic_cast<CExLabel *>( m_pDialogFrame->FindChildByName( "ItemNameLabel" ) ); + m_pDetailsView = dynamic_cast<EditablePanel *>( m_pDialogFrame->FindChildByName( "DetailsView" ) ); + + if ( m_pDetailsView ) + { + m_pDetailsViewChild = dynamic_cast<EditablePanel *>( m_pDetailsView->FindChildByName( "ScrollableChild" ) ); + + if ( !m_pDetailsViewChild ) + { + m_pDetailsViewChild = m_pDetailsView; + } + + m_pAttributesLabel = dynamic_cast<CExLabel *>( m_pDetailsViewChild->FindChildByName( "AttributesLabel" ) ); + m_pItemCollectionHighlight = dynamic_cast<EditablePanel *>( m_pDetailsViewChild->FindChildByName( "collectionhighlight" ) ); + if ( m_pItemCollectionHighlight ) + { + m_pItemCollectionHighlight->InvalidateLayout( true, true ); + } + m_pItemWikiPageButton = dynamic_cast<CExButton *>( m_pDetailsViewChild->FindChildByName( "ItemWikiPageButton" ) ); + + if ( m_pItemWikiPageButton ) + { + m_pItemWikiPageButton->AddActionSignalTarget( this ); + } + } + } + + m_pTeamNavPanel = dynamic_cast<CNavigationPanel *>( FindChildByName( "TeamNavPanel" ) ); + + m_pMouseOverItemPanel->SetBorder( pScheme->GetBorder("LoadoutItemPopupBorder") ); + + SetState( PS_ITEM ); +} + +//----------------------------------------------------------------------------- +// Purpose: Given two controls A and B, place B such that it is (nXOffset,nYOffset) +// pixels offset from A, using it's content width or height, depending on bVertical. +// pControlNameA can be NULL, in which case zeros will be used as the offset. +//----------------------------------------------------------------------------- +int CTFStorePreviewItemPanel2::PlaceControl( Panel *pParent, const char *pControlNameA, const char *pControlNameB, int nOffset, bool bVertical, bool bSizeAToContents/*=true*/, bool bUseContentSize/*=true*/ ) +{ + if ( !pParent || !pControlNameB ) + { + AssertMsg( 0, "Bad!" ); + return 0; + } + + Label *pControlA = pControlNameA ? dynamic_cast<Label *>( pParent->FindChildByName( pControlNameA ) ) : NULL; + Label *pControlB = dynamic_cast<Label *>( pParent->FindChildByName( pControlNameB ) ); + if ( !pControlB ) + { + return 0; + } + + if ( !pControlA && bVertical ) + { + pControlA = m_pLastNewLineControl; + } + + int aSize[2] = { 0, 0 }; + int aPos[2] = { 0, 0 }; + if ( pControlA ) + { + pControlA->SetVisible( true ); + + if ( bSizeAToContents ) + { + pControlA->SizeToContents(); + pControlA->InvalidateLayout( true ); + } + + if ( bUseContentSize ) + { + pControlA->GetContentSize( aSize[0], aSize[1] ); + } + else + { + pControlA->GetSize( aSize[0], aSize[1] ); + } + + pControlA->GetPos( aPos[0], aPos[1] ); + } + + int aOffset[2] = { 0, 0 }; + if ( bVertical ) + { + aOffset[1] = aSize[1] + nOffset; + } + else + { + aOffset[0] = aSize[0] + nOffset; + } + + // NOTE: We add in the slider position here + pControlB->SetPos( aPos[0] + aOffset[0], aPos[1] + aOffset[1] ); + + pControlB->SetVisible( true ); + +#if _DEBUG + /* + Msg( "control A: %s size: w=%i h=%i pos: (%i, %i)\n", pControlNameA, aSize[0], aSize[1], aPos[0], aPos[1] ); + int x,y; + pControlB->GetPos(x,y); + Msg( "control B: %s pos: (%i, %i)\n", pControlNameB, x,y ); + */ +#endif + + if ( bVertical ) + { + m_pLastNewLineControl = pControlB; + } + + m_nViewMaxHeight = MAX( m_nViewMaxHeight, aPos[1] + aOffset[1] + pControlB->GetTall() ); + return m_nViewMaxHeight; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel2::PerformLayout( void ) +{ +// BaseClass::PerformLayout(); // We override completely here + + // center the icons (we need to redo some of the work of CStorePreviewItemPanel, because we + // center the base item icons along with our TF specific class ones) + int iNumItemIcons = 0; + FOR_EACH_VEC( m_pItemIcons, i ) + { + if ( m_pItemIcons[i]->IsVisible() ) + { + ++iNumItemIcons; + } + } + + int iNumClassIcons = 0; + FOR_EACH_VEC( m_pClassIcons, i ) + { + if ( m_pClassIcons[i]->IsVisible() ) + { + ++iNumClassIcons; + } + } + + if ( m_pDialogFrame && ( iNumItemIcons || iNumClassIcons ) ) + { + int aDialogFramePos[2]; + m_pDialogFrame->GetPos( aDialogFramePos[0], aDialogFramePos[1] ); + + int iCenterX = aDialogFramePos[0] + m_pDialogFrame->GetWide() / 4; + int interval = XRES(2); + int totalWidth = (iNumItemIcons > 0 ? iNumItemIcons * m_pItemIcons[0]->GetWide() : 0) + (iNumClassIcons * m_pClassIcons[0]->GetWide()) + (interval * (iNumItemIcons + iNumClassIcons - 1)); + int iX = iCenterX - ( totalWidth / 2 ); + + int posX, posY; + if ( iNumItemIcons > 0 ) + { + m_pItemIcons[0]->GetPos( posX, posY ); + } + else + { + m_pClassIcons[0]->GetPos( posX, posY ); + } + + int iButton = 0; + for ( int i = 0; i < m_pItemIcons.Count(); i++ ) + { + if ( m_pItemIcons[i]->IsVisible() ) + { + m_pItemIcons[i]->SetPos( iX, posY ); + iX += m_pItemIcons[i]->GetWide() + interval; + + iButton++; + } + } + + for ( int i = 0; i < m_pClassIcons.Count(); i++ ) + { + if ( m_pClassIcons[i]->IsVisible() ) + { + m_pClassIcons[i]->SetPos( iX, posY ); + iX += m_pClassIcons[i]->GetWide() + interval; + + iButton++; + } + } + } + + if ( !m_pPreviewViewportBg || !m_pItemNameLabel || !m_pDetailsViewChild || !m_pDetailsView || !m_pDialogFrame ) + { + m_pScrollBar->SetVisible( false ); + return; + } + + // Make sure we size the item name in case it needs multiple lines. + m_pItemNameLabel->SizeToContents(); + m_pItemNameLabel->InvalidateLayout( true ); + if ( m_pItemNameLabel && m_item.IsValid() ) + { + const CEconItemRarityDefinition* pItemRarity = GetItemSchema()->GetRarityDefinition( m_item.GetItemDefinition()->GetRarity() ); + + // Setup the rarity color overlay + { + Color color( 255, 255, 255, 255 ); + if ( pItemRarity ) + { + attrib_colors_t attribColor = pItemRarity->GetAttribColor(); + vgui::IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); + color = pScheme->GetColor( GetColorNameForAttribColor( attribColor ), Color( 255, 255, 255, 255 ) ); + } + m_pItemNameLabel->SetColorStr( color ); + } + } + + int aItemNameLabelPos[2]; + m_pItemNameLabel->GetPos( aItemNameLabelPos[0], aItemNameLabelPos[1] ); + + // This is the item label's new bottom y coordinate after it's been sized + const int nNewYRelativeToDlgFrame = aItemNameLabelPos[1] + m_pItemNameLabel->GetTall(); + + // Set ypos for details view and scroll bar + int aDetailsViewPos[2]; + m_pDetailsView->GetPos( aDetailsViewPos[0], aDetailsViewPos[1] ); + m_pDetailsView->SetPos( aDetailsViewPos[0], nNewYRelativeToDlgFrame ); + + int aDialogFramePos[2]; + m_pDialogFrame->GetPos( aDialogFramePos[0], aDialogFramePos[1] ); + + if ( m_pScrollBar ) + { + int aScrollBar[2]; + m_pScrollBar->GetPos( aScrollBar[0], aScrollBar[1] ); + m_pScrollBar->SetPos( aScrollBar[0], nNewYRelativeToDlgFrame + aDialogFramePos[1] ); + } + + int nNewHeight = m_pPreviewViewportBg->GetTall() - m_pItemNameLabel->GetTall(); + m_pDetailsView->SetTall( nNewHeight ); + if ( m_pScrollBar ) + { + m_pScrollBar->SetTall( nNewHeight ); + } + + // Place paint and style buttons + CUtlVector<CExButton *> vecVisibleButtons; + const int nNumPossibilyVisibleWeapons = 1; + CExButton *pPossiblyVisibleButtons[nNumPossibilyVisibleWeapons] = { m_pNextWeaponButton }; + for ( int i = 0; i < nNumPossibilyVisibleWeapons; ++i ) + { + CExButton *pCurButton = pPossiblyVisibleButtons[i]; + if ( !pCurButton || !pCurButton->IsVisible() ) + continue; + + vecVisibleButtons.AddToTail( pCurButton ); + } + + int nNumButtonsNeeded = vecVisibleButtons.Count(); + if ( nNumButtonsNeeded ) + { + // Center however many buttons we need to along the top of the viewport + int aViewportPos[2]; + m_pPreviewViewportBg->GetPos( aViewportPos[0], aViewportPos[1] ); + for ( int i = 0; i < nNumButtonsNeeded; ++i ) + { + CExButton *pCurButton = vecVisibleButtons[i]; + pCurButton->SetPos( aDialogFramePos[0] + aViewportPos[0] + ( i + 1 ) * m_pPreviewViewportBg->GetWide() / ( nNumButtonsNeeded + 1 ) - m_iControlButtonWidth / 2, m_iControlButtonY ); + pCurButton->SetSize( m_iControlButtonWidth, m_iControlButtonHeight ); + } + } + + m_pLastNewLineControl = NULL; + m_nViewMaxHeight = 0; + + PlaceControl( m_pDetailsViewChild, NULL, "ItemLevelInfoLabel", m_iSmallVerticalBreakSize, true ); + + if ( m_bIsHalloweenOrFullmoonOnlyItem ) + { + PlaceControl( m_pDetailsViewChild, "ItemLevelInfoLabel", "RestrictionsLabel", m_iMediumVerticalBreakSize, true ); + PlaceControl( m_pDetailsViewChild, "RestrictionsLabel", "RestrictionsTextLabel", m_iHorizontalBreakSize, false ); + PlaceControl( m_pDetailsViewChild, "RestrictionsLabel", "UsedByLabel", m_iSmallVerticalBreakSize, true ); + } + else + { + PlaceControl( m_pDetailsViewChild, "ItemLevelInfoLabel", "UsedByLabel", m_iMediumVerticalBreakSize, true ); + } + + PlaceControl( m_pDetailsViewChild, "UsedByLabel", "UsedByTextLabel", m_iHorizontalBreakSize, false ); + PlaceControl( m_pDetailsViewChild, "UsedByLabel", "SlotLabel", m_iSmallVerticalBreakSize, true ); + PlaceControl( m_pDetailsViewChild, "SlotLabel", "SlotTextLabel", m_iHorizontalBreakSize, false ); + PlaceControl( m_pDetailsViewChild, "SlotLabel", "PriceLabel", m_iBigVerticalBreakSize, true ); + + if ( m_bArmoryTextAdded ) + { + PlaceControl( m_pDetailsViewChild, "PriceLabel", "ArmoryTextLabel", m_iBigVerticalBreakSize, true ); + PlaceControl( m_pDetailsViewChild, "ArmoryTextLabel", "AttributesLabel", m_iBigVerticalBreakSize, true ); + } + else + { + PlaceControl( m_pDetailsViewChild, "PriceLabel", "AttributesLabel", m_iBigVerticalBreakSize, true ); + } + + PlaceControl( m_pDetailsViewChild, m_nNumAttribLinesAdded == 0 ? "PriceLabel" : "AttributesLabel", "ItemWikiPageButton", m_iBigVerticalBreakSize, true ); + + PlaceControl( m_pDetailsViewChild, "ItemWikiPageButton", "TradableLabel", m_iBigVerticalBreakSize, true, false, false ); + PlaceControl( m_pDetailsViewChild, "TradableLabel", "TradableTextLabel", m_iHorizontalBreakSize, false ); + PlaceControl( m_pDetailsViewChild, "TradableLabel", "CraftableLabel", m_iSmallVerticalBreakSize, true ); + PlaceControl( m_pDetailsViewChild, "CraftableLabel", "CraftableTextLabel", m_iHorizontalBreakSize, false ); + PlaceControl( m_pDetailsViewChild, "CraftableLabel", "GiftableLabel", m_iSmallVerticalBreakSize, true ); + PlaceControl( m_pDetailsViewChild, "GiftableLabel", "GiftableTextLabel", m_iHorizontalBreakSize, false ); + PlaceControl( m_pDetailsViewChild, "GiftableLabel", "NameableLabel", m_iSmallVerticalBreakSize, true ); + PlaceControl( m_pDetailsViewChild, "NameableLabel", "NameableTextLabel", m_iHorizontalBreakSize, false ); + + if ( m_pScrollBar ) + { + m_pScrollBar->SetVisible( true ); + m_pScrollBar->InvalidateLayout( true ); + m_pScrollBar->SetRange( 0, m_nViewMaxHeight ); + m_pScrollBar->SetRangeWindow( m_pScrollBar->GetTall() ); + + m_pDetailsViewChild->SetTall( m_nViewMaxHeight ); + UpdateScrollableChild(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel2::UpdateScrollableChild() +{ + m_pDetailsViewChild->SetPos( 0, -m_iSliderPos ); +} + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel2::OnCommand( const char *command ) +{ + C_CTFGameStats::ImmediateWriteInterfaceEvent( "on_command(store_preview_item_panel)", command ); + + if ( !V_strnicmp( command, "closex", 5 ) ) + { + // This is just a way for us to differentiate between 'x' button being pressed vs. + // the "back" button vs. clicking outside the preview window. + DoClose(); + } + else if ( !V_strnicmp( command, "tryitout", 8 ) ) + { + // OGS data gets written elsewhere for this event + PostMessage( m_pOwner, new KeyValues( "PreviewItem", "item_def_index", m_item.GetItemDefIndex() ) ); + DoClose(); + } + else if ( !V_strnicmp( command, "addtocart", 9 ) +#ifdef ENABLE_STORE_RENTAL_BACKEND + || !V_strnicmp( command, "addrentaltocart", 15 ) +#endif + ) + { +#ifdef ENABLE_STORE_RENTAL_BACKEND + ECartItemType eCartItemType = !V_stricmp( command, "addrentaltocart_1day" ) + ? kCartItem_Rental_1Day + : !V_stricmp( command, "addrentaltocart_3day" ) + ? kCartItem_Rental_3Day + : !V_stricmp( command, "addrentaltocart_7day" ) + ? kCartItem_Rental_7Day + : kCartItem_Purchase; +#else + ECartItemType eCartItemType = kCartItem_Purchase; +#endif + + KeyValues *pParams = new KeyValues( "AddItemToCart" ); + pParams->SetInt( "item_def", m_item.GetItemDefIndex() ); + pParams->SetInt( "cart_add_type", eCartItemType ); + PostMessage( m_pOwner, pParams ); + DoClose(); + } + else if ( !V_strnicmp( command, "viewwikipage", 12 ) ) + { + if ( steamapicontext && steamapicontext->SteamFriends() && m_pItemFullImage ) + { + CEconItemView *pItem = m_pItemFullImage->GetItem(); + if ( pItem->IsValid() ) + { + // Determine which language we should use + char uilanguage[ 64 ]; + uilanguage[0] = 0; + engine->GetUILanguage( uilanguage, sizeof( uilanguage ) ); + ELanguage iLang = PchLanguageToELanguage( uilanguage ); + + char szURL[512]; + Q_snprintf( szURL, sizeof(szURL), "http://wiki.teamfortress.com/scripts/itemredirect.php?id=%d&lang=%s", pItem->GetItemDefIndex(), GetLanguageICUName( iLang ) ); + steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( szURL ); + + C_CTF_GameStats.Event_Catalog( IE_ARMORY_BROWSE_WIKI, NULL, pItem ); + } + } + } + else if ( !V_strnicmp( command, "team_", 5 ) ) + { + const char *pTeam = command + 5; + if ( !V_strnicmp( pTeam, "red", 3 ) ) + { + m_pPlayerModelPanel->SetTeam( TF_TEAM_RED ); + } + else + { + m_pPlayerModelPanel->SetTeam( TF_TEAM_BLUE ); + } + } + else if ( !V_strnicmp( command, "gofullscreen", 11 ) ) + { + if ( m_pFullscreenPanel ) + { + m_pFullscreenPanel->GoFullscreen( m_pPlayerModelPanel ); + } + } + else + { + BaseClass::OnCommand( command ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel2::OnClassIconSelected( KeyValues *data ) +{ + BaseClass::OnClassIconSelected( data ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel2::OnHideClassIconMouseover( void ) +{ + BaseClass::OnHideClassIconMouseover(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel2::OnShowClassIconMouseover( KeyValues *data ) +{ + // We decided not to show the "this item is + // usable by the [Class name]" tooltip. + //BaseClass::OnShowClassIconMouseover( data ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel2::PreviewItemCopy( int iClass, CEconItemView *pItem, const econ_store_entry_t* pEntry ) +{ + // Make a copy of SO data since it comes from the market and will fall out of scope + if ( m_pItemViewData ) + { + delete m_pItemViewData; + m_pItemViewData = NULL; + } + + if ( m_pSOEconItemData ) + { + delete m_pSOEconItemData; + m_pSOEconItemData = NULL; + } + + m_pItemViewData = new CEconItemView( *pItem ); + m_pSOEconItemData = new CEconItem( *pItem->GetSOCData() ); + m_pItemViewData->SetNonSOEconItem( m_pSOEconItemData ); + PreviewItem( iClass, m_pItemViewData, pEntry ); +} +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel2::PreviewItem( int iClass, CEconItemView *pItem, const econ_store_entry_t* pEntry ) +{ + BaseClass::PreviewItem( iClass, pItem, pEntry ); + + C_CTFGameStats::ImmediateWriteInterfaceEvent( "store_preview_item_panel(preview_item)", CFmtStr( "%i", m_item.GetItemDefIndex() ).Access() ); + + // Reload the .res file right now, since we need to make sure the item name label, on which all other controls + // base their position, is valid. + InvalidateLayout( true, true ); + + // Update the fullscreen item def index + if ( m_pFullscreenPanel ) + { + m_pFullscreenPanel->SetItemDef( m_item.GetItemDefIndex() ); + } + + // If we didn't have a store entry passed in, look for one. + if ( !pEntry ) + { + CStorePanel *pStorePanel = EconUI()->GetStorePanel(); + if ( pStorePanel ) + { + pEntry = pStorePanel->GetPriceSheet()->GetEntry( m_item.GetItemDefIndex() ); + } + } + + if ( m_pDialogFrame && m_pDetailsView && m_pItemFullImage && m_pItemFullImage->GetItem() && m_pAttributesLabel ) + { + const CEconItemView *pFullItem = m_pItemFullImage->GetItem(); + const CEconItemDefinition *pBaseDef = pFullItem->GetItemDefinition(); + const CTFItemDefinition *pDef = dynamic_cast<const CTFItemDefinition *>( pBaseDef ); + + if ( pFullItem && pDef ) + { + m_pDialogFrame->SetDialogVariable( "itemname", pFullItem->GetItemName() ); + + // Holiday restrictions? + const char *pHolidayRestriction = pDef->GetHolidayRestriction() ? pDef->GetHolidayRestriction() : ""; + m_bIsHalloweenOrFullmoonOnlyItem = StringHasPrefix( pHolidayRestriction, "halloween" ); + + CTFItemSchema *pSchema = ItemSystem()->GetItemSchema(); + if ( pSchema ) + { + // Build a list of classes by which this item can be used + const CBitVec<LOADOUT_COUNT> *pbvClassUsability = pDef->GetClassUsability(); + + const int kClassNamesSize = 512; + wchar_t wszClassNames[ kClassNamesSize ] = L""; + int nClassAdded = 0; + bool bAllClasses = true; + for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; ++i ) + { + if ( !pbvClassUsability->IsBitSet( i ) ) + { + bAllClasses = false; + break; + } + } + + if ( bAllClasses ) + { + m_pDetailsViewChild->SetDialogVariable( "used_by_classes", g_pVGuiLocalize->Find( "#Store_ItemDesc_AllClasses" ) ); + } + else + { + for ( int i = 0; i < LOADOUT_COUNT; ++i ) + { + if ( pbvClassUsability->IsBitSet( i ) ) + { + V_wcscat_safe( wszClassNames, nClassAdded++ == 0 ? L"" : L", " ); // add empty lines everywhere except before the first line + V_wcscat_safe( wszClassNames, g_pVGuiLocalize->Find( g_aPlayerClassNames[i] ) ); + } + } + m_pDetailsViewChild->SetDialogVariable( "used_by_classes", wszClassNames ); + } + + // Setup the slot string + const CUtlVector< const char * > &vecLoadoutStrings = pSchema->GetLoadoutStrings( pDef->GetEquipType() ); + const int iSlot = pDef->GetDefaultLoadoutSlot(); + const bool bSlotValid = vecLoadoutStrings.IsValidIndex( iSlot ); + m_pDetailsViewChild->SetDialogVariable( "slot", g_pVGuiLocalize->Find( bSlotValid ? CFmtStr( "#LoadoutSlot_%s", vecLoadoutStrings[iSlot] ).Access() : "#Store_ItemDesc_Slot_None" ) ); + + // Make an attempt to display tradability accurately even though we don't have an item to pull from. + static CSchemaAttributeDefHandle pAttrib_CannotTrade( "cannot trade" ); + Assert( pAttrib_CannotTrade ); + + // Get localized versions of "yes" and "no" + const wchar_t *pYesNo[2] = { + g_pVGuiLocalize->Find( "#Store_ItemDesc_Yes" ), + g_pVGuiLocalize->Find( "#Store_ItemDesc_No" ) + }; + bool bIsMapStamp = pDef->GetItemClass() && !V_strncmp( pDef->GetItemClass(), "map_token", 9 ); + bool bIsTradeable = bIsMapStamp || FindAttribute( pDef, pAttrib_CannotTrade ) + ? pYesNo[ 1 ] + : g_pVGuiLocalize->Find( "#Attrib_Store_TradableAfterDate" ); + + m_pDetailsViewChild->SetDialogVariable( "giftable", bIsTradeable ); + m_pDetailsViewChild->SetDialogVariable( "nameable", ( pDef->GetCapabilities() & ITEM_CAP_NAMEABLE ) != 0 ? pYesNo[0] : pYesNo[1] ); + + m_pDetailsViewChild->SetDialogVariable( "tradable", bIsTradeable ); + + // No store-bought items are craftable, but items that were going to be tradable would show as craftable because + // they weren't real items with a real origin that would prevent them from being crafted. In the short term it makes + // more sense to just force this to always display "false" because at least it will never be wrong. + m_pDetailsViewChild->SetDialogVariable( "craftable", pDef->IsBundle() + ? g_pVGuiLocalize->Find( "#Attrib_CannotCraftWeapons" ) + : pDef->GetCapabilities() & ITEM_CAP_CAN_BE_CRAFTED_IF_PURCHASED + ? pYesNo[0] + : pYesNo[1] ); + + // Setup price + if ( pEntry ) + { + ECurrency eCurrency = EconUI()->GetStorePanel()->GetCurrency(); + + int iTotalPrice = pEntry->GetCurrentPrice( eCurrency ); + wchar_t wzLocalizedPrice[ kLocalizedPriceSizeInChararacters ]; + MakeMoneyString( wzLocalizedPrice, ARRAYSIZE( wzLocalizedPrice ), iTotalPrice, eCurrency ); + +#ifdef ENABLE_STORE_RENTAL_BACKEND + const wchar_t *pwsRentalPriceFormat = GLocalizationProvider()->Find( "#TF_Store_RentalPriceFormat" ); + if ( pEntry->IsRentable() && pwsRentalPriceFormat ) + { + wchar_t wzRentalLocalizedPrice[ kLocalizedPriceSizeInChararacters ]; + MakeMoneyString( wzRentalLocalizedPrice, ARRAYSIZE( wzRentalLocalizedPrice ), pEntry->GetRentalPriceScale() * iTotalPrice, eCurrency ) + + wchar_t wzLocalizedPriceString[96]; + ::ILocalize::ConstructString_safe( wzLocalizedPriceString, pwsRentalPriceFormat, 2, wzLocalizedPrice, wzRentalLocalizedPrice ); + m_pDetailsViewChild->SetDialogVariable( "price", wzLocalizedPriceString ); + } + else +#endif + { + if ( pEntry->m_bIsMarketItem ) + { + if ( iTotalPrice != 0 ) + { + wchar_t wzMarketString[96]; + g_pVGuiLocalize->ConstructString_safe( + wzMarketString, + LOCCHAR( "%s1 %s2" ), + 2, + g_pVGuiLocalize->Find( "#Store_StartingAt" ), + wzLocalizedPrice ); + + m_pDetailsViewChild->SetDialogVariable( "price", wzMarketString ); + } + else + { + m_pDetailsViewChild->SetDialogVariable( "price", "..." ); + } + } + else + { + // if market item. Prefix 'Starting at' + m_pDetailsViewChild->SetDialogVariable( "price", wzLocalizedPrice ); + } + } + } + + // Show/hide rental button. + for ( int i = 0; i < ARRAYSIZE( m_pAddRentalToCartButtons ); i++ ) + { + if ( m_pAddRentalToCartButtons[i] ) + { + m_pAddRentalToCartButtons[i]->SetVisible( pEntry && pEntry->IsRentable() ); + } + } + } + + // Final label value for our armory description text block. + const wchar_t *pwszLabelValue = L""; + m_bArmoryTextAdded = false; + + const char *pszArmoryDescString = pDef->GetArmoryDescString(); + if ( pszArmoryDescString ) + { + const ArmoryStringDict_t& ArmoryKeys = GetItemSchema()->GetArmoryDataItems(); + const ArmoryStringDict_t::IndexType_t armoryIndex = ArmoryKeys.Find( pszArmoryDescString ); + + if ( ArmoryKeys.IsValidIndex( armoryIndex ) ) + { + const char *pszArmoryDescLocalizationKey = ArmoryKeys[ armoryIndex ].Get(); + + pwszLabelValue = g_pVGuiLocalize->Find( pszArmoryDescLocalizationKey ); + m_bArmoryTextAdded = true; + } + } + + m_pDetailsViewChild->SetDialogVariable( "armory_text", pwszLabelValue ); + } + + // clear all old reference item panels before adding new ones + FOR_EACH_VEC( m_vecReferenceItemPanels, i ) + { + m_vecReferenceItemPanels[i]->MarkForDeletion(); + } + m_vecReferenceItemPanels.RemoveAll(); + int iReferenceItemHeight = 0; + + const CEconItemDescription *pDescription = m_pItemFullImage->GetItem()->GetDescription(); + if ( pDescription ) + { + TextImage *pAttributesTextImage = m_pAttributesLabel->GetTextImage(); // This pointer already verified above + pAttributesTextImage->ClearColorChangeStream(); + + const int kAttribBufferSize = 4 * 1024; + wchar_t wszAttribBuffer[ kAttribBufferSize ] = L""; + + int iAttribLine = 0; + m_nNumAttribLinesAdded = 0; + Color clrPrev( 0, 0, 0, 0 ); + uint32 unCurrentTextStreamIndex = 0; + IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); + int iFontHeight = surface()->GetFontTall( m_pAttributesLabel->GetFont() ); + + if ( m_pItemCollectionHighlight ) + { + m_pItemCollectionHighlight->SetVisible( false ); + } + + iReferenceItemHeight = iFontHeight; + + int iAttributePanelX, iAttributePanelY; + m_pAttributesLabel->GetPos( iAttributePanelX, iAttributePanelY ); + + for ( uint32 i = 0; i < pDescription->GetLineCount(); ++i ) + { + const econ_item_description_line_t& line = pDescription->GetLine(i); + int nLineLength = StringFuncs<locchar_t>::Length( line.sText.Get() ); + if ( ( line.unMetaType & kDescLineFlag_Type ) != 0 ) + { + m_pDetailsViewChild->SetDialogVariable( "item_level_info", line.sText.Get() ); + } + else if ( ( line.unMetaType & kDescLineFlagSet_DisplayInAttributeBlock ) != 0 && m_pAttributesLabel ) + { + ++iAttribLine; + + // current collection item line + bool bIsCurrentCollectionItem = ( line.unMetaType & kDescLineFlag_CollectionCurrentItem ) != 0; + // use bg color as text color for current item for a better highlight + Color col = bIsCurrentCollectionItem ? Color( 0, 0, 0, 255 ) : pScheme->GetColor( GetColorNameForAttribColor( line.eColor ), Color( 255, 255, 255, 255 ) ); + + // Output a color change if necessary. + if ( i == 0 || clrPrev != col ) + { + pAttributesTextImage->AddColorChange( col, unCurrentTextStreamIndex ); + clrPrev = col; + } + + // Current line highlight + if ( bIsCurrentCollectionItem && m_pItemCollectionHighlight ) + { + // use text color as bg color for the current item for a better highlight + Color bgColor = pScheme->GetColor( GetColorNameForAttribColor( line.eColor ), Color( 255, 255, 255, 255 ) ); + + // Get the current ypos + int x, y; + m_pAttributesLabel->GetPos( x, y ); + m_pItemCollectionHighlight->SetPos( x, y + ( iAttribLine - 1 ) * iFontHeight ); + m_pItemCollectionHighlight->SetBgColor( bgColor ); + m_pItemCollectionHighlight->SetVisible( bIsCurrentCollectionItem ); + } + + if ( ( line.unMetaType & ( kDescLineFlag_Name | kDescLineFlag_Type ) ) == 0 ) + { + V_wcscat_safe( wszAttribBuffer, m_nNumAttribLinesAdded++ == 0 ? L"" : L"\n" ); // add empty lines everywhere except before the first line + V_wcscat_safe( wszAttribBuffer, line.sText.Get() ); + unCurrentTextStreamIndex += nLineLength + 1; // add one character to deal with newlines + } + + if ( line.unDefIndex != INVALID_ITEM_DEF_INDEX ) + { + // set text and recalculate the size now to compute for button pos + m_pAttributesLabel->SetText( wszAttribBuffer ); + m_pAttributesLabel->SizeToContents(); + + CItemModelPanel* pItemModelPanel = new CItemModelPanel( m_pAttributesLabel, CFmtStr( "reference_item_%d", m_vecReferenceItemPanels.Count() ) ); + pItemModelPanel->SetActAsButton( true, true ); + pItemModelPanel->SetAutoDelete( true ); + + pItemModelPanel->SetPos( 0, m_pAttributesLabel->GetTall() - iFontHeight ); + pItemModelPanel->SetZPos( m_pAttributesLabel->GetZPos() + 1 ); + + CEconItemView itemData; + itemData.Init( line.unDefIndex, AE_UNIQUE, AE_USE_SCRIPT_VALUE, true ); + itemData.SetClientItemFlags( kEconItemFlagClient_Preview ); + pItemModelPanel->SetItem( &itemData ); + pItemModelPanel->MakeFakeButton(); + + pItemModelPanel->SetTooltip( m_pMouseOverTooltip, "" ); + + m_vecReferenceItemPanels.AddToTail( pItemModelPanel ); + } + } + } + + // Make sure our string is NUL-terminated. + wszAttribBuffer[ kAttribBufferSize-1 ] = 0; + + m_pAttributesLabel->SetText( wszAttribBuffer ); + } + + // match the highlight width to attribute width + if ( m_pItemCollectionHighlight && m_pItemCollectionHighlight->IsVisible() ) + { + m_pAttributesLabel->SizeToContents(); + m_pItemCollectionHighlight->SetWide( m_pAttributesLabel->GetWide() ); + } + + if ( m_vecReferenceItemPanels.Count() ) + { + m_pAttributesLabel->SizeToContents(); + FOR_EACH_VEC( m_vecReferenceItemPanels, i ) + { + m_vecReferenceItemPanels[i]->SetSize( m_pAttributesLabel->GetWide(), iReferenceItemHeight ); + } + } + + // Get PerformLayout() called, now that we have text in our controls - without this, SizeToContents() sizes all the labels as tall and narrow. + InvalidateLayout( true ); + } + + // Set the visibility of the "Try it now!" button based on whether we as a client think this item should be able + // to be previewed. + const CEconStorePriceSheet *pPriceSheet = EconUI()->GetStorePanel()->GetPriceSheet(); + if ( pPriceSheet && m_pPreviewButton ) + { + const econ_store_entry_t *pStoreEntry = pPriceSheet->GetEntry( m_item.GetItemDefIndex() ); + m_pPreviewButton->SetVisible( pStoreEntry && pStoreEntry->CanPreview() ); + } + + m_pItemFullImage->InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel2::SetState( preview_state_t iState ) +{ + BaseClass::SetState( iState ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel2::UpdateIcons( void ) +{ + BaseClass::UpdateIcons(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel2::UpdatePlayerModelButtons() +{ + BaseClass::UpdatePlayerModelButtons(); + + if ( m_pPlayerModelPanel ) + { + if ( m_pTeamNavPanel ) + { + m_pTeamNavPanel->SetVisible( m_pPlayerModelPanel->IsVisible() ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel2::SetPlayerModelVisible( bool bVisible ) +{ + BaseClass::SetPlayerModelVisible( bVisible ); + + if ( m_pCycleTextLabel ) + { + m_pCycleTextLabel->SetVisible( bVisible ); + } + + if ( m_pGoFullscreenButton ) + { + m_pGoFullscreenButton->SetVisible( bVisible ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel2::SetCycleLabelText( vgui::Label *pTargetLabel, const char *pCycleText ) +{ + BaseClass::SetCycleLabelText( pTargetLabel, pCycleText ); + + if ( m_pCycleTextLabel ) + { + const wchar_t *pwszText = g_pVGuiLocalize->Find( pCycleText ); + m_pCycleTextLabel->SetText( pwszText ? pwszText : L"" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel2::OnNavButtonSelected( KeyValues *pData ) +{ + const int iTeam = pData->GetInt( "userdata", -1 ); AssertMsg( iTeam >= 0, "Bad filter" ); + if ( iTeam < 0 ) + return; + + if ( !m_pPlayerModelPanel ) + return; + + m_pPlayerModelPanel->SetTeam( iTeam ); + CyclePaint( false ); + + C_CTFGameStats::ImmediateWriteInterfaceEvent( "team_switch(store_preview_item_panel)", iTeam == TF_TEAM_RED ? "red" : "blu" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel2::OnExitFullscreen( KeyValues *pData ) +{ + if ( !m_pPlayerModelPanel ) + return; + + // If team or class changed in fullscreen mode, update our ui components here + if ( m_pTeamNavPanel ) + { + m_pTeamNavPanel->UpdateButtonSelectionStates( m_pPlayerModelPanel->GetTeam() == TF_TEAM_RED ? 0 : 1 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel2::OnTick( void ) +{ + BaseClass::OnTick(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel2::OnMouseWheeled( int delta ) +{ + if ( !m_pScrollBar ) + return; + + int val = m_pScrollBar->GetValue(); + val -= (delta * 50); + m_pScrollBar->SetValue( val ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel2::OnSliderMoved( int position ) +{ + m_iSliderPos = position; + UpdateScrollableChild(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel2::OnThink() +{ + BaseClass::OnThink(); + + Assert( IsVisible() ); + + // If the user clicks outside of the dialog frame, close the preview, like + // many web sites do on the internet. + bool bMouseDown = vgui::input()->IsMouseDown( MOUSE_LEFT ); + + // User just clicked? + if ( !m_pFullscreenPanel || !m_pFullscreenPanel->IsFullscreenMode() ) + { + if ( !m_bMouseWasDown && bMouseDown ) + { + vgui::input()->GetCursorPos( m_aClickPos[0], m_aClickPos[1] ); + m_bMouseWasDown = true; + } + else if ( m_pDialogFrame && bMouseDown && !m_pDialogFrame->IsWithin( m_aClickPos[0], m_aClickPos[1] ) ) + { + //m_bCloseOnUp = true; + } + else if ( !bMouseDown ) + { + if ( m_bCloseOnUp ) + { + C_CTFGameStats::ImmediateWriteInterfaceEvent( "store_preview_item_panel", "close_from_outside_click" ); + + DoClose(); + } + m_bCloseOnUp = false; + m_bMouseWasDown = false; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStorePreviewItemPanel2::DoClose() +{ + if ( m_pFullscreenPanel ) + { + m_pFullscreenPanel->ExitFullscreen(); + } + + OnClose(); + + SetVisible( false ); +} diff --git a/game/client/tf/vgui/store/v2/tf_store_preview_item2.h b/game/client/tf/vgui/store/v2/tf_store_preview_item2.h new file mode 100644 index 0000000..05f0657 --- /dev/null +++ b/game/client/tf/vgui/store/v2/tf_store_preview_item2.h @@ -0,0 +1,177 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_STORE_PREVIEW_ITEM2_H +#define TF_STORE_PREVIEW_ITEM2_H +#ifdef _WIN32 +#pragma once +#endif + +#include "store/tf_store_preview_item_base.h" + +namespace vgui +{ + class ScrollBar; +}; +class CNavigationPanel; +class CExLabel; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CFullscreenStorePreviewItem : public EditablePanel +{ + DECLARE_CLASS_SIMPLE( CFullscreenStorePreviewItem, EditablePanel ); +public: + CFullscreenStorePreviewItem( vgui::Panel *pParent, EditablePanel *pOwner ); + + void SetItemDef( itemid_t iItemDef ); + + void GoFullscreen( CTFPlayerModelPanel *pPlayerModelPanel ); + void ExitFullscreen(); + bool IsFullscreenMode(); + +private: + MESSAGE_FUNC_PARAMS( OnNavButtonSelected, "NavButtonSelected", pData ); + + virtual void OnThink(); + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void OnCommand( const char *command ); + + itemid_t m_iItemDef; + + CExLabel *m_pCycleTextLabel; + CNavigationPanel *m_pTeamNavPanel; + CExButton *m_pPreviewButton; + + struct ModelState_t + { + int m_aPlayerModelPanelBounds[4]; + Vector m_vecPlayerPos; + bool m_bZoomed; + } + m_OldModelState; + + struct Stats_t + { + Stats_t() { Clear(); } + void Clear() { V_memset( this, 0, sizeof( Stats_t ) ); } + + float m_flRotationTime; + } + m_Stats; + + float m_flGoFullscreenStartTime; + bool m_bIsHalloweenOrFullmoonOnlyItem; + vgui::DHANDLE< CTFPlayerModelPanel > m_pPlayerModelPanel; + + CExButton *m_pZoomButton; + CExButton *m_pRotLeftButton; + CExButton *m_pRotRightButton; + + EditablePanel *m_pOverlayPanel; + + PHandle m_hOwner; + + int m_nLastMouseX; + int m_nLastMouseY; + float m_flLastMouseMoveTime; + + CPanelAnimationVar( float, m_flFullscreenFadeToBlackDuration, "fullscreen_fade_to_black_duration", "1.0" ); + CPanelAnimationVar( float, m_flModelPanelOriginX, "fullscreen_modelpanel_origin_x", "170" ); + CPanelAnimationVar( float, m_flModelPanelOriginY, "fullscreen_modelpanel_origin_y", "0" ); + CPanelAnimationVar( float, m_flModelPanelOriginZ, "fullscreen_modelpanel_origin_z", "-36" ); + CPanelAnimationVar( float, m_flUiFadeoutTime, "ui_fadeout_time", "5.0" ); + CPanelAnimationVar( float, m_flUiFadeoutDuration, "ui_fadeout_duration", "1.0" ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CTFStorePreviewItemPanel2 : public CTFStorePreviewItemPanelBase +{ + DECLARE_CLASS_SIMPLE( CTFStorePreviewItemPanel2, CTFStorePreviewItemPanelBase ); +public: + CTFStorePreviewItemPanel2( vgui::Panel *pParent, const char *pResFile, const char *pPanelName, CStorePage *pOwner ); + + virtual void PreviewItem( int iClass, CEconItemView *pItem, const econ_store_entry_t* pEntry=NULL ) OVERRIDE; + void PreviewItemCopy( int iClass, CEconItemView *pItem, const econ_store_entry_t* pEntry=NULL ); + virtual void SetState( preview_state_t iState ); + + MESSAGE_FUNC_PARAMS( OnClassIconSelected, "ClassIconSelected", data ); + MESSAGE_FUNC( OnHideClassIconMouseover, "HideClassIconMouseover" ); + MESSAGE_FUNC_PARAMS( OnShowClassIconMouseover, "ShowClassIconMouseover", data ); + +protected: + virtual void OnThink(); + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void OnCommand( const char *command ); + virtual void PerformLayout( void ); + virtual void OnTick( void ); + virtual void OnMouseWheeled( int delta ); + + int PlaceControl( Panel *pParent, const char *pControlNameA, const char *pControlNameB, int nOffset, bool bVertical, + bool bSizeAToContents = true, bool bUseContentSize = true ); + void DoClose(); + void Clear(); + void UpdateScrollableChild(); + + virtual void SetPlayerModelVisible( bool bVisible ); + virtual void UpdateIcons( void ); + virtual void UpdatePlayerModelButtons( void ); + virtual void SetCycleLabelText( vgui::Label *pTargetLabel, const char *pCycleText ); + + MESSAGE_FUNC_PARAMS( OnNavButtonSelected, "NavButtonSelected", pData ); + MESSAGE_FUNC_PARAMS( OnExitFullscreen, "ExitFullscreen", pData ); + + Label *m_pLastNewLineControl; + EditablePanel *m_pDialogFrame; /// The background border + EditablePanel *m_pPreviewViewportBg; + CExLabel *m_pItemNameLabel; + CExLabel *m_pAttributesLabel; + vgui::EditablePanel *m_pItemCollectionHighlight; + CExLabel *m_pCycleTextLabel; + int m_nNumAttribLinesAdded; + bool m_bArmoryTextAdded; + EditablePanel *m_pDetailsView; + EditablePanel *m_pDetailsViewChild; + CExButton *m_pAddRentalToCartButtons[3]; + EditablePanel *m_pScrollableChild; + ScrollBar *m_pScrollBar; + int m_iSliderPos; + bool m_bCloseOnUp; + bool m_bMouseWasDown; + int m_aClickPos[2]; + CExButton *m_pItemWikiPageButton; + CNavigationPanel *m_pTeamNavPanel; + CExButton *m_pPreviewButton; + CExImageButton *m_pGoFullscreenButton; + int m_nViewMaxHeight; + + CFullscreenStorePreviewItem *m_pFullscreenPanel; + bool m_bIsHalloweenOrFullmoonOnlyItem; + + CEconItemView *m_pItemViewData; + CEconItem *m_pSOEconItemData; + + // mouse over reference item tooltip + CItemModelPanel *m_pMouseOverItemPanel; + CItemModelPanelToolTip *m_pMouseOverTooltip; + CUtlVector< CItemModelPanel* > m_vecReferenceItemPanels; + + CPanelAnimationVarAliasType( int, m_iSmallVerticalBreakSize, "small_vertical_break_size", "0", "proportional_ypos" ); + CPanelAnimationVarAliasType( int, m_iMediumVerticalBreakSize, "medium_vertical_break_size", "0", "proportional_ypos" ); + CPanelAnimationVarAliasType( int, m_iBigVerticalBreakSize, "big_vertical_break_size", "0", "proportional_ypos" ); + CPanelAnimationVarAliasType( int, m_iHorizontalBreakSize, "horizontal_break_size", "0", "proportional_xpos" ); + CPanelAnimationVarAliasType( int, m_iControlButtonWidth, "control_button_width", "0", "proportional_xpos" ); + CPanelAnimationVarAliasType( int, m_iControlButtonHeight, "control_button_height", "0", "proportional_ypos" ); + CPanelAnimationVarAliasType( int, m_iControlButtonY, "control_button_y", "0", "proportional_ypos" ); + + MESSAGE_FUNC_INT( OnSliderMoved, "ScrollBarSliderMoved", position ); +}; + +#endif // TF_STORE_PREVIEW_ITEM2_H diff --git a/game/client/tf/vgui/strange_count_transfer_panel.cpp b/game/client/tf/vgui/strange_count_transfer_panel.cpp new file mode 100644 index 0000000..d16a941 --- /dev/null +++ b/game/client/tf/vgui/strange_count_transfer_panel.cpp @@ -0,0 +1,269 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "strange_count_transfer_panel.h" +#include "cdll_client_int.h" +#include "ienginevgui.h" +#include "econ_item_tools.h" +#include "econ_ui.h" +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CStatModuleItemSelectionPanel : public CItemCriteriaSelectionPanel +{ + DECLARE_CLASS_SIMPLE( CStatModuleItemSelectionPanel, CItemCriteriaSelectionPanel ); +public: + CStatModuleItemSelectionPanel( Panel *pParent, const CEconItemView* pCorrespondingItem ) + : BaseClass( pParent, NULL ) + , m_pCorrespondingItem( pCorrespondingItem ) + , m_mapXifierClassCount( CaselessStringLessThan ) + { + int nCount = InventoryManager()->GetLocalInventory()->GetItemCount(); + for( int i=0; i<nCount; ++i ) + { + if ( !BIsItemStrange( InventoryManager()->GetLocalInventory()->GetItem( i ) ) ) + continue; + + const char *pItemXifier = InventoryManager()->GetLocalInventory()->GetItem( i )->GetItemDefinition()->GetXifierRemapClass(); + auto idx = m_mapXifierClassCount.Find( pItemXifier ); + if ( idx == m_mapXifierClassCount.InvalidIndex() ) + { + idx = m_mapXifierClassCount.Insert( pItemXifier, 0 ); + } + + m_mapXifierClassCount[ idx ] = m_mapXifierClassCount[ idx ] + 1; + } + } + + void ApplySchemeSettings( vgui::IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + vgui::Label* pWeaponLabel = dynamic_cast<vgui::Label*>( FindChildByName("ItemSlotLabel") ); + if ( pWeaponLabel ) + { + pWeaponLabel->SetVisible( false ); + } + + } + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + const char *GetItemNotSelectableReason( const CEconItemView *pItem ) const + { + if ( !pItem ) + return NULL; + + if ( !BIsItemStrange( pItem ) ) + return "#TF_StrangeCount_Transfer_NotStrange"; + + if ( m_pCorrespondingItem ) + { + if ( pItem->GetItemID() == m_pCorrespondingItem->GetItemID() ) + return "#TF_StrangeCount_Transfer_Self"; + + if ( !CEconTool_StrangeCountTransfer::AreItemsEligibleForStrangeCountTransfer( m_pCorrespondingItem, pItem ) ) + return "#TF_StrangeCount_Transfer_TypeMismatch"; + } + + const char *pItemXifier = pItem->GetItemDefinition()->GetXifierRemapClass(); + auto idx = m_mapXifierClassCount.Find( pItemXifier ); + int nCount = 0; + if ( !pItemXifier ) + { + // if no xifier, find atleast 1 other matching item + CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory(); + if ( pInventory ) + { + for ( int i = 0; i < pInventory->GetItemCount(); i++ ) + { + CEconItemView *pIterItem = pInventory->GetItem( i ); + if ( pIterItem->GetItemDefIndex() == pItem->GetItemDefIndex() && BIsItemStrange(pIterItem) ) + { + // find 2 or more, yourself and another + if ( ++nCount >= 2 ) + return NULL; + } + } + } + } + else if ( idx != m_mapXifierClassCount.InvalidIndex() ) + { + nCount = m_mapXifierClassCount[ idx ]; + } + + if ( nCount < 2 ) + { + return "#TF_StrangeCount_Transfer_NotEnoughMatches"; + } + + return NULL; + } + +protected: + const char * m_pszTitleToken; + const CEconItemView* m_pCorrespondingItem; + CUtlMap< const char*, int > m_mapXifierClassCount; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CStrangeCountTransferPanel::CStrangeCountTransferPanel( vgui::Panel *parent, CEconItemView* pToolItem ) + : BaseClass( parent, "StrangeCountTrasnferDialog" ) + , m_pToolItem( pToolItem ) +{ + Assert( pToolItem ); + + ListenForGameEvent( "gameui_hidden" ); + + m_hSelectionPanel = 0; + m_pSelectingItemModelPanel = NULL; + + EditablePanel* pBG = new EditablePanel( this, "BG" ); + + m_pSourceStrangeModelPanel = new CItemModelPanel( pBG, "SourceItem" ); + m_pSourceStrangeModelPanel->SetActAsButton( true, true ); + m_pTargetStrangeModelPanel = new CItemModelPanel( pBG, "TargetItem" ); + m_pTargetStrangeModelPanel->SetActAsButton( true, true ); + m_pOKButton = new CExButton( pBG, "OkButton", "" ); + + vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme" ); + SetScheme( scheme ); + SetProportional( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CStrangeCountTransferPanel::~CStrangeCountTransferPanel( void ) +{ + if ( m_hSelectionPanel ) + { + m_hSelectionPanel->MarkForDeletion(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CStrangeCountTransferPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( GetResFile() ); +} + +void CStrangeCountTransferPanel::PerformLayout() +{ + BaseClass::PerformLayout(); + + UpdateOKButton(); + + m_pSourceStrangeModelPanel->SetTooltip( EconUI()->GetBackpackPanel()->GetMouseOverToolTipPanel(), "" ); + m_pTargetStrangeModelPanel->SetTooltip( EconUI()->GetBackpackPanel()->GetMouseOverToolTipPanel(), "" ); +} + +void CStrangeCountTransferPanel::OnCommand( const char *command ) +{ + if( FStrEq( "apply", command ) ) + { + GCSDK::CProtoBufMsg<CMsgApplyStrangeCountTransfer> msg( k_EMsgGCApplyStrangeCountTransfer ); + + if ( !m_pToolItem || !m_pSourceStrangeModelPanel->GetItem() || !m_pTargetStrangeModelPanel->GetItem() ) + return; + + msg.Body().set_tool_item_id( m_pToolItem->GetItemID() ); + msg.Body().set_item_src_item_id( m_pSourceStrangeModelPanel->GetItem()->GetItemID() ); + msg.Body().set_item_dest_item_id( m_pTargetStrangeModelPanel->GetItem()->GetItemID() ); + GCClientSystem()->BSendMessage( msg ); + + EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolItem, "applied_strangecounttransfer", m_pToolItem->GetItemDefIndex() ); + + GCClientSystem()->BSendMessage( msg ); + + SetVisible( false ); + MarkForDeletion(); + + return; + } + else if ( FStrEq( "cancel", command ) ) + { + MarkForDeletion(); + return; + } + + BaseClass::OnCommand( command ); +} + +void CStrangeCountTransferPanel::FireGameEvent( IGameEvent *event ) +{ + if ( FStrEq( event->GetName(), "gameui_hidden" ) ) + { + SetVisible( false ); + MarkForDeletion(); + return; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CStrangeCountTransferPanel::OnItemPanelMousePressed( vgui::Panel *panel ) +{ + CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel ); + + if ( pItemPanel && IsVisible() && !pItemPanel->IsGreyedOut() ) + { + m_pSelectingItemModelPanel = pItemPanel; + + CEconItemView* pOtherItem = pItemPanel == m_pSourceStrangeModelPanel ? m_pTargetStrangeModelPanel->GetItem() + : m_pSourceStrangeModelPanel->GetItem(); + + m_hSelectionPanel = new CStatModuleItemSelectionPanel( GetParent(), pOtherItem ); + + // Clicked on an item in the crafting area. Open up the selection panel. + m_hSelectionPanel->ShowDuplicateCounts( false ); + m_hSelectionPanel->ShowPanel( 0, true ); + m_hSelectionPanel->SetCaller( this ); + m_hSelectionPanel->SetZPos( GetZPos() + 1 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CStrangeCountTransferPanel::OnSelectionReturned( KeyValues *data ) +{ + Assert( m_pSelectingItemModelPanel ); + + if ( data && m_pSelectingItemModelPanel ) + { + uint64 ulIndex = data->GetUint64( "itemindex", INVALID_ITEM_ID ); + + CEconItemView* pSelectedItem = InventoryManager()->GetLocalInventory()->GetInventoryItemByItemID( ulIndex ); + m_pSelectingItemModelPanel->SetItem( pSelectedItem ); + } + + UpdateOKButton(); + + m_pSelectingItemModelPanel = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CStrangeCountTransferPanel::UpdateOKButton() +{ + bool bOKEnabled = m_pSourceStrangeModelPanel->GetItem() && m_pTargetStrangeModelPanel->GetItem(); + m_pOKButton->SetEnabled( bOKEnabled ); +}
\ No newline at end of file diff --git a/game/client/tf/vgui/strange_count_transfer_panel.h b/game/client/tf/vgui/strange_count_transfer_panel.h new file mode 100644 index 0000000..912260e --- /dev/null +++ b/game/client/tf/vgui/strange_count_transfer_panel.h @@ -0,0 +1,60 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef STRANGE_COUNT_TRANSFER_H +#define STRANGE_COUNT_TRANSFER_H +#ifdef _WIN32 +#pragma once +#endif + +#include "backpack_panel.h" +#include "vgui_controls/ScrollableEditablePanel.h" +#include "tf_gcmessages.h" +#include "econ_gcmessages.h" +#include "tf_imagepanel.h" +#include "tf_controls.h" +#include "item_selection_panel.h" +#include "confirm_dialog.h" + +//----------------------------------------------------------------------------- +// A panel to let users choose 2 weapons to tranfer strange counts with +//----------------------------------------------------------------------------- +class CStrangeCountTransferPanel : public vgui::EditablePanel, public CGameEventListener +{ +public: + DECLARE_CLASS_SIMPLE( CStrangeCountTransferPanel, vgui::EditablePanel ); + CStrangeCountTransferPanel( vgui::Panel *parent, CEconItemView* pToolItem ); + ~CStrangeCountTransferPanel( void ); + + virtual const char *GetResFile( void ) { return "Resource/UI/econ/StrangeCountTransferDialog.res"; } + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void PerformLayout() OVERRIDE; + virtual void FireGameEvent( IGameEvent *event ) OVERRIDE; + virtual void OnCommand( const char *command ) OVERRIDE; + + MESSAGE_FUNC_PTR( OnItemPanelMousePressed, "ItemPanelMousePressed", panel ); + MESSAGE_FUNC_PARAMS( OnSelectionReturned, "SelectionReturned", data ); + +private: + + void UpdateOKButton(); + + CTFTextToolTip *m_pToolTip; + vgui::EditablePanel *m_pToolTipEmbeddedPanel; + + DHANDLE<CItemCriteriaSelectionPanel> m_hSelectionPanel; + + CExButton *m_pOKButton; + CEconItemView *m_pToolItem; + CItemModelPanel *m_pSelectingItemModelPanel; + CItemModelPanel *m_pSourceStrangeModelPanel; + CItemModelPanel *m_pTargetStrangeModelPanel; + CItemModelPanel *m_pMouseOverItemPanel; + CItemModelPanelToolTip *m_pMouseOverTooltip; +}; + +#endif // STRANGE_COUNT_TRANSFER diff --git a/game/client/tf/vgui/testitem_dialog.cpp b/game/client/tf/vgui/testitem_dialog.cpp new file mode 100644 index 0000000..47b6fae --- /dev/null +++ b/game/client/tf/vgui/testitem_dialog.cpp @@ -0,0 +1,754 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include <vgui/ILocalize.h> +#include "vgui_controls/TextEntry.h" +#include "vgui_controls/ComboBox.h" +#include "vgui_controls/CheckButton.h" +#include "testitem_dialog.h" +#include "tf_controls.h" +#include "c_playerresource.h" +#include "gcsdk/gcmsg.h" +#include "tf_gcmessages.h" +#include "econ_item_inventory.h" +#include "econ_gcmessages.h" +#include "ienginevgui.h" +#include "filesystem.h" +#include "vgui_controls/FileOpenDialog.h" +#include "econ_item_system.h" +#include "testitem_root.h" +#include "econ_item_tools.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +extern const char *g_TeamVisualSections[TEAM_VISUAL_SECTIONS]; + +static const char *g_pszTestItemHideBodygroup[] = +{ + "hat", // TI_HIDEBG_HAT, + "headphones", // TI_HIDEBG_HEADPHONES, + "medal", // TI_HIDEBG_MEDALS, + "grenades", // TI_HIDEBG_GRENADES, + "bullets", // TI_HIDEBG_BULLETS + "arrows", // TI_HIDEBG_ARROWS + "rightarm", // TI_HIDEBG_RIGHTARM + "shoes_socks", // TI_HIDEBG_SHOES_SOCKS +}; +COMPILE_TIME_ASSERT( ARRAYSIZE( g_pszTestItemHideBodygroup ) == TI_HIDEBG_COUNT ); + +static const char *g_pszClassSubdirectories[] = +{ + "all_class", // TF_CLASS_UNDEFINED = 0, + "scout", // TF_CLASS_SCOUT, // TF_FIRST_NORMAL_CLASS + "sniper", // TF_CLASS_SNIPER, + "soldier", // TF_CLASS_SOLDIER, + "demo", // TF_CLASS_DEMOMAN, + "medic", // TF_CLASS_MEDIC, + "heavy", // TF_CLASS_HEAVYWEAPONS, + "pyro", // TF_CLASS_PYRO, + "spy", // TF_CLASS_SPY, + "engineer", // TF_CLASS_ENGINEER, +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTestItemDialog::CTestItemDialog( vgui::Panel *parent, testitem_itemtypes_t iItemType, int iClassUsage, KeyValues *pExistingKVs ) : vgui::EditablePanel( parent, "TestItemDialog" ) +{ + // Need to use the clientscheme (we're not parented to a clientscheme'd panel) + vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme"); + SetScheme(scheme); + SetProportional( true ); + + ListenForGameEvent( "gameui_hidden" ); + + m_hImportModelDialog = NULL; + m_pModelLabel = NULL; + m_pSelectModelLabel = NULL; + m_pNoItemsToReplaceLabel = NULL; + m_pSelectModelButton = NULL; + m_pOkButton = NULL; + + m_pItemReplacedPanel = new vgui::EditablePanel( this, "ItemReplacedPanel" ); + m_pItemReplacedComboBox = new vgui::ComboBox( m_pItemReplacedPanel, "ItemReplacedComboBox", 20, false ); + m_pItemReplacedComboBox->AddActionSignalTarget( this ); + + m_pExistingItemToTestPanel = new vgui::EditablePanel( this, "ExistingItemToTestPanel" ); + m_pExistingItemComboBox = new vgui::ComboBox( m_pExistingItemToTestPanel, "ExistingItemComboBox", 20, false ); + m_pExistingItemComboBox->AddActionSignalTarget( this ); + + m_pBodygroupPanel = new vgui::EditablePanel( this, "BodygroupPanel" ); + for ( int i = 0; i < TI_HIDEBG_COUNT; i++ ) + { + m_pBodygroupCheckButtons[i] = new vgui::CheckButton( m_pBodygroupPanel, VarArgs("HideBodygroupCheckBox%d",i), "" ); + m_pBodygroupCheckButtons[i]->AddActionSignalTarget( this ); + } + + m_pCustomizationsPanel = new vgui::EditablePanel( this, "CustomizationsPanel" ); + m_pPaintColorComboBox = new vgui::ComboBox( m_pCustomizationsPanel, "PaintColorComboBox", 20, false ); + m_pPaintColorComboBox->AddActionSignalTarget( this ); + + m_pUnusualEffectComboBox = new vgui::ComboBox( m_pCustomizationsPanel, "UnusualEffectComboBox", 20, false ); + m_pUnusualEffectComboBox->AddActionSignalTarget( this ); + + m_iItemType = iItemType; + m_iClassUsage = iClassUsage; + + m_szRelativePath[0] = '\0'; + SetDialogVariable("testmodel", g_pVGuiLocalize->Find( "#IT_NoModel" ) ); + SetEntryStep( TI_STEP_MODELNAME ); + + // Load our scheme right away so we have all our pieces ready + MakeReadyForUse(); + + SetupPaintColorComboBox(); + SetupUnusualEffectComboBox(); + + // Pull the data out of the existing KVs + if ( pExistingKVs ) + { + InitializeFromExistingKVs( pExistingKVs ); + } + else + { + for ( int i = 0; i < TI_HIDEBG_COUNT; i++ ) + { + // Start with the "hat" bodygroup checked (for non-weapons) + bool bIsHat = ( m_iItemType != TI_TYPE_WEAPON ) && ( i == 0 ); + m_pBodygroupCheckButtons[i]->SetSelected( bIsHat ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemDialog::InitializeFromExistingKVs( KeyValues *pExistingKVs ) +{ + // If we're testing an existing item, it supercedes everything else + item_definition_index_t iExistingItemDef = pExistingKVs->GetInt( "existing_itemdef", INVALID_ITEM_DEF_INDEX ); + if ( iExistingItemDef != INVALID_ITEM_DEF_INDEX ) + { + SetupItemComboBox( m_pExistingItemComboBox ); + + // Loop through the entries until we find the specified item def + for ( int i = 0; i < m_pExistingItemComboBox->GetItemCount(); i++ ) + { + int iItemID = m_pExistingItemComboBox->GetItemIDFromRow(i); + KeyValues *pRowKV = m_pExistingItemComboBox->GetItemUserData( iItemID ); + if ( pRowKV && pRowKV->GetInt( "item", INVALID_ITEM_DEF_INDEX ) == iExistingItemDef ) + { + m_pExistingItemComboBox->SilentActivateItemByRow(i); + SetEntryStep( TI_STEP_FINISHED ); + } + } + } + else + { + const char *pszModel = pExistingKVs->GetString( "model_player", NULL ); + if ( pszModel && pszModel[0] ) + { + Q_strncpy( m_szRelativePath, pszModel, MAX_PATH ); + SetDialogVariable("testmodel", m_szRelativePath ); + SetEntryStep( TI_STEP_MODELNAME ); + + SetEntryStep( TI_STEP_WPN_ITEMREPLACED ); + if ( m_iItemType == TI_TYPE_WEAPON ) + { + item_definition_index_t iItemDefToReplace = pExistingKVs->GetInt( "item_replace", INVALID_ITEM_DEF_INDEX ); + if ( iItemDefToReplace != INVALID_ITEM_DEF_INDEX ) + { + SetupItemComboBox( m_pItemReplacedComboBox ); + + // Loop through the entries until we find the specified item def + for ( int i = 0; i < m_pItemReplacedComboBox->GetItemCount(); i++ ) + { + int iItemID = m_pItemReplacedComboBox->GetItemIDFromRow(i); + KeyValues *pRowKV = m_pItemReplacedComboBox->GetItemUserData( iItemID ); + if ( pRowKV && pRowKV->GetInt( "item", INVALID_ITEM_DEF_INDEX ) == iItemDefToReplace ) + { + m_pItemReplacedComboBox->SilentActivateItemByRow(i); + SetEntryStep( TI_STEP_FINISHED ); + } + } + } + } + else + { + KeyValues *pkvVisuals = pExistingKVs->FindKey( g_TeamVisualSections[0] ); + if ( pkvVisuals ) + { + KeyValues *pKVEntry = pkvVisuals->GetFirstSubKey(); + while ( pKVEntry ) + { + if ( !Q_stricmp( pKVEntry->GetName(), "player_bodygroups" ) ) + { + FOR_EACH_SUBKEY( pKVEntry, pKVSubEntry ) + { + int iBG = StringFieldToInt( pKVSubEntry->GetName(), g_pszTestItemHideBodygroup, ARRAYSIZE(g_pszTestItemHideBodygroup) ); + if ( iBG >= 0 && iBG < TI_HIDEBG_COUNT ) + { + m_pBodygroupCheckButtons[iBG]->SetSelected( pKVSubEntry->GetInt() == 0 ); + } + } + } + + pKVEntry = pKVEntry->GetNextKey(); + } + } + + // Start with the right paint can selected + int iPaintCanIndex = pExistingKVs->GetInt("paintcan_index", 0); + for ( int i = 0; i < m_pPaintColorComboBox->GetItemCount(); i++ ) + { + int iItemID = m_pPaintColorComboBox->GetItemIDFromRow(i); + KeyValues *pRowKV = m_pPaintColorComboBox->GetItemUserData( iItemID ); + if ( pRowKV && pRowKV->GetInt("paintcan_index",0) == iPaintCanIndex ) + { + m_pPaintColorComboBox->SilentActivateItemByRow(i); + } + } + + // Start with the right unusual effect selected + int iUnusualIndex = pExistingKVs->GetInt("unusual_index", 0); + for ( int i = 0; i < m_pUnusualEffectComboBox->GetItemCount(); i++ ) + { + int iItemID = m_pUnusualEffectComboBox->GetItemIDFromRow(i); + KeyValues *pRowKV = m_pUnusualEffectComboBox->GetItemUserData( iItemID ); + if ( pRowKV && pRowKV->GetInt("unusual_index",0) == iUnusualIndex ) + { + m_pUnusualEffectComboBox->SilentActivateItemByRow(i); + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTestItemDialog::~CTestItemDialog( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemDialog::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "resource/ui/TestItemDialog.res" ); + + m_pModelLabel = dynamic_cast<CExLabel*>( FindChildByName( "ModelLabel" ) ); + m_pSelectModelLabel = dynamic_cast<CExLabel*>( FindChildByName( "SelectModelLabel" ) ); + m_pSelectModelButton = dynamic_cast<CExButton*>( FindChildByName( "SelectModelButton" ) ); + m_pOkButton = dynamic_cast<CExButton*>( FindChildByName( "OkButton" ) ); + + m_pNoItemsToReplaceLabel = dynamic_cast<CExLabel*>( m_pItemReplacedPanel->FindChildByName( "NoItemsToReplaceLabel" ) ); + + SetEntryStep( m_iEntryStep ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemDialog::PerformLayout( void ) +{ + BaseClass::PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemDialog::FireGameEvent( IGameEvent *event ) +{ + const char *type = event->GetName(); + + if ( Q_strcmp(type, "gameui_hidden") == 0 ) + { + Close(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemDialog::Close( void ) +{ + TFModalStack()->PopModal( this ); + SetVisible( false ); + MarkForDeletion(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemDialog::CloseAndUpdateItem( void ) +{ + // We're going to assemble a KV block that describes this test item + KeyValues *kv = new KeyValues( "SetTestItemKVs" ); + kv->SetInt( "item_type", m_iItemType ); + kv->SetString( "model_player", m_szRelativePath ); + kv->SetBool( "test_existing_item", false ); + kv->SetInt( "attach_to_hands", (m_iItemType == TI_TYPE_WEAPON) ); + + KeyValues *pKVModels = new KeyValues( "model_player_per_class" ); + kv->AddSubKey( pKVModels ); + const char *pFilename = V_UnqualifiedFileName( m_szRelativePath ); + if ( pFilename) + { + for ( int i = TF_FIRST_NORMAL_CLASS; i < ARRAYSIZE( g_pszClassSubdirectories ); i++ ) + { + if ( m_iClassUsage == 1 || ( m_iClassUsage & (1 << i) ) ) + { + CFmtStr1024 path( "models/player/items/%s/%s", g_pszClassSubdirectories[i], pFilename ); + if ( g_pFullFileSystem->FileExists( path.Access() ) ) + { + pKVModels->SetString( ItemSystem()->GetItemSchema()->GetClassUsabilityStrings()[i], path.Access() ); + } + } + } + } + + KeyValues *pkvVisuals = new KeyValues( g_TeamVisualSections[0] ), + *pkvPlayerBodyGroups = new KeyValues( "player_bodygroups" ); + + kv->AddSubKey( pkvVisuals ); + pkvVisuals->AddSubKey( pkvPlayerBodyGroups ); + + for ( int i = 0; i < TI_HIDEBG_COUNT; i++ ) + { + KeyValues *pKVBG = new KeyValues( g_pszTestItemHideBodygroup[i] ); + pKVBG->SetInt( NULL, m_pBodygroupCheckButtons[i]->IsSelected() ? 0 : 1 ); + pkvPlayerBodyGroups->AddSubKey( pKVBG ); + } + + // Extract the paint can index + KeyValues *pPaintComboKV = m_pPaintColorComboBox->GetActiveItemUserData(); + int iPaintCanIndex = pPaintComboKV ? pPaintComboKV->GetInt( "paintcan_index", 0 ) : 0; + kv->SetInt( "paintcan_index", iPaintCanIndex ); + + // Extract the unusual effect index + KeyValues *pUnusualComboKV = m_pUnusualEffectComboBox->GetActiveItemUserData(); + int iUnusualIndex = pUnusualComboKV ? pUnusualComboKV->GetInt( "unusual_index", 0 ) : 0; + kv->SetInt( "unusual_index", iUnusualIndex ); + + item_definition_index_t iItemDef = INVALID_ITEM_DEF_INDEX; + + // See if we're copying an existing item + KeyValues *pExistingUserData = m_pExistingItemComboBox->GetActiveItemUserData(); + item_definition_index_t iExistingItemDef = pExistingUserData ? pExistingUserData->GetInt( "item", INVALID_ITEM_DEF_INDEX ) : INVALID_ITEM_DEF_INDEX; + if ( iExistingItemDef != INVALID_ITEM_DEF_INDEX ) + { + iItemDef = iExistingItemDef; + kv->SetInt( "existing_itemdef", iItemDef ); + kv->SetBool( "test_existing_item", true ); + + // copy model path from existing items + GameItemDefinition_t *pItemDef = ItemSystem()->GetStaticDataForItemByDefIndex( iItemDef ); + if ( pItemDef ) + { + for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ ) + { + if ( m_iClassUsage == 1 || ( m_iClassUsage & (1 << iClass) ) ) + { + const char *pszClassString = ItemSystem()->GetItemSchema()->GetClassUsabilityStrings()[iClass]; + const char *pszModel = pItemDef->GetPlayerDisplayModel( iClass ); + pKVModels->SetString( pszClassString, pszModel ); + } + } + } + } + else + { + KeyValues *pUserData = m_pItemReplacedComboBox->GetActiveItemUserData(); + iItemDef = pUserData ? pUserData->GetInt( "item", INVALID_ITEM_DEF_INDEX ) : INVALID_ITEM_DEF_INDEX; + + // Find the item def we're going to build off + switch ( m_iItemType ) + { + case TI_TYPE_WEAPON: + // Need an item def to replace + if ( iItemDef == INVALID_ITEM_DEF_INDEX ) + return; + break; + case TI_TYPE_HEADGEAR: + iItemDef = ItemSystem()->GetItemSchema()->GetItemDefinitionByName("Football Helmet")->GetDefinitionIndex(); + break; + case TI_TYPE_MISC1: + iItemDef = ItemSystem()->GetItemSchema()->GetItemDefinitionByName("Employee Badge A")->GetDefinitionIndex(); + break; + case TI_TYPE_MISC2: + iItemDef = ItemSystem()->GetItemSchema()->GetItemDefinitionByName("High Five Taunt")->GetDefinitionIndex(); + break; + } + } + + // Tell the server what item we're replacing, and what def index we used + kv->SetInt( "item_replace", iItemDef ); + + // Send it to the testing root panel + PostMessage( GetParent(), kv ); + + Close(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemDialog::OnCommand( const char *command ) +{ + if ( !Q_stricmp( command, "cancel" ) ) + { + Close(); + return; + } + else if ( !Q_stricmp( command, "ok" ) ) + { + CloseAndUpdateItem(); + return; + } + else if ( !Q_stricmp( command, "reloadscheme" ) ) + { + InvalidateLayout( false, true ); + return; + } + else if ( !Q_stricmp( command, "select_model" ) ) + { + OpenSelectModelDialog(); + return; + } + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemDialog::OpenSelectModelDialog( void ) +{ + if (m_hImportModelDialog == NULL) + { + m_hImportModelDialog = new vgui::FileOpenDialog( NULL, "#ToolCustomizeTextureTitle", true ); + m_hImportModelDialog->AddFilter( "*.mdl", "#IT_MDL_Files", true ); + m_hImportModelDialog->AddActionSignalTarget( this ); + } + + char szModelsDir[MAX_PATH]; + switch( m_iItemType ) + { + default: + break; + + case TI_TYPE_WEAPON: + m_hImportModelDialog->SetStartDirectory( g_pFullFileSystem->RelativePathToFullPath( "models/weapons/c_models", "MOD", szModelsDir, sizeof(szModelsDir) ) ); + break; + + case TI_TYPE_HEADGEAR: + case TI_TYPE_MISC1: + case TI_TYPE_MISC2: + { + const char *pszSubDir = NULL; + + // All classes? + if ( m_iClassUsage == 1 ) + { + pszSubDir = g_pszClassSubdirectories[0]; + } + else + { + // If we only have one class, jump into that directory + for ( int i = TF_FIRST_NORMAL_CLASS; i < LOADOUT_COUNT; i++ ) + { + if ( m_iClassUsage & (1 << i) ) + { + if ( !pszSubDir ) + { + pszSubDir = g_pszClassSubdirectories[i]; + } + else + { + // Found multiple classes. Move back up to the base dir. + pszSubDir = NULL; + break; + } + } + } + } + + if ( pszSubDir ) + { + m_hImportModelDialog->SetStartDirectory( g_pFullFileSystem->RelativePathToFullPath( VarArgs("models/player/items/%s",pszSubDir), "MOD", szModelsDir, sizeof(szModelsDir) ) ); + } + else + { + m_hImportModelDialog->SetStartDirectory( g_pFullFileSystem->RelativePathToFullPath( "models/player/items", "MOD", szModelsDir, sizeof(szModelsDir) ) ); + } + } + break; + } + + m_hImportModelDialog->DoModal( false ); + m_hImportModelDialog->Activate(); + + // Base file dialog won't refresh if it's opening to the same directory it was in. Force it to. + PostMessage( m_hImportModelDialog->GetVPanel(), new KeyValues( "PopulateFileList" ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +struct ComboBoxTestItem_t +{ + const wchar_t *pwszItemName; + item_definition_index_t itemDef; +}; +static int SortComboBoxTestItem( const ComboBoxTestItem_t *a, const ComboBoxTestItem_t *b ) +{ + return V_wcscmp( a->pwszItemName, b->pwszItemName ); +} + +void CTestItemDialog::SetupItemComboBox( vgui::ComboBox *pComboBox ) +{ + pComboBox->RemoveAll(); + + CUtlVector<item_definition_index_t> vecDefs; + int iReplacements = ((CTestItemRoot*)GetParent())->FindReplaceableItemsForSelectedClass( &vecDefs, m_iItemType == TI_TYPE_WEAPON ); + if ( iReplacements ) + { + KeyValues *pKeyValues = new KeyValues( "data" ); + pKeyValues->SetInt( "item", INVALID_ITEM_DEF_INDEX ); + pComboBox->AddItem( "#IT_ItemReplaced_Select", pKeyValues ); + + CUtlVector< ComboBoxTestItem_t > testItems; + FOR_EACH_VEC( vecDefs, i ) + { + CEconItemDefinition *pDef = ItemSystem()->GetStaticDataForItemByDefIndex( vecDefs[i] ); + if ( pDef ) + { + const wchar_t *pwszLocalizedItemName = g_pVGuiLocalize->Find( pDef->GetItemBaseName() ); + if ( pwszLocalizedItemName ) + { + int newIndex = testItems.AddToTail(); + testItems[newIndex].itemDef = vecDefs[i]; + testItems[newIndex].pwszItemName = pwszLocalizedItemName; + } + } + } + + if ( testItems.Count() ) + { + testItems.Sort( &SortComboBoxTestItem ); + FOR_EACH_VEC( testItems, i ) + { + pKeyValues = new KeyValues( "data" ); + pKeyValues->SetInt( "item", testItems[i].itemDef ); + pComboBox->AddItem( testItems[i].pwszItemName, pKeyValues ); + } + } + } + + // No valid entries? + if ( pComboBox == m_pItemReplacedComboBox ) + { + if ( m_pNoItemsToReplaceLabel ) + { + m_pNoItemsToReplaceLabel->SetVisible( !iReplacements ); + } + m_pItemReplacedPanel->SetVisible( iReplacements ); + } + + pComboBox->SetItemEnabled( 0, false ); + pComboBox->SilentActivateItemByRow( 0 ); + pComboBox->GetMenu()->SetBgColor( Color(0,0,0,255) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemDialog::SetupPaintColorComboBox( void ) +{ + m_pPaintColorComboBox->RemoveAll(); + + KeyValues *pKeyValues = new KeyValues( "data" ); + pKeyValues->SetInt( "paintcan_index", 0 ); + m_pPaintColorComboBox->AddItem( "#IT_PaintNone", pKeyValues ); + + // Now loop through all our paints and add them to the list + const CEconItemSchema::SortedItemDefinitionMap_t& mapItemDefs = ItemSystem()->GetItemSchema()->GetSortedItemDefinitionMap(); + FOR_EACH_MAP( mapItemDefs, i ) + { + const CEconItemDefinition *pDef = mapItemDefs[i]; + + const CEconTool_PaintCan *pEconToolPaintCan = pDef->GetTypedEconTool<CEconTool_PaintCan>(); + if ( !pEconToolPaintCan ) + continue; + + pKeyValues->SetInt( "paintcan_index", pDef->GetDefinitionIndex() ); + m_pPaintColorComboBox->AddItem( g_pVGuiLocalize->Find( pDef->GetItemBaseName() ), pKeyValues ); + + // Make sure it has valid colors (to skip the store version of the paint can) + KeyValues *pAttribs = pDef->GetDefinitionKey( "attributes" ); + if ( !pAttribs ) + continue; + + KeyValues *pRGBAttrib = pAttribs->FindKey( "set_item_tint_rgb" ); + if ( !pRGBAttrib ) + continue; + + int iModifiedRGB = pRGBAttrib->GetInt( "value", -1 ); + if ( iModifiedRGB != -1 ) + { + m_pPaintColorComboBox->AddItem( g_pVGuiLocalize->Find( pDef->GetItemBaseName() ), pKeyValues ); + } + } + + m_pPaintColorComboBox->SilentActivateItemByRow( 0 ); + m_pPaintColorComboBox->GetMenu()->SetBgColor( Color(0,0,0,255) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemDialog::SetupUnusualEffectComboBox( void ) +{ + m_pUnusualEffectComboBox->RemoveAll(); + + KeyValues *pKeyValues = new KeyValues( "data" ); + pKeyValues->SetInt( "unusual_index", 0 ); + m_pUnusualEffectComboBox->AddItem( "#IT_UnusualNone", pKeyValues ); + + // Now loop through all unusual effects and add them to the list. + const CEconItemSchema::ParticleDefinitionMap_t& mapParticleDefs = ItemSystem()->GetItemSchema()->GetAttributeControlledParticleSystems(); + FOR_EACH_MAP( mapParticleDefs, i ) + { + pKeyValues->SetInt( "unusual_index", mapParticleDefs[i].nSystemID ); + + char particleNameEntry[128]; + Q_snprintf( particleNameEntry, ARRAYSIZE( particleNameEntry ), "#Attrib_Particle%i", mapParticleDefs[i].nSystemID ); + m_pUnusualEffectComboBox->AddItem( g_pVGuiLocalize->Find( particleNameEntry ), pKeyValues ); + } + + m_pUnusualEffectComboBox->SilentActivateItemByRow( 0 ); + m_pUnusualEffectComboBox->GetMenu()->SetBgColor( Color(0,0,0,255) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemDialog::SetEntryStep( testitem_entrysteps_t iStep ) +{ + // Skip over the item replacement if we're not a weapon + if ( iStep == TI_STEP_WPN_ITEMREPLACED && m_iItemType != TI_TYPE_WEAPON ) + { + iStep = (testitem_entrysteps_t)(iStep+1); + } + + if ( iStep == TI_STEP_NONWPN_BODYGROUPS || iStep == TI_STEP_OTHER_OPTIONS ) + { + // Move to "finished" straight away + iStep = TI_STEP_FINISHED; + } + + m_iEntryStep = iStep; + + if ( m_pSelectModelButton ) + { + m_pSelectModelButton->SetVisible( iStep >= TI_STEP_MODELNAME ); + m_pSelectModelLabel->SetVisible( iStep >= TI_STEP_MODELNAME ); + m_pModelLabel->SetVisible( iStep >= TI_STEP_MODELNAME ); + } + + bool bTestingExistingItem = (iStep > TI_STEP_MODELNAME && m_szRelativePath[0] == '\0'); + + m_pBodygroupPanel->SetVisible( iStep >= TI_STEP_NONWPN_BODYGROUPS && m_iItemType != TI_TYPE_WEAPON && !bTestingExistingItem ); + m_pExistingItemToTestPanel->SetVisible( iStep == TI_STEP_MODELNAME || bTestingExistingItem ); + m_pItemReplacedPanel->SetVisible( iStep >= TI_STEP_WPN_ITEMREPLACED && m_iItemType == TI_TYPE_WEAPON && !bTestingExistingItem ); + if ( m_pNoItemsToReplaceLabel ) + { + m_pNoItemsToReplaceLabel->SetVisible( false ); + } + + m_pCustomizationsPanel->SetVisible( (iStep >= TI_STEP_CUSTOMIZATION && m_iItemType != TI_TYPE_WEAPON) ); + + if ( m_pOkButton ) + { + m_pOkButton->SetEnabled( m_iEntryStep >= TI_STEP_FINISHED ); + } + + switch ( m_iEntryStep ) + { + case TI_STEP_MODELNAME: + if ( !m_szRelativePath[0] ) + { + SetDialogVariable("testmodel", g_pVGuiLocalize->Find( "#IT_NoModel" ) ); + } + SetupItemComboBox( m_pExistingItemComboBox ); + break; + case TI_STEP_WPN_ITEMREPLACED: + SetupItemComboBox( m_pItemReplacedComboBox ); + break; + case TI_STEP_NONWPN_BODYGROUPS: + break; + + default: + case TI_STEP_FINISHED: + break; + } + + SetDialogVariable( "testtitle", g_pVGuiLocalize->Find( VarArgs("#IT_Title_%d",m_iItemType) ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemDialog::OnTextChanged( KeyValues *data ) +{ + Panel *pPanel = reinterpret_cast<vgui::Panel *>( data->GetPtr("panel") ); + if ( pPanel == m_pExistingItemComboBox ) + { + if ( m_iItemType != TI_TYPE_WEAPON ) + { + SetEntryStep( TI_STEP_OTHER_OPTIONS ); + } + else + { + SetEntryStep( TI_STEP_FINISHED ); + } + } + else if ( pPanel == m_pItemReplacedComboBox ) + { + SetEntryStep( TI_STEP_FINISHED ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemDialog::OnFileSelected(const char *fullpath) +{ + m_szRelativePath[0] = '\0'; + if ( g_pFullFileSystem->FullPathToRelativePathEx( fullpath, "GAME", m_szRelativePath, sizeof(m_szRelativePath) ) ) + { + Q_FixSlashes( m_szRelativePath, '/' ); + SetDialogVariable("testmodel", m_szRelativePath ); + SetEntryStep( TI_STEP_WPN_ITEMREPLACED ); + } + else + { + SetDialogVariable("testmodel", g_pVGuiLocalize->Find( "#IT_NoModel" ) ); + } + + // Nuke the file open dialog + m_hImportModelDialog->MarkForDeletion(); + m_hImportModelDialog = NULL; +} + diff --git a/game/client/tf/vgui/testitem_dialog.h b/game/client/tf/vgui/testitem_dialog.h new file mode 100644 index 0000000..ca7a518 --- /dev/null +++ b/game/client/tf/vgui/testitem_dialog.h @@ -0,0 +1,99 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TESTITEM_DIALOG_H +#define TESTITEM_DIALOG_H +#ifdef _WIN32 +#pragma once +#endif + +#include "vgui_controls/EditablePanel.h" +#include "vgui_controls/ScrollableEditablePanel.h" +#include "tf_controls.h" + +enum testitem_entrysteps_t +{ + TI_STEP_MODELNAME, + TI_STEP_WPN_ITEMREPLACED, + TI_STEP_NONWPN_BODYGROUPS, + TI_STEP_OTHER_OPTIONS, + TI_STEP_CUSTOMIZATION, + + TI_STEP_FINISHED, +}; + +enum testitem_bodygroups_to_hide_t +{ + TI_HIDEBG_HAT, + TI_HIDEBG_HEADPHONES, + TI_HIDEBG_MEDALS, + TI_HIDEBG_GRENADES, + TI_HIDEBG_BULLETS, + TI_HIDEBG_ARROWS, + TI_HIDEBG_RIGHTARM, + TI_HIDEBG_SHOES_SOCKS, + + TI_HIDEBG_COUNT, +}; + +//----------------------------------------------------------------------------- +// A dialog that handles adding or modifying an item we're testing +//----------------------------------------------------------------------------- +class CTestItemDialog : public vgui::EditablePanel, public CGameEventListener +{ + DECLARE_CLASS_SIMPLE( CTestItemDialog, vgui::EditablePanel ); +public: + CTestItemDialog( vgui::Panel *parent, testitem_itemtypes_t iItemType, int iClassUsage, KeyValues *pExistingKVs ); + ~CTestItemDialog( void ); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void PerformLayout( void ); + virtual void OnCommand( const char *command ); + virtual void FireGameEvent( IGameEvent *event ); + + void Close( void ); + void CloseAndUpdateItem( void ); + + MESSAGE_FUNC_PARAMS( OnTextChanged, "TextChanged", data ); + MESSAGE_FUNC_CHARPTR( OnFileSelected, "FileSelected", fullpath ); + +private: + void InitializeFromExistingKVs( KeyValues *pExistingKVs ); + void SetEntryStep( testitem_entrysteps_t iStep ); + void OpenSelectModelDialog( void ); + void SetupItemComboBox( vgui::ComboBox *pComboBox ); + void SetupPaintColorComboBox( void ); + void SetupUnusualEffectComboBox( void ); + void HandleClassCheckbuttonChecked( vgui::Panel *pPanel ); + +private: + testitem_entrysteps_t m_iEntryStep; + testitem_itemtypes_t m_iItemType; + int m_iClassUsage; + + vgui::FileOpenDialog *m_hImportModelDialog; + char m_szRelativePath[MAX_PATH]; + + CExLabel *m_pModelLabel; + CExLabel *m_pSelectModelLabel; + CExLabel *m_pNoItemsToReplaceLabel; + CExButton *m_pSelectModelButton; + CExButton *m_pOkButton; + vgui::ComboBox *m_pItemReplacedComboBox; + vgui::EditablePanel *m_pBodygroupPanel; + vgui::EditablePanel *m_pItemReplacedPanel; + vgui::CheckButton *m_pBodygroupCheckButtons[TI_HIDEBG_COUNT]; + + vgui::EditablePanel *m_pCustomizationsPanel; + vgui::ComboBox *m_pPaintColorComboBox; + vgui::ComboBox *m_pUnusualEffectComboBox; + + vgui::EditablePanel *m_pExistingItemToTestPanel; + vgui::ComboBox *m_pExistingItemComboBox; +}; + +#endif // TESTITEM_DIALOG_H diff --git a/game/client/tf/vgui/testitem_root.cpp b/game/client/tf/vgui/testitem_root.cpp new file mode 100644 index 0000000..5769db7 --- /dev/null +++ b/game/client/tf/vgui/testitem_root.cpp @@ -0,0 +1,931 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include <vgui/ILocalize.h> +#include "vgui_controls/TextEntry.h" +#include "vgui_controls/ComboBox.h" +#include "vgui_controls/CheckButton.h" +#include "testitem_root.h" +#include "tf_controls.h" +#include "c_playerresource.h" +#include "gcsdk/gcmsg.h" +#include "tf_gcmessages.h" +#include "econ_item_inventory.h" +#include "econ_gcmessages.h" +#include "ienginevgui.h" +#include "econ_item_system.h" +#include "vgui_controls/FileOpenDialog.h" +#include <filesystem.h> +#include "ai_activity.h" +#include "tf_gamerules.h" +#include "vgui_controls/Slider.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +ConVar tf_testitem_recent( "tf_testitem_recent", "", FCVAR_ARCHIVE ); + +KeyValues *g_pRootItemTestingKV = NULL; + +// Bot animations +const char *g_pszBotAnimStrings[TI_BOTANIM_COUNT] = +{ + "#IT_BotAnim_Idle", // TI_BOTANIM_IDLE, + "#IT_BotAnim_Crouch_Idle", // TI_BOTANIM_CROUCH, + "#IT_BotAnim_Run", // TI_BOTANIM_RUN, + "#IT_BotAnim_Crouch_Walk", // TI_BOTANIM_CROUCH_WALK + "#IT_BotAnim_Jump", // TI_BOTANIM_JUMP +}; + +void UpdateItemTestKVs( void ) +{ + KeyValues *pTmpCopy = g_pRootItemTestingKV->MakeCopy(); + engine->ServerCmdKeyValues( pTmpCopy ); + + // Setup any clientside variables to match what we're sending to the server + TFGameRules()->ItemTesting_SetupFromKV( g_pRootItemTestingKV ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTestItemRoot::CTestItemRoot( vgui::Panel *parent ) : vgui::EditablePanel( parent, "TestItemRoot" ) +{ + // Need to use the clientscheme (we're not parented to a clientscheme'd panel) + vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme"); + SetScheme(scheme); + SetProportional( true ); + + ListenForGameEvent( "gameui_hidden" ); + + m_hEditItemDialog = NULL; + m_iClassUsage = 0; + m_pClassUsagePanel = NULL; + m_pTestingPanel = NULL; + m_hImportExportDialog = NULL; + m_bExporting = false; + memset( m_pItemTestButtons, 0, sizeof(m_pItemTestButtons) ); + memset( m_pItemRemoveButtons, 0, sizeof(m_pItemRemoveButtons) ); + memset( m_pClassCheckButtons, NULL, sizeof(m_pClassCheckButtons) ); + memset( m_pItemTestKVs, 0, sizeof(m_pItemTestKVs) ); + + m_pBotAdditionPanel = new vgui::EditablePanel( this, "BotAdditionPanel" ); + m_pBotSelectionComboBox = new vgui::ComboBox( m_pBotAdditionPanel, "BotSelectionComboBox", 9, false ); + m_pBotSelectionComboBox->AddActionSignalTarget( this ); + m_pAutoAddBotsCheckBox = new vgui::CheckButton( m_pBotAdditionPanel, "AutoAddBotsCheckBox", "" ); + m_pAutoAddBotsCheckBox->AddActionSignalTarget( this ); + m_pAutoAddBotsCheckBox->SetSelected( true ); + m_pBotsOnBlueTeamCheckBox = new vgui::CheckButton( m_pBotAdditionPanel, "BotsOnBlueTeamCheckBox", "" ); + m_pBotsOnBlueTeamCheckBox->AddActionSignalTarget( this ); + m_pBotsOnBlueTeamCheckBox->SetSelected( true ); + m_pAddBotButton = NULL; + + m_pBotControlPanel = new CTestItemBotControls( this ); + m_pBotControlPanel->SetEmbedded( true ); + + SetupComboBoxes(); + + if ( !g_pRootItemTestingKV ) + { + g_pRootItemTestingKV = new KeyValues( "TestItems" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTestItemRoot::~CTestItemRoot( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemRoot::SetupComboBoxes( void ) +{ + // Setup our Bot Selection combo box + KeyValues *pKeyValues; + for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass <= TF_LAST_NORMAL_CLASS; iClass++ ) + { + if ( iClass == TF_CLASS_CIVILIAN ) + continue; + pKeyValues = new KeyValues( "data" ); + pKeyValues->SetInt( "class", iClass ); + m_pBotSelectionComboBox->AddItem( g_aPlayerClassNames[iClass], pKeyValues ); + } + m_pBotSelectionComboBox->SilentActivateItemByRow( 0 ); + + m_pBotControlPanel->SetupComboBoxes(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemRoot::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "resource/ui/TestItemRoot.res" ); + + m_pTestingPanel = dynamic_cast<vgui::EditablePanel*>( FindChildByName( "TestingPanel" ) ); + if ( m_pTestingPanel ) + { + for ( int i = 0; i < TI_TYPE_COUNT; i++ ) + { + m_pItemTestButtons[i] = dynamic_cast<CExButton*>( m_pTestingPanel->FindChildByName( VarArgs("TestItemButton%d",i) ) ); + m_pItemTestButtons[i]->AddActionSignalTarget( this ); + m_pItemRemoveButtons[i] = dynamic_cast<CExButton*>( m_pTestingPanel->FindChildByName( VarArgs("RemoveItemButton%d",i) ) ); + m_pItemRemoveButtons[i]->AddActionSignalTarget( this ); + m_pItemTestLabels[i] = dynamic_cast<CExLabel*>( m_pTestingPanel->FindChildByName( VarArgs("TestItemEntry%d",i) ) ); + } + } + + m_pClassUsagePanel = dynamic_cast<vgui::EditablePanel*>( FindChildByName( "ClassUsagePanel" ) ); + if ( m_pClassUsagePanel ) + { + for ( int i = 0; i < TF_LAST_NORMAL_CLASS; i++ ) + { + m_pClassCheckButtons[i] = dynamic_cast<vgui::CheckButton*>( m_pClassUsagePanel->FindChildByName( VarArgs("ClassCheckBox%d",i)) ); + m_pClassCheckButtons[i]->AddActionSignalTarget( this ); + } + } + + m_pAddBotButton = dynamic_cast<CExButton*>( m_pBotAdditionPanel->FindChildByName( "AddBotButton" ) ); + if ( m_pAddBotButton ) + { + m_pAddBotButton->AddActionSignalTarget( this ); + } + CExButton *pKickAllBotsButton = dynamic_cast<CExButton*>( m_pBotAdditionPanel->FindChildByName( "KickAllBotsButton" ) ); + if ( pKickAllBotsButton ) + { + pKickAllBotsButton->AddActionSignalTarget( this ); + } + + AddChildActionSignalTarget( this, "SteamWorkshopButtonSubButton", this, true ); + + UpdateTestItems(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemRoot::PerformLayout( void ) +{ + BaseClass::PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemRoot::FireGameEvent( IGameEvent *event ) +{ + const char *type = event->GetName(); + + if ( Q_strcmp(type, "gameui_hidden") == 0 ) + { + Close(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemRoot::Close( void ) +{ + TFModalStack()->PopModal( this ); + SetVisible( false ); + MarkForDeletion(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemRoot::OnSetTestItemKVs( KeyValues *pKV ) +{ + if ( !pKV ) + return; + + testitem_itemtypes_t iItemType = (testitem_itemtypes_t)pKV->GetInt("item_type"); + if ( iItemType <= TI_TYPE_UNKNOWN || iItemType > TI_TYPE_COUNT ) + return; + + // If we already have KVs for that slot, nuke them + if ( m_pItemTestKVs[iItemType] ) + { + g_pRootItemTestingKV->RemoveSubKey( m_pItemTestKVs[iItemType] ); + m_pItemTestKVs[iItemType]->deleteThis(); + } + + // Make our copy, and store it in the root KVs + m_pItemTestKVs[iItemType] = pKV->MakeCopy(); + m_pItemTestKVs[iItemType]->SetName( VarArgs("Item%d",iItemType) ); + g_pRootItemTestingKV->AddSubKey( m_pItemTestKVs[iItemType] ); + + UpdateTestItems(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemRoot::OnButtonChecked( KeyValues *pData ) +{ + Panel *pPanel = reinterpret_cast<vgui::Panel *>( pData->GetPtr("panel") ); + + if ( pPanel == m_pAutoAddBotsCheckBox ) + { + if ( m_pAutoAddBotsCheckBox->IsSelected() ) + { + m_pAddBotButton->SetEnabled( false ); + m_pBotSelectionComboBox->SetEnabled( false ); + } + else + { + m_pAddBotButton->SetEnabled( true ); + m_pBotSelectionComboBox->SetEnabled( true ); + } + + return; + } + + // If they hit all classes, disable everything else. + if ( pPanel == m_pClassCheckButtons[0] ) + { + bool bAllClass = m_pClassCheckButtons[0]->IsSelected(); + for ( int i = 1; i < TF_LAST_NORMAL_CLASS; i++ ) + { + m_pClassCheckButtons[i]->SetEnabled( !bAllClass ); + if ( bAllClass ) + { + m_pClassCheckButtons[i]->SetSelected( false ); + } + } + } + else + { + // If they've individually checked all boxes, switch to all-classes being checked + bool bAllChecked = true; + for ( int i = 1; i < TF_LAST_NORMAL_CLASS; i++ ) + { + if ( !m_pClassCheckButtons[i]->IsSelected() ) + { + bAllChecked = false; + break; + } + } + + if ( bAllChecked ) + { + m_pClassCheckButtons[0]->SetSelected( true ); + } + } + + m_iClassUsage = 0; + for ( int i = 0; i < TF_LAST_NORMAL_CLASS; i++ ) + { + if ( m_pClassCheckButtons[i]->IsSelected() ) + { + m_iClassUsage |= (1 << i); + } + } + + UpdateTestItems(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemRoot::CommitSettingsToKV( void ) +{ + g_pRootItemTestingKV->SetInt( "class_usage", m_iClassUsage ); + g_pRootItemTestingKV->SetInt( "auto_add_bots", m_pAutoAddBotsCheckBox->IsSelected() ); + g_pRootItemTestingKV->SetInt( "bots_on_blue_team", m_pBotsOnBlueTeamCheckBox->IsSelected() ); + + m_pBotControlPanel->CommitSettingsToKV(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemRoot::OnFileSelected(const char *fullpath) +{ + if ( m_bExporting ) + { + ExportTestSetup( fullpath ); + } + else + { + ImportTestSetup( fullpath ); + } + + // Nuke the file open dialog + m_hImportExportDialog->MarkForDeletion(); + m_hImportExportDialog = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemRoot::ExportTestSetup( const char *pFilename ) +{ + if ( !pFilename || !pFilename[0] ) + return; + + CommitSettingsToKV(); + + g_pRootItemTestingKV->SaveToFile( g_pFullFileSystem, pFilename ); + tf_testitem_recent.SetValue( pFilename ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemRoot::ImportTestSetup( KeyValues *pKV ) +{ + // Setup the class usage checkboxes + m_iClassUsage = pKV->GetInt( "class_usage", 0 ); + for ( int i = 0; i < TF_LAST_NORMAL_CLASS; i++ ) + { + m_pClassCheckButtons[i]->SetSelected( (m_iClassUsage & (1<<i)) ); + } + + // Pull out the item KV blocks + for ( int i = 0; i < TI_TYPE_COUNT; i++ ) + { + m_pItemTestKVs[i] = pKV->FindKey( VarArgs("Item%d",i) ); + } + + bool bAutoAdd = pKV->GetInt( "auto_add_bots", 1 ); + m_pAutoAddBotsCheckBox->SetSelected(bAutoAdd); + bool bBlueTeamBots = pKV->GetInt( "bots_on_blue_team", 0 ); + m_pBotsOnBlueTeamCheckBox->SetSelected(bBlueTeamBots); + + m_pBotControlPanel->ImportTestSetup( pKV ); + + UpdateTestItems(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemRoot::ImportTestSetup( const char *pFilename ) +{ + if ( !pFilename || !pFilename[0] ) + return; + + g_pRootItemTestingKV->deleteThis(); + g_pRootItemTestingKV = new KeyValues( "TestItems" ); + if ( g_pRootItemTestingKV->LoadFromFile( g_pFullFileSystem, pFilename ) ) + { + ImportTestSetup( g_pRootItemTestingKV ); + } + else + { + m_iClassUsage = 0; + memset( m_pItemTestKVs, 0, sizeof(m_pItemTestKVs) ); + + g_pRootItemTestingKV->deleteThis(); + g_pRootItemTestingKV = new KeyValues( "TestItems" ); + + UpdateTestItems(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CTestItemRoot::FindReplaceableItemsForSelectedClass( CUtlVector<item_definition_index_t> *pItemDefs, bool bWeapons ) +{ + // Build our list of checked classes + bool bClasses[TF_LAST_NORMAL_CLASS]; + for ( int i = 0; i < TF_LAST_NORMAL_CLASS; i++ ) + { + bClasses[i] = m_iClassUsage & (1 << i); + } + + int iReplaceableItems = 0; + + // Find all the weapons that can be used by the combination of classes we've checked + const CEconItemSchema::SortedItemDefinitionMap_t& mapItemDefs = ItemSystem()->GetItemSchema()->GetSortedItemDefinitionMap(); + FOR_EACH_MAP( mapItemDefs, i ) + { + const CTFItemDefinition *pDef = dynamic_cast<const CTFItemDefinition *>( mapItemDefs[i] ); + + // Never show: + // - Hidden items + // - Items that don't have fixed qualities + if ( !pDef || pDef->IsHidden() || pDef->GetQuality() == k_unItemQuality_Any ) + continue; + + // Only show in staging (internal dev branch): + // - Normal quality items + // - Items that haven't asked to be shown in the armory + static const bool bIsStaging = ( engine->GetAppID() == 810 ); + if ( !bIsStaging ) + { + if ( pDef->GetQuality() == AE_NORMAL || !pDef->ShouldShowInArmory() ) + continue; + } + + // Make sure it's the right type of item + int iDefSlot = pDef->GetDefaultLoadoutSlot(); + bool bValidSlot = false; + if ( bWeapons ) + { + bValidSlot = (iDefSlot == LOADOUT_POSITION_PRIMARY || iDefSlot == LOADOUT_POSITION_SECONDARY || iDefSlot == LOADOUT_POSITION_MELEE ); + if ( !bValidSlot ) + { + bValidSlot = pDef->CanBePlacedInSlot(LOADOUT_POSITION_PRIMARY) || pDef->CanBePlacedInSlot(LOADOUT_POSITION_SECONDARY) || pDef->CanBePlacedInSlot(LOADOUT_POSITION_MELEE); + } + } + else + { + bValidSlot = (iDefSlot == LOADOUT_POSITION_HEAD || iDefSlot == LOADOUT_POSITION_MISC ); + if ( !bValidSlot ) + { + bValidSlot = pDef->CanBePlacedInSlot(LOADOUT_POSITION_HEAD) || pDef->CanBePlacedInSlot(LOADOUT_POSITION_MISC); + } + } + if ( !bValidSlot ) + continue; + + // Make sure it's used by all the checked classes + bool bUsable = false; + if ( bClasses[0] ) + { + bUsable = pDef->CanBeUsedByAllClasses(); + } + else + { + bUsable = true; + for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ ) + { + if ( bClasses[iClass] && !pDef->CanBeUsedByClass(iClass) ) + { + bUsable = false; + break; + } + } + } + if ( !bUsable ) + continue; + + if ( pItemDefs ) + { + pItemDefs->AddToTail( pDef->GetDefinitionIndex() ); + } + iReplaceableItems++; + } + + return iReplaceableItems; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemRoot::UpdateTestItems( void ) +{ + for ( int i = 0; i < TI_TYPE_COUNT; i++ ) + { + // Weapon is handled specially, because it's tied to the class usage + if ( i == TI_TYPE_WEAPON ) + { + int iValidWeapons = FindReplaceableItemsForSelectedClass( NULL, true ); + m_pItemTestButtons[0]->SetEnabled( iValidWeapons ); + if ( !iValidWeapons ) + { + m_pItemTestLabels[0]->SetText( g_pVGuiLocalize->Find("#IT_ItemReplaced_Invalid") ); + continue; + } + } + + if ( m_pItemTestKVs[i] ) + { + m_pItemTestButtons[i]->SetText( "#IT_Item_Edit" ); + + item_definition_index_t iExistingDef = m_pItemTestKVs[i]->GetInt( "existing_itemdef", INVALID_ITEM_DEF_INDEX ); + if ( iExistingDef != INVALID_ITEM_DEF_INDEX ) + { + CEconItemDefinition *pDef = ItemSystem()->GetItemSchema()->GetItemDefinition(iExistingDef); + if ( pDef ) + { + m_pItemTestLabels[i]->SetText( g_pVGuiLocalize->Find( pDef->GetItemBaseName() ) ); + } + else + { + m_pItemTestLabels[i]->SetText( "#IT_TestingSlot_Empty" ); + } + } + else + { + const char *pszModel = m_pItemTestKVs[i]->GetString("model_player", "#IT_TestingSlot_Empty"); + + char szModel[MAX_PATH+1]=""; + Q_FileBase( pszModel, szModel, ARRAYSIZE( szModel ) ); + m_pItemTestLabels[i]->SetText( szModel ); + } + m_pItemRemoveButtons[i]->SetEnabled( true ); + } + else + { + m_pItemTestButtons[i]->SetText( "#IT_Item_Add" ); + m_pItemTestLabels[i]->SetText( "#IT_TestingSlot_Empty" ); + m_pItemRemoveButtons[i]->SetEnabled( false ); + } + } + + // Hide the testing panel if we don't have any classes selected + if ( m_pTestingPanel ) + { + m_pTestingPanel->SetVisible( m_iClassUsage != 0 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemRoot::CloseAndTestItem( void ) +{ + // Go through and update the schema definitions before we send them off to the server + for ( int i = 0; i < TI_TYPE_COUNT; i++ ) + { + if ( !m_pItemTestKVs[i] ) + continue; + + item_definition_index_t iNewDef = TESTITEM_DEFINITIONS_BEGIN_AT + i; + item_definition_index_t iItemDef = m_pItemTestKVs[i]->GetInt( "item_replace", INVALID_ITEM_DEF_INDEX ); + ItemSystem()->GetItemSchema()->ItemTesting_CreateTestDefinition( iItemDef, iNewDef, m_pItemTestKVs[i] ); + m_pItemTestKVs[i]->SetInt( "item_def", iNewDef ); + } + + // Not connected to a game? + if ( !TFGameRules() ) + return; + + CommitSettingsToKV(); + g_pRootItemTestingKV->SetName("TestItems"); + UpdateItemTestKVs(); + Close(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemRoot::OnCommand( const char *command ) +{ + if ( !Q_stricmp( command, "cancel" ) ) + { + Close(); + return; + } + else if ( !Q_stricmp( command, "ok" ) ) + { + CloseAndTestItem(); + return; + } + else if ( !Q_stricmp( command, "steamworkshop" ) ) + { + Close(); + engine->ClientCmd_Unrestricted( "OpenSteamWorkshopDialog;" ); + } + else if ( !Q_stricmp( command, "reloadscheme" ) ) + { + InvalidateLayout( false, true ); + return; + } + else if ( !Q_strnicmp( command, "item_test", 9 ) ) + { + int iItemType = atoi( command+9 ); + if ( iItemType >= 0 && iItemType < TI_TYPE_COUNT ) + { + if (!m_hEditItemDialog.Get()) + { + m_hEditItemDialog = vgui::SETUP_PANEL( new CTestItemDialog( this, (testitem_itemtypes_t)iItemType, m_iClassUsage, m_pItemTestKVs[iItemType] ) ); + } + m_hEditItemDialog->InvalidateLayout( false, true ); + m_hEditItemDialog->SetVisible( true ); + m_hEditItemDialog->MoveToFront(); + m_hEditItemDialog->SetKeyBoardInputEnabled(true); + m_hEditItemDialog->SetMouseInputEnabled(true); + TFModalStack()->PushModal( m_hEditItemDialog ); + } + return; + } + else if ( !Q_strnicmp( command, "item_remove", 11 ) ) + { + int iItemType = atoi( command+11 ); + if ( iItemType >= 0 && iItemType < TI_TYPE_COUNT ) + { + if ( m_pItemTestKVs[iItemType] ) + { + g_pRootItemTestingKV->RemoveSubKey( m_pItemTestKVs[iItemType] ); + m_pItemTestKVs[iItemType]->deleteThis(); + m_pItemTestKVs[iItemType] = NULL; + } + UpdateTestItems(); + } + return; + } + else if ( !Q_stricmp( command, "export" ) || !Q_stricmp( command, "import" ) ) + { + m_bExporting = ( command[0] == 'e' ); + + if (m_hImportExportDialog == NULL) + { + m_hImportExportDialog = new vgui::FileOpenDialog( NULL, "#ToolCustomizeTextureTitle", m_bExporting ? vgui::FOD_SAVE : vgui::FOD_OPEN, NULL ); + m_hImportExportDialog->AddFilter( "*.itf", "#IT_TestingFiles", true ); + m_hImportExportDialog->AddActionSignalTarget( this ); + + char szModelsDir[MAX_PATH]; + m_hImportExportDialog->SetStartDirectory( g_pFullFileSystem->RelativePathToFullPath( "cfg", "MOD", szModelsDir, sizeof(szModelsDir) ) ); + } + m_hImportExportDialog->DoModal( false ); + m_hImportExportDialog->Activate(); + return; + } + else if ( !Q_stricmp( command, "importrecent" ) ) + { + ImportTestSetup( tf_testitem_recent.GetString() ); + return; + } + else if ( !Q_stricmp( command, "bot_add" ) ) + { + KeyValues *pKV = m_pBotSelectionComboBox->GetActiveItemUserData(); + int iClass = pKV->GetInt( "class", TF_CLASS_UNDEFINED ); + if ( iClass >= TF_FIRST_NORMAL_CLASS && iClass < TF_LAST_NORMAL_CLASS ) + { + bool bBlueTeam = m_pBotsOnBlueTeamCheckBox->IsSelected(); + engine->ClientCmd_Unrestricted( VarArgs( "bot -team %s -class %s\n", bBlueTeam ? "blue" : "red", g_aPlayerClassNames_NonLocalized[iClass] ) ); + } + return; + } + else if ( !Q_stricmp( command, "bot_removeall" ) ) + { + // Kick everyone above the first player + for ( int i = 2; i <= gpGlobals->maxClients; i++ ) + { + C_BasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + { + engine->ClientCmd_Unrestricted( VarArgs( "kickid %d\n", pPlayer->GetUserID() ) ); + } + } + return; + } + + + BaseClass::OnCommand( command ); +} + + +static vgui::DHANDLE<CTestItemRoot> g_hTestItemRoot; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void OpenTestItemRoot( void ) +{ + if (!g_hTestItemRoot.Get()) + { + g_hTestItemRoot = vgui::SETUP_PANEL( new CTestItemRoot( NULL ) ); + } + + g_hTestItemRoot->SetVisible( true ); + g_hTestItemRoot->MakePopup(); + g_hTestItemRoot->MoveToFront(); + g_hTestItemRoot->SetKeyBoardInputEnabled(true); + g_hTestItemRoot->SetMouseInputEnabled(true); + TFModalStack()->PushModal( g_hTestItemRoot ); + + g_hTestItemRoot->MakeReadyForUse(); + if ( g_pRootItemTestingKV ) + { + g_hTestItemRoot->ImportTestSetup( g_pRootItemTestingKV ); + } +} +ConCommand testitem( "itemtest", OpenTestItemRoot, "Open the item testing panel.", FCVAR_NONE ); + + + +//======================================================================================================================================== +// BOT CONTROLS PANEL +//======================================================================================================================================== +static vgui::DHANDLE<CTestItemBotControls> g_hTestItemBotControls; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTestItemBotControls::CTestItemBotControls( vgui::Panel *parent ) : vgui::EditablePanel( parent, "TestItemBotControls" ) +{ + // Need to use the clientscheme (we're not parented to a clientscheme'd panel) + vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme"); + SetScheme(scheme); + SetProportional( true ); + + ListenForGameEvent( "gameui_hidden" ); + + m_pBotAnimationComboBox = new vgui::ComboBox( this, "BotAnimationComboBox", 9, false ); + m_pBotAnimationComboBox->AddActionSignalTarget( this ); + m_pBotForceFireCheckBox = new vgui::CheckButton( this, "BotForceFireCheckBox", "" ); + m_pBotForceFireCheckBox->AddActionSignalTarget( this ); + m_pBotTurntableCheckBox = new vgui::CheckButton( this, "BotTurntableCheckBox", "" ); + m_pBotTurntableCheckBox->AddActionSignalTarget( this ); + m_pBotViewScanCheckBox = new vgui::CheckButton( this, "BotViewScanCheckBox", "" ); + m_pBotViewScanCheckBox->AddActionSignalTarget( this ); + m_pBotAnimationSpeedSlider = new vgui::Slider( this, "BotAnimationSpeedSlider" ); + m_pBotAnimationSpeedSlider->SetRange( 0, 100 ); + m_pBotAnimationSpeedSlider->SetNumTicks( 10 ); + m_pBotAnimationSpeedSlider->AddActionSignalTarget( this ); + + m_bEmbedded = false; + + SetupComboBoxes(); + + if ( !g_pRootItemTestingKV ) + { + g_pRootItemTestingKV = new KeyValues( "TestItems" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTestItemBotControls::~CTestItemBotControls( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemBotControls::SetupComboBoxes( void ) +{ + KeyValues *pKeyValues; + // Setup our bot animation combo box + for ( int i = 0; i < TI_BOTANIM_COUNT; i++ ) + { + pKeyValues = new KeyValues( "data" ); + pKeyValues->SetInt( "anim", i ); + m_pBotAnimationComboBox->AddItem( g_pszBotAnimStrings[i], pKeyValues ); + } + m_pBotAnimationComboBox->SilentActivateItemByRow( 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemBotControls::FireGameEvent( IGameEvent *event ) +{ + const char *type = event->GetName(); + + if ( Q_strcmp(type, "gameui_hidden") == 0 ) + { + Close(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemBotControls::Close( void ) +{ + TFModalStack()->PopModal( this ); + SetVisible( false ); + MarkForDeletion(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemBotControls::ImportTestSetup( KeyValues *pKV ) +{ + bool bForceFire = pKV->GetInt( "bot_force_fire", 0 ); + m_pBotForceFireCheckBox->SetSelected(bForceFire); + bool bViewScan = pKV->GetInt( "bot_view_scan", 0 ); + m_pBotViewScanCheckBox->SetSelected(bViewScan); + bool bTurnTable = pKV->GetInt( "bot_turntable", 0 ); + m_pBotTurntableCheckBox->SetSelected(bTurnTable); + + int iAnim = g_pRootItemTestingKV->GetInt( "bot_anim", TI_BOTANIM_IDLE ); + m_pBotAnimationComboBox->SilentActivateItemByRow( iAnim ); + + int iAnimSpeed = g_pRootItemTestingKV->GetInt( "bot_animspeed", 100 ); + m_pBotAnimationSpeedSlider->SetValue( iAnimSpeed, false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemBotControls::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "resource/ui/TestItemBotControls.res" ); + + // Dumb, but the slider needs to have its scheme forcibly loaded to make it create the left/right text + m_pBotAnimationSpeedSlider->InvalidateLayout( true, true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemBotControls::PerformLayout( void ) +{ + BaseClass::PerformLayout(); + + CExButton *pButton = dynamic_cast<CExButton*>( FindChildByName( "OkButton" ) ); + if ( pButton ) + { + pButton->SetVisible( !m_bEmbedded ); + } + pButton = dynamic_cast<CExButton*>( FindChildByName( "CloseButton" ) ); + if ( pButton ) + { + pButton->SetVisible( !m_bEmbedded ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemBotControls::OnCommand( const char *command ) +{ + if ( !Q_stricmp( command, "cancel" ) ) + { + Close(); + return; + } + else if ( !Q_stricmp( command, "ok" ) ) + { + UpdateBots(); + return; + } + else if ( !Q_stricmp( command, "reloadscheme" ) ) + { + InvalidateLayout( false, true ); + return; + } + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemBotControls::UpdateBots( void ) +{ + // Not connected to a game? + if ( !TFGameRules() ) + return; + + CommitSettingsToKV(); + + g_pRootItemTestingKV->SetName("TestItemsBotUpdate"); + UpdateItemTestKVs(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTestItemBotControls::CommitSettingsToKV( void ) +{ + g_pRootItemTestingKV->SetInt( "bot_force_fire", m_pBotForceFireCheckBox->IsSelected() ); + g_pRootItemTestingKV->SetInt( "bot_view_scan", m_pBotViewScanCheckBox->IsSelected() ); + g_pRootItemTestingKV->SetInt( "bot_turntable", m_pBotTurntableCheckBox->IsSelected() ); + + KeyValues *pKV = m_pBotAnimationComboBox->GetActiveItemUserData(); + int iAnim = pKV->GetInt( "anim", TI_BOTANIM_IDLE ); + g_pRootItemTestingKV->SetInt( "bot_anim", iAnim ); + + int iAnimSpeed = clamp( m_pBotAnimationSpeedSlider->GetValue(), 0, 100 ); + g_pRootItemTestingKV->SetInt( "bot_animspeed", iAnimSpeed ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void OpenTestItemBotControls( void ) +{ + if (!g_hTestItemBotControls.Get()) + { + g_hTestItemBotControls = vgui::SETUP_PANEL( new CTestItemBotControls( NULL ) ); + } + + g_hTestItemBotControls->SetVisible( true ); + g_hTestItemBotControls->MakePopup(); + g_hTestItemBotControls->MoveToFront(); + g_hTestItemBotControls->SetKeyBoardInputEnabled(true); + g_hTestItemBotControls->SetMouseInputEnabled(true); + TFModalStack()->PushModal( g_hTestItemBotControls ); + + g_hTestItemBotControls->MakeReadyForUse(); + if ( g_pRootItemTestingKV ) + { + g_hTestItemBotControls->ImportTestSetup( g_pRootItemTestingKV ); + g_hTestItemBotControls->SetEmbedded( false ); + } +} +ConCommand testitem_botcontrols( "itemtest_botcontrols", OpenTestItemBotControls, "Open the item testing bot control panel.", FCVAR_NONE ); diff --git a/game/client/tf/vgui/testitem_root.h b/game/client/tf/vgui/testitem_root.h new file mode 100644 index 0000000..1fed8b3 --- /dev/null +++ b/game/client/tf/vgui/testitem_root.h @@ -0,0 +1,109 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TESTITEM_ROOT_H +#define TESTITEM_ROOT_H +#ifdef _WIN32 +#pragma once +#endif + +#include "vgui_controls/EditablePanel.h" +#include "vgui_controls/ScrollableEditablePanel.h" +#include "tf_controls.h" +#include "testitem_dialog.h" + + +//----------------------------------------------------------------------------- +// A panel that handles the overall item testing process +//----------------------------------------------------------------------------- +class CTestItemBotControls : public vgui::EditablePanel, public CGameEventListener +{ + DECLARE_CLASS_SIMPLE( CTestItemBotControls, vgui::EditablePanel ); +public: + CTestItemBotControls( vgui::Panel *parent ); + ~CTestItemBotControls( void ); + + void SetupComboBoxes( void ); + virtual void FireGameEvent( IGameEvent *event ); + void ImportTestSetup( KeyValues *pKV ); + void Close( void ); + void SetEmbedded( bool bEmbedded ) { m_bEmbedded = bEmbedded; InvalidateLayout(); } + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void PerformLayout( void ); + virtual void OnCommand( const char *command ); + void UpdateBots( void ); + void CommitSettingsToKV( void ); + +private: + vgui::ComboBox *m_pBotAnimationComboBox; + vgui::Slider *m_pBotAnimationSpeedSlider; + vgui::CheckButton *m_pBotForceFireCheckBox; + vgui::CheckButton *m_pBotTurntableCheckBox; + vgui::CheckButton *m_pBotViewScanCheckBox; + bool m_bEmbedded; +}; + + +//----------------------------------------------------------------------------- +// A panel that handles the overall item testing process +//----------------------------------------------------------------------------- +class CTestItemRoot : public vgui::EditablePanel, public CGameEventListener +{ + DECLARE_CLASS_SIMPLE( CTestItemRoot, vgui::EditablePanel ); +public: + CTestItemRoot( vgui::Panel *parent ); + ~CTestItemRoot( void ); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void PerformLayout( void ); + virtual void OnCommand( const char *command ); + virtual void FireGameEvent( IGameEvent *event ); + + void Close( void ); + void CloseAndTestItem( void ); + void UpdateTestItems( void ); + int FindReplaceableItemsForSelectedClass( CUtlVector<item_definition_index_t> *pItemDefs = NULL, bool bWeapons = false ); + void ExportTestSetup( const char *pFilename ); + void ImportTestSetup( const char *pFilename ); + void ImportTestSetup( KeyValues *pKV ); + void CommitSettingsToKV( void ); + + MESSAGE_FUNC_PARAMS( OnSetTestItemKVs, "SetTestItemKVs", pKV ); + MESSAGE_FUNC_PARAMS( OnButtonChecked, "CheckButtonChecked", pData ); + MESSAGE_FUNC_CHARPTR( OnFileSelected, "FileSelected", fullpath ); + +private: + void SetupComboBoxes( void ); + +private: + int m_iClassUsage; + + vgui::EditablePanel *m_pClassUsagePanel; + vgui::EditablePanel *m_pTestingPanel; + vgui::EditablePanel *m_pBotAdditionPanel; + CTestItemBotControls *m_pBotControlPanel; + + // Testing panel + CExButton *m_pItemTestButtons[TI_TYPE_COUNT]; + CExButton *m_pItemRemoveButtons[TI_TYPE_COUNT]; + CExLabel *m_pItemTestLabels[TI_TYPE_COUNT]; + vgui::CheckButton *m_pClassCheckButtons[TF_LAST_NORMAL_CLASS]; + KeyValues *m_pItemTestKVs[TI_TYPE_COUNT]; + + // Bot addition panel + vgui::ComboBox *m_pBotSelectionComboBox; + vgui::CheckButton *m_pAutoAddBotsCheckBox; + vgui::CheckButton *m_pBotsOnBlueTeamCheckBox; + CExButton *m_pAddBotButton; + + vgui::DHANDLE<CTestItemDialog> m_hEditItemDialog; + vgui::FileOpenDialog *m_hImportExportDialog; + bool m_bExporting; +}; + +#endif // TESTITEM_ROOT_H diff --git a/game/client/tf/vgui/tf_arenateammenu.cpp b/game/client/tf/vgui/tf_arenateammenu.cpp new file mode 100644 index 0000000..c5ff4c3 --- /dev/null +++ b/game/client/tf/vgui/tf_arenateammenu.cpp @@ -0,0 +1,426 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "cbase.h" + +#include <vgui_controls/Label.h> +#include <vgui_controls/Button.h> +#include <vgui_controls/ImagePanel.h> +#include <vgui_controls/RichText.h> +#include <vgui_controls/Frame.h> +#include <vgui/IScheme.h> +#include <game/client/iviewport.h> +#include <vgui/IVGui.h> +#include <KeyValues.h> +#include <filesystem.h> + +#include "vguicenterprint.h" +#include "tf_controls.h" +#include "basemodelpanel.h" +#include "tf_arenateammenu.h" +#include <convar.h> +#include "IGameUIFuncs.h" // for key bindings +#include "hud.h" // for gEngfuncs +#include "c_tf_player.h" +#include "tf_gamerules.h" +#include "c_team.h" +#include "tf_hud_notification_panel.h" +#include "inputsystem/iinputsystem.h" + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CTFArenaTeamMenu::CTFArenaTeamMenu( IViewPort *pViewPort ) : CTeamMenu( pViewPort ) +{ + SetMinimizeButtonVisible( false ); + SetMaximizeButtonVisible( false ); + SetCloseButtonVisible( false ); + SetVisible( false ); + SetKeyBoardInputEnabled( true ); + + m_iTeamMenuKey = BUTTON_CODE_INVALID; + + m_pAutoTeamButton = new CTFTeamButton( this, "teambutton2" ); + m_pSpecTeamButton = new CTFTeamButton( this, "teambutton3" ); + m_pSpecLabel = new CExLabel( this, "TeamMenuSpectate", "" ); + +#ifdef _X360 + m_pFooter = new CTFFooter( this, "Footer" ); +#else + m_pCancelButton = new CExButton( this, "CancelButton", "#TF_Cancel" ); + m_pJoinAutoHintIcon = m_pJoinSpectatorsHintIcon = m_pCancelHintIcon = nullptr; +#endif + + vgui::ivgui()->AddTickSignal( GetVPanel() ); + + m_bRedDisabled = false; + m_bBlueDisabled = false; + + if ( ::input->IsSteamControllerActive() ) + { + LoadControlSettings( "Resource/UI/HudArenaTeamMenu_SC.res" ); + } + else + { + LoadControlSettings( "Resource/UI/HudArenaTeamMenu.res" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CTFArenaTeamMenu::~CTFArenaTeamMenu() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +void CTFArenaTeamMenu::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + if ( ::input->IsSteamControllerActive() ) + { + LoadControlSettings( "Resource/UI/HudArenaTeamMenu_SC.res" ); + + m_pCancelHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "CancelHintIcon" ) ); + m_pJoinAutoHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "JoinAutoHintIcon" ) ); + m_pJoinSpectatorsHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "JoinSpectatorsHintIcon" ) ); + + SetMouseInputEnabled( false ); + } + else + { + LoadControlSettings( "Resource/UI/HudArenaTeamMenu.res" ); + SetMouseInputEnabled( true ); + + m_pCancelHintIcon = m_pJoinAutoHintIcon = m_pJoinSpectatorsHintIcon = nullptr; + } + + Update(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFArenaTeamMenu::ShowPanel( bool bShow ) +{ + if ( BaseClass::IsVisible() == bShow ) + return; + + if ( !gameuifuncs || !gViewPortInterface || !engine ) + return; + + if ( bShow ) + { + if ( !C_TFPlayer::GetLocalTFPlayer() ) + return; + + if ( TFGameRules()->State_Get() == GR_STATE_TEAM_WIN && + C_TFPlayer::GetLocalTFPlayer() && + C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TFGameRules()->GetWinningTeam() + && C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TEAM_SPECTATOR + && C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TEAM_UNASSIGNED ) + { + SetVisible( false ); + + CHudNotificationPanel *pNotifyPanel = GET_HUDELEMENT( CHudNotificationPanel ); + if ( pNotifyPanel ) + { + pNotifyPanel->SetupNotifyCustom( "#TF_CantChangeTeamNow", "ico_notify_flag_moving", C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() ); + } + + return; + } + + gViewPortInterface->ShowPanel( PANEL_CLASS_RED, false ); + gViewPortInterface->ShowPanel( PANEL_CLASS_BLUE, false ); + + engine->CheckPoint( "TeamMenu" ); + + InvalidateLayout( true, true ); + + Activate(); + + // get key bindings if shown + m_iTeamMenuKey = gameuifuncs->GetButtonCodeForBind( "changeteam" ); + m_iScoreBoardKey = gameuifuncs->GetButtonCodeForBind( "showscores" ); + + GetFocusNavGroup().SetCurrentFocus( m_pAutoTeamButton->GetVPanel(), m_pAutoTeamButton->GetVPanel() ); + ActivateSelectIconHint( GetFocusNavGroup().GetCurrentFocus() ? GetFocusNavGroup().GetCurrentFocus()->GetTabPosition() : -1 ); + } + else + { + SetVisible( false ); + + if ( IsConsole() ) + { + // Close the door behind us + CTFArenaTeamMenu *pButton = dynamic_cast< CTFArenaTeamMenu *> ( GetFocusNavGroup().GetCurrentFocus() ); + if ( pButton ) + { + pButton->OnCursorExited(); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Activate the right selection hint icon, depending on the focus group number selected +//----------------------------------------------------------------------------- +void CTFArenaTeamMenu::ActivateSelectIconHint( int focus_group_number ) +{ + if ( m_pJoinAutoHintIcon ) m_pJoinAutoHintIcon->SetVisible( false ); + if ( m_pJoinSpectatorsHintIcon ) m_pJoinSpectatorsHintIcon->SetVisible( false ); + + CSCHintIcon* icon = nullptr; + switch ( focus_group_number ) + { + case 1: icon = m_pJoinAutoHintIcon; break; + case 2: icon = m_pJoinSpectatorsHintIcon; break; + } + + if ( icon ) + { + icon->SetVisible( true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: called to update the menu with new information +//----------------------------------------------------------------------------- +void CTFArenaTeamMenu::Update( void ) +{ + BaseClass::Update(); + + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + + if ( pLocalPlayer && ( pLocalPlayer->GetTeamNumber() != TEAM_UNASSIGNED ) ) + { +#ifdef _X360 + if ( m_pFooter ) + { + m_pFooter->ShowButtonLabel( "cancel", true ); + } +#else + if ( m_pCancelButton ) + { + m_pCancelButton->SetVisible( true ); + if ( m_pCancelHintIcon ) + { + m_pCancelHintIcon->SetVisible( true ); + } + } +#endif + } + else + { +#ifdef _X360 + if ( m_pFooter ) + { + m_pFooter->ShowButtonLabel( "cancel", false ); + } +#else + if ( m_pCancelButton && m_pCancelButton->IsVisible() ) + { + m_pCancelButton->SetVisible( false ); + if ( m_pCancelHintIcon ) + { + m_pCancelHintIcon->SetVisible( false ); + } + } +#endif + } +} + +#ifdef _X360 +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFArenaTeamMenu::Join_Team( const CCommand &args ) +{ + if ( args.ArgC() > 1 ) + { + char cmd[256]; + Q_snprintf( cmd, sizeof( cmd ), "jointeam_nomenus %s", args.Arg( 1 ) ); + OnCommand( cmd ); + } +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: chooses and loads the text page to display that describes mapName map +//----------------------------------------------------------------------------- +void CTFArenaTeamMenu::LoadMapPage( const char *mapName ) +{ + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFArenaTeamMenu::OnKeyCodePressed( KeyCode code ) +{ + if ( ( m_iTeamMenuKey != BUTTON_CODE_INVALID && m_iTeamMenuKey == code ) || + code == KEY_XBUTTON_BACK || + code == KEY_XBUTTON_B || + code == STEAMCONTROLLER_B ) + { + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + + if ( pLocalPlayer && ( pLocalPlayer->GetTeamNumber() != TEAM_UNASSIGNED ) ) + { + ShowPanel( false ); + } + } + else if( code == KEY_SPACE || code == STEAMCONTROLLER_Y ) + { + engine->ClientCmd( "jointeam auto" ); + + ShowPanel( false ); + OnClose(); + } + else if( code == KEY_XBUTTON_A || code == KEY_XBUTTON_RTRIGGER || code == STEAMCONTROLLER_A ) + { + // select the active focus + if ( GetFocusNavGroup().GetCurrentFocus() ) + { + ipanel()->SendMessage( GetFocusNavGroup().GetCurrentFocus()->GetVPanel(), new KeyValues( "PressButton" ), GetVPanel() ); + } + } + else if( code == KEY_XBUTTON_RIGHT || code == KEY_XSTICK1_RIGHT || code == STEAMCONTROLLER_DPAD_RIGHT ) + { + CTFTeamButton *pButton; + + pButton = dynamic_cast< CTFTeamButton *> ( GetFocusNavGroup().GetCurrentFocus() ); + if ( pButton ) + { + pButton->OnCursorExited(); + GetFocusNavGroup().RequestFocusNext( pButton->GetVPanel() ); + } + else + { + GetFocusNavGroup().RequestFocusNext( NULL ); + } + + pButton = dynamic_cast< CTFTeamButton * > ( GetFocusNavGroup().GetCurrentFocus() ); + if ( pButton ) + { + pButton->OnCursorEntered(); + } + + ActivateSelectIconHint( GetFocusNavGroup().GetCurrentFocus() ? GetFocusNavGroup().GetCurrentFocus()->GetTabPosition() : -1 ); + } + else if( code == KEY_XBUTTON_LEFT || code == KEY_XSTICK1_LEFT || code == STEAMCONTROLLER_DPAD_LEFT ) + { + CTFTeamButton *pButton; + + pButton = dynamic_cast< CTFTeamButton *> ( GetFocusNavGroup().GetCurrentFocus() ); + if ( pButton ) + { + pButton->OnCursorExited(); + GetFocusNavGroup().RequestFocusPrev( pButton->GetVPanel() ); + } + else + { + GetFocusNavGroup().RequestFocusPrev( NULL ); + } + + pButton = dynamic_cast< CTFTeamButton * > ( GetFocusNavGroup().GetCurrentFocus() ); + if ( pButton ) + { + pButton->OnCursorEntered(); + } + + ActivateSelectIconHint( GetFocusNavGroup().GetCurrentFocus() ? GetFocusNavGroup().GetCurrentFocus()->GetTabPosition() : -1 ); + } + else if ( m_iScoreBoardKey != BUTTON_CODE_INVALID && m_iScoreBoardKey == code ) + { + gViewPortInterface->ShowPanel( PANEL_SCOREBOARD, true ); + gViewPortInterface->PostMessageToPanel( PANEL_SCOREBOARD, new KeyValues( "PollHideCode", "code", code ) ); + } + else + { + BaseClass::OnKeyCodePressed( code ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called when the user picks a team +//----------------------------------------------------------------------------- +void CTFArenaTeamMenu::OnCommand( const char *command ) +{ + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + + if ( Q_stricmp( command, "vguicancel" ) ) + { + // we're selecting a team, so make sure it's not the team we're already on before sending to the server + if ( pLocalPlayer && ( Q_strstr( command, "jointeam " ) ) ) + { + engine->ClientCmd( command ); + } + else if ( pLocalPlayer && ( Q_strstr( command, "jointeam_nomenus " ) ) ) + { + engine->ClientCmd( command ); + } + } + + BaseClass::OnCommand( command ); + ShowPanel( false ); + OnClose(); +} + +//----------------------------------------------------------------------------- +// Frame-based update +//----------------------------------------------------------------------------- +void CTFArenaTeamMenu::OnTick() +{ + // update the number of players on each team + + // enable or disable buttons based on team limit + + C_Team *pRed = GetGlobalTeam( TF_TEAM_RED ); + C_Team *pBlue = GetGlobalTeam( TF_TEAM_BLUE ); + + if ( !pRed || !pBlue ) + return; + + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + + if ( !pLocalPlayer ) + return; + + CTFGameRules *pRules = TFGameRules(); + + if ( !pRules ) + return; + + if ( m_pSpecTeamButton && m_pSpecLabel ) + { + { + if ( mp_allowspectators.GetBool() ) + { + if ( !m_pSpecTeamButton->IsVisible() ) + { + m_pSpecTeamButton->SetVisible( true ); + m_pSpecLabel->SetVisible( true ); + } + } + else + { + if ( m_pSpecTeamButton->IsVisible() ) + { + m_pSpecTeamButton->SetVisible( false ); + m_pSpecLabel->SetVisible( false ); + } + } + } + } +}
\ No newline at end of file diff --git a/game/client/tf/vgui/tf_arenateammenu.h b/game/client/tf/vgui/tf_arenateammenu.h new file mode 100644 index 0000000..daab38e --- /dev/null +++ b/game/client/tf/vgui/tf_arenateammenu.h @@ -0,0 +1,79 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef TF_ARENATEAMMENU_H +#define TF_ARENATEAMMENU_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_controls.h" +#include <teammenu.h> +#include "tf_teammenu.h" + + +//----------------------------------------------------------------------------- +// Purpose: Displays the team menu +//----------------------------------------------------------------------------- +class CTFArenaTeamMenu : public CTeamMenu +{ +private: + DECLARE_CLASS_SIMPLE( CTFArenaTeamMenu, CTeamMenu ); + +public: + CTFArenaTeamMenu( IViewPort *pViewPort ); + ~CTFArenaTeamMenu(); + + void Update(); + void ShowPanel( bool bShow ); + +#ifdef _X360 + CON_COMMAND_MEMBER_F( CTFTeamMenu, "join_team", Join_Team, "Send a jointeam command", 0 ); +#endif + +protected: + virtual void ApplySchemeSettings(vgui::IScheme *pScheme); + virtual void OnKeyCodePressed( vgui::KeyCode code ); + + // command callbacks + virtual void OnCommand( const char *command ); + + virtual void LoadMapPage( const char *mapName ); + + virtual void OnTick( void ); + + virtual const char *GetName( void ) { return PANEL_ARENA_TEAM; } + +private: + + CTFTeamButton *m_pAutoTeamButton; + CTFTeamButton *m_pSpecTeamButton; + CExLabel *m_pSpecLabel; + +#ifdef _X360 + CTFFooter *m_pFooter; +#else + CExButton *m_pCancelButton; +#endif + + CSCHintIcon *m_pCancelHintIcon; + CSCHintIcon *m_pJoinAutoHintIcon; + CSCHintIcon *m_pJoinSpectatorsHintIcon; + + bool m_bRedDisabled; + bool m_bBlueDisabled; + + +private: + enum { NUM_TEAMS = 3 }; + + ButtonCode_t m_iTeamMenuKey; + + void ActivateSelectIconHint( int focus_group_number ); +}; + +#endif // TF_ARENATEAMMENU_H diff --git a/game/client/tf/vgui/tf_asyncpanel.cpp b/game/client/tf/vgui/tf_asyncpanel.cpp new file mode 100644 index 0000000..dd63119 --- /dev/null +++ b/game/client/tf/vgui/tf_asyncpanel.cpp @@ -0,0 +1,121 @@ +#include "cbase.h" +#include "tf_asyncpanel.h" +#include "econ_controls.h" + +static const float k_flRequestInterval = 5.f; +static const float k_flNeverRequestUpdate = -1.f; + + +CBaseASyncPanel::CBaseASyncPanel( Panel *pParent, const char *pszPanelName ) + : EditablePanel( pParent, pszPanelName ) + , m_flLastRequestTime( 0.f ) + , m_flLastUpdatedTime( 0.f ) + , m_bDataInitialized( false ) + , m_bSettingsApplied( false ) +{ + ivgui()->AddTickSignal( GetVPanel(), 1.f ); +} + +void CBaseASyncPanel::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + m_bSettingsApplied = true; + m_flLastUpdatedTime = 0.f; // Force a refresh +} + + +void CBaseASyncPanel::LoadControlSettings(const char *dialogResourceName, const char *pathID, KeyValues *pPreloadedKeyValues, KeyValues *pConditions ) +{ + m_vecLoadingPanels.Purge(); + m_vecPanelsToShow.Purge(); + + BaseClass::LoadControlSettings( dialogResourceName, pathID, pPreloadedKeyValues, pConditions ); +} + +void CBaseASyncPanel::PerformLayout() +{ + BaseClass::PerformLayout(); + + FOR_EACH_VEC( m_vecLoadingPanels, i ) + { + m_vecLoadingPanels[ i ]->SetVisible( true ); + } + + FOR_EACH_VEC( m_vecPanelsToShow, i ) + { + m_vecPanelsToShow[ i ]->SetVisible( false ); + } +} + + +void CBaseASyncPanel::OnChildSettingsApplied( KeyValues *pInResourceData, Panel *pChild ) +{ + const char *pszAsync = pInResourceData->GetString( "asynchandling", NULL ); + + if ( pszAsync == NULL ) + return; + + if ( FStrEq( pszAsync, "content" ) ) + { + m_vecPanelsToShow[ m_vecPanelsToShow.AddToTail() ].Set( pChild ); + } + else if ( FStrEq( pszAsync, "loading" ) ) + { + m_vecLoadingPanels[ m_vecLoadingPanels.AddToTail() ].Set( pChild ); + } +} + +bool CBaseASyncPanel::IsInitialized() const +{ + return m_bDataInitialized; +} + +void CBaseASyncPanel::PresentDataIfReady() +{ + if ( m_bDataInitialized && m_bSettingsApplied ) + { + FOR_EACH_VEC( m_vecLoadingPanels, i ) + { + m_vecLoadingPanels[ i ]->SetVisible( false ); + } + + FOR_EACH_VEC( m_vecPanelsToShow, i ) + { + m_vecPanelsToShow[ i ]->SetVisible( true ); + } + + // stop ticking. job is done. + ivgui()->RemoveTickSignal( GetVPanel() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Checks if data is ready. If so, mark the time and hide the loading image +//----------------------------------------------------------------------------- +void CBaseASyncPanel::CheckForData() +{ + m_flLastRequestTime = Plat_FloatTime(); + if ( CheckForData_Internal() ) + { + m_flLastUpdatedTime = Plat_FloatTime(); + m_bDataInitialized = true; + PresentDataIfReady(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Check if we need to check for data +//----------------------------------------------------------------------------- +void CBaseASyncPanel::OnTick() +{ + const float flTimeSinceUpdate = Plat_FloatTime() - m_flLastUpdatedTime; + const float flTimeSinceRequest = Plat_FloatTime() - m_flLastRequestTime; + // Need an update if we're beyodn the refresh delay, and our refresh delay isn't k_flNeverRequestUpdate, or if we're just not initialized + const bool bNeedsUpdate = ( ( flTimeSinceUpdate > m_flRefreshDelay ) && ( m_flRefreshDelay != k_flNeverRequestUpdate ) ) || m_flLastUpdatedTime == 0.f; + + if ( m_bSettingsApplied && bNeedsUpdate && flTimeSinceRequest > k_flRequestInterval ) + { + CheckForData(); + } +} diff --git a/game/client/tf/vgui/tf_asyncpanel.h b/game/client/tf/vgui/tf_asyncpanel.h new file mode 100644 index 0000000..478c83f --- /dev/null +++ b/game/client/tf/vgui/tf_asyncpanel.h @@ -0,0 +1,40 @@ +#ifndef TF_ASYNCPANEL +#define TF_ASYNCPANEL + +#include "vgui_controls/EditablePanel.h" + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CBaseASyncPanel : public EditablePanel +{ + DECLARE_CLASS_SIMPLE( CBaseASyncPanel, EditablePanel ); +public: + CBaseASyncPanel( Panel *pParent, const char *pszPanelName ); + virtual ~CBaseASyncPanel() {} + + bool IsInitialized() const; + void CheckForData(); + virtual void OnTick() OVERRIDE; + virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE; + virtual void PerformLayout() OVERRIDE; + virtual void LoadControlSettings(const char *dialogResourceName, const char *pathID = NULL, KeyValues *pPreloadedKeyValues = NULL, KeyValues *pConditions = NULL) OVERRIDE; +protected: + virtual void OnChildSettingsApplied( KeyValues *pInResourceData, Panel *pChild ) OVERRIDE; + +private: + void PresentDataIfReady(); + virtual bool CheckForData_Internal() = 0; + + bool m_bDataInitialized; + bool m_bSettingsApplied; + float m_flLastRequestTime; + float m_flLastUpdatedTime; + CUtlVector< PHandle > m_vecLoadingPanels; + CUtlVector< PHandle > m_vecPanelsToShow; + CPanelAnimationVar( float, m_flRefreshDelay, "refresh_delay", "-1.f" ); +}; + +#endif //TF_ASYNCPANEL
\ No newline at end of file diff --git a/game/client/tf/vgui/tf_badge_panel.cpp b/game/client/tf/vgui/tf_badge_panel.cpp new file mode 100644 index 0000000..d66918f --- /dev/null +++ b/game/client/tf/vgui/tf_badge_panel.cpp @@ -0,0 +1,49 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "cbase.h" + +#include "modelimagepanel.h" +#include "tf_badge_panel.h" + +DECLARE_BUILD_FACTORY( CTFBadgePanel ); + +CTFBadgePanel::CTFBadgePanel( vgui::Panel *pParent, const char *pName ) : BaseClass( pParent, pName ) +{ + m_pBadgePanel = new CModelImagePanel( this, "BadgePanel" ); + m_nPrevLevel = 0; +} + + +void CTFBadgePanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + m_pBadgePanel->LoadControlSettings( "resource/ui/BadgePanel.res" ); +} + + +void CTFBadgePanel::SetupBadge( const IProgressionDesc* pProgress, const LevelInfo_t& levelInfo ) +{ + if ( !pProgress ) + return; + + pProgress->SetupBadgePanel( m_pBadgePanel, levelInfo ); + + if ( m_nPrevLevel != levelInfo.m_nLevelNum ) + { + m_nPrevLevel = levelInfo.m_nLevelNum; + m_pBadgePanel->InvalidateImage(); + } +} + + +void CTFBadgePanel::SetupBadge( const IProgressionDesc* pProgress, const CSteamID& steamID ) +{ + if ( pProgress && steamID.IsValid() ) + { + SetupBadge( pProgress, pProgress->YieldingGetLevelForSteamID( steamID ) ); + } +} diff --git a/game/client/tf/vgui/tf_badge_panel.h b/game/client/tf/vgui/tf_badge_panel.h new file mode 100644 index 0000000..edcba8f --- /dev/null +++ b/game/client/tf/vgui/tf_badge_panel.h @@ -0,0 +1,32 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#ifndef TF_BADGE_PANEL_H +#define TF_BADGE_PANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "vgui_controls/EditablePanel.h" +#include "tf_match_description.h" + +class CTFBadgePanel : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CTFBadgePanel, vgui::EditablePanel ); +public: + CTFBadgePanel( vgui::Panel *pParent, const char *pName ); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + + void SetupBadge( const IProgressionDesc* pProgress, const LevelInfo_t& levelInfo ); + void SetupBadge( const IProgressionDesc* pProgress, const CSteamID& steamID ); + +private: + class CModelImagePanel *m_pBadgePanel; + uint32 m_nPrevLevel; +}; + + +#endif // TF_BADGE_PANEL_H diff --git a/game/client/tf/vgui/tf_classmenu.cpp b/game/client/tf/vgui/tf_classmenu.cpp new file mode 100644 index 0000000..c6ba14c --- /dev/null +++ b/game/client/tf/vgui/tf_classmenu.cpp @@ -0,0 +1,1703 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tf_classmenu.h" +#include "IGameUIFuncs.h" // for key bindings +#include "tf_hud_notification_panel.h" +#include "character_info_panel.h" +#include "playerspawncache.h" +#include "iclientmode.h" +#include "econ_gcmessages.h" +#include "gc_clientsystem.h" +#include "vgui/IInput.h" +#include <vgui_controls/PanelListPanel.h> +#include <vgui_controls/ScrollBarSlider.h> +#include "tf_gamerules.h" +#include "engine/IEngineSound.h" +#include "inputsystem/iinputsystem.h" + +#if defined( REPLAY_ENABLED ) +#include "replay/iclientreplaycontext.h" +#include "replay/ireplaysystem.h" +#include "replay/ienginereplay.h" +#include "replay/shared_defs.h" +#endif + +extern IGameUIFuncs *gameuifuncs; // for key binding details + +using namespace vgui; + +ConVar _cl_classmenuopen( "_cl_classmenuopen", "0", 0, "internal cvar used to tell server when class menu is open" ); + +#if defined( REPLAY_ENABLED ) +ConVar replay_replaywelcomedlgcount( "replay_replaywelcomedlgcount", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_ARCHIVE | FCVAR_HIDDEN, "The number of times the replay help dialog has displayed." ); +#endif + +ConVar tf_mvm_classupgradehelpcount( "tf_mvm_classupgradehelpcount", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_ARCHIVE | FCVAR_HIDDEN, "The number of times the player upgrade help dialog has displayed." ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFClassTipsItemPanel::CTFClassTipsItemPanel( Panel *parent, const char *name, int iListItemID ) : BaseClass( parent, name ) +{ + m_pTipIcon = new vgui::ImagePanel( this, "TipIcon" ); + m_pTipLabel = new CExLabel( this, "TipLabel", "" ); + +// m_pTipIcon = dynamic_cast<vgui::ImagePanel*>( FindChildByName( "TipIcon" ) ); +// m_pTipLabel = dynamic_cast<CExLabel*>( FindChildByName( "TipLabel" ) ); +} + +CTFClassTipsItemPanel::~CTFClassTipsItemPanel() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClassTipsItemPanel::SetClassTip( const wchar_t *pwszText, const char *pszIcon ) +{ + // Set tf_english string and .res icon path + if ( m_pTipLabel && m_pTipIcon ) + { + if ( pszIcon ) + { + m_pTipIcon->SetImage( pszIcon ); + } + else + { + m_pTipIcon->SetVisible( false ); +// int iX, iY = 0; +// m_pTipLabel->GetPos( iX, iY ); +// int nIconWidth = m_pTipIcon->GetWide(); +// m_pTipLabel->SetPos( ( iX - nIconWidth ), iY ); + } + m_pTipLabel->SetText( pwszText ); + } + + InvalidateLayout( true, true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClassTipsItemPanel::ApplySchemeSettings( IScheme* pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "Resource/UI/ClassTipsItem.res" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CTFClassTipsListPanel : public PanelListPanel +{ +private: + DECLARE_CLASS_SIMPLE( CTFClassTipsListPanel, PanelListPanel ); + +public: + CTFClassTipsListPanel( Panel *parent, const char *panelName ) + : PanelListPanel( parent, panelName ) + { + m_pScrollBar = GetScrollbar(); + + m_pUpArrow = new CExImageButton( this, "UpArrow", "" ); + if ( m_pUpArrow ) + { + m_pUpArrow->AddActionSignalTarget( m_pScrollBar ); + m_pUpArrow->SetCommand(new KeyValues("ScrollButtonPressed", "index", 0)); + m_pUpArrow->GetImage()->SetShouldScaleImage( true ); + m_pUpArrow->SetFgColor( Color( 255, 255, 255, 255 ) ); + m_pUpArrow->SetAlpha( 255 ); + m_pUpArrow->SetPaintBackgroundEnabled( false ); + m_pUpArrow->SetVisible( false ); + m_pUpArrow->PassMouseTicksTo( m_pScrollBar ); + m_pUpArrow->SetImageDefault( "chalkboard_scroll_up" ); + } + + m_pDownArrow = new CExImageButton( this, "DownArrow", "" ); + if ( m_pDownArrow ) + { + m_pDownArrow->AddActionSignalTarget( m_pScrollBar ); + m_pDownArrow->SetCommand(new KeyValues("ScrollButtonPressed", "index", 1)); + m_pDownArrow->GetImage()->SetShouldScaleImage( true ); + m_pDownArrow->SetFgColor( Color( 255, 255, 255, 255 ) ); + m_pDownArrow->SetAlpha( 255 ); + m_pDownArrow->SetPaintBackgroundEnabled( false ); + m_pDownArrow->SetVisible( false ); + m_pDownArrow->PassMouseTicksTo( m_pScrollBar ); + m_pDownArrow->SetImageDefault( "chalkboard_scroll_down" ); + } + + if ( m_pScrollBar ) + { + m_pScrollBar->SetOverriddenButtons( m_pUpArrow, m_pDownArrow ); + m_pScrollBar->SetVisible( false ); + } + + m_pLine = new vgui::ImagePanel( this, "Line" ); + m_pBox = new vgui::ImagePanel( this, "Box" ); + if ( m_pLine ) + { + m_pLine->SetImage( "chalkboard_scroll_line" ); + m_pLine->SetShouldScaleImage( true ); + } + + if ( m_pBox ) + { + m_pBox->SetImage( "chalkboard_scroll_box" ); + m_pBox->SetShouldScaleImage( true ); + } + + SetScrollBarImagesVisible( false ); + + vgui::ivgui()->AddTickSignal( GetVPanel() ); + } + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + ~CTFClassTipsListPanel() + { + ivgui()->RemoveTickSignal( GetVPanel() ); + } + +private: + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + int GetListHeight( void ) + { + int nHeight = 0; + + for ( int i = FirstItem(); i < GetItemCount(); i++ ) + { + vgui::Panel *pClassTipsItemPanel = GetItemPanel( i ); + if ( pClassTipsItemPanel ) + { + nHeight += pClassTipsItemPanel->GetTall(); + } + } + + return nHeight; + } + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + void SetScrollBarImagesVisible( bool visible ) + { + if ( m_pDownArrow && m_pDownArrow->IsVisible() != visible ) + { + m_pDownArrow->SetVisible( visible ); + m_pDownArrow->SetEnabled( visible ); + } + + if ( m_pUpArrow && m_pUpArrow->IsVisible() != visible ) + { + m_pUpArrow->SetVisible( visible ); + m_pUpArrow->SetEnabled( visible ); + } + + if ( m_pLine && m_pLine->IsVisible() != visible ) + { + m_pLine->SetVisible( visible ); + } + + if ( m_pBox && m_pBox->IsVisible() != visible ) + { + m_pBox->SetVisible( visible ); + } + } + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + void OnTick() + { + if ( !IsVisible() ) + return; + + if ( m_pDownArrow && m_pUpArrow && m_pLine && m_pBox ) + { + if ( m_pScrollBar && m_pScrollBar->IsVisible() && GetListHeight() > GetTall() ) + { + SetScrollBarImagesVisible ( true ); + + // set the alpha on the up arrow + int nMin, nMax; + m_pScrollBar->GetRange( nMin, nMax ); + int nScrollPos = m_pScrollBar->GetValue(); + int nRangeWindow = m_pScrollBar->GetRangeWindow(); + int nBottom = nMax - nRangeWindow; + if ( nBottom < 0 ) + { + nBottom = 0; + } + + int nAlpha = ( nScrollPos - nMin <= 0 ) ? 90 : 255; + m_pUpArrow->SetAlpha( nAlpha ); + + // set the alpha on the down arrow + nAlpha = ( nScrollPos >= nBottom ) ? 90 : 255; + m_pDownArrow->SetAlpha( nAlpha ); + + ScrollBarSlider *pSlider = m_pScrollBar->GetSlider(); + if ( pSlider && pSlider->GetRangeWindow() > 0 ) + { + int x, y, w, t, min, max; + m_pLine->GetBounds( x, y, w, t ); + pSlider->GetNobPos( min, max ); + m_pBox->SetBounds( x, y + min, w, ( max - min ) ); + } + } + else + { + // turn off our images + SetScrollBarImagesVisible ( false ); + } + } + } + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + if ( m_pScrollBar ) + { + m_pScrollBar->SetZPos( 500 ); + m_pUpArrow->SetZPos( 501 ); + m_pDownArrow->SetZPos( 501 ); + + // turn off painting the vertical scrollbar + m_pScrollBar->SetPaintBackgroundEnabled( false ); + m_pScrollBar->SetPaintBorderEnabled( false ); + m_pScrollBar->SetPaintEnabled( false ); + m_pScrollBar->SetScrollbarButtonsVisible( false ); + m_pScrollBar->GetButton(0)->SetMouseInputEnabled( false ); + m_pScrollBar->GetButton(1)->SetMouseInputEnabled( false ); + + if ( m_pScrollBar->IsVisible() ) + { + int nMin, nMax; + m_pScrollBar->GetRange( nMin, nMax ); + m_pScrollBar->SetValue( nMin ); + + int nScrollbarWide = m_pScrollBar->GetWide(); + + int wide, tall; + GetSize( wide, tall ); + + if ( m_pUpArrow ) + { + m_pUpArrow->SetBounds( wide - nScrollbarWide, 0, nScrollbarWide, nScrollbarWide ); + m_pUpArrow->GetImage()->SetSize( nScrollbarWide, nScrollbarWide ); + } + + if ( m_pLine ) + { + m_pLine->SetBounds( wide - nScrollbarWide, nScrollbarWide, nScrollbarWide, tall - ( 2 * nScrollbarWide ) ); + } + + if ( m_pBox ) + { + m_pBox->SetBounds( wide - nScrollbarWide, nScrollbarWide, nScrollbarWide, nScrollbarWide ); + } + + if ( m_pDownArrow ) + { + m_pDownArrow->SetBounds( wide - nScrollbarWide, tall - nScrollbarWide, nScrollbarWide, nScrollbarWide ); + m_pDownArrow->GetImage()->SetSize( nScrollbarWide, nScrollbarWide ); + } + + SetScrollBarImagesVisible( false ); + } + } + } + + vgui::ScrollBar *m_pScrollBar; + CExImageButton *m_pUpArrow; + CExImageButton *m_pDownArrow; + vgui::ImagePanel *m_pLine; + vgui::ImagePanel *m_pBox; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CTFClassTipsPanel : public EditablePanel +{ +private: + DECLARE_CLASS_SIMPLE( CTFClassTipsPanel, EditablePanel ); + +public: + CTFClassTipsPanel( Panel *parent, const char *panelName ) + : EditablePanel( parent, panelName ) + { + m_pClassTipsListPanel = new CTFClassTipsListPanel( this, "ClassTipsListPanel" ); + } + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + ~CTFClassTipsPanel() + { + } + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + void SetClass( int iClass ) + { + const char *pPathID = IsX360() ? "MOD" : "GAME"; + m_fmtResFilename.sprintf( "classes/%s.res", g_aRawPlayerClassNames[ iClass ] ); + if ( !g_pFullFileSystem->FileExists( m_fmtResFilename.Access(), pPathID ) && + g_pFullFileSystem->FileExists( "classes/default.res", pPathID ) ) + { + m_fmtResFilename.sprintf( "classes/default.res" ); + } + + if ( m_pClassTipsListPanel ) + { + m_pClassTipsListPanel->DeleteAllItems(); + + int nScrollToItem = 0; + + // Get tip count + const wchar_t *wzTipCount = g_pVGuiLocalize->Find( CFmtStr( "ClassTips_%d_Count", iClass ) ); + int nTipCount = wzTipCount ? _wtoi( wzTipCount ) : 0; + for ( int iTip = 1; iTip < nTipCount+1; ++iTip ) + { + const wchar_t *pwszText = g_pVGuiLocalize->Find( CFmtStr( "#ClassTips_%d_%d", iClass, iTip ) ); + const wchar_t *pwszTextMvM = g_pVGuiLocalize->Find( CFmtStr( "#ClassTips_%d_%d_MvM", iClass, iTip ) ); + wchar_t *pwszIcon = g_pVGuiLocalize->Find( CFmtStr( "ClassTips_%d_%d_Icon", iClass, iTip ) ); + char szIcon[MAX_PATH]; + + szIcon[0] = 0; + if ( pwszIcon ) + { + g_pVGuiLocalize->ConvertUnicodeToANSI( pwszIcon, szIcon, sizeof( szIcon ) ); + } + + // Don't load MvM tips outside the mode + if ( pwszTextMvM ) + { + if ( !TFGameRules()->IsMannVsMachineMode() ) + continue; + + // If we're MvM mode, remember first MvM tip + if ( !nScrollToItem ) + nScrollToItem = iTip; + } + + // Create a TipsItemPanel for each tip + if ( pwszText || pwszTextMvM ) + { + CTFClassTipsItemPanel *pClassTipsItemPanel = new CTFClassTipsItemPanel( this, "ClassTipsItemPanel", iTip ); + if ( pwszText ) + { + pClassTipsItemPanel->SetClassTip( pwszText, szIcon ); + } + else if ( pwszTextMvM ) + { + pClassTipsItemPanel->SetClassTip( pwszTextMvM, szIcon ); + } + + m_pClassTipsListPanel->AddItem( NULL, pClassTipsItemPanel ); + } + } + + if ( m_pClassTipsListPanel->GetItemCount() > 0 ) + { + m_pClassTipsListPanel->SetFirstColumnWidth( 0 ); + m_pClassTipsListPanel->ScrollToItem( nScrollToItem ); + } + } + + InvalidateLayout( true, true ); + } + +private: + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "Resource/UI/ClassTipsList.res" ); + } + + CTFClassTipsListPanel *m_pClassTipsListPanel; + + CFmtStr m_fmtResFilename; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFClassMenu::CTFClassMenu( IViewPort *pViewPort ) +: CClassMenu( pViewPort ) +{ + MakePopup(); + + m_mouseoverButtons.RemoveAll(); + + m_iClassMenuKey = BUTTON_CODE_INVALID; + m_iCurrentClassIndex = TF_CLASS_HEAVYWEAPONS; + +#ifdef _X360 + m_pFooter = new CTFFooter( this, "Footer" ); +#endif + + m_pTFPlayerModelPanel = NULL; + m_pSelectAClassLabel = NULL; + + m_pClassTipsPanel = new CTFClassTipsPanel( this, "ClassTipsPanel" ); + + Q_memset( m_pClassButtons, 0, sizeof( m_pClassButtons ) ); + +#ifndef _X360 + char tempName[MAX_PATH]; + for ( int i = 0 ; i < CLASS_COUNT_IMAGES ; ++i ) + { + Q_snprintf( tempName, sizeof( tempName ), "countImage%d", i ); + m_ClassCountImages[i] = new CTFImagePanel( this, tempName ); + } + + m_pCountLabel = NULL; + + m_pLocalPlayerImage = new CTFImagePanel( this, "localPlayerImage" ); + m_pLocalPlayerBG = new CTFImagePanel( this, "localPlayerBG" ); + m_iLocalPlayerClass = TEAM_UNASSIGNED; + + m_pClassButtons[TF_CLASS_SCOUT] = new CExImageButton( this, "scout", "", this ); + m_pClassButtons[TF_CLASS_SOLDIER] = new CExImageButton( this, "soldier", "", this ); + m_pClassButtons[TF_CLASS_PYRO] = new CExImageButton( this, "pyro", "", this ); + m_pClassButtons[TF_CLASS_DEMOMAN] = new CExImageButton( this, "demoman", "", this ); + m_pClassButtons[TF_CLASS_MEDIC] = new CExImageButton( this, "medic", "", this ); + m_pClassButtons[TF_CLASS_HEAVYWEAPONS] = new CExImageButton( this, "heavyweapons", "", this ); + m_pClassButtons[TF_CLASS_SNIPER] = new CExImageButton( this, "sniper", "", this ); + m_pClassButtons[TF_CLASS_ENGINEER] = new CExImageButton( this, "engineer", "", this ); + m_pClassButtons[TF_CLASS_SPY] = new CExImageButton( this, "spy", "", this ); + m_pClassButtons[TF_CLASS_RANDOM] = new CExImageButton( this, "random", "", this ); +#endif + + m_pEditLoadoutButton = NULL; + m_nBaseMusicGuid = -1; + + ListenForGameEvent( "localplayer_changeteam" ); + ListenForGameEvent( "show_match_summary" ); + + Q_memset( m_pMvmUpgradeImages, 0, sizeof( m_pMvmUpgradeImages ) ); + m_pMvmUpgradeImages[TF_CLASS_SCOUT] = new vgui::ImagePanel( this, "MvMUpgradeImageScout" ); + m_pMvmUpgradeImages[TF_CLASS_SOLDIER] = new vgui::ImagePanel( this, "MvMUpgradeImageSolider" ); + m_pMvmUpgradeImages[TF_CLASS_PYRO] = new vgui::ImagePanel( this, "MvMUpgradeImagePyro" ); + m_pMvmUpgradeImages[TF_CLASS_DEMOMAN] = new vgui::ImagePanel( this, "MvMUpgradeImageDemoman" ); + m_pMvmUpgradeImages[TF_CLASS_MEDIC] = new vgui::ImagePanel( this, "MvMUpgradeImageMedic" ); + m_pMvmUpgradeImages[TF_CLASS_HEAVYWEAPONS] = new vgui::ImagePanel( this, "MvMUpgradeImageHeavy" ); + m_pMvmUpgradeImages[TF_CLASS_SNIPER] = new vgui::ImagePanel( this, "MvMUpgradeImageSniper" ); + m_pMvmUpgradeImages[TF_CLASS_ENGINEER] = new vgui::ImagePanel( this, "MvMUpgradeImageEngineer" ); + m_pMvmUpgradeImages[TF_CLASS_SPY] = new vgui::ImagePanel( this, "MvMUpgradeImageSpy" ); + + vgui::ivgui()->AddTickSignal( GetVPanel() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClassMenu::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + for ( int i = 0; i < TF_CLASS_MENU_BUTTONS; i++ ) + { + m_pClassHintIcons[i] = nullptr; + } + + // Load the .res file + if ( ::input->IsSteamControllerActive() ) + { + LoadControlSettings( "Resource/UI/ClassSelection_SC.res" ); + m_pCancelHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "CancelHintIcon" ) ); + m_pEditLoadoutHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "EditLoadoutHintIcon" ) ); + + m_pClassHintIcons[TF_CLASS_SCOUT] = dynamic_cast< CSCHintIcon* >( FindChildByName( "ScoutHintIcon" ) ); + m_pClassHintIcons[TF_CLASS_SOLDIER] = dynamic_cast< CSCHintIcon* >( FindChildByName( "SoldierHintIcon" ) ); + m_pClassHintIcons[TF_CLASS_PYRO] = dynamic_cast< CSCHintIcon* >( FindChildByName( "PyroHintIcon" ) ); + m_pClassHintIcons[TF_CLASS_DEMOMAN] = dynamic_cast< CSCHintIcon* >( FindChildByName( "DemomanHintIcon" ) ); + m_pClassHintIcons[TF_CLASS_HEAVYWEAPONS] = dynamic_cast< CSCHintIcon* >( FindChildByName( "HeavyHintIcon" ) ); + m_pClassHintIcons[TF_CLASS_MEDIC] = dynamic_cast< CSCHintIcon* >( FindChildByName( "MedicHintIcon" ) ); + m_pClassHintIcons[TF_CLASS_SPY] = dynamic_cast< CSCHintIcon* >( FindChildByName( "SpyHintIcon" ) ); + m_pClassHintIcons[TF_CLASS_ENGINEER] = dynamic_cast< CSCHintIcon* >( FindChildByName( "EngineerHintIcon" ) ); + m_pClassHintIcons[TF_CLASS_SNIPER] = dynamic_cast< CSCHintIcon* >( FindChildByName( "SniperHintIcon" ) ); + m_pClassHintIcons[TF_CLASS_RANDOM] = dynamic_cast< CSCHintIcon* >( FindChildByName( "RandomHintIcon" ) ); + + for ( int i = 0; i < TF_CLASS_MENU_BUTTONS; i++ ) + { + if ( m_pClassHintIcons[i] ) + { + m_pClassHintIcons[i]->SetVisible( false ); + } + } + + SetMouseInputEnabled( false ); + } + else + { + LoadControlSettings( "Resource/UI/ClassSelection.res" ); + m_pCancelHintIcon = m_pEditLoadoutHintIcon = nullptr; + SetMouseInputEnabled( true ); + } + + m_pTFPlayerModelPanel = dynamic_cast<CTFPlayerModelPanel*>( FindChildByName("TFPlayerModel") ); + m_pSelectAClassLabel = dynamic_cast<CExLabel*>( FindChildByName( "ClassMenuSelect" ) ); + m_pEditLoadoutButton = dynamic_cast<CExButton*>( FindChildByName( "EditLoadoutButton" ) ); + + const char *pTeamExtension = GetTeamNumber() == TF_TEAM_BLUE ? "blu" : "red"; + for ( int i = 0; i < ARRAYSIZE( m_pClassButtons ); ++i ) + { + if ( !m_pClassButtons[i] ) + continue; + + if ( !IsValidTFPlayerClass( i ) && i != TF_CLASS_RANDOM ) + continue; + + m_pClassButtons[i]->SetImageSelected( CFmtStr( "class_sel_sm_%s_%s", g_aRawPlayerClassNamesShort[i], pTeamExtension ).Access() ); + if( i != TF_CLASS_RANDOM ) + { + m_pClassButtons[i]->SetArmedSound("misc/null.wav"); + } + } +} + +void CTFClassMenu::PerformLayout() +{ + BaseClass::PerformLayout(); + +#ifndef _X360 + m_pCountLabel = dynamic_cast< CExLabel * >( FindChildByName( "CountLabel" ) ); + + if ( m_pCountLabel ) + { + m_pCountLabel->SizeToContents(); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CTFClassMenu::GetCurrentPlayerClass() +{ + int iClass = TF_CLASS_HEAVYWEAPONS; + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + + if ( pLocalPlayer && pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex() != TF_CLASS_UNDEFINED ) + { + iClass = pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex(); + } + + return iClass; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CExImageButton *CTFClassMenu::GetCurrentClassButton() +{ + const int iClass = GetCurrentPlayerClass(); + m_iCurrentClassIndex = iRemapIndexToClass[ iClass ]; + return m_pClassButtons[iClass]; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClassMenu::ShowPanel( bool bShow ) +{ + if ( bShow ) + { + // Hide the other class menu + if ( gViewPortInterface ) + { + gViewPortInterface->ShowPanel( GetTeamNumber() == TF_TEAM_BLUE ? PANEL_CLASS_RED : PANEL_CLASS_BLUE, false ); + } + + // can't change class if you're on the losing team during the "bonus time" after a team has won the round + if ( ( TFGameRules()->State_Get() == GR_STATE_TEAM_WIN && + C_TFPlayer::GetLocalTFPlayer() && + C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TFGameRules()->GetWinningTeam() + && C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TEAM_SPECTATOR + && C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TEAM_UNASSIGNED + && GetSpectatorMode() == OBS_MODE_NONE ) || + TFGameRules()->State_Get() == GR_STATE_GAME_OVER || + ( TFGameRules()->IsInTraining() && C_TFPlayer::GetLocalTFPlayer() && + ( C_TFPlayer::GetLocalTFPlayer()->GetPlayerClass() == NULL || C_TFPlayer::GetLocalTFPlayer()->GetPlayerClass()->GetClassIndex() != TF_CLASS_UNDEFINED ) ) ) + { + SetVisible( false ); + + CHudNotificationPanel *pNotifyPanel = GET_HUDELEMENT( CHudNotificationPanel ); + if ( pNotifyPanel ) + { + if ( C_TFPlayer::GetLocalTFPlayer() ) + { + pNotifyPanel->SetupNotifyCustom( "#TF_CantChangeClassNow", "ico_notify_flag_moving", C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() ); + } + } + + return; + } + + engine->CheckPoint( "ClassMenu" ); + + // Force us to reload our scheme, in case Steam Controller stuff has changed. + InvalidateLayout( true, true ); + + Activate(); + + m_iClassMenuKey = gameuifuncs->GetButtonCodeForBind( "changeclass" ); + m_iScoreBoardKey = gameuifuncs->GetButtonCodeForBind( "showscores" ); + + SelectClass( GetCurrentPlayerClass() ); + } + else + { + SetVisible( false ); + if ( m_pTFPlayerModelPanel ) + { + m_pTFPlayerModelPanel->ClearCarriedItems(); + } + } +} + +const char *g_pszLegacyClassSelectVCDWeapons[TF_LAST_NORMAL_CLASS] = +{ + "", // TF_CLASS_UNDEFINED = 0, + "", // TF_CLASS_SCOUT, // weapons handled individually + "", // TF_CLASS_SNIPER, // weapons handled individually + "", // TF_CLASS_SOLDIER, // weapons handled individually + "tf_weapon_grenadelauncher", // TF_CLASS_DEMOMAN, + "tf_weapon_medigun", // TF_CLASS_MEDIC, + "tf_weapon_minigun", // TF_CLASS_HEAVYWEAPONS, + "tf_weapon_flamethrower", // TF_CLASS_PYRO, + "", // TF_CLASS_SPY, // weapons handled individually + "tf_weapon_wrench", // TF_CLASS_ENGINEER, +}; + +int g_iLegacyClassSelectWeaponSlots[TF_LAST_NORMAL_CLASS] = +{ + LOADOUT_POSITION_PRIMARY, // TF_CLASS_UNDEFINED = 0, + LOADOUT_POSITION_PRIMARY, // TF_CLASS_SCOUT, // TF_FIRST_NORMAL_CLASS + LOADOUT_POSITION_PRIMARY, // TF_CLASS_SNIPER, + LOADOUT_POSITION_PRIMARY, // TF_CLASS_SOLDIER, + LOADOUT_POSITION_PRIMARY, // TF_CLASS_DEMOMAN, + LOADOUT_POSITION_SECONDARY, // TF_CLASS_MEDIC, + LOADOUT_POSITION_PRIMARY, // TF_CLASS_HEAVYWEAPONS, + LOADOUT_POSITION_PRIMARY, // TF_CLASS_PYRO, + LOADOUT_POSITION_MELEE, // TF_CLASS_SPY, + LOADOUT_POSITION_MELEE, // TF_CLASS_ENGINEER, +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClassMenu::UpdateButtonSelectionStates( int iClass ) +{ + // Set the correct button as selected, all other buttons as not selected + for ( int i = 0; i < ARRAYSIZE( m_pClassButtons ); ++i ) + { + if ( !m_pClassButtons[ i ] ) + continue; + + m_pClassButtons[ i ]->SetSelected( i == iClass ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClassMenu::SelectClass( int iClass ) +{ + if ( !engine->IsInGame() ) + return; + + if ( !m_pTFPlayerModelPanel ) + return; + + if ( !m_pClassTipsPanel ) + return; + + if ( !m_pEditLoadoutButton ) + return; + + // Update select hint icon for Steam Controller + for ( int i = 0; i < TF_CLASS_MENU_BUTTONS; i++ ) + { + if ( m_pClassHintIcons[i] ) + { + m_pClassHintIcons[i]->SetVisible( i == iClass ); + } + } + + // Were we random? If so, we'll force our class to refresh later to prevent the + // model panel thinking the class hasn't changed. + const bool bClassWasRandom = m_iCurrentClassIndex == TF_CLASS_RANDOM; + + // Cache current player class + m_iCurrentClassIndex = iClass; + + UpdateButtonSelectionStates( iClass ); + + bool bRandomClass = iClass == TF_CLASS_RANDOM; + if ( !IsValidTFPlayerClass( iClass ) && !bRandomClass ) + { + m_pTFPlayerModelPanel->SetVisible( false ); + m_pTFPlayerModelPanel->ClearCarriedItems(); + return; + } + + m_pTFPlayerModelPanel->SetVisible( true ); + m_pTFPlayerModelPanel->ClearCarriedItems(); + + if ( bRandomClass ) + { + m_pEditLoadoutButton->SetVisible( false ); + if ( m_pEditLoadoutHintIcon ) + { + m_pEditLoadoutHintIcon->SetVisible( false ); + } + + MDLHandle_t hModel = mdlcache->FindMDL( "models/class_menu/random_class_icon.mdl" ); + m_pTFPlayerModelPanel->SetMDL( hModel ); + m_pTFPlayerModelPanel->SetSequence( ACT_IDLE ); + m_pTFPlayerModelPanel->InvalidateLayout( true, true ); // Updates position + m_pTFPlayerModelPanel->SetSkin( GetTeamNumber() == TF_TEAM_RED ? 0 : 1 ); + mdlcache->Release( hModel ); // counterbalance addref from within FindMDL + } + else + { + bool bIsRobot = false; + // Check for Robot + static CSchemaAttributeDefHandle pAttrDef_PlayerRobot( "appear as mvm robot" ); + for ( int i = 0; i < CLASS_LOADOUT_POSITION_COUNT; i++ ) + { + CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( iClass, i ); + if ( !pItemData ) + continue; + if ( FindAttribute( pItemData, pAttrDef_PlayerRobot ) ) + { + bIsRobot = true; + break; + } + } + + /*if ( pLocalPlayer ) + { + int iRobot = 0; + CALL_ATTRIB_HOOK_INT_ON_OTHER( pLocalPlayer, iRobot, appear_as_mvm_robot ); + bIsRobot = iRobot ? true : false; + }*/ + m_pTFPlayerModelPanel->SetToPlayerClass( iClass, bIsRobot, bClassWasRandom ); + + m_pEditLoadoutButton->SetVisible( true ); + if ( m_pEditLoadoutHintIcon ) + { + m_pEditLoadoutHintIcon->SetVisible( true ); + } + + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pLocalPlayer ) + { + int iTeam = GetTeamNumber(); + m_pTFPlayerModelPanel->SetTeam( iTeam ); + } + + LoadItems(); + } + + m_pClassTipsPanel->SetClass( iClass ); + + enginesound->StopSoundByGuid( m_nBaseMusicGuid ); + CBroadcastRecipientFilter filter; + char nClassMusicStr[64]; + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) + { + sprintf( nClassMusicStr, "music.mvm_class_menu_0%i", iClass ); + } + else + { + sprintf( nClassMusicStr, "music.class_menu_0%i", iClass ); + } + CBaseEntity::EmitSound( filter, SOUND_FROM_UI_PANEL, nClassMusicStr ); + m_nBaseMusicGuid = enginesound->GetGuidForLastSoundEmitted(); + + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClassMenu::LoadItems() +{ + const int iClass = m_pTFPlayerModelPanel->GetPlayerClass(); + + m_pTFPlayerModelPanel->ClearCarriedItems(); + + static CSchemaAttributeDefHandle pAttrDef_DisableFancyLoadoutAnim( "disable fancy class select anim" ); + bool bCanUseFancyClassSelectAnimation = true; + + static CSchemaAttributeDefHandle pAttrDef_ClassSelectOverrideVCD( "class select override vcd" ); + CAttribute_String attrClassSelectOverrideVCD; + + const char *pszVCD = "class_select"; + + for ( int i = 0; i < CLASS_LOADOUT_POSITION_COUNT; i++ ) + { + CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( iClass, i ); + if ( pItemData && pItemData->IsValid() ) + { + m_pTFPlayerModelPanel->AddCarriedItem( pItemData ); + + // Certain items have different shapes and would interfere with our class select animations. + bCanUseFancyClassSelectAnimation = bCanUseFancyClassSelectAnimation + && !pItemData->FindAttribute( pAttrDef_DisableFancyLoadoutAnim ); + + // Some items want to override the class select VCD + if ( pItemData->FindAttribute( pAttrDef_ClassSelectOverrideVCD, &attrClassSelectOverrideVCD ) ) + { + const char *pszClassSelectOverrideVCD = attrClassSelectOverrideVCD.value().c_str(); + if ( pszClassSelectOverrideVCD && *pszClassSelectOverrideVCD ) + { + pszVCD = pszClassSelectOverrideVCD; + } + } + } + } + + m_pTFPlayerModelPanel->PlayVCD( bCanUseFancyClassSelectAnimation ? pszVCD : NULL, g_pszLegacyClassSelectVCDWeapons[iClass] ); + m_pTFPlayerModelPanel->HoldItemInSlot( g_iLegacyClassSelectWeaponSlots[iClass] ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClassMenu::OnKeyCodePressed( KeyCode code ) +{ + m_KeyRepeat.KeyDown( code ); + + if ( code > KEY_0 && code <= KEY_9 ) + { + const int iButton = code - KEY_0; + const int iClass = iRemapIndexToClass[ iButton ]; + SelectClass( iClass ); + Go(); + } + else if ( code > KEY_PAD_0 && code <= KEY_PAD_9 ) + { + const int iButton = code - KEY_PAD_0; + const int iClass = iRemapIndexToClass[ iButton ]; + SelectClass( iClass ); + Go(); + } + else if( code == KEY_ENTER || code == KEY_SPACE || code == KEY_XBUTTON_A || code == KEY_XBUTTON_RTRIGGER || code == STEAMCONTROLLER_A ) + { + Go(); + } + else if ( ( m_iClassMenuKey != BUTTON_CODE_INVALID && m_iClassMenuKey == code ) || + code == KEY_XBUTTON_BACK || + code == KEY_XBUTTON_B || + code == STEAMCONTROLLER_B || + code == KEY_0 || + code == KEY_PAD_0 ) + { + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + + if ( pLocalPlayer && ( pLocalPlayer->GetPlayerClass()->GetClassIndex() != TF_CLASS_UNDEFINED ) ) + { + ShowPanel( false ); + } + } + else if( code == KEY_XBUTTON_RIGHT || code == KEY_XSTICK1_RIGHT || code == STEAMCONTROLLER_DPAD_RIGHT ) + { + int loopCheck = 0; + int nCurrentClass = GetRemappedMenuIndexForClass( m_iCurrentClassIndex ); + + do + { + loopCheck++; + nCurrentClass++; + nCurrentClass = ( nCurrentClass % TF_CLASS_MENU_BUTTONS ); + } while( ( m_pClassButtons[ iRemapIndexToClass[nCurrentClass] ] == NULL ) && ( loopCheck < TF_CLASS_MENU_BUTTONS ) ); + + SelectClass( iRemapIndexToClass[ nCurrentClass ] ); + } + else if ( code == STEAMCONTROLLER_Y ) + { + OnCommand( "openloadout" ); + } + else if( code == KEY_XBUTTON_LEFT || code == KEY_XSTICK1_LEFT || code == STEAMCONTROLLER_DPAD_LEFT ) + { + int loopCheck = 0; + int nCurrentClass = GetRemappedMenuIndexForClass( m_iCurrentClassIndex ); + + do + { + loopCheck++; + nCurrentClass--; + if( nCurrentClass <= 0 ) + { + nCurrentClass = GetRemappedMenuIndexForClass( TF_CLASS_RANDOM ); + } + } while( ( m_pClassButtons[ iRemapIndexToClass[nCurrentClass] ] == NULL ) && ( loopCheck < TF_CLASS_MENU_BUTTONS ) ); + + SelectClass( iRemapIndexToClass[ nCurrentClass ] ); + } + else if( code == KEY_XBUTTON_UP || code == KEY_XSTICK1_UP || code == STEAMCONTROLLER_DPAD_UP ) + { + // Scroll class info text up + if ( g_lastPanel ) + { + CExRichText *pRichText = dynamic_cast< CExRichText * >( g_lastPanel->FindChildByName( "classInfo" ) ); + + if ( pRichText ) + { + PostMessage( pRichText, new KeyValues("MoveScrollBarDirect", "delta", 1) ); + } + } + } + else if( code == KEY_XBUTTON_DOWN || code == KEY_XSTICK1_DOWN || code == STEAMCONTROLLER_DPAD_DOWN ) + { + // Scroll class info text up + if ( g_lastPanel ) + { + CExRichText *pRichText = dynamic_cast< CExRichText * >( g_lastPanel->FindChildByName( "classInfo" ) ); + + if ( pRichText ) + { + PostMessage( pRichText, new KeyValues("MoveScrollBarDirect", "delta", -1) ); + } + } + } + else + { + BaseClass::OnKeyCodePressed( code ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClassMenu::OnKeyCodeReleased( vgui::KeyCode code ) +{ + m_KeyRepeat.KeyUp( code ); + + BaseClass::OnKeyCodeReleased( code ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClassMenu::OnThink() +{ + vgui::KeyCode code = m_KeyRepeat.KeyRepeated(); + if ( code ) + { + OnKeyCodePressed( code ); + } + + // Get mouse cursor position + int aCursorPos[2]; + vgui::input()->GetCursorPos( aCursorPos[0], aCursorPos[1] ); + + // Go through all buttons - if the mouse is within one, select that class + for ( int i = 0; i < ARRAYSIZE( m_pClassButtons ); ++i ) + { + if ( !m_pClassButtons[ i ] ) + continue; + + if ( m_iCurrentClassIndex != i && m_pClassButtons[ i ]->IsWithin( aCursorPos[0], aCursorPos[1] ) ) + { + SelectClass( i ); + } + } + + //Always hide the health... this needs to be done every frame because a message from the server keeps resetting this. + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pLocalPlayer ) + { + pLocalPlayer->m_Local.m_iHideHUD |= HIDEHUD_HEALTH; + } + + BaseClass::OnThink(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClassMenu::SetCancelButtonVisible( bool bVisible ) +{ + SetVisibleButton( "CancelButton", bVisible ); + if ( m_pCancelHintIcon ) + { + m_pCancelHintIcon->SetVisible( bVisible ); + } + + if ( m_pSelectAClassLabel ) + { + m_pSelectAClassLabel->SetVisible( !bVisible ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClassMenu::Update() +{ + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + + // Force them to pick a class if they haven't picked one yet. + if ( ( pLocalPlayer && pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex() != TF_CLASS_UNDEFINED ) ) + { +#ifdef _X360 + if ( m_pFooter ) + { + m_pFooter->ShowButtonLabel( "cancel", true ); + } +#else + SetCancelButtonVisible( true ); + + if ( TFGameRules() && TFGameRules()->IsInHighlanderMode() ) + { + SetVisibleButton( "ResetButton", true ); + } + else + { + SetVisibleButton( "ResetButton", false ); + } +#endif + } + else + { +#ifdef _X360 + if ( m_pFooter ) + { + m_pFooter->ShowButtonLabel( "cancel", false ); + } +#else + SetCancelButtonVisible( false ); + SetVisibleButton( "ResetButton", false ); +#endif + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Panel *CTFClassMenu::CreateControlByName( const char *controlName ) +{ + if ( !Q_stricmp( "CIconPanel", controlName ) ) + { + return new CIconPanel( this, "icon_panel" ); + } + else + { + return BaseClass::CreateControlByName( controlName ); + } +} + +//----------------------------------------------------------------------------- +// Catch the mouseover event and set the active class +//----------------------------------------------------------------------------- +void CTFClassMenu::OnShowPage( vgui::Panel *panel, const char *pagename ) +{ + for ( int i = 0; i < TF_CLASS_MENU_BUTTONS; i++ ) + { + if (m_pClassButtons[i] == panel ) + { +// SelectClass( i ); + break; + } + } +} + +//----------------------------------------------------------------------------- +// Draw nothing +//----------------------------------------------------------------------------- +void CTFClassMenu::PaintBackground( void ) +{ +} + +//----------------------------------------------------------------------------- +// Do things that should be done often, eg number of players in the +// selected class +//----------------------------------------------------------------------------- +void CTFClassMenu::OnTick( void ) +{ + //When a player changes teams, their class and team values don't get here + //necessarily before the command to update the class menu. This leads to the cancel button + //being visible and people cancelling before they have a class. check for class == TF_CLASS_UNDEFINED and if so + //hide the cancel button + + if ( !IsVisible() ) + return; + +#ifndef _X360 + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + + // Force them to pick a class if they haven't picked one yet. + if ( pLocalPlayer && pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex() == TF_CLASS_UNDEFINED ) + { + SetCancelButtonVisible( false ); + SetVisibleButton( "ResetButton", false ); + } + + UpdateClassCounts(); + +#endif + + BaseClass::OnTick(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClassMenu::OnClose() +{ + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pLocalPlayer ) + { + // Clear the HIDEHUD_HEALTH bit we hackily added. Turns out prediction + // was restoring these bits every frame. Unfortunately, prediction + // is off for karts which means the spell hud item would disappear if you + // brought up this menu and returned. + pLocalPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_HEALTH; + } + + ShowPanel( false ); + + BaseClass::OnClose(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClassMenu::SetVisible( bool state ) +{ + BaseClass::SetVisible( state ); + + m_KeyRepeat.Reset(); + + if ( state ) + { + engine->ServerCmd( "menuopen" ); // to the server + engine->ClientCmd( "_cl_classmenuopen 1" ); // for other panels + CBroadcastRecipientFilter filter; + + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) + { + CBaseEntity::EmitSound( filter, SOUND_FROM_UI_PANEL, "music.mvm_class_menu" ); + } + else + { + CBaseEntity::EmitSound( filter, SOUND_FROM_UI_PANEL, "music.class_menu" ); + } + + CheckMvMUpgrades(); + } + else + { + engine->ServerCmd( "menuclosed" ); + engine->ClientCmd( "_cl_classmenuopen 0" ); + + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) + { + CBaseEntity::StopSound( SOUND_FROM_UI_PANEL, "music.mvm_class_menu" ); + } + else + { + CBaseEntity::StopSound( SOUND_FROM_UI_PANEL, "music.class_menu" ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClassMenu::Go() +{ + const int iClass = m_iCurrentClassIndex; + if ( iClass == TF_CLASS_UNDEFINED ) + return; + + // Check class limits + if ( TFGameRules() && !TFGameRules()->CanPlayerChooseClass( C_TFPlayer::GetLocalTFPlayer(), iClass ) ) + return; + +#if defined( REPLAY_ENABLED ) + // Display replay recording message if appropriate + int &nDisplayedConnectedRecording = CPlayerSpawnCache::Instance().m_Data.m_nDisplayedConnectedRecording; + if ( g_pReplay->IsReplayEnabled() && + !g_pEngineClientReplay->IsPlayingReplayDemo() && // FIXME: We shouldn't need this here but for some reason the engine thinks a replay is recording during demo playback, even though replay_recording has a FCVAR_DONTRECORD flag + !nDisplayedConnectedRecording && + replay_replaywelcomedlgcount.GetInt() <= MAX_TIMES_TO_SHOW_REPLAY_WELCOME_DLG ) + { + wchar_t wText[256]; + wchar wKeyBind[80]; + char szText[256]; + + const char *pSaveReplayKey = engine->Key_LookupBinding( "save_replay" ); + if ( !pSaveReplayKey ) + { + pSaveReplayKey = "< not bound >"; + } + g_pVGuiLocalize->ConvertANSIToUnicode( pSaveReplayKey, wKeyBind, sizeof( wKeyBind ) ); + g_pVGuiLocalize->ConstructString_safe( wText, g_pVGuiLocalize->Find( "#Replay_ConnectRecording" ), 1, wKeyBind ); + g_pVGuiLocalize->ConvertUnicodeToANSI( wText, szText, sizeof( szText ) ); + + extern ConVar replay_msgduration_connectrecording; + g_pClientMode->DisplayReplayMessage( szText, replay_msgduration_connectrecording.GetFloat(), false, NULL, true ); + + // Don't execute this clause next time the player spawns, unless the cache has been cleared + ++nDisplayedConnectedRecording; + + // Increment (archives) + replay_replaywelcomedlgcount.SetValue( replay_replaywelcomedlgcount.GetInt() + 1 ); + } +#endif + + // This will complete any pending replay, commit if necessary, and clear - this way when the player respawns + // we will start with a fresh replay for the new life. + g_pClientReplayContext->OnPlayerClassChanged(); + + // Change class + BaseClass::OnCommand( CFmtStr( "joinclass %s", g_aRawPlayerClassNames[ iClass ] ).Access() ); + + CBroadcastRecipientFilter filter; + + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) + { + CBaseEntity::EmitSound( filter, SOUND_FROM_UI_PANEL, "music.mvm_class_select" ); + } + else + { + + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClassMenu::OnCommand( const char *command ) +{ + if ( !V_strnicmp( command, "select", 6 ) ) + { + const char *pClass = command + 6; + const int iClass = atoi( pClass ); + + // Avoid restarting the animation if the user selected on the same class + if ( iClass != m_iCurrentClassIndex ) + { + SelectClass( iClass ); + } + else + { + // Ensure selection states, in case the user clicks a button and drags away + UpdateButtonSelectionStates( iClass ); + } + + Go(); + } + else if ( !V_strnicmp( command, "resetclass", 10 ) ) + { + if ( TFGameRules() && !TFGameRules()->IsInHighlanderMode() ) + return; + + engine->ClientCmd( const_cast<char *>( command ) ); + } + else if ( !V_strnicmp( command, "openloadout", 11 ) ) + { + // Let this panel know when you've closed, so we can reload items + EconUI()->AddPanelCloseListener( this ); + + // Make the back button close, rather than go back to the econ root panel + EconUI()->SetClosePanel( -m_iCurrentClassIndex ); + + // Set team number, so the model's color will match + EconUI()->SetDefaultTeam( GetTeamNumber() ); + + // Go directly to the loadout for the selected class + EconUI()->OpenEconUI( -m_iCurrentClassIndex ); + } + else + { + BaseClass::OnCommand( command ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Console command to select a class +//----------------------------------------------------------------------------- +void CTFClassMenu::Join_Class( const CCommand &args ) +{ + if ( args.ArgC() > 1 ) + { + char cmd[256]; + Q_snprintf( cmd, sizeof( cmd ), "joinclass %s", args.Arg( 1 ) ); + OnCommand( cmd ); + ShowPanel( false ); + } +} + +static const char *g_sDialogVariables[] = { + "", + "numScout", + "numSoldier", + "numPyro", + + "numDemoman", + "numHeavy", + "numEngineer", + + "numMedic", + "numSniper", + "numSpy", + "", +}; + +static const char *g_sClassImagesBlue[] = { + "", + "class_sel_sm_scout_blu", + "class_sel_sm_soldier_blu", + "class_sel_sm_pyro_blu", + + "class_sel_sm_demo_blu", + "class_sel_sm_heavy_blu", + "class_sel_sm_engineer_blu", + + "class_sel_sm_medic_blu", + "class_sel_sm_sniper_blu", + "class_sel_sm_spy_blu", + + "class_sel_sm_scout_blu", +}; + +static const char *g_sClassImagesRed[] = { + "", + "class_sel_sm_scout_red", + "class_sel_sm_soldier_red", + "class_sel_sm_pyro_red", + + "class_sel_sm_demo_red", + "class_sel_sm_heavy_red", + "class_sel_sm_engineer_red", + + "class_sel_sm_medic_red", + "class_sel_sm_sniper_red", + "class_sel_sm_spy_red", + + "class_sel_sm_scout_red", +}; + +int g_ClassDefinesRemap[] = { + 0, + TF_CLASS_SCOUT, + TF_CLASS_SOLDIER, + TF_CLASS_PYRO, + + TF_CLASS_DEMOMAN, + TF_CLASS_HEAVYWEAPONS, + TF_CLASS_ENGINEER, + + TF_CLASS_MEDIC, + TF_CLASS_SNIPER, + TF_CLASS_SPY, + TF_CLASS_CIVILIAN, +}; + +void CTFClassMenu::UpdateNumClassLabels( int iTeam ) +{ +#ifndef _X360 + int nTotalCount = 0; + + // count how many of each class there are + if ( !g_TF_PR ) + return; + + if ( iTeam < FIRST_GAME_TEAM || iTeam >= TF_TEAM_COUNT ) // invalid team number + return; + + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + + bool bSpectator = pLocalPlayer && pLocalPlayer->GetTeamNumber() == TEAM_SPECTATOR; + + int iLocalPlayerClass = TF_CLASS_UNDEFINED; + if ( pLocalPlayer ) + { + iLocalPlayerClass = pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex(); + } + + if ( iLocalPlayerClass == TF_CLASS_UNDEFINED ) + { + m_iLocalPlayerClass = iLocalPlayerClass; + + if ( m_pLocalPlayerImage && m_pLocalPlayerImage->IsVisible() ) + { + m_pLocalPlayerImage->SetVisible( false ); + } + + if ( m_pLocalPlayerBG && m_pLocalPlayerBG->IsVisible() ) + { + m_pLocalPlayerBG->SetVisible( false ); + } + } + + for( int i = TF_FIRST_NORMAL_CLASS ; i <= TF_LAST_NORMAL_CLASS ; i++ ) + { + if ( bSpectator == true ) + { + SetDialogVariable( g_sDialogVariables[i], "" ); + continue; + } + + int classCount = g_TF_PR->GetCountForPlayerClass( iTeam, g_ClassDefinesRemap[i], false ); + int iClassLimit = TFGameRules()->GetClassLimit( g_ClassDefinesRemap[i] ); + + if ( iClassLimit != NO_CLASS_LIMIT ) + { + if ( classCount >= iClassLimit ) + { + if ( classCount > 0 ) + { + wchar_t wTemp[32]; + wchar_t wzCount[10]; + _snwprintf( wzCount, ARRAYSIZE( wzCount ), L"%d", classCount ); + g_pVGuiLocalize->ConstructString_safe( wTemp, g_pVGuiLocalize->Find("TF_ClassLimitHit"), 1, wzCount ); + SetDialogVariable( g_sDialogVariables[i], wTemp ); + } + else + { + SetDialogVariable( g_sDialogVariables[i], g_pVGuiLocalize->Find("TF_ClassLimitHit_None") ); + } + } + else + { + wchar_t wTemp[32]; + wchar_t wzCount[10]; + _snwprintf( wzCount, ARRAYSIZE( wzCount ), L"%d", classCount ); + wchar_t wzMax[10]; + _snwprintf( wzMax, ARRAYSIZE( wzMax ), L"%d", iClassLimit ); + g_pVGuiLocalize->ConstructString_safe( wTemp, g_pVGuiLocalize->Find("TF_ClassLimitUnder"), 2, wzCount, wzMax ); + SetDialogVariable( g_sDialogVariables[i], wTemp ); + } + } + else if ( classCount > 0 ) + { + SetDialogVariable( g_sDialogVariables[i], classCount ); + } + else + { + SetDialogVariable( g_sDialogVariables[i], "" ); + } + + if ( g_ClassDefinesRemap[i] == iLocalPlayerClass ) + { + // take 1 off the count for the images since the local player has their own image already + if ( classCount > 0 ) + { + classCount--; + } + + if ( m_pLocalPlayerImage ) + { + if ( !m_pLocalPlayerImage->IsVisible() ) + { + m_pLocalPlayerImage->SetVisible( true ); + } + + if ( m_iLocalPlayerClass != iLocalPlayerClass ) + { + m_iLocalPlayerClass = iLocalPlayerClass; + m_pLocalPlayerImage->SetImage( iTeam == TF_TEAM_BLUE ? g_sClassImagesBlue[i] : g_sClassImagesRed[i] ); + } + } + + if ( m_pLocalPlayerBG && !m_pLocalPlayerBG->IsVisible() ) + { + m_pLocalPlayerBG->SetVisible( true ); + } + } + + if ( nTotalCount < CLASS_COUNT_IMAGES ) + { + for ( int j = 0 ; j < classCount ; ++j ) + { + CTFImagePanel *pImage = m_ClassCountImages[nTotalCount]; + if ( pImage ) + { + pImage->SetVisible( true ); + pImage->SetImage( iTeam == TF_TEAM_BLUE ? g_sClassImagesBlue[i] : g_sClassImagesRed[i] ); + } + + nTotalCount++; + if ( nTotalCount >= CLASS_COUNT_IMAGES ) + { + break; + } + } + } + } + + if ( nTotalCount == 0 ) + { + // no classes for our team yet + if ( m_pCountLabel && m_pCountLabel->IsVisible() ) + { + m_pCountLabel->SetVisible( false ); + } + } + else + { + if ( m_pCountLabel && !m_pCountLabel->IsVisible() ) + { + m_pCountLabel->SetVisible( true ); + } + } + + // turn off any unused images + while ( nTotalCount < CLASS_COUNT_IMAGES ) + { + CTFImagePanel *pImage = m_ClassCountImages[nTotalCount]; + if ( pImage ) + { + pImage->SetVisible( false ); + } + + nTotalCount++; + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClassMenu::FireGameEvent( IGameEvent *event ) +{ + const char *pszEventName = event->GetName(); + + // when we are changing levels + if ( FStrEq( pszEventName, "localplayer_changeteam" ) ) + { + if ( IsVisible() ) + { + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pLocalPlayer ) + { + int iTeam = pLocalPlayer->GetTeamNumber(); + if ( iTeam != GetTeamNumber() ) + { + ShowPanel( false ); + + if ( iTeam == TF_TEAM_BLUE ) + { + gViewPortInterface->ShowPanel( PANEL_CLASS_BLUE, true ); + } + else + { + gViewPortInterface->ShowPanel( PANEL_CLASS_RED, true ); + } + } + } + } + } + else if ( FStrEq( pszEventName, "show_match_summary" ) ) + { + if ( IsVisible() ) + { + ShowPanel( false ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClassMenu::OnEconUIClosed() +{ + // Reload items on model panel, in case anything's changed + LoadItems(); +} + +int g_nNumUpgradeIconsForLastHint = 0; + +//----------------------------------------------------------------------------- +void CTFClassMenu::CheckMvMUpgrades() +{ + // Set MvM Icons invisible + for ( int icons = 0; icons < ARRAYSIZE( m_pMvmUpgradeImages ); ++icons ) + { + if ( m_pMvmUpgradeImages[icons] == NULL ) + continue; + m_pMvmUpgradeImages[icons]->SetVisible( false ); + } + + if ( !TFGameRules() || !TFGameRules()->IsMannVsMachineMode() ) + return; + + CMannVsMachineStats *pStats = MannVsMachineStats_GetInstance(); + if ( !pStats ) + return; + + CUtlVector< CUpgradeInfo > *upgrades = pStats->GetLocalPlayerUpgrades(); + + int nShowUpgradingHint = -1; + int nNumUpgradeIconsForHint = 0; + + for ( int i = 0; i < upgrades->Count(); ++i ) + { + vgui::Panel *pUpgradeImage = m_pMvmUpgradeImages[upgrades->Element(i).m_iPlayerClass]; + + if ( !pUpgradeImage ) + continue; + + if ( !pUpgradeImage->IsVisible() ) + { + pUpgradeImage->SetVisible( true ); + nNumUpgradeIconsForHint++; + + // Only show the hint if we've shown it 3 or less times ever + if ( nShowUpgradingHint == -1 && tf_mvm_classupgradehelpcount.GetInt() < 3 ) + { + int nY; + pUpgradeImage->GetPos( nShowUpgradingHint, nY ); + nShowUpgradingHint += pUpgradeImage->GetWide() / 2; + } + } + } + + // Only show the hint if there are more upgrade icon than the last time we openned the menu + if ( nShowUpgradingHint != -1 && g_nNumUpgradeIconsForLastHint < nNumUpgradeIconsForHint ) + { + CExplanationPopup *pPopup = dynamic_cast< CExplanationPopup* >( FindChildByName("StartExplanation") ); + if ( pPopup ) + { + pPopup->SetCalloutInParentsX( nShowUpgradingHint ); + pPopup->Popup(); + + g_nNumUpgradeIconsForLastHint = nNumUpgradeIconsForHint; + tf_mvm_classupgradehelpcount.SetValue( tf_mvm_classupgradehelpcount.GetInt() + 1 ); + } + } +} + + diff --git a/game/client/tf/vgui/tf_classmenu.h b/game/client/tf/vgui/tf_classmenu.h new file mode 100644 index 0000000..8d908da --- /dev/null +++ b/game/client/tf/vgui/tf_classmenu.h @@ -0,0 +1,177 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_CLASSMENU_H +#define TF_CLASSMENU_H +#ifdef _WIN32 +#pragma once +#endif + +#include <classmenu.h> +#include <vgui_controls/EditablePanel.h> +#include "vgui_controls/KeyRepeat.h" +#include <filesystem.h> +#include <tf_shareddefs.h> +#include "cbase.h" +#include "tf_controls.h" +#include "tf_gamerules.h" +#include "basemodelpanel.h" +#include "IconPanel.h" +#include <vgui_controls/CheckButton.h> +#include "GameEventListener.h" +#include "c_tf_playerresource.h" +#include "tf_playermodelpanel.h" +#include "tf_mann_vs_machine_stats.h" + +using namespace vgui; + +#define CLASS_COUNT_IMAGES 11 + +class CTFClassTipsPanel; + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +class CTFClassTipsItemPanel : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CTFClassTipsItemPanel, vgui::EditablePanel ); + +public: + CTFClassTipsItemPanel( Panel *parent, const char *pszName, int iListItemID ); + ~CTFClassTipsItemPanel(); + + void SetClassTip( const wchar_t *pwszText, const char *pszIcon ); + virtual void ApplySchemeSettings( IScheme *pScheme ); + +private: + vgui::ImagePanel *m_pTipIcon; + CExLabel *m_pTipLabel; +}; + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +class CTFClassMenu : public CClassMenu, public CGameEventListener +{ +private: + DECLARE_CLASS_SIMPLE( CTFClassMenu, CClassMenu ); + +public: + CTFClassMenu( IViewPort *pViewPort ); + + virtual void Update( void ); + virtual Panel *CreateControlByName( const char *controlName ); + virtual void OnTick( void ); + virtual void PaintBackground( void ); + virtual void SetVisible( bool state ); + virtual void PerformLayout(); + + MESSAGE_FUNC_PTR_CHARPTR( OnShowPage, "ShowPage", panel, page ); + CON_COMMAND_MEMBER_F( CTFClassMenu, "join_class", Join_Class, "Send a joinclass command", 0 ); + + virtual void OnCommand( const char *command ); + virtual void OnClose(); + virtual void ShowPanel( bool bShow ); + virtual void UpdateClassCounts( void ){} + void SelectClass( int iClass ); + + virtual int GetTeamNumber( void ) = 0; + + // IGameEventListener interface: + virtual void FireGameEvent( IGameEvent *event ); + + MESSAGE_FUNC( OnEconUIClosed, "EconUIClosed" ); // If the econ UI was opened (for editing loadout), we'll get notified when the user's done. + + virtual GameActionSet_t GetPreferredActionSet() { return GAME_ACTION_SET_IN_GAME_HUD; } + +protected: + virtual void ApplySchemeSettings( IScheme *pScheme ); + virtual void OnKeyCodePressed( KeyCode code ); + CExImageButton *GetCurrentClassButton(); + virtual void OnKeyCodeReleased( vgui::KeyCode code ); + virtual void OnThink(); + virtual void UpdateNumClassLabels( int iTeam ); + + void UpdateButtonSelectionStates( int iClass ); + void SetCancelButtonVisible( bool bVisible ); + int GetCurrentPlayerClass(); + void LoadItems(); + void Go(); + +protected: + + CExImageButton *m_pClassButtons[TF_CLASS_MENU_BUTTONS]; + vgui::ImagePanel *m_pMvmUpgradeImages[TF_CLASS_MENU_BUTTONS]; + CSCHintIcon *m_pClassHintIcons[TF_CLASS_MENU_BUTTONS]; + + CTFClassTipsPanel *m_pClassTipsPanel; + CTFPlayerModelPanel *m_pTFPlayerModelPanel; + CExButton *m_pEditLoadoutButton; + CExLabel *m_pSelectAClassLabel; + CExplanationPopup *m_pClassHighlightPanel; + CSCHintIcon *m_pEditLoadoutHintIcon; + CSCHintIcon *m_pCancelHintIcon; + +private: + + void CheckMvMUpgrades(); + +#ifdef _X360 + CTFFooter *m_pFooter; +#endif + + ButtonCode_t m_iClassMenuKey; + int m_iCurrentClassIndex; + vgui::CKeyRepeatHandler m_KeyRepeat; + + int m_nBaseMusicGuid; + +#ifndef _X360 + CTFImagePanel *m_ClassCountImages[CLASS_COUNT_IMAGES]; + CExLabel *m_pCountLabel; + CTFImagePanel *m_pLocalPlayerImage; + CTFImagePanel *m_pLocalPlayerBG; + int m_iLocalPlayerClass; +#endif +}; + +//----------------------------------------------------------------------------- +// Purpose: Draws the blue class menu +//----------------------------------------------------------------------------- + +class CTFClassMenu_Blue : public CTFClassMenu +{ +private: + DECLARE_CLASS_SIMPLE( CTFClassMenu_Blue, CTFClassMenu ); + +public: + CTFClassMenu_Blue( IViewPort *pViewPort ) : BaseClass( pViewPort ) {} + + virtual const char *GetName( void ) { return PANEL_CLASS_BLUE; } + virtual int GetTeamNumber( void ) { return TF_TEAM_BLUE; } + virtual void UpdateClassCounts( void ){ UpdateNumClassLabels( TF_TEAM_BLUE ); } +}; + +//----------------------------------------------------------------------------- +// Purpose: Draws the red class menu +//----------------------------------------------------------------------------- + +class CTFClassMenu_Red : public CTFClassMenu +{ +private: + DECLARE_CLASS_SIMPLE( CTFClassMenu_Red, CTFClassMenu ); + +public: + CTFClassMenu_Red( IViewPort *pViewPort ) : BaseClass( pViewPort ) {} + + virtual const char *GetName( void ) { return PANEL_CLASS_RED; } + virtual int GetTeamNumber( void ) { return TF_TEAM_RED; } + virtual void UpdateClassCounts( void ){ UpdateNumClassLabels( TF_TEAM_RED ); } +}; + +#endif // TF_CLASSMENU_H + diff --git a/game/client/tf/vgui/tf_clientscoreboard.cpp b/game/client/tf/vgui/tf_clientscoreboard.cpp new file mode 100644 index 0000000..2cbdfe0 --- /dev/null +++ b/game/client/tf/vgui/tf_clientscoreboard.cpp @@ -0,0 +1,2424 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include <tier1/fmtstr.h> +#include <vgui_controls/Label.h> +#include <vgui_controls/Button.h> +#include <vgui_controls/ImagePanel.h> +#include <vgui_controls/RichText.h> +#include <vgui_controls/Frame.h> +#include <vgui/IScheme.h> +#include <vgui/ILocalize.h> +#include <vgui/ISurface.h> +#include <vgui/IVGui.h> +#include <vgui_controls/SectionedListPanel.h> +#include <vgui_controls/ImageList.h> +#include <game/client/iviewport.h> +#include <KeyValues.h> +#include <filesystem.h> +#include "IGameUIFuncs.h" // for key bindings +#include "VGuiMatSurface/IMatSystemSurface.h" + +#include "tf_controls.h" +#include "tf_shareddefs.h" +#include "tf_playermodelpanel.h" +#include "tf_clientscoreboard.h" +#include "c_playerresource.h" +#include "c_tf_playerresource.h" +#include "c_tf_team.h" +#include "c_tf_player.h" +#include "vgui_avatarimage.h" +#include "tf_gamerules.h" +#include "econ_item_description.h" +#include "vgui_controls/MenuItem.h" +#include "vgui/IInput.h" +#include "voice_status.h" +#include "vgui_controls/ScrollBarSlider.h" +#include "econ/econ_trading.h" +#include "in_buttons.h" +#include "tf_mapinfo.h" + +#if defined ( _X360 ) +#include "engine/imatchmaking.h" +#endif + +#if defined( REPLAY_ENABLED ) +#include "replay/ireplaysystem.h" +#include "replay/ienginereplay.h" +#endif + +#include "econ_item_system.h" +#include "tf_mann_vs_machine_stats.h" +#include "player_vs_environment/c_tf_upgrades.h" +#include "tf_badge_panel.h" +#include "report_player_dialog.h" + +using namespace vgui; + +#define SCOREBOARD_MAX_LIST_ENTRIES 12 + +ConVar tf_scoreboard_mouse_mode( "tf_scoreboard_mouse_mode", "0", FCVAR_ARCHIVE ); + + +void cc_scoreboard_convar_changed( IConVar *pConVar, const char *pOldString, float flOldValue ) +{ + CTFClientScoreBoardDialog *pScoreboard = dynamic_cast< CTFClientScoreBoardDialog *>( gViewPortInterface->FindPanelByName( PANEL_SCOREBOARD ) ); + if ( pScoreboard ) + { + pScoreboard->Reset(); + } +} +ConVar tf_scoreboard_ping_as_text( "tf_scoreboard_ping_as_text", "0", FCVAR_ARCHIVE, "Show ping values as text in the scoreboard.", cc_scoreboard_convar_changed ); +ConVar tf_scoreboard_alt_class_icons( "tf_scoreboard_alt_class_icons", "0", FCVAR_ARCHIVE, "Show alternate class icons in the scoreboard." ); + +extern bool IsInCommentaryMode( void ); +extern bool DuelMiniGame_GetStats( C_TFPlayer **ppPlayer, uint32 &unMyScore, uint32 &unOpponentScore ); +extern void AddSubKeyNamed( KeyValues *pKeys, const char *pszName ); + +extern ConVar cl_hud_playerclass_use_playermodel; +extern ConVar mp_tournament; + +//...................................................... +enum +{ + PING_LOW, + PING_MED, + PING_HIGH, + PING_VERY_HIGH, + PING_BOT_RED, + PING_BOT_BLUE, + + PING_MAX_TYPES +}; +static const char *pszPingIcons[SCOREBOARD_PING_ICONS] = { + "../hud/scoreboard_ping_low", + "../hud/scoreboard_ping_med", + "../hud/scoreboard_ping_high", + "../hud/scoreboard_ping_very_high", + "../hud/scoreboard_ping_bot_red", + "../hud/scoreboard_ping_bot_blue", +}; +static const char *pszPingIconsDead[SCOREBOARD_PING_ICONS] = { + "../hud/scoreboard_ping_low_d", + "../hud/scoreboard_ping_med_d", + "../hud/scoreboard_ping_high_d", + "../hud/scoreboard_ping_very_high_d", + "../hud/scoreboard_ping_bot_red_d", + "../hud/scoreboard_ping_bot_blue_d", +}; +COMPILE_TIME_ASSERT( SCOREBOARD_PING_ICONS == PING_MAX_TYPES ); +//...................................................... + +static const char *pszDominationIcons[SCOREBOARD_DOMINATION_ICONS] = { + "", + "../hud/leaderboard_dom1", + "../hud/leaderboard_dom2", + "../hud/leaderboard_dom3", + "../hud/leaderboard_dom4", + "../hud/leaderboard_dom5", + "../hud/leaderboard_dom6", + "../hud/leaderboard_dom7", + "../hud/leaderboard_dom8", + "../hud/leaderboard_dom9", + "../hud/leaderboard_dom10", + "../hud/leaderboard_dom11", + "../hud/leaderboard_dom12", + "../hud/leaderboard_dom13", + "../hud/leaderboard_dom14", + "../hud/leaderboard_dom15", + "../hud/leaderboard_dom16", +}; + +static const char *pszDominationIconsDead[SCOREBOARD_DOMINATION_ICONS] = { + "", + "../hud/leaderboard_dom1_d", + "../hud/leaderboard_dom2_d", + "../hud/leaderboard_dom3_d", + "../hud/leaderboard_dom4_d", + "../hud/leaderboard_dom5_d", + "../hud/leaderboard_dom6_d", + "../hud/leaderboard_dom7_d", + "../hud/leaderboard_dom8_d", + "../hud/leaderboard_dom9_d", + "../hud/leaderboard_dom10_d", + "../hud/leaderboard_dom11_d", + "../hud/leaderboard_dom12_d", + "../hud/leaderboard_dom13_d", + "../hud/leaderboard_dom14_d", + "../hud/leaderboard_dom15_d", + "../hud/leaderboard_dom16_d", +}; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CTFClientScoreBoardDialog::CTFClientScoreBoardDialog( IViewPort *pViewPort ) : CClientScoreBoardDialog( pViewPort ) +{ + SetProportional( true ); + SetKeyBoardInputEnabled( false ); + SetScheme( "ClientScheme" ); + + m_pPlayerListBlue = new SectionedListPanel( this, "BluePlayerList" ); + m_pPlayerListRed = new SectionedListPanel( this, "RedPlayerList" ); + m_pLabelPlayerName = new CExLabel( this, "PlayerNameLabel", "" ); + m_pImagePanelHorizLine = new ImagePanel( this, "HorizontalLine" ); + m_pClassImage = new CTFClassImage( this, "ClassImage" ); + m_pPlayerModelPanel = new CTFPlayerModelPanel( this, "classmodelpanel" ); + m_pLocalPlayerStatsPanel = new vgui::EditablePanel( this, "LocalPlayerStatsPanel" ); + m_pLocalPlayerDuelStatsPanel = new vgui::EditablePanel( this, "LocalPlayerDuelStatsPanel" ); + m_duelPanelLocalPlayer.m_pPanel = new vgui::EditablePanel( m_pLocalPlayerDuelStatsPanel, "LocalPlayerData" ); + m_duelPanelOpponent.m_pPanel = new vgui::EditablePanel( m_pLocalPlayerDuelStatsPanel, "OpponentData" ); + m_pRedTeamName = new CExLabel( this, "RedTeamLabel", "" ); + m_pBlueTeamName = new CExLabel( this, "BlueTeamLabel", "" ); + m_pRedLeaderAvatarImage = new CAvatarImagePanel( this, "RedLeaderAvatar" ); + m_pRedLeaderAvatarBG = new EditablePanel( this, "RedLeaderAvatarBG" ); + m_pRedTeamImage = new ImagePanel( this, "RedTeamImage" ); + m_pBlueLeaderAvatarImage = new CAvatarImagePanel( this, "BlueLeaderAvatar" ); + m_pBlueLeaderAvatarBG = new EditablePanel( this, "BlueLeaderAvatarBG" ); + m_pBlueTeamImage = new ImagePanel( this, "BlueTeamImage" ); + + m_pServerTimeLeftValue = NULL; + m_pFontTimeLeftNumbers = vgui::INVALID_FONT; + m_pFontTimeLeftString = vgui::INVALID_FONT; + + m_pMvMScoreboard = new CTFHudMannVsMachineScoreboard( this, "MvMScoreboard" ); + m_pRightClickMenu = NULL; + + m_iImageDominated = 0; + m_iImageDominatedDead = 0; + m_iImageNemesis = 0; + m_iImageNemesisDead = 0; + m_iImageStreak = 0; + m_iImageStreakDead = 0; + m_iTextureCamera = -1; + + m_bIsPVEMode = false; +// m_bDisplayLevel = false; + m_bMouseActivated = true; + + Q_memset( m_iImageClass, NULL, sizeof( m_iImageClass ) ); + Q_memset( m_iImageClassAlt, NULL, sizeof( m_iImageClassAlt ) ); + Q_memset( m_iImageDom, NULL, sizeof( m_iImageDom ) ); + Q_memset( m_iImageDomDead, NULL, sizeof( m_iImageDomDead ) ); + + ListenForGameEvent( "server_spawn" ); +#ifdef STAGING_ONLY +// ListenForGameEvent( "bountymode_toggled" ); +#endif + + SetDialogVariable( "server", "" ); + SetVisible( false ); + + m_nPlayerModelPanelIndex = -1; + m_bRedScrollBarVisible = false; + m_bBlueScrollBarVisible = false; + m_nExtraSpace = 0; + m_hSelectedPlayer = NULL; + m_bUsePlayerModel = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CTFClientScoreBoardDialog::~CTFClientScoreBoardDialog() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::PerformLayout() +{ + BaseClass::PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::UpdatePlayerModel() +{ + if ( !m_pPlayerModelPanel->IsVisible() ) + return; + + if ( m_nPlayerModelPanelIndex == -1 ) + { + m_nPlayerModelPanelIndex = GetLocalPlayerIndex(); + } + + C_TFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( m_nPlayerModelPanelIndex ) ); + if ( !pPlayer ) + return; + + int nClass = pPlayer->GetPlayerClass()->GetClassIndex(); + int nTeam = pPlayer->GetTeamNumber(); + int nItemSlot = ( pPlayer->IsAlive() && pPlayer->GetActiveTFWeapon() ) ? pPlayer->GetActiveTFWeapon()->GetAttributeContainer()->GetItem()->GetStaticData()->GetLoadoutSlot( nClass ) : LOADOUT_POSITION_PRIMARY;; + CEconItemView *pWeapon = NULL; + + CTFWeaponBase *pEnt = dynamic_cast<CTFWeaponBase*>( pPlayer->GetEntityForLoadoutSlot( nItemSlot ) ); + if ( pEnt ) + { + pWeapon = pEnt->GetAttributeContainer()->GetItem(); + } + + bool bIsRobot = false; + int iRobot = 0; + CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer, iRobot, appear_as_mvm_robot ); + bIsRobot = iRobot ? true : false; + + m_pPlayerModelPanel->ClearCarriedItems(); + m_pPlayerModelPanel->SetToPlayerClass( nClass, bIsRobot ); + m_pPlayerModelPanel->SetTeam( nTeam ); + + if ( pWeapon ) + { + m_pPlayerModelPanel->AddCarriedItem( pWeapon ); + } + + for ( int wbl = pPlayer->GetNumWearables() - 1; wbl >= 0; wbl-- ) + { + C_TFWearable *pItem = dynamic_cast<C_TFWearable*>( pPlayer->GetWearable( wbl ) ); + if ( !pItem ) + continue; + + if ( pItem->IsViewModelWearable() ) + continue; + + if ( pItem->IsDisguiseWearable() ) + continue; + + CAttributeContainer *pCont = pItem->GetAttributeContainer(); + CEconItemView *pEconItemView = pCont ? pCont->GetItem() : NULL; + + if ( pEconItemView && pEconItemView->IsValid() ) + { + m_pPlayerModelPanel->AddCarriedItem( pEconItemView ); + } + } + + m_pPlayerModelPanel->HoldItemInSlot( nItemSlot ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + KeyValues *pConditions = NULL; + + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) + { + pConditions = new KeyValues( "conditions" ); + AddSubKeyNamed( pConditions, "if_mvm" ); + } + + LoadControlSettings( "Resource/UI/Scoreboard.res", NULL, NULL, pConditions ); + m_hScoreFontDefault = pScheme->GetFont( "Default", true ); + m_hScoreFontSmallest = pScheme->GetFont( "ScoreboardSmallest", true ); + + if ( pConditions ) + { + pConditions->deleteThis(); + } + + if ( m_pImageList ) + { + m_iImageDominated = m_pImageList->AddImage( scheme()->GetImage( "../hud/leaderboard_dominated", true ) ); + m_iImageDominatedDead = m_pImageList->AddImage( scheme()->GetImage( "../hud/leaderboard_dominated_d", true ) ); + m_iImageNemesis = m_pImageList->AddImage( scheme()->GetImage( "../hud/leaderboard_nemesis", true ) ); + m_iImageNemesisDead = m_pImageList->AddImage( scheme()->GetImage( "../hud/leaderboard_nemesis_d", true ) ); + m_iImageStreak = m_pImageList->AddImage( scheme()->GetImage( "../hud/scoreboard_streak", true ) ); + m_iImageStreakDead = m_pImageList->AddImage( scheme()->GetImage( "../hud/scoreboard_streak_d", true ) ); + + for(int i = 1 ; i < SCOREBOARD_DOMINATION_ICONS ; i++) + { + m_iImageDom[i] = m_pImageList->AddImage( scheme()->GetImage( pszDominationIcons[i], true ) ); + m_iImageDomDead[i] = m_pImageList->AddImage( scheme()->GetImage( pszDominationIconsDead[i], true ) ); + } + for( int i = 1 ; i < SCOREBOARD_CLASS_ICONS ; i++ ) + { + m_iImageClass[i] = m_pImageList->AddImage( scheme()->GetImage( g_pszClassIcons[i], true ) ); + m_iImageClassAlt[i] = m_pImageList->AddImage( scheme()->GetImage( g_pszClassIconsAlt[i], true ) ); + } + for ( int i = 0; i < SCOREBOARD_PING_ICONS; i++ ) + { + m_iImagePing[i] = m_pImageList->AddImage( scheme()->GetImage( pszPingIcons[i], true ) ); + m_iImagePingDead[i] = m_pImageList->AddImage( scheme()->GetImage( pszPingIconsDead[i], true ) ); + } + + // resize the images to our resolution + for (int i = 1 ; i < m_pImageList->GetImageCount(); i++ ) + { + int wide = 13, tall = 13; + m_pImageList->GetImage(i)->SetSize(scheme()->GetProportionalScaledValueEx( GetScheme(), wide ), scheme()->GetProportionalScaledValueEx( GetScheme(),tall ) ); + } + } + + SetPlayerListImages( m_pPlayerListBlue ); + SetPlayerListImages( m_pPlayerListRed ); + + SetBgColor( Color( 0, 0, 0, 0) ); + SetBorder( NULL ); + SetVisible( false ); + + m_duelPanelLocalPlayer.m_pAvatar = dynamic_cast< CAvatarImagePanel *>( m_duelPanelLocalPlayer.m_pPanel->FindChildByName("AvatarImage") ); + m_duelPanelLocalPlayer.m_pPlayerNameLabel = dynamic_cast< CExLabel *>( m_duelPanelLocalPlayer.m_pPanel->FindChildByName("AvatarTextLabel") ); + m_duelPanelOpponent.m_pAvatar = dynamic_cast< CAvatarImagePanel *>( m_duelPanelOpponent.m_pPanel->FindChildByName("AvatarImage") ); + m_duelPanelOpponent.m_pPlayerNameLabel = dynamic_cast< CExLabel *>( m_duelPanelOpponent.m_pPanel->FindChildByName("AvatarTextLabel") ); + + if ( m_pLocalPlayerStatsPanel ) + { + m_pKillsLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Kills" ) ); + m_pDeathsLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Deaths" ) ); + m_pAssistLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Assists" ) ); + m_pDestructionLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Destruction" ) ); + m_pCapturesLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Captures" ) ); + m_pDefensesLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Defenses" ) ); + m_pDominationsLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Domination" ) ); + m_pRevengeLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Revenge" ) ); + m_pHealingLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Healing" ) ); + m_pInvulnsLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Invuln" ) ); + m_pTeleportsLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Teleports" ) ); + m_pHeadshotsLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Headshots" ) ); + m_pBackstabsLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Backstabs" ) ); + m_pBonusLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Bonus" ) ); + m_pSupportLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Support" ) ); + m_pDamageLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Damage" ) ); + } + + m_pServerTimeLeftValue = dynamic_cast< CExLabel* >( FindChildByName( "ServerTimeLeftValue" ) ); + m_pFontTimeLeftNumbers = pScheme->GetFont( "ScoreboardMediumSmall", true ); + m_pFontTimeLeftString = pScheme->GetFont( "ScoreboardVerySmall", true ); + + Reset(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::ShowPanel( bool bShow ) +{ + if ( bShow ) + { + if ( TFGameRules() && TFGameRules()->ShowMatchSummary() ) + return; + } + + // Catch the case where we call ShowPanel before ApplySchemeSettings, eg when + // going from windowed <-> fullscreen + if ( m_pImageList == NULL ) + { + InvalidateLayout( true, true ); + } + + bool bIsPVEMode = TFGameRules() && TFGameRules()->IsPVEModeActive(); + if ( m_bIsPVEMode != bIsPVEMode ) + { + m_bIsPVEMode = bIsPVEMode; + InvalidateLayout( true, true ); + } + + // Don't show in commentary mode + if ( IsInCommentaryMode() ) + { + bShow = false; + } + + if ( IsVisible() == bShow ) + { + return; + } + + int iRenderGroup = gHUD.LookupRenderGroupIndexByName( "global" ); + + if ( bShow ) + { + SetVisible( true ); + MoveToFront(); + InitializeInputScheme(); + + gHUD.LockRenderGroup( iRenderGroup ); + + // Clear the selected item, this forces the default to the local player + SectionedListPanel *pList = GetSelectedPlayerList(); + if ( pList ) + { + pList->ClearSelection(); + } + + if ( tf_scoreboard_mouse_mode.GetInt() == 2 ) + m_bMouseActivated = false; + + UpdateServerTimeLeft(); + + m_nPlayerModelPanelIndex = -1; + m_hSelectedPlayer = NULL; + UpdatePlayerModel(); + } + else + { + SetVisible( false ); + + if ( m_pRightClickMenu ) + { + delete m_pRightClickMenu; + m_pRightClickMenu = NULL; + } + + gHUD.UnlockRenderGroup( iRenderGroup ); + m_bMouseActivated = false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::OnCommand( const char *command ) +{ + if ( V_stristr( command, "report_player" ) ) + { + engine->ClientCmd( command ); + } + else if ( !V_strcmp( command, "muteplayer" ) ) + { + SectionedListPanel *pList = GetSelectedPlayerList(); + if ( pList ) + { + int iSelectedItem = pList->GetSelectedItem(); + if ( iSelectedItem >= 0 ) + { + KeyValues *pIssueKeyValues = pList->GetItemData( iSelectedItem ); + if ( !pIssueKeyValues ) + return; + + int playerIndex = pIssueKeyValues->GetInt( "playerIndex", 0 ); + if ( playerIndex > 0 && playerIndex <= MAX_PLAYERS && GetClientVoiceMgr() ) + { + // Toggle + GetClientVoiceMgr()->SetPlayerBlockedState( playerIndex, ( GetClientVoiceMgr()->IsPlayerBlocked( playerIndex ) ? false : true ) ); + } + } + } + } + else if ( !V_strcmp( command, "specplayer" ) ) + { + SectionedListPanel *pList = GetSelectedPlayerList(); + if ( pList ) + { + int iSelectedItem = pList->GetSelectedItem(); + if ( iSelectedItem >= 0 ) + { + KeyValues *pIssueKeyValues = pList->GetItemData( iSelectedItem ); + if ( !pIssueKeyValues ) + return; + + int playerIndex = pIssueKeyValues->GetInt( "playerIndex", 0 ); + if ( playerIndex > 0 && playerIndex <= MAX_PLAYERS ) + { + engine->ClientCmd_Unrestricted( VarArgs( "spec_player %d\n", playerIndex ) ); + } + } + } + } + else if ( !V_strcmp( command, "friendadd" ) ) + { + SectionedListPanel *pList = GetSelectedPlayerList(); + if ( pList ) + { + int iSelectedItem = pList->GetSelectedItem(); + if ( iSelectedItem >= 0 ) + { + KeyValues *pIssueKeyValues = pList->GetItemData( iSelectedItem ); + if ( !pIssueKeyValues ) + return; + + int playerIndex = pIssueKeyValues->GetInt( "playerIndex", 0 ); + CBasePlayer *pTarget = UTIL_PlayerByIndex( playerIndex ); + if ( pTarget && !( pTarget->IsBot() || pTarget->IsHLTV() ) ) + { + CSteamID steamID; + if ( pTarget->GetSteamID( &steamID ) && steamapicontext && steamapicontext->SteamFriends() ) + { + steamapicontext->SteamFriends()->ActivateGameOverlayToUser( "friendadd", steamID ); + } + } + } + } + } + else if ( !V_strcmp( command, "jointrade" ) ) + { + SectionedListPanel *pList = GetSelectedPlayerList(); + if ( pList ) + { + int iSelectedItem = pList->GetSelectedItem(); + if ( iSelectedItem >= 0 ) + { + KeyValues *pIssueKeyValues = pList->GetItemData( iSelectedItem ); + if ( !pIssueKeyValues ) + return; + + int playerIndex = pIssueKeyValues->GetInt( "playerIndex", 0 ); + CBasePlayer *pTarget = UTIL_PlayerByIndex( playerIndex ); + if ( pTarget && !( pTarget->IsBot() || pTarget->IsHLTV() ) ) + { + // Prevent large UI popup during a match + if ( pTarget->GetTeamNumber() >= FIRST_GAME_TEAM ) + { + if ( TFGameRules() && TFGameRules()->UsePlayerReadyStatusMode() && TFGameRules()->State_Get() == GR_STATE_RND_RUNNING ) + return; + } + + Trading_RequestTrade( playerIndex ); + } + } + } + } + else if ( !V_strcmp( command, "profile" ) ) + { + SectionedListPanel *pList = GetSelectedPlayerList(); + if ( pList ) + { + int iSelectedItem = pList->GetSelectedItem(); + if ( iSelectedItem >= 0 ) + { + KeyValues *pIssueKeyValues = pList->GetItemData( iSelectedItem ); + if ( !pIssueKeyValues ) + return; + + int playerIndex = pIssueKeyValues->GetInt( "playerIndex", 0 ); + CBasePlayer *pTarget = UTIL_PlayerByIndex( playerIndex ); + if ( pTarget && !( pTarget->IsBot() || pTarget->IsHLTV() ) ) + { + CSteamID steamID; + if ( pTarget->GetSteamID( &steamID ) && steamapicontext && steamapicontext->SteamFriends() ) + { + steamapicontext->SteamFriends()->ActivateGameOverlayToUser( "steamid", steamID ); + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::OnItemSelected( vgui::Panel *panel ) +{ + if ( panel == m_pPlayerListBlue || panel == m_pPlayerListRed ) + { + // There can be only one... selection + if ( panel == m_pPlayerListBlue && m_pPlayerListBlue->GetSelectedItem() >= 0 && m_pPlayerListRed->GetSelectedItem() >= 0 ) + { + m_pPlayerListRed->ClearSelection(); + } + else if ( panel == m_pPlayerListRed && m_pPlayerListRed->GetSelectedItem() >= 0 && m_pPlayerListBlue->GetSelectedItem() >= 0 ) + { + m_pPlayerListBlue->ClearSelection(); + } + + if ( vgui::input()->IsMouseDown( MOUSE_RIGHT ) ) + { + OnScoreBoardMouseRightRelease(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::OnItemContextMenu( vgui::Panel *panel ) +{ + if ( panel == m_pPlayerListBlue || panel == m_pPlayerListRed ) + { + if ( vgui::input()->IsMouseDown( MOUSE_RIGHT ) ) + { + OnScoreBoardMouseRightRelease(); + } + } +} + +void CTFClientScoreBoardDialog::OnReportPlayer( KeyValues *pData ) +{ + int playerIndex = pData->GetInt( "playerIndex" ); + int nReason = pData->GetInt( "reason" ); + CSteamID steamID = GetSteamIDForPlayerIndex( playerIndex ); + ReportPlayerAccount( steamID, nReason ); +} + +//----------------------------------------------------------------------------- +// Purpose: Figure out where to put the camera view +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::GetCameraUnderlayBounds( int *pX, int *pY, int *pWide, int *pTall ) +{ + GetBounds( *pX, *pY, *pWide, *pTall ); + + // inset these b*pY a bit + *pX += 15; + *pY += 25; + *pWide -= 30; + *pTall -= 40; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::OnScoreBoardMouseRightRelease( void ) +{ + if ( !IsVisible() ) + return; + + C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( !pLocalTFPlayer ) + return; + + SectionedListPanel *pList = GetSelectedPlayerList(); + if ( !pList ) + return; + + int iSelectedItem = pList->GetSelectedItem(); + if ( iSelectedItem < 0 ) + return; + + KeyValues *pIssueKeyValues = pList->GetItemData( iSelectedItem ); + if ( !pIssueKeyValues ) + return; + + int playerIndex = pIssueKeyValues->GetInt( "playerIndex", 0 ); + if ( GetLocalPlayerIndex() == playerIndex ) + return; + + if ( m_pRightClickMenu ) + delete m_pRightClickMenu; + + const char *pszContextMenuBorder = "DarkComboBoxBorder"; + const char *pszContextMenuFont = "HudFontMediumSecondary"; + m_pRightClickMenu = new Menu( pList, "RightClickMenu" ); + m_pRightClickMenu->SetKeyBoardInputEnabled( false ); + m_pRightClickMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) ); + m_pRightClickMenu->SetFgColor( Color( 0, 0, 255, 255 ) ); + m_pRightClickMenu->SetPaintBackgroundEnabled( true ); + m_pRightClickMenu->SetPaintBackgroundType( 0 ); + m_pRightClickMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) ); + + bool bFakeClient = ( g_TF_PR->IsFakePlayer( playerIndex ) ); + bool bTournamentGame = ( g_TF_PR->GetTeam( playerIndex ) >= FIRST_GAME_TEAM && TFGameRules() && TFGameRules()->UsePlayerReadyStatusMode() && TFGameRules()->State_Get() == GR_STATE_RND_RUNNING ); + + MenuBuilder contextMenuBuilder( m_pRightClickMenu, this ); + + // Report + if ( !bFakeClient && engine->GetLocalPlayer() != playerIndex ) + { + Menu *pReportSubMenu = new Menu( this, "ReportSubMenu" ); + pReportSubMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) ); + pReportSubMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) ); + contextMenuBuilder.AddCascadingMenuItem( "#TF_ScoreBoard_Context_Report", pReportSubMenu, "report" ); + for ( int iReason=CMsgGC_ReportPlayer_EReason_kReason_INVALID+1; iReason<CMsgGC_ReportPlayer_EReason_kReason_COUNT; ++iReason ) + { + CFmtStr name( "#TF_ScoreBoard_Context_Report_Reason%d", iReason ); + KeyValues *pReport = new KeyValues( "ReportPlayer" ); + pReport->SetInt( "playerIndex", playerIndex ); + pReport->SetInt( "reason", iReason ); + pReportSubMenu->AddMenuItem( name, pReport, this ); + } + } + + // Mute + if ( !bFakeClient && GetClientVoiceMgr() ) + { + const char *pszString = GetClientVoiceMgr()->IsPlayerBlocked( playerIndex ) ? "#TF_ScoreBoard_Context_UnMute" : "#TF_ScoreBoard_Context_Mute"; + contextMenuBuilder.AddMenuItem( pszString, "muteplayer", "mute" ); + } + + // Spectate + if ( g_TF_PR->IsAlive( playerIndex ) && ( !pLocalTFPlayer->IsAlive() || pLocalTFPlayer->GetTeamNumber() == TEAM_SPECTATOR ) ) + { + contextMenuBuilder.AddMenuItem( "#TF_ScoreBoard_Context_Spec", "specplayer", "spectate" ); + } + + // Add Friend + if ( !bFakeClient ) + { + CSteamID steamID; + C_TFPlayer *pTFTarget = ToTFPlayer( UTIL_PlayerByIndex( playerIndex ) ); + if ( pTFTarget && pTFTarget->GetSteamID( &steamID ) ) + { + if ( steamapicontext && steamapicontext->SteamFriends() && steamapicontext->SteamFriends()->GetFriendRelationship( steamID ) == k_EFriendRelationshipNone ) + { + contextMenuBuilder.AddMenuItem( "#TF_ScoreBoard_Context_Friend", "friendadd", "friend" ); + } + } + } + + // Trade + if ( !bFakeClient && !bTournamentGame ) + { + contextMenuBuilder.AddMenuItem( "#TF_ScoreBoard_Context_Trade", "jointrade", "trade" ); + } + + // Profile + if ( !bFakeClient ) + { + contextMenuBuilder.AddMenuItem( "#TF_ScoreBoard_Context_Profile", "profile", "profile" ); + } + + int x, y; + vgui::input()->GetCursorPosition( x, y ); + m_pRightClickMenu->SetPos( x, y ); + m_pRightClickMenu->SetVisible( true ); + m_pRightClickMenu->AddActionSignalTarget( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFClientScoreBoardDialog::UseMouseMode( void ) +{ + return tf_scoreboard_mouse_mode.GetInt() == 1 || ( tf_scoreboard_mouse_mode.GetInt() == 2 && m_bMouseActivated ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::InitializeInputScheme( void ) +{ + if ( !IsVisible() ) + return; + + if ( !m_pPlayerListBlue || !m_pPlayerListRed ) + return; + + MakePopup( false, UseMouseMode() ); + SetKeyBoardInputEnabled( false ); + SetMouseInputEnabled( UseMouseMode() ); + m_pPlayerListBlue->SetMouseInputEnabled( UseMouseMode() ); + m_pPlayerListBlue->AddActionSignalTarget( this ); + m_pPlayerListBlue->SetClickable( UseMouseMode() ); + m_pPlayerListRed->SetMouseInputEnabled( UseMouseMode() ); + m_pPlayerListRed->AddActionSignalTarget( this ); + m_pPlayerListRed->SetClickable( UseMouseMode() ); + + if ( m_pPlayerListBlue->GetScrollBar() && + m_pPlayerListBlue->GetScrollBar()->GetButton( 0 ) && + m_pPlayerListBlue->GetScrollBar()->GetButton( 1 ) && + m_pPlayerListBlue->GetScrollBar()->GetSlider() ) + { + m_pPlayerListBlue->SetVerticalScrollbar( UseMouseMode() ); + m_pPlayerListBlue->GetScrollBar()->GetButton( 0 )->SetMouseInputEnabled( UseMouseMode() ); + m_pPlayerListBlue->GetScrollBar()->GetButton( 1 )->SetMouseInputEnabled( UseMouseMode() ); + m_pPlayerListBlue->GetScrollBar()->GetSlider()->SetMouseInputEnabled( UseMouseMode() ); + m_pPlayerListBlue->GetScrollBar()->SetMouseInputEnabled( UseMouseMode() ); + } + + if ( m_pPlayerListRed->GetScrollBar() && + m_pPlayerListRed->GetScrollBar()->GetButton( 0 ) && + m_pPlayerListRed->GetScrollBar()->GetButton( 1 ) && + m_pPlayerListRed->GetScrollBar()->GetSlider() ) + { + m_pPlayerListRed->SetVerticalScrollbar( UseMouseMode() ); + m_pPlayerListRed->GetScrollBar()->SetMouseInputEnabled( UseMouseMode() ); + m_pPlayerListRed->GetScrollBar()->GetButton( 0 )->SetMouseInputEnabled( UseMouseMode() ); + m_pPlayerListRed->GetScrollBar()->GetButton( 1 )->SetMouseInputEnabled( UseMouseMode() ); + m_pPlayerListRed->GetScrollBar()->GetSlider()->SetMouseInputEnabled( UseMouseMode() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Resets the scoreboard panel +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::Reset() +{ + if ( !m_bIsPVEMode ) + { + InitPlayerList( m_pPlayerListBlue ); + InitPlayerList( m_pPlayerListRed ); + } + + m_pPlayerListBlue->SetVisible( !m_bIsPVEMode ); + m_pPlayerListRed->SetVisible( !m_bIsPVEMode ); +} + +//----------------------------------------------------------------------------- +// Purpose: Inits the player list in a list panel +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::InitPlayerList( SectionedListPanel *pPlayerList ) +{ + pPlayerList->SetVerticalScrollbar( UseMouseMode() ); + pPlayerList->RemoveAll(); + pPlayerList->RemoveAllSections(); + pPlayerList->AddSection( 0, "Players", TFPlayerSortFunc ); + pPlayerList->SetSectionAlwaysVisible( 0, true ); + pPlayerList->SetSectionFgColor( 0, Color( 255, 255, 255, 255 ) ); + pPlayerList->SetBgColor( Color( 0, 0, 0, 0 ) ); + pPlayerList->SetBorder( NULL ); + + pPlayerList->AddColumnToSection( 0, "medal", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_CENTER, m_iMedalWidth ); + + // Avatars are always displayed at 32x32 regardless of resolution + if ( ShowAvatars() ) + { + pPlayerList->AddColumnToSection( 0, "avatar", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_RIGHT, m_iAvatarWidth ); + pPlayerList->AddColumnToSection( 0, "spacer", "", 0, m_iSpacerWidth ); + } + + // the player avatar is always a fixed size, so as we change resolutions we need to vary the size of the name column to adjust the total width of all the columns + m_nExtraSpace = pPlayerList->GetWide() - m_iMedalWidth - m_iAvatarWidth - m_iSpacerWidth - m_iNameWidth - m_iKillstreakWidth - m_iKillstreakImageWidth - m_iNemesisWidth - m_iNemesisWidth - m_iScoreWidth - m_iClassWidth - m_iPingWidth - m_iSpacerWidth - ( 2 * SectionedListPanel::COLUMN_DATA_INDENT ); // the SectionedListPanel will indent the columns on either end by SectionedListPanel::COLUMN_DATA_INDENT + + pPlayerList->AddColumnToSection( 0, "name", "#TF_Scoreboard_Name", 0, m_iNameWidth + m_nExtraSpace ); + pPlayerList->AddColumnToSection( 0, "killstreak", "", SectionedListPanel::COLUMN_RIGHT, m_iKillstreakWidth ); + pPlayerList->AddColumnToSection( 0, "killstreak_image", "", SectionedListPanel::COLUMN_IMAGE, m_iKillstreakImageWidth ); + pPlayerList->AddColumnToSection( 0, "dominating", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_CENTER, m_iNemesisWidth ); + pPlayerList->AddColumnToSection( 0, "nemesis", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_CENTER, m_iNemesisWidth ); + pPlayerList->AddColumnToSection( 0, "score", "#TF_Scoreboard_Score", SectionedListPanel::COLUMN_RIGHT, m_iScoreWidth ); + pPlayerList->AddColumnToSection( 0, "class", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_RIGHT, m_iClassWidth ); + + if ( tf_scoreboard_ping_as_text.GetBool() ) + { + pPlayerList->AddColumnToSection( 0, "ping", "#TF_Scoreboard_Ping", SectionedListPanel::COLUMN_RIGHT, m_iPingWidth ); + } + else + { + pPlayerList->AddColumnToSection( 0, "ping", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_RIGHT, m_iPingWidth ); + } +#ifdef STAGING_ONLY +// if ( m_bDisplayLevel ) +// { +// pPlayerList->AddColumnToSection( 0, "level", "#TF_ScoreBoard_LevelLabel", SectionedListPanel::COLUMN_RIGHT, m_iPingWidth ); +// } +#endif // STAGING_ONLY +} + +//----------------------------------------------------------------------------- +// Purpose: Builds the image list to use in the player list +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::SetPlayerListImages( vgui::SectionedListPanel *pPlayerList ) +{ + pPlayerList->SetImageList( m_pImageList, false ); + pPlayerList->SetVisible( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: Updates the dialog +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::Update() +{ + // MvM + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) + { + m_pMvMScoreboard->OnTick(); + } + + UpdateTeamInfo(); + UpdatePlayerList(); + UpdateSpectatorList(); + UpdateArenaWaitingToPlayList(); + UpdatePlayerDetails(); + MoveToCenterOfScreen(); + UpdateServerTimeLeft(); + AdjustForVisibleScrollbar(); + UpdateBadgePanels( m_pRedBadgePanels, m_pPlayerListRed ); + UpdateBadgePanels( m_pBlueBadgePanels, m_pPlayerListBlue ); + + float flNextUpdate = 1.0f; + if ( UseMouseMode() ) + { + if ( ( m_pPlayerListRed && m_pPlayerListRed->GetScrollBar() && m_pPlayerListRed->GetScrollBar()->IsVisible() ) || + ( m_pPlayerListBlue && m_pPlayerListBlue->GetScrollBar() && m_pPlayerListBlue->GetScrollBar()->IsVisible() ) ) + { + // we need a much faster update rate for MouseMode() so the MedalList can update when the scrollbar is quickly moved + // we're listening for the ScrollBarSliderMoved message but it still doesn't keep up with dragging the mouse around + flNextUpdate = 0.02f; + } + else + { + flNextUpdate = 0.15f; + } + } + + m_fNextUpdateTime = gpGlobals->curtime + flNextUpdate; +} + +//----------------------------------------------------------------------------- +// Purpose: Updates information about teams +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::UpdateTeamInfo() +{ + // update the team sections in the scoreboard + for ( int teamIndex = TF_TEAM_RED; teamIndex <= TF_TEAM_BLUE; teamIndex++ ) + { + C_TFTeam *team = GetGlobalTFTeam( teamIndex ); + if ( team ) + { + // choose dialog variables to set depending on team + const char *pDialogVarTeamScore = ""; + const char *pDialogVarTeamPlayerCount = ""; + const char *pDialogVarTeamName = ""; + + switch ( teamIndex ) + { + case TF_TEAM_RED: + pDialogVarTeamScore = "redteamscore"; + pDialogVarTeamPlayerCount = "redteamplayercount"; + pDialogVarTeamName = "redteamname"; + break; + case TF_TEAM_BLUE: + pDialogVarTeamScore = "blueteamscore"; + pDialogVarTeamPlayerCount = "blueteamplayercount"; + pDialogVarTeamName = "blueteamname"; + break; + default: + Assert( false ); + break; + } + + // update # of players on each team + wchar_t string1[1024]; + wchar_t wNumPlayers[6]; + _snwprintf( wNumPlayers, ARRAYSIZE( wNumPlayers ), L"%i", team->Get_Number_Players() ); + if ( team->Get_Number_Players() == 1 ) + { + g_pVGuiLocalize->ConstructString_safe( string1, g_pVGuiLocalize->Find( "#TF_ScoreBoard_Player" ), 1, wNumPlayers ); + } + else + { + g_pVGuiLocalize->ConstructString_safe( string1, g_pVGuiLocalize->Find( "#TF_ScoreBoard_Players" ), 1, wNumPlayers ); + } + + // set # of players for team in dialog + SetDialogVariable( pDialogVarTeamPlayerCount, string1 ); + + // set team score in dialog + SetDialogVariable( pDialogVarTeamScore, team->Get_Score() ); + + // set the team name + SetDialogVariable( pDialogVarTeamName, team->Get_Localized_Name() ); + } + } + + bool bMvM = TFGameRules() && TFGameRules()->IsMannVsMachineMode(); + bool bTournament = mp_tournament.GetBool() && !bMvM; + if ( m_pRedTeamName->IsVisible() != bTournament ) + { + m_pRedTeamName->SetVisible( bTournament ); + } + + if ( m_pBlueTeamName->IsVisible() != bTournament ) + { + m_pBlueTeamName->SetVisible( bTournament ); + } + + bool bShowAvatars = g_TF_PR && g_TF_PR->HasPremadeParties(); + if ( bShowAvatars ) + { + m_pRedLeaderAvatarImage->SetPlayer( GetSteamIDForPlayerIndex( g_TF_PR->GetPartyLeaderRedTeamIndex() ), k_EAvatarSize64x64 ); + m_pRedLeaderAvatarImage->SetShouldDrawFriendIcon( false ); + m_pBlueLeaderAvatarImage->SetPlayer( GetSteamIDForPlayerIndex( g_TF_PR->GetPartyLeaderBlueTeamIndex() ), k_EAvatarSize64x64 ); + m_pBlueLeaderAvatarImage->SetShouldDrawFriendIcon( false ); + } + + m_pRedLeaderAvatarImage->SetVisible( bShowAvatars ); + m_pRedLeaderAvatarBG->SetVisible( bShowAvatars ); + m_pRedTeamImage->SetVisible( !bShowAvatars && !bMvM ); + + m_pBlueLeaderAvatarImage->SetVisible( bShowAvatars ); + m_pBlueLeaderAvatarBG->SetVisible( bShowAvatars ); + m_pBlueTeamImage->SetVisible( !bShowAvatars && !bMvM ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool AreEnemyTeams( int iTeam1, int iTeam2 ) +{ + if ( iTeam1 == TF_TEAM_RED && iTeam2 == TF_TEAM_BLUE ) + return true; + + if ( iTeam1 == TF_TEAM_BLUE && iTeam2 == TF_TEAM_RED ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::AdjustForVisibleScrollbar( void ) +{ + if ( m_pPlayerListRed && m_pPlayerListRed->GetScrollBar() && ( m_bRedScrollBarVisible != m_pPlayerListRed->GetScrollBar()->IsVisible() ) ) + { + m_bRedScrollBarVisible = m_pPlayerListRed->GetScrollBar()->IsVisible(); + int iScrollBarWidth = m_bRedScrollBarVisible ? m_pPlayerListRed->GetScrollBar()->GetWide() : 0; + m_pPlayerListRed->SetColumnWidthBySection( 0, "name", m_iNameWidth + m_nExtraSpace - iScrollBarWidth ); + } + + if ( m_pPlayerListBlue && m_pPlayerListBlue->GetScrollBar() && ( m_bBlueScrollBarVisible != m_pPlayerListBlue->GetScrollBar()->IsVisible() ) ) + { + m_bBlueScrollBarVisible = m_pPlayerListBlue->GetScrollBar()->IsVisible(); + int iScrollBarWidth = m_bBlueScrollBarVisible ? m_pPlayerListBlue->GetScrollBar()->GetWide() : 0; + m_pPlayerListBlue->SetColumnWidthBySection( 0, "name", m_iNameWidth + m_nExtraSpace - iScrollBarWidth ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::UpdateBadgePanels( CUtlVector<CTFBadgePanel*> &pBadgePanels, vgui::SectionedListPanel *pPlayerList ) +{ + int iNumPanels = 0; + + const IMatchGroupDescription *pMatchDesc = TFGameRules() ? GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() ) : NULL; + const IProgressionDesc *pProgressionDesc = pMatchDesc ? pMatchDesc->m_pProgressionDesc : NULL; + if ( pProgressionDesc && pPlayerList ) + { + if ( TFGameRules()->IsMatchTypeCasual() ) + { + int parentTall = pPlayerList->GetTall(); + CTFBadgePanel *pPanel = NULL; + + for ( int i = 0; i < pPlayerList->GetItemCount(); i++ ) + { + KeyValues *pKeyValues = pPlayerList->GetItemData( i ); + if ( !pKeyValues ) + continue; + + int iPlayerIndex = pKeyValues->GetInt( "playerIndex" ); + const CSteamID steamID = GetSteamIDForPlayerIndex( iPlayerIndex ); + if ( steamID.IsValid() ) + { + if ( iNumPanels >= pBadgePanels.Count() ) + { + pPanel = new CTFBadgePanel( this, "BadgePanel" ); + pPanel->MakeReadyForUse(); + pPanel->SetVisible( true ); + pPanel->SetZPos( 9999 ); + pBadgePanels.AddToTail( pPanel ); + } + else + { + pPanel = pBadgePanels[iNumPanels]; + } + + int x, y, wide, tall; + pPlayerList->GetMaxCellBounds( i, 0, x, y, wide, tall ); + + if ( y + tall > parentTall ) + continue; + + if ( !pPanel->IsVisible() ) + { + pPanel->SetVisible( true ); + } + + int xParent, yParent; + pPlayerList->GetPos( xParent, yParent ); + + int nPanelXPos, nPanelYPos, nPanelWide, nPanelTall; + pPanel->GetBounds( nPanelXPos, nPanelYPos, nPanelWide, nPanelTall ); + + if ( ( nPanelXPos != xParent + x ) + || ( nPanelYPos != yParent + y ) + || ( nPanelWide != wide ) + || ( nPanelTall != tall ) ) + { + pPanel->SetBounds( xParent + x, yParent + y, wide, tall ); + pPanel->InvalidateLayout( true, true ); + } + + pPanel->SetupBadge( pProgressionDesc, steamID ); + iNumPanels++; + } + } + } + } + + // hide any unused images + for ( int i = iNumPanels; i < pBadgePanels.Count(); i++ ) + { + if ( pBadgePanels[i]->IsVisible() ) + { + pBadgePanels[i]->SetVisible( false ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Updates the player list +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::UpdatePlayerList() +{ + int iSelectedPlayerIndex = GetLocalPlayerIndex(); + + // Save off which player we had selected + SectionedListPanel *pList = GetSelectedPlayerList(); + if ( pList ) + { + int itemID = pList->GetSelectedItem(); + + if ( itemID >= 0 ) + { + KeyValues *pInfo = pList->GetItemData( itemID ); + if ( pInfo ) + { + iSelectedPlayerIndex = pInfo->GetInt( "playerIndex" ); + } + } + } + + m_pPlayerListRed->ClearSelection(); + m_pPlayerListBlue->ClearSelection(); + m_pPlayerListRed->RemoveAll(); + m_pPlayerListBlue->RemoveAll(); + + if ( !g_TF_PR ) + return; + + C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( !pLocalTFPlayer ) + return; + + int localteam = pLocalTFPlayer->GetTeamNumber(); + if ( pLocalTFPlayer->m_bIsCoaching && pLocalTFPlayer->m_hStudent ) + { + localteam = pLocalTFPlayer->m_hStudent->GetTeamNumber(); + } + + bool bMadeSelection = false; + + for ( int playerIndex = 1; playerIndex <= MAX_PLAYERS; playerIndex++ ) + { + if ( g_PR->IsConnected( playerIndex ) || g_PR->IsValid( playerIndex ) ) + { + SectionedListPanel *pPlayerList = NULL; + int nTeam = g_PR->GetTeam( playerIndex ); + switch ( nTeam ) + { + case TF_TEAM_BLUE: + pPlayerList = m_pPlayerListBlue; + break; + case TF_TEAM_RED: + pPlayerList = m_pPlayerListRed; + break; + } + if ( !pPlayerList ) + continue; + + MM_PlayerConnectionState_t eConnectionState = g_TF_PR->GetPlayerConnectionState( playerIndex ); + const wchar_t *pwszFormat = NULL; + if ( eConnectionState == MM_DISCONNECTED ) + { + pwszFormat = g_pVGuiLocalize->Find( "#TF_MM_PlayerLostConnection" ); + } + else if ( ( eConnectionState == MM_CONNECTING ) || ( eConnectionState == MM_LOADING ) ) + { + pwszFormat = g_pVGuiLocalize->Find( "#TF_MM_PlayerConnecting" ); + } + + if ( pwszFormat ) + { + KeyValues *pKV = new KeyValues( "data" ); + pKV->SetInt( "playerIndex", playerIndex ); + pKV->SetInt( "connected", 1 ); + + // HOLY CHEESEBALL BUSY INDICATOR + const wchar_t *pwszEllipses = &L"....."[4 - ( (unsigned)Plat_FloatTime() % 5U )]; + wchar_t wszLocalized[512]; + g_pVGuiLocalize->ConstructString_safe( wszLocalized, pwszFormat, 1, pwszEllipses ); + pKV->SetWString( "name", wszLocalized ); + + int itemID = pPlayerList->AddItem( 0, pKV ); + pPlayerList->SetItemFgColor( itemID, ( eConnectionState == MM_DISCONNECTED ) ? Color( 208, 147, 7, 255 ) : Color( 76, 107, 34, 255 ) ); + pPlayerList->SetItemBgColor( itemID, Color( 0, 0, 0, 80 ) ); + pPlayerList->SetItemFont( itemID, m_hScoreFontSmallest ); + + pKV->deleteThis(); + + continue; + } + + int iActiveDominations = g_TF_PR->GetActiveDominations( playerIndex ); + + // Debug getting some bogus counts here. + if ( iActiveDominations < 0 ) + { + Assert( iActiveDominations >= 0 ); + iActiveDominations = 0; + } + else if ( iActiveDominations >= MAX_PLAYERS ) + { + Assert( iActiveDominations < MAX_PLAYERS ); + iActiveDominations = MAX_PLAYERS - 1; + } + + // Now limit based on the number of images we have + if ( iActiveDominations >= SCOREBOARD_DOMINATION_ICONS ) + { + iActiveDominations = SCOREBOARD_DOMINATION_ICONS - 1; + } + + bool bDominating; + if ( iActiveDominations > 0 ) + { + bDominating = true; + } + else + { + bDominating = false; + } + + bool bAlive = g_TF_PR->IsAlive( playerIndex ); + + int iDominationIndex = ( bDominating ? ( m_iImageDom[iActiveDominations] ) : 0 ); + if ( !bAlive ) + { + iDominationIndex = ( bDominating ? ( m_iImageDomDead[iActiveDominations] ) : 0 ); + } + + KeyValues *pKeyValues = new KeyValues( "data" ); + pKeyValues->SetInt( "playerIndex", playerIndex ); + pKeyValues->SetString( "name", g_TF_PR->GetPlayerName( playerIndex ) ); + pKeyValues->SetInt( "dominating", iDominationIndex ); + pKeyValues->SetInt( "score", g_TF_PR->GetTotalScore( playerIndex ) ); + pKeyValues->SetInt( "connected", 2 ); + + C_TFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( playerIndex ) ); + + if ( pTFPlayer && pTFPlayer->GetActiveTFWeapon() ) + { + int nCount = g_TF_PR->GetStreak( playerIndex, CTFPlayerShared::kTFStreak_Kills ); + if ( nCount ) + { + pKeyValues->SetInt( "killstreak_image", bAlive ? m_iImageStreak : m_iImageStreakDead ); + pKeyValues->SetInt( "killstreak", nCount ); + } + else + { + pKeyValues->SetInt( "killstreak_image", 0 ); + pKeyValues->SetString( "killstreak", "" ); + } + } + + // check for bots first, so malicious server operators can't fake a ping and stuff their server with bots that look like players + if ( g_PR->IsFakePlayer( playerIndex ) ) + { + if ( tf_scoreboard_ping_as_text.GetBool() ) + { + pKeyValues->SetString( "ping", "#TF_Scoreboard_Bot" ); + } + else + { + int iIndex = ( nTeam == TF_TEAM_RED ) ? PING_BOT_RED : PING_BOT_BLUE; + pKeyValues->SetInt( "ping", bAlive ? m_iImagePing[iIndex] : m_iImagePingDead[iIndex] ); + } + } + else + { + int nPing = g_PR->GetPing( playerIndex ); + + if ( nPing < 1 ) + { + if ( tf_scoreboard_ping_as_text.GetBool() ) + { + pKeyValues->SetString( "ping", "" ); + } + else + { + pKeyValues->SetInt( "ping", 0 ); + } + } + else + { + if ( tf_scoreboard_ping_as_text.GetBool() ) + { + pKeyValues->SetInt( "ping", nPing ); + } + else + { + int iIndex = PING_VERY_HIGH; + + if ( nPing < 125 ) + { + iIndex = PING_LOW; + } + else if ( nPing < 200 ) + { + iIndex = PING_MED; + } + else if ( nPing < 275 ) + { + iIndex = PING_HIGH; + } + + pKeyValues->SetInt( "ping", bAlive ? m_iImagePing[iIndex] : m_iImagePingDead[iIndex] ); + } + } + } + + if ( TFGameRules() && TFGameRules()->IsHolidayActive( kHoliday_Halloween ) && TFGameRules()->ArePlayersInHell() ) + { + bAlive &= pTFPlayer && !pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ); + } + + // are they a Spy that's feigning death? mark them as dead in the scoreboard... + if ( g_TF_PR->GetPlayerClass( playerIndex ) == TF_CLASS_SPY ) + { + if ( pTFPlayer && pTFPlayer->m_Shared.InCond( TF_COND_FEIGN_DEATH ) ) + { + bAlive = false; + } + } + + // can only see class information if we're on the same team + if ( !AreEnemyTeams( g_PR->GetTeam( playerIndex ), localteam ) && !( localteam == TEAM_UNASSIGNED ) ) + { + // class name + if ( g_PR->IsConnected( playerIndex ) ) + { + int iClass = g_TF_PR->GetPlayerClass( playerIndex ); + if ( GetLocalPlayerIndex() == playerIndex && !bAlive ) + { + // If this is local player and he is dead, show desired class (which he will spawn as) rather than current class. + int iDesiredClass = pLocalTFPlayer->m_Shared.GetDesiredPlayerClassIndex(); + // use desired class unless it's random -- if random, his future class is not decided until moment of spawn + if ( TF_CLASS_RANDOM != iDesiredClass ) + { + iClass = iDesiredClass; + } + } + else + { + // for non-local players, show the current class + iClass = g_TF_PR->GetPlayerClass( playerIndex ); + } + + if ( iClass >= TF_FIRST_NORMAL_CLASS && iClass <= TF_LAST_NORMAL_CLASS ) + { + if ( bAlive ) + { + pKeyValues->SetInt( "class", tf_scoreboard_alt_class_icons.GetBool() ? m_iImageClassAlt[iClass] : m_iImageClass[iClass] ); + } + else + { + pKeyValues->SetInt( "class", tf_scoreboard_alt_class_icons.GetBool() ? m_iImageClassAlt[iClass + 9] : m_iImageClass[iClass + 9] ); // +9 is to jump ahead to the darker dead icons + } + } + else + { + pKeyValues->SetInt( "class", 0 ); + } + } + } + else + { + if ( pTFPlayer && pTFPlayer->m_Shared.IsPlayerDominated( pLocalTFPlayer->entindex() ) ) + { + // if local player is dominated by this player, show a nemesis icon + pKeyValues->SetInt( "nemesis", bAlive ? m_iImageNemesis : m_iImageNemesisDead ); + } + else if ( pLocalTFPlayer->m_Shared.IsPlayerDominated( playerIndex ) ) + { + // if this player is dominated by the local player, show the domination icon + pKeyValues->SetInt( "nemesis", bAlive ? m_iImageDominated : m_iImageDominatedDead ); + } + } + +#ifdef STAGING_ONLY +// if ( m_bDisplayLevel ) +// { +// pKeyValues->SetInt( "level", g_TF_PR->GetPlayerLevel( playerIndex ) ); +// } +#endif // STAGING_ONLY + + UpdatePlayerAvatar( playerIndex, pKeyValues ); + + // the medal column is just a place holder for the images that are displayed later + pKeyValues->SetInt( "medal", 0 ); + + int itemID = pPlayerList->AddItem( 0, pKeyValues ); + Color clr = g_PR->GetTeamColor( nTeam ); + + // change color based off alive or dead status. Also slightly different if its local player since the name is highlighted. + if ( !bAlive ) + { + if ( nTeam == TF_TEAM_RED ) + { + if ( GetLocalPlayerIndex() != playerIndex ) + { + clr = Color( 135, 83, 83, 255 ); + } + else + { + clr = Color( 182, 75, 75, 255 ); + } + } + else + { + if ( GetLocalPlayerIndex() != playerIndex ) + { + clr = Color( 81, 97, 129, 255 ); + } + else + { + clr = Color( 123, 153, 187, 255 ); + } + } + } + + pPlayerList->SetItemFgColor( itemID, clr ); + pPlayerList->SetItemBgColor( itemID, Color( 0, 0, 0, 80 ) ); + pPlayerList->SetItemFont( itemID, m_hScoreFontDefault ); + + if ( iSelectedPlayerIndex == playerIndex ) + { + bMadeSelection = true; + pPlayerList->SetSelectedItem( itemID ); + } + + pKeyValues->deleteThis(); + } + else + { + MM_PlayerConnectionState_t eConnectionState = g_TF_PR->GetPlayerConnectionState( playerIndex ); + if ( eConnectionState == MM_WAITING_FOR_PLAYER ) + { + SectionedListPanel *pPlayerList = NULL; + int nTeam = g_PR->GetTeam( playerIndex ); + switch ( nTeam ) + { + case TF_TEAM_BLUE: + pPlayerList = m_pPlayerListBlue; + break; + case TF_TEAM_RED: + pPlayerList = m_pPlayerListRed; + break; + } + if ( pPlayerList ) + { + const wchar_t *pwszFormat = g_pVGuiLocalize->Find( "#TF_MM_LookingForPlayer" ); + if ( pwszFormat ) + { + KeyValues *pKV = new KeyValues( "data" ); + pKV->SetInt( "playerIndex", playerIndex ); + pKV->SetInt( "connected", 0 ); + + // HOLY CHEESEBALL BUSY INDICATOR + const wchar_t *pwszEllipses = &L"....."[4 - ( (unsigned)Plat_FloatTime() % 5U )]; + wchar_t wszLocalized[512]; + g_pVGuiLocalize->ConstructString_safe( wszLocalized, pwszFormat, 1, pwszEllipses ); + pKV->SetWString( "name", wszLocalized ); + + int itemID = pPlayerList->AddItem( 0, pKV ); + pPlayerList->SetItemFgColor( itemID, Color( 120, 120, 120, 255 ) ); + pPlayerList->SetItemBgColor( itemID, Color( 0, 0, 0, 80 ) ); + pPlayerList->SetItemFont( itemID, m_hScoreFontSmallest ); + + pKV->deleteThis(); + } + } + } + } + } + + // If we're on spectator, find a default selection +#ifdef _X360 + if ( !bMadeSelection ) + { + if ( m_pPlayerListBlue->GetItemCount() > 0 ) + { + m_pPlayerListBlue->SetSelectedItem( 0 ); + } + else if ( m_pPlayerListRed->GetItemCount() > 0 ) + { + m_pPlayerListRed->SetSelectedItem( 0 ); + } + } +#endif + + // force the lists to PerformLayout() now so we can update our medal images after we return + m_pPlayerListRed->InvalidateLayout( true ); + m_pPlayerListBlue->InvalidateLayout( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: Updates the spectator list +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::UpdateSpectatorList() +{ + char szCoachList[512] = "" ; + int nCoaches = 0; + char szSpectatorList[512] = "" ; + int nSpectators = 0; + for( int playerIndex = 1 ; playerIndex <= MAX_PLAYERS; playerIndex++ ) + { + if ( ShouldShowAsSpectator( playerIndex ) ) + { + C_TFPlayer *pPlayer = (C_TFPlayer*)UTIL_PlayerByIndex( playerIndex ); + if ( pPlayer && pPlayer->m_bIsCoaching && pPlayer->m_hStudent ) + { + if ( nCoaches > 0 ) + { + Q_strncat( szCoachList, ", ", ARRAYSIZE( szCoachList ) ); + } + + Q_strncat( szCoachList, g_PR->GetPlayerName( playerIndex ), ARRAYSIZE( szCoachList ) ); + nCoaches++; + } + else + { + if ( nSpectators > 0 ) + { + Q_strncat( szSpectatorList, ", ", ARRAYSIZE( szSpectatorList ) ); + } + + Q_strncat( szSpectatorList, g_PR->GetPlayerName( playerIndex ), ARRAYSIZE( szSpectatorList ) ); + nSpectators++; + } + } + } + + wchar_t wzText[512] = L""; + if ( nCoaches > 0 ) + { + const char *pchFormat = ( 1 == nCoaches ? "#ScoreBoard_Coach" : "#ScoreBoard_Coaches" ); + + wchar_t wzCount[16]; + wchar_t wzList[1024]; + _snwprintf( wzCount, ARRAYSIZE( wzCount ), L"%i", nCoaches ); + g_pVGuiLocalize->ConvertANSIToUnicode( szCoachList, wzList, sizeof( wzList ) ); + g_pVGuiLocalize->ConstructString_safe( wzText, g_pVGuiLocalize->Find( pchFormat), 2, wzCount, wzList ); + } + if ( nSpectators > 0 ) + { + const char *pchFormat = ( 1 == nSpectators ? "#ScoreBoard_Spectator" : "#ScoreBoard_Spectators" ); + + wchar_t wzSpectatorText[512] = L""; + wchar_t wzSpectatorCount[16]; + wchar_t wzSpectatorList[1024]; + _snwprintf( wzSpectatorCount, ARRAYSIZE( wzSpectatorCount ), L"%i", nSpectators ); + g_pVGuiLocalize->ConvertANSIToUnicode( szSpectatorList, wzSpectatorList, sizeof( wzSpectatorList ) ); + g_pVGuiLocalize->ConstructString_safe( wzSpectatorText, g_pVGuiLocalize->Find( pchFormat), 2, wzSpectatorCount, wzSpectatorList ); + if ( nCoaches > 0 ) + { + V_wcscat_safe( wzText, L". " ); + } + V_wcscat_safe( wzText, wzSpectatorText ); + } + + SetDialogVariable( "spectators", wzText ); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns whether the specified player index is an arena spectator +//----------------------------------------------------------------------------- +bool CTFClientScoreBoardDialog::ShouldShowAsArenaWaitingToPlay( int iPlayerIndex ) +{ + if ( !g_TF_PR ) + return false; + + // see if player is connected + if ( g_TF_PR->IsConnected( iPlayerIndex ) ) + { + if ( g_TF_PR->IsArenaSpectator( iPlayerIndex ) == true ) + return false; + + if ( g_TF_PR->GetPlayerClass( iPlayerIndex ) == TF_CLASS_UNDEFINED ) + return false; + + if ( g_TF_PR->GetTeam( iPlayerIndex ) > LAST_SHARED_TEAM ) + return false; + + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Updates the spectator list +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::UpdateArenaWaitingToPlayList() +{ + char szSpectatorList[512] = "" ; + wchar_t wzSpectators[512] = L""; + + if ( TFGameRules() && TFGameRules()->IsInArenaMode() == true ) + { + int nSpectators = 0; + for( int playerIndex = 1 ; playerIndex <= MAX_PLAYERS; playerIndex++ ) + { + if ( ShouldShowAsArenaWaitingToPlay( playerIndex ) ) + { + if ( nSpectators > 0 ) + { + Q_strncat( szSpectatorList, ", ", ARRAYSIZE( szSpectatorList ) ); + } + + Q_strncat( szSpectatorList, g_PR->GetPlayerName( playerIndex ), ARRAYSIZE( szSpectatorList ) ); + nSpectators++; + } + } + + if ( nSpectators > 0 ) + { + const char *pchFormat = ( 1 == nSpectators ? "#TF_Arena_ScoreBoard_Spectator" : "#TF_Arena_ScoreBoard_Spectators" ); + + wchar_t wzSpectatorCount[16]; + wchar_t wzSpectatorList[1024]; + _snwprintf( wzSpectatorCount, ARRAYSIZE( wzSpectatorCount ), L"%i", nSpectators ); + g_pVGuiLocalize->ConvertANSIToUnicode( szSpectatorList, wzSpectatorList, sizeof( wzSpectatorList ) ); + g_pVGuiLocalize->ConstructString_safe( wzSpectators, g_pVGuiLocalize->Find( pchFormat), 2, wzSpectatorCount, wzSpectatorList ); + } + } + + SetDialogVariable( "waitingtoplay", wzSpectators ); +} + +static void PopulateDuelPanel( CTFClientScoreBoardDialog::duel_panel_t &duelPanel, C_TFPlayer *pPlayer, int unScore ) +{ + CSteamID steamID; + if ( duelPanel.m_pAvatar && pPlayer->GetSteamID( &steamID ) ) + { + duelPanel.m_pAvatar->SetVisible( true ); + duelPanel.m_pAvatar->SetShouldDrawFriendIcon( false ); + duelPanel.m_pAvatar->SetPlayer( steamID, k_EAvatarSize64x64 ); + } + + if ( duelPanel.m_pPlayerNameLabel && g_TF_PR != NULL ) + { + duelPanel.m_pPanel->SetDialogVariable( "playername", g_TF_PR->GetPlayerName( pPlayer->entindex() ) ); + Color clr = g_PR->GetTeamColor( g_PR->GetTeam( pPlayer->entindex() ) ); + duelPanel.m_pPlayerNameLabel->SetFgColor( clr ); + } + duelPanel.m_pPanel->SetDialogVariable( "score", unScore ); +} + +//----------------------------------------------------------------------------- +// Purpose: Updates details about a player +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::UpdatePlayerDetails() +{ + ClearPlayerDetails(); + + // by default, hide these, especially for source tv + m_pLocalPlayerStatsPanel->SetVisible( false ); + m_pLocalPlayerDuelStatsPanel->SetVisible( false ); + + if ( !g_TF_PR ) + return; + + // Default is local player + C_TFPlayer *pSelectedPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( !pSelectedPlayer ) + return; + + int playerIndex = pSelectedPlayer->entindex(); + + // Change to selected player when using mouse mode + if ( UseMouseMode() ) + { + SectionedListPanel *pList = GetSelectedPlayerList(); + if ( pList ) + { + int iSelectedItem = pList->GetSelectedItem(); + if ( iSelectedItem >= 0 ) + { + KeyValues *pIssueKeyValues = pList->GetItemData( iSelectedItem ); + if ( pIssueKeyValues ) + { + playerIndex = pIssueKeyValues->GetInt( "playerIndex", 0 ); + pSelectedPlayer = ToTFPlayer( UTIL_PlayerByIndex( playerIndex ) ); + if ( !pSelectedPlayer || !playerIndex ) + return; + } + } + } + } + + // Make sure the selected player is still connected. + if ( !g_TF_PR->IsConnected( playerIndex ) ) + return; + +#if defined( REPLAY_ENABLED ) + if ( engine->IsHLTV() || g_pEngineClientReplay->IsPlayingReplayDemo() ) +#else + if ( engine->IsHLTV() ) +#endif + { + SetDialogVariable( "playername", g_TF_PR->GetPlayerName( playerIndex ) ); + return; + } + + uint32 unMyScore, unOpponentScore; + C_TFPlayer *pDuelingPartner = NULL; + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( pSelectedPlayer == pLocalPlayer && DuelMiniGame_GetStats( &pDuelingPartner, unMyScore, unOpponentScore ) ) + { + PopulateDuelPanel( m_duelPanelLocalPlayer, pLocalPlayer, unMyScore ); + PopulateDuelPanel( m_duelPanelOpponent, pDuelingPartner, unOpponentScore ); + + if ( m_pLocalPlayerStatsPanel->IsVisible() == true ) + { + m_pLocalPlayerStatsPanel->SetVisible( false ); + } + if ( m_pLocalPlayerDuelStatsPanel->IsVisible() == false ) + { + m_pLocalPlayerDuelStatsPanel->SetVisible( true ); + } + } + else + { + if ( m_pLocalPlayerStatsPanel->IsVisible() == false ) + { + m_pLocalPlayerStatsPanel->SetVisible( true ); + } + if ( m_pLocalPlayerDuelStatsPanel->IsVisible() == true ) + { + m_pLocalPlayerDuelStatsPanel->SetVisible( false ); + } + + Color cGreen = Color( 0, 255, 0, 255 ); + Color cWhite = Color( 255, 255, 255, 255 ); + + m_pLocalPlayerStatsPanel->SetDialogVariable( "kills", g_TF_PR->GetPlayerScore( playerIndex ) ); + if ( m_pKillsLabel ) + { + m_pKillsLabel->SetFgColor( g_TF_PR->GetPlayerScore( playerIndex ) ? cGreen : cWhite ); + } + m_pLocalPlayerStatsPanel->SetDialogVariable( "deaths", g_TF_PR->GetDeaths( playerIndex ) ); + if ( m_pDeathsLabel ) + { + m_pDeathsLabel->SetFgColor( g_TF_PR->GetDeaths( playerIndex ) ? cGreen : cWhite ); + } + m_pLocalPlayerStatsPanel->SetDialogVariable( "assists", pSelectedPlayer->m_Shared.GetKillAssists( playerIndex ) ); + if ( m_pAssistLabel ) + { + m_pAssistLabel->SetFgColor( pSelectedPlayer->m_Shared.GetKillAssists( playerIndex ) ? cGreen : cWhite ); + } + m_pLocalPlayerStatsPanel->SetDialogVariable( "destruction", pSelectedPlayer->m_Shared.GetBuildingsDestroyed( playerIndex ) ); + if ( m_pDestructionLabel ) + { + m_pDestructionLabel->SetFgColor( pSelectedPlayer->m_Shared.GetBuildingsDestroyed( playerIndex ) ? cGreen : cWhite ); + } + m_pLocalPlayerStatsPanel->SetDialogVariable( "captures", pSelectedPlayer->m_Shared.GetCaptures( playerIndex ) ); + if ( m_pCapturesLabel ) + { + m_pCapturesLabel->SetFgColor( pSelectedPlayer->m_Shared.GetCaptures( playerIndex ) ? cGreen : cWhite ); + } + m_pLocalPlayerStatsPanel->SetDialogVariable( "defenses", pSelectedPlayer->m_Shared.GetDefenses( playerIndex ) ); + if ( m_pDefensesLabel ) + { + m_pDefensesLabel->SetFgColor( pSelectedPlayer->m_Shared.GetDefenses( playerIndex ) ? cGreen : cWhite ); + } + m_pLocalPlayerStatsPanel->SetDialogVariable( "dominations", pSelectedPlayer->m_Shared.GetDominations( playerIndex ) ); + if ( m_pDominationsLabel ) + { + m_pDominationsLabel->SetFgColor( pSelectedPlayer->m_Shared.GetDominations( playerIndex ) ? cGreen : cWhite ); + } + m_pLocalPlayerStatsPanel->SetDialogVariable( "revenge", pSelectedPlayer->m_Shared.GetRevenge( playerIndex ) ); + if ( m_pRevengeLabel ) + { + m_pRevengeLabel->SetFgColor( pSelectedPlayer->m_Shared.GetRevenge( playerIndex ) ? cGreen : cWhite ); + } + m_pLocalPlayerStatsPanel->SetDialogVariable( "healing", pSelectedPlayer->m_Shared.GetHealPoints( playerIndex ) ); + if ( m_pHealingLabel ) + { + m_pHealingLabel->SetFgColor( pSelectedPlayer->m_Shared.GetHealPoints( playerIndex ) ? cGreen : cWhite ); + } + m_pLocalPlayerStatsPanel->SetDialogVariable( "invulns", pSelectedPlayer->m_Shared.GetInvulns( playerIndex ) ); + if ( m_pInvulnsLabel ) + { + m_pInvulnsLabel->SetFgColor( pSelectedPlayer->m_Shared.GetInvulns( playerIndex ) ? cGreen : cWhite ); + } + m_pLocalPlayerStatsPanel->SetDialogVariable( "teleports", pSelectedPlayer->m_Shared.GetTeleports( playerIndex ) ); + if ( m_pTeleportsLabel ) + { + m_pTeleportsLabel->SetFgColor( pSelectedPlayer->m_Shared.GetTeleports( playerIndex ) ? cGreen : cWhite ); + } + m_pLocalPlayerStatsPanel->SetDialogVariable( "headshots", pSelectedPlayer->m_Shared.GetHeadshots( playerIndex ) ); + if ( m_pHeadshotsLabel ) + { + m_pHeadshotsLabel->SetFgColor( pSelectedPlayer->m_Shared.GetHeadshots( playerIndex ) ? cGreen : cWhite ); + } + m_pLocalPlayerStatsPanel->SetDialogVariable( "backstabs", pSelectedPlayer->m_Shared.GetBackstabs( playerIndex ) ); + if ( m_pBackstabsLabel ) + { + m_pBackstabsLabel->SetFgColor( pSelectedPlayer->m_Shared.GetBackstabs( playerIndex ) ? cGreen : cWhite ); + } + m_pLocalPlayerStatsPanel->SetDialogVariable( "bonus", pSelectedPlayer->m_Shared.GetBonusPoints( playerIndex ) ); + if ( m_pBonusLabel ) + { + m_pBonusLabel->SetFgColor( pSelectedPlayer->m_Shared.GetBonusPoints( playerIndex ) ? cGreen : cWhite ); + } + + int nSupport = TFGameRules() ? TFGameRules()->CalcPlayerSupportScore( NULL, playerIndex ) : 0; + m_pLocalPlayerStatsPanel->SetDialogVariable( "support", nSupport ); + if ( m_pSupportLabel ) + { + m_pSupportLabel->SetFgColor( nSupport ? cGreen : cWhite ); + } + m_pLocalPlayerStatsPanel->SetDialogVariable( "damage", g_TF_PR->GetDamage( playerIndex ) ); + if ( m_pDamageLabel ) + { + m_pDamageLabel->SetFgColor( g_TF_PR->GetDamage( playerIndex ) ? cGreen : cWhite ); + } + } + + SetDialogVariable( "playername", g_TF_PR->GetPlayerName( playerIndex ) ); + + Color clr = g_PR->GetTeamColor( g_PR->GetTeam( playerIndex ) ); + m_pLabelPlayerName->SetFgColor( clr ); + m_pImagePanelHorizLine->SetFillColor( clr ); + + // update our image if our selected player or mode of display has changed + if ( ( m_hSelectedPlayer != pSelectedPlayer ) || ( m_bUsePlayerModel != cl_hud_playerclass_use_playermodel.GetBool() ) ) + { + m_hSelectedPlayer = pSelectedPlayer; + m_bUsePlayerModel = cl_hud_playerclass_use_playermodel.GetBool(); + + int iClass = pSelectedPlayer->m_Shared.GetDesiredPlayerClassIndex(); + int iTeam = pSelectedPlayer->GetTeamNumber(); + if ( ( pLocalPlayer->InSameTeam( pSelectedPlayer ) || pLocalPlayer->GetTeamNumber() < FIRST_GAME_TEAM ) && + iTeam >= FIRST_GAME_TEAM && iClass >= TF_FIRST_NORMAL_CLASS && iClass <= TF_LAST_NORMAL_CLASS ) + { + if ( cl_hud_playerclass_use_playermodel.GetBool() ) + { + if ( !m_pPlayerModelPanel->IsVisible() ) + { + m_pPlayerModelPanel->SetVisible( true ); + } + + if ( m_pClassImage->IsVisible() ) + { + m_pClassImage->SetVisible( false ); + } + + m_nPlayerModelPanelIndex = pSelectedPlayer->entindex(); + UpdatePlayerModel(); + } + else + { + if ( m_pPlayerModelPanel->IsVisible() ) + { + m_pPlayerModelPanel->SetVisible( false ); + } + + if ( !m_pClassImage->IsVisible() ) + { + m_pClassImage->SetVisible( true ); + } + + m_pClassImage->SetClass( iTeam, iClass, 0 ); + } + } + else + { + if ( m_pPlayerModelPanel->IsVisible() ) + { + m_pPlayerModelPanel->SetVisible( false ); + } + if ( m_pClassImage->IsVisible() ) + { + m_pClassImage->SetVisible( false ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Updates Server Time Left +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::UpdateServerTimeLeft() +{ + wchar_t wzServerTimeHrsLeft[128]; + wchar_t wzServerTimeMinLeft[128]; + wchar_t wzServerTimeSecLeft[128]; + wchar_t wzServerTimeLeft[128]; + + int iTimeLeft = 0; + int iHours = 0; + int iMinutes = 0; + int iSeconds = 0; + int iServerTimeLimit = mp_timelimit.GetInt() * 60; + + if ( iServerTimeLimit == 0 ) + { + g_pVGuiLocalize->ConstructString_safe( wzServerTimeLeft, g_pVGuiLocalize->Find( "#Scoreboard_NoTimeLimit" ), 0 ); + SetDialogVariable( "servertimeleft", wzServerTimeLeft ); + + g_pVGuiLocalize->ConstructString_safe( wzServerTimeLeft, g_pVGuiLocalize->Find( "#Scoreboard_NoTimeLimitNew" ), 0 ); + SetDialogVariable( "servertime", wzServerTimeLeft ); + + if ( m_pServerTimeLeftValue && m_pServerTimeLeftValue->IsVisible() && ( m_pFontTimeLeftString != vgui::INVALID_FONT ) ) + { + m_pServerTimeLeftValue->SetFont( m_pFontTimeLeftString ); + m_pServerTimeLeftValue->InvalidateLayout(); + } + + return; + } + + iTimeLeft = TFGameRules() ? TFGameRules()->GetTimeLeft() : 0; + + if ( iTimeLeft < 0 ) + { + iTimeLeft = 0; + } + if ( iTimeLeft == 0 ) + { + g_pVGuiLocalize->ConstructString_safe( wzServerTimeLeft, g_pVGuiLocalize->Find( "#Scoreboard_ChangeOnRoundEnd" ), 0 ); + SetDialogVariable( "servertimeleft", wzServerTimeLeft ); + + g_pVGuiLocalize->ConstructString_safe( wzServerTimeLeft, g_pVGuiLocalize->Find( "#Scoreboard_ChangeOnRoundEndNew" ), 0 ); + SetDialogVariable( "servertime", wzServerTimeLeft ); + + if ( m_pServerTimeLeftValue && m_pServerTimeLeftValue->IsVisible() && ( m_pFontTimeLeftString != vgui::INVALID_FONT ) ) + { + m_pServerTimeLeftValue->SetFont( m_pFontTimeLeftString ); + m_pServerTimeLeftValue->InvalidateLayout(); + } + + return; + } + + iHours = iTimeLeft / 3600; + iMinutes = ( iTimeLeft % 3600 ) / 60; + iSeconds = ( iTimeLeft % 60 ); + + _snwprintf( wzServerTimeHrsLeft, ARRAYSIZE( wzServerTimeHrsLeft ), L"%i", iHours ); + _snwprintf( wzServerTimeMinLeft, ARRAYSIZE( wzServerTimeMinLeft ), L"%02i", iMinutes ); + _snwprintf( wzServerTimeSecLeft, ARRAYSIZE( wzServerTimeSecLeft ), L"%02i", iSeconds ); + + if ( m_pServerTimeLeftValue && m_pServerTimeLeftValue->IsVisible() && ( m_pFontTimeLeftNumbers != vgui::INVALID_FONT ) ) + { + m_pServerTimeLeftValue->SetFont( m_pFontTimeLeftNumbers ); + m_pServerTimeLeftValue->InvalidateLayout(); + } + + if ( iHours == 0 ) + { + g_pVGuiLocalize->ConstructString_safe( wzServerTimeLeft, g_pVGuiLocalize->Find( "#Scoreboard_TimeLeftNoHours" ), 2, wzServerTimeMinLeft, wzServerTimeSecLeft ); + SetDialogVariable( "servertimeleft", wzServerTimeLeft ); + + g_pVGuiLocalize->ConstructString_safe( wzServerTimeLeft, g_pVGuiLocalize->Find( "#Scoreboard_TimeLeftNoHoursNew" ), 2, wzServerTimeMinLeft, wzServerTimeSecLeft ); + SetDialogVariable( "servertime", wzServerTimeLeft ); + + return; + } + + g_pVGuiLocalize->ConstructString_safe( wzServerTimeLeft, g_pVGuiLocalize->Find( "#Scoreboard_TimeLeft" ), 3, wzServerTimeHrsLeft, wzServerTimeMinLeft, wzServerTimeSecLeft ); + SetDialogVariable( "servertimeleft", wzServerTimeLeft ); + + g_pVGuiLocalize->ConstructString_safe( wzServerTimeLeft, g_pVGuiLocalize->Find( "#Scoreboard_TimeLeftNew" ), 3, wzServerTimeHrsLeft, wzServerTimeMinLeft, wzServerTimeSecLeft ); + SetDialogVariable( "servertime", wzServerTimeLeft ); +} + +//----------------------------------------------------------------------------- +// Purpose: Clears score details +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::ClearPlayerDetails() +{ + // HLTV has no game stats +#if defined( REPLAY_ENABLED ) + bool bVisible = !engine->IsHLTV() && !g_pEngineClientReplay->IsPlayingReplayDemo(); +#else + bool bVisible = !engine->IsHLTV(); +#endif + + SetDialogVariable( "kills", "" ); + SetControlVisible( "KillsLabel", bVisible ); + + SetDialogVariable( "deaths", "" ); + SetControlVisible( "DeathsLabel", bVisible ); + + SetDialogVariable( "captures", "" ); + SetControlVisible( "CapturesLabel", bVisible ); + + SetDialogVariable( "defenses", "" ); + SetControlVisible( "DefensesLabel", bVisible ); + + SetDialogVariable( "dominations", "" ); + SetControlVisible( "DominationLabel", bVisible ); + + SetDialogVariable( "revenge", "" ); + SetControlVisible( "RevengeLabel", bVisible ); + + SetDialogVariable( "assists", "" ); + SetControlVisible( "AssistsLabel", bVisible ); + + SetDialogVariable( "destruction", "" ); + SetControlVisible( "DestructionLabel", bVisible ); + + SetDialogVariable( "healing", "" ); + SetControlVisible( "HealingLabel", bVisible ); + + SetDialogVariable( "invulns", "" ); + SetControlVisible( "InvulnLabel", bVisible ); + + SetDialogVariable( "teleports", "" ); + SetControlVisible( "TeleportsLabel", bVisible ); + + SetDialogVariable( "headshots", "" ); + SetControlVisible( "HeadshotsLabel", bVisible ); + + SetDialogVariable( "backstabs", "" ); + SetControlVisible( "BackstabsLabel", bVisible ); + + SetDialogVariable( "bonus", "" ); + SetControlVisible( "BonusLabel", bVisible ); + + SetDialogVariable( "support", "" ); + SetControlVisible( "SupportLabel", bVisible ); + + SetDialogVariable( "damage", "" ); + SetControlVisible( "DamageLabel", bVisible ); + + SetDialogVariable( "playername", "" ); + +// SetDialogVariable( "playerscore", "" ); + + +} + +//----------------------------------------------------------------------------- +// Purpose: Used for sorting players +//----------------------------------------------------------------------------- +bool CTFClientScoreBoardDialog::TFPlayerSortFunc( vgui::SectionedListPanel *list, int itemID1, int itemID2 ) +{ + KeyValues *it1 = list->GetItemData(itemID1); + KeyValues *it2 = list->GetItemData(itemID2); + Assert(it1 && it2); + + // first compare score + int v1 = it1->GetInt("score"); + int v2 = it2->GetInt("score"); + if (v1 > v2) + return true; + else if (v1 < v2) + return false; + + // sort by connected status next ( 0 is looking for player, 1 is disconnected/connecting/loading, 2 is fully connected ) + int connected1 = it1->GetInt( "connected" ); + int connected2 = it2->GetInt( "connected" ); + if ( ( connected1 != 2 ) || ( connected2 != 2 ) ) + return ( connected1 > connected2 ); + + // if score is the same, use player index to get deterministic sort + int iPlayerIndex1 = it1->GetInt( "playerIndex" ); + int iPlayerIndex2 = it2->GetInt( "playerIndex" ); + return ( iPlayerIndex1 > iPlayerIndex2 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns a localized string of form "1 point", "2 points", etc for specified # of points +//----------------------------------------------------------------------------- +const wchar_t *GetPointsString( int iPoints ) +{ + wchar_t wzScoreVal[128]; + static wchar_t wzScore[128]; + _snwprintf( wzScoreVal, ARRAYSIZE( wzScoreVal ), L"%i", iPoints ); + if ( 1 == iPoints ) + { + g_pVGuiLocalize->ConstructString_safe( wzScore, g_pVGuiLocalize->Find( "#TF_ScoreBoard_Point" ), 1, wzScoreVal ); + } + else + { + g_pVGuiLocalize->ConstructString_safe( wzScore, g_pVGuiLocalize->Find( "#TF_ScoreBoard_Points" ), 1, wzScoreVal ); + } + return wzScore; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns whether the specified player index is a spectator +//----------------------------------------------------------------------------- +bool CTFClientScoreBoardDialog::ShouldShowAsSpectator( int iPlayerIndex ) +{ + if ( !g_TF_PR ) + return false; + + // see if player is connected + if ( g_TF_PR->IsConnected( iPlayerIndex ) ) + { + // either spectating or unassigned team should show in spectator list + int iTeam = g_TF_PR->GetTeam( iPlayerIndex ); + + if ( TFGameRules() ) + { + if ( TFGameRules()->IsInArenaMode() ) + { + if ( g_TF_PR->IsArenaSpectator( iPlayerIndex ) ) + return true; + + return false; + } + else if ( TFGameRules()->IsMannVsMachineMode() ) + { + if ( g_TF_PR->IsFakePlayer( iPlayerIndex ) ) + return false; + } + } + + if ( TEAM_SPECTATOR == iTeam || TEAM_UNASSIGNED == iTeam ) + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Event handler +//----------------------------------------------------------------------------- +void CTFClientScoreBoardDialog::FireGameEvent( IGameEvent *event ) +{ + const char *type = event->GetName(); + + if ( FStrEq( type, "server_spawn" ) ) + { + // set server name in scoreboard + const char *hostname = event->GetString( "hostname" ); + wchar_t wzHostName[256]; + wchar_t wzServerLabel[256]; + g_pVGuiLocalize->ConvertANSIToUnicode( hostname, wzHostName, sizeof( wzHostName ) ); + g_pVGuiLocalize->ConstructString_safe( wzServerLabel, g_pVGuiLocalize->Find( "#Scoreboard_Server" ), 1, wzHostName ); + SetDialogVariable( "server", wzServerLabel ); + const char *pMapName = event->GetString( "mapname" ); + SetDialogVariable( "mapname", GetMapDisplayName( pMapName ) ); + // m_pLocalPlayerStatsPanel->SetDialogVariable( "gametype", g_pVGuiLocalize->Find( GetMapType( pMapName ) ) ); + } +#ifdef STAGING_ONLY +// else if ( FStrEq( type, "bountymode_toggled" ) ) +// { +// m_bDisplayLevel = event->GetBool( "active" ); +// InvalidateLayout( true, true ); +// } +#endif + + if ( IsVisible() ) + { + Update(); + } +} +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +SectionedListPanel *CTFClientScoreBoardDialog::GetSelectedPlayerList( void ) +{ + SectionedListPanel *pList = NULL; + + // navigation + if ( m_pPlayerListBlue->GetSelectedItem() >= 0 ) + { + pList = m_pPlayerListBlue; + } + else if ( m_pPlayerListRed->GetSelectedItem() >= 0 ) + { + pList = m_pPlayerListRed; + } + + return pList; +} + +//----------------------------------------------------------------------------- +// Purpose: Event handler +//----------------------------------------------------------------------------- +int CTFClientScoreBoardDialog::HudElementKeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding ) +{ + if ( !IsVisible() ) + return 1; + + if ( !down ) + { + return 1; + } + + if ( tf_scoreboard_mouse_mode.GetInt() == 2 ) + { + if ( !m_bMouseActivated && ( keynum == MOUSE_LEFT || keynum == MOUSE_RIGHT ) ) + { + m_bMouseActivated = true; + InitializeInputScheme(); + return 0; + } + } + +#if defined ( _X360 ) + SectionedListPanel *pList = GetSelectedPlayerList(); + + switch( keynum ) + { + case KEY_XBUTTON_UP: + { + if ( pList ) + { + pList->MoveSelectionUp(); + } + } + return 0; + + case KEY_XBUTTON_DOWN: + { + if ( pList ) + { + pList->MoveSelectionDown(); + } + } + return 0; + + case KEY_XBUTTON_RIGHT: + { + if ( m_pPlayerListRed->GetItemCount() == 0 ) + return 0; + + // move to the red list + + // get the row we're in now + int iSelectedBlueItem = m_pPlayerListBlue->GetSelectedItem(); + + m_pPlayerListBlue->ClearSelection(); + + if ( iSelectedBlueItem >= 0 ) + { + int row = m_pPlayerListBlue->GetRowFromItemID( iSelectedBlueItem ); + + if ( row >= 0 ) + { + int iNewItem = m_pPlayerListRed->GetItemIDFromRow( row ); + + if ( iNewItem >= 0 ) + { + m_pPlayerListRed->SetSelectedItem( iNewItem ); + } + else + { + // we have fewer items. Select the last one + int iLastRow = m_pPlayerListRed->GetItemCount()-1; + + iNewItem = m_pPlayerListRed->GetItemIDFromRow( iLastRow ); + + if ( iNewItem >= 0 ) + { + m_pPlayerListRed->SetSelectedItem( iNewItem ); + } + } + } + } + } + return 0; + + case KEY_XBUTTON_LEFT: + { + if ( m_pPlayerListBlue->GetItemCount() == 0 ) + return 0; + + // move to the blue list + + // get the row we're in now + int iSelectedRedItem = m_pPlayerListRed->GetSelectedItem(); + + if ( iSelectedRedItem < 0 ) + iSelectedRedItem = 0; + + m_pPlayerListRed->ClearSelection(); + + if ( iSelectedRedItem >= 0 ) + { + int row = m_pPlayerListRed->GetRowFromItemID( iSelectedRedItem ); + + if ( row >= 0 ) + { + int iNewItem = m_pPlayerListBlue->GetItemIDFromRow( row ); + + if ( iNewItem >= 0 ) + { + m_pPlayerListBlue->SetSelectedItem( iNewItem ); + } + else + { + // we have fewer items. Select the last one + int iLastRow = m_pPlayerListBlue->GetItemCount()-1; + + iNewItem = m_pPlayerListBlue->GetItemIDFromRow( iLastRow ); + + if ( iNewItem >= 0 ) + { + m_pPlayerListBlue->SetSelectedItem( iNewItem ); + } + } + } + } + } + return 0; + + case KEY_XBUTTON_B: + { + ShowPanel( false ); + } + return 0; + + case KEY_XBUTTON_A: // Show GamerCard for the selected player + { + if ( pList ) + { + int iSelectedItem = pList->GetSelectedItem(); + + if ( iSelectedItem >= 0 ) + { + KeyValues *pInfo = pList->GetItemData( iSelectedItem ); + + DevMsg( 1, "XShowGamerCardUI for player '%s'\n", pInfo->GetString( "name" ) ); + + uint64 xuid = matchmaking->PlayerIdToXuid( pInfo->GetInt( "playerIndex" ) ); + XShowGamerCardUI( XBX_GetPrimaryUserId(), xuid ); + } + } + } + return 0; + + case KEY_XBUTTON_X: // Show player review for the selected player + { + if ( pList ) + { + int iSelectedItem = pList->GetSelectedItem(); + + if ( iSelectedItem >= 0 ) + { + KeyValues *pInfo = pList->GetItemData( iSelectedItem ); + + DevMsg( 1, "XShowPlayerReviewUI for player '%s'\n", pInfo->GetString( "name" ) ); + + uint64 xuid = matchmaking->PlayerIdToXuid( pInfo->GetInt( "playerIndex" ) ); + XShowPlayerReviewUI( XBX_GetPrimaryUserId(), xuid ); + } + } + } + return 0; + + default: + break; + } +#endif //_X360 + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool ShouldScoreBoardHandleKeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding ) +{ + // We're only looking for specific mouse input + if ( keynum == MOUSE_LEFT || keynum == MOUSE_RIGHT ) + { + CTFClientScoreBoardDialog *pScoreBoard = dynamic_cast< CTFClientScoreBoardDialog* >( gViewPortInterface->FindPanelByName( PANEL_SCOREBOARD ) ); + if ( pScoreBoard ) + { + return !pScoreBoard->HudElementKeyInput( down, keynum, pszCurrentBinding ); + } + } + + return false; +} diff --git a/game/client/tf/vgui/tf_clientscoreboard.h b/game/client/tf/vgui/tf_clientscoreboard.h new file mode 100644 index 0000000..c297035 --- /dev/null +++ b/game/client/tf/vgui/tf_clientscoreboard.h @@ -0,0 +1,183 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_SCOREBOARD_H +#define TF_SCOREBOARD_H +#ifdef _WIN32 +#pragma once +#endif + +#include "hud.h" +#include "hudelement.h" +#include "tf_hud_playerstatus.h" +#include "clientscoreboarddialog.h" +#include "tf_hud_mann_vs_machine_scoreboard.h" + +class CAvatarImagePanel; +class CTFBadgePanel; +//class CTFStatsGraph; + +//----------------------------------------------------------------------------- +// Purpose: displays the scoreboard +//----------------------------------------------------------------------------- + +class CTFClientScoreBoardDialog : public CClientScoreBoardDialog +{ +private: + DECLARE_CLASS_SIMPLE( CTFClientScoreBoardDialog, CClientScoreBoardDialog ); + +public: + CTFClientScoreBoardDialog( IViewPort *pViewPort ); + virtual ~CTFClientScoreBoardDialog(); + + virtual void Reset() OVERRIDE; + virtual void Update() OVERRIDE; + virtual void ShowPanel( bool bShow ) OVERRIDE; + virtual void OnCommand( const char *command ) OVERRIDE; + + int HudElementKeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding ); + + struct duel_panel_t + { + vgui::EditablePanel *m_pPanel; + CAvatarImagePanel *m_pAvatar; + CExLabel *m_pPlayerNameLabel; + }; + + MESSAGE_FUNC_PTR( OnItemSelected, "ItemSelected", panel ); + MESSAGE_FUNC_PTR( OnItemContextMenu, "ItemContextMenu", panel ); + void OnScoreBoardMouseRightRelease( void ); + + MESSAGE_FUNC_PARAMS( OnReportPlayer, "ReportPlayer", pData ); + +protected: + virtual void PerformLayout(); + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + + virtual void PostApplySchemeSettings( vgui::IScheme *pScheme ) {}; + + vgui::SectionedListPanel *GetPlayerListRed( void ){ return m_pPlayerListRed; } + vgui::SectionedListPanel *GetPlayerListBlue( void ){ return m_pPlayerListBlue; } + +private: + void InitPlayerList( vgui::SectionedListPanel *pPlayerList ); + void SetPlayerListImages( vgui::SectionedListPanel *pPlayerList ); + void UpdateTeamInfo(); + void UpdatePlayerList(); + void UpdateSpectatorList(); + void UpdatePlayerDetails(); + void UpdateServerTimeLeft(); + void UpdateArenaWaitingToPlayList( void ); + void ClearPlayerDetails(); + bool ShouldShowAsSpectator( int iPlayerIndex ); + bool ShouldShowAsArenaWaitingToPlay( int iPlayerIndex ); + void GetCameraUnderlayBounds( int *pX, int *pY, int *pWide, int *pTall ); + bool UseMouseMode( void ); + void InitializeInputScheme( void ); + + void AdjustForVisibleScrollbar( void ); + void UpdateBadgePanels( CUtlVector<CTFBadgePanel*> &pBadgePanels, vgui::SectionedListPanel *pPlayerList ); + + virtual void FireGameEvent( IGameEvent *event ); + + static bool TFPlayerSortFunc( vgui::SectionedListPanel *list, int itemID1, int itemID2 ); + + vgui::SectionedListPanel *GetSelectedPlayerList( void ); + + void UpdatePlayerModel(); + + vgui::SectionedListPanel *m_pPlayerListBlue; + vgui::SectionedListPanel *m_pPlayerListRed; + CExLabel *m_pLabelPlayerName; + CExLabel *m_pLabelDuelOpponentPlayerName; + vgui::ImagePanel *m_pImagePanelHorizLine; + CTFClassImage *m_pClassImage; + vgui::EditablePanel *m_pLocalPlayerStatsPanel; + vgui::EditablePanel *m_pLocalPlayerDuelStatsPanel; + duel_panel_t m_duelPanelLocalPlayer; + duel_panel_t m_duelPanelOpponent; + vgui::Menu *m_pRightClickMenu; + + CExLabel *m_pKillsLabel; + CExLabel *m_pDeathsLabel; + CExLabel *m_pAssistLabel; + CExLabel *m_pDestructionLabel; + CExLabel *m_pCapturesLabel; + CExLabel *m_pDefensesLabel; + CExLabel *m_pDominationsLabel; + CExLabel *m_pRevengeLabel; + CExLabel *m_pHealingLabel; + CExLabel *m_pInvulnsLabel; + CExLabel *m_pTeleportsLabel; + CExLabel *m_pHeadshotsLabel; + CExLabel *m_pBackstabsLabel; + CExLabel *m_pBonusLabel; + CExLabel *m_pSupportLabel; + CExLabel *m_pDamageLabel; + + CExLabel *m_pServerTimeLeftValue; + vgui::HFont m_pFontTimeLeftNumbers; + vgui::HFont m_pFontTimeLeftString; + + CTFHudMannVsMachineScoreboard *m_pMvMScoreboard; + + int m_iImageDominated; + int m_iImageDominatedDead; + int m_iImageNemesis; + int m_iImageNemesisDead; + int m_iImageStreak; + int m_iImageStreakDead; + + int m_iImageDom[SCOREBOARD_DOMINATION_ICONS]; + int m_iImageDomDead[SCOREBOARD_DOMINATION_ICONS]; + int m_iImageClass[SCOREBOARD_CLASS_ICONS]; + int m_iImageClassAlt[SCOREBOARD_CLASS_ICONS]; + + int m_iImagePing[SCOREBOARD_PING_ICONS]; + int m_iImagePingDead[SCOREBOARD_PING_ICONS]; + + int m_iTextureCamera; + + bool m_bIsPVEMode; +// bool m_bDisplayLevel; + bool m_bMouseActivated; + vgui::HFont m_hScoreFontDefault; + vgui::HFont m_hScoreFontSmallest; + + CPanelAnimationVarAliasType( int, m_iSpacerWidth, "spacer", "5", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iNemesisWidth, "nemesis_width", "20", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iMedalWidth, "medal_width", "15", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iKillstreakWidth, "killstreak_width", "20", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iKillstreakImageWidth, "killstreak_image_width", "20", "proportional_int" ); + + CTFPlayerModelPanel *m_pPlayerModelPanel; + int m_nPlayerModelPanelIndex; + + bool m_bRedScrollBarVisible; + bool m_bBlueScrollBarVisible; + int m_nExtraSpace; + + CExLabel *m_pRedTeamName; + CExLabel *m_pBlueTeamName; + + CAvatarImagePanel *m_pRedLeaderAvatarImage; + EditablePanel *m_pRedLeaderAvatarBG; + vgui::ImagePanel *m_pRedTeamImage; + CAvatarImagePanel *m_pBlueLeaderAvatarImage; + EditablePanel *m_pBlueLeaderAvatarBG; + vgui::ImagePanel *m_pBlueTeamImage; + + CUtlVector< CTFBadgePanel* > m_pBlueBadgePanels; + CUtlVector< CTFBadgePanel* > m_pRedBadgePanels; + + CHandle< C_TFPlayer > m_hSelectedPlayer; + bool m_bUsePlayerModel; +}; + +const wchar_t *GetPointsString( int iPoints ); + +#endif // TF_SCOREBOARD_H diff --git a/game/client/tf/vgui/tf_controls.cpp b/game/client/tf/vgui/tf_controls.cpp new file mode 100644 index 0000000..23f61b2 --- /dev/null +++ b/game/client/tf/vgui/tf_controls.cpp @@ -0,0 +1,1292 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" + +#include "ienginevgui.h" +#include <vgui_controls/ScrollBarSlider.h> +#include "vgui/ILocalize.h" +#include "vgui/ISurface.h" +#include "vgui/IInput.h" +#include "tf_controls.h" +#include "vgui_controls/TextImage.h" +#include "vgui_controls/PropertyPage.h" +#include "econ_item_system.h" +#include "iachievementmgr.h" +#include <vgui_controls/ListPanel.h> +#include <vgui_controls/PanelListPanel.h> +#include <vgui_controls/Label.h> +#include <vgui_controls/Button.h> +#include <vgui_controls/MessageBox.h> +#include <vgui_controls/CheckButton.h> +#include <vgui_controls/ComboBox.h> +#include <vgui_controls/TextEntry.h> +#include <../common/GameUI/cvarslider.h> +#include "filesystem.h" + +using namespace vgui; + +wchar_t* LocalizeNumberWithToken( const char* pszLocToken, int nValue ) +{ + static wchar_t wszOutString[ 128 ]; + wchar_t wszCount[ 16 ]; + _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", nValue ); + const wchar_t *wpszFormat = g_pVGuiLocalize->Find( pszLocToken ); + g_pVGuiLocalize->ConstructString_safe( wszOutString, wpszFormat, 1, wszCount ); + + return wszOutString; +} + +DECLARE_BUILD_FACTORY( CExCheckButton ); +DECLARE_BUILD_FACTORY( CTFFooter ); + +//----------------------------------------------------------------------------- +// Purpose: Xbox-specific panel that displays button icons text labels +//----------------------------------------------------------------------------- +CTFFooter::CTFFooter( Panel *parent, const char *panelName ) : BaseClass( parent, panelName ) +{ + SetVisible( true ); + SetAlpha( 0 ); + + m_nButtonGap = 32; + m_ButtonPinRight = 100; + m_FooterTall = 80; + + m_ButtonOffsetFromTop = 0; + m_ButtonSeparator = 4; + m_TextAdjust = 0; + + m_bPaintBackground = false; + m_bCenterHorizontal = true; + + m_szButtonFont[0] = '\0'; + m_szTextFont[0] = '\0'; + m_szFGColor[0] = '\0'; + m_szBGColor[0] = '\0'; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFFooter::~CTFFooter() +{ + ClearButtons(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFFooter::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + m_hButtonFont = pScheme->GetFont( ( m_szButtonFont[0] != '\0' ) ? m_szButtonFont : "GameUIButtons" ); + m_hTextFont = pScheme->GetFont( ( m_szTextFont[0] != '\0' ) ? m_szTextFont : "MenuLarge" ); + + SetFgColor( pScheme->GetColor( m_szFGColor, Color( 255, 255, 255, 255 ) ) ); + SetBgColor( pScheme->GetColor( m_szBGColor, Color( 0, 0, 0, 255 ) ) ); + + int x, y, w, h; + GetParent()->GetBounds( x, y, w, h ); + SetBounds( x, h - m_FooterTall, w, m_FooterTall ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFFooter::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + // gap between hints + m_nButtonGap = inResourceData->GetInt( "buttongap", 32 ); + m_ButtonPinRight = inResourceData->GetInt( "button_pin_right", 100 ); + m_FooterTall = inResourceData->GetInt( "tall", 80 ); + m_ButtonOffsetFromTop = inResourceData->GetInt( "buttonoffsety", 0 ); + m_ButtonSeparator = inResourceData->GetInt( "button_separator", 4 ); + m_TextAdjust = inResourceData->GetInt( "textadjust", 0 ); + + m_bCenterHorizontal = ( inResourceData->GetInt( "center", 1 ) == 1 ); + m_bPaintBackground = ( inResourceData->GetInt( "paintbackground", 0 ) == 1 ); + + // fonts for text and button + Q_strncpy( m_szTextFont, inResourceData->GetString( "fonttext", "MenuLarge" ), sizeof( m_szTextFont ) ); + Q_strncpy( m_szButtonFont, inResourceData->GetString( "fontbutton", "GameUIButtons" ), sizeof( m_szButtonFont ) ); + + // fg and bg colors + Q_strncpy( m_szFGColor, inResourceData->GetString( "fgcolor", "White" ), sizeof( m_szFGColor ) ); + Q_strncpy( m_szBGColor, inResourceData->GetString( "bgcolor", "Black" ), sizeof( m_szBGColor ) ); + + // clear the buttons because we're going to re-add them here + ClearButtons(); + + for ( KeyValues *pButton = inResourceData->GetFirstSubKey(); pButton != NULL; pButton = pButton->GetNextKey() ) + { + const char *pNameButton = pButton->GetName(); + + if ( !Q_stricmp( pNameButton, "button" ) ) + { + // Add a button to the footer + const char *pName = pButton->GetString( "name", "NULL" ); + const char *pText = pButton->GetString( "text", "NULL" ); + const char *pIcon = pButton->GetString( "icon", "NULL" ); + AddNewButtonLabel( pName, pText, pIcon ); + } + } + + InvalidateLayout( false, true ); // force ApplySchemeSettings to run +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFFooter::AddNewButtonLabel( const char *name, const char *text, const char *icon ) +{ + FooterButton_t *button = new FooterButton_t; + + button->bVisible = true; + Q_strncpy( button->name, name, sizeof( button->name ) ); + + // Button icons are a single character + wchar_t *pIcon = g_pVGuiLocalize->Find( icon ); + if ( pIcon ) + { + button->icon[0] = pIcon[0]; + button->icon[1] = '\0'; + } + else + { + button->icon[0] = '\0'; + } + + // Set the help text + wchar_t *pText = g_pVGuiLocalize->Find( text ); + if ( pText ) + { + wcsncpy( button->text, pText, wcslen( pText ) + 1 ); + } + else + { + button->text[0] = '\0'; + } + + m_Buttons.AddToTail( button ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFFooter::ShowButtonLabel( const char *name, bool show ) +{ + for ( int i = 0; i < m_Buttons.Count(); ++i ) + { + if ( !Q_stricmp( m_Buttons[ i ]->name, name ) ) + { + m_Buttons[ i ]->bVisible = show; + break; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFFooter::PaintBackground( void ) +{ + if ( !m_bPaintBackground ) + return; + + BaseClass::PaintBackground(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFFooter::Paint( void ) +{ + // inset from right edge + int wide = GetWide(); + + // center the text within the button + int buttonHeight = vgui::surface()->GetFontTall( m_hButtonFont ); + int fontHeight = vgui::surface()->GetFontTall( m_hTextFont ); + int textY = ( buttonHeight - fontHeight )/2 + m_TextAdjust; + + if ( textY < 0 ) + { + textY = 0; + } + + int y = m_ButtonOffsetFromTop; + + if ( !m_bCenterHorizontal ) + { + // draw the buttons, right to left + int x = wide - m_ButtonPinRight; + + vgui::Label label( this, "temp", L"" ); + for ( int i = m_Buttons.Count() - 1 ; i >= 0 ; --i ) + { + FooterButton_t *pButton = m_Buttons[i]; + if ( !pButton->bVisible ) + continue; + + // Get the string length + label.SetFont( m_hTextFont ); + label.SetText( pButton->text ); + label.SizeToContents(); + + int iTextWidth = label.GetWide(); + + if ( iTextWidth == 0 ) + x += m_nButtonGap; // There's no text, so remove the gap between buttons + else + x -= iTextWidth; + + // Draw the string + vgui::surface()->DrawSetTextFont( m_hTextFont ); + vgui::surface()->DrawSetTextColor( GetFgColor() ); + vgui::surface()->DrawSetTextPos( x, y + textY ); + vgui::surface()->DrawPrintText( pButton->text, wcslen( pButton->text ) ); + + // Draw the button + // back up button width and a little extra to leave a gap between button and text + x -= ( vgui::surface()->GetCharacterWidth( m_hButtonFont, pButton->icon[0] ) + m_ButtonSeparator ); + vgui::surface()->DrawSetTextFont( m_hButtonFont ); + vgui::surface()->DrawSetTextColor( 255, 255, 255, 255 ); + vgui::surface()->DrawSetTextPos( x, y ); + vgui::surface()->DrawPrintText( pButton->icon, 1 ); + + // back up to next string + x -= m_nButtonGap; + } + } + else + { + // center the buttons (as a group) + int x = wide / 2; + int totalWidth = 0; + int i = 0; + int nButtonCount = 0; + + vgui::Label label( this, "temp", L"" ); + + // need to loop through and figure out how wide our buttons and text are (with gaps between) so we can offset from the center + for ( i = 0; i < m_Buttons.Count(); ++i ) + { + FooterButton_t *pButton = m_Buttons[i]; + + if ( !pButton->bVisible ) + continue; + + // Get the string length + label.SetFont( m_hTextFont ); + label.SetText( pButton->text ); + label.SizeToContents(); + + totalWidth += vgui::surface()->GetCharacterWidth( m_hButtonFont, pButton->icon[0] ); + totalWidth += m_ButtonSeparator; + totalWidth += label.GetWide(); + + nButtonCount++; // keep track of how many active buttons we'll be drawing + } + + totalWidth += ( nButtonCount - 1 ) * m_nButtonGap; // add in the gaps between the buttons + x -= ( totalWidth / 2 ); + + for ( i = 0; i < m_Buttons.Count(); ++i ) + { + FooterButton_t *pButton = m_Buttons[i]; + + if ( !pButton->bVisible ) + continue; + + // Get the string length + label.SetFont( m_hTextFont ); + label.SetText( pButton->text ); + label.SizeToContents(); + + int iTextWidth = label.GetWide(); + + // Draw the icon + vgui::surface()->DrawSetTextFont( m_hButtonFont ); + vgui::surface()->DrawSetTextColor( 255, 255, 255, 255 ); + vgui::surface()->DrawSetTextPos( x, y ); + vgui::surface()->DrawPrintText( pButton->icon, 1 ); + x += vgui::surface()->GetCharacterWidth( m_hButtonFont, pButton->icon[0] ) + m_ButtonSeparator; + + // Draw the string + vgui::surface()->DrawSetTextFont( m_hTextFont ); + vgui::surface()->DrawSetTextColor( GetFgColor() ); + vgui::surface()->DrawSetTextPos( x, y + textY ); + vgui::surface()->DrawPrintText( pButton->text, wcslen( pButton->text ) ); + + x += iTextWidth + m_nButtonGap; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFFooter::ClearButtons( void ) +{ + m_Buttons.PurgeAndDeleteElements(); +} + +#define OPTIONS_DIR "cfg" +#define DEFAULT_OPTIONS_FILE OPTIONS_DIR "/user_default.scr" +#define OPTIONS_FILE OPTIONS_DIR "/user.scr" + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CTFAdvancedOptionsDialog::CTFAdvancedOptionsDialog(vgui::Panel *parent) : BaseClass(NULL, "TFAdvancedOptionsDialog") +{ + // Need to use the clientscheme (we're not parented to a clientscheme'd panel) + vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme"); + SetScheme(scheme); + SetProportional( true ); + + m_pListPanel = new vgui::PanelListPanel( this, "PanelListPanel" ); + + m_pList = NULL; + + m_pToolTip = new CTFTextToolTip( this ); + m_pToolTipEmbeddedPanel = new vgui::EditablePanel( this, "TooltipPanel" ); + m_pToolTipEmbeddedPanel->SetKeyBoardInputEnabled( false ); + m_pToolTipEmbeddedPanel->SetMouseInputEnabled( false ); + m_pToolTip->SetEmbeddedPanel( m_pToolTipEmbeddedPanel ); + m_pToolTip->SetTooltipDelay( 0 ); + + m_pDescription = new CInfoDescription(); + m_pDescription->InitFromFile( DEFAULT_OPTIONS_FILE ); + m_pDescription->InitFromFile( OPTIONS_FILE, false ); + m_pDescription->TransferCurrentValues( NULL ); + +// MoveToCenterOfScreen(); +// SetSizeable( false ); +// SetDeleteSelfOnClose( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CTFAdvancedOptionsDialog::~CTFAdvancedOptionsDialog() +{ + delete m_pDescription; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFAdvancedOptionsDialog::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings("resource/ui/TFAdvancedOptionsDialog.res"); + m_pListPanel->SetFirstColumnWidth( 0 ); + + CreateControls(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFAdvancedOptionsDialog::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFAdvancedOptionsDialog::OnClose() +{ + BaseClass::OnClose(); + + TFModalStack()->PopModal( this ); + MarkForDeletion(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *command - +//----------------------------------------------------------------------------- +void CTFAdvancedOptionsDialog::OnCommand( const char *command ) +{ + if ( !stricmp( command, "Ok" ) ) + { + // OnApplyChanges(); + SaveValues(); + OnClose(); + return; + } + else if ( !stricmp( command, "Close" ) ) + { + OnClose(); + return; + } + + BaseClass::OnCommand( command ); +} + +void CTFAdvancedOptionsDialog::OnKeyCodeTyped(KeyCode code) +{ + // force ourselves to be closed if the escape key it pressed + if ( code == KEY_ESCAPE ) + { + OnClose(); + } + else + { + BaseClass::OnKeyCodeTyped(code); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFAdvancedOptionsDialog::OnKeyCodePressed(KeyCode code) +{ + // force ourselves to be closed if the escape key it pressed + if ( GetBaseButtonCode( code ) == KEY_XBUTTON_B || GetBaseButtonCode( code ) == STEAMCONTROLLER_B || GetBaseButtonCode( code ) == STEAMCONTROLLER_START ) + { + OnClose(); + } + else + { + BaseClass::OnKeyCodePressed(code); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFAdvancedOptionsDialog::GatherCurrentValues() +{ + if ( !m_pDescription ) + return; + + // OK + CheckButton *pBox; + TextEntry *pEdit; + ComboBox *pCombo; + CCvarSlider *pSlider; + + mpcontrol_t *pList; + + CScriptObject *pObj; + CScriptListItem *pItem; + + char szValue[256]; + char strValue[ 256 ]; + + pList = m_pList; + while ( pList ) + { + pObj = pList->pScrObj; + + if ( pObj->type == O_CATEGORY ) + { + pList = pList->next; + continue; + } + + if ( !pList->pControl ) + { + pObj->SetCurValue( pObj->defValue ); + pList = pList->next; + continue; + } + + switch ( pObj->type ) + { + case O_BOOL: + pBox = (CheckButton *)pList->pControl; + sprintf( szValue, "%s", pBox->IsSelected() ? "1" : "0" ); + break; + case O_NUMBER: + pEdit = ( TextEntry * )pList->pControl; + pEdit->GetText( strValue, sizeof( strValue ) ); + sprintf( szValue, "%s", strValue ); + break; + case O_STRING: + pEdit = ( TextEntry * )pList->pControl; + pEdit->GetText( strValue, sizeof( strValue ) ); + sprintf( szValue, "%s", strValue ); + break; + case O_LIST: + { + pCombo = (ComboBox *)pList->pControl; + // pCombo->GetText( strValue, sizeof( strValue ) ); + int activeItem = pCombo->GetActiveItem(); + + pItem = pObj->pListItems; + // int n = (int)pObj->fdefValue; + + while ( pItem ) + { + if (!activeItem--) + break; + + pItem = pItem->pNext; + } + + if ( pItem ) + { + sprintf( szValue, "%s", pItem->szValue ); + } + else // Couln't find index + { + //assert(!("Couldn't find string in list, using default value")); + sprintf( szValue, "%s", pObj->defValue ); + } + break; + } + case O_SLIDER: + pSlider = ( CCvarSlider * )pList->pControl; + sprintf( szValue, "%.2f", pSlider->GetSliderValue() ); + break; + } + + // Remove double quotes and % characters + UTIL_StripInvalidCharacters( szValue, sizeof(szValue) ); + + V_strcpy_safe( strValue, szValue ); + + pObj->SetCurValue( strValue ); + + pList = pList->next; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFAdvancedOptionsDialog::CreateControls() +{ + DestroyControls(); + + // Go through desciption creating controls + CScriptObject *pObj; + + pObj = m_pDescription->pObjList; + + // Build out the clan dropdown + CScriptObject *pClanObj = m_pDescription->FindObject( "cl_clanid" ); + ISteamFriends *pFriends = steamapicontext->SteamFriends(); + if ( pFriends && pClanObj ) + { + pClanObj->RemoveAndDeleteAllItems(); + int iGroupCount = pFriends->GetClanCount(); + pClanObj->AddItem( new CScriptListItem( "#Cstrike_ClanTag_None", "0" ) ); + for ( int k = 0; k < iGroupCount; ++ k ) + { + CSteamID clanID = pFriends->GetClanByIndex( k ); + const char *pName = pFriends->GetClanName( clanID ); + const char *pTag = pFriends->GetClanTag( clanID ); + + char id[12]; + Q_snprintf( id, sizeof( id ), "%d", clanID.GetAccountID() ); + pClanObj->AddItem( new CScriptListItem( CFmtStr( "%s (%s)", pTag, pName ), id ) ); + } + } + + mpcontrol_t *pCtrl; + + CheckButton *pBox; + TextEntry *pEdit; + ComboBox *pCombo; + CCvarSlider *pSlider; + CScriptListItem *pListItem; + + Panel *objParent = m_pListPanel; + + IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); + vgui::HFont hTextFont = pScheme->GetFont( "HudFontSmallestBold", true ); + Color tanDark = pScheme->GetColor( "TanDark", Color(255,0,0,255) ); + + while ( pObj ) + { + if ( pObj->type == O_OBSOLETE ) + { + pObj = pObj->pNext; + continue; + } + + pCtrl = new mpcontrol_t( objParent, "mpcontrol_t" ); + pCtrl->type = pObj->type; + + // Force it to invalidate scheme now, so we can change color afterwards and have it persist + pCtrl->InvalidateLayout( true, true ); + + switch ( pCtrl->type ) + { + case O_BOOL: + pBox = new CheckButton( pCtrl, "DescCheckButton", pObj->prompt ); + pBox->SetSelected( pObj->fdefValue != 0.0f ? true : false ); + + pCtrl->pControl = (Panel *)pBox; + pBox->SetFont( hTextFont ); + + pBox->InvalidateLayout( true, true ); + + // This is utterly fucking retarded. + pBox->SetFgColor( tanDark ); + pBox->SetDefaultColor( tanDark, pBox->GetBgColor() ); + pBox->SetArmedColor( tanDark, pBox->GetBgColor() ); + pBox->SetDepressedColor( tanDark, pBox->GetBgColor() ); + pBox->SetSelectedColor( tanDark, pBox->GetBgColor() ); + pBox->SetHighlightColor( tanDark ); + pBox->GetCheckImage()->SetColor( tanDark ); + break; + case O_STRING: + case O_NUMBER: + pEdit = new TextEntry( pCtrl, "DescTextEntry"); + pEdit->InsertString(pObj->defValue); + pCtrl->pControl = (Panel *)pEdit; + pEdit->SetFont( hTextFont ); + + pEdit->InvalidateLayout( true, true ); + pEdit->SetBgColor( Color(0,0,0,255) ); + break; + case O_LIST: + { + pCombo = new ComboBox( pCtrl, "DescComboBox", 5, false ); + + // track which row matches the current value + int iRow = -1; + int iCount = 0; + pListItem = pObj->pListItems; + while ( pListItem ) + { + if ( iRow == -1 && !Q_stricmp( pListItem->szValue, pObj->curValue ) ) + iRow = iCount; + + pCombo->AddItem( pListItem->szItemText, NULL ); + pListItem = pListItem->pNext; + ++iCount; + } + + + pCombo->ActivateItemByRow( iRow ); + + pCtrl->pControl = (Panel *)pCombo; + pCombo->SetFont( hTextFont ); + } + break; + case O_SLIDER: + pSlider = new CCvarSlider( pCtrl, "DescSlider", "Test", pObj->fMin, pObj->fMax, pObj->cvarname, false ); + pCtrl->pControl = (Panel *)pSlider; + break; + case O_CATEGORY: + pCtrl->SetBorder( pScheme->GetBorder("OptionsCategoryBorder") ); + break; + default: + break; + } + + if ( pCtrl->type != O_BOOL ) + { + pCtrl->pPrompt = new vgui::Label( pCtrl, "DescLabel", "" ); + pCtrl->pPrompt->SetContentAlignment( vgui::Label::a_west ); + pCtrl->pPrompt->SetTextInset( 5, 0 ); + pCtrl->pPrompt->SetText( pObj->prompt ); + pCtrl->pPrompt->SetFont( hTextFont ); + + pCtrl->pPrompt->InvalidateLayout( true, true ); + + if ( pCtrl->type == O_CATEGORY ) + { + pCtrl->pPrompt->SetFont( pScheme->GetFont( "HudFontSmallBold", true ) ); + pCtrl->pPrompt->SetFgColor( pScheme->GetColor( "TanLight", Color(255,0,0,255) ) ); + } + else + { + pCtrl->pPrompt->SetFgColor( tanDark ); + } + } + + pCtrl->pScrObj = pObj; + + switch ( pCtrl->type ) + { + case O_BOOL: + case O_STRING: + case O_NUMBER: + case O_LIST: + case O_CATEGORY: + pCtrl->SetSize( m_iControlW, m_iControlH ); + break; + case O_SLIDER: + pCtrl->SetSize( m_iSliderW, m_iSliderH ); + break; + default: + break; + } + + // Hook up the tooltip, if the entry has one + if ( pObj->tooltip && pObj->tooltip[0] ) + { + if ( pCtrl->pPrompt ) + { + pCtrl->pPrompt->SetTooltip( m_pToolTip, pObj->tooltip ); + } + else + { + pCtrl->SetTooltip( m_pToolTip, pObj->tooltip ); + pCtrl->pControl->SetTooltip( m_pToolTip, pObj->tooltip ); + } + } + + m_pListPanel->AddItem( NULL, pCtrl ); + + // Link it in + if ( !m_pList ) + { + m_pList = pCtrl; + pCtrl->next = NULL; + } + else + { + mpcontrol_t *p; + p = m_pList; + while ( p ) + { + if ( !p->next ) + { + p->next = pCtrl; + pCtrl->next = NULL; + break; + } + p = p->next; + } + } + + pObj = pObj->pNext; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFAdvancedOptionsDialog::DestroyControls() +{ + mpcontrol_t *p, *n; + + p = m_pList; + while ( p ) + { + n = p->next; + // + if ( p->pControl ) + { + p->pControl->MarkForDeletion(); + p->pControl = NULL; + } + if ( p->pPrompt ) + { + p->pPrompt->MarkForDeletion(); + p->pPrompt = NULL; + } + delete p; + p = n; + } + + m_pList = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFAdvancedOptionsDialog::SaveValues() +{ + // Get the values from the controls: + GatherCurrentValues(); + + // Create the game.cfg file + if ( m_pDescription ) + { + FileHandle_t fp; + + // Add settings to config.cfg + m_pDescription->WriteToConfig(); + + g_pFullFileSystem->CreateDirHierarchy( OPTIONS_DIR ); + fp = g_pFullFileSystem->Open( OPTIONS_FILE, "wb" ); + if ( fp ) + { + m_pDescription->WriteToScriptFile( fp ); + g_pFullFileSystem->Close( fp ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFAdvancedOptionsDialog::Deploy( void ) +{ + SetVisible( true ); + MakePopup(); + MoveToFront(); + SetKeyBoardInputEnabled(true); + SetMouseInputEnabled(true); + TFModalStack()->PushModal( this ); + + // Center it, keeping requested size + int x, y, ww, wt, wide, tall; + vgui::surface()->GetWorkspaceBounds( x, y, ww, wt ); + GetSize(wide, tall); + SetPos(x + ((ww - wide) / 2), y + ((wt - tall) / 2)); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTextToolTip::PerformLayout() +{ + if ( !ShouldLayout() ) + return; + + _isDirty = false; + + // Resize our text labels to fit. + int iW = m_pEmbeddedPanel->GetWide(); + int iH = 0; + for (int i = 0; i < m_pEmbeddedPanel->GetChildCount(); i++) + { + vgui::Label *pLabel = dynamic_cast<vgui::Label*>( m_pEmbeddedPanel->GetChild(i) ); + if ( !pLabel ) + continue; + + // Only checking to see if we have any text + char szTmp[2]; + pLabel->GetText( szTmp, sizeof(szTmp) ); + if ( !szTmp[0] ) + continue; + + int iLX, iLY; + pLabel->GetPos( iLX, iLY ); + + int iMaxWidth = m_pEmbeddedPanel->GetWide() - (iLX * 2); + pLabel->GetTextImage()->ResizeImageToContentMaxWidth( iMaxWidth ); + pLabel->SizeToContents(); + pLabel->SetWide( iMaxWidth ); + pLabel->InvalidateLayout(true); + + int iX, iY; + pLabel->GetPos( iX, iY ); + iW = MAX( iW, ( pLabel->GetWide() + (iX * 2) ) ); + + if ( iH == 0 ) + { + iH += MAX( iH, pLabel->GetTall() + (iY * 2) ); + } + else + { + iH += MAX( iH, pLabel->GetTall() ); + } + } + m_pEmbeddedPanel->SetSize( m_pEmbeddedPanel->GetWide(), iH ); + + m_pEmbeddedPanel->SetVisible(true); + + PositionWindow( m_pEmbeddedPanel ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTextToolTip::PositionWindow( Panel *pTipPanel ) +{ + int iTipW, iTipH; + pTipPanel->GetSize( iTipW, iTipH ); + + int cursorX, cursorY; + input()->GetCursorPos(cursorX, cursorY); + + int px, py, wide, tall; + ipanel()->GetAbsPos( m_pEmbeddedPanel->GetParent()->GetVPanel(), px, py ); + m_pEmbeddedPanel->GetParent()->GetSize(wide, tall); + + if ( !m_pEmbeddedPanel->IsPopup() ) + { + // Move the cursor into our parent space + cursorX -= px; + cursorY -= py; + } + + if (wide - iTipW > cursorX) + { + cursorY += 20; + // menu hanging right + if (tall - iTipH > cursorY) + { + // menu hanging down + pTipPanel->SetPos(cursorX, cursorY); + } + else + { + // menu hanging up + pTipPanel->SetPos(cursorX, cursorY - iTipH - 20); + } + } + else + { + // menu hanging left + if (tall - iTipH > cursorY) + { + // menu hanging down + pTipPanel->SetPos( Max( 0, cursorX - iTipW ), cursorY); + } + else + { + // menu hanging up + pTipPanel->SetPos( Max( 0, cursorX - iTipW ), cursorY - iTipH - 20 ); + } + } +} + +static vgui::DHANDLE<CTFAdvancedOptionsDialog> g_pTFAdvancedOptionsDialog; + +//----------------------------------------------------------------------------- +// Purpose: Callback to open the game menus +//----------------------------------------------------------------------------- +void CL_OpenTFAdvancedOptionsDialog( const CCommand &args ) +{ + if ( g_pTFAdvancedOptionsDialog.Get() == NULL ) + { + g_pTFAdvancedOptionsDialog = vgui::SETUP_PANEL( new CTFAdvancedOptionsDialog( NULL ) ); + } + + g_pTFAdvancedOptionsDialog->Deploy(); +} + +// the console commands +static ConCommand opentf2options( "opentf2options", &CL_OpenTFAdvancedOptionsDialog, "Displays the TF2 Advanced Options dialog." ); + +//----------------------------------------------------------------------------- +// Purpose: A scroll bar that can have specified width +//----------------------------------------------------------------------------- +class CExScrollBar : public ScrollBar +{ + DECLARE_CLASS_SIMPLE( CExScrollBar, ScrollBar ); +public: + + CExScrollBar( Panel *parent, const char *name, bool bVertical ) + : ScrollBar( parent, name, bVertical ) + {} + + virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE + { + // Deliberately skip ScrollBar + Panel::ApplySchemeSettings( pScheme ); + } +}; + +DECLARE_BUILD_FACTORY( CExScrollingEditablePanel ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CExScrollingEditablePanel::CExScrollingEditablePanel( Panel *pParent, const char *pszName ) + : EditablePanel( pParent, pszName ) + , m_nLastScrollValue( 0 ) + , m_bUseMouseWheelToScroll( true ) +{ + m_pScrollBar = new CExScrollBar( this, "ScrollBar", true ); + m_pScrollBar->AddActionSignalTarget( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CExScrollingEditablePanel::~CExScrollingEditablePanel() +{} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExScrollingEditablePanel::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + KeyValues *pScrollbarKV = inResourceData->FindKey( "Scrollbar" ); + if ( pScrollbarKV ) + { + m_pScrollBar->ApplySettings( pScrollbarKV ); + } + + m_bUseMouseWheelToScroll = inResourceData->GetBool( "allow_mouse_wheel_to_scroll", true ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExScrollingEditablePanel::OnSizeChanged( int newWide, int newTall ) +{ + BaseClass::OnSizeChanged( newWide, newTall ); + + int nDelta = m_nLastScrollValue; + // Go through all our children and move them BACK into position + int nNumChildren = GetChildCount(); + for ( int i=0; i < nNumChildren; ++i ) + { + Panel* pChild = GetChild( i ); + + if ( pChild == m_pScrollBar ) + continue; + + EditablePanel* pEditableChild = dynamic_cast< EditablePanel* >( pChild ); + if ( pEditableChild && pEditableChild->ShouldSkipAutoResize() ) + continue; + + int x,y; + pChild->GetPos( x, y ); + + pChild->SetPos( x, y - nDelta ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExScrollingEditablePanel::PerformLayout() +{ + BaseClass::PerformLayout(); + + int nFurthestY = 0; + + // Go through all our children and find the lowest point on the lowest child + // that we'd need to scroll to + int nNumChildren = GetChildCount(); + for ( int i=0; i < nNumChildren; ++i ) + { + Panel* pChild = GetChild( i ); + + if ( pChild == m_pScrollBar ) + continue; + + int x,y,wide,tall; + pChild->GetBounds( x, y, wide, tall ); + + // Offset by our scroll value + y += m_nLastScrollValue; + + if ( m_bRestrictWidth ) + { + int nMaxWide = Min( x + wide, GetWide() - m_pScrollBar->GetWide() ); + pChild->SetWide( nMaxWide - x ); + } + + nFurthestY = Max( y + tall, nFurthestY ); + } + + int nMaxRange = nFurthestY + m_iBottomBuffer; + m_pScrollBar->SetRange( 0, nMaxRange ); + m_pScrollBar->SetRangeWindow( GetTall() ); + + OnScrollBarSliderMoved(); +} + +//----------------------------------------------------------------------------- +// Called when the scroll bar moves +//----------------------------------------------------------------------------- +void CExScrollingEditablePanel::OnScrollBarSliderMoved() +{ + // Figure out how far they just scrolled + int nScrollAmount = m_pScrollBar->GetValue(); + int nDelta = nScrollAmount - m_nLastScrollValue; + + if ( nDelta == 0 ) + return; + + ShiftChildren( nDelta ); + + m_nLastScrollValue = nScrollAmount; +} + +void CExScrollingEditablePanel::ShiftChildren( int nDistance ) +{ + // Go through all our children and move them + int nNumChildren = GetChildCount(); + for ( int i=0; i < nNumChildren; ++i ) + { + Panel* pChild = GetChild( i ); + + if ( pChild == m_pScrollBar ) + continue; + + int x,y; + pChild->GetPos( x, y ); + + pChild->SetPos( x, y - nDistance ); + } +} + +//----------------------------------------------------------------------------- +// respond to mouse wheel events +//----------------------------------------------------------------------------- +void CExScrollingEditablePanel::OnMouseWheeled( int delta ) +{ + if ( !m_bUseMouseWheelToScroll ) + { + BaseClass::OnMouseWheeled( delta ); + return; + } + + int val = m_pScrollBar->GetValue(); + val -= ( delta * m_iScrollStep ); + m_pScrollBar->SetValue( val ); +} + +DECLARE_BUILD_FACTORY( CScrollableList ); +//----------------------------------------------------------------------------- +// Clearnup +//----------------------------------------------------------------------------- +CScrollableList::~CScrollableList() +{ + ClearAutoLayoutPanels(); +} + +void CScrollableList::PerformLayout() +{ + int nYpos = -m_nLastScrollValue; + + for( int i=0; i<m_vecAutoLayoutPanels.Count(); ++i ) + { + LayoutInfo_t layout = m_vecAutoLayoutPanels[ i ]; + nYpos += layout.m_nGap; + + layout.m_pPanel->SetPos( layout.m_pPanel->GetXPos(), nYpos ); + + nYpos += layout.m_pPanel->GetTall(); + } + + BaseClass::PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Add a panel to the bottom +//----------------------------------------------------------------------------- +void CScrollableList::AddPanel( Panel* pPanel, int nGap ) +{ + // We're the captain now + pPanel->SetParent( this ); + pPanel->SetAutoDelete( false ); + + auto idx = m_vecAutoLayoutPanels.AddToTail(); + LayoutInfo_t& layout = m_vecAutoLayoutPanels[ idx ]; + layout.m_pPanel = pPanel; + layout.m_nGap = nGap; + + // Need to do a perform layout so we get sized correctly + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Delete any panels we own +//----------------------------------------------------------------------------- +void CScrollableList::ClearAutoLayoutPanels() +{ + FOR_EACH_VEC( m_vecAutoLayoutPanels, i ) + { + m_vecAutoLayoutPanels[ i ].m_pPanel->MarkForDeletion(); + } + + m_vecAutoLayoutPanels.Purge(); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CExpandablePanel::CExpandablePanel( Panel* pParent, const char* pszName ) + : vgui::EditablePanel( pParent, pszName ) + , m_bExpanded( false ) + , m_flAnimEndTime( 0.f ) + , m_flResizeTime( 0.f ) +{} + + +//----------------------------------------------------------------------------- +// Set spcific collapsed state +//----------------------------------------------------------------------------- +void CExpandablePanel::SetCollapsed( bool bCollapsed ) +{ + if ( bCollapsed == m_bExpanded ) + { + ToggleCollapse(); + } +} + + +//----------------------------------------------------------------------------- +// Toggle collapsed state +//----------------------------------------------------------------------------- +void CExpandablePanel::ToggleCollapse() +{ + m_bExpanded = !m_bExpanded; + // Allow for quick bounce-back if they click while we're already animating + float flEndTime = RemapValClamped( GetPercentAnimated(), 0.f, 1.f, 0.f, m_flResizeTime ); + m_flAnimEndTime = Plat_FloatTime() + flEndTime; + + OnToggleCollapse( m_bExpanded ); +} + +//----------------------------------------------------------------------------- +// Toggle collapsed state +//----------------------------------------------------------------------------- +void CExpandablePanel::OnCommand( const char *command ) +{ + if ( FStrEq( "toggle_collapse", command ) ) + { + ToggleCollapse(); + + return; + } + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Do collapsing interpolation +//----------------------------------------------------------------------------- +void CExpandablePanel::OnThink() +{ + BaseClass::OnThink(); + + float flTimeProgress = Gain( GetPercentAnimated(), 0.8f ); + + const int& nStartHeight = m_bExpanded ? m_nCollapsedHeight : m_nExpandedHeight; + const int& nEndHeight = m_bExpanded ? m_nExpandedHeight : m_nCollapsedHeight; + int nCurrentHeight = RemapValClamped( flTimeProgress, 0.f, 1.f, nStartHeight, nEndHeight ); + + if ( nCurrentHeight != GetTall() ) + { + SetTall( nCurrentHeight ); + Panel* pParent = GetParent(); + if ( pParent ) + { + pParent->InvalidateLayout(); + } + } +} + +//----------------------------------------------------------------------------- +// Where we're at in our interpolation +//----------------------------------------------------------------------------- +float CExpandablePanel::GetPercentAnimated() const +{ + return RemapValClamped( Plat_FloatTime() - ( m_flAnimEndTime - m_flResizeTime ), 0.f, m_flResizeTime, 0.f, 1.f ); +} + diff --git a/game/client/tf/vgui/tf_controls.h b/game/client/tf/vgui/tf_controls.h new file mode 100644 index 0000000..d888ef6 --- /dev/null +++ b/game/client/tf/vgui/tf_controls.h @@ -0,0 +1,308 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_CONTROLS_H +#define TF_CONTROLS_H +#ifdef _WIN32 +#pragma once +#endif + +#include <vgui/IScheme.h> +#include <vgui/KeyCode.h> +#include <KeyValues.h> +#include <vgui/IVGui.h> +#include <vgui_controls/ScrollBar.h> +#include <vgui_controls/EditablePanel.h> +#include <vgui_controls/Button.h> +#include <vgui_controls/Label.h> +#include <vgui_controls/RichText.h> +#include "utlvector.h" +#include "vgui_controls/PHandle.h" +#include <vgui_controls/Tooltip.h> +#include "econ_controls.h" +#include "sc_hinticon.h" +#if defined( TF_CLIENT_DLL ) +#include "tf_shareddefs.h" +#include "tf_imagepanel.h" +#endif +#include <vgui_controls/Frame.h> +#include <../common/GameUI/scriptobject.h> +#include <vgui/KeyCode.h> +#include <vgui_controls/Tooltip.h> +#include <vgui_controls/CheckButton.h> + +wchar_t* LocalizeNumberWithToken( const char* pszLocToken, int nValue ); + +//----------------------------------------------------------------------------- +// Purpose: Xbox-specific panel that displays button icons text labels +//----------------------------------------------------------------------------- +class CTFFooter : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CTFFooter, vgui::EditablePanel ); + +public: + CTFFooter( Panel *parent, const char *panelName ); + virtual ~CTFFooter(); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void ApplySettings( KeyValues *pResourceData ); + virtual void Paint( void ); + virtual void PaintBackground( void ); + + void ShowButtonLabel( const char *name, bool show = true ); + void AddNewButtonLabel( const char *name, const char *text, const char *icon ); + void ClearButtons(); + +private: + struct FooterButton_t + { + bool bVisible; + char name[MAX_PATH]; + wchar_t text[MAX_PATH]; + wchar_t icon[3]; // icon can be one or two characters + }; + + CUtlVector< FooterButton_t* > m_Buttons; + + bool m_bPaintBackground; // fill the background? + int m_nButtonGap; // space between buttons + int m_FooterTall; // height of the footer + int m_ButtonOffsetFromTop; // how far below the top the buttons should be drawn + int m_ButtonSeparator; // space between the button icon and text + int m_TextAdjust; // extra adjustment for the text (vertically)...text is centered on the button icon and then this value is applied + bool m_bCenterHorizontal; // center buttons horizontally? + int m_ButtonPinRight; // if not centered, this is the distance from the right margin that we use to start drawing buttons (right to left) + + char m_szTextFont[64]; // font for the button text + char m_szButtonFont[64]; // font for the button icon + char m_szFGColor[64]; // foreground color (text) + char m_szBGColor[64]; // background color (fill color) + + vgui::HFont m_hButtonFont; + vgui::HFont m_hTextFont; +}; + +//----------------------------------------------------------------------------- +// Purpose: Tooltip for the main menu. Isn't a panel, it just wraps the +// show/hide/position handling for the embedded panel. +//----------------------------------------------------------------------------- +class CMainMenuToolTip : public vgui::BaseTooltip +{ + DECLARE_CLASS_SIMPLE( CMainMenuToolTip, vgui::BaseTooltip ); +public: + CMainMenuToolTip(vgui::Panel *parent, const char *text = NULL) : vgui::BaseTooltip( parent, text ) + { + m_pEmbeddedPanel = NULL; + } + virtual ~CMainMenuToolTip() {} + + virtual void SetText(const char *text); + const char *GetText() { return NULL; } + + virtual void HideTooltip(); + virtual void PerformLayout(); + + void SetEmbeddedPanel( vgui::EditablePanel *pPanel ) + { + m_pEmbeddedPanel = pPanel; + } + +protected: + vgui::EditablePanel *m_pEmbeddedPanel; +}; + +//----------------------------------------------------------------------------- +// Purpose: Simple TF-styled text tooltip +//----------------------------------------------------------------------------- +class CTFTextToolTip : public CMainMenuToolTip +{ + DECLARE_CLASS_SIMPLE( CTFTextToolTip, CMainMenuToolTip ); +public: + CTFTextToolTip(vgui::Panel *parent, const char *text = NULL) : CMainMenuToolTip( parent, text ) + { + } + virtual void PerformLayout(); + virtual void PositionWindow( vgui::Panel *pTipPanel ); + virtual void SetText(const char *text) + { + _isDirty = true; + BaseClass::SetText( text ); + } +}; + + +//----------------------------------------------------------------------------- +// Purpose: Displays a TF specific list of options +// This is essentially a TF-styled version of the GameUI Advanced Multiplayer Options Dialog +//----------------------------------------------------------------------------- +class CTFAdvancedOptionsDialog : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CTFAdvancedOptionsDialog, vgui::EditablePanel ); + +public: + CTFAdvancedOptionsDialog(vgui::Panel *parent); + ~CTFAdvancedOptionsDialog(); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void ApplySettings( KeyValues *pResourceData ); + + void Deploy( void ); + +private: + + void CreateControls(); + void DestroyControls(); + void GatherCurrentValues(); + void SaveValues(); + + virtual void OnCommand( const char *command ); + virtual void OnClose(); + virtual void OnKeyCodeTyped(vgui::KeyCode code); + virtual void OnKeyCodePressed(vgui::KeyCode code); + +private: + CInfoDescription *m_pDescription; + mpcontrol_t *m_pList; + vgui::PanelListPanel *m_pListPanel; + CTFTextToolTip *m_pToolTip; + vgui::EditablePanel *m_pToolTipEmbeddedPanel; + + CPanelAnimationVarAliasType( int, m_iControlW, "control_w", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iControlH, "control_h", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iSliderW, "slider_w", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iSliderH, "slider_h", "0", "proportional_int" ); +}; + +//----------------------------------------------------------------------------- +// Purpose: Scrollable panel where you can define children within the .res file +//----------------------------------------------------------------------------- +class CExScrollingEditablePanel : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CExScrollingEditablePanel, vgui::EditablePanel ); +public: + CExScrollingEditablePanel( Panel *pParent, const char *pszName ); + virtual ~CExScrollingEditablePanel(); + + virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE; + virtual void PerformLayout() OVERRIDE; + virtual void OnSizeChanged( int newWide, int newTall ) OVERRIDE; + + MESSAGE_FUNC( OnScrollBarSliderMoved, "ScrollBarSliderMoved" ); + virtual void OnMouseWheeled( int delta ) OVERRIDE; // respond to mouse wheel events + void ResetScrollAmount() { m_nLastScrollValue = 0; m_pScrollBar->SetValue(0); } +protected: + + void ShiftChildren( int nDistance ); + + vgui::ScrollBar *m_pScrollBar; + int m_nLastScrollValue; + bool m_bUseMouseWheelToScroll; + CPanelAnimationVarAliasType( int, m_iScrollStep, "scroll_step", "10", "proportional_xpos" ); + CPanelAnimationVarAliasType( int, m_iBottomBuffer, "bottom_buffer", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_bRestrictWidth, "restrict_width", "1", "proportional_int" ); +}; + +//----------------------------------------------------------------------------- +// An extension of CExScrollingEditablePanel where panels can be added to form +// a list. +//----------------------------------------------------------------------------- +class CScrollableList : public CExScrollingEditablePanel +{ + DECLARE_CLASS_SIMPLE( CScrollableList, CExScrollingEditablePanel ); +public: + CScrollableList( Panel* pParent, const char* pszName ) + : CExScrollingEditablePanel( pParent, pszName ) + {} + + virtual ~CScrollableList(); + + virtual void PerformLayout() OVERRIDE; + + void AddPanel( Panel* pPanel, int nGap ); + void ClearAutoLayoutPanels(); + +private: + + struct LayoutInfo_t + { + Panel* m_pPanel; + int m_nGap; + }; + + CUtlVector< LayoutInfo_t > m_vecAutoLayoutPanels; +}; + +//----------------------------------------------------------------------------- +// A checkbox where keyvalue data can be stored and retrieved (typically in OnCheckButtonChecked) +// so that you don't need a pointer to the checkbox in order to determine WHICH +// checkbox got checked. +//----------------------------------------------------------------------------- +class CExCheckButton : public vgui::CheckButton +{ + DECLARE_CLASS_SIMPLE( CExCheckButton, vgui::CheckButton ); +public: + CExCheckButton( Panel* pParent, const char* pszName ) + : BaseClass( pParent, pszName, NULL ) + , m_pKVData( NULL ) + {} + + virtual ~CExCheckButton() + { + if ( m_pKVData ) + m_pKVData->deleteThis(); + } + + void SetData( KeyValues* pKVData ) + { + if ( m_pKVData ) + { + m_pKVData->deleteThis(); + m_pKVData = NULL; + } + + m_pKVData = pKVData; + } + + KeyValues* GetData() const + { + return m_pKVData; + } + +private: + KeyValues *m_pKVData; +}; + +class CExpandablePanel : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CExpandablePanel, vgui::EditablePanel ); +public: + CExpandablePanel( Panel* pParent, const char* pszName ); + + virtual void OnCommand( const char *command ) OVERRIDE; + virtual void OnThink() OVERRIDE; + + virtual void OnToggleCollapse( bool bIsExpanded ) {} + + void SetCollapsed( bool bCollapsed ); + void ToggleCollapse(); + bool BIsExpanded() const { return m_bExpanded; } + void SetExpandedHeight( int nNewHeight ); + float GetPercentAnimated() const; + +protected: + + CPanelAnimationVarAliasType( float, m_flResizeTime, "resize_time", "0.4", "float" ); + CPanelAnimationVarAliasType( int, m_nCollapsedHeight, "collapsed_height", "17", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_nExpandedHeight, "expanded_height", "50", "proportional_int" ); + +private: + + bool m_bExpanded; + float m_flAnimEndTime; +}; + +#endif // TF_CONTROLS_H diff --git a/game/client/tf/vgui/tf_giveawayitempanel.cpp b/game/client/tf/vgui/tf_giveawayitempanel.cpp new file mode 100644 index 0000000..e85bd06 --- /dev/null +++ b/game/client/tf/vgui/tf_giveawayitempanel.cpp @@ -0,0 +1,447 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "vgui/IInput.h" +#include <vgui/IVGui.h> +#include <vgui/IScheme.h> +#include "tf_giveawayitempanel.h" +#include "iclientmode.h" +#include "baseviewport.h" +#include "econ_entity.h" +#include "c_tf_player.h" +#include "gamestringpool.h" +#include "vgui_controls/TextImage.h" +#include "vgui_controls/Label.h" +#include "vgui_controls/Button.h" +#include "econ_item_system.h" +#include "ienginevgui.h" +#include "achievementmgr.h" +#include "fmtstr.h" +#include "c_tf_playerresource.h" +#include "tf_gamerules.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CGiveawayPlayerPanel::CGiveawayPlayerPanel( vgui::Panel *parent, const char *name ) : BaseClass(parent,name) +{ + m_pNameLabel = new vgui::Label( this, "name_label", "" ); + m_pScoreLabel = new vgui::Label( this, "score_label", "" ); + + REGISTER_COLOR_AS_OVERRIDABLE( m_PlayerColorLocal, "fgcolor_local" ); + REGISTER_COLOR_AS_OVERRIDABLE( m_PlayerColorOther, "fgcolor_other" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGiveawayPlayerPanel::PerformLayout( void ) +{ + BaseClass::PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGiveawayPlayerPanel::SetPlayer( CTFPlayer *pPlayer ) +{ + m_iBonus = 0; + + if ( pPlayer ) + { + SetVisible( true ); + + if ( g_TF_PR ) + { + int playerIndex = pPlayer->entindex(); + const char *pszName = g_TF_PR->GetPlayerName( playerIndex ); + m_iBonus = pPlayer->m_Shared.GetItemFindBonus(); + + SetDialogVariable( "playername", pszName ); + SetDialogVariable( "playerscore", m_iBonus ); + } + + m_iPlayerIndex = pPlayer->entindex(); + + if ( pPlayer->IsLocalPlayer() ) + { + m_pNameLabel->SetFgColor( m_PlayerColorLocal ); + m_pScoreLabel->SetFgColor( m_PlayerColorLocal ); + } + else + { + m_pNameLabel->SetFgColor( m_PlayerColorOther ); + m_pScoreLabel->SetFgColor( m_PlayerColorOther ); + } + } + else + { + SetVisible( false ); + m_iPlayerIndex = 0; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGiveawayPlayerPanel::SpinBonus( void ) +{ + SetDialogVariable( "playerscore", RandomInt( PLAYER_ROLL_MIN, PLAYER_ROLL_MAX ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGiveawayPlayerPanel::LockBonus( int iRoll ) +{ + m_iRoll = iRoll; + SetDialogVariable( "playerscore", m_iBonus + m_iRoll ); +} + +//=========================================================================================================================== + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFGiveawayItemPanel::CTFGiveawayItemPanel( IViewPort *pViewPort ) : Frame( NULL, PANEL_GIVEAWAY_ITEM ) +{ + m_pViewPort = pViewPort; + + // load the new scheme early!! + SetScheme( "ClientScheme" ); + + SetTitleBarVisible( false ); + SetMinimizeButtonVisible( false ); + SetMaximizeButtonVisible( false ); + SetCloseButtonVisible( false ); + SetSizeable( false ); + SetMoveable( false ); + SetProportional( true ); + SetVisible( false ); + SetKeyBoardInputEnabled( true ); + SetMouseInputEnabled( true ); + + m_pModelPanel = new CItemModelPanel( this, "item_panel" ); + m_pPlayerListPanelKVs = NULL; + + m_iNumActivePlayers = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFGiveawayItemPanel::~CTFGiveawayItemPanel( void ) +{ + if ( m_pPlayerListPanelKVs ) + { + m_pPlayerListPanelKVs->deleteThis(); + m_pPlayerListPanelKVs = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFGiveawayItemPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "Resource/UI/GiveawayItemPanel.res" ); + + for ( int i = 0; i < m_aPlayerList.Count(); i++ ) + { + m_aPlayerList[i]->ApplySettings( m_pPlayerListPanelKVs ); + m_aPlayerList[i]->SetBorder( pScheme->GetBorder("EconItemBorder") ); + m_aPlayerList[i]->InvalidateLayout(); + } + + m_pModelPanel->SetNoItemText( "#Item_Giveaway_NoItem" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFGiveawayItemPanel::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + KeyValues *pItemKV = inResourceData->FindKey( "playerlist_panel_kvs" ); + if ( pItemKV ) + { + if ( m_pPlayerListPanelKVs ) + { + m_pPlayerListPanelKVs->deleteThis(); + } + m_pPlayerListPanelKVs = new KeyValues("playerlist_panel_kvs"); + pItemKV->CopySubkeys( m_pPlayerListPanelKVs ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFGiveawayItemPanel::PerformLayout( void ) +{ + BaseClass::PerformLayout(); + + int iPosition = 0; + for ( int i = 0; i < m_aPlayerList.Count(); i++ ) + { + if ( !m_aPlayerList[i]->IsVisible() ) + continue; + + int iCenter = GetWide() * 0.5; + int iXPos = iCenter; + int iYPos = 0; + if ( iPosition < (m_iNumActivePlayers * 0.5) ) + { + iXPos -= m_aPlayerList[i]->GetWide() + m_iPlayerXOffset; + iYPos = m_iPlayerYPos + iPosition * m_aPlayerList[i]->GetTall(); + } + else + { + iXPos += m_iPlayerXOffset; + iYPos = m_iPlayerYPos + (iPosition - ceil(m_iNumActivePlayers * 0.5)) * m_aPlayerList[i]->GetTall(); + } + m_aPlayerList[i]->SetPos( iXPos, iYPos ); + + iPosition++; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFGiveawayItemPanel::ShowPanel(bool bShow) +{ + if ( bShow ) + { + SetItem( NULL ); + m_bBuiltPlayerList = false; + m_flNextRollStart = 0; + m_iRollingForPlayer = 0; + m_iNumActivePlayers = 0; + } + + SetMouseInputEnabled( bShow ); + SetVisible( bShow ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFGiveawayItemPanel::FireGameEvent( IGameEvent *event ) +{ + const char * type = event->GetName(); + + if ( Q_strcmp(type, "gameui_hidden") == 0 ) + { + ShowPanel( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFGiveawayItemPanel::OnCommand( const char *command ) +{ + if ( !Q_stricmp( command, "vguicancel" ) ) + { + ShowPanel( false ); + + // If we're connected to a game server, we also close the game UI. + if ( engine->IsInGame() ) + { + engine->ClientCmd_Unrestricted( "gameui_hide" ); + } + } + else + { + engine->ClientCmd( const_cast<char *>( command ) ); + } + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFGiveawayItemPanel::Update( void ) +{ + // First, wait for the player list to get built + if ( !m_bBuiltPlayerList ) + { + BuildPlayerList(); + if ( !m_bBuiltPlayerList ) + return; + } + + // Then wait for the server to send us the item details + if ( !m_pModelPanel->HasItem() ) + { + if ( TFGameRules() ) + { + CBonusRoundLogic *pLogic = TFGameRules()->GetBonusLogic(); + if ( pLogic ) + { + m_pModelPanel->SetItem( pLogic->GetBonusItem() ); + } + } + + if ( !m_pModelPanel->HasItem() ) + return; + } + + // Then start rolling through the players and locking in their bonuses + if ( m_iRollingForPlayer < m_iNumActivePlayers ) + { + // Spin all the numbers & lock them in one by one + for ( int i = 0; i < m_aPlayerList.Count(); i++ ) + { + if ( m_iRollingForPlayer < i ) + { + // Haven't got to this player yet, so spin their bonus. + m_aPlayerList[i]->SpinBonus(); + continue; + } + + if ( i == m_iRollingForPlayer ) + { + if ( gpGlobals->curtime > m_flNextRollStart ) + { + // Lock in this player's bonus and move on to the next one + CBonusRoundLogic *pLogic = TFGameRules()->GetBonusLogic(); + if ( pLogic ) + { + m_aPlayerList[i]->LockBonus( pLogic->GetPlayerBonusRoll( m_aPlayerList[i]->GetPlayerIndex() ) ); + } + + m_iRollingForPlayer++; + m_flNextRollStart = gpGlobals->curtime + 0.4; + } + else + { + m_aPlayerList[i]->SpinBonus(); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFGiveawayItemPanel::BuildPlayerList( void ) +{ + CBonusRoundLogic *pLogic = TFGameRules()->GetBonusLogic(); + if ( !pLogic ) + return; + + m_bBuiltPlayerList = true; + + pLogic->BuildBonusPlayerList(); + + for ( int i = 0; i < m_aPlayerList.Count(); i++ ) + { + m_aPlayerList[i]->SetPlayer(NULL); + } + + m_iNumActivePlayers = 0; + for( int playerIndex = 0; playerIndex < pLogic->GetNumBonusPlayers(); playerIndex++ ) + { + C_TFPlayer *pPlayer = pLogic->GetBonusPlayer(playerIndex); + if ( !pPlayer ) + continue; + + CGiveawayPlayerPanel *pPlayerPanel; + if ( m_iNumActivePlayers < m_aPlayerList.Count() ) + { + pPlayerPanel = m_aPlayerList[m_iNumActivePlayers]; + } + else + { + const char *pszCommand = VarArgs("playerpanel%d",m_iNumActivePlayers); + pPlayerPanel = new CGiveawayPlayerPanel( this, pszCommand ); + if ( m_pPlayerListPanelKVs ) + { + pPlayerPanel->ApplySettings( m_pPlayerListPanelKVs ); + } + pPlayerPanel->MakeReadyForUse(); + + vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); + pPlayerPanel->SetBorder( pScheme->GetBorder("EconItemBorder") ); + + m_aPlayerList.AddToTail( pPlayerPanel ); + } + + pPlayerPanel->SetPlayer( pPlayer ); + + m_iNumActivePlayers++; + } + + // Start the animation + m_flNextRollStart = gpGlobals->curtime + 1.0; + m_iRollingForPlayer = 0; + + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFGiveawayItemPanel::SetItem( CEconItemView *pItem ) +{ + m_pModelPanel->SetItem( pItem ); +} + +static vgui::DHANDLE<CTFGiveawayItemPanel> g_GiveawayItemPanel; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFGiveawayItemPanel *OpenGiveawayItemPanel( CEconItemView *pItem ) +{ + CTFGiveawayItemPanel *pPanel = (CTFGiveawayItemPanel*)gViewPortInterface->FindPanelByName( PANEL_GIVEAWAY_ITEM ); + if ( pPanel ) + { + pPanel->InvalidateLayout( false, true ); + pPanel->SetItem( pItem ); + gViewPortInterface->ShowPanel( pPanel, true ); + } + return pPanel; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Test_GiveawayItemPanel( const CCommand &args ) +{ + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( !pLocalPlayer ) + return; + + CEconItemView *pScriptCreatedItem = NULL; + + bool bAllItems = (args.ArgC() <= 1); + for ( int i = bAllItems ? 0 : clamp( atoi(args[1]), 0, 2 ); i <= 2; i++ ) + { + CEconEntity *pItem = dynamic_cast<CEconEntity *>( pLocalPlayer->Weapon_GetWeaponByType( i ) ); + if ( !pItem ) + continue; + + pScriptCreatedItem = pItem->GetAttributeContainer()->GetItem(); + break; + } + + if ( pScriptCreatedItem ) + { + OpenGiveawayItemPanel( pScriptCreatedItem ); + } +} + +ConCommand test_giveawayitem( "test_giveawayitem", Test_GiveawayItemPanel, "Debugging tool to test the item giveaway panel. Usage: test_giveawayitem <weapon name>\n <weapon id>: 0 = primary, 1 = secondary, 2 = melee.", FCVAR_CHEAT ); diff --git a/game/client/tf/vgui/tf_giveawayitempanel.h b/game/client/tf/vgui/tf_giveawayitempanel.h new file mode 100644 index 0000000..cd8b106 --- /dev/null +++ b/game/client/tf/vgui/tf_giveawayitempanel.h @@ -0,0 +1,108 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_GIVEAWAYITEMPANEL_H +#define TF_GIVEAWAYITEMPANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include <vgui_controls/Panel.h> +#include <vgui_controls/Frame.h> +#include <game/client/iviewport.h> +#include "GameEventListener.h" +#include "basemodel_panel.h" +#include "basemodelpanel.h" +#include "tf_shareddefs.h" +#include "econ_item_inventory.h" +#include "econ_item_view.h" +#include "item_model_panel.h" +#include "c_tf_player.h" + +class CEconItemView; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CGiveawayPlayerPanel : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CGiveawayPlayerPanel, vgui::EditablePanel ); +public: + CGiveawayPlayerPanel( vgui::Panel *parent, const char *name ); + + virtual void PerformLayout( void ); + + void SetPlayer( CTFPlayer *pPlayer ); + int GetPlayerIndex( void ) { return m_iPlayerIndex; } + int GetBonus( void ) { return m_iBonus; } + void SpinBonus( void ); + void LockBonus( int iRoll ); + +private: + vgui::Label *m_pNameLabel; + vgui::Label *m_pScoreLabel; + Color m_PlayerColorLocal; + Color m_PlayerColorOther; + int m_iBonus; + int m_iRoll; + int m_iPlayerIndex; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CTFGiveawayItemPanel : public vgui::Frame, public IViewPortPanel, public CGameEventListener +{ + DECLARE_CLASS_SIMPLE( CTFGiveawayItemPanel, vgui::Frame ); +public: + CTFGiveawayItemPanel( IViewPort *pViewPort ); + ~CTFGiveawayItemPanel( void ); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void ApplySettings( KeyValues *inResourceData ); + virtual void PerformLayout( void ); + virtual void OnCommand( const char *command ); + virtual void FireGameEvent( IGameEvent *event ); + + void SetItem( CEconItemView *pItem ); + void BuildPlayerList( void ); + + // IViewPortPanel overrides + virtual const char *GetName( void ){ return PANEL_GIVEAWAY_ITEM; } + virtual void SetData( KeyValues *data ) { return; } + virtual void Reset(){ Update(); } + virtual void Update(); + virtual void ShowPanel( bool bShow ); + virtual bool NeedsUpdate( void ){ return true; } + virtual bool HasInputElements( void ){ return true; } + + // both vgui::Frame and IViewPortPanel define these, so explicitly define them here as passthroughs to vgui + vgui::VPANEL GetVPanel( void ){ return BaseClass::GetVPanel(); } + virtual bool IsVisible(){ return BaseClass::IsVisible(); } + virtual void SetParent( vgui::VPANEL parent ){ BaseClass::SetParent( parent ); } + + virtual GameActionSet_t GetPreferredActionSet() { return GAME_ACTION_SET_MENUCONTROLS; } + +private: + IViewPort *m_pViewPort; + CItemModelPanel *m_pModelPanel; + CUtlVector< CGiveawayPlayerPanel * > m_aPlayerList; + KeyValues *m_pPlayerListPanelKVs; + int m_iNumActivePlayers; + + // Animation + bool m_bBuiltPlayerList; + float m_flNextRollStart; + int m_iRollingForPlayer; + + CPanelAnimationVarAliasType( int, m_iPlayerYPos, "player_ypos", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iPlayerXOffset, "player_xoffset", "0", "proportional_int" ); +}; + +CTFGiveawayItemPanel *OpenGiveawayItemPanel( CEconItemView *pItem ); + +#endif // TF_GIVEAWAYITEMPANEL_H diff --git a/game/client/tf/vgui/tf_imagepanel.cpp b/game/client/tf/vgui/tf_imagepanel.cpp new file mode 100644 index 0000000..28ba950 --- /dev/null +++ b/game/client/tf/vgui/tf_imagepanel.cpp @@ -0,0 +1,87 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include <KeyValues.h> +#include <vgui/IScheme.h> +#include <vgui/ISurface.h> +#include <vgui/ISystem.h> +#include <vgui_controls/AnimationController.h> +#include <vgui_controls/EditablePanel.h> +#include <vgui/ISurface.h> +#include <vgui/IImage.h> +#include <vgui_controls/Label.h> + +#include "tf_imagepanel.h" +#include "c_tf_player.h" + +using namespace vgui; + +DECLARE_BUILD_FACTORY( CTFImagePanel ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFImagePanel::CTFImagePanel( Panel *parent, const char *name ) : ScalableImagePanel( parent, name ) +{ + for ( int i = 0; i < TF_TEAM_COUNT; i++ ) + { + m_szTeamBG[i][0] = '\0'; + } + + C_TFPlayer *pPlayer = ToTFPlayer( C_BasePlayer::GetLocalPlayer() ); + m_iBGTeam = pPlayer ? pPlayer->GetTeamNumber() : TEAM_UNASSIGNED; + + ListenForGameEvent( "localplayer_changeteam" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFImagePanel::ApplySettings( KeyValues *inResourceData ) +{ + for ( int i = 0; i < TF_TEAM_COUNT; i++ ) + { + Q_strncpy( m_szTeamBG[i], inResourceData->GetString( VarArgs("teambg_%d", i), "" ), sizeof( m_szTeamBG[i] ) ); + + if ( m_szTeamBG[i] && m_szTeamBG[i][0] ) + { + PrecacheMaterial( VarArgs( "vgui/%s", m_szTeamBG[i] ) ); + } + } + + BaseClass::ApplySettings( inResourceData ); + + UpdateBGImage(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFImagePanel::UpdateBGImage( void ) +{ + if ( m_iBGTeam >= 0 && m_iBGTeam < TF_TEAM_COUNT ) + { + if ( m_szTeamBG[m_iBGTeam] && m_szTeamBG[m_iBGTeam][0] ) + { + SetImage( m_szTeamBG[m_iBGTeam] ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFImagePanel::FireGameEvent( IGameEvent * event ) +{ + if ( FStrEq( "localplayer_changeteam", event->GetName() ) ) + { + C_TFPlayer *pPlayer = ToTFPlayer( C_BasePlayer::GetLocalPlayer() ); + m_iBGTeam = pPlayer ? pPlayer->GetTeamNumber() : TEAM_UNASSIGNED; + UpdateBGImage(); + } +}
\ No newline at end of file diff --git a/game/client/tf/vgui/tf_imagepanel.h b/game/client/tf/vgui/tf_imagepanel.h new file mode 100644 index 0000000..ffa842f --- /dev/null +++ b/game/client/tf/vgui/tf_imagepanel.h @@ -0,0 +1,41 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_IMAGEPANEL_H +#define TF_IMAGEPANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_shareddefs.h" +#include <vgui/IScheme.h> +#include <vgui_controls/ScalableImagePanel.h> +#include "GameEventListener.h" + +#define MAX_BG_LENGTH 128 + +class CTFImagePanel : public vgui::ScalableImagePanel, public CGameEventListener +{ +public: + DECLARE_CLASS_SIMPLE( CTFImagePanel, vgui::ScalableImagePanel ); + + CTFImagePanel( vgui::Panel *parent, const char *name ); + + virtual void ApplySettings( KeyValues *inResourceData ); + void UpdateBGImage( void ); + void SetBGTeam( int iTeam ) { m_iBGTeam = iTeam; } + +public: // IGameEventListener Interface + virtual void FireGameEvent( IGameEvent * event ); + +public: + char m_szTeamBG[TF_TEAM_COUNT][MAX_BG_LENGTH]; + int m_iBGTeam; +}; + + +#endif // TF_IMAGEPANEL_H diff --git a/game/client/tf/vgui/tf_intromenu.cpp b/game/client/tf/vgui/tf_intromenu.cpp new file mode 100644 index 0000000..5cf7670 --- /dev/null +++ b/game/client/tf/vgui/tf_intromenu.cpp @@ -0,0 +1,703 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include <KeyValues.h> +#include <vgui/IVGui.h> +#include <vgui/ISurface.h> +#include <filesystem.h> +#include <vgui_controls/AnimationController.h> +#include "iclientmode.h" +#include "clientmode_shared.h" +#include "shareddefs.h" +#include "tf_shareddefs.h" +#include "tf_controls.h" +#include "tf_gamerules.h" +#ifdef WIN32 +#include "winerror.h" +#endif +#include "ixboxsystem.h" +#include "intromenu.h" +#include "tf_intromenu.h" +#include "inputsystem/iinputsystem.h" + +// used to determine the action the intro menu should take when OnTick handles a think for us +enum +{ + INTRO_NONE, + INTRO_STARTVIDEO, + INTRO_BACK, + INTRO_CONTINUE, +}; + +using namespace vgui; + +// sort function for the list of captions that we're going to show +int CaptionsSort( CVideoCaption* const *p1, CVideoCaption* const *p2 ) +{ + // check the start time + if ( (*p2)->m_flStartTime < (*p1)->m_flStartTime ) + { + return 1; + } + + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CTFIntroMenu::CTFIntroMenu( IViewPort *pViewPort ) : BaseClass( pViewPort ) +{ + m_pVideo = new CTFVideoPanel( this, "VideoPanel" ); + m_pModel = new CModelPanel( this, "MenuBG" ); + m_pCaptionLabel = new CExLabel( this, "VideoCaption", "" ); + +#ifdef _X360 + m_pFooter = new CTFFooter( this, "Footer" ); +#else + m_pBack = new CExButton( this, "Back", "" ); + m_pOK = new CExButton( this, "Skip", "" ); + m_pReplayVideo = new CExButton( this, "ReplayVideo", "" ); + m_pContinue = new CExButton( this, "Continue", "" ); +#endif + + m_iCurrentCaption = 0; + m_flVideoStartTime = 0; + + m_flActionThink = -1; + m_iAction = INTRO_NONE; + + //============================================================================= + // HPE_BEGIN + // [msmith] Flag for weather or not we're playing an in game video. + //============================================================================= + m_bPlayingInGameVideo = false; + //============================================================================= + // HPE_END + //============================================================================= + + vgui::ivgui()->AddTickSignal( GetVPanel() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CTFIntroMenu::~CTFIntroMenu() +{ + m_Captions.PurgeAndDeleteElements(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFIntroMenu::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + if ( ::input->IsSteamControllerActive() ) + { + LoadControlSettings( "Resource/UI/IntroMenu_SC.res" ); + SetMouseInputEnabled( false ); + } + else + { + LoadControlSettings( "Resource/UI/IntroMenu.res" ); + SetMouseInputEnabled( true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFIntroMenu::SetNextThink( float flActionThink, int iAction ) +{ + m_flActionThink = flActionThink; + m_iAction = iAction; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFIntroMenu::OnTick() +{ + // @note Tom Bui: (yuck) + // in training, never show the back button + // we do this late, because there's a race condition for when IsInTraining() will return true + if ( m_pBack->IsVisible() && TFGameRules() && TFGameRules()->IsInTraining() ) + { + m_pBack->SetVisible(false); + } + + //============================================================================= + // HPE_BEGIN + // [msmith] Used to play a movie during a map. For training videos. + //============================================================================= + if ( PendingInGameVideo() && !BaseClass::IsVisible() ) + { + m_pViewPort->ShowPanel( this, true ); + } + //============================================================================= + // HPE_END + //============================================================================= + + // do we have anything special to do? + else if ( m_flActionThink > 0 && m_flActionThink < gpGlobals->curtime ) + { + if ( m_iAction == INTRO_STARTVIDEO ) + { + + //============================================================================= + // HPE_BEGIN + // [msmith] Pulled start video into a separate function. + //============================================================================= + StartVideo(); + //============================================================================= + // HPE_END + //============================================================================= + } + else if ( m_iAction == INTRO_BACK ) + { + m_pViewPort->ShowPanel( this, false ); + m_pViewPort->ShowPanel( PANEL_MAPINFO, true ); + } + else if ( m_iAction == INTRO_CONTINUE ) + { + m_pViewPort->ShowPanel( this, false ); + + //============================================================================= + // HPE_BEGIN + // [msmith] Used for the client to tell the server that we're whatching a movie or not + //============================================================================= + tf_training_client_message.SetValue( "" ); + tf_training_client_message.SetValue( TRAINING_CLIENT_MESSAGE_NONE ); + //============================================================================= + // HPE_END + //============================================================================= + + + if ( GetLocalPlayerTeam() == TEAM_UNASSIGNED ) + { + if ( TFGameRules()->IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true ) + { + m_pViewPort->ShowPanel( PANEL_ARENA_TEAM, true ); + } + else if ( TFGameRules()->IsMannVsMachineMode() || TFGameRules()->IsCompetitiveMode() ) + { + engine->ClientCmd( "autoteam" ); + } + else + { + m_pViewPort->ShowPanel( PANEL_TEAM, true ); + } + } + else + { + C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer(); + + // only open the class menu if they're not on team Spectator and they haven't already picked a class + if ( pPlayer && + ( GetLocalPlayerTeam() != TEAM_SPECTATOR ) && + ( pPlayer->GetPlayerClass()->GetClassIndex() == TF_CLASS_UNDEFINED ) ) + { + if ( tf_arena_force_class.GetBool() == false ) + { + switch( GetLocalPlayerTeam() ) + { + case TF_TEAM_RED: + m_pViewPort->ShowPanel( PANEL_CLASS_RED, true ); + break; + + case TF_TEAM_BLUE: + m_pViewPort->ShowPanel( PANEL_CLASS_BLUE, true ); + break; + } + } + } + } + } + + // reset our think + SetNextThink( -1, INTRO_NONE ); + } + + // check if we need to update our captions + if ( m_pCaptionLabel && m_pCaptionLabel->IsVisible() ) + { + UpdateCaptions(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFIntroMenu::OnThink() +{ + //Always hide the health... this needs to be done every frame because a message from the server keeps resetting this. + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pLocalPlayer ) + { + pLocalPlayer->m_Local.m_iHideHUD |= HIDEHUD_HEALTH; + } + + BaseClass::OnThink(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFIntroMenu::LoadCaptions( void ) +{ + bool bSuccess = false; + + // clear any current captions + m_Captions.PurgeAndDeleteElements(); + m_iCurrentCaption = 0; + + if ( m_pCaptionLabel ) + { + const char *szVideoFileName = GetVideoFileName( false ); + KeyValues *kvCaptions = NULL; + char strFullpath[MAX_PATH]; + if ( szVideoFileName != NULL ) + { + //============================================================================= + // HPE_BEGIN + // [msmith] The video may now be either a map video or an in game video. + // Made a function to decide which video name to give back. + //============================================================================= + Q_strncpy( strFullpath, szVideoFileName, MAX_PATH ); // Assume we must play out of the media directory + //============================================================================= + // HPE_END + //============================================================================= + + Q_strncat( strFullpath, ".res", MAX_PATH ); // Assume we're a .res extension type + + if ( g_pFullFileSystem->FileExists( strFullpath ) ) + { + kvCaptions = new KeyValues( strFullpath ); + + if ( kvCaptions ) + { + if ( kvCaptions->LoadFromFile( g_pFullFileSystem, strFullpath ) ) + { + for ( KeyValues *pData = kvCaptions->GetFirstSubKey(); pData != NULL; pData = pData->GetNextKey() ) + { + CVideoCaption *pCaption = new CVideoCaption; + if ( pCaption ) + { + pCaption->m_pszString = ReadAndAllocStringValue( pData, "string" ); + pCaption->m_flStartTime = pData->GetFloat( "start", 0.0 ); + pCaption->m_flDisplayTime = pData->GetFloat( "length", 3.0 ); + + m_Captions.AddToTail( pCaption ); + + // we have at least one caption to show + bSuccess = true; + } + } + } + + kvCaptions->deleteThis(); + } + } + } + } + + if ( bSuccess ) + { + // sort the captions so we show them in the correct order (they're not necessarily in order in the .res file) + m_Captions.Sort( CaptionsSort ); + } + + return bSuccess; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFIntroMenu::UpdateCaptions( void ) +{ + //============================================================================= + // HPE_BEGIN + // [msmith] Timing should be realtime when playing in game becase the curtime is paused. + //============================================================================= + float testTime = m_bPlayingInGameVideo ? gpGlobals->realtime : gpGlobals->curtime; + //============================================================================= + // HPE_END + //============================================================================= + + if ( m_pCaptionLabel && m_pCaptionLabel->IsVisible() && ( m_Captions.Count() > 0 ) ) + { + CVideoCaption *pCaption = m_Captions[m_iCurrentCaption]; + + if ( pCaption ) + { + if ( ( pCaption->m_flCaptionStart >= 0 ) && ( pCaption->m_flCaptionStart + pCaption->m_flDisplayTime < testTime ) ) + { + // fade out the caption + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "VideoCaptionFadeOut" ); + + // move to the next caption + m_iCurrentCaption++; + + if ( !m_Captions.IsValidIndex( m_iCurrentCaption ) ) + { + // we're done showing captions + m_pCaptionLabel->SetVisible( false ); + } + } + // is it time to show the caption? + else if ( m_flVideoStartTime + pCaption->m_flStartTime < testTime ) + { + // have we already started this video? + if ( pCaption->m_flCaptionStart < 0 ) + { + m_pCaptionLabel->SetText( pCaption->m_pszString ); + pCaption->m_flCaptionStart = testTime; + + // fade in the next caption + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "VideoCaptionFadeIn" ); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFIntroMenu::ShowPanel( bool bShow ) +{ + + //============================================================================= + // HPE_BEGIN: + // [msmith] Don't show the back button when in training. You can only skip intro + // movies. + //============================================================================= + m_pBack->SetVisible(true); + if ( TFGameRules() && TFGameRules()->IsInTraining() ) + { + m_pBack->SetVisible( false ); + if ( PendingInGameVideo() == false ) + { + VideoSystem_t playbackSystem = VideoSystem::NONE; + char resolvedFile[MAX_PATH]; + if ( g_pVideo != NULL && g_pVideo->LocatePlayableVideoFile( GetVideoFileName(), "GAME", &playbackSystem, resolvedFile, sizeof(resolvedFile) ) != VideoResult::SUCCESS ) + { + //If we have no movie, no need to show the intro screen on a training mission. + bShow = false; + } + } + } + //============================================================================= + // HPE_END + //============================================================================= + + + if ( BaseClass::IsVisible() == bShow ) + return; + + // reset our think + SetNextThink( -1, INTRO_NONE ); + + if ( bShow ) + { + InvalidateLayout( true, true ); + Activate(); + + if ( m_pVideo ) + { + //============================================================================= + // HPE_BEGIN + // [msmith] Pulled shutting down the video into a separate function. + // If we're showing an in game video, we need to enable pausing so that + // we can pause the game during the video. + // If we're showing an intro training movie, we also need to tell the server that + // we're whatching the intro movie so that the round does not start until it's over. + // If we are watching an in game video, we do NOT send a message for that because + // tf_training_client_message will contain the name of the video we're watching. + //============================================================================= + ShutdownVideo(); + SetNextThink( gpGlobals->curtime + m_pVideo->GetStartDelay(), INTRO_STARTVIDEO ); + + if ( TFGameRules() && TFGameRules()->IsInTraining() ) + { + if ( PendingInGameVideo() ) + { + engine->ClientCmd( "sv_pausable 1" ); + } + else + { + tf_training_client_message.SetValue( TRAINING_CLIENT_MESSAGE_WATCHING_INTRO_MOVIE ); + } + } + //============================================================================= + // HPE_END + //============================================================================= + + } + + if ( m_pModel ) + { + m_pModel->SetPanelDirty(); + } + } + else + { + Shutdown(); + + SetVisible( false ); + + //============================================================================= + // HPE_BEGIN + // [msmith] We must disable the ability to pause. If we don't, it looks like + // some other function in TF2 causes the entire game to pause if sv_pausable is enabled. + //============================================================================= + if ( TFGameRules() && TFGameRules()->IsInTraining() ) + { + engine->ClientCmd( "sv_pausable 0" ); + } + //============================================================================= + // HPE_END + //============================================================================= + + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFIntroMenu::OnIntroFinished( void ) +{ + // in training we want to give the user the ability to replay the movie + if ( TFGameRules() && TFGameRules()->IsInTraining() ) + { + m_pReplayVideo->SetVisible( true ); + m_pContinue->SetVisible( true ); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "IntroMovieContinueBlink" ); + m_pOK->SetVisible( false ); + } + else + { + float flTime = gpGlobals->curtime; + + if ( m_pModel && m_pModel->SetSequence( "UpSlow" ) ) + { + // wait for the model sequence to finish before going to the next menu + flTime = gpGlobals->curtime + m_pVideo->GetEndDelay(); + } + + Shutdown(); + + SetNextThink( flTime, INTRO_CONTINUE ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFIntroMenu::OnCommand( const char *command ) +{ + if ( !Q_strcmp( command, "back" ) ) + { + float flTime = gpGlobals->curtime; + + Shutdown(); + + // try to play the screenup sequence + if ( m_pModel && m_pModel->SetSequence( "Up" ) ) + { + flTime = gpGlobals->curtime + 0.35f; + } + + // wait for the model sequence to finish before going back to the mapinfo menu + SetNextThink( flTime, INTRO_BACK ); + } + else if ( !Q_strcmp( command, "skip" ) ) + { + Shutdown(); + + // continue right now + SetNextThink( gpGlobals->curtime, INTRO_CONTINUE ); + } + else if ( !Q_strcmp( command, "replayVideo" ) ) + { + ShutdownVideo(); + SetNextThink( gpGlobals->curtime, INTRO_STARTVIDEO ); + } + else + { + BaseClass::OnCommand( command ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFIntroMenu::OnKeyCodePressed( KeyCode code ) +{ + if ( code == KEY_XBUTTON_A || code == STEAMCONTROLLER_A ) + { + OnCommand( "skip" ); + } + else if ( code == KEY_XBUTTON_B || code == STEAMCONTROLLER_B ) + { + OnCommand( "back" ); + } + else + { + BaseClass::OnKeyCodePressed( code ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFIntroMenu::Shutdown( void ) +{ + //============================================================================= + // HPE_BEGIN + // [msmith] Refactored the shutdown video logic into a containing function. + //============================================================================= + ShutdownVideo(); + //============================================================================= + // HPE_END + //============================================================================= + + if ( m_pCaptionLabel && m_pCaptionLabel->IsVisible() ) + { + m_pCaptionLabel->SetVisible( false ); + } + + m_iCurrentCaption = 0; + m_flVideoStartTime = 0; + +} + + + + +//============================================================================= +// HPE_BEGIN +// [msmith] New helper functions +//============================================================================= +void CTFIntroMenu::ShutdownVideo() +{ + if ( m_pVideo ) + { + m_pVideo->Shutdown(); // make sure we're not currently running + } + + //Make sure we unpause the game if it was paused from an in game play of a video. + if ( m_bPlayingInGameVideo ) + { + UnpauseGame(); + } + + m_bPlayingInGameVideo = false; +} + +bool CTFIntroMenu::PendingInGameVideo( void ) +{ + if ( TFGameRules() && TFGameRules()->IsInTraining() ) + { + //If the message is a string, it's a video name. + return strlen( tf_training_client_message.GetString() ) > 3; + } + + return false; +} + +const char *CTFIntroMenu::GetVideoFileName( bool withExtension ) +{ + if ( PendingInGameVideo() ) + { + return TFGameRules()->FormatVideoName( tf_training_client_message.GetString(), withExtension ); + } + + if ( TFGameRules() && TFGameRules()->IsInTraining() ) + { + ConVarRef training_map_video("training_map_video"); + if ( strlen( training_map_video.GetString() ) > 3 ) + { + return TFGameRules()->FormatVideoName( training_map_video.GetString(), withExtension ); + } + } + + return TFGameRules()->GetVideoFileForMap( withExtension ); +} + +void CTFIntroMenu::StartVideo() +{ + m_pOK->SetVisible( true ); + m_pReplayVideo->SetVisible( false ); + m_pContinue->SetVisible( false ); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "IntroMovieContinueBlinkStop" ); + if ( m_pVideo ) + { + // turn on the captions if we have them + if ( LoadCaptions() ) + { + if ( m_pCaptionLabel && !m_pCaptionLabel->IsVisible() ) + { + m_pCaptionLabel->SetText( " " ); + m_pCaptionLabel->SetVisible( true ); + //Make sure the label is fully faded in when starting to play. + //It could have been faded out from a prior animation event form an animation effect in a previous video instance. + m_pCaptionLabel->SetAlpha( 255 ); + } + } + else + { + if ( m_pCaptionLabel && m_pCaptionLabel->IsVisible() ) + { + m_pCaptionLabel->SetVisible( false ); + } + } + + m_pVideo->Activate(); + + if ( PendingInGameVideo() ) + { + m_pVideo->BeginPlayback( GetVideoFileName() ); + PauseGame(); + m_bPlayingInGameVideo = true; + + //Since we have started playing the video, we can reset the message string to empty. + tf_training_client_message.SetValue( "" ); + } + else + { + m_pVideo->BeginPlayback( GetVideoFileName() ); + } + + m_pVideo->MoveToFront(); + + m_flVideoStartTime = m_bPlayingInGameVideo ? gpGlobals->realtime : gpGlobals->curtime; + } +} + +void CTFIntroMenu::UnpauseGame( void ) +{ + if ( TFGameRules() && TFGameRules()->IsInTraining() ) + { + engine->ClientCmd( "unpause" ); + } +} + +void CTFIntroMenu::PauseGame( void ) +{ + if ( TFGameRules() && TFGameRules()->IsInTraining() ) + { + engine->ClientCmd( "pause" ); + } +} +//============================================================================= +// HPE_END +//============================================================================= diff --git a/game/client/tf/vgui/tf_intromenu.h b/game/client/tf/vgui/tf_intromenu.h new file mode 100644 index 0000000..b9f2cf2 --- /dev/null +++ b/game/client/tf/vgui/tf_intromenu.h @@ -0,0 +1,128 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_INTROMENU_H +#define TF_INTROMENU_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_vgui_video.h" +#include "basemodelpanel.h" + +#define MAX_CAPTION_LENGTH 256 + +class CVideoCaption +{ +public: + CVideoCaption() + { + m_pszString = NULL; + m_flStartTime = 0; + m_flDisplayTime = 0; + m_flCaptionStart = -1; + } + + ~CVideoCaption() + { + if ( m_pszString && m_pszString[0] ) + { + delete [] m_pszString; + m_pszString = NULL; + } + } + + const char *m_pszString; // the string to display (can be a localized # string) + float m_flStartTime; // the offset from the beginning of the video when we should show this caption + float m_flDisplayTime; // the length of time the string should be displayed once it's shown + float m_flCaptionStart; // the time when the caption is shown (so we know when to turn it off +}; + +//----------------------------------------------------------------------------- +// Purpose: displays the Intro menu +//----------------------------------------------------------------------------- + +class CTFIntroMenu : public CIntroMenu +{ +private: + DECLARE_CLASS_SIMPLE( CTFIntroMenu, CIntroMenu ); + +public: + CTFIntroMenu( IViewPort *pViewPort ); + ~CTFIntroMenu(); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void ShowPanel( bool bShow ); + virtual void OnCommand( const char *command ); + virtual void OnKeyCodePressed( KeyCode code ); + + virtual void OnTick() OVERRIDE; + virtual void OnThink() OVERRIDE; + + //============================================================================= + // HPE_BEGIN + // [msmith] Some refactoring. + //============================================================================= + void StartVideo(); + void ShutdownVideo(); + //============================================================================= + // HPE_END + //============================================================================= + + MESSAGE_FUNC( OnIntroFinished, "IntroFinished" ); + +private: + void SetNextThink( float flActionThink, int iAction ); + void Shutdown( void ); + bool LoadCaptions( void ); + void UpdateCaptions( void ); + + + + //============================================================================= + // HPE_BEGIN + // [msmith] Added support for in game videos. + //============================================================================= + bool PendingInGameVideo( void ); + const char *GetVideoFileName( bool withExtension = true ); + void UnpauseGame( void ); + void PauseGame( void ); + //============================================================================= + // HPE_END + //============================================================================= + + CTFVideoPanel *m_pVideo; + CModelPanel *m_pModel; + CExLabel *m_pCaptionLabel; + +#ifdef _X360 + CTFFooter *m_pFooter; +#else + CExButton *m_pBack; + CExButton *m_pOK; + CExButton *m_pReplayVideo; + CExButton *m_pContinue; +#endif + + float m_flActionThink; + int m_iAction; + + CUtlVector< CVideoCaption* > m_Captions; + int m_iCurrentCaption; + float m_flVideoStartTime; + //============================================================================= + // HPE_BEGIN + // [msmith] Added support for in game videos. + //============================================================================= + bool m_bPlayingInGameVideo; + //============================================================================= + // HPE_END + //============================================================================= +}; + + +#endif // TF_INTROMENU_H diff --git a/game/client/tf/vgui/tf_item_card_panel.cpp b/game/client/tf/vgui/tf_item_card_panel.cpp new file mode 100644 index 0000000..0852db4 --- /dev/null +++ b/game/client/tf/vgui/tf_item_card_panel.cpp @@ -0,0 +1,767 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "tf_item_card_panel.h" +#include "econ_item_description.h" +#include "vgui_controls/TextImage.h" +#include "VGuiMatSurface/IMatSystemSurface.h" +#include "navigationpanel.h" +#include "IconPanel.h" +#include "vgui_controls/ScrollBar.h" +#include "vgui_controls/ScrollBarSlider.h" +#include <vgui_controls/Label.h> +#include <vgui_controls/ImagePanel.h> +#include <vgui_controls/Tooltip.h> +#include <vgui_controls/AnimationController.h> +#include "clientmode_tf.h" + +using namespace vgui; + +#ifdef STAGING_ONLY +extern ConVar tf_use_card_tooltips; +#endif // STAGING_ONLY + +//----------------------------------------------------------------------------- +// Purpose: A label that can have multiple fonts specified and will try to use +// them in order specified, using the first one that fits. +//----------------------------------------------------------------------------- +class CAutoFittingLabel : public Label +{ + DECLARE_CLASS_SIMPLE( CAutoFittingLabel, Label ); +public: + + CAutoFittingLabel( Panel *parent, const char *name ) + : Label( parent, name, (const char*)NULL ) + {} + + virtual void ApplySettings( KeyValues *inResourceData ) + { + BaseClass::ApplySettings( inResourceData ); + + KeyValues *pFonts = inResourceData->FindKey( "fonts" ); + if ( pFonts ) + { + vgui::IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); + + // Get all the fonts + FOR_EACH_SUBKEY( pFonts, pFont ) + { + const HFont& font = pScheme->GetFont( pFont->GetString( "font" ), true ); + m_vecFonts.AddToTail( font ); + } + } + } + + virtual void PerformLayout() + { + BaseClass::PerformLayout(); + + SetFont( m_vecFonts.Head() ); + + // Go through all the fonts and try to find one that fits + int nIndex = 0; + GetTextImage()->ResizeImageToContentMaxWidth( GetWide() ); + while ( ( GetTextImage()->IsWrapping() || GetTextImage()->GetEllipsesPosition() ) && nIndex < m_vecFonts.Count() ) + { + SetFont( m_vecFonts[ nIndex ] ); + GetTextImage()->ResizeImageToContentMaxWidth( GetWide() ); + + ++nIndex; + } + } + +private: + + CUtlVector< HFont > m_vecFonts; +}; + +DECLARE_BUILD_FACTORY( CAutoFittingLabel ); + + +DECLARE_BUILD_FACTORY( CRepeatingContainer ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CRepeatingContainer::CRepeatingContainer( Panel *pParent, const char *pszName ) + : EditablePanel( pParent, pszName ) + , m_eLayoutMethod( METHOD_EVEN ) +{} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CRepeatingContainer::~CRepeatingContainer() +{} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRepeatingContainer::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + KeyValues* pCommonSettings = inResourceData->FindKey( "CommonSettings" ); + KeyValues* pIndividualSettings = inResourceData->FindKey( "IndividualSettings" ); + + // Delete old panels + m_vecChildren.PurgeAndDeleteElements(); + + if ( pIndividualSettings && pCommonSettings ) + { + // Go through every individual panel + FOR_EACH_SUBKEY( pIndividualSettings, pSubKey ) + { + // Merge the individual keys onto the common keys, keeping "individual" values if there's a conflict + pSubKey->RecursiveMergeKeyValues( pCommonSettings ); + + // Create each panel + Panel *pNewPanel = CreateControlByName( pSubKey->GetString( "ControlName" ) ); + if ( pNewPanel ) + { + pNewPanel->SetParent( this ); + pNewPanel->SetBuildGroup( GetBuildGroup() ); + pNewPanel->ApplySettings( pSubKey ); + m_vecChildren.AddToTail( pNewPanel ); + } + } + } + + const char *pszSpacingMethod = inResourceData->GetString( "spacing_method", NULL ); + if ( pszSpacingMethod ) + { + // Figure out how we're going to layout all these panels + if ( FStrEq( pszSpacingMethod, "METHOD_STEP" ) ) + { + m_eLayoutMethod = METHOD_STEP; + } + else // Default to event + { + m_eLayoutMethod = METHOD_EVEN; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRepeatingContainer::PerformLayout() +{ + BaseClass::PerformLayout(); + + // No children? We're done. + if ( m_vecChildren.IsEmpty() ) + return; + + // Fixed step gaps + if ( m_eLayoutMethod == METHOD_STEP ) + { + FOR_EACH_VEC( m_vecChildren, i ) + { + m_vecChildren[i]->SetPos( m_iXStep * i , 0 ); + } + } + else // default METHOD_EVEN + { + // Evently spaced + int nParentWide = GetWide(); + int nTotalChildWide = 0; + + FOR_EACH_VEC( m_vecChildren, i ) + { + nTotalChildWide += m_vecChildren[i]->GetWide(); + } + + int nXStep = 0; + if ( nTotalChildWide < nParentWide ) + { + nXStep = ( nParentWide - nTotalChildWide ) / ( m_vecChildren.Count() - 1 ); + } + + int nXPos = 0; + FOR_EACH_VEC( m_vecChildren, i ) + { + m_vecChildren[i]->SetPos( nXPos, 0 ); + nXPos += m_vecChildren[i]->GetWide() + nXStep; + } + } +} + + +DECLARE_BUILD_FACTORY( CTFItemCardPanel ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFItemCardPanel::CTFItemCardPanel( Panel *pParent, const char *pszName ) + : BaseClass( pParent, pszName ) + , m_pItem( NULL ) + , m_bAllControlsValid( false ) + , m_bPinned( false ) + , m_pDropShadow( NULL ) + , m_pRarityBackgroundOverlay( NULL ) + , m_pCardTop( NULL ) + , m_pItemModel( NULL ) + , m_pRarityContainer( NULL ) + , m_pItemName( NULL ) + , m_pRarityName( NULL ) + , m_pInfoContainer( NULL ) + , m_pClassLabel( NULL ) + , m_pClassIconContainer( NULL ) + , m_pTypeLabel( NULL ) + , m_pTypeLabelValue( NULL ) + , m_pExteriorLabel( NULL ) + , m_pExteriorLabelValue( NULL ) + , m_pBottomContainer( NULL ) + , m_pBottomScrollingContainer( NULL ) + , m_pAttribsLabel( NULL ) + , m_pEquipSlotLabel( NULL ) +{ + m_pDropShadow = new ImagePanel( pParent, "ItemCardShadow" ); + m_pDropShadow->SetVisible( false ); + m_pDropShadow->SetAutoDelete( false ); // We'll delete this panel +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFItemCardPanel::~CTFItemCardPanel() +{ + m_pDropShadow->MarkForDeletion(); + m_pDropShadow = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Do FindControl() but also verify that we got what we were looking for +//----------------------------------------------------------------------------- +template < class T > +T* CTFItemCardPanel::FindAndVerifyControl( Panel* pParent, const char* pszPanelName ) +{ + if ( !m_bAllControlsValid ) + return NULL; + + // Find the panel + T* pChild = pParent->FindControl< T >( pszPanelName, true ); + // Make sure it's still there + m_bAllControlsValid &= pChild != NULL; + + return pChild; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemCardPanel::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadResFileForCurrentItem(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemCardPanel::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemCardPanel::PerformLayout() +{ + BaseClass::PerformLayout(); + + if ( !m_pItem ) + { + return; + } + + if ( !m_bAllControlsValid ) + { + return; + } + + m_pBottomScrollingContainer->InvalidateLayout(); + + UpdateDescription(); + UpdateModelOrIcon(); + + // Position grime + { + // Randomize based on our original item ID, if we have one. If not, just use defindex + RandomSeed( m_pItem->GetSOCData() ? m_pItem->GetSOCData()->GetOriginalID() : m_pItem->GetItemDefIndex() ); + + // Randomize X/Y + int nGrimeX = RandomInt( -abs( m_pGrime->GetWide() - GetWide() ), 0 ); + int nGrimeY = RandomInt( -abs( m_pGrime->GetTall() - GetTall() ), 0 ); + m_pGrime->SetPos( nGrimeX, nGrimeY ); + // Randomize 0,90,180,270 rotation + m_pGrime->GetImage()->SetRotation( RandomInt( 0, 3 ) ); // Have to GetImage()->SetRotation because ImagePanel::SetRotation does nothing! + } + + // Update our shadow's settings + { + m_pDropShadow->SetZPos( GetZPos() - 1 ); + m_pDropShadow->SetShouldScaleImage( true ); + m_pDropShadow->SetMouseInputEnabled( false ); + m_pDropShadow->SetImage( "item_card/standard_background_dropshadow" ); + } + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemCardPanel::SetVisible( bool bVisible ) +{ + // Update the position of our external shadow panel + if ( m_bAllControlsValid ) + { + int x=0,y=0,wide,tall,xTemp,yTemp; + m_pBackground->GetBounds( xTemp, yTemp, wide, tall ); + x += xTemp; y += yTemp; + m_pMainContainer->GetPos( xTemp, yTemp ); + x += xTemp; y += yTemp; + GetPos( xTemp, yTemp ); + x += xTemp; y += yTemp; + + m_pDropShadow->SetBounds( x + m_iShadowOffset, y + m_iShadowOffset, wide * 1.15f, tall * 1.15f ); + } + + BaseClass::SetVisible( bVisible ); + + m_pDropShadow->SetVisible( bVisible ); +} + +//----------------------------------------------------------------------------- +// Purpose: Force the scrolling container to have mouse input matching the panel's +//----------------------------------------------------------------------------- +void CTFItemCardPanel::SetMouseInputEnabled( bool state ) +{ + BaseClass::SetVisible( state ); + + if ( m_bAllControlsValid ) + { + m_pBottomScrollingContainer->SetMouseInputEnabled( state ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemCardPanel::SetItem( CEconItemView* pItem ) +{ + m_pItem = pItem; + + // Update the panels + LoadResFileForCurrentItem(); + MakeReadyForUse(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemCardPanel::PinCard( bool bPin ) +{ +#ifdef STAGING_ONLY + if ( !tf_use_card_tooltips.GetBool() ) + { + return; + } +#endif // STAGING_ONLY + + bool bDiff = bPin != m_bPinned; + m_bPinned = bPin; + + if ( bDiff && bPin ) + { + g_pClientMode->GetViewportAnimationController()->CancelAnimationsForPanel( this ); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "ItemCard_HidePinHint" ); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "ItemCard_ShowCloseButton" ); + SetVisible( true ); + } + else if ( bDiff && !bPin ) + { + g_pClientMode->GetViewportAnimationController()->CancelAnimationsForPanel( this ); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "ItemCard_ShowPinHint" ); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "ItemCard_HideCloseButton" ); + SetVisible( false ); + } + + // Force mouse input + SetMouseInputEnabled( bPin ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemCardPanel::UpdateDescription() +{ + const GameItemDefinition_t *pItemDef = m_pItem->GetItemDefinition(); + if ( !pItemDef ) + return; + + // Itm name + m_pItemName->SetText( m_pItem->GetItemName() ); + + // If we dont have a rarity, then we assume we're an "old" item. + if ( !GetItemSchema()->GetRarityColor(pItemDef->GetRarity() ) ) + { + // Grab the item quality (ie. strange, unusual) + const char *pszQualityColorString = EconQuality_GetColorString( (EEconItemQuality)m_pItem->GetItemQuality() ); + if ( m_pItem->IsValid() && pszQualityColorString ) + { + IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); + Color colorName = pScheme->GetColor( pszQualityColorString, Color( 0, 255, 0, 255 ) ); + m_pItemName->SetFgColor( colorName ); + } + } + + + // Set highlighting on the class icons + { + for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; i++ ) + { + CExImageButton *pExImage = dynamic_cast< CExImageButton* >( m_pClassIconContainer->GetRepeatingChild( GetRemappedMenuIndexForClass(i) - 1 ) ); + if ( pExImage ) + { + pExImage->SetSelected( pItemDef->CanBeUsedByClass( i ) ); + } + } + } + + // Set type name into the label + { + const locchar_t *locTypename = g_pVGuiLocalize->Find( pItemDef->GetItemTypeName() ); + m_pTypeLabelValue->SetText( locTypename ); + } + + const CEconItemRarityDefinition* pItemRarity = GetItemSchema()->GetRarityDefinition( pItemDef->GetRarity() ); + + // Setup the rarity color overlay + { + attrib_colors_t attribColor = ATTRIB_COL_RARITY_DEFAULT; + + if ( pItemRarity ) + { + attribColor = pItemRarity->GetAttribColor(); + } + + vgui::IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); + Color color = pScheme->GetColor( GetColorNameForAttribColor( attribColor ), Color( 255, 255, 255, 255 ) ); + m_pRarityBackgroundOverlay->SetDrawColor( color ); + m_pRarityName->SetFgColor( color ); + } + + // Rarity name into the label + { + const char *pszRarityName = "#Rarity_Default"; + if ( pItemRarity ) + { + pszRarityName = pItemRarity->GetLocKey(); + } + + m_pRarityName->SetText( g_pVGuiLocalize->Find( pszRarityName ) ); + } + + enum { kAttribBufferSize = 4 * 1024 }; + wchar_t wszAttribBuffer[ kAttribBufferSize ] = L""; + + // Space out the attributes + const CEconItemDescription *pDescription = m_pItem->GetDescription(); + if ( pDescription ) + { + unsigned int unWrittenLines = 0; + for ( unsigned int i = 0; i < pDescription->GetLineCount(); i++ ) + { + const econ_item_description_line_t& line = pDescription->GetLine(i); + if ( (line.unMetaType & ( kDescLineFlag_Name ) ) == 0 ) + { + V_wcscat_safe( wszAttribBuffer, L"\n" ); // add empty lines everywhere + V_wcscat_safe( wszAttribBuffer, line.sText.Get() ); + ++unWrittenLines; + } + } + + // Get all the attributes + Assert( m_pItem->GetDescription() ); + if ( m_pAttribsLabel->GetTextImage() && m_pItem->GetDescription() ) + { + m_pAttribsLabel->SetText( wszAttribBuffer ); + + TextImage *pTextImage = m_pAttribsLabel->GetTextImage(); + Assert( pTextImage ); + + pTextImage->ClearColorChangeStream(); + + IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); + + Color prevCol; + unsigned int unCurrentTextStreamIndex = 0; + for ( unsigned int i = 0; i < pDescription->GetLineCount(); i++ ) + { + const econ_item_description_line_t& line = pDescription->GetLine(i); + + // Ignore the name line, it was added above + if ( ( line.unMetaType & ( kDescLineFlag_Name ) ) != 0 ) + { + continue; + } + + Color col = pScheme->GetColor( GetColorNameForAttribColor( line.eColor ), Color( 255, 255, 255, 255 ) ); + + // Output a color change if necessary. + if ( i == 0 || prevCol != col ) + { + pTextImage->AddColorChange( col, unCurrentTextStreamIndex ); + prevCol = col; + } + + unCurrentTextStreamIndex += StringFuncs<locchar_t>::Length( line.sText.Get() ) + 1; // add one character to deal with newlines + } + + int nWide, nTall; + pTextImage->GetContentSize( nWide, nTall ); + m_pAttribsLabel->SetTall( nTall ); + } + } + + // Set equip slot + { + int nEquipSlot = pItemDef->GetDefaultLoadoutSlot(); + if ( nEquipSlot != -1 ) + { + m_pEquipSlotLabel->SetText( g_pVGuiLocalize->Find( GetItemSchema()->GetLoadoutStringsForDisplay( pItemDef->GetEquipType() )[ nEquipSlot ] ) ); + } + else + { + m_pEquipSlotLabel->SetText( "" ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemCardPanel::UpdateModelOrIcon() +{ + if ( !m_pItem ) + { + return; + } + + m_pItemModel->SetItem( m_pItem ); + + const char *pszModelName = m_pItem->GetPlayerDisplayModel( 0, 0 ); + if ( pszModelName ) + { + MDLHandle_t hMDL = mdlcache->FindMDL( pszModelName ); + m_pItemModel->SetMDL( hMDL, static_cast<IClientRenderable*>(m_pItem) ); + mdlcache->Release( hMDL ); // counterbalance addref from within FindMDL + m_pItemModel->SetForceModelUsage( true ); + } + else + { + m_pItemModel->SetInventoryImageType( CEmbeddedItemModelPanel::IMAGETYPE_LARGE ); + m_pItemModel->LoadInventoryImage(); + m_pItemModel->SetForceModelUsage( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemCardPanel::LoadResFileForCurrentItem() +{ + // Temp hack. New items get the new cards + const char *pszResFile = "Resource/UI/econ/ItemCardPanel_Series1.res"; + if ( m_pItem ) + { + const GameItemDefinition_t *pItemDef = m_pItem->GetItemDefinition(); + if ( pItemDef ) + { + const CEconItemRarityDefinition* pItemRarity = GetItemSchema()->GetRarityDefinition( pItemDef->GetRarity() ); + if ( pItemRarity && pItemRarity->GetDBValue() > 0 ) + { + pszResFile = "Resource/UI/econ/ItemCardPanel_Series2.res"; + } + } + } + + m_bAllControlsValid = false; + + LoadControlSettings( pszResFile ); + + m_bAllControlsValid = true; + // Grab all the controls... + m_pMainContainer = FindAndVerifyControl< EditablePanel >( this, "MainContainer" ); + m_pRarityBackgroundOverlay = FindAndVerifyControl< ImagePanel >( m_pMainContainer, "RarityBackgroundOverlay" ); + m_pBackground = FindAndVerifyControl< ImagePanel >( m_pMainContainer, "Background" ); + m_pGrime = FindAndVerifyControl< ImagePanel >( m_pMainContainer, "GrimeLayer" ); + m_pCardTop = FindAndVerifyControl< EditablePanel >( m_pMainContainer, "CardTop" ); + m_pItemModel = FindAndVerifyControl< CEmbeddedItemModelPanel >( m_pCardTop, "ItemModel" ); + m_pRarityContainer = FindAndVerifyControl< EditablePanel >( m_pMainContainer, "RarityContainer" ); + m_pItemName = FindAndVerifyControl< Label >( m_pRarityContainer, "ItemNameLabel" ); + m_pRarityName = FindAndVerifyControl< Label >( m_pRarityContainer, "ItemRarityLabel" ); + m_pClassIconContainer = FindAndVerifyControl< CRepeatingContainer >( m_pMainContainer, "ClassIconContainer" ); + m_pInfoContainer = FindAndVerifyControl< EditablePanel >( m_pMainContainer, "InfoContainer" ); + m_pClassLabel = FindAndVerifyControl< Label >( m_pInfoContainer, "ClassLabel" ); + m_pTypeLabel = FindAndVerifyControl< Label >( m_pInfoContainer, "TypeLabel" ); + m_pTypeLabelValue = FindAndVerifyControl< Label >( m_pInfoContainer, "TypeValueLabel" ); + m_pExteriorLabel = FindAndVerifyControl< Label >( m_pInfoContainer, "ExteriorLabel" ); + m_pExteriorLabelValue = FindAndVerifyControl< Label >( m_pInfoContainer, "ExteriorValueLabel" ); + m_pBottomContainer = FindAndVerifyControl< EditablePanel >( m_pMainContainer, "BottomContainer" ); + m_pBottomScrollingContainer = FindAndVerifyControl< CExScrollingEditablePanel >( m_pBottomContainer, "ScrollableBottomContainer" ); + m_pAttribsLabel = FindAndVerifyControl< Label >( m_pBottomScrollingContainer, "AttribsLabel" ); + m_pEquipSlotLabel = FindAndVerifyControl< Label >( this, "EquipSlotLabel" ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CItemCardPanelToolTip::CItemCardPanelToolTip( Panel *parent, const char *text ) +: BaseTooltip( parent, text ) +, m_pMouseOverItemPanel( NULL ) +, m_iPositioningStrategy( IPTTP_BOTTOM_SIDE ) +{ + m_hCurrentPanel = NULL; + SetTooltipDelay( 100 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemCardPanelToolTip::GetPosition( itempanel_tooltippos_t iTooltipPosition, CItemModelPanel *pItemPanel, int iItemX, int iItemY, int *iXPos, int *iYPos ) +{ + switch ( iTooltipPosition ) + { + case IPTTP_LEFT: + *iXPos = ( iItemX - m_pMouseOverItemPanel->GetWide() ); + *iYPos = ( iItemY + pItemPanel->GetTall() * 0.5f ) - ( m_pMouseOverItemPanel->GetTall() * 0.5f ); + break; + case IPTTP_RIGHT: + *iXPos = ( iItemX + pItemPanel->GetWide() ); + *iYPos = ( iItemY + pItemPanel->GetTall() * 0.5f ) - ( m_pMouseOverItemPanel->GetTall() * 0.5f ); + break; + } + + *iYPos = Clamp( *iYPos, (int)YRES( -30 ), int( m_pParentPanel->GetTall() - m_pMouseOverItemPanel->GetTall() - YRES( 30 ) ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CItemCardPanelToolTip::ValidatePosition( CItemModelPanel *pItemPanel, int iItemX, int iItemY, int *iXPos, int *iYPos ) +{ + if ( *iXPos < 0 ) + return false; + + if ( ( *iXPos + m_pMouseOverItemPanel->GetWide() ) > m_pParentPanel->GetWide() ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemCardPanelToolTip::PerformLayout() +{ + BaseClass::PerformLayout(); + + if ( !ShouldLayout() ) + return; + + _isDirty = false; + + CItemModelPanel *pItemPanel = m_hCurrentPanel.Get(); + if ( m_pMouseOverItemPanel && pItemPanel && !m_pMouseOverItemPanel->IsPinned() ) + { + CEconItemView *pItem = pItemPanel->GetItem(); + if ( pItem ) + { + m_pMouseOverItemPanel->SetItem( pItem ); + + + int x,y; + + // If the panel is somewhere in a derived class, we need to get its position in our space + if ( pItemPanel->GetParent() != m_pMouseOverItemPanel->GetParent() ) + { + int iItemAbsX, iItemAbsY; + ipanel()->GetAbsPos( pItemPanel->GetVPanel(), iItemAbsX, iItemAbsY ); + int iParentAbsX, iParentAbsY; + ipanel()->GetAbsPos( m_pMouseOverItemPanel->GetParent()->GetVPanel(), iParentAbsX, iParentAbsY ); + + x = (iItemAbsX - iParentAbsX); + y = (iItemAbsY - iParentAbsY); + } + else + { + pItemPanel->GetPos( x, y ); + } + + int iXPos = 0; + int iYPos = 0; + + // Loop through the positions in our strategy, and hope we find a valid spot + for ( int i = 0; i < NUM_POSITIONS_PER_STRATEGY; i++ ) + { + itempanel_tooltippos_t iPos = g_iTooltipStrategies[m_iPositioningStrategy][i]; + if ( iPos != IPTTP_LEFT && iPos != IPTTP_RIGHT ) + continue; + + GetPosition( iPos, pItemPanel, x, y, &iXPos, &iYPos ); + + if ( ValidatePosition( pItemPanel, x, y, &iXPos, &iYPos ) ) + break; + } + + m_pMouseOverItemPanel->SetPos( iXPos, iYPos ); + m_pMouseOverItemPanel->SetVisible( true ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemCardPanelToolTip::ShowTooltip( Panel *currentPanel ) +{ + if ( m_pMouseOverItemPanel && currentPanel != m_hCurrentPanel.Get() ) + { + CItemModelPanel *pItemPanel = assert_cast<CItemModelPanel *>(currentPanel); + m_hCurrentPanel.Set( pItemPanel ); + pItemPanel->PostActionSignal( new KeyValues("ItemPanelEntered") ); + vgui::surface()->PlaySound( "ui/item_info_mouseover.wav" ); + } + BaseClass::ShowTooltip( currentPanel ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemCardPanelToolTip::HideTooltip() +{ + if ( m_pMouseOverItemPanel ) + { + if ( m_pMouseOverItemPanel->IsPinned() ) + return; + + m_pMouseOverItemPanel->SetVisible( false ); + } + + if ( m_hCurrentPanel ) + { + m_hCurrentPanel.Get()->PostActionSignal( new KeyValues("ItemPanelExited") ); + m_hCurrentPanel = NULL; + } +} diff --git a/game/client/tf/vgui/tf_item_card_panel.h b/game/client/tf/vgui/tf_item_card_panel.h new file mode 100644 index 0000000..602991e --- /dev/null +++ b/game/client/tf/vgui/tf_item_card_panel.h @@ -0,0 +1,159 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_ITEM_CARD_PANEL_H +#define TF_ITEM_CARD_PANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include <vgui_controls/Panel.h> +#include <vgui_controls/EditablePanel.h> +#include "item_model_panel.h" +#include "tf_controls.h" + +class CEconItemView; +class CEmbeddedItemModelPanel; +namespace vgui +{ + class ScrollBar; + class ImagePanel; +} +class CIconPanel; + +using namespace vgui; + + +//----------------------------------------------------------------------------- +// Purpose: A simple container that contains repeating elements with common +// and individual characteristics +//----------------------------------------------------------------------------- +class CRepeatingContainer : public EditablePanel +{ + DECLARE_CLASS_SIMPLE( CRepeatingContainer, EditablePanel ); +public: + CRepeatingContainer( Panel *pParent, const char *pszName ); + virtual ~CRepeatingContainer(); + + virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE; + virtual void PerformLayout() OVERRIDE; + + Panel* GetRepeatingChild( int nIndex ) const { return m_vecChildren[ nIndex ]; } +private: + + enum ELayoutMethod_t + { + METHOD_EVEN, + METHOD_STEP, + }; + + CUtlVector< Panel* > m_vecChildren; + ELayoutMethod_t m_eLayoutMethod; + CPanelAnimationVarAliasType( int, m_iXStep, "x_step", "0", "proportional_xpos" ); +}; + +//----------------------------------------------------------------------------- +// Purpose: A representation of an econ item as a collectible card +//----------------------------------------------------------------------------- +class CTFItemCardPanel : public EditablePanel +{ + DECLARE_CLASS_SIMPLE( CTFItemCardPanel, EditablePanel ); +public: + CTFItemCardPanel( Panel *parent, const char *name ); + virtual ~CTFItemCardPanel( void ); + + virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE; + virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE; + virtual void PerformLayout( void ) OVERRIDE; + virtual void SetVisible( bool bVisible ) OVERRIDE; + virtual void SetMouseInputEnabled( bool state ) OVERRIDE; + + void SetItem( CEconItemView* pItem ); + CEconItemView* GetItem() { return m_pItem; } + + void PinCard( bool bPin ); + bool IsPinned() const { return m_bPinned; } +private: + + void UpdateDescription(); + void UpdateModelOrIcon(); + void LoadResFileForCurrentItem(); + + template < class T > + T* FindAndVerifyControl( Panel* pParent, const char* pszPanelName ); + + + CEconItemView* m_pItem; + + ImagePanel *m_pDropShadow; + CExImageButton *m_pCloseButton; + + ImagePanel *m_pBackground; + ImagePanel *m_pGrime; + ImagePanel *m_pRarityBackgroundOverlay; + EditablePanel *m_pMainContainer; + + EditablePanel *m_pCardTop; + CEmbeddedItemModelPanel *m_pItemModel; + + EditablePanel *m_pRarityContainer; + Label *m_pItemName; + Label *m_pRarityName; + + EditablePanel *m_pInfoContainer; + Label *m_pClassLabel; + CRepeatingContainer *m_pClassIconContainer; + Label *m_pTypeLabel; + Label *m_pTypeLabelValue; + Label *m_pExteriorLabel; + Label *m_pExteriorLabelValue; + + EditablePanel *m_pBottomContainer; + CExScrollingEditablePanel *m_pBottomScrollingContainer; + Label *m_pAttribsLabel; + Label *m_pEquipSlotLabel; + + bool m_bAllControlsValid; + bool m_bPinned; + + CPanelAnimationVarAliasType( int, m_iShadowOffset, "shadowoffset", "5", "proportional_int" ); +}; + +//----------------------------------------------------------------------------- +// Purpose: Item model panel tooltip. Calls setvisible on the controlled panel +// and positions it below/above the current panel. +//----------------------------------------------------------------------------- +class CItemCardPanelToolTip : public vgui::BaseTooltip +{ + DECLARE_CLASS_SIMPLE( CItemCardPanelToolTip, vgui::BaseTooltip ); +public: + CItemCardPanelToolTip(vgui::Panel *parent, const char *text = NULL); + + void SetText(const char *text) { return; } + const char *GetText() { return NULL; } + + virtual void PerformLayout(); + virtual void ShowTooltip( vgui::Panel *currentPanel ); + virtual void HideTooltip(); + + void SetupPanels( vgui::Panel *pParentPanel, CTFItemCardPanel *pMouseOverItemPanel ) { m_pParentPanel = pParentPanel; m_pMouseOverItemPanel = pMouseOverItemPanel; } + void SetPositioningStrategy( itempanel_tooltip_strategies_t iStrat ) { m_iPositioningStrategy = iStrat; } + +private: + void GetPosition( itempanel_tooltippos_t iTooltipPosition, CItemModelPanel *pItemPanel, int iItemX, int iItemY, int *iXPos, int *iYPos ); + bool ValidatePosition( CItemModelPanel *pItemPanel, int iItemX, int iItemY, int *iXPos, int *iYPos ); + +private: + CTFItemCardPanel *m_pMouseOverItemPanel; // This is the tooltip panel we make visible. Must be a CItemModelPanel. + vgui::Panel *m_pParentPanel; // This is the panel that we send item entered/exited messages to + vgui::DHANDLE<CItemModelPanel> m_hCurrentPanel; + + itempanel_tooltip_strategies_t m_iPositioningStrategy; + bool m_bHorizontalPreferLeft; +}; + +#endif // TF_ITEM_CARD_PANEL_H diff --git a/game/client/tf/vgui/tf_item_inspection_panel.cpp b/game/client/tf/vgui/tf_item_inspection_panel.cpp new file mode 100644 index 0000000..8b3fc57 --- /dev/null +++ b/game/client/tf/vgui/tf_item_inspection_panel.cpp @@ -0,0 +1,391 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "tf_item_inspection_panel.h" +#include "item_model_panel.h" +#include "navigationpanel.h" +#include "gc_clientsystem.h" +#include "econ_ui.h" +#include "backpack_panel.h" +#include "vgui_int.h" +#include "cdll_client_int.h" +#include "clientmode_tf.h" +#include "ienginevgui.h" +#include "tf_hud_mainmenuoverride.h" +#include "econ_item_system.h" + +using namespace vgui; + +#ifdef STAGING_ONLY +void cc_toggle_debug_inspect( IConVar *pConVar, const char *pOldString, float flOldValue ) +{ + // Too early? + if ( ItemSystem()->GetItemSchema()->GetVersion() == 0 ) + return; + + ConVarRef var( pConVar ); + if ( var.IsValid() ) + { + CTFItemInspectionPanel* pInspectionPanel = dynamic_cast< CTFItemInspectionPanel* >( EconUI()->GetBackpackPanel()->FindChildByName( "InspectionPanel" ) ); + if ( pInspectionPanel ) + { + bool bVisible = pInspectionPanel->IsVisible(); // Save visibility + pInspectionPanel->Reset(); + pInspectionPanel->SetVisible( bVisible ); // Restore visibility + } + } +} + + + +ConVar tf_use_debug_inspection_panel( "tf_use_debug_inspection_panel", "0", FCVAR_ARCHIVE, "Toggles developer controls for the item inspection panel", cc_toggle_debug_inspect ); +extern ConVar tf_paint_kit_force_regen; +#endif + + + +// ******************************************************************************************************************************** +// Given proto data, create an inspect panel +static void Helper_LaunchPreviewWithPreviewDataBlock( CEconItemPreviewDataBlock const &protoData ) +{ + // Create an econ item view + CEconItem econItem; + econItem.DeserializeFromProtoBufItem( protoData.econitem() ); + + CEconItemView itemView; + itemView.Init( econItem.GetDefinitionIndex(), econItem.GetQuality(), econItem.GetItemLevel(), econItem.GetAccountID() ); + itemView.SetNonSOEconItem( &econItem ); + + // Get the Backpack to tell the inspect panel to render + if ( EconUI()->GetBackpackPanel() ) + { + EconUI()->GetBackpackPanel()->OpenInspectModelPanelAndCopyItem( &itemView ); + } +} + +// ******************************************************************************************************************************** +// Request an item from the GC to inspect +static void Helper_RequestEconActionPreview( uint64 paramS, uint64 paramA, uint64 paramD, uint64 paramM ) +{ + if ( !paramA || !paramD ) + return; + if ( !paramS && !paramM ) + return; + + static double s_flTime = 0.0f; + double flNow = Plat_FloatTime(); + if ( s_flTime && ( flNow - s_flTime <= 2.5 ) ) + return; + + s_flTime = flNow; + GCSDK::CProtoBufMsg< CMsgGC_Client2GCEconPreviewDataBlockRequest > msg( k_EMsgGC_Client2GCEconPreviewDataBlockRequest ); + msg.Body().set_param_s( paramS ); + msg.Body().set_param_a( paramA ); + msg.Body().set_param_d( paramD ); + msg.Body().set_param_m( paramM ); + GCClientSystem()->GetGCClient()->BSendMessage( msg ); +} + +// ******************************************************************************************************************************** +class CGCClient2GCEconPreviewDataBlockResponse : public GCSDK::CGCClientJob +{ +public: + CGCClient2GCEconPreviewDataBlockResponse( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { } + + virtual bool BYieldingRunJobFromMsg( GCSDK::IMsgNetPacket *pNetPacket ) + { + GCSDK::CProtoBufMsg<CMsgGC_Client2GCEconPreviewDataBlockResponse> msg( pNetPacket ); + Helper_LaunchPreviewWithPreviewDataBlock( msg.Body().iteminfo() ); + return true; + } +}; +GC_REG_JOB( GCSDK::CGCClient, CGCClient2GCEconPreviewDataBlockResponse, "CGCClient2GCEconPreviewDataBlockResponse", k_EMsgGC_Client2GCEconPreviewDataBlockResponse, GCSDK::k_EServerTypeGCClient ); + +// ******************************************************************************************************************************** +CON_COMMAND_F( tf_econ_item_preview, "Preview an economy item", FCVAR_DONTRECORD | FCVAR_HIDDEN | FCVAR_CLIENTCMD_CAN_EXECUTE ) +{ + if ( args.ArgC() < 2 ) + return; + + //// kill the workshop preview dialog if it's up + //if ( g_pWorkshopWorkbenchDialog ) + //{ + // delete g_pWorkshopWorkbenchDialog; + // g_pWorkshopWorkbenchDialog = NULL; + //} + + //extern float g_flReadyToCheckForPCBootInvite; + //if ( !g_flReadyToCheckForPCBootInvite || !gpGlobals->curtime || !gpGlobals->framecount ) + //{ + // ConMsg( "Deferring csgo_econ_action_preview command!\n" ); + // return; + //} + + // Encoded parameter, validate basic length + char const *pchEncodedAscii = args.Arg( 1 ); + int nLen = Q_strlen( pchEncodedAscii ); + if ( nLen <= 16 ) { Assert( 0 ); return; } + + // If we are launched with new format requesting steam_ownerid and assetid then do async query + if ( *pchEncodedAscii == 'S' ) + { + uint64 uiParamS = Q_atoui64( pchEncodedAscii + 1 ); + uint64 uiParamA = 0; + uint64 uiParamD = 0; + if ( char const *pchParamA = strchr( pchEncodedAscii, 'A' ) ) + { + uiParamA = Q_atoui64( pchParamA + 1 ); + if ( char const *pchParamD = strchr( pchEncodedAscii, 'D' ) ) + { + uiParamD = Q_atoui64( pchParamD + 1 ); + Helper_RequestEconActionPreview( uiParamS, uiParamA, uiParamD, 0ull ); + } + } + return; + } + + // Else if we are launched with new format requesting market listing id and assetid then do async query + if ( *pchEncodedAscii == 'M' ) + { + uint64 uiParamM = Q_atoui64( pchEncodedAscii + 1 ); + uint64 uiParamA = 0; + uint64 uiParamD = 0; + if ( char const *pchParamA = strchr( pchEncodedAscii, 'A' ) ) + { + uiParamA = Q_atoui64( pchParamA + 1 ); + if ( char const *pchParamD = strchr( pchEncodedAscii, 'D' ) ) + { + uiParamD = Q_atoui64( pchParamD + 1 ); + Helper_RequestEconActionPreview( 0ull, uiParamA, uiParamD, uiParamM ); + } + } + return; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFItemInspectionPanel::CTFItemInspectionPanel( Panel* pPanel, const char *pszName ) + : BaseClass( pPanel, pszName ) + , m_pModelInspectPanel( NULL ) + , m_pItemViewData( NULL ) + , m_pSOEconItemData( NULL ) +{ + m_pModelInspectPanel = new CEmbeddedItemModelPanel( this, "ModelInspectionPanel" ); + m_pTeamColorNavPanel = new CNavigationPanel( this, "TeamNavPanel" ); + m_pItemNamePanel = new CItemModelPanel( this, "ItemName" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemInspectionPanel::ApplySchemeSettings( IScheme *pScheme ) +{ + const char* pszFileName = "Resource/UI/econ/InspectionPanel.res"; +#ifdef STAGING_ONLY + if ( tf_use_debug_inspection_panel.GetBool() ) + { + pszFileName = "Resource/UI/econ/InspectionPanel_Dev.res"; + } +#endif + + LoadControlSettings( pszFileName ); + + BaseClass::ApplySchemeSettings( pScheme ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemInspectionPanel::PerformLayout() +{ + CEconItemView* pItem = m_pModelInspectPanel->GetItem(); + if ( pItem && pItem->IsValid() ) + { + // Find out if we should show the team buttons if the item has team skins + if ( m_pTeamColorNavPanel ) + { + static CSchemaAttributeDefHandle pAttrTeamColoredPaintkit( "has team color paintkit" ); + uint32 iHasTeamColoredPaintkit = 0; + + // Find out if the item has the attribute + bool bShowTeamButton = FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pItem, pAttrTeamColoredPaintkit, &iHasTeamColoredPaintkit ) && iHasTeamColoredPaintkit > 0; + + m_pTeamColorNavPanel->SetVisible( bShowTeamButton ); + } + + // Show only the name if there's no rarity + //m_pItemNamePanel->SetNameOnly( true ); + + // Force the description to update right now, or else it might be caught up + // in the queue of 50 panels in the backpack which want to load their crap first + m_pItemNamePanel->UpdateDescription(); + } + + BaseClass::PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemInspectionPanel::OnCommand( const char *command ) +{ + if ( FStrEq( command, "close" ) ) + { + // Clean up after ourselves and set the item back to red + CEconItemView* pItem = m_pModelInspectPanel->GetItem(); + if ( pItem ) + { + pItem->SetTeamNumber( TF_TEAM_RED ); + // We need to recomposite again because we might have a blue version + // in the cache that might get used in a tooltip. + RecompositeItem(); + } + + SetVisible( false ); + return; + } +#ifdef STAGING_ONLY + else if ( FStrEq( command, "newseed" ) ) + { + ConVarRef cv_seed( "tf_paint_kit_seed_override" ); + cv_seed.SetValue( RandomInt( 0, 10000000 ) ); + RecompositeItem(); + return; + } + else if ( FStrEq( command, "changeteam" ) ) + { + ConVarRef cv_team( "tf_paint_kit_team_override" ); + cv_team.SetValue ( cv_team.GetInt() == 0 ? 1 : 0 ); + RecompositeItem(); + return; + } + else if ( V_strncmp( "wear", command, 4 ) == 0 ) + { + int nWearLevel = atoi( command + 4 ); + ConVarRef cv_wear( "tf_paint_kit_force_wear" ); + cv_wear.SetValue( nWearLevel ); + RecompositeItem(); + return; + } + else if ( FStrEq( command, "refresh_skin" ) ) + { + engine->ClientCmd_Unrestricted( "tf_paint_kit_override_refresh" ); + RecompositeItem(); + return; + } +#endif + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemInspectionPanel::SetItem( CEconItemView *pItem ) +{ + m_pItemNamePanel->SetItem( pItem ); + m_pModelInspectPanel->SetItem( pItem ); + RecompositeItem(); + InvalidateLayout(); + m_pTeamColorNavPanel->UpdateButtonSelectionStates( 0 ); +} +//----------------------------------------------------------------------------- +void CTFItemInspectionPanel::SetSpecialAttributesOnly( bool bSpecialOnly ) +{ + if ( m_pItemNamePanel ) + { + if ( bSpecialOnly ) + { + m_pItemNamePanel->SetNameOnly( false ); + } + m_pItemNamePanel->SetSpecialAttributesOnly( bSpecialOnly ); + } +} +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemInspectionPanel::Reset() +{ + InvalidateLayout( true, true ); + SetItem( m_pItemViewData ); +} + +//----------------------------------------------------------------------------- +void CTFItemInspectionPanel::SetItemCopy( CEconItemView *pItem ) +{ + // Make a copy of SO data since it comes from the market and will fall out of scope + if ( m_pItemViewData ) + { + delete m_pItemViewData; + m_pItemViewData = NULL; + } + + if ( m_pSOEconItemData ) + { + delete m_pSOEconItemData; + m_pSOEconItemData = NULL; + } + + if ( pItem ) + { + m_pItemViewData = new CEconItemView( *pItem ); + m_pSOEconItemData = new CEconItem( *pItem->GetSOCData() ); + m_pItemViewData->SetNonSOEconItem( m_pSOEconItemData ); + // always use high res for inspect + m_pItemViewData->SetWeaponSkinUseHighRes( true ); + } + SetItem( m_pItemViewData ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemInspectionPanel::OnRadioButtonChecked( vgui::Panel *panel ) +{ + OnCommand( panel->GetName() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemInspectionPanel::OnNavButtonSelected( KeyValues *pData ) +{ + const int iTeam = pData->GetInt( "userdata", -1 ); AssertMsg( iTeam >= 0, "Bad filter" ); + if ( iTeam < 0 ) + return; + + CEconItemView* pItem = m_pModelInspectPanel->GetItem(); + if ( pItem ) + { + pItem->SetTeamNumber( iTeam ); + + RecompositeItem(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemInspectionPanel::RecompositeItem() +{ + // Force a reload of the skin + CEconItemView* pItem = m_pModelInspectPanel->GetItem(); + if ( pItem ) + { +#ifdef STAGING_ONLY + if ( !tf_paint_kit_force_regen.GetBool() ) +#endif + { + pItem->CancelWeaponSkinComposite(); + pItem->SetWeaponSkinBase( NULL ); + pItem->SetWeaponSkinBaseCompositor( NULL ); + } + } +} diff --git a/game/client/tf/vgui/tf_item_inspection_panel.h b/game/client/tf/vgui/tf_item_inspection_panel.h new file mode 100644 index 0000000..5b57d70 --- /dev/null +++ b/game/client/tf/vgui/tf_item_inspection_panel.h @@ -0,0 +1,54 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_ITEM_INSPECTION_PANEL_H +#define TF_ITEM_INSPECTION_PANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include <vgui_controls/Panel.h> +#include <vgui_controls/EditablePanel.h> +#include "tf_controls.h" + +using namespace vgui; + +class CEconItemView; +class CEmbeddedItemModelPanel; +class CNavigationPanel; +class CItemModelPanel; + +class CTFItemInspectionPanel : public EditablePanel +{ + DECLARE_CLASS_SIMPLE( CTFItemInspectionPanel, EditablePanel ) +public: + CTFItemInspectionPanel( Panel* pPanel, const char *pszName ); + + virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE; + virtual void PerformLayout() OVERRIDE; + virtual void OnCommand( const char *command ) OVERRIDE; + void SetItemCopy ( CEconItemView *pItem ); + void Reset(); + void SetSpecialAttributesOnly( bool bSpecialOnly ); + +private: + // we always want to copy item with SetItemCopy to make sure that we use high res skin + void SetItem( CEconItemView *pItem ); + void RecompositeItem(); + CNavigationPanel* m_pTeamColorNavPanel; + + MESSAGE_FUNC_PARAMS( OnNavButtonSelected, "NavButtonSelected", pData ); + MESSAGE_FUNC_PTR( OnRadioButtonChecked, "RadioButtonChecked", panel ); + + CEmbeddedItemModelPanel *m_pModelInspectPanel; + CItemModelPanel *m_pItemNamePanel; + + CEconItemView *m_pItemViewData; + CEconItem *m_pSOEconItemData; +}; + +#endif // TF_ITEM_INSPECTION_PANEL_H diff --git a/game/client/tf/vgui/tf_item_pickup_panel.cpp b/game/client/tf/vgui/tf_item_pickup_panel.cpp new file mode 100644 index 0000000..c26d5bd --- /dev/null +++ b/game/client/tf/vgui/tf_item_pickup_panel.cpp @@ -0,0 +1,384 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "vgui/IInput.h" +#include <vgui/IVGui.h> +#include <vgui/IScheme.h> +#include "tf_item_pickup_panel.h" +#include "iclientmode.h" +#include "baseviewport.h" +#include "econ_entity.h" +#include "c_baseplayer.h" +#include "gamestringpool.h" +#include "vgui_controls/TextImage.h" +#include "vgui_controls/Label.h" +#include "vgui_controls/Button.h" +#include "econ_item_system.h" +#include "ienginevgui.h" +#include "achievementmgr.h" +#include "fmtstr.h" +#include "tf_item_inventory.h" +#include "item_confirm_delete_dialog.h" +#include "backpack_panel.h" +#include "econ_ui.h" +#include "c_tf_player.h" +#include "character_info_panel.h" + +ConVar tf_explanations_discardpanel( "tf_explanations_discardpanel", "0", FCVAR_ARCHIVE, "Whether the user has seen explanations for this panel." ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFItemPickupPanel::CTFItemPickupPanel( Panel *parent ) : CItemPickupPanel( parent ) +{ + m_pClassImage = NULL; + m_pClassImageBG = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemPickupPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + m_pClassImage = dynamic_cast<vgui::ImagePanel*>(FindChildByName( "classimage" )); + m_pClassImageBG = FindChildByName( "classimageoutline" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemPickupPanel::OnCommand( const char *command ) +{ + if ( !Q_stricmp( command, "changeloadout" ) ) + { + // We dont want the UI to close -- we're about to change our loadout + SetReturnToGame( false ); + AcknowledgeItems(); + + int iClass = TF_CLASS_UNDEFINED; + if ( m_iSelectedItem >= 0 && m_iSelectedItem < m_aItems.Count() ) + { + if ( m_aItems[m_iSelectedItem].pItem.IsValid() && !m_aItems[m_iSelectedItem].bDiscarded ) + { + // Open the loadout panel with the first class that can use this item (or the base loadout screen if it's an all-class item) + if ( m_aItems[m_iSelectedItem].pItem.GetStaticData()->CanBeUsedByAllClasses() ) + { + iClass = TF_CLASS_UNDEFINED; + } + else + { + for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; i++ ) + { + if ( m_aItems[m_iSelectedItem].pItem.GetStaticData()->CanBeUsedByClass(i) ) + { + iClass = -i; + break; + } + } + + if ( iClass == TF_CLASS_UNDEFINED ) + { + // Item's not usable by any class. Go to backpack. + iClass = ECONUI_BACKPACK; + } + } + } + } + + ShowPanel( false ); + + EconUI()->OpenEconUI( iClass, true ); + } + else + { + BaseClass::OnCommand( command ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemPickupPanel::UpdateModelPanels( void ) +{ + BaseClass::UpdateModelPanels(); + + if ( m_pClassImage ) + { + m_pClassImage->SetVisible( false ); + if ( m_pClassImageBG ) + { + m_pClassImageBG->SetVisible( false ); + } + if ( m_aModelPanels[2]->HasItem() ) + { + CEconItemView *pItem = m_aModelPanels[2]->GetItem(); + + int iClass = -1; + if ( pItem->GetStaticData()->CanBeUsedByAllClasses() ) + { + iClass = TF_CLASS_UNDEFINED; + } + else + { + // Find a class that can use the item, and show that class image + for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; i++ ) + { + if ( pItem->GetStaticData()->CanBeUsedByClass(i) ) + { + iClass = i; + break; + } + } + } + + if ( iClass != -1 ) + { + m_pClassImage->SetImage( g_pszItemClassImagesRed[iClass] ); + m_pClassImage->SetVisible( true ); + if ( m_pClassImageBG ) + { + m_pClassImageBG->SetVisible( true ); + } + } + } + } + + // Update the loadout button as appropriate + if ( m_iSelectedItem >= 0 && m_iSelectedItem < m_aItems.Count() ) + { + bool bDiscarded = false; + if ( m_iSelectedItem >= 0 && m_iSelectedItem < m_aItems.Count() ) + { + bDiscarded = m_aItems[m_iSelectedItem].bDiscarded; + } + + // Open the loadout panel with the first class that can use this item + if ( m_aItems[m_iSelectedItem].pItem.IsValid() && !bDiscarded ) + { + if ( m_aItems[m_iSelectedItem].pItem.GetStaticData()->CanBeUsedByAllClasses() ) + { + SetDialogVariable("loadouttext", g_pVGuiLocalize->Find( "#OpenGeneralLoadout" ) ); + } + else + { + int iClass = TF_CLASS_UNDEFINED; + for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; i++ ) + { + if ( m_aItems[m_iSelectedItem].pItem.GetStaticData()->CanBeUsedByClass(i) ) + { + iClass = i; + break; + } + } + + if ( iClass != TF_CLASS_UNDEFINED ) + { + wchar_t wzLocalized[128]; + g_pVGuiLocalize->ConstructString_safe( wzLocalized, g_pVGuiLocalize->Find( "#OpenSpecificLoadout" ), 1, g_pVGuiLocalize->Find( g_aPlayerClassNames[iClass] ) ); + SetDialogVariable("loadouttext", wzLocalized ); + } + else + { + SetDialogVariable("loadouttext", g_pVGuiLocalize->Find( "#OpenBackpack" ) ); + m_pOpenLoadoutButton->SetVisible( true ); + } + } + } + } +} + +static vgui::DHANDLE<CTFItemPickupPanel> g_TFItemPickupPanel; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFItemPickupPanel *OpenTFItemPickupPanel( void ) +{ + if (!g_TFItemPickupPanel.Get()) + { + g_TFItemPickupPanel = vgui::SETUP_PANEL( new CTFItemPickupPanel( NULL ) ); + g_TFItemPickupPanel->InvalidateLayout( false, true ); + } + + engine->ClientCmd_Unrestricted( "gameui_activate" ); + g_TFItemPickupPanel->ShowPanel( true ); + + return g_TFItemPickupPanel; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFItemPickupPanel *GetTFItemPickupPanel( void ) +{ + return g_TFItemPickupPanel.Get(); +} + +//======================================================================================================================================================= +// ITEM DISCARD PANEL +//======================================================================================================================================================= +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFItemDiscardPanel::CTFItemDiscardPanel( Panel *parent ) : CItemDiscardPanel( parent ) +{ + m_flStartExplanationsAt = 0; + m_pExplanationALabel = NULL; + m_pExplanationBLabel = NULL; + m_pExplanationCaratLabel = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemDiscardPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + m_pExplanationALabel = dynamic_cast<vgui::Label*>( FindChildByName("ExplanationLabel") ); + m_pExplanationBLabel = dynamic_cast<vgui::Label*>( FindChildByName("ExplanationLabel2") ); + m_pExplanationCaratLabel = dynamic_cast<vgui::Label*>( FindChildByName("CaratLabel2") ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemDiscardPanel::PerformLayout( void ) +{ + BaseClass::PerformLayout(); + + m_pExplanationALabel->SetVisible( !m_bDiscardedNewItem && !m_bMadeRoom ); + m_pExplanationBLabel->SetVisible( !m_bDiscardedNewItem && !m_bMadeRoom ); + m_pExplanationCaratLabel->SetVisible( !m_bDiscardedNewItem && !m_bMadeRoom ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemDiscardPanel::ShowPanel(bool bShow) +{ + BaseClass::ShowPanel( bShow ); + + if ( bShow ) + { + if ( !tf_explanations_discardpanel.GetBool() ) + { + m_flStartExplanationsAt = engine->Time() + 0.5; + vgui::ivgui()->AddTickSignal( GetVPanel() ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemDiscardPanel::OnTick( void ) +{ + BaseClass::OnTick(); + + if ( m_flStartExplanationsAt && m_flStartExplanationsAt < engine->Time() && TFModalStack()->IsEmpty() ) + { + m_flStartExplanationsAt = 0; + + tf_explanations_discardpanel.SetValue( 1 ); + + CExplanationPopup *pPopup = dynamic_cast<CExplanationPopup*>( FindChildByName("StartExplanation") ); + if ( pPopup ) + { + pPopup->Popup(); + } + } + + if ( !m_flStartExplanationsAt ) + { + vgui::ivgui()->RemoveTickSignal( GetVPanel() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFItemDiscardPanel::OnCommand( const char *command ) +{ + if ( !Q_stricmp( command, "show_explanations" ) ) + { + if ( !m_flStartExplanationsAt ) + { + m_flStartExplanationsAt = engine->Time(); + vgui::ivgui()->AddTickSignal( GetVPanel() ); + } + RequestFocus(); + } + else + { + BaseClass::OnCommand( command ); + } +} + +#if defined(DEBUG) +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Test_ItemPickupPanel( const CCommand &args ) +{ + int iClass = TF_CLASS_PYRO; + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( pLocalPlayer ) + { + iClass = pLocalPlayer->GetPlayerClass()->GetClassIndex(); + } + + CItemPickupPanel *pItemPanel = EconUI()->OpenItemPickupPanel(); + pItemPanel->InvalidateLayout( false, true ); + + for ( int i = 0; i < CLASS_LOADOUT_POSITION_COUNT; i++ ) + { + CEconItemView *pItem = TFInventoryManager()->GetItemInLoadoutForClass( iClass, i ); + if ( pItem && pItem->IsValid() ) + { + pItemPanel->AddItem( pItem ); + } + } + + pItemPanel->DebugRandomizePickupMethods(); +} +ConCommand test_itempickuppanel( "test_itempickuppanel", Test_ItemPickupPanel, "Debugging tool to test the item pickup panel. Usage: test_itempickuppanel\n", FCVAR_CHEAT ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Test_ItemDiscardPanel( const CCommand &args ) +{ + int iClass = TF_CLASS_PYRO; + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( pLocalPlayer ) + { + iClass = pLocalPlayer->GetPlayerClass()->GetClassIndex(); + } + + CItemDiscardPanel *pItemPanel = EconUI()->OpenItemDiscardPanel(); + pItemPanel->InvalidateLayout( false, true ); + + CEconItemView *pItemView = NULL; + + bool bAllItems = (args.ArgC() <= 1); + for ( int i = bAllItems ? 0 : clamp( atoi(args[1]), 0, 2 ); i <= 2 && !pItemView; i++ ) + { + pItemView = TFInventoryManager()->GetItemInLoadoutForClass( iClass, i ); + } + + if ( pItemView ) + { + pItemPanel->SetItem( pItemView ); + } +} + +ConCommand test_itemdiscardpanel( "test_itemdiscardpanel", Test_ItemDiscardPanel, "Debugging tool to test the item discard panel. Usage: test_itemdiscardpanel <weapon name>\n <weapon id>: 0 = primary, 1 = secondary, 2 = melee.", FCVAR_CHEAT ); +#endif // defined(DEBUG) + diff --git a/game/client/tf/vgui/tf_item_pickup_panel.h b/game/client/tf/vgui/tf_item_pickup_panel.h new file mode 100644 index 0000000..8c8728d --- /dev/null +++ b/game/client/tf/vgui/tf_item_pickup_panel.h @@ -0,0 +1,58 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_ITEM_PICKUP_PANEL_H +#define TF_ITEM_PICKUP_PANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "item_pickup_panel.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CTFItemPickupPanel : public CItemPickupPanel +{ + DECLARE_CLASS_SIMPLE( CTFItemPickupPanel, CItemPickupPanel ); +public: + CTFItemPickupPanel( Panel *parent ); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void OnCommand( const char *command ); + +protected: + virtual void UpdateModelPanels( void ); + +private: + vgui::ImagePanel *m_pClassImage; + vgui::Panel *m_pClassImageBG; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CTFItemDiscardPanel : public CItemDiscardPanel +{ + DECLARE_CLASS_SIMPLE( CTFItemDiscardPanel, CItemDiscardPanel ); +public: + CTFItemDiscardPanel( Panel *parent ); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void PerformLayout( void ); + virtual void ShowPanel( bool bShow ); + virtual void OnTick( void ); + virtual void OnCommand( const char *command ); + +private: + vgui::Label *m_pExplanationALabel; + vgui::Label *m_pExplanationBLabel; + vgui::Label *m_pExplanationCaratLabel; + float m_flStartExplanationsAt; +}; + +#endif // TF_ITEM_PICKUP_PANEL_H diff --git a/game/client/tf/vgui/tf_layeredmappanel.cpp b/game/client/tf/vgui/tf_layeredmappanel.cpp new file mode 100644 index 0000000..65fd609 --- /dev/null +++ b/game/client/tf/vgui/tf_layeredmappanel.cpp @@ -0,0 +1,343 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tf_layeredmappanel.h" + +using namespace vgui; + +//========================================================= +// CLayeredMapToolTip +//========================================================= +CLayeredMapToolTip::CLayeredMapToolTip(vgui::Panel *parent, const char *text ) : vgui::BaseTooltip( parent, text ) +{ + m_hCurrentPanel = NULL; +} + +//========================================================= +void CLayeredMapToolTip::PerformLayout() +{ + BaseClass::PerformLayout(); + + if ( !ShouldLayout() ) + return; + + CTFLayeredMapItemPanel *pMapItemPanel = m_hCurrentPanel.Get(); + if ( !pMapItemPanel ) + return; + + m_pControlledPanel->SetVisible( false ); + + int x,y; + pMapItemPanel->GetPos( x, y ); + + int iXPos = 0; + int iYPos = 0; + + // Loop through the positions in our strategy, and hope we find a valid spot + for ( int i = 0; i < NUM_POSITIONS_PER_STRATEGY; i++ ) + { + itempanel_tooltippos_t iPos = g_iTooltipStrategies[IPTTP_TOP_SIDE][i]; + GetPosition( iPos, pMapItemPanel, x, y, &iXPos, &iYPos ); + + if ( ValidatePosition( pMapItemPanel, x, y, &iXPos, &iYPos ) ) + break; + } + + m_pControlledPanel->SetPos( iXPos, iYPos ); + m_pControlledPanel->SetVisible( true ); +} + +//========================================================= +void CLayeredMapToolTip::ShowTooltip( vgui::Panel *currentPanel ) +{ + // Set Data on visible panel + if ( !m_pControlledPanel ) + return; + + if ( currentPanel != m_hCurrentPanel.Get() ) + { + CTFLayeredMapItemPanel *pMapItemPanel = dynamic_cast<CTFLayeredMapItemPanel*>( currentPanel ); + m_hCurrentPanel.Set( pMapItemPanel ); + m_pControlledPanel->SetVisible( false ); + if ( pMapItemPanel ) + { + KeyValues *pData = pMapItemPanel->GetItemKvData(); + if ( pData ) + { + m_pControlledPanel->SetDialogVariable( "tooltipdescription", pData->GetString( "name", "No Name" ) ); + } + } + } + BaseClass::ShowTooltip( currentPanel ); +} + +//========================================================= +void CLayeredMapToolTip::HideTooltip() +{ + if ( !m_pControlledPanel ) + return; + + m_pControlledPanel->SetVisible( false ); + m_hCurrentPanel = NULL; +} + +//========================================================= +void CLayeredMapToolTip::SetupPanels( CTFLayeredMapPanel *pParentPanel, vgui::EditablePanel *pControlledPanel ) +{ + m_pParentPanel = pParentPanel; + m_pControlledPanel = pControlledPanel; +} + +//========================================================= +void CLayeredMapToolTip::GetPosition( itempanel_tooltippos_t iTooltipPosition, CTFLayeredMapItemPanel *pItemPanel, int iItemX, int iItemY, int *iXPos, int *iYPos ) +{ + switch ( iTooltipPosition ) + { + case IPTTP_LEFT: + *iXPos = (iItemX - m_pControlledPanel->GetWide() + XRES(18)); + *iYPos = iItemY - YRES(7); + break; + case IPTTP_RIGHT: + *iXPos = (iItemX + pItemPanel->GetWide() - XRES(20)); + *iYPos = iItemY - YRES(7); + break; + case IPTTP_LEFT_CENTERED: + *iXPos = (iItemX - m_pControlledPanel->GetWide()) - XRES(4); + *iYPos = (iItemY - (m_pControlledPanel->GetTall() * 0.5)); + break; + case IPTTP_RIGHT_CENTERED: + *iXPos = (iItemX + pItemPanel->GetWide()) + XRES(4); + *iYPos = (iItemY - (m_pControlledPanel->GetTall() * 0.5)); + break; + case IPTTP_ABOVE: + *iXPos = (iItemX + (pItemPanel->GetWide() * 0.5)) - (m_pControlledPanel->GetWide() * 0.5); + *iYPos = (iItemY - m_pControlledPanel->GetTall() - YRES(4)); + break; + case IPTTP_BELOW: + *iXPos = (iItemX + (pItemPanel->GetWide() * 0.5)) - (m_pControlledPanel->GetWide() * 0.5); + *iYPos = (iItemY + pItemPanel->GetTall() + YRES(4)); + break; + } +} + +//========================================================= +bool CLayeredMapToolTip::ValidatePosition( CTFLayeredMapItemPanel *pItemPanel, int iItemX, int iItemY, int *iXPos, int *iYPos ) +{ + bool bSucceeded = true; + + // Make sure the popup stays onscreen. + if ( *iXPos < 0 ) + { + *iXPos = 0; + } + else if ( (*iXPos + m_pControlledPanel->GetWide()) > m_pParentPanel->GetWide() ) + { + int iXPosNew = m_pParentPanel->GetWide() - m_pControlledPanel->GetWide(); + // make sure it is still on the screen + if ( iXPosNew >= 0 ) + { + *iXPos = iXPosNew; + } + else + { + bSucceeded = false; + } + } + + if ( *iYPos < 0 ) + { + *iYPos = 0; + } + else if ( (*iYPos + m_pControlledPanel->GetTall() + YRES(32)) > m_pParentPanel->GetTall() ) + { + // Move it up above our item + int iYPosNew = iItemY - m_pControlledPanel->GetTall() - YRES(4); + // make sure it is still on the screen + if ( iYPosNew >= 0 ) + { + *iYPos = iYPosNew; + } + else + { + bSucceeded = false; + } + } + + if ( bSucceeded ) + { + // We also fail if moving it to keep it on screen moved it over the item panel itself + Vector2D vecToolTipMin, vecToolTipMax, vecItemMin, vecItemMax; + vecToolTipMin.x = *iXPos; + vecToolTipMin.y = *iYPos; + vecToolTipMax.x = vecToolTipMin.x + m_pControlledPanel->GetWide(); + vecToolTipMax.y = vecToolTipMin.y + m_pControlledPanel->GetTall(); + + vecItemMin.x = iItemX; + vecItemMin.y = iItemY; + vecItemMax.x = vecItemMin.x + m_hCurrentPanel->GetWide(); + vecItemMax.y = vecItemMin.y + m_hCurrentPanel->GetTall(); + + bSucceeded = !( vecToolTipMin.x < vecItemMax.x && vecToolTipMax.x > vecItemMin.x && vecToolTipMin.y < vecItemMax.y && vecToolTipMax.y > vecItemMin.y ); + } + + return bSucceeded; +} + +//----------------------------------------------------------------------------- +// CTFLayeredImagePanelItem +//----------------------------------------------------------------------------- +DECLARE_BUILD_FACTORY( CTFLayeredMapItemPanel ); + +CTFLayeredMapItemPanel::CTFLayeredMapItemPanel( Panel *parent, const char *pName ) : vgui::EditablePanel( parent, pName ) +{ + m_bIsCompleted = false; + m_bIsMouseOvered = false; + + m_kvData = NULL; +} + +//----------------------------------------------------------------------------- +void CTFLayeredMapItemPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + // load control settings... + LoadControlSettings( "resource/UI/LayeredMapPanelItem.res" ); + + // get References to the 4 images + m_pIsCompleted = dynamic_cast<vgui::ScalableImagePanel*>( FindChildByName("IsCompletedImage") ); + m_pIsCompletedHighlight = dynamic_cast<vgui::ScalableImagePanel*>( FindChildByName("IsCompletedHighlight") ); + + m_pNotCompleted = dynamic_cast<vgui::ScalableImagePanel*>( FindChildByName("NotCompletedImage") ); + m_pNotCompletedHighlight = dynamic_cast<vgui::ScalableImagePanel*>( FindChildByName("NotCompletedHighlight") ); + + m_pIsCompleted->SetVisible( true ); + m_pIsCompleted->SetMouseInputEnabled( false ); + m_pIsCompletedHighlight->SetVisible( false ); + m_pIsCompletedHighlight->SetMouseInputEnabled( false ); + m_pNotCompleted->SetVisible( false ); + m_pNotCompleted->SetMouseInputEnabled( false ); + m_pNotCompletedHighlight->SetVisible( false ); + m_pNotCompletedHighlight->SetMouseInputEnabled( false ); + + SetMouseInputEnabled( true ); +} + +//----------------------------------------------------------------------------- +void CTFLayeredMapItemPanel::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + // Store Information needed for a mouse over + + KeyValues *kvData = inResourceData->FindKey( "mapitem_kv" ); + if ( kvData ) + { + if ( m_kvData ) + { + m_kvData->deleteThis(); + } + m_kvData = new KeyValues( "mapitem_kv" ); + kvData->CopySubkeys( m_kvData ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFLayeredMapItemPanel::OnCursorEntered( void ) +{ + BaseClass::OnCursorEntered(); + m_pIsCompletedHighlight->SetVisible( m_bIsCompleted ); + m_pNotCompletedHighlight->SetVisible( !m_bIsCompleted ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFLayeredMapItemPanel::OnCursorExited( void ) +{ + BaseClass::OnCursorExited(); + m_pIsCompletedHighlight->SetVisible( false ); + m_pNotCompletedHighlight->SetVisible( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFLayeredMapItemPanel::OnMousePressed(vgui::MouseCode code) +{ + SetCompletionState( !m_bIsCompleted ); +} + +//----------------------------------------------------------------------------- +void CTFLayeredMapItemPanel::SetCompletionState( bool bIsCompleted ) +{ + m_bIsCompleted = bIsCompleted; + m_pIsCompleted->SetVisible( m_bIsCompleted ); + m_pNotCompleted->SetVisible( !m_bIsCompleted ); +} + +//----------------------------------------------------------------------------- +// CTFLayeredMapPanel +// - MultiImage Panel that places all images on top of each other. +// An external manager may then individually toggle each image on or off +// Each image is sized to fit the panel +//----------------------------------------------------------------------------- +DECLARE_BUILD_FACTORY( CTFLayeredMapPanel ); + +CTFLayeredMapPanel::CTFLayeredMapPanel( Panel *parent, const char *pName ) : vgui::EditablePanel( parent, pName ) +{ + m_pLayeredMapKv = NULL; +} + +//----------------------------------------------------------------------------- +void CTFLayeredMapPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + // load control settings... + LoadControlSettings( "resource/UI/LayeredMapPanel.res" ); + + m_MapItems.PurgeAndDeleteElements(); + + // Get ToolTip + m_pToolTipPanel = dynamic_cast<EditablePanel*>( FindChildByName( "ToolTipPanel" ) ); + m_pToolTip = new CLayeredMapToolTip( this ); + m_pToolTip->SetupPanels( this, m_pToolTipPanel ); + + if ( m_pLayeredMapKv ) + { + int iItemCount = m_pLayeredMapKv->GetInt( "item_count", 0 ); + for ( int i = 0; i < iItemCount; ++i ) + { + CTFLayeredMapItemPanel *pItem = dynamic_cast<CTFLayeredMapItemPanel*>( FindChildByName( VarArgs( "MapItem%d", i ) ) ); + if ( pItem ) + { + pItem->SetTooltip( m_pToolTip, "" ); + m_MapItems.AddToTail( pItem ); + } + } + } +} + +//----------------------------------------------------------------------------- +void CTFLayeredMapPanel::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + if ( inResourceData ) + { + if ( m_pLayeredMapKv ) + { + m_pLayeredMapKv->deleteThis(); + } + m_pLayeredMapKv = new KeyValues( "layeredMapPanel_kv" ); + inResourceData->CopySubkeys( m_pLayeredMapKv ); + } +} + diff --git a/game/client/tf/vgui/tf_layeredmappanel.h b/game/client/tf/vgui/tf_layeredmappanel.h new file mode 100644 index 0000000..e3418fc --- /dev/null +++ b/game/client/tf/vgui/tf_layeredmappanel.h @@ -0,0 +1,106 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Layered Map that contains a background and stateful locations. +// Each location will support mouse over information +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_LAYEREDMAPPANEL_H +#define TF_LAYEREDMAPPANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include <vgui/IScheme.h> +#include <vgui_controls/EditablePanel.h> +#include <vgui_controls/ScalableImagePanel.h> +#include <vgui_controls/Tooltip.h> +#include "item_model_panel.h" + +class CTFLayeredMapPanel; +class CTFLayeredMapItemPanel; + +//========================================================= +class CLayeredMapToolTip : public vgui::BaseTooltip +{ + DECLARE_CLASS_SIMPLE( CLayeredMapToolTip, vgui::BaseTooltip ); +public: + CLayeredMapToolTip(vgui::Panel *parent, const char *text = NULL); + + void SetText(const char *text) { return; } + const char *GetText() { return NULL; } + + virtual void PerformLayout(); + virtual void ShowTooltip( vgui::Panel *currentPanel ); + virtual void HideTooltip(); + + void SetupPanels( CTFLayeredMapPanel *pParentPanel, vgui::EditablePanel *pControlledPanel ); + +private: + void GetPosition( itempanel_tooltippos_t iTooltipPosition, CTFLayeredMapItemPanel *pMapItemPanel, int iItemX, int iItemY, int *iXPos, int *iYPos ); + bool ValidatePosition( CTFLayeredMapItemPanel *pItemPanel, int iItemX, int iItemY, int *iXPos, int *iYPos ); + + vgui::DHANDLE<CTFLayeredMapItemPanel> m_hCurrentPanel; + + CTFLayeredMapPanel *m_pParentPanel; + vgui::EditablePanel *m_pControlledPanel; +}; + +//========================================================= +class CTFLayeredMapItemPanel : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CTFLayeredMapItemPanel, vgui::EditablePanel ); +public: + CTFLayeredMapItemPanel( Panel *parent, const char *pName ); + + virtual void ApplySettings( KeyValues *inResourceData ); + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + + // Button + virtual void OnCursorEntered(); + virtual void OnCursorExited(); + virtual void OnMousePressed(vgui::MouseCode code); + + // + void SetCompletionState( bool bIsCompleted ); + KeyValues * GetItemKvData () { return m_kvData; } + +private: + + vgui::ScalableImagePanel *m_pIsCompleted; + vgui::ScalableImagePanel *m_pIsCompletedHighlight; + + vgui::ScalableImagePanel *m_pNotCompleted; + vgui::ScalableImagePanel *m_pNotCompletedHighlight; + + bool m_bIsCompleted; + bool m_bIsMouseOvered; + + KeyValues *m_kvData; +}; + +//========================================================= +class CTFLayeredMapPanel : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CTFLayeredMapPanel, vgui::EditablePanel ); +public: + CTFLayeredMapPanel( Panel *parent, const char *pName ); + + virtual void ApplySettings( KeyValues *inResourceData ); + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + + + //CUtlVector<vgui::ImagePanel*> m_pImages; + +private: + + EditablePanel *m_pToolTipPanel; + CLayeredMapToolTip *m_pToolTip; + + KeyValues *m_pLayeredMapKv; + + CUtlVector<CTFLayeredMapItemPanel*> m_MapItems; +}; + +#endif // TF_LAYEREDIMAGEPANEL_H
\ No newline at end of file diff --git a/game/client/tf/vgui/tf_leaderboardpanel.cpp b/game/client/tf/vgui/tf_leaderboardpanel.cpp new file mode 100644 index 0000000..2e0327d --- /dev/null +++ b/game/client/tf/vgui/tf_leaderboardpanel.cpp @@ -0,0 +1,103 @@ +#include "cbase.h" +#include "tf_leaderboardpanel.h" +#include "econ_controls.h" +#include "tf_asyncpanel.h" +#include "tf_mapinfo.h" +#include "vgui_avatarimage.h" +#include "tf_item_inventory.h" + + +CTFLeaderboardPanel::CTFLeaderboardPanel( Panel *pParent, const char *pszPanelName ) + : CBaseASyncPanel( pParent, pszPanelName ) +{} + +//----------------------------------------------------------------------------- +// Purpose: Create leaderboard panels +//----------------------------------------------------------------------------- +void CTFLeaderboardPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "Resource/UI/econ/LeaderboardPanel.res" ); +} + +void CTFLeaderboardPanel::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); + m_EvenTextColor = GetSchemeColor( inResourceData->GetString( "EvenTextColor" ), pScheme); + m_OddTextColor = GetSchemeColor( inResourceData->GetString( "OddTextColor" ), pScheme); + m_LocalPlayerTextColor = GetSchemeColor( inResourceData->GetString( "LocalPlayerTextColor" ), pScheme); + + EditablePanel *pScoresContainer = dynamic_cast< EditablePanel* >( FindChildByName( "ScoresContainer", true ) ); + + if ( pScoresContainer ) + { + m_vecLeaderboardEntries.Purge(); + for ( int i = 0; i < 7; ++ i ) + { + vgui::EditablePanel *pEntryUI = new vgui::EditablePanel( pScoresContainer, "LeaderboardEntry" ); + pEntryUI->ApplySchemeSettings( pScheme ); + pEntryUI->LoadControlSettings( "Resource/UI/LeaderboardSpreadEntry.res" ); + m_vecLeaderboardEntries.AddToTail( pEntryUI ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Check for leaderboard data +//----------------------------------------------------------------------------- +bool CTFLeaderboardPanel::CheckForData_Internal() +{ + return UpdateLeaderboards(); +} + +//----------------------------------------------------------------------------- +// Purpose: Checks if we have friends leaderboard data downloaded. If so, sets +// the data into the panels +//----------------------------------------------------------------------------- +bool CTFLeaderboardPanel::UpdateLeaderboards() +{ + CUtlVector< LeaderboardEntry_t* > scores; + if ( !GetLeaderboardData( scores ) ) + return false; + + int x=0,y=0; + FOR_EACH_VEC( m_vecLeaderboardEntries, i ) + { + Color colorToUse = i % 2 == 1 ? m_OddTextColor : m_EvenTextColor; + EditablePanel *pContainer = dynamic_cast< EditablePanel* >( m_vecLeaderboardEntries[i] ); + if ( pContainer ) + { + bool bIsEntryVisible = i < scores.Count(); + pContainer->SetVisible( bIsEntryVisible ); + pContainer->SetPos( x, y ); + y += m_yEntryStep; + if ( bIsEntryVisible ) + { + const LeaderboardEntry_t* leaderboardEntry = scores[i]; + const CSteamID &steamID = leaderboardEntry->m_steamIDUser; + bool bIsLocalPlayer = steamapicontext && steamapicontext->SteamUser() && steamapicontext->SteamUser()->GetSteamID() == steamID; + pContainer->SetDialogVariable( "rank", leaderboardEntry->m_nGlobalRank ); + pContainer->SetDialogVariable( "username", InventoryManager()->PersonaName_Get( steamID.GetAccountID() ) ); + pContainer->SetDialogVariable( "score", leaderboardEntry->m_nScore ); + + CExLabel *pText = dynamic_cast< CExLabel* >( pContainer->FindChildByName( "UserName" ) ); + if ( pText ) + { + pText->SetColorStr( bIsLocalPlayer ? m_LocalPlayerTextColor : colorToUse ); + } + + CAvatarImagePanel *pAvatar = dynamic_cast< CAvatarImagePanel* >( pContainer->FindChildByName( "AvatarImage" ) ); + if ( pAvatar ) + { + pAvatar->SetShouldDrawFriendIcon( false ); + pAvatar->SetPlayer( steamID, k_EAvatarSize32x32 ); + } + } + } + } + + return true; +} diff --git a/game/client/tf/vgui/tf_leaderboardpanel.h b/game/client/tf/vgui/tf_leaderboardpanel.h new file mode 100644 index 0000000..8420d84 --- /dev/null +++ b/game/client/tf/vgui/tf_leaderboardpanel.h @@ -0,0 +1,36 @@ +#ifndef TF_LEADERBOARDPANEL_H +#define TF_LEADERBOARDPANEL_H + +#include "vgui_controls/EditablePanel.h" +#include "tf_wardata.h" +#include "vgui_controls/ProgressBar.h" +#include "quest_log_panel.h" +#include "tf_asyncpanel.h" + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CTFLeaderboardPanel : public CBaseASyncPanel +{ + DECLARE_CLASS_SIMPLE( CTFLeaderboardPanel, CBaseASyncPanel ); +public: + CTFLeaderboardPanel( Panel *pParent, const char *pszPanelName ); + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE; + virtual void ApplySettings( KeyValues *inResourceData ); + +protected: + virtual bool GetLeaderboardData( CUtlVector< LeaderboardEntry_t* >& scores ) = 0; + virtual bool UpdateLeaderboards(); + virtual bool CheckForData_Internal() OVERRIDE; + + CUtlVector< EditablePanel* > m_vecLeaderboardEntries; + + CPanelAnimationVarAliasType( int, m_yEntryStep, "entry_step", "5", "proportional_int"); + Color m_EvenTextColor; + Color m_OddTextColor; + Color m_LocalPlayerTextColor; +}; + +#endif //TF_LEADERBOARDPANEL_H
\ No newline at end of file diff --git a/game/client/tf/vgui/tf_lobby_container_frame.cpp b/game/client/tf/vgui/tf_lobby_container_frame.cpp new file mode 100644 index 0000000..b8d0e48 --- /dev/null +++ b/game/client/tf/vgui/tf_lobby_container_frame.cpp @@ -0,0 +1,630 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" + +#include "ienginevgui.h" +#include "vgui/IVGui.h" +#include "vgui/IInput.h" +#include "vgui_controls/MenuItem.h" +#include "vgui_controls/PropertySheet.h" + +#include "tf_party.h" +#include "tf_lobbypanel.h" +#include "tf_pvp_rank_panel.h" + +#include "tf_lobby_container_frame.h" +#include "tf_controls.h" + +#include "tf_item_inventory.h" +#include "tf_lobbypanel.h" +#include "tf_hud_mainmenuoverride.h" +#include "tf_matchmaking_dashboard.h" +#include "tf_ping_panel.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +bool BIsPartyLeader() +{ + CTFParty *pParty = GTFGCClientSystem()->GetParty(); + return ( pParty == NULL || pParty->GetLeader() == steamapicontext->SteamUser()->GetSteamID() ); +} + +bool BIsPartyInUIState() +{ + if ( !GCClientSystem()->BConnectedtoGC() ) + return false; + CTFParty *pParty = GTFGCClientSystem()->GetParty(); + return ( pParty == NULL || pParty->GetState() == CSOTFParty_State_UI ); +} + +CSteamID SteamIDFromDecimalString( const char *pszUint64InDecimal ) +{ + uint64 ulSteamID = 0; + if ( sscanf( pszUint64InDecimal, "%llu", &ulSteamID ) ) + { + return CSteamID( ulSteamID ); + } + else + { + Assert( false ); + return CSteamID(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseLobbyContainerFrame::CBaseLobbyContainerFrame( const char* pszPanelName ) + : vgui::PropertyDialog( NULL, pszPanelName ) + , m_bNextButtonEnabled( false ) + , m_pContextMenu( NULL ) +{ + vgui::VPANEL gameuiPanel = enginevgui->GetPanel( PANEL_GAMEUIDLL ); + SetParent( gameuiPanel ); + + SetMoveable( false ); + SetSizeable( false ); + + vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme"); + SetScheme(scheme); + SetProportional( true ); + + ListenForGameEvent( "gameui_hidden" ); + + ListenForGameEvent( "lobby_updated" ); + ListenForGameEvent( "party_updated" ); + ListenForGameEvent( "client_beginconnect" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseLobbyContainerFrame::~CBaseLobbyContainerFrame( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseLobbyContainerFrame::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + // load control settings... + LoadControlSettings( GetResFile() ); + + m_pStartPartyButton = dynamic_cast<vgui::Button *>(FindChildByName( "StartPartyButton", true )); Assert( m_pStartPartyButton ); + m_pBackButton = dynamic_cast<vgui::Button *>( FindChildByName( "BackButton", true ) ); Assert( m_pBackButton ); + m_pNextButton = dynamic_cast<vgui::Button *>( FindChildByName( "NextButton", true ) ); Assert( m_pNextButton ); + + SetOKButtonVisible(false); + SetCancelButtonVisible(false); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseLobbyContainerFrame::ShowPanel(bool bShow) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + + // Keep the MM dashboard on top of us + bShow ? GetMMDashboardParentManager()->PushModalFullscreenPopup( this ) + : GetMMDashboardParentManager()->PopModalFullscreenPopup( this ); + + m_pContents->SetControlVisible( "PartyActiveGroupBox", false ); + + // Make sure we're signed on + if ( bShow ) + { + if ( GetPropertySheet()->GetActivePage() != m_pContents ) + { + GetPropertySheet()->SetActivePage( m_pContents ); + } + else + { + // VGUI doesn't tell the starting active page that it's active, so we post a pageshow to it + vgui::ivgui()->PostMessage( m_pContents->GetVPanel(), new KeyValues("PageShow"), GetPropertySheet()->GetVPanel() ); + } + + Activate(); + + // I don't know why, I don't want to know why, I shouldn't + // have to wonder why, but for whatever reason this stupid + // panel isn't laying out correctly unless we do this terribleness + InvalidateLayout( true ); + m_pContents->InvalidateLayout( true, true ); + + GTFGCClientSystem()->SetLocalPlayerSquadSurplus( false ); + WriteControls(); + m_pContents->UpdateControls(); + + Panel* pPvPRankPanel = FindChildByName( "RankPanel", true ); + if ( pPvPRankPanel ) + { + pPvPRankPanel->OnCommand( "update_base_state" ); + pPvPRankPanel->OnCommand( "begin_xp_lerp" ); + } + } + else + { + if ( m_hPingPanel ) + { + m_hPingPanel->MarkForDeletion(); + } + } + + OnCommand( "leave_party" ); + + SetVisible( bShow ); + m_pContents->SetVisible( bShow ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseLobbyContainerFrame::SetNextButtonEnabled( bool bValue ) +{ + m_bNextButtonEnabled = bValue; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseLobbyContainerFrame::OnThink() +{ + BaseClass::OnThink(); + + // Check if we don't want to be here, then get out! + if ( GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_Inactive ) + { + Msg( "Hiding LobbyContainerFrame" ); + ShowPanel(false); + return; + } + + WriteControls(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseLobbyContainerFrame::FireGameEvent( IGameEvent *event ) +{ + if ( GTFGCClientSystem()->GetSearchMode() != GetHandledMode() ) + return; + + const char *pszEventname = event->GetName(); + if ( !Q_stricmp( pszEventname, "lobby_updated" ) || !Q_stricmp( pszEventname, "party_updated" ) ) + { + WriteControls(); + return; + } + + // Bail when we connect to any server + if ( !Q_stricmp( pszEventname, "client_beginconnect" ) ) + { + ShowPanel( false ); + return; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseLobbyContainerFrame::StartSearch( void ) +{ + // Is anyone banned from matchmaking? + RTime32 rtimeExpire = 0; + if ( m_pContents->IsAnyoneBanned( rtimeExpire ) ) + { + CRTime timeExpire( rtimeExpire ); + timeExpire.SetToGMT( false ); + char out_buf[k_RTimeRenderBufferSize]; + wchar_t wszExpire[512]; + wchar_t wszLocalized[512]; + g_pVGuiLocalize->ConvertANSIToUnicode( timeExpire.Render( out_buf ), wszExpire, sizeof( wszExpire ) ); + g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_Banned" ), 1, wszExpire ); + ShowMessageBox( "#TF_Matchmaking_Title", wszLocalized, "#GameUI_OK" ); + return; + } + + if ( VerifyPartyAuthorization() ) + { + GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_SEARCHING ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseLobbyContainerFrame::OnCommand( const char *command ) +{ + if ( FStrEq( command, "options" ) ) + { + OpenOptionsContextMenu(); + } + else if ( FStrEq( command, "back" ) ) + { + if ( !GCClientSystem()->BConnectedtoGC() || !BIsPartyLeader() ) + { + // TODO: Remove this when we have the dashboard everywhere. + // Well...this entire panel should be gone. + GTFGCClientSystem()->EndMatchmaking(); + // And hide us + ShowPanel( false ); + return; + } + + HandleBackPressed(); + } + else if ( FStrEq( command, "leave_party" ) ) + { + m_pStartPartyButton->SetVisible( true ); + SetControlVisible( "PlayWithFriendsExplanation", true ); + m_pContents->SetControlVisible( "PartyActiveGroupBox", false ); + } + else if ( FStrEq( command, "start_party" ) ) + { + m_pStartPartyButton->SetVisible( false ); + SetControlVisible( "PlayWithFriendsExplanation", false ); + m_pContents->SetControlVisible( "PartyActiveGroupBox", true ); + + Assert( steamapicontext ); + + IGameEvent *pEvent = gameeventmanager->CreateEvent( "mm_lobby_member_join" ); + if ( pEvent ) + { + pEvent->SetString( "steamid", CFmtStr( "%llu", steamapicontext->SteamUser()->GetSteamID().ConvertToUint64() ) ); + pEvent->SetInt( "solo", 1 ); + gameeventmanager->FireEventClientSide( pEvent ); + } + } + else + { + + // What other commands are there? + Assert( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseLobbyContainerFrame::PerformLayout( void ) +{ + if ( GetVParent() ) + { + int w,h; + vgui::ipanel()->GetSize( GetVParent(), w, h ); + SetBounds(0,0,w,h); + } + + BaseClass::PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +static void LeaveSearch( bool bConfirmed, void *pContext ) +{ + if ( bConfirmed ) + { + switch ( GTFGCClientSystem()->GetSearchMode() ) + { + case TF_Matchmaking_MVM: + GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_MVM_CHALLENGE ); + break; + + case TF_Matchmaking_LADDER: + GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_LADDER ); + break; + + default: + AssertMsg1( false, "Unknown search mode %d", (int)GTFGCClientSystem()->GetSearchMode() ); + break; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseLobbyContainerFrame::OnKeyCodeTyped(vgui::KeyCode code) +{ + if ( code == KEY_ESCAPE ) + { + if ( m_pContents->IsPartyActiveGroupBoxVisible() ) + { + ShowConfirmDialog( "#TF_MM_LeaveParty_Title", "#TF_MM_LeaveParty_Confirm", + "#TF_Coach_Yes", "#TF_Coach_No", + &CBaseLobbyContainerFrame::LeaveLobbyPanel ); + return; + } + else if ( GTFGCClientSystem()->GetWizardStep() == TF_Matchmaking_WizardStep_SEARCHING ) + { + ShowConfirmDialog( "#TF_MM_LeaveQueue_Title", "#TF_MM_LeaveQueue_Confirm", + "#TF_Coach_Yes", "#TF_Coach_No", + &LeaveSearch ); + return; + } + + OnCommand( "back" ); + return; + } + + BaseClass::OnKeyCodeTyped( code ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseLobbyContainerFrame::OnKeyCodePressed(vgui::KeyCode code) +{ + ButtonCode_t nButtonCode = GetBaseButtonCode( code ); + + if ( nButtonCode == KEY_XBUTTON_B || nButtonCode == STEAMCONTROLLER_B || nButtonCode == STEAMCONTROLLER_START ) + { + OnCommand( "back" ); + return; + } + else if ( nButtonCode == KEY_XBUTTON_X ) + { + m_pContents->ToggleJoinLateCheckButton(); + } + + BaseClass::OnKeyCodePressed( code ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseLobbyContainerFrame::WriteControls() +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + + if ( !IsVisible() ) + return; + + // Make sure we want to be in matchmaking. (If we don't, the frame should hide us pretty quickly.) + // We might get an event or something right at the transition point occasionally when the UI should + // not be visible + if ( GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_Inactive ) + { + return; + } + + bool bNoGC = false; + if ( !GCClientSystem()->BConnectedtoGC() || + GTFGCClientSystem()->BHaveLiveMatch() || + ( GTFGCClientSystem()->GetParty() && GTFGCClientSystem()->GetParty()->BOffline() ) ) + { + bNoGC = true; + } + + SetControlVisible( "PlayWithFriendsExplanation", !bNoGC && ShouldShowPartyButton(), true ); + SetControlVisible( "RankPanel", !bNoGC, true ); + SetControlVisible( "NoGCGroupBox", bNoGC, true ); + + GetPropertySheet()->SetTabWidth( -1 ); + + // Check if we already have a party, then make sure and show it + if ( !m_pStartPartyButton->IsVisible() && m_pContents->NumPlayersInParty() > 1 ) + { + m_pContents->SetControlVisible( "PartyActiveGroupBox", true ); + } + + // Show/hide start party button as appropriate + bool bShowPartyButton = ShouldShowPartyButton(); + + m_pStartPartyButton->SetVisible( bShowPartyButton && !bNoGC ); + + // Check for matchmaking bans and display time remaining if we're banned + if ( steamapicontext == NULL || steamapicontext->SteamUser() == NULL ) + return; + + CEconGameAccountClient *pGameAccountClient = NULL; + if ( InventoryManager() && TFInventoryManager()->GetLocalTFInventory() && TFInventoryManager()->GetLocalTFInventory()->GetSOC() ) + { + pGameAccountClient = TFInventoryManager()->GetLocalTFInventory()->GetSOC()->GetSingleton<CEconGameAccountClient>(); + } + + const IMatchGroupDescription *pMatchDesc = GetMatchGroupDescription( m_pContents->GetMatchGroup() ); + EMMPenaltyPool ePenaltyPool = pMatchDesc ? pMatchDesc->m_params.m_ePenaltyPool : eMMPenaltyPool_Invalid; + + CRTime timeExpire = CRTime::RTime32TimeCur(); + int nDuration = -1; + bool bBanned = false; + + if ( pGameAccountClient && ePenaltyPool != eMMPenaltyPool_Invalid ) + { + switch ( ePenaltyPool ) + { + case eMMPenaltyPool_Casual: + timeExpire = pGameAccountClient->Obj().matchmaking_casual_ban_expiration(); + nDuration = pGameAccountClient->Obj().matchmaking_casual_ban_last_duration(); + bBanned = timeExpire > CRTime::RTime32TimeCur(); + break; + case eMMPenaltyPool_Ranked: + timeExpire = pGameAccountClient->Obj().matchmaking_ranked_ban_expiration(); + nDuration = pGameAccountClient->Obj().matchmaking_ranked_ban_last_duration(); + bBanned = timeExpire > CRTime::RTime32TimeCur(); + break; + default: Assert( false ); + } + + SetControlVisible( "MatchmakingBanPanel", bBanned ); + + if ( bBanned ) + { + CExLabel *pBanLabel = FindControl<CExLabel>( "MatchmakingBanDurationLabel", true ); + + if ( pBanLabel ) + { + timeExpire.SetToGMT( false ); + + CRTime rtNow = CRTime::RTime32TimeCur(); + + int nSecondsRemaining = timeExpire.GetRTime32() - rtNow.GetRTime32(); + + if ( nSecondsRemaining >= 0 ) + { + + const int nDaysForLongBan = 2; + + int nDaysRemaining = nSecondsRemaining / 86400; + int nHoursRemaining = nSecondsRemaining / 3600; + int nMinutesRemaining = ( nSecondsRemaining % 3600 ) / 60; + + int nDurationDays = nDuration / 86400; + int nDurationHours = nDuration / 3600; + int nDurationMinutes = ( nDuration % 3600 ) / 60; + + // Want the remainder hours if we're going to display 'days' remaining + if ( nDurationDays >= nDaysForLongBan ) + { + nDurationHours = ( nDuration % ( 86400 * nDurationDays ) ) / 3600; + + if ( nDaysRemaining >= nDaysForLongBan ) + { + nHoursRemaining = ( nSecondsRemaining % ( 86400 * nDaysRemaining ) ) / 3600; + } + } + + wchar_t wszDaysRemaining[16]; + wchar_t wszHoursRemaining[16]; + wchar_t wszMinutesRemaining[16]; + wchar_t wszDurationDays[16]; + wchar_t wszDurationHours[16]; + wchar_t wszDurationMinutes[16]; + + _snwprintf( wszDaysRemaining, ARRAYSIZE( wszDaysRemaining ), L"%d", nDaysRemaining ); + _snwprintf( wszHoursRemaining, ARRAYSIZE( wszHoursRemaining ), L"%d", nHoursRemaining ); + _snwprintf( wszMinutesRemaining, ARRAYSIZE( wszMinutesRemaining ), L"%d", nMinutesRemaining ); + _snwprintf( wszDurationDays, ARRAYSIZE( wszDurationDays ), L"%d", nDurationDays ); + _snwprintf( wszDurationHours, ARRAYSIZE( wszDurationHours ), L"%d", nDurationHours ); + _snwprintf( wszDurationMinutes, ARRAYSIZE( wszDurationMinutes ), L"%d", nDurationMinutes ); + + wchar_t wszLocalized[512]; + + // Short ban (less than "nDaysForLongBan" days and thus less than that remaining) + if ( nDurationDays < nDaysForLongBan ) + { + // Less than an hour ban + if ( nDurationHours < 1 ) + { + g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_Ban_Duration_Remaining_Short" ), 2, wszDurationMinutes, wszMinutesRemaining ); + } + else + { + g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_Ban_Duration_Remaining" ), 4, wszDurationHours, wszDurationMinutes, wszHoursRemaining, wszMinutesRemaining ); + } + } + // Long ban (at least "nDaysForLongBan" days) but less than that remaining + else if ( nDaysRemaining < nDaysForLongBan ) + { + g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_Ban_Duration_Remaining_Long_Penalty_Short_Duration" ), 4, wszDurationDays, wszDurationHours, wszHoursRemaining, wszMinutesRemaining ); + } + // Long ban and at least that long remaining) + else + { + g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "TF_Matchmaking_Ban_Duration_Remaining_Long_Penalty" ), 4, wszDurationDays, wszDurationHours, wszDaysRemaining, wszHoursRemaining ); + } + pBanLabel->SetText( wszLocalized ); + } + else + { + pBanLabel->SetText( "#TF_Matchmaking_Ban_Duration_Remaining_Shortly" ); + } + } + } + } + + m_pNextButton->SetEnabled( m_bNextButtonEnabled && !bBanned && !bNoGC ); +} + +//----------------------------------------------------------------------------- +// Purpose: Handle cases not handled by our derived classes +//----------------------------------------------------------------------------- +void CBaseLobbyContainerFrame::HandleBackPressed() +{ + // We dont know how or why we got here. Failsafe and just leave. + if( GTFGCClientSystem()->GetSearchMode() != GetHandledMode() ) + { + Msg( "Lobby handles mode %d, but search mode is %d. Ending matchmaking", (int)( GetHandledMode() ), (int)( GTFGCClientSystem()->GetSearchMode() ) ); + } + + GTFGCClientSystem()->EndMatchmaking(); + ShowPanel( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseLobbyContainerFrame::ShouldShowPartyButton() const +{ + return !m_pContents->IsPartyActiveGroupBoxVisible() && + GTFGCClientSystem()->GetWizardStep() != TF_Matchmaking_WizardStep_SEARCHING && + GCClientSystem()->BConnectedtoGC() && + BIsPartyLeader(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseLobbyContainerFrame::OpenOptionsContextMenu() +{ + if ( m_pContextMenu ) + delete m_pContextMenu; + + m_pContextMenu = new Menu( this, "ContextMenu" ); + MenuBuilder contextMenuBuilder( m_pContextMenu, this ); + const char *pszContextMenuBorder = "NotificationDefault"; + const char *pszContextMenuFont = "HudFontMediumSecondary"; + m_pContextMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) ); + m_pContextMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) ); + + contextMenuBuilder.AddMenuItem( "#TF_LobbyContainer_Ping", new KeyValues( "Context_Ping" ), "ping" ); + contextMenuBuilder.AddMenuItem( "#TF_LobbyContainer_Help", "show_explanations", "help" ); + + m_pContextMenu->SetVisible(true); + m_pContextMenu->AddActionSignalTarget(this); + + m_pContextMenu->MakeReadyForUse(); + + Panel* pOptionsButton = FindChildByName( "OptionsButton" ); + + if ( !pOptionsButton ) + { + // Position to the cursor's position + int nX, nY; + g_pVGuiInput->GetCursorPosition( nX, nY ); + m_pContextMenu->SetPos( nX - 1, nY - 1 ); + } + else + { + int nOptionsX = pOptionsButton->GetXPos(); + int nX = Min( nOptionsX, GetWide() - m_pContextMenu->GetWide() ); + int nY = pOptionsButton->GetYPos() + pOptionsButton->GetTall(); + m_pContextMenu->SetPos( nX - 1, nY - 1 ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseLobbyContainerFrame::OpenPingOptions() +{ + // just create a new one. panel will destroy itself on close. + m_hPingPanel = new CTFPingPanel( this, "PingPanel", m_pContents->GetMatchGroup() ); +} diff --git a/game/client/tf/vgui/tf_lobby_container_frame.h b/game/client/tf/vgui/tf_lobby_container_frame.h new file mode 100644 index 0000000..a97002e --- /dev/null +++ b/game/client/tf/vgui/tf_lobby_container_frame.h @@ -0,0 +1,88 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TF_LOBBY_CONTAINER_FRAME_H +#define TF_LOBBY_CONTAINER_FRAME_H + + +#include "cbase.h" +#include "vgui_controls/PropertyDialog.h" +#include "GameEventListener.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +bool BIsPartyLeader(); +bool BIsPartyInUIState(); +CSteamID SteamIDFromDecimalString( const char *pszUint64InDecimal ); + +class CBaseLobbyPanel; + +// This is a big fat kludge so I can use the PropertyPage +class CBaseLobbyContainerFrame : public vgui::PropertyDialog, public CGameEventListener +{ + DECLARE_CLASS_SIMPLE( CBaseLobbyContainerFrame, PropertyDialog ); +public: + CBaseLobbyContainerFrame( const char *pszPanelName ); + virtual ~CBaseLobbyContainerFrame(); + + // + // PropertyDialog overrides + // + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE; + virtual void PerformLayout( void ) OVERRIDE; + virtual void OnKeyCodeTyped(vgui::KeyCode code) OVERRIDE; + virtual void OnKeyCodePressed(vgui::KeyCode code) OVERRIDE; + virtual void OnCommand( const char *command ) OVERRIDE; + + // + // CGameEventListener overrides + // + virtual void FireGameEvent( IGameEvent *event ) OVERRIDE; + + void StartSearch( void ); + virtual void ShowPanel(bool bShow); + void SetNextButtonEnabled( bool bValue ); + + virtual void OnThink() OVERRIDE; + + static void LeaveLobbyPanel( bool bConfirmed, void *pContext ) + { + CBaseLobbyContainerFrame* pCaller = (CBaseLobbyContainerFrame*)pContext; + if ( bConfirmed && pCaller ) + { + pCaller->OnCommand( "back" ); + } + } + + MESSAGE_FUNC( OpenPingOptions, "Context_Ping" ); + +protected: + + bool ShouldShowPartyButton() const; + virtual void WriteControls(); + virtual void HandleBackPressed(); + void OpenOptionsContextMenu(); + + CBaseLobbyPanel *m_pContents; + vgui::Button *m_pNextButton; + vgui::Button *m_pBackButton; + +private: + + virtual const char* GetResFile() const = 0; + virtual TF_MatchmakingMode GetHandledMode() const = 0; + virtual bool VerifyPartyAuthorization() const = 0; + + vgui::Button *m_pStartPartyButton; + vgui::Menu *m_pContextMenu; + + bool m_bNextButtonEnabled; + + vgui::DHANDLE< vgui::Panel > m_hPingPanel; +}; + +#endif //TF_LOBBY_CONTAINER_FRAME_H
\ No newline at end of file diff --git a/game/client/tf/vgui/tf_lobby_container_frame_casual.cpp b/game/client/tf/vgui/tf_lobby_container_frame_casual.cpp new file mode 100644 index 0000000..c5a4d5f --- /dev/null +++ b/game/client/tf/vgui/tf_lobby_container_frame_casual.cpp @@ -0,0 +1,268 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#include "cbase.h" +#include "tf_gc_client.h" +#include "tf_party.h" + +#include "vgui_controls/PropertySheet.h" +#include "vgui_controls/ComboBox.h" +#include "vgui_controls/ScrollableEditablePanel.h" +#include "vgui_avatarimage.h" +#include "tf_leaderboardpanel.h" +#include "tf_lobbypanel_casual.h" +#include "tf_hud_mainmenuoverride.h" + +#include "tf_lobby_container_frame_casual.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +extern ConVar tf_matchmaking_join_in_progress; + +//----------------------------------------------------------------------------- +// Purpose: Override of the generic messagebox dialog to provide a welcome-to-competitive message +//----------------------------------------------------------------------------- +ConVar tf_casual_welcome_hide_forever( "tf_casual_welcome_hide_forever", "0", FCVAR_ARCHIVE | FCVAR_HIDDEN ); +ConVar tf_casual_welcome_hide( "tf_casual_welcome_hide", "0", FCVAR_HIDDEN ); +class CTFCasualWelcomeDialog : public CTFMessageBoxDialog +{ + DECLARE_CLASS_SIMPLE( CTFCasualWelcomeDialog, CTFMessageBoxDialog ); +public: + CTFCasualWelcomeDialog() + : CTFMessageBoxDialog( NULL, ( const char * )NULL, NULL, NULL, NULL ) + { + } + + virtual ~CTFCasualWelcomeDialog() {}; + + virtual void OnCommand( const char *command ) + { + if ( FStrEq( "hideforever", command ) ) + { + tf_casual_welcome_hide_forever.SetValue( 1 ); + return; + } + else if ( FStrEq( "show_explanations", command ) ) + { + CHudMainMenuOverride *pMMOverride = (CHudMainMenuOverride*)( gViewPortInterface->FindPanelByName( PANEL_MAINMENUOVERRIDE ) ); + pMMOverride->GetCasualLobbyPanel()->OnCommand( command ); + OnCommand( "confirm" ); + return; + } + + BaseClass::OnCommand( command ); + } + + MESSAGE_FUNC_PTR( OnCheckButtonChecked, "CheckButtonChecked", panel ) + { + CheckButton* pNeverAskAgainCheckBox = FindControl< CheckButton >( "NeverShowAgainCheckBox" ); + if ( panel == pNeverAskAgainCheckBox ) + { + tf_casual_welcome_hide_forever.SetValue( pNeverAskAgainCheckBox->IsSelected() ); + } + } + + virtual const char *GetResFile() OVERRIDE + { + // FIXME controller? + return "Resource/UI/CasualWelcomeDialog.res"; + } +}; + +//----------------------------------------------------------------------------- +CLobbyContainerFrame_Casual::CLobbyContainerFrame_Casual() + : CBaseLobbyContainerFrame( "LobbyContainerFrame" ) +{ + // Our internal lobby panel + m_pContents = new CLobbyPanel_Casual( this, this ); + m_pContents->AddActionSignalTarget( this ); + AddPage( m_pContents, "#TF_Matchmaking_HeaderCasual" ); + GetPropertySheet()->SetNavToRelay( m_pContents->GetName() ); + m_pContents->SetVisible( true ); + + m_pToolTip = new CMainMenuToolTip( this ); + vgui::EditablePanel* pToolTipEmbeddedPanel = new vgui::EditablePanel( this, "Tooltip_CasualLobby" ); + pToolTipEmbeddedPanel->SetKeyBoardInputEnabled( false ); + pToolTipEmbeddedPanel->SetMouseInputEnabled( false ); + m_pToolTip->SetEmbeddedPanel( pToolTipEmbeddedPanel ); + m_pToolTip->SetTooltipDelay( 0 ); +} + +//----------------------------------------------------------------------------- +CLobbyContainerFrame_Casual::~CLobbyContainerFrame_Casual( void ) +{ +} + +void CLobbyContainerFrame_Casual::ShowPanel( bool bShow ) +{ + if ( bShow ) + { + if ( tf_casual_welcome_hide.GetBool() == false + && tf_casual_welcome_hide_forever.GetBool() == false + && GTFGCClientSystem()->GetWizardStep() == TF_Matchmaking_WizardStep_CASUAL ) + { + CTFCasualWelcomeDialog *pDialog = vgui::SETUP_PANEL( new CTFCasualWelcomeDialog() ); + + if ( pDialog ) + { + tf_casual_welcome_hide.SetValue( 1 ); + pDialog->Show(); + } + else + { + Warning( "Failed to create CasualWelcomeDialog. Outdated HUD?\n" ); + } + } + + // Slam to true for casual + tf_matchmaking_join_in_progress.SetValue( true ); + GTFGCClientSystem()->SetSearchJoinLate( true ); + } + + BaseClass::ShowPanel( bShow ); +} + +void CLobbyContainerFrame_Casual::OnCommand( const char *command ) +{ + if ( FStrEq( command, "next" ) ) + { + switch ( GTFGCClientSystem()->GetWizardStep() ) + { + case TF_Matchmaking_WizardStep_CASUAL: + StartSearch(); + break; + + default: + AssertMsg1( false, "Unexpected wizard step %d", (int)GTFGCClientSystem()->GetWizardStep() ); + break; + } + return; + } + else if ( FStrEq( command, "show_explanations" ) ) + { + CExplanationPopup *pPopup = FindControl<CExplanationPopup>( "StartExplanation" ); + if ( pPopup ) + { + pPopup->Popup(); + } + return; + } + else if ( FStrEq( command, "show_maps_details_explanation" ) ) + { + CExplanationPopup *pPopup = FindControl<CExplanationPopup>( "MapSelectionDetailsExplanation" ); + if ( pPopup ) + { + pPopup->Popup(); + } + return; + } + else if ( FStrEq( command, "restore_search_criteria" ) ) + { + if ( GTFGCClientSystem() ) + { + GTFGCClientSystem()->ClearCasualSearchCriteria(); + GTFGCClientSystem()->LoadCasualSearchCriteria(); + } + return; + } + else if ( FStrEq( command, "save_search_criteria" ) ) + { + if ( GTFGCClientSystem() ) + { + GTFGCClientSystem()->SaveCasualSearchCriteriaToDisk(); + } + return; + } + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +void CLobbyContainerFrame_Casual::WriteControls() +{ + // Make sure we want to be in matchmaking. (If we don't, the frame should hide us pretty quickly.) + // We might get an event or something right at the transition point occasionally when the UI should + // not be visible + if ( GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_Inactive ) + { + return; + } + + const char *pszBackButtonText = "#TF_Matchmaking_Back"; + const char *pszNextButtonText = NULL; + + if ( GCClientSystem()->BConnectedtoGC() ) + { + if ( BIsPartyLeader() ) + { + switch ( GTFGCClientSystem()->GetWizardStep() ) + { + case TF_Matchmaking_WizardStep_CASUAL: + pszBackButtonText = "#TF_Matchmaking_Back"; + pszNextButtonText = "#TF_Matchmaking_StartSearch"; + break; + + case TF_Matchmaking_WizardStep_SEARCHING: + pszBackButtonText = "#TF_Matchmaking_CancelSearch"; + break; + + case TF_Matchmaking_WizardStep_INVALID: + // Still being setup + break; + + default: + AssertMsg1( false, "Unknown wizard step %d", (int)GTFGCClientSystem()->GetWizardStep() ); + break; + } + } + else + { + pszBackButtonText = "#TF_Matchmaking_LeaveParty"; + m_pNextButton->SetEnabled( false ); + } + } + + m_pBackButton->SetText( pszBackButtonText ); + m_pNextButton->SetText( pszNextButtonText ); + m_pNextButton->SetVisible( pszNextButtonText != NULL ); + + BaseClass::WriteControls(); +} + +bool CLobbyContainerFrame_Casual::VerifyPartyAuthorization() const +{ + // For now, there's no additional restrictions for playing casual + return true; +} + +void CLobbyContainerFrame_Casual::HandleBackPressed() +{ + switch ( GTFGCClientSystem()->GetWizardStep() ) + { + case TF_Matchmaking_WizardStep_CASUAL: + // !FIXME! Really need to confirm this! + GTFGCClientSystem()->EndMatchmaking(); + // And hide us + ShowPanel( false ); + return; + + case TF_Matchmaking_WizardStep_SEARCHING: + switch ( GTFGCClientSystem()->GetSearchMode() ) + { + case TF_Matchmaking_CASUAL: + GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_CASUAL ); + return; + } + break; + + default: + Msg( "Unexpected wizard step %d", (int)GTFGCClientSystem()->GetWizardStep() ); + break; + } + + BaseClass::HandleBackPressed(); +} + diff --git a/game/client/tf/vgui/tf_lobby_container_frame_casual.h b/game/client/tf/vgui/tf_lobby_container_frame_casual.h new file mode 100644 index 0000000..958687e --- /dev/null +++ b/game/client/tf/vgui/tf_lobby_container_frame_casual.h @@ -0,0 +1,82 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TF_LOBBY_CONTAINER_FRAME_CASUAL_H +#define TF_LOBBY_CONTAINER_FRAME_CASUAL_H + + +#include "cbase.h" +//#include "tf_pvelobbypanel.h" +#include "game/client/iviewport.h" +#include "tf_shareddefs.h" +#include "econ/confirm_dialog.h" +#include "econ/econ_controls.h" +#include "ienginevgui.h" +#include "tf_gc_client.h" +#include "tf_party.h" +#include "tf_item_inventory.h" +#include "econ_ui.h" + +#include "vgui_controls/Tooltip.h" +#include "vgui_controls/PropertyDialog.h" +#include "vgui_controls/PropertySheet.h" +#include "vgui_controls/ComboBox.h" +#include "vgui_controls/RadioButton.h" +#include "vgui_controls/SectionedListPanel.h" +#include "vgui_controls/ScrollableEditablePanel.h" +#include "vgui_bitmapimage.h" +#include "vgui/IInput.h" +#include <vgui_controls/ImageList.h> +#include <vgui/IVGui.h> +#include "GameEventListener.h" +#include "vgui_avatarimage.h" +#include <vgui/ISurface.h> +#include <VGuiMatSurface/IMatSystemSurface.h> +#include "rtime.h" +#include "econ_game_account_client.h" +#include "tf_leaderboardpanel.h" +#include "tf_mapinfo.h" +#include "tf_ladder_data.h" +#include "tf_gamerules.h" +#include "confirm_dialog.h" +#include "tf_lobby_container_frame.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +class CBaseLobbyPanel; + +// This is a big fat kludge so I can use the PropertyPage +class CLobbyContainerFrame_Casual : public CBaseLobbyContainerFrame +{ + DECLARE_CLASS_SIMPLE( CLobbyContainerFrame_Casual, CBaseLobbyContainerFrame ); +public: + CLobbyContainerFrame_Casual(); + ~CLobbyContainerFrame_Casual(); + + // + // PropertyDialog overrides + // + virtual void ShowPanel( bool bShow ) OVERRIDE; + virtual void OnCommand( const char *command ) OVERRIDE; + + CMainMenuToolTip *GetTooltipPanel(){ return m_pToolTip; } + +protected: + + virtual void WriteControls(); + +private: + virtual const char* GetResFile() const OVERRIDE { return "Resource/UI/LobbyContainerFrame_Casual.res"; } + virtual TF_MatchmakingMode GetHandledMode() const { return TF_Matchmaking_CASUAL; } + virtual bool VerifyPartyAuthorization() const; + virtual void HandleBackPressed() OVERRIDE; + + + CMainMenuToolTip *m_pToolTip; +}; + +#endif //TF_LOBBY_CONTAINER_FRAME_CASUAL_H
\ No newline at end of file diff --git a/game/client/tf/vgui/tf_lobby_container_frame_comp.cpp b/game/client/tf/vgui/tf_lobby_container_frame_comp.cpp new file mode 100644 index 0000000..838f271 --- /dev/null +++ b/game/client/tf/vgui/tf_lobby_container_frame_comp.cpp @@ -0,0 +1,297 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "tf_gc_client.h" +#include "tf_party.h" + +#include "vgui_controls/PropertySheet.h" +#include "vgui_controls/ComboBox.h" +#include "vgui_controls/ScrollableEditablePanel.h" +#include "vgui_avatarimage.h" +#include "tf_leaderboardpanel.h" +#include "tf_lobbypanel_comp.h" +#include "tf_hud_mainmenuoverride.h" + +#include "tf_lobby_container_frame_comp.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +//----------------------------------------------------------------------------- + +// Purpose: Override of the generic messagebox dialog to provide a welcome-to-competitive message + +//----------------------------------------------------------------------------- +ConVar tf_comp_welcome_hide_forever( "tf_comp_welcome_hide_forever", "0", FCVAR_ARCHIVE | FCVAR_HIDDEN ); +ConVar tf_comp_welcome_hide( "tf_comp_welcome_hide", "0", FCVAR_HIDDEN ); +class CTFCompetitiveWelcomeDialog : public CTFMessageBoxDialog +{ + DECLARE_CLASS_SIMPLE( CTFCompetitiveWelcomeDialog, CTFMessageBoxDialog ); +public: + CTFCompetitiveWelcomeDialog() + : CTFMessageBoxDialog( NULL, (const char *)NULL, NULL, NULL, NULL ) + {} + + virtual ~CTFCompetitiveWelcomeDialog() {}; + + virtual void OnCommand( const char *command ) + { + if ( FStrEq( "hideforever", command ) ) + { + tf_comp_welcome_hide_forever.SetValue( 1 ); + return; + } + else if ( FStrEq( "show_explanations", command ) ) + { + CHudMainMenuOverride *pMMOverride = (CHudMainMenuOverride*)( gViewPortInterface->FindPanelByName( PANEL_MAINMENUOVERRIDE ) ); + pMMOverride->GetCompLobbyPanel()->OnCommand( command ); + OnCommand( "confirm" ); + return; + } + + BaseClass::OnCommand( command ); + } + + MESSAGE_FUNC_PTR( OnCheckButtonChecked, "CheckButtonChecked", panel ) + { + CheckButton* pNeverAskAgainCheckBox = FindControl< CheckButton >( "NeverShowAgainCheckBox" ); + if ( panel == pNeverAskAgainCheckBox ) + { + tf_comp_welcome_hide_forever.SetValue( pNeverAskAgainCheckBox->IsSelected() ); + } + } + + virtual const char *GetResFile() OVERRIDE + { + // FIXME controller? + return "Resource/UI/CompetitiveWelcomeDialog.res"; + } +}; + + +//----------------------------------------------------------------------------- +CLobbyContainerFrame_Comp::CLobbyContainerFrame_Comp() + : CBaseLobbyContainerFrame( "LobbyContainerFrame" ) +{ + // Our internal lobby panel + m_pContents = new CLobbyPanel_Comp( this, this ); + m_pContents->AddActionSignalTarget( this ); + AddPage( m_pContents, "#TF_Matchmaking_HeaderCompetitive" ); + GetPropertySheet()->SetNavToRelay( m_pContents->GetName() ); + m_pContents->SetVisible( true ); +} + +//----------------------------------------------------------------------------- +CLobbyContainerFrame_Comp::~CLobbyContainerFrame_Comp( void ) +{ +} + +void CLobbyContainerFrame_Comp::ShowPanel( bool bShow ) +{ + if ( bShow ) + { + if ( tf_comp_welcome_hide.GetBool() == false + && tf_comp_welcome_hide_forever.GetBool() == false + && GTFGCClientSystem()->GetWizardStep() == TF_Matchmaking_WizardStep_LADDER ) + { + CTFCompetitiveWelcomeDialog *pDialog = vgui::SETUP_PANEL( new CTFCompetitiveWelcomeDialog() ); + + if ( pDialog ) + { + tf_comp_welcome_hide.SetValue( 1 ); + pDialog->Show(); + } + else + { + Warning( "Failed to create CompetitiveWelcomeDialog. Outdated HUD?\n" ); + } + } + } + + + + BaseClass::ShowPanel( bShow ); +} + +void CLobbyContainerFrame_Comp::OnCommand( const char *command ) +{ + if ( FStrEq( command, "next" ) ) + { + switch ( GTFGCClientSystem()->GetWizardStep() ) + { + case TF_Matchmaking_WizardStep_LADDER: + StartSearch(); + break; + + default: + AssertMsg1( false, "Unexpected wizard step %d", (int)GTFGCClientSystem()->GetWizardStep() ); + break; + } + return; + } + else if ( FStrEq( command, "show_explanations" ) ) + { + CExplanationPopup *pPopup = FindControl<CExplanationPopup>( "StartExplanation" ); + if ( pPopup ) + { + pPopup->Popup(); + } + return; + } + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +void CLobbyContainerFrame_Comp::WriteControls() +{ + // Make sure we want to be in matchmaking. (If we don't, the frame should hide us pretty quickly.) + // We might get an event or something right at the transition point occasionally when the UI should + // not be visible + if ( GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_Inactive ) + { + return; + } + + const char *pszBackButtonText = "#TF_Matchmaking_Back"; + const char *pszNextButtonText = NULL; + + if ( GCClientSystem()->BConnectedtoGC() ) + { + if ( BIsPartyLeader() ) + { + switch ( GTFGCClientSystem()->GetWizardStep() ) + { + case TF_Matchmaking_WizardStep_LADDER: + pszBackButtonText = "#TF_Matchmaking_Back"; + pszNextButtonText = "#TF_Matchmaking_StartSearch"; + break; + + case TF_Matchmaking_WizardStep_SEARCHING: + pszBackButtonText = "#TF_Matchmaking_CancelSearch"; + break; + + case TF_Matchmaking_WizardStep_INVALID: + // Still being setup + break; + + default: + AssertMsg1( false, "Unknown wizard step %d", (int)GTFGCClientSystem()->GetWizardStep() ); + break; + } + } + else + { + pszBackButtonText = "#TF_Matchmaking_LeaveParty"; + m_pNextButton->SetEnabled( false ); + } + } + + m_pBackButton->SetText( pszBackButtonText ); + m_pNextButton->SetText( pszNextButtonText ); + m_pNextButton->SetVisible( pszNextButtonText != NULL ); + + BaseClass::WriteControls(); +} + +//----------------------------------------------------------------------------- +bool CheckCompetitiveConvars() +{ + static ConVarRef mat_dxlevel( "mat_dxlevel"); + return mat_dxlevel.GetInt() >= 90; +} + +//----------------------------------------------------------------------------- +bool CLobbyContainerFrame_Comp::VerifyPartyAuthorization() const +{ + // Solo + CTFParty *pParty = GTFGCClientSystem()->GetParty(); + if ( pParty == NULL || pParty->GetNumMembers() <= 1 ) + { + if ( !GTFGCClientSystem()->BHasCompetitiveAccess() ) + { + ShowEconRequirementDialog( "#TF_Competitive_RequiresPass_Title", "#TF_Competitive_RequiresPass", CTFItemSchema::k_rchLadderPassItemDefName ); + return false; + } + else if ( !CheckCompetitiveConvars() ) + { + ShowMessageBox( "#TF_Competitive_Convars_CantProceed_Title", "#TF_Competitive_Convars_CantProceed", "#GameUI_OK" ); + return false; + } + } + // Group + else + { + wchar_t wszLocalized[512]; + char szLocalized[512]; + wchar_t wszCharPlayerName[128]; + + bool bAnyMembersWithoutAuth = false; + + for ( int i = 0 ; i < pParty->GetNumMembers() ; ++i ) + { + // Need a ticket and two-factor for the beta + if ( !pParty->Obj().members( i ).competitive_access() ) + { + bAnyMembersWithoutAuth = true; + V_UTF8ToUnicode( steamapicontext->SteamFriends()->GetFriendPersonaName( pParty->GetMember( i ) ), wszCharPlayerName, sizeof( wszCharPlayerName ) ); + g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_MissingPass" ), 1, wszCharPlayerName ); + g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalized, szLocalized, sizeof( szLocalized ) ); + + GTFGCClientSystem()->SendSteamLobbyChat( CTFGCClientSystem::k_eLobbyMsg_SystemMsgFromLeader, szLocalized ); + } + } + + if ( bAnyMembersWithoutAuth ) + { + ShowMessageBox( "#TF_Competitive_RequiresPass_Title", "#TF_Competitive_RequiresPass", "#GameUI_OK" ); + return false; + } + } + + CLobbyPanel_Comp* pCompContents = static_cast< CLobbyPanel_Comp* >( m_pContents ); + if ( pCompContents ) + { + uint32 unModeType = pCompContents->GetMatchGroup(); + + if ( unModeType >= k_nMatchGroup_Ladder_First && unModeType <= k_nMatchGroup_Ladder_Last ) + { + GTFGCClientSystem()->SetLadderType( unModeType ); + } + } + + return true; +} + +void CLobbyContainerFrame_Comp::HandleBackPressed() +{ + switch ( GTFGCClientSystem()->GetWizardStep() ) + { + case TF_Matchmaking_WizardStep_LADDER: + // !FIXME! Really need to confirm this! + GTFGCClientSystem()->EndMatchmaking(); + // And hide us + ShowPanel( false ); + return; + + case TF_Matchmaking_WizardStep_SEARCHING: + switch ( GTFGCClientSystem()->GetSearchMode() ) + { + case TF_Matchmaking_LADDER: + GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_LADDER ); + return; + } + break; + + default: + Msg( "Unexpected wizard step %d", (int)GTFGCClientSystem()->GetWizardStep() ); + break; + } + + // Unhandled case + BaseClass::HandleBackPressed(); +} diff --git a/game/client/tf/vgui/tf_lobby_container_frame_comp.h b/game/client/tf/vgui/tf_lobby_container_frame_comp.h new file mode 100644 index 0000000..6c06bf0 --- /dev/null +++ b/game/client/tf/vgui/tf_lobby_container_frame_comp.h @@ -0,0 +1,77 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TF_LOBBY_CONTAINER_FRAME_COMP_H +#define TF_LOBBY_CONTAINER_FRAME_COMP_H + + +#include "cbase.h" +//#include "tf_pvelobbypanel.h" +#include "game/client/iviewport.h" +#include "tf_shareddefs.h" +#include "econ/confirm_dialog.h" +#include "econ/econ_controls.h" +#include "ienginevgui.h" +#include "tf_gc_client.h" +#include "tf_party.h" +#include "tf_item_inventory.h" +#include "econ_ui.h" + +#include "vgui_controls/Tooltip.h" +#include "vgui_controls/PropertyDialog.h" +#include "vgui_controls/PropertySheet.h" +#include "vgui_controls/ComboBox.h" +#include "vgui_controls/RadioButton.h" +#include "vgui_controls/SectionedListPanel.h" +#include "vgui_controls/ScrollableEditablePanel.h" +#include "vgui_bitmapimage.h" +#include "vgui/IInput.h" +#include <vgui_controls/ImageList.h> +#include <vgui/IVGui.h> +#include "GameEventListener.h" +#include "vgui_avatarimage.h" +#include <vgui/ISurface.h> +#include <VGuiMatSurface/IMatSystemSurface.h> +#include "rtime.h" +#include "econ_game_account_client.h" +#include "tf_leaderboardpanel.h" +#include "tf_mapinfo.h" +#include "tf_ladder_data.h" +#include "tf_gamerules.h" +#include "confirm_dialog.h" +#include "tf_lobby_container_frame.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +class CBaseLobbyPanel; + +// This is a big fat kludge so I can use the PropertyPage +class CLobbyContainerFrame_Comp : public CBaseLobbyContainerFrame +{ + DECLARE_CLASS_SIMPLE( CLobbyContainerFrame_Comp, CBaseLobbyContainerFrame ); +public: + CLobbyContainerFrame_Comp(); + ~CLobbyContainerFrame_Comp(); + + // + // PropertyDialog overrides + // + virtual void ShowPanel( bool bShow ) OVERRIDE; + virtual void OnCommand( const char *command ) OVERRIDE; + +protected: + + virtual void WriteControls(); + +private: + virtual const char* GetResFile() const OVERRIDE { return "Resource/UI/LobbyContainerFrame_Comp.res"; } + virtual TF_MatchmakingMode GetHandledMode() const { return TF_Matchmaking_LADDER; } + virtual bool VerifyPartyAuthorization() const; + virtual void HandleBackPressed() OVERRIDE; +}; + +#endif //TF_LOBBY_CONTAINER_FRAME_COMP_H
\ No newline at end of file diff --git a/game/client/tf/vgui/tf_lobby_container_frame_mvm.cpp b/game/client/tf/vgui/tf_lobby_container_frame_mvm.cpp new file mode 100644 index 0000000..016f5a1 --- /dev/null +++ b/game/client/tf/vgui/tf_lobby_container_frame_mvm.cpp @@ -0,0 +1,357 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "tf_party.h" +#include "vgui_controls/PropertySheet.h" +#include "vgui_controls/SectionedListPanel.h" +#include "tf_lobbypanel_mvm.h" + +#include "tf_lobby_container_frame_mvm.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +//----------------------------------------------------------------------------- +CLobbyContainerFrame_MvM::CLobbyContainerFrame_MvM() + : CBaseLobbyContainerFrame( "LobbyContainerFrame" ) +{ + // Our internal lobby panel + m_pContents = new CLobbyPanel_MvM( this, this ); + m_pContents->MoveToFront(); + m_pContents->AddActionSignalTarget( this ); + AddPage( m_pContents, "#TF_Matchmaking_HeaderMvM" ); + GetPropertySheet()->SetNavToRelay( m_pContents->GetName() ); + m_pContents->SetVisible( true ); +} + +//----------------------------------------------------------------------------- +CLobbyContainerFrame_MvM::~CLobbyContainerFrame_MvM( void ) +{ +} + +//----------------------------------------------------------------------------- +void CLobbyContainerFrame_MvM::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + m_pStartPartyButton = dynamic_cast<vgui::Button *>(FindChildByName( "StartPartyButton", true )); Assert( m_pStartPartyButton ); + m_pPlayNowButton = dynamic_cast<vgui::Button *>(FindChildByName( "PlayNowButton", true )); Assert( m_pPlayNowButton ); + m_pPracticeButton = dynamic_cast<vgui::Button *>(FindChildByName( "PracticeButton", true )); Assert( m_pPracticeButton ); +} + +bool CLobbyContainerFrame_MvM::VerifyPartyAuthorization() const +{ + // They want to Mann Up. Confirm that everybody in the party has a ticket. + // if they are in a party of one, we provide slightly more specific UI. + bool bBraggingRights = GTFGCClientSystem()->GetSearchPlayForBraggingRights(); + + // Early out. Anyone can play for free + if ( !bBraggingRights ) + return true; + + // Solo + CTFParty *pParty = GTFGCClientSystem()->GetParty(); + if ( pParty == NULL || pParty->GetNumMembers() <= 1 ) + { + if ( bBraggingRights && !GTFGCClientSystem()->BLocalPlayerInventoryHasMvmTicket() ) + { + ShowEconRequirementDialog( "#TF_MvM_RequiresTicket_Title", "#TF_MvM_RequiresTicket", CTFItemSchema::k_rchMvMTicketItemDefName ); + return false; + } + } + // Group + else + { + wchar_t wszLocalized[512]; + char szLocalized[512]; + wchar_t wszCharPlayerName[128]; + + bool bAnyMembersWithoutAuth = false; + + if ( bBraggingRights ) + { + for ( int i = 0 ; i < pParty->GetNumMembers() ; ++i ) + { + if ( !pParty->Obj().members( i ).owns_ticket() ) + { + bAnyMembersWithoutAuth = true; + + V_UTF8ToUnicode( steamapicontext->SteamFriends()->GetFriendPersonaName( pParty->GetMember( i ) ), wszCharPlayerName, sizeof( wszCharPlayerName ) ); + g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_MissingTicket" ), 1, wszCharPlayerName ); + g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalized, szLocalized, sizeof( szLocalized ) ); + + GTFGCClientSystem()->SendSteamLobbyChat( CTFGCClientSystem::k_eLobbyMsg_SystemMsgFromLeader, szLocalized ); + } + } + } + + if ( bAnyMembersWithoutAuth ) + { + if ( bBraggingRights ) + { + ShowMessageBox( "#TF_MvM_RequiresTicket_Title", "#TF_MvM_RequiresTicketParty", "#GameUI_OK" ); + return false; + } + } + } + + return true; +} + +void CLobbyContainerFrame_MvM::HandleBackPressed() +{ + switch ( GTFGCClientSystem()->GetWizardStep() ) + { + case TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS: + // !FIXME! Rreally need to confirm this! + GTFGCClientSystem()->EndMatchmaking(); + // And hide us + ShowPanel( false ); + return; + +#ifdef USE_MVM_TOUR + case TF_Matchmaking_WizardStep_MVM_TOUR_OF_DUTY: + GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS ); + return; +#endif // USE_MVM_TOUR + + case TF_Matchmaking_WizardStep_MVM_CHALLENGE: +#ifdef USE_MVM_TOUR + if ( GTFGCClientSystem()->GetSearchPlayForBraggingRights() ) + { + TF_Matchmaking_WizardStep step = TF_Matchmaking_WizardStep_MVM_TOUR_OF_DUTY; + GTFGCClientSystem()->RequestSelectWizardStep( step ); + } + else + { + GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS ); + } +#else // new mm + GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS ); +#endif // USE_MVM_TOUR + return; + + case TF_Matchmaking_WizardStep_SEARCHING: + GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_MVM_CHALLENGE ); + return; + + default: + Msg( "Unexpected wizard step %d", (int)GTFGCClientSystem()->GetWizardStep() ); + break; + } + + // Unhandled case + BaseClass::HandleBackPressed(); +} + +//----------------------------------------------------------------------------- +void CLobbyContainerFrame_MvM::OnCommand( const char *command ) +{ + if ( FStrEq( command, "learn_more" ) ) + { + if ( steamapicontext && steamapicontext->SteamFriends() ) + { + steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( "http://www.teamfortress.com/mvm/" ); + } + return; + } + else if ( FStrEq( command, "mannup" ) ) + { + GTFGCClientSystem()->SetSearchPlayForBraggingRights( true ); +#ifdef USE_MVM_TOUR + GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_MVM_TOUR_OF_DUTY ); +#else // new mm + GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_MVM_CHALLENGE ); +#endif // USE_MVM_TOUR + return; + } + else if ( FStrEq( command, "practice" ) ) + { + GTFGCClientSystem()->SetSearchPlayForBraggingRights( false ); + GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_MVM_CHALLENGE ); + return; + } + else if ( FStrEq( command, "next" ) ) + { + switch ( GTFGCClientSystem()->GetWizardStep() ) + { + case TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS: +#ifdef USE_MVM_TOUR + if ( GTFGCClientSystem()->GetSearchPlayForBraggingRights() ) + { + GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_MVM_TOUR_OF_DUTY ); + } + else + { + GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_MVM_CHALLENGE ); + } + break; + + case TF_Matchmaking_WizardStep_MVM_TOUR_OF_DUTY: +#endif // USE_MVM_TOUR + GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_MVM_CHALLENGE ); + break; + + case TF_Matchmaking_WizardStep_MVM_CHALLENGE: + case TF_Matchmaking_WizardStep_LADDER: + StartSearch(); + break; + + default: + AssertMsg1( false, "Unexpected wizard step %d", (int)GTFGCClientSystem()->GetWizardStep() ); + break; + } + return; + } + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLobbyContainerFrame_MvM::OnKeyCodePressed(vgui::KeyCode code) +{ + ButtonCode_t nButtonCode = GetBaseButtonCode( code ); + + if ( nButtonCode == KEY_XBUTTON_Y ) + { + static_cast< CLobbyPanel_MvM* >( m_pContents )->ToggleSquadSurplusCheckButton(); + } + + BaseClass::OnKeyCodePressed( code ); +} + + +//----------------------------------------------------------------------------- +void CLobbyContainerFrame_MvM::WriteControls() +{ + // Make sure we want to be in matchmaking. (If we don't, the frame should hide us pretty quickly.) + // We might get an event or something right at the transition point occasionally when the UI should + // not be visible + if ( GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_Inactive ) + { + return; + } + const char *pszBackButtonText = "#TF_Matchmaking_Back"; + const char *pszNextButtonText = NULL; + + CMvMMissionSet challenges; + GTFGCClientSystem()->GetSearchChallenges( challenges ); + + bool bShowPlayNowButtons = false; + + if ( GCClientSystem()->BConnectedtoGC() ) + { + if ( BIsPartyLeader() ) + { + switch ( GTFGCClientSystem()->GetWizardStep() ) + { + case TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS: + { + if ( !m_pStartPartyButton->IsVisible() ) + { + pszBackButtonText = "#TF_Matchmaking_LeaveParty"; + } + else + { + pszBackButtonText = "#TF_Matchmaking_Back"; + } + + bShowPlayNowButtons = BIsPartyLeader(); + break; + } + + case TF_Matchmaking_WizardStep_MVM_TOUR_OF_DUTY: +#ifdef USE_MVM_TOUR + pszBackButtonText = "#TF_Matchmaking_Back"; + pszNextButtonText = "#TF_MvM_SelectChallenge"; + + SetNextButtonEnabled( GTFGCClientSystem()->GetSearchMannUpTourIndex() >= 0 ); +#else // new mm + AssertMsg( 0, "This is legacy code. We don't have concept of tour anymore." ); +#endif // USE_MVM_TOUR + break; + + case TF_Matchmaking_WizardStep_MVM_CHALLENGE: + pszBackButtonText = "#TF_Matchmaking_Back"; + + pszNextButtonText = "#TF_Matchmaking_StartSearch"; + SetNextButtonEnabled( !challenges.IsEmpty() ); + + break; + + case TF_Matchmaking_WizardStep_SEARCHING: + pszBackButtonText = "#TF_Matchmaking_CancelSearch"; + break; + + case TF_Matchmaking_WizardStep_INVALID: + // Still being setup + break; + + default: + AssertMsg1( false, "Unknown wizard step %d", (int)GTFGCClientSystem()->GetWizardStep() ); + break; + } + } + else + { + pszBackButtonText = "#TF_Matchmaking_LeaveParty"; + m_pNextButton->SetEnabled( false ); + } + } + + m_pPlayNowButton->SetVisible( bShowPlayNowButtons ); + m_pPracticeButton->SetVisible( bShowPlayNowButtons ); + SetControlVisible( "LearnMoreButton", GTFGCClientSystem()->GetWizardStep() == TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS ); + + // Set appropriate page title + switch ( GTFGCClientSystem()->GetSearchMode() ) + { + case TF_Matchmaking_MVM: + if ( GTFGCClientSystem()->GetSearchPlayForBraggingRights() || + GTFGCClientSystem()->GetWizardStep() == TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS ) + { + GetPropertySheet()->SetTabTitle( 0, "#TF_MvM_HeaderCoop" ); + } + else + { + GetPropertySheet()->SetTabTitle( 0, "#TF_MvM_HeaderPractice" ); + } + break; + + default: + AssertMsg1( false, "Invalid search mode %d", GTFGCClientSystem()->GetSearchMode() ); + break; + } + + // Check if we already have a party, then make sure and show it + if ( m_pStartPartyButton->IsVisible() && m_pContents->NumPlayersInParty() > 1 ) + { + m_pContents->SetControlVisible( "PartyActiveGroupBox", true ); + } + + SetControlVisible( "PlayWithFriendsExplanation", ShouldShowPartyButton() ); + + + static_cast< CLobbyPanel_MvM* >( m_pContents )->SetMannUpTicketCount( GTFGCClientSystem()->GetLocalPlayerInventoryMvmTicketCount() ); + static_cast< CLobbyPanel_MvM* >( m_pContents )->SetSquadSurplusCount( GTFGCClientSystem()->GetLocalPlayerInventorySquadSurplusVoucherCount() ); + + m_pBackButton->SetText( pszBackButtonText ); + if ( pszNextButtonText ) + { + m_pNextButton->SetText( pszNextButtonText ); + m_pNextButton->SetVisible( true ); + } + else + { + m_pNextButton->SetVisible( false ); + } + + BaseClass::WriteControls(); +} + diff --git a/game/client/tf/vgui/tf_lobby_container_frame_mvm.h b/game/client/tf/vgui/tf_lobby_container_frame_mvm.h new file mode 100644 index 0000000..0ed0548 --- /dev/null +++ b/game/client/tf/vgui/tf_lobby_container_frame_mvm.h @@ -0,0 +1,49 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TF_LOBBY_CONTAINER_FRAME_MVM_H +#define TF_LOBBY_CONTAINER_FRAME_MVM_H + + +#include "cbase.h" +//#include "tf_pvelobbypanel.h" +#include "tf_lobby_container_frame.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + + +class CBaseLobbyPanel; + +// This is a big fat kludge so I can use the PropertyPage +class CLobbyContainerFrame_MvM : public CBaseLobbyContainerFrame +{ + DECLARE_CLASS_SIMPLE( CLobbyContainerFrame_MvM, CBaseLobbyContainerFrame ); +public: + CLobbyContainerFrame_MvM(); + ~CLobbyContainerFrame_MvM(); + + // + // PropertyDialog overrides + // + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE; + virtual void OnKeyCodePressed(vgui::KeyCode code) OVERRIDE; + virtual void OnCommand( const char *command ) OVERRIDE; + +private: + + virtual const char* GetResFile() const OVERRIDE { return "Resource/UI/LobbyContainerFrame_MvM.res"; } + virtual TF_MatchmakingMode GetHandledMode() const { return TF_Matchmaking_MVM; } + virtual bool VerifyPartyAuthorization() const OVERRIDE; + virtual void WriteControls() OVERRIDE; + virtual void HandleBackPressed() OVERRIDE; + + vgui::Button *m_pStartPartyButton; + vgui::Button *m_pPlayNowButton; + vgui::Button *m_pPracticeButton; +}; + +#endif //TF_LOBBY_CONTAINER_FRAME_MVM_H
\ No newline at end of file diff --git a/game/client/tf/vgui/tf_lobbypanel.cpp b/game/client/tf/vgui/tf_lobbypanel.cpp new file mode 100644 index 0000000..8f8a182 --- /dev/null +++ b/game/client/tf/vgui/tf_lobbypanel.cpp @@ -0,0 +1,1107 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" + +#include "tf_party.h" +#include "tf_item_inventory.h" +#include "vgui_controls/PropertySheet.h" +#include "vgui_controls/SectionedListPanel.h" +#include "vgui/IInput.h" +#include <vgui_controls/ImageList.h> +#include "vgui_avatarimage.h" +#include "tf_ladder_data.h" +#include "vgui_controls/Menu.h" +#include "tf_match_description.h" +#include "tf_badge_panel.h" +#include "tf_controls.h" + +#include "tf_lobbypanel.h" +#include "tf_lobby_container_frame.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +ConVar tf_matchmaking_join_in_progress( "tf_matchmaking_join_in_progress", "0", FCVAR_DONTRECORD | FCVAR_ARCHIVE, "Saved preference for if the player wants to join games in progress." ); + +const int k_iPopIndex_Any = -1000; +const int k_iPopIndex_OnlyNotYetCompleted = -1001; +const int k_iPopIndex_AnyNormal = -1002; +const int k_iPopIndex_AnyIntermediate = -1003; +const int k_iPopIndex_AnyAdvanced = -1004; +const int k_iPopIndex_AnyExpert = -1005; +const int k_iPopIndex_AnyHaunted = -1006; + +static void GetMvmChallengeSet( int idxChallenge, CMvMMissionSet &result ) +{ + result.Clear(); + + if ( idxChallenge >= 0 ) + { + result.SetMissionBySchemaIndex( idxChallenge, true ); + return; + } + + bool bMannUP = GTFGCClientSystem()->GetSearchPlayForBraggingRights(); +#ifdef USE_MVM_TOUR + int idxTour = GTFGCClientSystem()->GetSearchMannUpTourIndex(); + Assert( bMannUP || idxTour < 0 ); +#endif // USE_MVM_TOUR + +#ifdef USE_MVM_TOUR + uint32 nNotCompletedChallenges = ~0U; + CTFParty *pParty = GTFGCClientSystem()->GetParty(); + if ( pParty ) + { + for ( int i = 0 ; i < pParty->GetNumMembers() ; ++i ) + { + nNotCompletedChallenges &= ~pParty->Obj().members( i ).completed_missions(); + } + } + else + { + if ( idxTour >= 0 ) + { + uint32 nTours = 0, nCompletedChallenge = 0; + GTFGCClientSystem()->BGetLocalPlayerBadgeInfoForTour( idxTour, &nTours, &nCompletedChallenge ); + + nNotCompletedChallenges = ~nCompletedChallenge; + } + } +#endif // USE_MVM_TOUR + + for ( int i = 0 ; i < GetItemSchema()->GetMvmMissions().Count() ; ++i ) + { + const MvMMission_t &chal = GetItemSchema()->GetMvmMissions()[ i ]; + + // Cannot select non-MannUp missions in mann up mode +#ifdef USE_MVM_TOUR + int iBadgeSlot = (idxTour < 0) ? -1 : GetItemSchema()->GetMvmMissionBadgeSlotForTour( idxTour, i ); + if ( bMannUP && iBadgeSlot < 0 ) + continue; +#else // new mm + bool bIsChallengeInMannUp = chal.m_unMannUpPoints > 0; + if ( bMannUP && !bIsChallengeInMannUp ) + continue; +#endif // USE_MVM_TOUR + + // Does this challenge fit the search criteria? + bool bSelect = false; + switch ( idxChallenge ) + { + case k_iPopIndex_Any: + bSelect = true; + break; + case k_iPopIndex_OnlyNotYetCompleted: +#ifdef USE_MVM_TOUR + if ( iBadgeSlot >= 0 ) + { + int iChallengeBit = ( 1 << iBadgeSlot ); + if ( nNotCompletedChallenges & iChallengeBit ) + { + bSelect = true; + } + } +#endif // USE_MVM_TOUR + break; + + case k_iPopIndex_AnyNormal: + bSelect = ( chal.m_eDifficulty == k_EMvMChallengeDifficulty_Normal ); + break; + + case k_iPopIndex_AnyIntermediate: + bSelect = ( chal.m_eDifficulty == k_EMvMChallengeDifficulty_Intermediate ); + break; + + case k_iPopIndex_AnyAdvanced: + bSelect = ( chal.m_eDifficulty == k_EMvMChallengeDifficulty_Advanced ); + break; + + case k_iPopIndex_AnyExpert: + bSelect = ( chal.m_eDifficulty == k_EMvMChallengeDifficulty_Expert ); + break; + + case k_iPopIndex_AnyHaunted: + bSelect = ( chal.m_eDifficulty == k_EMvMChallengeDifficulty_Haunted ); + break; + + default: + Assert( false ); + } + result.SetMissionBySchemaIndex( i, bSelect ); + } +} + +#ifdef ENABLE_GC_MATCHMAKING + +Color s_colorBannedPlayerListItem( 250, 50, 45, 255 ); +Color s_colorPlayerListItem( 255, 255, 255, 255 ); +Color s_colorChatRemovedFromQueue( 200, 10, 10, 255 ); +Color s_colorChatAddedToQueue( 10, 200, 10, 255 ); +Color s_colorChatPlayerJoinedParty( 255, 255, 255, 255 ); +Color s_colorChatPlayerJoinedPartyName( 200, 200, 10, 255 ); +Color s_colorChatPlayerLeftParty( 255, 255, 255, 255 ); +Color s_colorChatPlayerLeftPartyName( 200, 200, 10, 255 ); +Color s_colorChatPlayerChatName( 200, 200, 10, 255 ); +Color s_colorChatPlayerChatText( 180, 180, 180, 255 ); +Color s_colorChatDefault( 180, 180, 180, 255 ); +Color s_colorChallengeForegroundEnabled( 255, 255, 255, 255 ); +Color s_colorChallengeForegroundHaunted( 135, 79, 173, 255 ); +Color s_colorChallengeForegroundDisabled( 100, 100, 100, 128 ); +Color s_colorChallengeHeader( 250, 114, 45, 255 ); + +static void GetPlayerNameForSteamID( wchar_t *wCharPlayerName, int nBufSizeBytes, const CSteamID &steamID ) +{ + const char *pszName = steamapicontext->SteamFriends()->GetFriendPersonaName( steamID ); + V_UTF8ToUnicode( pszName, wCharPlayerName, nBufSizeBytes ); +} + +CBaseLobbyPanel::CBaseLobbyPanel( vgui::Panel *pParent, CBaseLobbyContainerFrame* pContainer ) + : vgui::PropertySheet( pParent, "LobbyPanel" ), m_sPersonaStateChangedCallback( this, &CBaseLobbyPanel::OnPersonaStateChanged ) + , m_pContainer( pContainer ) +{ + //ListenForGameEvent( "lobby_updated" ); + ListenForGameEvent( "party_updated" ); + ListenForGameEvent( "mm_lobby_chat" ); + ListenForGameEvent( "mm_lobby_member_join" ); + ListenForGameEvent( "mm_lobby_member_leave" ); + + m_iWritingPanel = 0; + + m_pSearchActiveGroupBox = NULL; + m_pSearchActiveTitleLabel = NULL; + m_pSearchActivePenaltyLabel = NULL; + m_pPartyHasLowPriority = NULL; + + m_pJoinLateCheckButton = NULL; + m_pJoinLateValueLabel = NULL; + + m_pInviteButton = NULL; + m_pChatLog = NULL; + //m_nFirstMapShown = -1; + m_pImageList = NULL; + + m_iImageIsBanned = -1; + m_iImageRadioButtonYes = -1; + m_iImageRadioButtonNo = -1; + m_iImageCheckBoxDisabled = -1; + m_iImageCheckBoxYes = -1; + m_iImageCheckBoxNo = -1; + m_iImageCheckBoxMixed = -1; + m_iImageNew = -1; + m_iImageNo = -1; + + m_fontPlayerListItem = 0; + + // Party + m_pPartyActiveGroupBox = new vgui::EditablePanel( this, "PartyActiveGroupBox" ); + vgui::EditablePanel *pPartyGroupPanel = new vgui::EditablePanel( m_pPartyActiveGroupBox, "PartyGroupBox" ); + m_pChatPlayerList = new vgui::SectionedListPanel( pPartyGroupPanel, "PartyPlayerList" ); + m_pChatTextEntry = new ChatTextEntry( m_pPartyActiveGroupBox, "ChatTextEntry" ); Assert( m_pChatTextEntry ); + m_pChatLog = new ChatLog( m_pPartyActiveGroupBox, "ChatLog" ); Assert( m_pChatLog ); + + m_mapAvatarsToImageList.SetLessFunc( DefLessFunc(int) ); + + m_eCurrentPartyState = CSOTFParty_State_UI; + + m_flRefreshPlayerListTime = -1.f; + + m_pToolTip = new CMainMenuToolTip( this ); + vgui::EditablePanel* pToolTipEmbeddedPanel = new vgui::EditablePanel( this, "TooltipPanel" ); + pToolTipEmbeddedPanel->SetKeyBoardInputEnabled( false ); + pToolTipEmbeddedPanel->SetMouseInputEnabled( false ); + m_pToolTip->SetEmbeddedPanel( pToolTipEmbeddedPanel ); + m_pToolTip->SetTooltipDelay( 0 ); +} + +CBaseLobbyPanel::~CBaseLobbyPanel() +{ + delete m_pImageList; + m_pImageList = NULL; +} + +void CBaseLobbyPanel::OnClickedOnPlayer() +{ + int iSelected = m_pChatPlayerList->GetSelectedItem(); + //m_pChatPlayerList->ClearSelection(); + if ( iSelected < 0 ) + return; + CSteamID steamID = SteamIDFromDecimalString( m_pChatPlayerList->GetItemData( iSelected )->GetString( "steamid", "" ) ); + if ( !steamID.IsValid() ) + return; + + vgui::Menu *menu = new vgui::Menu(this, "ContextMenu"); + + int x, y; + vgui::input()->GetCursorPos(x, y); + menu->SetPos(x, y); + + wchar_t wszLocalized[512]; + char szLocalized[512]; + g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_ScoreBoard_Context_Trade" ), 0 ); + g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalized, szLocalized, sizeof( szLocalized ) ); + + menu->AddMenuItem( szLocalized, new KeyValues( "TradeWithUser", "steamid", CFmtStr( "%llu", steamID.ConvertToUint64() ).Access() ), this ); + + menu->SetVisible(true); +} + +void CBaseLobbyPanel::SetMatchmakingModeBackground() +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + + // Get the background panel. + vgui::ImagePanel* pModeBackgroundImage = FindControl< vgui::ImagePanel >( "ModeBackgroundImage", true ); + if ( !pModeBackgroundImage ) + return; + + const char* pszImageName = NULL; + const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GetMatchGroup() ); + if ( pMatchDesc && pMatchDesc->m_pProgressionDesc ) + { + // Get the level of the local player, and pull the background image out of the level + const LevelInfo_t& level = pMatchDesc->m_pProgressionDesc->YieldingGetLevelForSteamID( steamapicontext->SteamUser()->GetSteamID() ); + pszImageName = level.m_pszLobbyBackgroundImage; + } + + // Set the image name, if we got one, into the panel + if ( pszImageName ) + { + pModeBackgroundImage->SetImage( pszImageName ); + } + + // Only show if we got one (MvM doesn't have any) + pModeBackgroundImage->SetVisible( pszImageName != NULL ); +} + +//----------------------------------------------------------------------------- +void CBaseLobbyPanel::FireGameEvent( IGameEvent *event ) +{ + if ( !IsVisible() || !m_pContainer->IsVisible() ) + return; + + const char *pszEventName = event->GetName(); + if ( !Q_stricmp( pszEventName, "party_updated" ) ) + { + + CTFParty *pParty = GTFGCClientSystem()->GetParty(); + if ( pParty ) + { + if ( m_eCurrentPartyState != pParty->GetState() ) + { + wchar_t wszLocalized[512]; + + switch ( pParty->GetState() ) + { + case CSOTFParty_State_UI: + m_pChatLog->InsertColorChange( s_colorChatRemovedFromQueue ); + g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_RemovedFromQueue" ), 0 ); + m_pChatLog->InsertString( wszLocalized ); + break; + + case CSOTFParty_State_FINDING_MATCH: + m_pChatLog->InsertColorChange( s_colorChatAddedToQueue ); + g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_AddedToQueue" ), 0 ); + m_pChatLog->InsertString( wszLocalized ); + break; + + default: + Assert( false ); + case CSOTFParty_State_IN_MATCH: + break; + } + m_eCurrentPartyState = pParty->GetState(); + } + } + else + { + m_eCurrentPartyState = CSOTFParty_State_UI; + } + + UpdatePlayerList(); + WriteGameSettingsControls(); + return; + } + + if ( !Q_stricmp( pszEventName, "mm_lobby_chat" ) ) + { + CSteamID steamID = SteamIDFromDecimalString( event->GetString( "steamid", "0" ) ); + + const char *pszText = event->GetString( "text", "" ); + int l = V_strlen( pszText ); + if ( l > 0 ) + { + int nBufSize = l * sizeof(wchar_t) + 4; + wchar_t *wText = (wchar_t *)stackalloc( nBufSize ); + V_UTF8ToUnicode( pszText, wText, nBufSize ); + switch ( event->GetInt( "type", CTFGCClientSystem::k_eLobbyMsg_UserChat ) ) + { + default: + Assert( !"Unknown chat message type" ); + case CTFGCClientSystem::k_eLobbyMsg_SystemMsgFromLeader: + m_pChatLog->InsertColorChange( s_colorChatDefault ); + m_pChatLog->InsertString( wText ); + m_pChatLog->InsertString("\n"); + break; + + case CTFGCClientSystem::k_eLobbyMsg_UserChat: + { + + wchar_t wCharPlayerName[ 128 ]; + GetPlayerNameForSteamID( wCharPlayerName, sizeof(wCharPlayerName), steamID ); + m_pChatLog->InsertColorChange( s_colorChatPlayerChatName ); + m_pChatLog->InsertString( wCharPlayerName ); + m_pChatLog->InsertString( ": " ); + m_pChatLog->InsertColorChange( s_colorChatPlayerChatText ); + m_pChatLog->InsertString( wText ); + m_pChatLog->InsertString("\n"); + } break; + } + } + + return; + } + + if ( !Q_stricmp( pszEventName, "mm_lobby_member_join" ) ) + { + CSteamID steamID = SteamIDFromDecimalString( event->GetString( "steamid", "0" ) ); + + bool bSolo = false; + if ( steamID == steamapicontext->SteamUser()->GetSteamID() ) + { + m_pChatLog->SetText(""); + bSolo = ( event->GetInt( "solo", 0 ) != 0 ); + } + + wchar_t wszLocalized[512]; + + // An empty lobby by ourselves? + if ( bSolo ) + { + m_pChatLog->InsertColorChange( s_colorChatDefault ); + + g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_StartSearchChat" ), 0 ); + m_pChatLog->InsertString( wszLocalized ); + + g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_InviteFriendsChat" ), 0 ); + m_pChatLog->InsertString( wszLocalized ); + } + else + { + wchar_t wCharPlayerName[128]; + m_pChatLog->InsertColorChange( s_colorChatPlayerJoinedPartyName ); + GetPlayerNameForSteamID( wCharPlayerName, sizeof( wCharPlayerName ), steamID ); + m_pChatLog->InsertColorChange( s_colorChatPlayerJoinedParty ); + + g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_PlayerJoinedPartyChat" ), 1, wCharPlayerName ); + m_pChatLog->InsertString( wszLocalized ); + } + + UpdatePlayerList(); + + return; + } + + if ( !Q_stricmp( pszEventName, "mm_lobby_member_leave" ) ) + { + CSteamID steamID = SteamIDFromDecimalString( event->GetString( "steamid", "0" ) ); + wchar_t wCharPlayerName[ 128 ]; + GetPlayerNameForSteamID( wCharPlayerName, sizeof(wCharPlayerName), steamID ); + m_pChatLog->InsertColorChange( s_colorChatPlayerLeftParty ); + + wchar_t wszLocalized[512]; + g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_PlayerLeftPartyChat" ), 1, wCharPlayerName ); + m_pChatLog->InsertString( wszLocalized ); + + UpdatePlayerList(); + WriteGameSettingsControls(); + + return; + } + + Assert( false ); +} + +void CBaseLobbyPanel::OnCommand( const char *command ) +{ + if ( FStrEq( command, "invite" ) ) + { + GTFGCClientSystem()->RequestActivateInvite(); + } + else if ( FStrEq( command, "open_charinfo" ) ) + { + engine->ClientCmd_Unrestricted( "open_econui_backpack" ); + } + else + { + // What other commands are there? + Assert( false ); + } +} + + +bool CBaseLobbyPanel::IsAnyoneBanned( RTime32 &rtimeExpire ) const +{ + bool bBanned = false; + RTime32 rtimeHighest = 0; + + // This only matters if we're searching for a mannup or ladder game + CTFParty *pParty = GTFGCClientSystem()->GetParty(); + if ( pParty && ( !pParty->GetSearchPlayForBraggingRights() && !IsLadderGroup( pParty->GetMatchGroup() ) ) ) + { + return false; + } + + for( int i=0; i<m_vecPlayers.Count(); ++i ) + { + if ( m_vecPlayers[i].m_bIsBanned ) + { + bBanned = true; + + if ( m_vecPlayers[i].m_rtimeBanExpire > rtimeHighest ) + { + rtimeHighest = m_vecPlayers[i].m_rtimeBanExpire; + } + } + } + + rtimeExpire = rtimeHighest; + return bBanned; +} + +const CBaseLobbyPanel::LobbyPlayerInfo* CBaseLobbyPanel::GetLobbyPlayerInfo( CSteamID &steamID ) const +{ + for ( int i = 0; i < m_vecPlayers.Count(); ++i ) + { + if ( m_vecPlayers[i].m_steamID == steamID ) + { + return &m_vecPlayers[i]; + } + } + + Assert( false ); + return NULL; +} + +bool CBaseLobbyPanel::IsAnyoneLowPriority( RTime32 &rtimeExpire ) const +{ + bool bLowPriority = false; + RTime32 rtimeHighest = 0; + + CTFParty *pParty = GTFGCClientSystem()->GetParty(); + if ( pParty && !pParty->GetSearchPlayForBraggingRights() && !IsLadderGroup( pParty->GetMatchGroup() ) ) + return false; + + for ( int i = 0; i < m_vecPlayers.Count(); ++i ) + { + if ( m_vecPlayers[i].m_bIsLowPriority ) + { + bLowPriority = true; + + if ( m_vecPlayers[i].m_rtimeLowPriorityExpire > rtimeHighest ) + { + rtimeHighest = m_vecPlayers[i].m_rtimeLowPriorityExpire; + } + } + } + + rtimeExpire = rtimeHighest; + return bLowPriority; +} + +void CBaseLobbyPanel::UpdateControls() +{ + WriteGameSettingsControls(); + WriteStatusControls(); + UpdatePlayerList(); +} + +void CBaseLobbyPanel::OnCheckButtonChecked( vgui::Panel *panel ) +{ + if ( m_iWritingPanel > 0 ) + return; + if ( panel == m_pJoinLateCheckButton ) + { + if ( BIsPartyLeader() && GCClientSystem()->BConnectedtoGC() ) + { + tf_matchmaking_join_in_progress.SetValue( m_pJoinLateCheckButton->IsSelected() ? 1 : 0 ); + GTFGCClientSystem()->SetSearchJoinLate( m_pJoinLateCheckButton->IsSelected() ); + } + else + { + WriteGameSettingsControls(); + } + } +} + +void CBaseLobbyPanel::OnTradeWithUser( KeyValues* params ) +{ + CSteamID steamID = SteamIDFromDecimalString( params->GetString( "steamid", "" ) ); + if ( !steamID.IsValid() ) + return; + steamapicontext->SteamFriends()->ActivateGameOverlayToUser( "jointrade", steamID ); +} + +void CBaseLobbyPanel::OnItemLeftClick( vgui::Panel* panel ) +{ + if ( m_iWritingPanel > 0 ) + return; + m_pChatTextEntry->RequestFocus(); + if ( panel == m_pChatPlayerList ) + { + OnClickedOnPlayer(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseLobbyPanel::WriteStatusControls() +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + + enum EDisabledState + { + DISABLED_NONE, + DISABLED_NO_GC, + DISABLED_MATCH_IN_PROGRESS + }; + + EDisabledState eDisabled = DISABLED_NONE; + + if ( GTFGCClientSystem()->BHaveLiveMatch() ) + { + eDisabled = DISABLED_MATCH_IN_PROGRESS; + } + + if ( !GCClientSystem()->BConnectedtoGC() || ( GTFGCClientSystem()->GetParty() && GTFGCClientSystem()->GetParty()->BOffline() ) ) + { + eDisabled = DISABLED_NO_GC; + } + + SetControlVisible( "NoGCGroupBox", eDisabled == DISABLED_NO_GC, true ); + SetControlVisible( "MatchInProgressGroupBox", eDisabled == DISABLED_MATCH_IN_PROGRESS, true ); + + if ( GTFGCClientSystem()->GetWizardStep() == TF_Matchmaking_WizardStep_SEARCHING ) + { + m_pSearchActiveGroupBox->SetVisible( true ); + + const CMsgMatchmakingProgress &progress = GTFGCClientSystem()->m_msgMatchmakingProgress; + wchar_t wszCount[32]; + CUtlVector<vgui::Label *> vecNearbyFields; + vgui::Label *pNearbyColumnHead = dynamic_cast<vgui::Label *>( FindChildByName( "NearbyColumnHead", true ) ); + Assert( pNearbyColumnHead ); + vecNearbyFields.AddToTail( pNearbyColumnHead ); + + #define DO_FIELD( protobufname, labelname, bNearby ) \ + vgui::Label *p##labelname = dynamic_cast<vgui::Label *>( FindChildByName( #labelname, true ) ); \ + Assert( p##labelname ); \ + if ( p##labelname ) \ + { \ + if ( progress.has_##protobufname() ) \ + { \ + _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", progress.protobufname() ); \ + p##labelname->SetText( wszCount ); \ + if ( bNearby ) bHasAnyNearbyData = true; \ + } \ + else \ + { \ + p##labelname->SetText( "#TF_Matchmaking_NoData" ); \ + } \ + if ( bNearby ) vecNearbyFields.AddToTail( p##labelname ); \ + } + + bool bHasAnyNearbyData = false; + DO_FIELD( matching_worldwide_searching_players, PlayersSearchingMatchingWorldwideValue, false ) + DO_FIELD( matching_near_you_searching_players, PlayersSearchingMatchingNearbyValue, true ) + DO_FIELD( matching_worldwide_active_players, PlayersInGameMatchingWorldwideValue, false ) + DO_FIELD( matching_near_you_active_players, PlayersInGameMatchingNearbyValue, true ) + DO_FIELD( matching_worldwide_empty_gameservers, EmptyGameserversMatchingWorldwideValue, false ) + DO_FIELD( matching_near_you_empty_gameservers, EmptyGameserversMatchingNearbyValue, true ) + DO_FIELD( total_worldwide_searching_players, PlayersSearchingTotalWorldwideValue, false ) + DO_FIELD( total_near_you_searching_players, PlayersSearchingTotalNearbyValue, true ) + DO_FIELD( total_worldwide_active_players, PlayersInGameTotalWorldwideValue, false ) + DO_FIELD( total_near_you_active_players, PlayersInGameTotalNearbyValue, true ) + + FOR_EACH_VEC( vecNearbyFields, i ) + { + vecNearbyFields[i]->SetVisible( bHasAnyNearbyData ); + } + + // Show the low priority message? This only really applies to ladder games for now. + RTime32 rtimeExpire = 0; + bool bShowTimer = IsAnyoneLowPriority( rtimeExpire ); + if ( bShowTimer ) + { + CRTime timeExpire( rtimeExpire ); + timeExpire.SetToGMT( false ); + char time_buf[k_RTimeRenderBufferSize]; + if ( m_pPartyHasLowPriority ) + { + m_pPartyHasLowPriority->SetDialogVariable( "penaltytimer", CFmtStr( "Expires: %s", ( ( rtimeExpire > 0 ) ? timeExpire.Render( time_buf ) : "" ) ) ); + } + if ( m_pSearchActivePenaltyLabel ) + { + m_pSearchActivePenaltyLabel->SetText( "#TF_Matchmaking_PartyLowPriority" ); + } + } + if ( m_pPartyHasLowPriority ) + { + m_pPartyHasLowPriority->SetVisible( bShowTimer ); + } + if ( m_pSearchActivePenaltyLabel ) + { + m_pSearchActivePenaltyLabel->SetVisible( bShowTimer ); + } + + // HOLY CHEESEBALL BUSY INDICATOR + const wchar_t *pwszEllipses = &L"....."[ 4 - ( (unsigned)Plat_FloatTime() % 5U ) ]; + wchar_t wszLocalized[512]; + g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_Searching" ), 1, pwszEllipses ); + if ( m_pSearchActiveTitleLabel ) + { + m_pSearchActiveTitleLabel->SetText( wszLocalized ); + } + } + else + { + m_pSearchActiveGroupBox->SetVisible( false ); + } +} + +void CBaseLobbyPanel::WriteGameSettingsControls() +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + + // Make sure we want to be in matchmaking. (If we don't, the frame should hide us pretty quickly.) + // We might get an event or something right at the transition point occasionally when the UI should + // not be visible + if ( GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_Inactive ) + { + return; + } + + SetMatchmakingModeBackground(); + + bool bLeader = BIsPartyLeader(); + bool bInUIState = BIsPartyInUIState(); + + m_pJoinLateCheckButton->ToggleButton::SetSelected( GTFGCClientSystem()->GetSearchJoinLate() ); // !KLUDGE! call base to avoid firing the signal + + bool bShowLateJoin = ShouldShowLateJoin(); + m_pJoinLateCheckButton->SetVisible( bShowLateJoin && bLeader ); + m_pJoinLateValueLabel->SetText( GTFGCClientSystem()->GetSearchJoinLate() ? "#TF_Matchmaking_SearchForAll" : "#TF_Matchmaking_SearchForNew" ); + m_pJoinLateValueLabel->SetVisible( bShowLateJoin && !bLeader ); + //m_pJoinLateValueLabel->SetEnabled( bInUIState ); + + m_pInviteButton->SetVisible( bInUIState && ( m_vecPlayers.Count() < k_nTFPartyMaxSize ) ); + + m_pChatTextEntry->RequestFocus(); +} + +//----------------------------------------------------------------------------- +// Purpose: Updates the player list +//----------------------------------------------------------------------------- +void CBaseLobbyPanel::UpdatePlayerList() +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + + if ( !IsVisible() || !m_pContainer->IsVisible() ) + return; + + m_pChatPlayerList->ClearSelection(); + m_pChatPlayerList->RemoveAll(); + m_vecPlayers.RemoveAll(); + + bool bLadderGame = GTFGCClientSystem()->GetSearchMode() == TF_Matchmaking_LADDER && + IsLadderGroup( (EMatchGroup)GTFGCClientSystem()->GetLadderType() ); + const IMatchGroupDescription *pMatchDesc = GetMatchGroupDescription( GetMatchGroup() ); + EMMPenaltyPool ePenaltyPool = pMatchDesc ? pMatchDesc->m_params.m_ePenaltyPool : eMMPenaltyPool_Invalid; + + if ( steamapicontext == NULL || steamapicontext->SteamUser() == NULL ) + return; + + // Locate party, if we have one. + CTFParty *pParty = GTFGCClientSystem()->GetParty(); + + if ( pParty == NULL ) + { + LobbyPlayerInfo p; + p.m_steamID = steamapicontext->SteamUser()->GetSteamID(); + p.m_sName = steamapicontext->SteamFriends()->GetPersonaName(); + p.m_bHasTicket = GTFGCClientSystem()->BLocalPlayerInventoryHasMvmTicket(); + p.m_bSquadSurplus = GTFGCClientSystem()->GetLocalPlayerSquadSurplus(); +#ifdef USE_MVM_TOUR + int idxTour = GTFGCClientSystem()->GetSearchMannUpTourIndex(); + if ( idxTour < 0 || !GTFGCClientSystem()->BGetLocalPlayerBadgeInfoForTour( idxTour, &p.m_nBadgeLevel, &p.m_nCompletedChallenges ) ) + { + p.m_nBadgeLevel = 0; + p.m_nCompletedChallenges = 0; + } +#endif // USE_MVM_TOUR + p.m_pAvatarImage = NULL; + p.m_bHasCompetitiveAccess = GTFGCClientSystem()->BHasCompetitiveAccess(); + CSOTFLadderData *pData = GetLocalPlayerLadderData( (EMatchGroup)GTFGCClientSystem()->GetLadderType() ); + p.m_unLadderRank = ( pData ? pData->Obj().rank() : 1u ); + + uint32 unExperienceLevel = 1u; + const IProgressionDesc *pProgressionDesc = pMatchDesc ? pMatchDesc->m_pProgressionDesc : NULL; + if ( pData && pProgressionDesc && pMatchDesc->m_params.m_eMatchType == MATCH_TYPE_CASUAL ) + { + LevelInfo_t levelInfo = pProgressionDesc->GetLevelForExperience( pData->Obj().experience() ); + unExperienceLevel = levelInfo.m_nLevelNum; + } + p.m_unExperienceLevel = unExperienceLevel; + + CEconGameAccountClient *pGameAccountClient = NULL; + if ( InventoryManager() && TFInventoryManager()->GetLocalTFInventory() && TFInventoryManager()->GetLocalTFInventory()->GetSOC() ) + { + pGameAccountClient = TFInventoryManager()->GetLocalTFInventory()->GetSOC()->GetSingleton<CEconGameAccountClient>(); + } + + p.m_bIsBanned = false; + p.m_rtimeBanExpire = 0; + p.m_rtimeLowPriorityExpire = 0; + p.m_bIsLowPriority = false; + if ( pGameAccountClient && ePenaltyPool != eMMPenaltyPool_Invalid ) + { + switch ( ePenaltyPool ) + { + case eMMPenaltyPool_Casual: + p.m_rtimeBanExpire = pGameAccountClient->Obj().matchmaking_casual_ban_expiration(); + p.m_rtimeLowPriorityExpire = pGameAccountClient->Obj().matchmaking_casual_low_priority_expiration(); + p.m_bIsBanned = p.m_rtimeBanExpire > CRTime::RTime32TimeCur(); + p.m_bIsLowPriority = p.m_rtimeLowPriorityExpire > CRTime::RTime32TimeCur(); + break; + case eMMPenaltyPool_Ranked: + p.m_rtimeBanExpire = pGameAccountClient->Obj().matchmaking_ranked_ban_expiration(); + p.m_rtimeLowPriorityExpire = pGameAccountClient->Obj().matchmaking_ranked_low_priority_expiration(); + p.m_bIsBanned = p.m_rtimeBanExpire > CRTime::RTime32TimeCur(); + p.m_bIsLowPriority = p.m_rtimeLowPriorityExpire > CRTime::RTime32TimeCur(); + break; + default: Assert( false ); + } + } + + // !TEST! + //p.m_bIsBanned = true; + + //for (int i = 0 ; i < 6 ; ++i) // !TEST! + m_vecPlayers.AddToTail( p ); + } + else + { + for ( int i = 0 ; i < pParty->GetNumMembers() ; ++i ) + { + LobbyPlayerInfo p; + p.m_steamID = pParty->GetMember( i ); + p.m_sName = steamapicontext->SteamFriends()->GetFriendPersonaName( p.m_steamID ); + if ( p.m_sName.IsEmpty() ) + continue; + p.m_bHasTicket = pParty->Obj().members( i ).owns_ticket(); + p.m_nBadgeLevel = pParty->Obj().members( i ).badge_level(); + p.m_nCompletedChallenges = pParty->Obj().members( i ).completed_missions(); + p.m_bSquadSurplus = pParty->Obj().members( i ).squad_surplus(); + p.m_pAvatarImage = NULL; + p.m_bIsBanned = pParty->Obj().members( i ).is_banned(); + p.m_bHasCompetitiveAccess = pParty->Obj().members( i ).competitive_access(); + p.m_unLadderRank = pParty->Obj().members( i ).ladder_rank(); + p.m_rtimeBanExpire = pParty->Obj().matchmaking_ban_time(); + p.m_rtimeLowPriorityExpire = pParty->Obj().matchmaking_low_priority_time(); + p.m_bIsLowPriority = pParty->Obj().members( i ).is_low_priority(); + + uint32 unExperienceLevel = 1u; + const IProgressionDesc *pProgressionDesc = pMatchDesc ? pMatchDesc->m_pProgressionDesc : NULL; + if ( pProgressionDesc && pMatchDesc->m_params.m_eMatchType == MATCH_TYPE_CASUAL ) + { + LevelInfo_t levelInfo = pProgressionDesc->GetLevelForExperience( pParty->Obj().members( i ).experience() ); + unExperienceLevel = levelInfo.m_nLevelNum; + } + p.m_unExperienceLevel = unExperienceLevel; + + if ( p.m_steamID == pParty->GetLeader() ) + { + m_vecPlayers.AddToHead( p ); + } + else + { + m_vecPlayers.AddToTail( p ); + } + } + } + + for( int i = 0; i < m_vecPlayers.Count(); ++i ) + { + KeyValues *pKeyValues = new KeyValues( "data" ); + CUtlString sName; + if ( i == 0 && m_vecPlayers.Count() > 1 ) + { + sName.Format( "%s (leader)", m_vecPlayers[i].m_sName.String() ); // !FIXME! Localize + } + else + { + sName = m_vecPlayers[i].m_sName; + } + pKeyValues->SetString( "name", sName ); + + pKeyValues->SetString( "steamid", CFmtStr( "%llu", m_vecPlayers[i].m_steamID.ConvertToUint64() ).Access() ); + pKeyValues->SetInt( "badge_level", Max( 1U, m_vecPlayers[i].m_nBadgeLevel ) ); + + ApplyChatUserSettings( m_vecPlayers[ i ], pKeyValues ); + + pKeyValues->SetInt( "is_banned", ( m_vecPlayers[i].m_bIsBanned || m_vecPlayers[i].m_bIsLowPriority ) ? m_iImageIsBanned : 0 ); + + // See if the avatar's changed + int iAvatar = steamapicontext->SteamFriends()->GetSmallFriendAvatar( m_vecPlayers[i].m_steamID ); + int iIndex = m_mapAvatarsToImageList.Find( iAvatar ); + if ( iIndex == m_mapAvatarsToImageList.InvalidIndex() ) + { + CAvatarImage *pImage = new CAvatarImage(); + pImage->SetAvatarSteamID( m_vecPlayers[i].m_steamID ); + pImage->SetDrawFriend( false ); // you can only invite friends, this isn't that useful + pImage->SetAvatarSize( m_iAvatarWidth, m_iAvatarWidth ); + int iImageIndex = m_pImageList->AddImage( pImage ); + + iIndex = m_mapAvatarsToImageList.Insert( iAvatar, iImageIndex ); + } + pKeyValues->SetInt( "avatar", m_mapAvatarsToImageList[iIndex] ); + CAvatarImage *pAvIm = (CAvatarImage *)m_pImageList->GetImage( m_mapAvatarsToImageList[iIndex] ); + pAvIm->UpdateFriendStatus(); + m_vecPlayers[i].m_pAvatarImage = pAvIm; + if ( bLadderGame ) + { + if ( !m_vecPlayers[i].m_bHasCompetitiveAccess ) + { + pKeyValues->SetInt( "has_competitive_access", m_iImageNo ); + } + } + + pKeyValues->SetInt( "ladder_rank", m_vecPlayers[i].m_unLadderRank ); + pKeyValues->SetInt( "experience_level", m_vecPlayers[i].m_unExperienceLevel ); + + int itemID = m_pChatPlayerList->AddItem( 0, pKeyValues ); + m_pChatPlayerList->SetItemFont( itemID, m_fontPlayerListItem ); + m_pChatPlayerList->SetItemFgColor( itemID, ( m_vecPlayers[i].m_bIsBanned || m_vecPlayers[i].m_bIsLowPriority ) ? s_colorBannedPlayerListItem : s_colorPlayerListItem ); + + pKeyValues->deleteThis(); + } + + // force the list to PerformLayout() now so we can update our medal images + m_pChatPlayerList->InvalidateLayout( true ); + + int iPanelCount = 0; + // This only works in 6v6 Comp and 12v12 Casual for now + if ( ( GetMatchGroup() == k_nMatchGroup_Ladder_6v6 ) || ( GetMatchGroup() == k_nMatchGroup_Casual_12v12 ) ) + { + int nColumn = m_pChatPlayerList->GetColumnIndexByName( 0, "rank" ); + + for ( int nRow = 0; nRow < m_pChatPlayerList->GetItemCount(); nRow++ ) + { + KeyValues *pKeyValues = m_pChatPlayerList->GetItemData( nRow ); + if ( !pKeyValues ) + continue; + + CSteamID steamID = SteamIDFromDecimalString( pKeyValues->GetString( "steamid", "0" ) ); + if ( !steamID.IsValid() ) + continue; + + uint32 unLevel = pKeyValues->GetInt( "ladder_rank" ); + if ( GetMatchGroup() == k_nMatchGroup_Casual_12v12 ) + { + unLevel = pKeyValues->GetInt( "experience_level" ); + } + + // Create a panel if we need one + if ( iPanelCount >= m_vecChatBadges.Count() ) + { + m_vecChatBadges.AddToTail(); + m_vecChatBadges[iPanelCount].m_pBadgeModel = vgui::SETUP_PANEL( new CTFBadgePanel( m_pChatPlayerList, "Model" ) ); + m_vecChatBadges[iPanelCount].m_pBadgeModel->SetZPos( 9999 ); + m_vecChatBadges[iPanelCount].m_nShownLevel = 0u; + } + + // Move it into place and resize. This is terrible, but VGUI has forced my hand + int nX, nY, nWide, nTall; + m_pChatPlayerList->GetMaxCellBounds( nRow, nColumn, nX, nY, nWide, nTall ); + int nSideLength = Max( nWide, nTall ); + nX = ( nX + ( nWide / 2 ) ) - ( nSideLength / 2 ); + nY = ( nY + ( nTall / 2 ) ) - ( nSideLength / 2 ); + + m_vecChatBadges[iPanelCount].m_pBadgeModel->SetBounds( nX, nY, nSideLength, nSideLength ); + + // Different dude in the slot or their level is different? Update the medal model + if ( m_vecChatBadges[iPanelCount].m_steamIDOwner != steamID || + m_vecChatBadges[iPanelCount].m_nShownLevel != unLevel ) + { + m_vecChatBadges[iPanelCount].m_steamIDOwner = steamID; + + const LevelInfo_t& level = pMatchDesc->m_pProgressionDesc->GetLevelByNumber( unLevel ); + m_vecChatBadges[iPanelCount].m_pBadgeModel->SetupBadge( pMatchDesc->m_pProgressionDesc, level ); + + wchar_t wszOutString[128]; + char szLocalized[512]; + wchar_t wszCount[16]; + _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", level.m_nLevelNum ); + const wchar_t *wpszFormat = g_pVGuiLocalize->Find( pMatchDesc->m_pProgressionDesc->m_pszLevelToken ); + g_pVGuiLocalize->ConstructString_safe( wszOutString, wpszFormat, 2, wszCount, g_pVGuiLocalize->Find( level.m_pszLevelTitle ) ); + g_pVGuiLocalize->ConvertUnicodeToANSI( wszOutString, szLocalized, sizeof( szLocalized ) ); + + m_vecChatBadges[iPanelCount].m_pBadgeModel->SetTooltip( m_pToolTip, szLocalized ); + m_vecChatBadges[iPanelCount].m_nShownLevel = level.m_nLevelNum; + m_vecChatBadges[iPanelCount].m_pBadgeModel->InvalidateLayout( true, true ); + m_vecChatBadges[iPanelCount].m_pBadgeModel->SetVisible( true ); + } + + iPanelCount++; + } + } + + for ( ; iPanelCount < m_vecChatBadges.Count(); ++iPanelCount ) + { + m_vecChatBadges[iPanelCount].m_pBadgeModel->SetVisible( false ); + m_vecChatBadges[iPanelCount].m_nShownLevel = 0u; // Will cause the badge to refresh when it gets a player + } +} + + +//----------------------------------------------------------------------------- +void CBaseLobbyPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( GetResFile() ); + + m_pSearchActiveGroupBox = dynamic_cast<vgui::EditablePanel *>(FindChildByName( "SearchActiveGroupBox", true )); Assert( m_pSearchActiveGroupBox ); + m_pSearchActiveTitleLabel = dynamic_cast<vgui::Label *>(FindChildByName( "SearchActiveTitle", true )); Assert( m_pSearchActiveTitleLabel ); + m_pSearchActivePenaltyLabel = dynamic_cast<vgui::Label *>( FindChildByName( "PartyHasLowPriorityLabel", true ) ); Assert( m_pSearchActivePenaltyLabel ); + + m_pPartyHasLowPriority = dynamic_cast<vgui::EditablePanel *>( FindChildByName( "PartyHasLowPriorityGroupBox", true ) ); Assert( m_pPartyHasLowPriority ); + + m_pJoinLateCheckButton = dynamic_cast<vgui::CheckButton *>(FindChildByName( "JoinLateCheckButton", true )); Assert( m_pJoinLateCheckButton ); + m_pJoinLateValueLabel = dynamic_cast<vgui::Label *>(FindChildByName( "JoinLateValueLabel", true )); Assert( m_pJoinLateValueLabel ); + + m_pInviteButton = dynamic_cast<vgui::Button *>(FindChildByName( "InviteButton", true )); Assert( m_pInviteButton ); + + delete m_pImageList; + m_pImageList = new vgui::ImageList( false ); + + m_iImageIsBanned = m_pImageList->AddImage( vgui::scheme()->GetImage( "pve/mvm_timeout_active", true ) ); + m_pImageList->GetImage( m_iImageIsBanned )->SetSize( m_iBannedWidth, m_iBannedWidth ); + m_iImageCheckBoxDisabled = m_pImageList->AddImage( vgui::scheme()->GetImage( "pve/mvm_check_box_disabled", true ) ); + m_pImageList->GetImage( m_iImageCheckBoxDisabled )->SetSize( m_iChallengeCheckBoxWidth, m_iChallengeCheckBoxWidth * ( 3.75f / 4.0f ) ); + m_iImageCheckBoxYes = m_pImageList->AddImage( vgui::scheme()->GetImage( "pve/mvm_check_box_yes", true ) ); + m_pImageList->GetImage( m_iImageCheckBoxYes )->SetSize( m_iChallengeCheckBoxWidth, m_iChallengeCheckBoxWidth * ( 3.75f / 4.0f ) ); + m_iImageCheckBoxNo = m_pImageList->AddImage( vgui::scheme()->GetImage( "pve/mvm_check_box_no", true ) ); + m_pImageList->GetImage( m_iImageCheckBoxNo )->SetSize( m_iChallengeCheckBoxWidth, m_iChallengeCheckBoxWidth * ( 3.75f / 4.0f ) ); + m_iImageCheckBoxMixed = m_pImageList->AddImage( vgui::scheme()->GetImage( "pve/mvm_check_box_mixed", true ) ); + m_pImageList->GetImage( m_iImageCheckBoxMixed )->SetSize( m_iChallengeCheckBoxWidth, m_iChallengeCheckBoxWidth * ( 3.75f / 4.0f ) ); + m_iImageRadioButtonYes = m_pImageList->AddImage( vgui::scheme()->GetImage( "pve/mvm_radio_button_yes", true ) ); + m_pImageList->GetImage( m_iImageRadioButtonYes )->SetSize( m_iChallengeCheckBoxWidth, m_iChallengeCheckBoxWidth * ( 3.75f / 4.0f ) ); + m_iImageRadioButtonNo = m_pImageList->AddImage( vgui::scheme()->GetImage( "pve/mvm_radio_button_no", true ) ); + m_pImageList->GetImage( m_iImageRadioButtonNo )->SetSize( m_iChallengeCheckBoxWidth, m_iChallengeCheckBoxWidth * ( 3.75f / 4.0f ) ); + m_iImageNew = m_pImageList->AddImage( vgui::scheme()->GetImage( "new", true ) ); + m_pImageList->GetImage( m_iImageNew )->SetSize( m_iNewWidth, m_iNewWidth * ( 3.75f / 4.0f ) ); + + m_iImageNo = m_pImageList->AddImage( vgui::scheme()->GetImage( "hud/vote_no", true ) ); + + m_mapAvatarsToImageList.RemoveAll(); + m_pChatPlayerList->SetImageList( m_pImageList, false ); + m_pChatPlayerList->SetVisible( true ); + + + // + // Populate the challenge list + // + m_pJoinLateCheckButton->AddActionSignalTarget( this ); + m_pInviteButton->AddActionSignalTarget( this ); + m_pChatPlayerList->AddActionSignalTarget( this ); + + m_fontPlayerListItem = pScheme->GetFont( "DefaultSmall", true ); + + // + // Populate the player list + // + + m_pChatPlayerList->SetVerticalScrollbar( false ); + m_pChatPlayerList->RemoveAll(); + m_pChatPlayerList->RemoveAllSections(); + m_pChatPlayerList->AddSection( 0, "Players" ); + m_pChatPlayerList->SetSectionAlwaysVisible( 0, true ); + m_pChatPlayerList->SetSectionFgColor( 0, Color( 255, 255, 255, 255 ) ); + m_pChatPlayerList->SetBgColor( Color( 0, 0, 0, 0 ) ); + m_pChatPlayerList->SetBorder( NULL ); + m_pChatPlayerList->SetClickable( false ); + //m_pChatPlayerList->SetClickable( true ); // enable context menu to trade / kick? + + bool bPartyLeader = BIsPartyLeader() && GCClientSystem()->BConnectedtoGC(); + + if ( bPartyLeader ) + { + extern bool TF_IsHolidayActive( int eHoliday ); + bool bHalloween = TF_IsHolidayActive( kHoliday_Halloween ); + static bool bForcedOnce = false; + if ( bHalloween && !bForcedOnce ) + { + GTFGCClientSystem()->SetQuickplayGameType( kGameCategory_Event247 ); + bForcedOnce = true; + } + } + + if ( bPartyLeader ) + { + GTFGCClientSystem()->SetSearchJoinLate( tf_matchmaking_join_in_progress.GetBool() ); + } +} + +void CBaseLobbyPanel::PerformLayout() +{ + BaseClass::PerformLayout(); + + WriteGameSettingsControls(); + UpdatePlayerList(); +} + +void CBaseLobbyPanel::OnItemContextMenu( vgui::Panel* panel ) +{ + if ( m_iWritingPanel > 0 ) + return; + m_pChatTextEntry->RequestFocus(); + if ( panel == m_pChatPlayerList ) + { + OnClickedOnPlayer(); + return; + } +} + + +//----------------------------------------------------------------------------- +// Command to launch the lobby UI, connecting to a particular lobby +//----------------------------------------------------------------------------- +static void CL_ConnectLobby( const CCommand &args ) +{ + if ( args.ArgC() < 2 ) + { + Warning( "connect_lobby missing LobbyID argument\n" ); + return; + } + + uint64 ulSteamID = 0; + sscanf( args.Arg( 1 ), "%lld", &ulSteamID ); + CSteamID steamIDLobby( ulSteamID ); + if ( !steamIDLobby.IsValid() || !steamIDLobby.IsLobby() ) + { + Warning( "connect_lobby passed invalid LobbyID '%s'\n", args.Arg( 1 ) ); + return; + } + + GTFGCClientSystem()->AcceptFriendInviteToJoinLobby( steamIDLobby ); +} + +void OnSteamGameLobbyJoinRequested( GameLobbyJoinRequested_t *pInfo ); + +static ConCommand connect_lobby_command( "connect_lobby", &CL_ConnectLobby, "<64-bit lobby ID> Accept friend invite, connecting to specified Steam lobby and joining the corresponding search party" ); + +#endif // #ifdef ENABLE_GC_MATCHMAKING diff --git a/game/client/tf/vgui/tf_lobbypanel.h b/game/client/tf/vgui/tf_lobbypanel.h new file mode 100644 index 0000000..f7a998f --- /dev/null +++ b/game/client/tf/vgui/tf_lobbypanel.h @@ -0,0 +1,238 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TF_LOBBYPANEL_H +#define TF_LOBBYPANEL_H + + +#include "cbase.h" +//#include "tf_pvelobbypanel.h" +#include "game/client/iviewport.h" +#include "vgui_controls/TextEntry.h" +#include "tf_matchmaking_shared.h" +#include "vgui_controls/RichText.h" +#include "vgui_controls/CheckButton.h" +#include "tf_gc_client.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> +class CBaseLobbyContainerFrame; +class CAvatarImage; +class CTFBadgePanel; +class CMainMenuToolTip; + +class CBaseLobbyPanel : public vgui::PropertySheet, public CGameEventListener +{ + DECLARE_CLASS_SIMPLE( CBaseLobbyPanel, PropertySheet ); + + friend class CBaseLobbyContainerFrame; +public: + CBaseLobbyPanel( vgui::Panel *pParent, CBaseLobbyContainerFrame* pLobbyContainer ); + + virtual ~CBaseLobbyPanel(); + + // + // Panel overrides + // + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE; + virtual void PerformLayout() OVERRIDE; + + // + // CGameEventListener overrides + // + virtual void FireGameEvent( IGameEvent *event ) OVERRIDE; + + virtual void OnCommand( const char *command ) OVERRIDE; + + int NumPlayersInParty( void ) { return m_vecPlayers.Count(); } + + void ToggleJoinLateCheckButton( void ) { m_pJoinLateCheckButton->SetSelected( !m_pJoinLateCheckButton->IsSelected() );} + + bool IsPartyActiveGroupBoxVisible() { return m_pPartyActiveGroupBox != NULL && m_pPartyActiveGroupBox->IsVisible(); } + bool IsAnyoneBanned( RTime32 &rtimeExpire ) const; + bool IsAnyoneLowPriority( RTime32 &rtimeExpire ) const; + + void UpdateControls(); + + virtual EMatchGroup GetMatchGroup( void ) const = 0; + +protected: + + virtual void WriteGameSettingsControls(); + + MESSAGE_FUNC_PTR( OnItemLeftClick, "ItemLeftClick", panel ); + MESSAGE_FUNC_PTR( OnItemContextMenu, "ItemContextMenu", panel ); + MESSAGE_FUNC_PTR( OnCheckButtonChecked, "CheckButtonChecked", panel ); + MESSAGE_FUNC_PARAMS( OnTradeWithUser, "TradeWithUser", params ); + + CPanelAnimationVarAliasType( int, m_iNewWidth, "challenge_new_width", "19", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iChallengeCheckBoxWidth, "challenge_check_box_width", "8", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iAvatarWidth, "avatar_width", "16", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iPlayerNameWidth, "player_name_width", "110", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iBannedWidth, "squad_surplus_width", "12", "proportional_int" ); + + // Sectioned list panels are the worst + struct ChatModelPanel_t + { + CTFBadgePanel* m_pBadgeModel; + CSteamID m_steamIDOwner; + uint32 m_nShownLevel; + }; + + CUtlVector< ChatModelPanel_t > m_vecChatBadges; + vgui::SectionedListPanel *m_pChatPlayerList; + vgui::ImageList *m_pImageList; + CBaseLobbyContainerFrame *m_pContainer; + + int m_iWritingPanel; + + int m_iImageIsBanned; + int m_iImageNew; + int m_iImageNo; + int m_iImageRadioButtonYes; + int m_iImageRadioButtonNo; + int m_iImageCheckBoxDisabled; + int m_iImageCheckBoxYes; + int m_iImageCheckBoxNo; + int m_iImageCheckBoxMixed; + + struct LobbyPlayerInfo + { + CSteamID m_steamID; + CUtlString m_sName; + bool m_bHasTicket; + bool m_bSquadSurplus; + uint32 m_nBadgeLevel; + CAvatarImage *m_pAvatarImage; + uint32 m_nCompletedChallenges; // bitmask of badge slots (not related to the challenge index in the schema!) + bool m_bIsBanned; + RTime32 m_rtimeBanExpire; + RTime32 m_rtimeLowPriorityExpire; + bool m_bHasCompetitiveAccess; + uint32 m_unLadderRank; + bool m_bIsLowPriority; + uint32 m_unExperienceLevel; + }; + + const LobbyPlayerInfo* GetLobbyPlayerInfo( CSteamID &steamID ) const; + + virtual void OnThink() OVERRIDE + { + BaseClass::OnThink(); + WriteStatusControls(); + + if ( gpGlobals->curtime > m_flRefreshPlayerListTime ) + { + UpdatePlayerList(); + m_flRefreshPlayerListTime = gpGlobals->curtime + 1.f; + } + } + + +private: + + void SetMatchmakingModeBackground(); + virtual const char* GetResFile() const = 0; + virtual void ApplyChatUserSettings( const LobbyPlayerInfo& player, KeyValues* pSettings ) const = 0; + void OnClickedOnPlayer(); + + class ChatTextEntry : public vgui::TextEntry + { + public: + ChatTextEntry( vgui::Panel *parent, const char *name ) : vgui::TextEntry( parent, name ) + { + SetCatchEnterKey( true ); + SetAllowNonAsciiCharacters( true ); + SetDrawLanguageIDAtLeft( true ); + } + + virtual void OnKeyCodeTyped(vgui::KeyCode code) + { + if ( code == KEY_ENTER || code == KEY_PAD_ENTER ) + { + if ( GetTextLength() > 0 ) + { + int nBufSizeBytes = ( GetTextLength() + 4 ) * sizeof( wchar_t ); + wchar_t *wText = (wchar_t *)stackalloc( nBufSizeBytes ); + GetText( wText, nBufSizeBytes ); + TextEntry::SetText(""); + + // Convert to UTF8, which is really what we should + // use for everything + int nUtf8BufferSizeBytes = nBufSizeBytes * 2; + char *szText = (char *)stackalloc( nUtf8BufferSizeBytes ); + V_UnicodeToUTF8( wText, szText, nUtf8BufferSizeBytes ); + GTFGCClientSystem()->SendSteamLobbyChat( CTFGCClientSystem::k_eLobbyMsg_UserChat, szText ); + } + } + else if ( code == KEY_TAB ) + { + // Ignore tab, otherwise vgui will screw up the focus. + return; + } + else + { + vgui::TextEntry::OnKeyCodeTyped( code ); + } + } + }; + + class ChatLog : public vgui::RichText + { + public: + ChatLog( vgui::Panel *parent, const char *name ) : vgui::RichText( parent, name ) + { + } + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) + { + vgui::RichText::ApplySchemeSettings(pScheme); + + vgui::HFont hFont = pScheme->GetFont( "ChatMiniFont" ); + SetFont( hFont ); + } + }; + + CUtlVector<vgui::Label *> m_vecSearchCriteriaLabels; + + vgui::EditablePanel *m_pSearchActiveGroupBox; + vgui::Label *m_pSearchActiveTitleLabel; + vgui::Label *m_pSearchActivePenaltyLabel; + vgui::EditablePanel *m_pPartyHasLowPriority; + + vgui::CheckButton *m_pJoinLateCheckButton; + vgui::Label *m_pJoinLateValueLabel; + + vgui::EditablePanel *m_pPartyActiveGroupBox; + vgui::Button *m_pInviteButton; + ChatLog *m_pChatLog; + ChatTextEntry *m_pChatTextEntry; + + int m_iImageAvatars[MAX_PLAYERS+1]; + CUtlMap<int,int> m_mapAvatarsToImageList; + + vgui::HFont m_fontPlayerListItem; + + CSOTFParty_State m_eCurrentPartyState; + float m_flRefreshPlayerListTime; + + CMainMenuToolTip* m_pToolTip; + + void UpdatePlayerList(); + void WriteStatusControls(); // MM status + virtual bool ShouldShowLateJoin() const = 0; + + CUtlVector<LobbyPlayerInfo> m_vecPlayers; + + CCallback<CBaseLobbyPanel, PersonaStateChange_t, false> m_sPersonaStateChangedCallback; + + void OnPersonaStateChanged( PersonaStateChange_t *info ) + { + UpdatePlayerList(); + } +}; + +#endif //TF_LOBBYPANEL_H
\ No newline at end of file diff --git a/game/client/tf/vgui/tf_lobbypanel_casual.cpp b/game/client/tf/vgui/tf_lobbypanel_casual.cpp new file mode 100644 index 0000000..bbb0e54 --- /dev/null +++ b/game/client/tf/vgui/tf_lobbypanel_casual.cpp @@ -0,0 +1,621 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#include "cbase.h" + +#include "tf_party.h" +#include "vgui_controls/PropertySheet.h" +#include "vgui_controls/ScrollableEditablePanel.h" + +#include "iclientmode.h" +#include <vgui_controls/AnimationController.h> + +#include "tf_lobbypanel_casual.h" +#include "tf_lobby_container_frame_casual.h" +#include "tf_matchmaking_shared.h" + +#include "tf_hud_mainmenuoverride.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +extern Color s_colorChallengeForegroundEnabled; +extern Color s_colorChallengeForegroundDisabled; +extern Color s_colorChallengeHeader; + +extern const char *s_pszMatchGroups[]; + +ConVar tf_show_maps_details_explanation_session( "tf_show_maps_details_explanation_session", "0", FCVAR_HIDDEN ); +ConVar tf_show_maps_details_explanation_count( "tf_show_maps_details_explanation_count", "2", FCVAR_ARCHIVE | FCVAR_HIDDEN ); + +class CLobbyPanel_Casual; + +class CCasualCategory : public CExpandablePanel +{ + DECLARE_CLASS_SIMPLE( CCasualCategory, CExpandablePanel ); + +public: + CCasualCategory( Panel *parent, const char *panelName, EGameCategory eCategory, Panel* pSignalHandler ) + : BaseClass( parent, panelName ) + , m_eCategory( eCategory ) + , pToggleButton( NULL ) + , m_mapMapPanels( DefLessFunc( uint32 ) ) + , m_pSignalHandler( pSignalHandler ) + {} + + ~CCasualCategory() + { + // Clear out the old map entries + ClearMapEntries(); + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE + { + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "resource/ui/MatchmakingCategoryPanel.res" ); + + const SchemaGameCategory_t* pCategory = GetItemSchema()->GetGameCategory( m_eCategory ); + Assert( pCategory ); + if ( !pCategory ) + return; + + EditablePanel* pTopContainer = FindControl< EditablePanel >( "TopContainer", true ); + if ( pTopContainer ) + { + // Set our dialog variables + pTopContainer->SetDialogVariable( "title_token", g_pVGuiLocalize->Find( pCategory->m_pszLocalizedName ) ); + pTopContainer->SetDialogVariable( "desc_token", g_pVGuiLocalize->Find( pCategory->m_pszLocalizedDesc ) ); + } + + ImagePanel* pImagePanel = FindControl< ImagePanel >( "BGImage", true ); + if ( pImagePanel && pCategory && pCategory->m_pszListImage ) + { + pImagePanel->SetImage( pCategory->m_pszListImage ); + } + + // Clear out the old map entries + ClearMapEntries(); + + EditablePanel* pMapsContainer = FindControl< EditablePanel >( "MapsContainer", true ); + + if ( pMapsContainer ) + { + int nYPos = 16; + + FOR_EACH_VEC( pCategory->m_vecEnabledMaps, i ) + { + const MapDef_t* pMap = pCategory->m_vecEnabledMaps[ i ]; + + // Load control settings + EditablePanel* pMapEntry = new EditablePanel( pMapsContainer, "MatchmakingCategoryMapPanel" ); + pMapEntry->LoadControlSettings( "resource/ui/MatchmakingCategoryMapPanel.res" ); + pMapEntry->SetAutoDelete( false ); + + CExCheckButton* pCheckButton = pMapEntry->FindControl< CExCheckButton >( "MapCheckbutton" ); + if ( pCheckButton ) + { + KeyValues* pKVData = new KeyValues( "data" ); + pKVData->SetInt( "map_index", pMap->m_nDefIndex ); + pCheckButton->SetData( pKVData ); + pCheckButton->AddActionSignalTarget( m_pSignalHandler ); + } + + // Add to map so we can look it up + m_mapMapPanels.Insert( pMap->m_nDefIndex, pMapEntry ); + + // Update label + pMapEntry->SetDialogVariable( "title_token", g_pVGuiLocalize->Find( pMap->pszMapNameLocKey ) ); + + bool bOdd = i % 2 == 1; + int nXPos = bOdd ? GetWide() * 0.5f : 0; + pMapEntry->SetPos( nXPos, nYPos ); + + nYPos += bOdd || i == pCategory->m_vecEnabledMaps.Count() - 1 ? pMapEntry->GetTall() : 0; + } + + pMapsContainer->SetTall( nYPos + 10 ); + pMapsContainer->SetAutoResize( PIN_BOTTOMRIGHT, Panel::AUTORESIZE_NO, 0, 0, 0, 0 ); + + // We want to be able to expand to this height + m_nExpandedHeight = nYPos + m_nCollapsedHeight; + } + + // Snag the button for later + pToggleButton = FindControl< CExImageButton >( "EntryToggleButton", true ); + } + + virtual void OnToggleCollapse( bool bIsExpanded ) OVERRIDE + { + if ( bIsExpanded && !tf_show_maps_details_explanation_session.GetBool() && tf_show_maps_details_explanation_count.GetInt() > 0 ) + { + tf_show_maps_details_explanation_count.SetValue( tf_show_maps_details_explanation_count.GetInt() - 1 ); + tf_show_maps_details_explanation_session.SetValue( true ); + + // I dont care anymore + GetParent()->GetParent()->GetParent()->GetParent()->GetParent()->GetParent()->OnCommand( "show_maps_details_explanation" ); + } + + BaseClass::OnToggleCollapse( bIsExpanded ); + } + + virtual void PerformLayout() OVERRIDE + { + BaseClass::PerformLayout(); + + SetControlVisible( "EntryToggleButtonCollapsed", !BIsExpanded(), true ); + SetControlVisible( "EntryToggleButtonExpanded", BIsExpanded(), true ); + + if ( pToggleButton ) + { + pToggleButton->SetImageArmed( BIsExpanded() ? "/pve/sell_selected" : "/pve/buy_selected" ); + pToggleButton->SetImageDefault( BIsExpanded() ? "/pve/sell_disabled" : "/pve/buy_disabled" ); + } + + // Update progress bars + FOR_EACH_MAP_FAST( m_mapMapPanels, i ) + { + int nMapIndex = m_mapMapPanels.Key( i ); + auto healthData = GTFGCClientSystem()->GetHealthDataForMap( nMapIndex ); + + // Update bars with latest health data + ProgressBar* pProgress = m_mapMapPanels[ i ]->FindControl< ProgressBar >( "HealthProgressBar", true ); + if ( pProgress ) + { + pProgress->SetProgress( healthData.m_flRatio ); + pProgress->SetFgColor( healthData.m_colorBar ); + } + } + } + + void SetCheckButtonState( uint32 nMapDefIndex, bool bSelected, bool bClickable ) + { + auto idx = m_mapMapPanels.Find( nMapDefIndex ); + if ( idx != m_mapMapPanels.InvalidIndex() ) + { + EditablePanel* pMapEntry = m_mapMapPanels[ idx ]; + CExCheckButton* pMapCheckButton = pMapEntry->FindControl< CExCheckButton >( "MapCheckbutton", true ); + if ( pMapCheckButton ) + { + // Update button state without sending signals + pMapCheckButton->SetSilentMode( true ); + pMapCheckButton->SetCheckButtonCheckable( true ); + pMapCheckButton->SetSelected( bSelected ); + pMapCheckButton->SetCheckButtonCheckable( bClickable ); + pMapCheckButton->SetSilentMode( false ); + } + + if ( g_pClientMode && g_pClientMode->GetViewport() ) + { + if ( bSelected ) + { + g_pClientMode->GetViewportAnimationController()->StopAnimationSequence( pMapEntry, "HealthProgressBar_NotSelected" ); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pMapEntry, "HealthProgressBar_Selected" ); + } + else + { + g_pClientMode->GetViewportAnimationController()->StopAnimationSequence( pMapEntry, "HealthProgressBar_Selected" ); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pMapEntry, "HealthProgressBar_NotSelected" ); + } + } + } + } + +private: + void ClearMapEntries() + { + // Clear out the old map entries + FOR_EACH_MAP_FAST( m_mapMapPanels, i ) + { + m_mapMapPanels[ i ]->MarkForDeletion(); + } + m_mapMapPanels.Purge(); + } + + const EGameCategory m_eCategory; + CExImageButton* pToggleButton; + Panel* m_pSignalHandler; + CUtlMap< uint32, EditablePanel* > m_mapMapPanels; +}; + + +static void GetPlayerNameForSteamID( wchar_t *wCharPlayerName, int nBufSizeBytes, const CSteamID &steamID ) +{ + const char *pszName = steamapicontext->SteamFriends()->GetFriendPersonaName( steamID ); + V_UTF8ToUnicode( pszName, wCharPlayerName, nBufSizeBytes ); +} + +CLobbyPanel_Casual::CLobbyPanel_Casual( vgui::Panel *pParent, CBaseLobbyContainerFrame* pLobbyContainer ) + : CBaseLobbyPanel( pParent, pLobbyContainer ) + , m_fontCategoryListItem( 0 ) + , m_fontGroupHeader( 0 ) + , m_flCompetitiveRankProgress( -1.f ) + , m_flCompetitiveRankPrevProgress( -1.f ) + , m_flRefreshPlayerListTime( -1.f ) + , m_bCompetitiveRankChangePlayedSound( false ) + , m_mapGroupPanels( DefLessFunc( EMatchmakingGroupType ) ) + , m_mapCategoryPanels( DefLessFunc( EGameCategory ) ) + , m_flNextCasualStatsUpdateTime( 0.f ) + , m_bCriteriaDirty( true ) +{ + GTFGCClientSystem()->LoadCasualSearchCriteria(); + + ListenForGameEvent( "matchmaker_stats_updated" ); +} + +CLobbyPanel_Casual::~CLobbyPanel_Casual() +{ + delete m_pImageList; + m_pImageList = NULL; +} + +bool CLobbyPanel_Casual::ShouldShowLateJoin() const +{ + return false; // We force the option on, so no need to show the checkbox +} + +void CLobbyPanel_Casual::ApplyChatUserSettings( const CBaseLobbyPanel::LobbyPlayerInfo &player, KeyValues *pKV ) const +{ + pKV->SetInt( "has_ticket", 0 ); + pKV->SetInt( "squad_surplus", 0 ); +} + +void CLobbyPanel_Casual::WriteGameSettingsControls() +{ + BaseClass::WriteGameSettingsControls(); + + // Make sure we want to be in matchmaking. (If we don't, the frame should hide us pretty quickly.) + // We might get an event or something right at the transition point occasionally when the UI should + // not be visible + if ( GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_Inactive ) + { + return; + } + + ++m_iWritingPanel; + + TF_Matchmaking_WizardStep eWizardStep = GTFGCClientSystem()->GetWizardStep(); + + m_bCriteriaDirty = true; + + // competitive + SetControlVisible( "GameModesContainer", eWizardStep == TF_Matchmaking_WizardStep_CASUAL ); + + --m_iWritingPanel; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +EMatchGroup CLobbyPanel_Casual::GetMatchGroup( void ) const +{ + return k_nMatchGroup_Casual_12v12; // Force to 12v12 for now +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLobbyPanel_Casual::OnThink() +{ + BaseClass::OnThink(); + + if ( Plat_FloatTime() > m_flNextCasualStatsUpdateTime ) + { + m_flNextCasualStatsUpdateTime = Plat_FloatTime() + 60.f; + GTFGCClientSystem()->RequestMatchMakerStats(); + } + + if ( m_bCriteriaDirty ) + { + WriteCategories(); + } +} + +void CLobbyPanel_Casual::FireGameEvent( IGameEvent *event ) +{ + if ( FStrEq( event->GetName(), "matchmaker_stats_updated" ) ) + { + m_bCriteriaDirty = true; + + return; + } + + BaseClass::FireGameEvent( event ); +} + +//----------------------------------------------------------------------------- +void CLobbyPanel_Casual::PerformLayout() +{ + BaseClass::PerformLayout(); + + CHudMainMenuOverride *pMMOverride = (CHudMainMenuOverride*)( gViewPortInterface->FindPanelByName( PANEL_MAINMENUOVERRIDE ) ); + if ( pMMOverride ) + { + CLobbyContainerFrame_Casual *pCasualFrame = pMMOverride->GetCasualLobbyPanel(); + if ( pCasualFrame ) + { + CMainMenuToolTip *pToolTip = pCasualFrame->GetTooltipPanel(); + if ( pToolTip ) + { + CExImageButton *pButton = FindControl<CExImageButton>( "RestoreCasualSearchCriteria", true ); + if ( pButton ) + { + pButton->SetTooltip( pToolTip, "#TF_Casual_Tip_Restore" ); + } + + pButton = FindControl<CExImageButton>( "SaveCasualSearchCriteria", true ); + if ( pButton ) + { + pButton->SetTooltip( pToolTip, "#TF_Casual_Tip_Save" ); + } + } + } + } +} + +//----------------------------------------------------------------------------- +void CLobbyPanel_Casual::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + m_mapGroupPanels.Purge(); + m_mapCategoryPanels.Purge(); + + BaseClass::ApplySchemeSettings( pScheme ); + + int nAvatarWidth = ( ( m_iAvatarWidth * 5 / 4 ) + 1 ); + int nExtraWidth = ( m_pChatPlayerList->GetWide() - ( 2 * nAvatarWidth ) - m_iPlayerNameWidth - m_iBannedWidth ); + + m_pChatPlayerList->AddColumnToSection( 0, "avatar", "#TF_Players", vgui::SectionedListPanel::COLUMN_IMAGE, nAvatarWidth ); + m_pChatPlayerList->AddColumnToSection( 0, "name", "", 0, m_iPlayerNameWidth + nExtraWidth ); + m_pChatPlayerList->AddColumnToSection( 0, "is_banned", "", vgui::SectionedListPanel::COLUMN_IMAGE | vgui::SectionedListPanel::COLUMN_CENTER, m_iBannedWidth ); + m_pChatPlayerList->AddColumnToSection( 0, "rank", "", vgui::SectionedListPanel::COLUMN_IMAGE | vgui::SectionedListPanel::COLUMN_CENTER, nAvatarWidth ); + m_pChatPlayerList->SetDrawHeaders( false ); + m_fontCategoryListItem = pScheme->GetFont( "HudFontSmallest", true ); + m_fontGroupHeader = pScheme->GetFont( "HudFontSmallestBold", true ); +} + +void SelectCategory( EGameCategory eCategory, bool bSelected ) +{ + auto pCat = GetItemSchema()->GetGameCategory( eCategory ); + if ( pCat ) + { + FOR_EACH_VEC( pCat->m_vecEnabledMaps, i ) + { + if ( pCat->m_vecEnabledMaps[ i ] ) + { + GTFGCClientSystem()->SelectCasualMap( pCat->m_vecEnabledMaps[ i ]->m_nDefIndex, bSelected ); + } + } + } +} + +void SelectGroup( EMatchmakingGroupType eGroup, bool bSelected ) +{ + auto pGroup = GetItemSchema()->GetMMGroup( eGroup ); + if ( pGroup ) + { + FOR_EACH_VEC( pGroup->m_vecModes, i ) + { + SelectCategory( pGroup->m_vecModes[ i ]->m_eGameCategory, bSelected ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: A check box got checked! Search criteria +//----------------------------------------------------------------------------- +void CLobbyPanel_Casual::OnCheckButtonChecked( vgui::Panel* panel ) +{ + if ( m_iWritingPanel > 0 ) + return; + + CExCheckButton* pCheckButton = dynamic_cast< CExCheckButton* >( panel ); + if ( pCheckButton ) + { + bool bSelected = pCheckButton->IsSelected(); + + pCheckButton->RemoveActionSignalTarget( this ); + pCheckButton->SetSelected( false ); + pCheckButton->AddActionSignalTarget( this ); + + if ( BIsPartyLeader() && BIsPartyInUIState() ) + { + int nMapIndex = pCheckButton->GetData()->GetInt( "map_index", -1 ); + int nCategoryIndex = pCheckButton->GetData()->GetInt( "category_index", -1 ); + int nGroupIndex = pCheckButton->GetData()->GetInt( "group_index", -1 ); + Assert( nCategoryIndex >= 0 || nGroupIndex >= 0 || nMapIndex >= 0 ); + if ( nGroupIndex >= 0 ) + { + EMatchmakingGroupType eGroup = EMatchmakingGroupType( nGroupIndex ); + SelectGroup( eGroup, bSelected ); + } + else if ( nCategoryIndex >= 0 ) + { + EGameCategory eCategory = EGameCategory( nCategoryIndex ); + SelectCategory( eCategory, bSelected ); + } + else if ( nMapIndex >= 0 ) + { + GTFGCClientSystem()->SelectCasualMap( nMapIndex, bSelected ); + } + } + + m_bCriteriaDirty = true; + } + + BaseClass::OnCheckButtonChecked( panel ); +} + +//----------------------------------------------------------------------------- +// Purpose: Write all the category controls settings +//----------------------------------------------------------------------------- +void CLobbyPanel_Casual::WriteCategories( void ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + + if ( !m_bCriteriaDirty ) + return; + + m_bCriteriaDirty = false; + + bool bLeader = BIsPartyLeader(); + bool bInUIState = BIsPartyInUIState(); + bool bClickable = bLeader && bInUIState; + m_bHasAMapSelected = false; + + CScrollableList* pScrollableList = FindControl< CScrollableList >( "GameModesList", true ); + if ( !pScrollableList ) + return; + + CUtlVector< const MapDef_t* > vecSelectedMaps; + + pScrollableList->SetMouseInputEnabled( true ); + + const MMGroupMap_t& mapMMGroups = GetItemSchema()->GetMMGroupMap(); + + int nCategory = 0; + // Go through every MM group, and create a label for it and all its categories underneath + FOR_EACH_MAP( mapMMGroups, i ) + { + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - FOR_EACH_MAP( mapMMGroups, i )", __FUNCTION__ ); + + const SchemaMMGroup_t* pCat = mapMMGroups[ i ]; + + if ( !pCat->m_bitsValidMMGroups.IsBitSet( GetMatchGroup() ) ) + { + continue; + } + + if ( !pCat->IsCategoryValid() ) + { + continue; + } + + ++nCategory; + + bool bGroupSelected = false; + + EditablePanel* pGroupPanel = NULL; + CExCheckButton* pTitleLabel = NULL; + auto idx = m_mapGroupPanels.Find( pCat->m_eMMGroup ); + // Create the check button/label if not created yet + if ( idx == m_mapGroupPanels.InvalidIndex() ) + { + pGroupPanel = new EditablePanel( pScrollableList, "MatchmakingGroupPanel" ); + pGroupPanel->LoadControlSettings( "resource/ui/MatchMakingGroupPanel.res" ); + pTitleLabel = pGroupPanel->FindControl< CExCheckButton >( "Checkbutton" ); + pTitleLabel->SetText( pCat->m_pszLocalizedName ); + + KeyValues* pKVData = new KeyValues( "data" ); + pKVData->SetInt( "group_index", pCat->m_eMMGroup ); + pTitleLabel->SetData( pKVData ); + + pScrollableList->AddPanel( pGroupPanel, nCategory > 1 ? 2 : -4 ); + m_mapGroupPanels.Insert( pCat->m_eMMGroup, pGroupPanel ); + } + else + { + pGroupPanel = (EditablePanel*)m_mapGroupPanels[ idx ]; + pTitleLabel = pGroupPanel->FindControl< CExCheckButton >( "Checkbutton" ); + } + + // Category items. + FOR_EACH_VEC( pCat->m_vecModes, j ) + { + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - FOR_EACH_VEC( pCat->m_vecModes, j )", __FUNCTION__ ); + + const SchemaGameCategory_t* pCategory = pCat->m_vecModes[ j ]; + + if ( !pCategory->PassesRestrictions() ) + { + continue; + } + + CCasualCategory* pListEntry = NULL; + auto idxCat = m_mapCategoryPanels.Find( pCategory->m_eGameCategory ); + // Create the entry if it doesnt exist yet + if ( idxCat == m_mapCategoryPanels.InvalidIndex() ) + { + pListEntry = new CCasualCategory( pScrollableList, "MatchmakingCategoryPanel", pCategory->m_eGameCategory, this ); + pListEntry->MakeReadyForUse(); + + pScrollableList->AddPanel( pListEntry, j > 0 ? 5 : 0 ); + m_mapCategoryPanels.Insert( pCategory->m_eGameCategory, pListEntry ); + } + else + { + pListEntry = (CCasualCategory*)m_mapCategoryPanels[ idxCat ]; + } + + bool bCatSelected = false; + + FOR_EACH_VEC( pCategory->m_vecEnabledMaps, k ) + { + bool bMapSelected = GTFGCClientSystem()->IsCasualMapSelected( pCategory->m_vecEnabledMaps[ k ]->m_nDefIndex ); + m_bHasAMapSelected |= bMapSelected; + bCatSelected = bCatSelected | bMapSelected; + bGroupSelected = bGroupSelected | bCatSelected; + + // Update map check button state + pListEntry->SetCheckButtonState( pCategory->m_vecEnabledMaps[ k ]->m_nDefIndex, bMapSelected, bLeader ); + + // We're going to use this to setup the tooltip for total selected maps + if ( bMapSelected ) + { + vecSelectedMaps.AddToTail( pCategory->m_vecEnabledMaps[ k ] ); + } + } + + if ( bCatSelected ) + { + g_pClientMode->GetViewportAnimationController()->StopAnimationSequence( pListEntry, "CasualCategory_NotSelected" ); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pListEntry, "CasualCategory_Selected" ); + } + else + { + g_pClientMode->GetViewportAnimationController()->StopAnimationSequence( pListEntry, "CasualCategory_Selected" ); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pListEntry, "CasualCategory_NotSelected" ); + } + + pListEntry->InvalidateLayout(); + + // Update the check button within the list entry + CExCheckButton* pCheckButton = pListEntry->FindControl< CExCheckButton >( "CheckButton", true ); + if ( pCheckButton ) + { + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - if ( pCheckButton )", __FUNCTION__ ); + + pCheckButton->RemoveActionSignalTarget( this ); // So we dont endlessly loop by checking + pCheckButton->SetCheckButtonCheckable( true ); // So we can potentially check it on the next line + pCheckButton->SetSelected( bCatSelected ); + pCheckButton->SetCheckButtonCheckable( bLeader ); + pCheckButton->AddActionSignalTarget( this ); // So that we get user check messages + + KeyValues* pKVData = new KeyValues( "data" ); + pKVData->SetInt( "category_index", pCategory->m_eGameCategory ); + pCheckButton->SetData( pKVData ); + pCheckButton->SetMouseInputEnabled( bClickable ); + } + } + + // Update check button state + pTitleLabel->RemoveActionSignalTarget( this ); + pTitleLabel->SetCheckButtonCheckable( true ); + pTitleLabel->SetSelected( bGroupSelected ); + pTitleLabel->SetCheckButtonCheckable( bLeader ); + pTitleLabel->AddActionSignalTarget( this ); + pGroupPanel->SetMouseInputEnabled( bClickable ); + pTitleLabel->SetMouseInputEnabled( bClickable ); // Update clickability + } + + EditablePanel* pPlayListPanel = FindControl< EditablePanel >( "PlaylistBGPanel", true ); + if ( pPlayListPanel ) + { + // Setup the "X maps selected" label + const char* pszToken = vecSelectedMaps.Count() == 1 ? "TF_Casual_SelectedMaps_Singular" : "TF_Casual_SelectedMaps_Plural"; + pPlayListPanel->SetDialogVariable( "selected_maps_count", LocalizeNumberWithToken( pszToken, vecSelectedMaps.Count() ) ); + } + + m_pContainer->SetNextButtonEnabled( m_bHasAMapSelected ); +} diff --git a/game/client/tf/vgui/tf_lobbypanel_casual.h b/game/client/tf/vgui/tf_lobbypanel_casual.h new file mode 100644 index 0000000..e7c882c --- /dev/null +++ b/game/client/tf/vgui/tf_lobbypanel_casual.h @@ -0,0 +1,77 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#ifndef TF_LOBBYPANEL_CASUAL_H +#define TF_LOBBYPANEL_CASUAL_H + +#include "cbase.h" +#include "game/client/iviewport.h" +#include "tf_lobbypanel.h" +#include "tf_leaderboardpanel.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + + +class CBaseLobbyPanel; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CLobbyPanel_Casual : public CBaseLobbyPanel +{ + DECLARE_CLASS_SIMPLE( CLobbyPanel_Casual, CBaseLobbyPanel ); + +public: + CLobbyPanel_Casual( vgui::Panel *pParent, CBaseLobbyContainerFrame* pLobbyContainer ); + virtual ~CLobbyPanel_Casual(); + + // + // Panel overrides + // + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE; + virtual void PerformLayout( void ) OVERRIDE; + + virtual EMatchGroup GetMatchGroup( void ) const OVERRIDE; + + virtual void OnThink() OVERRIDE; + + virtual void FireGameEvent( IGameEvent *event ) OVERRIDE; + +private: + + MESSAGE_FUNC_PTR( OnCheckButtonChecked, "CheckButtonChecked", panel ); + + CPanelAnimationVarAliasType( int, m_iCategorySpacer, "category_spacer", "4", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iCategoryNameWidth, "category_name_width", "190", "proportional_int" ); + + virtual bool ShouldShowLateJoin() const OVERRIDE; + virtual void ApplyChatUserSettings( const LobbyPlayerInfo &player,KeyValues *pKV ) const OVERRIDE; + virtual const char* GetResFile() const OVERRIDE { return "Resource/UI/LobbyPanel_Casual.res"; } + + void WriteGameSettingsControls() OVERRIDE; + + void WriteCategories( void ); + + CUtlVector<vgui::Label *> m_vecSearchCriteriaLabels; + + vgui::HFont m_fontCategoryListItem; + vgui::HFont m_fontGroupHeader; + + float m_flCompetitiveRankProgress; + float m_flCompetitiveRankPrevProgress; + float m_flRefreshPlayerListTime; + bool m_bCompetitiveRankChangePlayedSound; + float m_flNextCasualStatsUpdateTime; + + bool m_bHasAMapSelected; + + CUtlMap< EMatchmakingGroupType, Panel* > m_mapGroupPanels; + CUtlMap< EGameCategory, Panel* > m_mapCategoryPanels; + + bool m_bCriteriaDirty; +}; + +#endif //TF_LOBBYPANEL_COMP_H
\ No newline at end of file diff --git a/game/client/tf/vgui/tf_lobbypanel_comp.cpp b/game/client/tf/vgui/tf_lobbypanel_comp.cpp new file mode 100644 index 0000000..73577af --- /dev/null +++ b/game/client/tf/vgui/tf_lobbypanel_comp.cpp @@ -0,0 +1,826 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#include "cbase.h" + +#include "tf_party.h" +#include "vgui_controls/PropertySheet.h" +#include "vgui_controls/ScrollableEditablePanel.h" + +#include "tf_lobbypanel_comp.h" +#include "tf_lobby_container_frame_comp.h" + +#include "vgui/ISystem.h" + +#include "tf_streams.h" +#include "tf_badge_panel.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +ConVar tf_mm_ladder_ui_last_rating_change( "tf_mm_ladder_ui_last_rating_change", "0", FCVAR_HIDDEN | FCVAR_ARCHIVE, "Track last match skillrating change for UI." ); +ConVar tf_mm_ladder_ui_last_rating_time( "tf_mm_ladder_ui_last_rating_time", "-1", FCVAR_HIDDEN | FCVAR_ARCHIVE, "Track last match skillrating change time for UI." ); + +class CLobbyPanel_Comp; + +#include "iclientmode.h" +#include <vgui_controls/AnimationController.h> + +class CMatchHistoryEntryPanel : public CExpandablePanel +{ +public: + DECLARE_CLASS_SIMPLE( CMatchHistoryEntryPanel, CExpandablePanel ); + CMatchHistoryEntryPanel( Panel* pParent, const char *pszPanelname ) + : BaseClass( pParent, pszPanelname ) + {} + + virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE + { + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "resource/ui/MatchHistoryEntryPanel.res" ); + } + + void SetMatchData( const CSOTFMatchResultPlayerStats& stats ) + { + EditablePanel* pContainer = FindControl< EditablePanel >( "Container" ); + if ( !pContainer ) + return; + + // Match date + CRTime matchdate( stats.endtime() ); + char rtime_buf[k_RTimeRenderBufferSize]; + matchdate.Render( rtime_buf ); + pContainer->SetDialogVariable( "match_date", rtime_buf ); + + // Map name + const MapDef_t* pMapDef = GetItemSchema()->GetMasterMapDefByIndex( stats.map_index() ); + const char* pszMapToken = "#TF_Map_Unknown"; + if ( pMapDef ) + { + pszMapToken = pMapDef->pszMapNameLocKey; + } + pContainer->SetDialogVariable( "map_name", g_pVGuiLocalize->Find( pszMapToken ) ); + + // KD ratio + float flKDRatio = stats.kills(); + if ( stats.deaths() > 0 ) + { + flKDRatio /= (float)stats.deaths(); + } + pContainer->SetDialogVariable( "kd_ratio", CFmtStr( "%.1f", flKDRatio ) ); + + pContainer->SetControlVisible( "WinLabel", stats.display_rating_change() > 0 ); + pContainer->SetControlVisible( "LossLabel", stats.display_rating_change() < 0 ); + + EditablePanel* pStatsContainer = FindControl< EditablePanel >( "SlidingStatsContainer", true ); + if ( pStatsContainer ) + { + pStatsContainer->SetDialogVariable( "stat_kills", LocalizeNumberWithToken( "TF_Competitive_Kills", stats.kills() ) ); + pStatsContainer->SetDialogVariable( "stat_deaths", LocalizeNumberWithToken( "TF_Competitive_Deaths", stats.deaths() ) ); + pStatsContainer->SetDialogVariable( "stat_damage", LocalizeNumberWithToken( "TF_Competitive_Damage", stats.damage() ) ); + pStatsContainer->SetDialogVariable( "stat_healing", LocalizeNumberWithToken( "TF_Competitive_Healing", stats.healing() ) ); + pStatsContainer->SetDialogVariable( "stat_support", LocalizeNumberWithToken( "TF_Competitive_Support", stats.support() ) ); + pStatsContainer->SetDialogVariable( "stat_score", LocalizeNumberWithToken( "TF_Competitive_Score", stats.score() ) ); + + ScalableImagePanel* pMapImage = pStatsContainer->FindControl< ScalableImagePanel >( "BGImage", true ); + if ( pMapImage && pMapDef ) + { + char imagename[ 512 ]; + Q_snprintf( imagename, sizeof( imagename ), "..\\vgui\\maps\\menu_thumb_%s", pMapDef->pszMapName ); + pMapImage->SetImage( imagename ); + } + + // Lambdas, wherefore art thou... + SetupClassIcon( pStatsContainer, "scout", TF_CLASS_SCOUT, stats ); + SetupClassIcon( pStatsContainer, "soldier", TF_CLASS_SOLDIER, stats ); + SetupClassIcon( pStatsContainer, "pyro", TF_CLASS_PYRO, stats ); + SetupClassIcon( pStatsContainer, "demo", TF_CLASS_DEMOMAN, stats ); + SetupClassIcon( pStatsContainer, "heavy", TF_CLASS_HEAVYWEAPONS, stats ); + SetupClassIcon( pStatsContainer, "engineer", TF_CLASS_ENGINEER, stats ); + SetupClassIcon( pStatsContainer, "medic", TF_CLASS_MEDIC, stats ); + SetupClassIcon( pStatsContainer, "sniper", TF_CLASS_SNIPER, stats ); + SetupClassIcon( pStatsContainer, "spy", TF_CLASS_SPY, stats ); + + SetupMedalForStat( pStatsContainer, stats.kills_medal(), "kills" ); + SetupMedalForStat( pStatsContainer, stats.damage_medal(), "damage" ); + SetupMedalForStat( pStatsContainer, stats.healing_medal(), "healing" ); + SetupMedalForStat( pStatsContainer, stats.support_medal(), "support" ); + SetupMedalForStat( pStatsContainer, stats.score_medal(), "score" ); + } + } +private: + + void SetupMedalForStat( EditablePanel* pParent, int nStat, const char* pszStatName ) + { + ScalableImagePanel* pMedalImage = pParent->FindControl< ScalableImagePanel >( CFmtStr( "%sMedal", pszStatName ) ); + if ( pMedalImage ) + { + if ( nStat != StatMedal_None ) + { + pMedalImage->SetImage( g_pszCompetitiveMedalImages[ nStat ] ); + } + pMedalImage->SetVisible( nStat != StatMedal_None ); + } + } + + void SetupClassIcon( EditablePanel* pParent, const char* pszClassName, int nBit, const CSOTFMatchResultPlayerStats& stats ) + { + ScalableImagePanel* pClassIcon = pParent->FindControl< ScalableImagePanel >( CFmtStr( "%sIcon", pszClassName ), true ); + if( pClassIcon ) + { + pClassIcon->SetImage( stats.classes_played() & (1<<nBit) ? CFmtStr( "class_icons/filter_%s_on", pszClassName ) : CFmtStr( "class_icons/filter_%s", pszClassName ) ); + } + } +}; + +DECLARE_BUILD_FACTORY( CMatchHistoryEntryPanel ); + +extern Color s_colorChallengeHeader; + +DECLARE_BUILD_FACTORY( CLadderLobbyLeaderboard ); +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CLadderLobbyLeaderboard::CLadderLobbyLeaderboard( Panel *pParent, const char *pszPanelName ) + : CTFLeaderboardPanel( pParent, pszPanelName ) +{ + m_pScoreList = new vgui::EditablePanel( this, "ScoreList" ); + m_pScoreListScroller = new vgui::ScrollableEditablePanel( this, m_pScoreList, "ScoreListScroller" ); + m_pScoreListScroller->AddActionSignalTarget( this ); + + m_pszLeaderboardName = "tf2_ladder_6v6"; + + for ( int i = 0; i < 100; ++i ) + { + vgui::EditablePanel *pEntryUI = new vgui::EditablePanel( m_pScoreList, "LeaderboardEntry" ); + m_vecLeaderboardEntries.AddToTail( pEntryUI ); + } + + m_pToolTip = new CTFTextToolTip( this ); + m_pToolTipEmbeddedPanel = new vgui::EditablePanel( this, "TooltipPanel" ); + m_pToolTipEmbeddedPanel->SetKeyBoardInputEnabled( false ); + m_pToolTipEmbeddedPanel->SetMouseInputEnabled( false ); + m_pToolTip->SetEmbeddedPanel( m_pToolTipEmbeddedPanel ); + m_pToolTip->SetTooltipDelay( 0 ); + + m_bIsDataValid = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLadderLobbyLeaderboard::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "Resource/UI/econ/LobbyLeaderboard.res" ); + + FOR_EACH_VEC( m_vecLeaderboardEntries, i ) + { + m_vecLeaderboardEntries[i]->ApplySchemeSettings( pScheme ); + m_vecLeaderboardEntries[i]->LoadControlSettings( "Resource/UI/LeaderboardEntryRank.res" ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLadderLobbyLeaderboard::PerformLayout() +{ + BaseClass::PerformLayout(); + + UpdateLeaderboards(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLadderLobbyLeaderboard::OnCommand( const char *command ) +{ + if ( Q_strnicmp( command, "stream", 6 ) == 0 ) + { + vgui::system()->ShellExecute( "open", command + 7 ); + return; + } + else if ( FStrEq( "global", command ) ) + { + if ( m_bGlobal != true ) + { + m_bGlobal = true; + UpdateLeaderboards(); + } + + return; + } + else if ( FStrEq( "local", command ) ) + { + if ( m_bGlobal == true ) + { + m_bGlobal = false; + UpdateLeaderboards(); + } + } + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CLadderLobbyLeaderboard::GetLeaderboardData( CUtlVector< LeaderboardEntry_t* >& scores ) +{ + CUtlVector< LeaderboardEntry_t* > vecLadderScores; + if ( Leaderboards_GetLadderLeaderboard( vecLadderScores, m_pszLeaderboardName, m_bGlobal ) ) + { + scores.AddVectorToTail( vecLadderScores ); + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CLadderLobbyLeaderboard::UpdateLeaderboards() +{ + CUtlVector< LeaderboardEntry_t* > scores; + m_bIsDataValid = GetLeaderboardData( scores ); + if ( !m_bIsDataValid ) + return false; + + int nScoreListHeight = scores.Count() * m_yEntryStep; + int nScrollerWidth, nScrollerHeight; + m_pScoreListScroller->GetSize( nScrollerWidth, nScrollerHeight ); + + m_pScoreList->SetSize( nScrollerWidth, Max( nScoreListHeight, nScrollerHeight ) ); + + m_pScoreList->InvalidateLayout( true ); + m_pScoreListScroller->InvalidateLayout( true ); + m_pScoreListScroller->GetScrollbar()->InvalidateLayout( true ); + static int nScrollWidth = m_pScoreListScroller->GetScrollbar()->GetWide(); + m_pScoreListScroller->GetScrollbar()->SetWide( nScrollWidth>>1 ); + if ( m_pScoreListScroller->GetScrollbar()->GetButton( 0 ) && + m_pScoreListScroller->GetScrollbar()->GetButton( 1 ) ) + { + m_pScoreListScroller->GetScrollbar()->GetButton( 0 )->SetVisible( false ); + m_pScoreListScroller->GetScrollbar()->GetButton( 1 )->SetVisible( false ); + } + + FOR_EACH_VEC( m_vecLeaderboardEntries, i ) + { + EditablePanel *pContainer = dynamic_cast< EditablePanel* >( m_vecLeaderboardEntries[i] ); + if ( !pContainer ) + continue; + + Color colorToUse = i % 2 == 1 ? m_OddTextColor : m_EvenTextColor; + + bool bIsEntryVisible = i < scores.Count(); + pContainer->SetVisible( bIsEntryVisible ); + pContainer->SetPos( 0, i * m_yEntryStep ); + if ( bIsEntryVisible ) + { + const LeaderboardEntry_t *pLeaderboardEntry = scores[i]; + const CSteamID &steamID = pLeaderboardEntry->m_steamIDUser; + +#ifdef TWITCH_LEADERBOARD + TwitchTvAccountInfo_t *pTwitchInfo = StreamManager()->GetTwitchTvAccountInfo( steamID.ConvertToUint64() ); + ETwitchTvState_t twitchState = pTwitchInfo ? pTwitchInfo->m_eTwitchTvState : k_ETwitchTvState_Error; + // still waiting for twitch info to load + if ( twitchState <= k_ETwitchTvState_Loading ) + return false; +#endif // TWITCH_LEADERBOARD + + bool bIsLocalPlayer = steamapicontext && steamapicontext->SteamUser() && steamapicontext->SteamUser()->GetSteamID() == steamID; + pContainer->SetDialogVariable( "position", m_bGlobal ? CFmtStr( "%d.", pLeaderboardEntry->m_nGlobalRank ) : "" ); + pContainer->SetDialogVariable( "username", InventoryManager()->PersonaName_Get( steamID.GetAccountID() ) ); + + CExLabel *pNameText = dynamic_cast< CExLabel* >( pContainer->FindChildByName( "UserName" ) ); + if ( pNameText ) + { + pNameText->SetColorStr( bIsLocalPlayer ? m_LocalPlayerTextColor : colorToUse ); + } + + CAvatarImagePanel *pAvatar = dynamic_cast< CAvatarImagePanel* >( pContainer->FindChildByName( "AvatarImage" ) ); + if ( pAvatar ) + { + pAvatar->SetShouldDrawFriendIcon( false ); + pAvatar->SetPlayer( steamID, k_EAvatarSize32x32 ); + } + + CTFBadgePanel *pRankImage = dynamic_cast< CTFBadgePanel* >( pContainer->FindChildByName( "RankImage" ) ); + if ( pRankImage ) + { + const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( k_nMatchGroup_Ladder_6v6 ); + const LevelInfo_t& levelInfo = pMatchDesc->m_pProgressionDesc->GetLevelForExperience( pLeaderboardEntry->m_nScore ); + pRankImage->SetupBadge( pMatchDesc->m_pProgressionDesc, levelInfo ); + + wchar_t wszOutString[ 128 ]; + char szLocalized[512]; + wchar_t wszCount[ 16 ]; + _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", levelInfo.m_nLevelNum ); + const wchar_t *wpszFormat = g_pVGuiLocalize->Find( pMatchDesc->m_pProgressionDesc->m_pszLevelToken ); + g_pVGuiLocalize->ConstructString_safe( wszOutString, wpszFormat, 2, wszCount, g_pVGuiLocalize->Find( levelInfo.m_pszLevelTitle ) ); + g_pVGuiLocalize->ConvertUnicodeToANSI( wszOutString, szLocalized, sizeof( szLocalized ) ); + + pRankImage->SetMouseInputEnabled( true ); + pRankImage->SetVisible( true ); + pRankImage->SetTooltip( m_pToolTip, szLocalized ); + } + + CExImageButton *pStreamImage = dynamic_cast< CExImageButton* >( pContainer->FindChildByName( "StreamImageButton" ) ); + if ( pStreamImage ) + { +#ifdef TWITCH_LEADERBOARD + if ( twitchState == k_ETwitchTvState_Linked ) + { + pStreamImage->SetVisible( true ); + pStreamImage->SetCommand( CFmtStr( "stream %s", pTwitchInfo->m_sTwitchTvChannel.String() ) ); + } + else +#endif // TWITCH_LEADERBOARD + { + pStreamImage->SetVisible( false ); + pStreamImage->SetCommand( "" ); + } + } + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLadderLobbyLeaderboard::SetLeaderboard( const char *pszLeaderboardName, bool bGlobal ) +{ + m_pszLeaderboardName = pszLeaderboardName; + m_bGlobal = bGlobal; + + UpdateLeaderboards(); +} + +static void GetPlayerNameForSteamID( wchar_t *wCharPlayerName, int nBufSizeBytes, const CSteamID &steamID ) +{ + const char *pszName = steamapicontext->SteamFriends()->GetFriendPersonaName( steamID ); + V_UTF8ToUnicode( pszName, wCharPlayerName, nBufSizeBytes ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CLobbyPanel_Comp::CLobbyPanel_Comp( vgui::Panel *pParent, CBaseLobbyContainerFrame* pLobbyContainer ) + : CBaseLobbyPanel( pParent, pLobbyContainer ) + , m_pCompetitiveModeLeaderboard( NULL ) + , m_pMatchHistoryScroller( NULL ) + , m_eMatchSortMethod( SORT_BY_DATE ) + , m_bDescendingMatchHistorySort( true ) +{ + // Comp + m_fontMedalsCount = 0; + + m_flCompetitiveRankProgress = -1.f; + m_flCompetitiveRankPrevProgress = -1.f; + m_flRefreshPlayerListTime = -1.f; + m_bCompetitiveRankChangePlayedSound = false; + m_bMatchHistoryLoaded = false; + m_bMatchDataForLocalPlayerDirty = true; + + ListenForGameEvent( "gc_new_session" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CLobbyPanel_Comp::~CLobbyPanel_Comp() +{ + delete m_pImageList; + m_pImageList = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLobbyPanel_Comp::OnCommand( const char *command ) +{ + + if ( FStrEq( command, "medals_help" ) ) + { + CExplanationPopup *pPopup = dynamic_cast< CExplanationPopup* >( GetParent()->FindChildByName( "MedalsHelp" ) ); + if ( pPopup ) + { + pPopup->Popup(); + } + return; + } + else if ( FStrEq( command, "show_leaderboards" ) ) + { + if ( m_pCompetitiveModeLeaderboard ) + { + m_pCompetitiveModeLeaderboard->SetVisible( true ); + } + + SetControlVisible( "MatchHistoryCategories", false, true ); + SetControlVisible( "MatchHistoryContainer", false, true ); + + return; + } + else if ( FStrEq( command, "show_match_history" ) ) + { + m_bMatchDataForLocalPlayerDirty = true; + + if ( m_pCompetitiveModeLeaderboard ) + { + m_pCompetitiveModeLeaderboard->SetVisible( false ); + } + + SetControlVisible( "MatchHistoryCategories", true, true ); + SetControlVisible( "MatchHistoryContainer", true, true ); + + return; + } + else if ( !Q_strncmp( "sort", command, 4 ) ) + { + EMatchHistorySortMethods_t eNewMethod = (EMatchHistorySortMethods_t)atoi( command + 4 ); + + if ( eNewMethod == m_eMatchSortMethod ) + { + m_bDescendingMatchHistorySort = !m_bDescendingMatchHistorySort; + } + else + { + m_eMatchSortMethod = eNewMethod; + m_bDescendingMatchHistorySort = true; + } + + m_bMatchDataForLocalPlayerDirty = true; + + return; + } + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CLobbyPanel_Comp::ShouldShowLateJoin() const +{ + return false; // For now +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLobbyPanel_Comp::ApplyChatUserSettings( const CBaseLobbyPanel::LobbyPlayerInfo &player, KeyValues *pKV ) const +{ + pKV->SetInt( "has_ticket", 0 ); + pKV->SetInt( "squad_surplus", 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLobbyPanel_Comp::WriteGameSettingsControls() +{ + BaseClass::WriteGameSettingsControls(); + + // Make sure we want to be in matchmaking. (If we don't, the frame should hide us pretty quickly.) + // We might get an event or something right at the transition point occasionally when the UI should + // not be visible + if ( GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_Inactive ) + { + return; + } + + ++m_iWritingPanel; + + m_pContainer->SetNextButtonEnabled( true ); + + SetControlVisible( "ScrollableContainer", GTFGCClientSystem()->GetWizardStep()== TF_Matchmaking_WizardStep_LADDER ); + + --m_iWritingPanel; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CLobbyPanel_Comp::GetMedalCountForStat( EMatchGroup unLadderType, RankStatType_t nStatType, int nMedalLevel ) +{ + CSOTFLadderData *pData = GetLocalPlayerLadderData( unLadderType ); + if ( !pData ) + return 0; + + switch ( nStatType ) + { + case RankStat_Score: + if ( nMedalLevel == StatMedal_Bronze ) + { + return pData->Obj().score_bronze(); + } + else if ( nMedalLevel == StatMedal_Silver ) + { + return pData->Obj().score_silver(); + } + else if ( nMedalLevel == StatMedal_Gold ) + { + return pData->Obj().score_gold(); + } + break; + case RankStat_Kills: + if ( nMedalLevel == StatMedal_Bronze ) + { + return pData->Obj().kills_bronze(); + } + else if ( nMedalLevel == StatMedal_Silver ) + { + return pData->Obj().kills_silver(); + } + else if ( nMedalLevel == StatMedal_Gold ) + { + return pData->Obj().kills_gold(); + } + break; + case RankStat_Damage: + if ( nMedalLevel == StatMedal_Bronze ) + { + return pData->Obj().damage_bronze(); + } + else if ( nMedalLevel == StatMedal_Silver ) + { + return pData->Obj().damage_silver(); + } + else if ( nMedalLevel == StatMedal_Gold ) + { + return pData->Obj().damage_gold(); + } + break; + case RankStat_Healing: + if ( nMedalLevel == StatMedal_Bronze ) + { + return pData->Obj().healing_bronze(); + } + else if ( nMedalLevel == StatMedal_Silver ) + { + return pData->Obj().healing_silver(); + } + else if ( nMedalLevel == StatMedal_Gold ) + { + return pData->Obj().healing_gold(); + } + break; + case RankStat_Support: + if ( nMedalLevel == StatMedal_Bronze ) + { + return pData->Obj().support_bronze(); + } + else if ( nMedalLevel == StatMedal_Silver ) + { + return pData->Obj().support_silver(); + } + else if ( nMedalLevel == StatMedal_Gold ) + { + return pData->Obj().support_gold(); + } + break; + case RankStat_Deaths: + // Not supported + break; + default: + Assert( 0 ); + } + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLobbyPanel_Comp::OnThink() +{ + BaseClass::OnThink(); + + if ( m_bMatchDataForLocalPlayerDirty ) + { + UpdateMatchDataForLocalPlayer(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLobbyPanel_Comp::FireGameEvent( IGameEvent *event ) +{ + const char *pszEventname = event->GetName(); + if ( !Q_stricmp( pszEventname, "gc_new_session" ) ) + { + // This is loaded on demand by the GC - if we have a new session, we need to re-request + if ( m_bMatchHistoryLoaded ) + { + m_bMatchHistoryLoaded = false; + m_bMatchDataForLocalPlayerDirty = true; + } + } + + BaseClass::FireGameEvent( event ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +EMatchGroup CLobbyPanel_Comp::GetMatchGroup( void ) const +{ + return k_nMatchGroup_Ladder_6v6; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLobbyPanel_Comp::SOCreated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) +{ + if ( pObject->GetTypeID() != CSOTFMatchResultPlayerInfo::k_nTypeID ) + return; + + m_bMatchDataForLocalPlayerDirty = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLobbyPanel_Comp::SOUpdated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) +{ + if ( pObject->GetTypeID() != CSOTFMatchResultPlayerInfo::k_nTypeID ) + return; + + m_bMatchDataForLocalPlayerDirty = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLobbyPanel_Comp::PerformLayout() +{ + BaseClass::PerformLayout(); + + m_bMatchDataForLocalPlayerDirty = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLobbyPanel_Comp::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + EditablePanel* pPlaylistBGPanel = FindControl< EditablePanel >( "PlaylistBGPanel", true ); + m_pCompetitiveModeLeaderboard = pPlaylistBGPanel->FindControl< CLadderLobbyLeaderboard >( "Leaderboard", true ); + + m_pMatchHistoryScroller = pPlaylistBGPanel->FindControl< CScrollableList >( "MatchHistoryContainer" ); + + int nAvatarWidth = ( ( m_iAvatarWidth * 5 / 4 ) + 1 ); + int nExtraWidth = ( m_pChatPlayerList->GetWide() - ( 2 * nAvatarWidth ) - m_iPlayerNameWidth - m_iBannedWidth - m_iHasPassWidth ); + + m_pChatPlayerList->AddColumnToSection( 0, "avatar", "#TF_Players", vgui::SectionedListPanel::COLUMN_IMAGE, nAvatarWidth ); + m_pChatPlayerList->AddColumnToSection( 0, "name", "", 0, m_iPlayerNameWidth + nExtraWidth ); + m_pChatPlayerList->AddColumnToSection( 0, "is_banned", "", vgui::SectionedListPanel::COLUMN_IMAGE | vgui::SectionedListPanel::COLUMN_CENTER, m_iBannedWidth ); + m_pChatPlayerList->AddColumnToSection( 0, "has_competitive_access", "", vgui::SectionedListPanel::COLUMN_IMAGE | vgui::SectionedListPanel::COLUMN_CENTER, m_iHasPassWidth ); + m_pChatPlayerList->AddColumnToSection( 0, "rank", "", vgui::SectionedListPanel::COLUMN_IMAGE | vgui::SectionedListPanel::COLUMN_CENTER, nAvatarWidth ); + m_pChatPlayerList->SetDrawHeaders( false ); + m_fontMedalsCount = pScheme->GetFont( "HudFontSmallestBold", true ); +} + +static int SortResult( const CSOTFMatchResultPlayerStats * a, const CSOTFMatchResultPlayerStats * b ) +{ + return a->display_rating_change() < b->display_rating_change(); +} + +static int SortDate( const CSOTFMatchResultPlayerStats * a, const CSOTFMatchResultPlayerStats * b ) +{ + return a->endtime() < b->endtime(); +} + +static int SortMap( const CSOTFMatchResultPlayerStats * a, const CSOTFMatchResultPlayerStats * b ) +{ + const MapDef_t* pMapDef = GetItemSchema()->GetMasterMapDefByIndex( a->map_index() ); + const wchar_t* pszAMapName = g_pVGuiLocalize->Find( pMapDef ? pMapDef->pszMapNameLocKey : "#TF_Map_Unknown" ); + + pMapDef = GetItemSchema()->GetMasterMapDefByIndex( b->map_index() ); + const wchar_t* pszBMapName = g_pVGuiLocalize->Find( pMapDef ? pMapDef->pszMapNameLocKey : "#TF_Map_Unknown" ); + + return wcscoll( pszAMapName, pszBMapName ); +} + +static int SortKDR( const CSOTFMatchResultPlayerStats * a, const CSOTFMatchResultPlayerStats * b ) +{ + float flAKDRatio = a->kills(); + if ( a->deaths() > 0 ) + { + flAKDRatio /= (float)a->deaths(); + } + + float flBKDRatio = b->kills(); + if ( b->deaths() > 0 ) + { + flBKDRatio /= (float)b->deaths(); + } + + return ( flBKDRatio - flAKDRatio ) * 1000.f; +} + +void CLobbyPanel_Comp::UpdateMatchDataForLocalPlayer() +{ + m_bMatchDataForLocalPlayerDirty = false; + + CUtlVector < CSOTFMatchResultPlayerStats > vecMatches; + GetLocalPlayerMatchHistory( GetMatchGroup(), vecMatches ); + + if ( !m_bMatchHistoryLoaded ) + { + GCSDK::CProtoBufMsg< CMsgGCMatchHistoryLoad > msg( k_EMsgGCMatchHistoryLoad ); + GCClientSystem()->BSendMessage( msg ); + + m_bMatchHistoryLoaded = true; + } + + if ( m_pMatchHistoryScroller ) + { + m_pMatchHistoryScroller->ClearAutoLayoutPanels(); + + typedef int (*MatchSortFunc)( const CSOTFMatchResultPlayerStats * a, const CSOTFMatchResultPlayerStats * b ); + + struct SortMethodData_t + { + MatchSortFunc m_pfnSort; + CExButton* m_pSortButton; + }; + + EditablePanel* pPlaylistBGPanel = FindControl< EditablePanel >( "PlaylistBGPanel", true ); + Assert( pPlaylistBGPanel ); + if ( !pPlaylistBGPanel ) + return; + + SortMethodData_t sortMethods[ NUM_SORT_METHODS ] = { { &SortResult, pPlaylistBGPanel->FindControl< CExButton >( "ResultButton", true ) } + , { &SortDate, pPlaylistBGPanel->FindControl< CExButton >( "DateButton", true ) } + , { &SortMap, pPlaylistBGPanel->FindControl< CExButton >( "MapButton", true ) } + , { &SortKDR, pPlaylistBGPanel->FindControl< CExButton >( "KDRButton", true ) } }; + + // Sort + vecMatches.Sort( sortMethods[ m_eMatchSortMethod ].m_pfnSort ); + + Label* pSortArrow = pPlaylistBGPanel->FindControl< Label >( "SortArrow", true ); + Assert( pSortArrow ); + if ( !pSortArrow ) + return; + + // Update controls + for( int i=0; i<ARRAYSIZE( sortMethods ); ++i ) + { + if( sortMethods[ i ].m_pSortButton ) + { + bool bSelected = i == m_eMatchSortMethod; + sortMethods[ i ].m_pSortButton->SetSelected( bSelected ); + + if ( bSelected ) + { + int nDummy, nX; + sortMethods[ i ].m_pSortButton->GetContentSize( nX, nDummy ); + // Move the sort arrow to the right edge of the selected panel + pSortArrow->SetPos( sortMethods[ i ].m_pSortButton->GetXPos() + nX , pSortArrow->GetYPos() ); + // Fixup the label to be an up or down arrow + pSortArrow->SetText( m_bDescendingMatchHistorySort ? L"6" : L"5" ); + } + } + } + + // Potentially go backwards + if ( m_bDescendingMatchHistorySort ) + { + FOR_EACH_VEC( vecMatches, i ) + { + CMatchHistoryEntryPanel* pMatchEntryPanel = new CMatchHistoryEntryPanel( m_pMatchHistoryScroller, "MatchEntry" ); + pMatchEntryPanel->MakeReadyForUse(); + pMatchEntryPanel->SetMatchData( vecMatches[ i ] ); + m_pMatchHistoryScroller->AddPanel( pMatchEntryPanel, 5 ); + } + } + else + { + FOR_EACH_VEC_BACK( vecMatches, i ) + { + CMatchHistoryEntryPanel* pMatchEntryPanel = new CMatchHistoryEntryPanel( m_pMatchHistoryScroller, "MatchEntry" ); + pMatchEntryPanel->MakeReadyForUse(); + pMatchEntryPanel->SetMatchData( vecMatches[ i ] ); + m_pMatchHistoryScroller->AddPanel( pMatchEntryPanel, 5 ); + } + } + + m_pMatchHistoryScroller->MakeReadyForUse(); + } +} diff --git a/game/client/tf/vgui/tf_lobbypanel_comp.h b/game/client/tf/vgui/tf_lobbypanel_comp.h new file mode 100644 index 0000000..1e0d843 --- /dev/null +++ b/game/client/tf/vgui/tf_lobbypanel_comp.h @@ -0,0 +1,139 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#ifndef TF_LOBBYPANEL_COMP_H +#define TF_LOBBYPANEL_COMP_H + +#include "cbase.h" +#include "game/client/iviewport.h" +#include "tf_lobbypanel.h" +#include "tf_leaderboardpanel.h" +#include "local_steam_shared_object_listener.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace GCSDK; + +class CBaseLobbyPanel; + +namespace vgui +{ + class ScrollableEditablePanel; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CLadderLobbyLeaderboard : public CTFLeaderboardPanel +{ + DECLARE_CLASS_SIMPLE( CLadderLobbyLeaderboard, CTFLeaderboardPanel ); +public: + + CLadderLobbyLeaderboard( Panel *pParent, const char *pszPanelName ); + + //----------------------------------------------------------------------------- + // Purpose: Create leaderboard panels + //----------------------------------------------------------------------------- + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE; + virtual void PerformLayout() OVERRIDE; + virtual void OnCommand( const char *command ) OVERRIDE; + + virtual bool GetLeaderboardData( CUtlVector< LeaderboardEntry_t* >& scores ); + virtual bool UpdateLeaderboards(); + + void SetLeaderboard( const char *pszLeaderboardName, bool bGlobal ); + + const char *GetLeaderboardName() const { return m_pszLeaderboardName; } + bool IsDataValid( void ) { return m_bIsDataValid; } + +private: + const char *m_pszLeaderboardName; + bool m_bGlobal; + bool m_bIsDataValid; + + vgui::ScrollableEditablePanel *m_pScoreListScroller; + EditablePanel *m_pScoreList; + + CTFTextToolTip *m_pToolTip; + vgui::EditablePanel *m_pToolTipEmbeddedPanel; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CLobbyPanel_Comp : public CBaseLobbyPanel, public CLocalSteamSharedObjectListener +{ + DECLARE_CLASS_SIMPLE( CLobbyPanel_Comp, CBaseLobbyPanel ); + +public: + CLobbyPanel_Comp( vgui::Panel *pParent, CBaseLobbyContainerFrame* pLobbyContainer ); + virtual ~CLobbyPanel_Comp(); + + // + // Panel overrides + // + virtual void PerformLayout() OVERRIDE; + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE; + virtual void OnCommand( const char *command ) OVERRIDE; + + virtual EMatchGroup GetMatchGroup( void ) const OVERRIDE; + + virtual void SOCreated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) OVERRIDE; + virtual void SOUpdated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) OVERRIDE; + + virtual void OnThink() OVERRIDE; + + // + // CGameEventListener overrides + // + virtual void FireGameEvent( IGameEvent *event ) OVERRIDE; + +private: + virtual bool ShouldShowLateJoin() const OVERRIDE; + virtual void ApplyChatUserSettings( const LobbyPlayerInfo &player,KeyValues *pKV ) const OVERRIDE; + virtual const char* GetResFile() const OVERRIDE { return "Resource/UI/LobbyPanel_Comp.res"; } + + CPanelAnimationVarAliasType( int, m_iStatMedalWidth, "stat_medal_width", "14", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iMedalCountWidth, "stat_medal_count_width", "20", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iHasPassWidth, "has_pass_width", "12", "proportional_int" ); + + CUtlVector<vgui::Label *> m_vecSearchCriteriaLabels; + + // leaderboards + CLadderLobbyLeaderboard *m_pCompetitiveModeLeaderboard; + + vgui::HFont m_fontMedalsCount; + + enum EMatchHistorySortMethods_t + { + SORT_BY_RESULT = 0, + SORT_BY_DATE, + SORT_BY_MAP, + SORT_BY_KDR, + + NUM_SORT_METHODS + }; + + CScrollableList* m_pMatchHistoryScroller; + EMatchHistorySortMethods_t m_eMatchSortMethod; + bool m_bDescendingMatchHistorySort; + + float m_flCompetitiveRankProgress; + float m_flCompetitiveRankPrevProgress; + float m_flRefreshPlayerListTime; + bool m_bCompetitiveRankChangePlayedSound; + bool m_bMatchHistoryLoaded; + + void WriteGameSettingsControls() OVERRIDE; + + int GetMedalCountForStat( EMatchGroup unLadderType, RankStatType_t nStatType, int nMedalLevel ); + + + void UpdateMatchDataForLocalPlayer(); + bool m_bMatchDataForLocalPlayerDirty; +}; + +#endif //TF_LOBBYPANEL_COMP_H diff --git a/game/client/tf/vgui/tf_lobbypanel_mvm.cpp b/game/client/tf/vgui/tf_lobbypanel_mvm.cpp new file mode 100644 index 0000000..2132da3 --- /dev/null +++ b/game/client/tf/vgui/tf_lobbypanel_mvm.cpp @@ -0,0 +1,1114 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "tf_gc_client.h" +#include "tf_party.h" + +#include "vgui_controls/PropertySheet.h" +#include "vgui_controls/SectionedListPanel.h" +#include "vgui_bitmapimage.h" +#include "vgui_avatarimage.h" +#include "store/store_panel.h" +#include <VGuiMatSurface/IMatSystemSurface.h> +#include <vgui_controls/ImageList.h> + +#include "tf_lobbypanel_mvm.h" +#include "tf_lobby_container_frame_mvm.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +extern ConVar tf_matchmaking_join_in_progress; +ConVar tf_matchmaking_ticket_help( "tf_matchmaking_ticket_help", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_ARCHIVE | FCVAR_HIDDEN, "Saved if the player has see the ticket help screen." ); + +const int k_iPopIndex_Any = -1000; +const int k_iPopIndex_OnlyNotYetCompleted = -1001; +const int k_iPopIndex_AnyNormal = -1002; +const int k_iPopIndex_AnyIntermediate = -1003; +const int k_iPopIndex_AnyAdvanced = -1004; +const int k_iPopIndex_AnyExpert = -1005; +const int k_iPopIndex_AnyHaunted = -1006; + +static void GetMvmChallengeSet( int idxChallenge, CMvMMissionSet &result ) +{ + result.Clear(); + + if ( idxChallenge >= 0 ) + { + result.SetMissionBySchemaIndex( idxChallenge, true ); + return; + } + + bool bMannUP = GTFGCClientSystem()->GetSearchPlayForBraggingRights(); +#ifdef USE_MVM_TOUR + int idxTour = GTFGCClientSystem()->GetSearchMannUpTourIndex(); + Assert( bMannUP || idxTour < 0 ); +#endif // USE_MVM_TOUR + +#ifdef USE_MVM_TOUR + uint32 nNotCompletedChallenges = ~0U; + CTFParty *pParty = GTFGCClientSystem()->GetParty(); + if ( pParty ) + { + for ( int i = 0 ; i < pParty->GetNumMembers() ; ++i ) + { + nNotCompletedChallenges &= ~pParty->Obj().members( i ).completed_missions(); + } + } + else + { + if ( idxTour >= 0 ) + { + uint32 nTours = 0, nCompletedChallenge = 0; + GTFGCClientSystem()->BGetLocalPlayerBadgeInfoForTour( idxTour, &nTours, &nCompletedChallenge ); + + nNotCompletedChallenges = ~nCompletedChallenge; + } + } +#endif // USE_MVM_TOUR + + for ( int i = 0 ; i < GetItemSchema()->GetMvmMissions().Count() ; ++i ) + { + const MvMMission_t &chal = GetItemSchema()->GetMvmMissions()[ i ]; + + // Cannot select non-MannUp missions in mann up mode +#ifdef USE_MVM_TOUR + int iBadgeSlot = (idxTour < 0) ? -1 : GetItemSchema()->GetMvmMissionBadgeSlotForTour( idxTour, i ); + if ( bMannUP && iBadgeSlot < 0 ) + continue; +#else // new mm + bool bIsChallengeInMannUp = chal.m_unMannUpPoints > 0; + if ( bMannUP && !bIsChallengeInMannUp ) + continue; +#endif // USE_MVM_TOUR + + // Does this challenge fit the search criteria? + bool bSelect = false; + switch ( idxChallenge ) + { + case k_iPopIndex_Any: + bSelect = true; + break; + case k_iPopIndex_OnlyNotYetCompleted: +#ifdef USE_MVM_TOUR + if ( iBadgeSlot >= 0 ) + { + int iChallengeBit = ( 1 << iBadgeSlot ); + if ( nNotCompletedChallenges & iChallengeBit ) + { + bSelect = true; + } + } +#endif // USE_MVM_TOUR + break; + + case k_iPopIndex_AnyNormal: + bSelect = ( chal.m_eDifficulty == k_EMvMChallengeDifficulty_Normal ); + break; + + case k_iPopIndex_AnyIntermediate: + bSelect = ( chal.m_eDifficulty == k_EMvMChallengeDifficulty_Intermediate ); + break; + + case k_iPopIndex_AnyAdvanced: + bSelect = ( chal.m_eDifficulty == k_EMvMChallengeDifficulty_Advanced ); + break; + + case k_iPopIndex_AnyExpert: + bSelect = ( chal.m_eDifficulty == k_EMvMChallengeDifficulty_Expert ); + break; + + case k_iPopIndex_AnyHaunted: + bSelect = ( chal.m_eDifficulty == k_EMvMChallengeDifficulty_Haunted ); + break; + + default: + Assert( false ); + } + result.SetMissionBySchemaIndex( i, bSelect ); + } +} + +extern Color s_colorBannedPlayerListItem; +extern Color s_colorPlayerListItem; +extern Color s_colorChatRemovedFromQueue; +extern Color s_colorChatAddedToQueue; +extern Color s_colorChatPlayerJoinedParty; +extern Color s_colorChatPlayerJoinedPartyName; +extern Color s_colorChatPlayerLeftParty; +extern Color s_colorChatPlayerLeftPartyName; +extern Color s_colorChatPlayerChatName; +extern Color s_colorChatPlayerChatText; +extern Color s_colorChatDefault; +extern Color s_colorChallengeForegroundEnabled; +extern Color s_colorChallengeForegroundHaunted; +extern Color s_colorChallengeForegroundDisabled; +extern Color s_colorChallengeHeader; + + +static void GetPlayerNameForSteamID( wchar_t *wCharPlayerName, int nBufSizeBytes, const CSteamID &steamID ) +{ + const char *pszName = steamapicontext->SteamFriends()->GetFriendPersonaName( steamID ); + V_UTF8ToUnicode( pszName, wCharPlayerName, nBufSizeBytes ); +} + +CLobbyPanel_MvM::CLobbyPanel_MvM( vgui::Panel *pParent, CBaseLobbyContainerFrame* pLobbyContainer ) + : CBaseLobbyPanel( pParent, pLobbyContainer ) +{ + m_pMvMMannVsMachineGroupPanel = NULL; + m_pMvMMannUpGroupPanel = NULL; + m_pMvMPracticeGroupPanel = NULL; + m_pMvMTourOfDutyGroupPanel = NULL; + m_pMvMTourOfDutyListGroupBox = NULL; + m_pTourList = NULL; + + m_MvMEconItemsGroupBox = NULL; + m_pSquadSurplusCheckButton = NULL; + m_pOpenStoreButton = NULL; + m_pOpenStoreButton2 = NULL; + m_pOpenHelpButton = NULL; + m_pMannUpNowButton = NULL; + m_pMannUpTourLootDescriptionBox = NULL; + m_pMannUpTourLootImage = NULL; + //m_pMannUpTourLootDetailLabel = NULL; + m_pTourDifficultyWarning = NULL; + + m_MvMPracticeGroupPanel = NULL; + + m_pMvMSelectChallengeGroupPanel = NULL; + m_pMVMChallengeListGroupBox = NULL; + + // MvM + m_pMvMMannVsMachineGroupPanel = new vgui::EditablePanel( this, "MannVsMachineGroupBox" ); + m_pMvMMannUpGroupPanel = new vgui::EditablePanel( this, "MannUpGroupBox" ); + m_pMvMPracticeGroupPanel = new vgui::EditablePanel( this, "PracticeGroupBox" ); + m_pMvMTourOfDutyGroupPanel = new vgui::EditablePanel( this, "MvMTourOfDutyGroupBox" ); + + m_MvMEconItemsGroupBox = new vgui::EditablePanel( this, "MvMEconItemsGroupBox" ); + m_pMannUpTicketImage = new vgui::ImagePanel( m_MvMEconItemsGroupBox, "MannUpTicketImage" ); + m_pSquadSurplusImage = new vgui::ImagePanel( m_MvMEconItemsGroupBox, "SquadSurplusImage" ); + + m_pMannUpTourLootDescriptionBox = new vgui::EditablePanel( this, "MannUpTourLootDescriptionBox" ); + m_pMannUpTourLootImage = new vgui::ImagePanel( m_pMannUpTourLootDescriptionBox, "TourLootImage" ); + //m_pMannUpTourLootDetailLabel = new vgui::Label( m_pMannUpTourLootDescriptionBox, "TourLootDetailLabel", " ); + + m_MvMPracticeGroupPanel = new vgui::EditablePanel( this, "MvMPracticeGroupBox" ); + + m_pMvMSelectChallengeGroupPanel = new vgui::EditablePanel( this, "MvMSelectChallengeGroupBox" ); + m_pMVMChallengeListGroupBox = new vgui::EditablePanel( m_pMvMSelectChallengeGroupPanel, "ChallengeListGroupBox" ); + m_pChallengeList = new ChallengeList( this, m_pMVMChallengeListGroupBox, "ChallengeList" ); + + m_pMvMTourOfDutyListGroupBox = new vgui::EditablePanel( m_pMvMTourOfDutyGroupPanel, "TourlistGroupBox" ); + m_pTourList = new vgui::SectionedListPanel( m_pMvMTourOfDutyListGroupBox, "TourList" ); + + m_pTourDifficultyWarning = new vgui::Label( m_pMvMTourOfDutyGroupPanel, "TourDifficultyWarning", "" ); + + m_fontChallengeListHeader = 0; + m_fontChallengeListItem = 0; +} + +CLobbyPanel_MvM::~CLobbyPanel_MvM() +{} + +//----------------------------------------------------------------------------- +void CLobbyPanel_MvM::FireGameEvent( IGameEvent *event ) +{ + BaseClass::FireGameEvent( event ); + + const char *pszEventName = event->GetName(); + + if ( !Q_stricmp( pszEventName, "mm_lobby_member_join" ) ) + { +#ifdef USE_MVM_TOUR + WriteTourList(); +#endif // USE_MVM_TOUR + WriteChallengeList(); + + return; + } + else if ( !Q_stricmp( pszEventName, "mm_lobby_member_leave" ) ) + { +#ifdef USE_MVM_TOUR + WriteTourList(); +#endif // USE_MVM_TOUR + WriteChallengeList(); + + return; + } +} + +void CLobbyPanel_MvM::OnCommand( const char *command ) +{ + if ( FStrEq( command, "open_store_ticket" ) ) + { + // Open the store, and show the upgrade advice + EconUI()->CloseEconUI(); + + CSchemaItemDefHandle hItemDef( CTFItemSchema::k_rchMvMTicketItemDefName ); + EconUI()->GetStorePanel()->AddToCartAndCheckoutImmediately( hItemDef->GetDefinitionIndex() ); + return; + } + else if ( FStrEq( command, "open_store_voucher" ) ) + { + // Open the store, and show the upgrade advice + EconUI()->CloseEconUI(); + + CSchemaItemDefHandle hItemDef( CTFItemSchema::k_rchMvMSquadSurplusVoucherItemDefName ); + EconUI()->GetStorePanel()->AddToCartAndCheckoutImmediately( hItemDef->GetDefinitionIndex() ); + return; + } + else if ( FStrEq( command, "open_help" ) ) + { + CExplanationPopup *pPopup = dynamic_cast< CExplanationPopup* >( GetParent()->FindChildByName("StartExplanation") ); + if ( pPopup ) + { + pPopup->Popup(); + } + return; + } + else if ( FStrEq( command, "mann_up_now" ) ) + { + GTFGCClientSystem()->SetSearchChallenges( CMvMMissionSet() ); + m_pContainer->OnCommand( "back" ); + m_pContainer->OnCommand( "mannup" ); + return; + } + + BaseClass::OnCommand( command ); +} + +void CLobbyPanel_MvM::SetMannUpTicketCount( int nCount ) +{ + m_pMannUpTicketImage->SetImage( nCount > 0 ? "pve/mvm_ticket_active" : "pve/mvm_ticket_inactive" ); + + char szCount[ 5 ]; + V_snprintf( szCount, sizeof( szCount ), "%i", nCount ); + + m_MvMEconItemsGroupBox->SetDialogVariable( "ticket_count", szCount ); +} + +void CLobbyPanel_MvM::SetSquadSurplusCount( int nCount ) +{ + char szCount[ 5 ]; + V_snprintf( szCount, sizeof( szCount ), "%i", nCount ); + + m_MvMEconItemsGroupBox->SetDialogVariable( "voucher_count", szCount ); +} + +EMatchGroup CLobbyPanel_MvM::GetMatchGroup( void ) const +{ + return GTFGCClientSystem()->GetSearchPlayForBraggingRights() ? k_nMatchGroup_MvM_MannUp : k_nMatchGroup_MvM_Practice; +} + +void CLobbyPanel_MvM::OnCheckButtonChecked( vgui::Panel *panel ) +{ + if ( m_iWritingPanel > 0 ) + return; + + if ( panel == m_pSquadSurplusCheckButton ) + { + if ( BIsPartyInUIState() && GTFGCClientSystem()->GetSearchPlayForBraggingRights() ) + { + if ( m_pSquadSurplusCheckButton->IsSelected() ) + { + if ( GTFGCClientSystem()->BLocalPlayerInventoryHasSquadSurplusVoucher() ) + { + GTFGCClientSystem()->SetLocalPlayerSquadSurplus( true ); + m_pSquadSurplusImage->SetImage( "pve/mvm_voucher_active" ); + } + else + { + m_pSquadSurplusCheckButton->SetSilentMode( true ); + m_pSquadSurplusCheckButton->SetSelected( false ); + m_pSquadSurplusCheckButton->SetSilentMode( false ); + ShowEconRequirementDialog( "#TF_MvM_RequiresSquadSurplusVoucher_Title", "#TF_MvM_RequiresSquadSurplusVoucher", CTFItemSchema::k_rchMvMSquadSurplusVoucherItemDefName ); + } + } + else + { + GTFGCClientSystem()->SetLocalPlayerSquadSurplus( false ); + + m_pSquadSurplusImage->SetImage( "pve/mvm_voucher_inactive" ); + } + } + else + { + WriteGameSettingsControls(); + } + + return; + } + + BaseClass::OnCheckButtonChecked( panel ); +} + + +void CLobbyPanel_MvM::OnItemLeftClick( vgui::Panel* panel ) +{ + if ( m_iWritingPanel > 0 ) + return; + +#ifdef USE_MVM_TOUR + if ( panel == m_pTourList ) + { + OnClickedOnTour(); + } +#endif // USE_MVM_TOUR + else if ( panel == m_pChallengeList ) + { + OnClickedOnChallenge(); + } + + BaseClass::OnItemLeftClick( panel ); +} + +void CLobbyPanel_MvM::ApplyChatUserSettings( const LobbyPlayerInfo& player, KeyValues* pSettings ) const +{ + if ( GTFGCClientSystem()->GetSearchPlayForBraggingRights() ) + { + pSettings->SetInt( "has_ticket", player.m_bHasTicket ? m_iImageHasTicket : m_iImageNoTicket ); + pSettings->SetInt( "squad_surplus", player.m_bSquadSurplus ? m_iImageSquadSurplus : m_iImageNoSquadSurplus ); + } +} + +#ifdef USE_MVM_TOUR +void CLobbyPanel_MvM::OnClickedOnTour() +{ + int iSelected = m_pTourList->GetSelectedItem(); + m_pTourList->SetSelectedItem( -1 ); + if ( iSelected < 0 ) + return; + if ( BIsPartyLeader() && BIsPartyInUIState() ) + { + int iTourIndex = m_pTourList->GetItemData( iSelected )->GetInt( "tour_index", -1 ); + Assert( iTourIndex >= 0 ); + GTFGCClientSystem()->SetSearchMannUpTourIndex( iTourIndex ); + } + else + { + WriteChallengeList(); + } +} +#endif // USE_MVM_TOUR + +void CLobbyPanel_MvM::WriteGameSettingsControls() +{ + BaseClass::WriteGameSettingsControls(); + + // Make sure we want to be in matchmaking. (If we don't, the frame should hide us pretty quickly.) + // We might get an event or something right at the transition point occasionally when the UI should + // not be visible + if ( GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_Inactive ) + { + return; + } + + ++m_iWritingPanel; + + bool bLeader = BIsPartyLeader(); + bool bInUIState = BIsPartyInUIState(); + + TF_Matchmaking_WizardStep eWizardStep = GTFGCClientSystem()->GetWizardStep(); + + // MVM + m_pMvMMannVsMachineGroupPanel->SetVisible( eWizardStep == TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS ); + m_pMvMMannUpGroupPanel->SetVisible( eWizardStep == TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS ); + m_pMvMPracticeGroupPanel->SetVisible( eWizardStep == TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS ); + m_pMannUpTourLootDescriptionBox->SetVisible( eWizardStep == TF_Matchmaking_WizardStep_MVM_TOUR_OF_DUTY ); + m_pMvMTourOfDutyGroupPanel->SetVisible( eWizardStep == TF_Matchmaking_WizardStep_MVM_TOUR_OF_DUTY ); + m_pMvMSelectChallengeGroupPanel->SetVisible( eWizardStep == TF_Matchmaking_WizardStep_MVM_CHALLENGE ); + + bool bShowBottomPanel = ( eWizardStep == TF_Matchmaking_WizardStep_MVM_CHALLENGE ); + + if ( !tf_matchmaking_ticket_help.GetBool() && bShowBottomPanel && GTFGCClientSystem()->GetSearchPlayForBraggingRights() ) + { + OnCommand( "open_help" ); + tf_matchmaking_ticket_help.SetValue( 1 ); + } + + m_MvMEconItemsGroupBox->SetVisible( bShowBottomPanel && GTFGCClientSystem()->GetSearchPlayForBraggingRights() ); + m_MvMPracticeGroupPanel->SetVisible( bShowBottomPanel && !GTFGCClientSystem()->GetSearchPlayForBraggingRights() ); + + if ( m_pMvMTourOfDutyGroupPanel->IsVisible() ) + { + SetNavToRelay( m_pMvMTourOfDutyGroupPanel->GetName() ); + } + else if ( m_pMvMSelectChallengeGroupPanel->IsVisible() ) + if ( m_pMvMSelectChallengeGroupPanel->IsVisible() ) + { + SetNavToRelay( m_pMvMSelectChallengeGroupPanel->GetName() ); + } + + if ( m_MvMPracticeGroupPanel->IsVisible() ) + { + SetNavToRelay( m_MvMPracticeGroupPanel->GetName() ); + } + else if ( m_MvMEconItemsGroupBox->IsVisible() ) + { + SetNavToRelay( m_MvMEconItemsGroupBox->GetName() ); + } + + m_pContainer->SetNextButtonEnabled( true ); + + +#ifdef USE_MVM_TOUR + WriteTourList(); +#endif // USE_MVM_TOUR + WriteChallengeList(); + + FOR_EACH_VEC( m_vecSearchCriteriaLabels, i ) + { + m_vecSearchCriteriaLabels[i]->SetEnabled( bInUIState ); + } + + m_pMVMChallengeListGroupBox->SetControlVisible( "GreyOutPanel", !( bLeader && bInUIState ) ); +#ifdef USE_MVM_TOUR + m_pMvMTourOfDutyListGroupBox->SetControlVisible( "GreyOutPanel", !( bLeader && bInUIState ) ); +#endif // USE_MVM_TOUR + bool bPlayForBraggingRights = GTFGCClientSystem()->GetSearchPlayForBraggingRights(); + m_pSquadSurplusCheckButton->SetEnabled( bInUIState && bPlayForBraggingRights ); + m_pSquadSurplusCheckButton->SetSilentMode( true ); + m_pSquadSurplusCheckButton->SetSelected( GTFGCClientSystem()->GetLocalPlayerSquadSurplus() ); + m_pSquadSurplusCheckButton->SetSilentMode( false ); + + m_pSquadSurplusImage->SetImage( GTFGCClientSystem()->GetLocalPlayerSquadSurplus() ? "pve/mvm_voucher_active" : "pve/mvm_voucher_inactive" ); + + --m_iWritingPanel; +} + +bool CLobbyPanel_MvM::ShouldShowLateJoin() const +{ + TF_Matchmaking_WizardStep eWizardStep = GTFGCClientSystem()->GetWizardStep(); + return +#ifdef USE_MVM_TOUR + ( eWizardStep == TF_Matchmaking_WizardStep_MVM_TOUR_OF_DUTY ) || +#endif // USE_MVM_TOUR + ( eWizardStep == TF_Matchmaking_WizardStep_MVM_CHALLENGE ) || + ( ( eWizardStep == TF_Matchmaking_WizardStep_SEARCHING ) && ( GTFGCClientSystem()->GetSearchMode() == TF_Matchmaking_MVM ) ); +} + +#ifdef USE_MVM_TOUR +void CLobbyPanel_MvM::WriteTourList() +{ + if ( !GTFGCClientSystem()->GetSearchPlayForBraggingRights() ) + return; + + ++m_iWritingPanel; + + bool bLeader = BIsPartyLeader(); + bool bInUIState = BIsPartyInUIState(); + int idxSelectedTour = GTFGCClientSystem()->GetSearchMannUpTourIndex(); + + m_pTourList->RemoveAll(); + m_pTourList->RemoveAllSections(); + + m_pTourList->SetClickable( bLeader && bInUIState ); + m_pTourList->AddSection( 0, "Tour name" ); + m_pTourList->SetSectionAlwaysVisible( 0, false ); + m_pTourList->SetSectionFgColor( 0, s_colorChallengeHeader ); + m_pTourList->SetSectionDividerColor( 0, Color(0,0,0,0) ); + m_pTourList->AddColumnToSection( 0, "new","", vgui::SectionedListPanel::COLUMN_IMAGE, m_iNewWidth ); + m_pTourList->AddColumnToSection( 0, "check_box", "", vgui::SectionedListPanel::COLUMN_IMAGE, m_iChallengeCheckBoxWidth ); + m_pTourList->AddColumnToSection( 0, "spacer", "", 0, m_iChallengeSpacer ); + m_pTourList->AddColumnToSection( 0, "display_name", "Tour", 0, m_iTourNameWidth ); + m_pTourList->AddColumnToSection( 0, "skill", "Difficulty", 0, m_iTourSkillWidth ); + m_pTourList->AddColumnToSection( 0, "progress", "Progress", 0, m_iTourProgressWidth ); + m_pTourList->AddColumnToSection( 0, "badge_level", "Tours Completed", 0, m_iTourNumberWidth ); + m_pTourList->SetFontSection( 0, m_fontChallengeListHeader ); + + bool bCompletedOneAdvancedTour = false; + uint32 unBadgeLevel = 0, unCompletedChallengeMask = 0; + const char *pszWarningString = "#TF_MVM_Tour_ExpertDifficulty_Warning"; + + // Local player has completed at least one Advanced tour? + FOR_EACH_VEC( GetItemSchema()->GetMvmTours(), idxTour ) + { + GTFGCClientSystem()->BGetLocalPlayerBadgeInfoForTour( idxTour, &unBadgeLevel, &unCompletedChallengeMask ); + + const MvMTour_t &tourInfo = GetItemSchema()->GetMvmTours()[idxTour]; + if ( tourInfo.m_eDifficulty >= k_EMvMChallengeDifficulty_Advanced && unBadgeLevel > 0 ) + { + bCompletedOneAdvancedTour = true; + break; + } + } + + // Add a row for each tour + FOR_EACH_VEC( GetItemSchema()->GetMvmTours(), idxTour ) + { + const MvMTour_t &tour = GetItemSchema()->GetMvmTours()[ idxTour ]; + + KeyValues *kvItem = new KeyValues("item"); + + GTFGCClientSystem()->BGetLocalPlayerBadgeInfoForTour( idxTour, &unBadgeLevel, &unCompletedChallengeMask ); + + int nCompletedChallengeCount = 0; + for ( int i = 0 ; i < tour.m_vecMissions.Count() ; ++i ) + { + if ( unCompletedChallengeMask & ( 1 << tour.m_vecMissions[i].m_iBadgeSlot ) ) + { + ++nCompletedChallengeCount; + } + } + + char cchTemp[256]; + V_sprintf_safe( cchTemp, "%d / %d", nCompletedChallengeCount, tour.m_vecMissions.Count() ); + kvItem->SetString( "progress", cchTemp ); + + uint32 iTourNumber = Max( 1U, unBadgeLevel ); + V_sprintf_safe( cchTemp, "%d", iTourNumber ); + kvItem->SetString( "badge_level", cchTemp ); + + if ( tour.m_bIsNew ) + { + kvItem->SetInt( "new", m_iImageNew ); + } + + kvItem->SetInt( "check_box", idxSelectedTour == idxTour ? m_iImageRadioButtonYes : m_iImageRadioButtonNo ); + kvItem->SetString( "display_name", tour.m_sTourNameLocalizationToken.Get() ); + kvItem->SetString( "skill", GetMvMChallengeDifficultyLocName( tour.m_eDifficulty ) ); + kvItem->SetInt( "tour_index", idxTour ); + int itemID = m_pTourList->AddItem( 0, kvItem ); + m_pTourList->SetItemFont( itemID, m_fontChallengeListItem ); + + if ( tour.m_eDifficulty >= k_EMvMChallengeDifficulty_Expert && !bCompletedOneAdvancedTour ) + { + m_pTourList->SetItemFgColor( itemID, s_colorBannedPlayerListItem ); + pszWarningString = "#TF_MVM_Tour_ExpertDifficulty_Denied"; + } + else + { + m_pTourList->SetItemFgColor( itemID, s_colorChallengeForegroundEnabled ); + } + } + m_pTourList->SetSelectedItem( idxSelectedTour ); + + const char *pszSelectedTourLocToken = "TF_MvM_Tour_NoSelection"; + const char *pszLootImage = "pve/mvm_loot_image"; + bool bShowDifficultyWarning = false; + if ( idxSelectedTour >= 0 ) + { + const MvMTour_t &tour = GetItemSchema()->GetMvmTours()[ idxSelectedTour ]; + pszLootImage = tour.m_sLootImageName.Get(); + pszSelectedTourLocToken = tour.m_sTourNameLocalizationToken.Get(); + + // Check if we should show the difficulty warning + if ( tour.m_eDifficulty >= k_EMvMChallengeDifficulty_Expert ) + { + // Deny expert mode if they haven't completed at least one Advanced tour + if ( !bCompletedOneAdvancedTour ) + { + m_pContainer->SetNextButtonEnabled( false ); + } + + // Local player hasn't completed one mission? + if ( !GTFGCClientSystem()->BGetLocalPlayerBadgeInfoForTour( idxSelectedTour, &unBadgeLevel, &unCompletedChallengeMask ) + || unBadgeLevel == 0 ) + { + bShowDifficultyWarning = true; + } + + // Anybody in the party hasn't completed a mission? + CTFParty *pParty = GTFGCClientSystem()->GetParty(); + if ( pParty != NULL ) + { + for ( int i = 0 ; !bShowDifficultyWarning && i < pParty->GetNumMembers() ; ++i ) + { + if ( pParty->Obj().members( i ).badge_level() == 0 ) + { + bShowDifficultyWarning = true; + } + } + } + } + } + + char archTemp[ 256 ]; + V_sprintf_safe( archTemp, "%s_LootDescription", pszSelectedTourLocToken ); + m_pMannUpTourLootDescriptionBox->SetDialogVariable( "tour_loot_detail", g_pVGuiLocalize->Find( archTemp ) ); + + m_pMannUpTourLootImage->SetImage( pszLootImage ); + + wchar_t wszLocalized[512]; + g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( pszWarningString ), 0 ); + m_pTourDifficultyWarning->SetText( wszLocalized ); + m_pTourDifficultyWarning->SetVisible( bShowDifficultyWarning ); + + --m_iWritingPanel; +} +#endif // USE_MVM_TOUR + +void CLobbyPanel_MvM::WriteChallengeList() +{ + ++m_iWritingPanel; + + bool bLeader = BIsPartyLeader(); + bool bInUIState = BIsPartyInUIState(); + bool bForBraggingRights = GTFGCClientSystem()->GetSearchPlayForBraggingRights(); + +#ifdef USE_MVM_TOUR + int idxTour = GTFGCClientSystem()->GetSearchMannUpTourIndex(); + + char szTours[ 8 ] = ""; + if ( idxTour >= 0 ) + { + uint32 nTours, nCompletedChallenge; + GTFGCClientSystem()->BGetLocalPlayerBadgeInfoForTour( idxTour, &nTours, &nCompletedChallenge ); + if ( nTours < 1 ) // if we don't have a badge, show "1" + nTours = 1; + V_snprintf( szTours, sizeof( szTours ), "%u", nTours ); + } + + m_pChallengeList->SetClickable( bLeader && bInUIState ); + if ( idxTour < 0 ) + { + m_pMvMSelectChallengeGroupPanel->SetDialogVariable( "tour_name", g_pVGuiLocalize->Find( "#TF_MvM_Missions" ) ); + } + else + { + m_pMvMSelectChallengeGroupPanel->SetDialogVariable( "tour_name", g_pVGuiLocalize->Find( GetItemSchema()->GetMvmTours()[ idxTour ].m_sTourNameLocalizationToken.Get() ) ); + } + m_pMvMSelectChallengeGroupPanel->SetDialogVariable( "complete_heading", g_pVGuiLocalize->Find( bForBraggingRights ? "#TF_MvM_Complete" : "#TF_MvM_Difficulty" ) ); + m_pMvMSelectChallengeGroupPanel->SetDialogVariable( "tour_level", szTours ); + m_pMvMSelectChallengeGroupPanel->SetControlVisible( "TourLevelImage", idxTour >= 0 ); +#else // new mm + char szTours[ 8 ] = ""; + + m_pChallengeList->SetClickable( bLeader && bInUIState ); + m_pMvMSelectChallengeGroupPanel->SetDialogVariable( "tour_name", g_pVGuiLocalize->Find( "#TF_MvM_Missions" ) ); + m_pMvMSelectChallengeGroupPanel->SetDialogVariable( "complete_heading", g_pVGuiLocalize->Find( "#TF_MvM_Difficulty" ) ); + m_pMvMSelectChallengeGroupPanel->SetDialogVariable( "tour_level", szTours ); + m_pMvMSelectChallengeGroupPanel->SetControlVisible( "TourLevelImage", false ); +#endif // USE_MVM_TOUR + + CMvMMissionSet searchChallenges; + GTFGCClientSystem()->GetSearchChallenges( searchChallenges ); + + m_pChallengeList->RemoveAll(); + m_pChallengeList->RemoveAllSections(); +// int iSelectChallengeItem = -1; + + int nCurrentSection = -1; + KeyValues *kvItem = NULL; + int itemID = 0; + + // + // Top section is for special multi-select checkboxes + // + + ++nCurrentSection; + + m_pChallengeList->AddSection( nCurrentSection, "dummy_any_section" ); + m_pChallengeList->SetSectionAlwaysVisible( nCurrentSection, true ); + m_pChallengeList->SetSectionDividerColor( nCurrentSection, Color(0,0,0,0) ); + //m_pChallengeList->SetSectionFgColor( nCurrentSection, Color( 255, 255, 255, 255 ) ); + m_pChallengeList->AddColumnToSection( nCurrentSection, "check_box", "", vgui::SectionedListPanel::COLUMN_IMAGE, m_iChallengeCheckBoxWidth ); + m_pChallengeList->AddColumnToSection( nCurrentSection, "spacer", "", 0, m_iChallengeSpacer ); + m_pChallengeList->AddColumnToSection( nCurrentSection, "display_name", "", 0, m_iChallengeNameWidth ); + //m_pChallengeList->AddColumnToSection( nCurrentSection, "completed", "", 0, m_iChallengeCompletedWidth ); + //m_pChallengeList->SetFontSection( nCurrentSection, m_fontChallengeListHeader ); + //m_pChallengeList->SetSectionMinimumContentHeight( nCurrentSection, m_iMapImageHeight ); + + m_pChallengeList->m_vecMapImages.Purge(); + + // List of special multi-select options + struct MissionMultiSelect_t + { + int m_idxChallenge; + const char *m_pszDisplayName; + bool m_bMannUp; + bool m_bBootCamp; + }; + static const MissionMultiSelect_t arMultiSelect[] = + { +#ifdef USE_MVM_TOUR + { k_iPopIndex_Any, "#TF_MvM_AnyChallenge", true, false }, +// { k_iPopIndex_AnyHaunted, "#TF_MvM_AnyHauntedChallenge", false, true }, + { k_iPopIndex_AnyNormal, "#TF_MvM_AnyNormalChallenge", false, true }, + { k_iPopIndex_AnyIntermediate, "#TF_MvM_AnyIntermediateChallenge", false, true }, + { k_iPopIndex_AnyAdvanced, "#TF_MvM_AnyAdvancedChallenge", false, true }, + { k_iPopIndex_AnyExpert, "#TF_MvM_AnyExpertChallenge", false, true }, + { k_iPopIndex_OnlyNotYetCompleted, "#TF_MvM_OnlyChallengeNotYetCompleted", true, false } +#else // new mm + { k_iPopIndex_Any, "#TF_MvM_AnyChallenge", true, true }, +// { k_iPopIndex_AnyHaunted, "#TF_MvM_AnyHauntedChallenge", false, true }, + { k_iPopIndex_AnyNormal, "#TF_MvM_AnyNormalChallenge", true, true }, + { k_iPopIndex_AnyIntermediate, "#TF_MvM_AnyIntermediateChallenge", true, true }, + { k_iPopIndex_AnyAdvanced, "#TF_MvM_AnyAdvancedChallenge", true, true }, + { k_iPopIndex_AnyExpert, "#TF_MvM_AnyExpertChallenge", true, true }, + { k_iPopIndex_OnlyNotYetCompleted, "#TF_MvM_OnlyChallengeNotYetCompleted", false, false } +#endif // USE_MVM_TOUR + }; + + // Scan each potential multi-select option + for ( int i = 0 ; i < Q_ARRAYSIZE( arMultiSelect ) ; ++i ) + { + const MissionMultiSelect_t &ms = arMultiSelect[i]; + + // Check if entry is applicable for this mode + if ( bForBraggingRights ? !ms.m_bMannUp : !ms.m_bBootCamp ) + continue; + + // Gather list of all missions that fit this mode + CMvMMissionSet msChallenges; + GetMvmChallengeSet( ms.m_idxChallenge, msChallenges ); + + // Any missions actually met the criteria for this multi-select? + // (e.g. we might not have any intermediate missions active right now). + int iCheckImage; + if ( msChallenges.IsEmpty() ) + { + if ( ms.m_idxChallenge != k_iPopIndex_OnlyNotYetCompleted ) + continue; + iCheckImage = m_iImageCheckBoxDisabled; + } + else + { + + // Determine checkbox status. "Only not yet completed" is special + if ( ms.m_idxChallenge == k_iPopIndex_OnlyNotYetCompleted ) + { + if ( searchChallenges == msChallenges ) + iCheckImage = m_iImageCheckBoxYes; // all items currently selected + else + iCheckImage = m_iImageCheckBoxNo; // does not exactly match, show as a "no" + } + else + { + // Get set of checked challenges that fall under this category + CMvMMissionSet checked( searchChallenges ); + checked.Intersect( msChallenges ); + + if ( checked == msChallenges ) + iCheckImage = m_iImageCheckBoxYes; // all items currently selected + //else if ( !checked.IsEmpty() ) // Nope, don't ever show "mixed" state + // iCheckImage = m_iImageCheckBoxMixed; // some items currently selected + else + iCheckImage = m_iImageCheckBoxNo; // no items currently selected + } + + } + + kvItem = new KeyValues("item"); + kvItem->SetInt( "check_box", iCheckImage ); + kvItem->SetString( "display_name", ms.m_pszDisplayName ); + kvItem->SetInt( "pop_index", ms.m_idxChallenge ); + itemID = m_pChallengeList->AddItem( nCurrentSection, kvItem ); + m_pChallengeList->SetItemFont( itemID, m_fontChallengeListItem ); + + Color color = s_colorChallengeForegroundEnabled; + if ( ms.m_idxChallenge == k_iPopIndex_AnyHaunted ) + color = s_colorChallengeForegroundHaunted; + if ( iCheckImage == m_iImageCheckBoxDisabled ) + color = s_colorChallengeForegroundDisabled; + m_pChallengeList->SetItemFgColor( itemID, color ); + } + + + // + // Now add a section for each map + // + + int nCurrentMap = -1; + FOR_EACH_VEC( GetItemSchema()->GetMvmMissions(), iMissionIndex ) + { + const MvMMission_t &mission = GetItemSchema()->GetMvmMissions()[ iMissionIndex ]; + +#ifdef USE_MVM_TOUR + if ( bForBraggingRights && GetItemSchema()->FindMvmMissionInTour( idxTour, iMissionIndex) < 0 ) // !KLUDGE! This is sort of crappy, we probably should iterate the tour's mission list rather than iterating the larger list with filtering +#else // new mm + if ( bForBraggingRights && !searchChallenges.GetMissionBySchemaIndex( iMissionIndex ) ) +#endif // USE_MVM_TOUR + continue; + + kvItem = new KeyValues("item"); + + const MvMMap_t &map = GetItemSchema()->GetMvmMaps()[ mission.m_iDisplayMapIndex ]; + + if ( mission.m_iDisplayMapIndex != nCurrentMap ) + { + ++nCurrentSection; + + m_pChallengeList->AddSection( nCurrentSection, map.m_sDisplayName.Get() ); + m_pChallengeList->SetSectionAlwaysVisible( nCurrentSection, true ); + m_pChallengeList->SetSectionFgColor( nCurrentSection, s_colorChallengeHeader ); + m_pChallengeList->SetSectionDividerColor( nCurrentSection, Color(0,0,0,0) ); +#ifdef USE_MVM_TOUR + m_pChallengeList->AddColumnToSection( nCurrentSection, "check_box", "", vgui::SectionedListPanel::COLUMN_IMAGE, m_iChallengeCheckBoxWidth ); +#else // new mm + // for mannup, don't show check box + if ( bForBraggingRights ) + { + m_pChallengeList->AddColumnToSection( nCurrentSection, "spacer", "", 0, m_iChallengeCheckBoxWidth ); + } + else + { + m_pChallengeList->AddColumnToSection( nCurrentSection, "check_box", "", vgui::SectionedListPanel::COLUMN_IMAGE, m_iChallengeCheckBoxWidth ); + } +#endif // USE_MVM_TOUR + m_pChallengeList->AddColumnToSection( nCurrentSection, "spacer", "", 0, m_iChallengeSpacer ); + m_pChallengeList->AddColumnToSection( nCurrentSection, "display_name", map.m_sDisplayName.Get(), 0, m_iChallengeNameWidth ); + m_pChallengeList->AddColumnToSection( nCurrentSection, "skill", "", 0, m_iChallengeSkillWidth ); + m_pChallengeList->SetFontSection( nCurrentSection, m_fontChallengeListHeader ); + m_pChallengeList->SetSectionMinimumHeight( nCurrentSection, m_iMapImageHeight ); + + BitmapImage &img = m_pChallengeList->m_vecMapImages[ m_pChallengeList->m_vecMapImages.AddToTail() ]; + CFmtStr sImageName("vgui/maps/menu_thumb_%s", map.m_sMap.Get() ); + img.SetImageFile( sImageName ); + + nCurrentMap = mission.m_iDisplayMapIndex; + } + + //wchar_t wszChallengeName[ 256 ]; + //g_pVGuiLocalize->ConstructString_safe( wszChallengeName, L"%s1 (%s2)", 2, + // g_pVGuiLocalize->Find( mission.m_sDisplayName.Get() ), g_pVGuiLocalize->Find( mission.m_sMode.Get() ) ); + //kvItem->SetWString( "display_name", wszChallengeName ); + kvItem->SetString( "display_name", mission.m_sDisplayName.Get() ); + + bool bSelected = searchChallenges.GetMissionBySchemaIndex( iMissionIndex ); + + kvItem->SetInt( "check_box", bSelected ? m_iImageCheckBoxYes : m_iImageCheckBoxNo ); + const char *pszDifficulty = ""; +#ifdef USE_MVM_TOUR + if ( !bForBraggingRights ) +#endif // USE_MVM_TOUR + { + pszDifficulty = GetMvMChallengeDifficultyLocName( mission.m_eDifficulty ); + } + kvItem->SetString( "skill", pszDifficulty ); + kvItem->SetInt( "pop_index", iMissionIndex ); + itemID = m_pChallengeList->AddItem( nCurrentSection, kvItem ); + m_pChallengeList->SetItemFont( itemID, m_fontChallengeListItem ); + + Color color = s_colorChallengeForegroundEnabled; + if ( mission.m_eDifficulty == k_EMvMChallengeDifficulty_Haunted ) + color = s_colorChallengeForegroundHaunted; + m_pChallengeList->SetItemFgColor( itemID, color ); + + kvItem->deleteThis(); + } + + --m_iWritingPanel; +} + + +//----------------------------------------------------------------------------- +void CLobbyPanel_MvM::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + m_pOpenStoreButton = dynamic_cast<vgui::Button *>(FindChildByName( "OpenStoreButton", true )); Assert( m_pOpenStoreButton ); + m_pOpenStoreButton2 = dynamic_cast<vgui::Button *>(FindChildByName( "OpenStoreButton2", true )); Assert( m_pOpenStoreButton2 ); + m_pOpenHelpButton = dynamic_cast<vgui::Button *>(FindChildByName( "OpenHelpButton", true )); Assert( m_pOpenHelpButton ); + m_pSquadSurplusCheckButton = dynamic_cast<vgui::CheckButton *>(FindChildByName( "SquadSurplusCheckButton", true )); Assert( m_pSquadSurplusCheckButton ); + m_pMannUpNowButton = dynamic_cast<vgui::Button *>(FindChildByName( "MannUpNowButton", true )); Assert( m_pMannUpNowButton ); + + m_iImageHasTicket = m_pImageList->AddImage( vgui::scheme()->GetImage( "pve/mvm_ticket_small", true ) ); + m_pImageList->GetImage( m_iImageHasTicket )->SetSize( m_iHasTicketWidth, m_iHasTicketWidth ); + m_iImageNoTicket = m_pImageList->AddImage( vgui::scheme()->GetImage( "pve/mvm_no_ticket_small", true ) ); + m_pImageList->GetImage( m_iImageNoTicket )->SetSize( m_iHasTicketWidth, m_iHasTicketWidth ); + m_iImageSquadSurplus = m_pImageList->AddImage( vgui::scheme()->GetImage( "pve/mvm_squad_surplus_small", true ) ); + m_pImageList->GetImage( m_iImageSquadSurplus )->SetSize( m_iSquadSurplusWidth, m_iSquadSurplusWidth ); + m_iImageNoSquadSurplus = m_pImageList->AddImage( vgui::scheme()->GetImage( "pve/mvm_no_squad_surplus_small", true ) ); + m_pImageList->GetImage( m_iImageNoSquadSurplus )->SetSize( m_iSquadSurplusWidth, m_iSquadSurplusWidth ); + + m_pChatPlayerList->SetImageList( m_pImageList, false ); + m_pChatPlayerList->SetVisible( true ); + m_pChallengeList->SetImageList( m_pImageList, false ); +#ifdef USE_MVM_TOUR + m_pTourList->SetImageList( m_pImageList, false ); +#endif // USE_MVM_TOUR + + // + // Populate the challenge list + // + + m_pChallengeList->AddActionSignalTarget( this ); +#ifdef USE_MVM_TOUR + m_pTourList->AddActionSignalTarget( this ); +#endif // USE_MVM_TOUR + m_pSquadSurplusCheckButton->AddActionSignalTarget( this ); + m_pChatPlayerList->AddActionSignalTarget( this ); + m_pOpenStoreButton->AddActionSignalTarget( this ); + m_pOpenStoreButton2->AddActionSignalTarget( this ); + m_pOpenHelpButton->AddActionSignalTarget( this ); + m_pMannUpNowButton->AddActionSignalTarget( this ); + + m_pChallengeList->SetVerticalScrollbar( true ); + m_pChallengeList->RemoveAll(); + m_pChallengeList->RemoveAllSections(); + m_pChallengeList->SetDrawHeaders( true ); + m_pChallengeList->SetClickable( true ); + m_pChallengeList->SetBgColor( Color( 0, 0, 0, 0 ) ); + m_pChallengeList->SetBorder( NULL ); + +#ifdef USE_MVM_TOUR + m_pTourList->SetDrawHeaders( false ); +#endif // USE_MVM_TOUR + + m_fontChallengeListHeader = pScheme->GetFont( "HudFontSmallestBold", true ); + m_fontChallengeListItem = pScheme->GetFont( "HudFontSmallest", true ); + + // + // Populate the player list + // + int nAvatarWidth = ( ( m_iAvatarWidth * 5 / 4 ) + 1 ); + int nExtraWidth = m_pChatPlayerList->GetWide() - nAvatarWidth - m_iPlayerNameWidth - m_iBannedWidth - m_iHasTicketWidth - m_iSquadSurplusWidth - m_iBadgeLevelWidth; + + m_pChatPlayerList->AddColumnToSection( 0, "avatar", "#TF_Players", vgui::SectionedListPanel::COLUMN_IMAGE, nAvatarWidth ); + m_pChatPlayerList->AddColumnToSection( 0, "name", "", 0, m_iPlayerNameWidth + nExtraWidth ); + m_pChatPlayerList->AddColumnToSection( 0, "is_banned", "", vgui::SectionedListPanel::COLUMN_IMAGE | vgui::SectionedListPanel::COLUMN_CENTER, m_iBannedWidth ); + m_pChatPlayerList->AddColumnToSection( 0, "has_ticket", "", vgui::SectionedListPanel::COLUMN_IMAGE | vgui::SectionedListPanel::COLUMN_CENTER, m_iHasTicketWidth ); + m_pChatPlayerList->AddColumnToSection( 0, "squad_surplus", "", vgui::SectionedListPanel::COLUMN_IMAGE | vgui::SectionedListPanel::COLUMN_CENTER, m_iSquadSurplusWidth ); + m_pChatPlayerList->AddColumnToSection( 0, "badge_level", "#TF_MvM_Tours", vgui::SectionedListPanel::COLUMN_CENTER, m_iBadgeLevelWidth ); +} + +void CLobbyPanel_MvM::PerformLayout() +{ +#ifdef USE_MVM_TOUR + WriteTourList(); +#endif // USE_MVM_TOUR + WriteChallengeList(); + + BaseClass::PerformLayout(); +} + +void CLobbyPanel_MvM::OnClickedOnChallenge() +{ + int iSelected = m_pChallengeList->GetSelectedItem(); + m_pChallengeList->SetSelectedItem( -1 ); + if ( iSelected < 0 ) + return; + if ( BIsPartyLeader() && BIsPartyInUIState() ) + { + int iChallengeIndex = m_pChallengeList->GetItemData( iSelected )->GetInt( "pop_index", -1 ); + +#ifndef USE_MVM_TOUR + // disallow player to select individual challenge in mannup + if ( iChallengeIndex >= 0 && GTFGCClientSystem()->GetSearchPlayForBraggingRights() ) + return; +#endif // !USE_MVM_TOUR + + CMvMMissionSet searchChallenges; + + // Fetch current selection. Except when clicking the "only uncompleted" checkbox, which is special + if ( iChallengeIndex != k_iPopIndex_OnlyNotYetCompleted ) + GTFGCClientSystem()->GetSearchChallenges( searchChallenges ); + + CMvMMissionSet setChallenges; + GetMvmChallengeSet( iChallengeIndex, setChallenges ); + bool bSelect = ( m_pChallengeList->GetItemData( iSelected )->GetInt( "check_box" ) != m_iImageCheckBoxYes ); + for ( int i = 0 ; i < GetItemSchema()->GetMvmMissions().Count() ; ++i ) + { + if ( setChallenges.GetMissionBySchemaIndex( i ) ) + { + searchChallenges.SetMissionBySchemaIndex( i, bSelect ); + } + } + + GTFGCClientSystem()->SetSearchChallenges( searchChallenges ); + } + else + { + WriteChallengeList(); + } +} + + +void CLobbyPanel_MvM::ChallengeList::Paint() +{ + vgui::SectionedListPanel::Paint(); + + FOR_EACH_VEC( m_vecMapImages, i ) + { + int x, y, w, h; + if ( !GetSectionHeaderBounds( i + 1, x, y, w, h ) ) + { + Assert( "MvM map mismatch" ); + continue; + } + + // Dear god. Why is VGUI such a piece of crap? + // And why is it such excruciating pain to do + // anything at all? + + // Select subrectangle within image to draw + w = m_pLobbyPanel->m_iMapImageWidth; + h = m_pLobbyPanel->m_iMapImageHeight; + m_vecMapImages[i].SetViewport( true, 0.0, 0.0, 1.0, (float)h / (float)w ); + + // Compute horiziontal position of icon + int gutter = vgui::scheme()->GetProportionalScaledValue( 5 ); + x -= gutter + m_pLobbyPanel->m_iMapImageWidth; + + // Save clipping rectangle. We want to be able to draw off to the left, + // outside of our bounding box, but we need to keep the vertical clipping + int left, top, right, bottom; + bool bDisabled; + g_pMatSystemSurface->GetClippingRect( left, top, right, bottom, bDisabled ); + + // Adjust clipping rectangle + int sx = x; + int sy = y; + LocalToScreen(sx, sy); + g_pMatSystemSurface->SetClippingRect( sx, top, right, bottom ); + + m_vecMapImages[i].DoPaint( x, y, w, h ); + + // Restore clipping rectangle + g_pMatSystemSurface->SetClippingRect( left, top, right, bottom ); + g_pMatSystemSurface->DisableClipping( bDisabled ); + } + +#ifdef USE_MVM_TOUR + // We can only do checkmarks if we know what tour they are working towards + int idxTour = GTFGCClientSystem()->GetSearchMannUpTourIndex(); + if ( idxTour >= 0 ) + { + int nCheckSize = m_pLobbyPanel->m_iChallengeCompletedSize; + int nCompletedX0 = m_pLobbyPanel->m_iChallengeCheckBoxWidth + m_pLobbyPanel->m_iChallengeNameWidth + m_pLobbyPanel->m_iChallengeSkillWidth / 4; + + uint32 nTours, nCompletedChallenge; + bool bFoundPlayerCompletedChallenges = GTFGCClientSystem()->BGetLocalPlayerBadgeInfoForTour( idxTour, &nTours, &nCompletedChallenge ); + + for ( int i = 0 ; i < GetItemCount() ; ++i ) + { + // Get The Pop File Name for this item + KeyValues *pkv = GetItemData( GetItemIDFromRow( i ) ); + int iMissionIndexInSchema = pkv->GetInt( "pop_index", -1 ); + if ( iMissionIndexInSchema < 0 ) // special multi-select entry + continue; + + int iBadgeSlot = GetItemSchema()->GetMvmMissionBadgeSlotForTour( idxTour, iMissionIndexInSchema ); + if ( iBadgeSlot < 0 ) + continue; + + int x, y, w, h; + GetItemBounds( i, x, y, w, h ); + + if ( bFoundPlayerCompletedChallenges ) + { + if ( nCompletedChallenge & (1 << iBadgeSlot) ) + { + m_imageChallengeCompleted.SetColor( Color(255,255,255,255) ); + } + else if ( GetSelectedItem() == i ) + { + m_imageChallengeCompleted.SetColor( Color(0,0,0,255) ); + } + else + { + m_imageChallengeCompleted.SetColor( Color(0,0,0,70) ); + } + int checkX0 = nCompletedX0; + int checkY0 = y + ( h - nCheckSize ) / 2; + m_imageChallengeCompleted.DoPaint( checkX0, checkY0, nCheckSize, nCheckSize ); + } + } + } +#endif // USE_MVM_TOUR +} diff --git a/game/client/tf/vgui/tf_lobbypanel_mvm.h b/game/client/tf/vgui/tf_lobbypanel_mvm.h new file mode 100644 index 0000000..7ea3dc0 --- /dev/null +++ b/game/client/tf/vgui/tf_lobbypanel_mvm.h @@ -0,0 +1,151 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#ifndef TF_LOBBYPANEL_MVM_H +#define TF_LOBBYPANEL_MVM_H + + +#include "cbase.h" +#include "game/client/iviewport.h" +#include "vgui_bitmapimage.h" +#include "tf_lobbypanel.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + + +class CLobbyPanel_MvM : public CBaseLobbyPanel +{ + DECLARE_CLASS_SIMPLE( CLobbyPanel_MvM, CBaseLobbyPanel ); + +public: + CLobbyPanel_MvM( vgui::Panel *pParent, CBaseLobbyContainerFrame* pLobbyContainer ); + + virtual ~CLobbyPanel_MvM(); + + // + // Panel overrides + // + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE; + virtual void PerformLayout() OVERRIDE; + + // + // CGameEventListener overrides + // + virtual void FireGameEvent( IGameEvent *event ) OVERRIDE; + + virtual void OnCommand( const char *command ) OVERRIDE; + + void SetMannUpTicketCount( int nCount ); + void SetSquadSurplusCount( int nCount ); + + virtual EMatchGroup GetMatchGroup( void ) const OVERRIDE; + + void ToggleSquadSurplusCheckButton( void ) + { + if ( !GTFGCClientSystem()->BLocalPlayerInventoryHasSquadSurplusVoucher() ) + return; + + m_pSquadSurplusCheckButton->SetSelected( !m_pSquadSurplusCheckButton->IsSelected() ); + } + +private: + + virtual const char* GetResFile() const OVERRIDE { return "Resource/UI/LobbyPanel_MvM.res"; } ; + + CPanelAnimationVarAliasType( int, m_iChallengeSpacer, "challenge_spacer", "4", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iChallengeNameWidth, "challenge_name_width", "190", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iChallengeSkillWidth, "challenge_skill_width", "110", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iChallengeCompletedSize, "challenge_completed_size", "15", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iMapImageWidth, "challenge_map_width", "60", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iMapImageHeight, "challenge_map_height", "40", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iHasTicketWidth, "has_ticket_width", "12", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iSquadSurplusWidth, "squad_surplus_width", "12", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTourMapWidth, "squad_surplus_width", "20", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iBadgeLevelWidth, "badge_level_width", "20", "proportional_int" ); + + CPanelAnimationVarAliasType( int, m_iTourNameWidth, "tour_name_width", "160", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTourSkillWidth, "tour_skill_width", "90", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTourProgressWidth, "tour_progress_width", "70", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTourNumberWidth, "tour_number_width", "40", "proportional_int" ); + + MESSAGE_FUNC_PTR( OnCheckButtonChecked, "CheckButtonChecked", panel ); + + MESSAGE_FUNC_PTR( OnItemLeftClick, "ItemLeftClick", panel ); + + virtual void ApplyChatUserSettings( const LobbyPlayerInfo& player, KeyValues* pSettings ) const OVERRIDE; +#ifdef USE_MVM_TOUR + void OnClickedOnTour(); +#endif // USE_MVM_TOUR + void OnClickedOnChallenge(); + + class ChallengeList : public vgui::SectionedListPanel + { + public: + ChallengeList( CLobbyPanel_MvM *pLobbyPanel, vgui::Panel *parent, const char *name ) + : vgui::SectionedListPanel( parent, name ) + , m_pLobbyPanel( pLobbyPanel ) + { + m_imageChallengeCompleted.SetImageFile( "vgui/pve/mvm_challenge_completed" ); + } + + virtual void OnMouseDoublePressed(vgui::MouseCode code) OVERRIDE { /* Just eat it */ } + virtual void Paint() OVERRIDE; + + CLobbyPanel_MvM *m_pLobbyPanel; + + BitmapImage m_imageChallengeCompleted; + CUtlVector<BitmapImage> m_vecMapImages; + }; + + CUtlVector<vgui::Label *> m_vecSearchCriteriaLabels; + + vgui::EditablePanel *m_pMvMMannVsMachineGroupPanel; + vgui::EditablePanel *m_pMvMMannUpGroupPanel; + vgui::EditablePanel *m_pMvMPracticeGroupPanel; + + vgui::EditablePanel *m_pMvMTourOfDutyGroupPanel; + vgui::EditablePanel *m_pMvMTourOfDutyListGroupBox; + vgui::SectionedListPanel *m_pTourList; + + vgui::EditablePanel *m_MvMEconItemsGroupBox; + vgui::CheckButton *m_pSquadSurplusCheckButton; + vgui::Button *m_pOpenStoreButton; + vgui::Button *m_pOpenStoreButton2; + vgui::Button *m_pOpenHelpButton; + vgui::ImagePanel *m_pMannUpTicketImage; + vgui::ImagePanel *m_pSquadSurplusImage; + vgui::Button *m_pMannUpNowButton; + + vgui::EditablePanel *m_pMannUpTourLootDescriptionBox; + vgui::ImagePanel *m_pMannUpTourLootImage; + vgui::Label *m_pTourDifficultyWarning; + //vgui::Label *m_pMannUpTourLootDetailLabel; + + vgui::EditablePanel *m_MvMPracticeGroupPanel; + + vgui::EditablePanel *m_pMvMSelectChallengeGroupPanel; + vgui::EditablePanel *m_pMVMChallengeListGroupBox; + ChallengeList *m_pChallengeList; + + + vgui::HFont m_fontChallengeListHeader; + vgui::HFont m_fontChallengeListItem; + + int m_iImageNoTicket; + int m_iImageHasTicket; + int m_iImageNoSquadSurplus; + int m_iImageSquadSurplus; + + void WriteGameSettingsControls() OVERRIDE; + virtual bool ShouldShowLateJoin() const OVERRIDE; +#ifdef USE_MVM_TOUR + void WriteTourList(); +#endif // USE_MVM_TOUR + void WriteChallengeList(); +}; + +#endif // TF_LOBBYPANEL_MVM_H
\ No newline at end of file diff --git a/game/client/tf/vgui/tf_mapinfomenu.cpp b/game/client/tf/vgui/tf_mapinfomenu.cpp new file mode 100644 index 0000000..5c5956d --- /dev/null +++ b/game/client/tf/vgui/tf_mapinfomenu.cpp @@ -0,0 +1,648 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include <vgui_controls/ImagePanel.h> +#include <vgui_controls/RichText.h> +#include <game/client/iviewport.h> +#include <vgui/ILocalize.h> +#include <KeyValues.h> +#include <filesystem.h> +#include "IGameUIFuncs.h" // for key bindings +#include "inputsystem/iinputsystem.h" + +#include "ixboxsystem.h" +#include "tf_gamerules.h" +#include "tf_controls.h" +#include "tf_shareddefs.h" +#include "tf_mapinfomenu.h" + +#include "video/ivideoservices.h" + +using namespace vgui; + +const char *GetMapDisplayName( const char *mapName ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CTFMapInfoMenu::CTFMapInfoMenu( IViewPort *pViewPort ) : Frame( NULL, PANEL_MAPINFO ) +{ + m_pViewPort = pViewPort; + + // load the new scheme early!! + SetScheme( "ClientScheme" ); + + SetTitleBarVisible( false ); + SetMinimizeButtonVisible( false ); + SetMaximizeButtonVisible( false ); + SetCloseButtonVisible( false ); + SetSizeable( false ); + SetMoveable( false ); + SetProportional( true ); + SetVisible( false ); + SetKeyBoardInputEnabled( true ); + + m_pTitle = new CExLabel( this, "MapInfoTitle", " " ); + +#ifdef _X360 + m_pFooter = new CTFFooter( this, "Footer" ); +#else + m_pContinue = new CExButton( this, "MapInfoContinue", "#TF_Continue" ); + m_pBack = new CExButton( this, "MapInfoBack", "#TF_Back" ); + m_pIntro = new CExButton( this, "MapInfoWatchIntro", "#TF_WatchIntro" ); +#endif + + // info window about this map + m_pMapInfo = new CExRichText( this, "MapInfoText" ); + m_pMapImage = new ImagePanel( this, "MapImage" ); + + m_szMapName[0] = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CTFMapInfoMenu::~CTFMapInfoMenu() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapInfoMenu::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + if ( ::input->IsSteamControllerActive() ) + { + LoadControlSettings( "Resource/UI/MapInfoMenu_SC.res" ); + m_pContinueHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "MapInfoContinueHintIcon" ) ); + m_pBackHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "MapInfoBackHintIcon" ) ); + m_pIntroHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "MapInfoIntroHintIcon" ) ); + + SetMouseInputEnabled( false ); + } + else + { + LoadControlSettings( "Resource/UI/MapInfoMenu.res" ); + m_pContinueHintIcon = m_pBackHintIcon = m_pIntroHintIcon = nullptr; + SetMouseInputEnabled( true ); + } + + CheckIntroState(); + CheckBackContinueButtons(); + + char mapname[MAX_MAP_NAME]; + + Q_FileBase( engine->GetLevelName(), mapname, sizeof(mapname) ); + + // Save off the map name so we can re-load the page in ApplySchemeSettings(). + Q_strncpy( m_szMapName, mapname, sizeof( m_szMapName ) ); + Q_strupr( m_szMapName ); + +#ifdef _X360 + char *pExt = Q_stristr( m_szMapName, ".360" ); + if ( pExt ) + { + *pExt = '\0'; + } +#endif + + LoadMapPage(); + SetMapTitle(); + +#ifndef _X360 + if ( m_pContinue ) + { + m_pContinue->RequestFocus(); + } +#endif + + SetDialogVariable( "gamemode", g_pVGuiLocalize->Find( GetMapType( m_szMapName ) ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapInfoMenu::ShowPanel( bool bShow ) +{ + if ( IsVisible() == bShow ) + return; + + m_KeyRepeat.Reset(); + + if ( bShow ) + { + InvalidateLayout( true, true ); // Force scheme reload since the steam controller state may have changed. + Activate(); + CheckIntroState(); + } + else + { + SetVisible( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFMapInfoMenu::CheckForIntroMovie() +{ + const char *pVideoFileName = TFGameRules()->GetVideoFileForMap(); + if ( pVideoFileName == NULL ) + { + return false; + } + + VideoSystem_t playbackSystem = VideoSystem::NONE; + char resolvedFile[MAX_PATH]; + if ( g_pVideo && g_pVideo->LocatePlayableVideoFile( pVideoFileName, "GAME", &playbackSystem, resolvedFile, sizeof(resolvedFile) ) == VideoResult::SUCCESS ) + { + return true; + } + + return false; +} + +const char *COM_GetModDirectory(); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFMapInfoMenu::HasViewedMovieForMap() +{ + return ( UTIL_GetMapKeyCount( "viewed" ) > 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapInfoMenu::CheckIntroState() +{ + if ( CheckForIntroMovie() && HasViewedMovieForMap() ) + { +#ifdef _X360 + if ( m_pFooter ) + { + m_pFooter->ShowButtonLabel( "intro", true ); + } +#else + if ( m_pIntro && !m_pIntro->IsVisible() ) + { + m_pIntro->SetVisible( true ); + if ( m_pIntroHintIcon ) + { + m_pIntroHintIcon->SetVisible( true ); + } + } +#endif + } + else + { +#ifdef _X360 + if ( m_pFooter ) + { + m_pFooter->ShowButtonLabel( "intro", false ); + } +#else + if ( m_pIntro && m_pIntro->IsVisible() ) + { + m_pIntro->SetVisible( false ); + if ( m_pIntroHintIcon ) + { + m_pIntroHintIcon->SetVisible( false ); + } + } +#endif + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapInfoMenu::CheckBackContinueButtons() +{ +#ifndef _X360 + if ( m_pBack && m_pContinue ) + { + if ( GetLocalPlayerTeam() == TEAM_UNASSIGNED ) + { + m_pBack->SetVisible( true ); + if ( m_pBackHintIcon ) + { + m_pBackHintIcon->SetVisible( true ); + } + m_pContinue->SetText( "#TF_Continue" ); + } + else + { + m_pBack->SetVisible( false ); + if ( m_pBackHintIcon ) + { + m_pBackHintIcon->SetVisible( false ); + } + m_pContinue->SetText( "#TF_Close" ); + } + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapInfoMenu::OnCommand( const char *command ) +{ + m_KeyRepeat.Reset(); + + if ( !Q_strcmp( command, "back" ) ) + { + // only want to go back to the Welcome menu if we're not already on a team + if ( !IsX360() && ( GetLocalPlayerTeam() == TEAM_UNASSIGNED ) ) + { + m_pViewPort->ShowPanel( this, false ); + m_pViewPort->ShowPanel( PANEL_INFO, true ); + } + } + else if ( !Q_strcmp( command, "continue" ) ) + { + m_pViewPort->ShowPanel( this, false ); + + if ( CheckForIntroMovie() && !HasViewedMovieForMap() ) + { + m_pViewPort->ShowPanel( PANEL_INTRO, true ); + + UTIL_IncrementMapKey( "viewed" ); + } + else + { + // On console, we may already have a team due to the lobby assigning us one. + // We tell the server we're done with the map info menu, and it decides what to do with us. + if ( IsX360() ) + { + engine->ClientCmd( "closedwelcomemenu" ); + } + else if ( GetLocalPlayerTeam() == TEAM_UNASSIGNED ) + { + if ( TFGameRules()->IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true ) + { + m_pViewPort->ShowPanel( PANEL_ARENA_TEAM, true ); + } + else + { + engine->ClientCmd( "team_ui_setup" ); + } + } + + UTIL_IncrementMapKey( "viewed" ); + } + } + else if ( !Q_strcmp( command, "intro" ) ) + { + m_pViewPort->ShowPanel( this, false ); + + if ( CheckForIntroMovie() ) + { + m_pViewPort->ShowPanel( PANEL_INTRO, true ); + } + else + { + if ( TFGameRules()->IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true ) + { + m_pViewPort->ShowPanel( PANEL_ARENA_TEAM, true ); + } + else + { + engine->ClientCmd( "team_ui_setup" ); + } + } + } + else + { + BaseClass::OnCommand( command ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapInfoMenu::Update() +{ + InvalidateLayout( false, true ); +} + +//----------------------------------------------------------------------------- +// Purpose: chooses and loads the text page to display that describes mapName map +//----------------------------------------------------------------------------- +void CTFMapInfoMenu::LoadMapPage() +{ + if ( !m_szMapName[0] ) + { + m_pMapInfo->SetText( "" ); + m_pMapImage->SetVisible( false ); + return; + } + + // load the map image (if it exists for the current map) + char szMapImage[ MAX_PATH ]; + Q_snprintf( szMapImage, sizeof( szMapImage ), "VGUI/maps/menu_photos_%s", m_szMapName ); + Q_strlower( szMapImage ); + + IMaterial *pMapMaterial = materials->FindMaterial( szMapImage, TEXTURE_GROUP_VGUI, false ); + if ( pMapMaterial && !IsErrorMaterial( pMapMaterial ) ) + { + if ( m_pMapImage ) + { + if ( !m_pMapImage->IsVisible() ) + { + m_pMapImage->SetVisible( true ); + } + + // take off the vgui/ at the beginning when we set the image + Q_snprintf( szMapImage, sizeof( szMapImage ), "maps/menu_photos_%s", m_szMapName ); + Q_strlower( szMapImage ); + + m_pMapImage->SetImage( szMapImage ); + } + } + else + { + if ( m_pMapImage && m_pMapImage->IsVisible() ) + { + m_pMapImage->SetVisible( false ); + } + } + + // try loading map descriptions from the localization files first + char mapDescriptionKey[ 64 ]; + Q_snprintf( mapDescriptionKey, sizeof( mapDescriptionKey ), "#%s_description", m_szMapName ); + Q_strlower( mapDescriptionKey ); + wchar_t* wszMapDescription = g_pVGuiLocalize->Find( mapDescriptionKey ); + if( wszMapDescription ) + { + m_pMapInfo->SetText( wszMapDescription ); + } + else + { + // try loading map descriptions from .txt files first + char mapRES[ MAX_PATH ]; + + char uilanguage[ 64 ]; + uilanguage[0] = 0; + engine->GetUILanguage( uilanguage, sizeof( uilanguage ) ); + + Q_snprintf( mapRES, sizeof( mapRES ), "maps/%s_%s.txt", m_szMapName, uilanguage ); + + // try English if the file doesn't exist for our language + if( !g_pFullFileSystem->FileExists( mapRES, "GAME" ) ) + { + Q_snprintf( mapRES, sizeof( mapRES ), "maps/%s_english.txt", m_szMapName ); + + // if the file doesn't exist for English either, try the filename without any language extension + if( !g_pFullFileSystem->FileExists( mapRES, "GAME" ) ) + { + Q_snprintf( mapRES, sizeof( mapRES ), "maps/%s.txt", m_szMapName ); + } + } + + // if no map specific description exists, load default text + if( g_pFullFileSystem->FileExists( mapRES, "GAME" ) ) + { + FileHandle_t f = g_pFullFileSystem->Open( mapRES, "rb" ); + + // read into a memory block + int fileSize = g_pFullFileSystem->Size(f); + int dataSize = fileSize + sizeof( wchar_t ); + if ( dataSize % 2 ) + ++dataSize; + wchar_t *memBlock = (wchar_t *)malloc(dataSize); + memset( memBlock, 0x0, dataSize); + int bytesRead = g_pFullFileSystem->Read(memBlock, fileSize, f); + if ( bytesRead < fileSize ) + { + // NULL-terminate based on the length read in, since Read() can transform \r\n to \n and + // return fewer bytes than we were expecting. + char *data = reinterpret_cast<char *>( memBlock ); + data[ bytesRead ] = 0; + data[ bytesRead+1 ] = 0; + } + + #ifndef WIN32 + if ( ((ucs2 *)memBlock)[0] == 0xFEFF ) + { + // convert the win32 ucs2 data to wchar_t + dataSize*=2;// need to *2 to account for ucs2 to wchar_t (4byte) growth + wchar_t *memBlockConverted = (wchar_t *)malloc(dataSize); + V_UCS2ToUnicode( (ucs2 *)memBlock, memBlockConverted, dataSize ); + free(memBlock); + memBlock = memBlockConverted; + } + #else + // null-terminate the stream (redundant, since we memset & then trimmed the transformed buffer already) + memBlock[dataSize / sizeof(wchar_t) - 1] = 0x0000; + #endif + // check the first character, make sure this a little-endian unicode file + + #if defined( _X360 ) + if ( memBlock[0] != 0xFFFE ) + #else + if ( memBlock[0] != 0xFEFF ) + #endif + { + // its a ascii char file + m_pMapInfo->SetText( reinterpret_cast<char *>( memBlock ) ); + } + else + { + // ensure little-endian unicode reads correctly on all platforms + CByteswap byteSwap; + byteSwap.SetTargetBigEndian( false ); + byteSwap.SwapBufferToTargetEndian( memBlock, memBlock, dataSize/sizeof(wchar_t) ); + + m_pMapInfo->SetText( memBlock+1 ); + } + // go back to the top of the text buffer + m_pMapInfo->GotoTextStart(); + + g_pFullFileSystem->Close( f ); + free(memBlock); + } + else + { + // try loading map descriptions from localization files next + const char *pszDescription = NULL; + char mapInfoKey[ 64 ]; + + if ( TFGameRules() && TFGameRules()->IsPowerupMode() && ( FStrEq( m_szMapName, "ctf_foundry" ) || FStrEq( m_szMapName, "ctf_gorge" ) ) ) + { + Q_snprintf( mapInfoKey, sizeof( mapInfoKey ), "#%s_beta", m_szMapName ); + } + else + { + Q_snprintf( mapInfoKey, sizeof( mapInfoKey ), "#%s", m_szMapName ); + } + + Q_strlower( mapInfoKey ); + + if( !g_pVGuiLocalize->Find( mapInfoKey ) ) + { + if ( TFGameRules() ) + { + if ( TFGameRules()->IsMannVsMachineMode() ) + { + pszDescription = "#default_mvm_description"; + } + else + { + switch ( TFGameRules()->GetGameType() ) + { + case TF_GAMETYPE_CTF: + pszDescription = "#default_ctf_description"; + break; + case TF_GAMETYPE_CP: + if ( TFGameRules()->IsInKothMode() ) + { + pszDescription = "#default_koth_description"; + } + else + { + pszDescription = "#default_cp_description"; + } + break; + case TF_GAMETYPE_ESCORT: + if ( TFGameRules()->HasMultipleTrains() ) + { + pszDescription = "#default_payload_race_description"; + } + else + { + pszDescription = "#default_payload_description"; + } + break; + case TF_GAMETYPE_ARENA: + pszDescription = "#default_arena_description"; + break; + case TF_GAMETYPE_RD: + pszDescription = "#default_rd_description"; + break; + case TF_GAMETYPE_PASSTIME: + pszDescription = "#default_passtime_description"; + break; + case TF_GAMETYPE_PD: + pszDescription = "#default_pd_description"; + break; + } + } + } + } + else + { + pszDescription = mapInfoKey; + } + + if ( pszDescription && pszDescription[0] ) + { + m_pMapInfo->SetText( pszDescription ); + } + else + { + m_pMapInfo->SetText( "" ); + } + } + } + + // we haven't loaded a valid map image for the current map + if ( m_pMapImage && !m_pMapImage->IsVisible() ) + { + if ( m_pMapInfo ) + { + m_pMapInfo->SetWide( m_pMapInfo->GetWide() + ( m_pMapImage->GetWide() * 0.75 ) ); // add in the extra space the images would have taken + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapInfoMenu::SetMapTitle() +{ + SetDialogVariable( "mapname", GetMapDisplayName( m_szMapName ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapInfoMenu::OnKeyCodePressed( KeyCode code ) +{ + m_KeyRepeat.KeyDown( code ); + + if ( code == KEY_XBUTTON_A || code == STEAMCONTROLLER_A ) + { + OnCommand( "continue" ); + } + else if ( code == STEAMCONTROLLER_B ) + { + OnCommand( "back" ); + } + else if ( code == KEY_XBUTTON_Y || code == STEAMCONTROLLER_Y ) + { + OnCommand( "intro" ); + } + else if( code == KEY_XBUTTON_UP || code == KEY_XSTICK1_UP || code == STEAMCONTROLLER_DPAD_UP ) + { + // Scroll class info text up + if ( m_pMapInfo ) + { + PostMessage( m_pMapInfo, new KeyValues("MoveScrollBarDirect", "delta", 1) ); + } + } + else if( code == KEY_XBUTTON_DOWN || code == KEY_XSTICK1_DOWN || code == STEAMCONTROLLER_DPAD_DOWN ) + { + // Scroll class info text up + if ( m_pMapInfo ) + { + PostMessage( m_pMapInfo, new KeyValues("MoveScrollBarDirect", "delta", -1) ); + } + } + else + { + BaseClass::OnKeyCodePressed( code ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapInfoMenu::OnKeyCodeReleased( vgui::KeyCode code ) +{ + m_KeyRepeat.KeyUp( code ); + + BaseClass::OnKeyCodeReleased( code ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapInfoMenu::OnThink() +{ + vgui::KeyCode code = m_KeyRepeat.KeyRepeated(); + if ( code ) + { + OnKeyCodePressed( code ); + } + + //Always hide the health... this needs to be done every frame because a message from the server keeps resetting this. + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pLocalPlayer ) + { + pLocalPlayer->m_Local.m_iHideHUD |= HIDEHUD_HEALTH; + } + + BaseClass::OnThink(); +} + diff --git a/game/client/tf/vgui/tf_mapinfomenu.h b/game/client/tf/vgui/tf_mapinfomenu.h new file mode 100644 index 0000000..4f982ea --- /dev/null +++ b/game/client/tf/vgui/tf_mapinfomenu.h @@ -0,0 +1,85 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_MAPINFOMENU_H +#define TF_MAPINFOMENU_H +#ifdef _WIN32 +#pragma once +#endif + +#include <vgui_controls/Frame.h> +#include "vgui_controls/KeyRepeat.h" + +//----------------------------------------------------------------------------- +// Purpose: displays the MapInfo menu +//----------------------------------------------------------------------------- + +class CTFMapInfoMenu : public vgui::Frame, public IViewPortPanel +{ +private: + DECLARE_CLASS_SIMPLE( CTFMapInfoMenu, vgui::Frame ); + +public: + CTFMapInfoMenu( IViewPort *pViewPort ); + virtual ~CTFMapInfoMenu(); + + virtual const char *GetName( void ){ return PANEL_MAPINFO; } + virtual void SetData( KeyValues *data ){} + virtual void Reset(){ Update(); } + virtual void Update(); + virtual bool NeedsUpdate( void ){ return false; } + virtual bool HasInputElements( void ){ return true; } + virtual void ShowPanel( bool bShow ); + + // both vgui::Frame and IViewPortPanel define these, so explicitly define them here as passthroughs to vgui + vgui::VPANEL GetVPanel( void ){ return BaseClass::GetVPanel(); } + virtual bool IsVisible(){ return BaseClass::IsVisible(); } + virtual void SetParent( vgui::VPANEL parent ){ BaseClass::SetParent( parent ); } + + virtual GameActionSet_t GetPreferredActionSet() { return GAME_ACTION_SET_IN_GAME_HUD; } + +protected: + virtual void OnKeyCodePressed(vgui::KeyCode code); + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void OnCommand( const char *command ); + virtual void OnKeyCodeReleased( vgui::KeyCode code ); + virtual void OnThink(); + +private: + // helper functions + void LoadMapPage(); + void SetMapTitle(); + bool HasViewedMovieForMap(); + bool CheckForIntroMovie(); + void CheckIntroState(); + void CheckBackContinueButtons(); + +protected: + IViewPort *m_pViewPort; + CExLabel *m_pTitle; + CExRichText *m_pMapInfo; + +#ifdef _X360 + CTFFooter *m_pFooter; +#else + CExButton *m_pContinue; + CExButton *m_pBack; + CExButton *m_pIntro; + CSCHintIcon *m_pContinueHintIcon; + CSCHintIcon *m_pBackHintIcon; + CSCHintIcon *m_pIntroHintIcon; +#endif + + vgui::ImagePanel *m_pMapImage; + + char m_szMapName[MAX_PATH]; + + vgui::CKeyRepeatHandler m_KeyRepeat; +}; + + +#endif // TF_MAPINFOMENU_H diff --git a/game/client/tf/vgui/tf_match_join_handlers.cpp b/game/client/tf/vgui/tf_match_join_handlers.cpp new file mode 100644 index 0000000..408c464 --- /dev/null +++ b/game/client/tf/vgui/tf_match_join_handlers.cpp @@ -0,0 +1,145 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "tf_shareddefs.h" +#include "tf_match_join_handlers.h" +#include "tf_gc_client.h" +#include "tf_controls.h" +#include "tf_gamerules.h" +#include "ienginevgui.h" +#include "clientmode_tf.h" +#include "tf_hud_disconnect_prompt.h" +#include <vgui_controls/AnimationController.h> +#include "vgui_int.h" +#include "tf_gc_client.h" +#include "tf_party.h" +#include "../vgui2/src/VPanel.h" + +using namespace vgui; + +#define MM_REJOIN_WAIT_TIME 1.0f +#define MM_REJOIN_PROMPT_TIMEOUT 3.f + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +IMatchJoiningHandler::IMatchJoiningHandler() +{} + +IMatchJoiningHandler::~IMatchJoiningHandler() +{} + +void IMatchJoiningHandler::JoinMatch() +{ + GTFGCClientSystem()->JoinMMMatch(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFImmediateAutoJoinHandler::CTFImmediateAutoJoinHandler() + : m_flNextAutoJoinTime( 0.f ) +{} + +void CTFImmediateAutoJoinHandler::MatchFound() +{ + // No special logic. Just try to join every 2 seconds + if ( Plat_FloatTime() > m_flNextAutoJoinTime && !GTFGCClientSystem()->BConnectedToMatchServer( false ) ) + { + JoinMatch(); + m_flNextAutoJoinTime = Plat_FloatTime() + 2.f; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFRejoinConfirmDialog* CTFMatchMakingPopupPrompJoinHandler::m_pRejoinPrompt = NULL; + +CTFMatchMakingPopupPrompJoinHandler::CTFMatchMakingPopupPrompJoinHandler() + : m_flNextRejoinThinkTime( 0.f ) +{} + + +void CTFMatchMakingPopupPrompJoinHandler::MatchFound() +{ + if ( !m_pRejoinPrompt ) + { + if ( m_flNextRejoinThinkTime == 0.f || Plat_FloatTime() >= ( m_flNextRejoinThinkTime + MM_REJOIN_PROMPT_TIMEOUT ) ) + { + m_flNextRejoinThinkTime = Plat_FloatTime() + MM_REJOIN_WAIT_TIME; + } + else if ( Plat_FloatTime() > m_flNextRejoinThinkTime ) + { + UpdatePromptState(); + m_flNextRejoinThinkTime = 0.f; + } + } +} + +void CTFMatchMakingPopupPrompJoinHandler::OnJoinLobbyInProgressCallback( bool bConfirmed, void *pContext ) +{ + GTFGCClientSystem()->RejoinLobby( bConfirmed ); + m_pRejoinPrompt = NULL; +} + +void CTFMatchMakingPopupPrompJoinHandler::UpdatePromptState() +{ + bool bHasMatch = GTFGCClientSystem()->BHaveLiveMatch(); + bool bInLiveMatch = GTFGCClientSystem()->BConnectedToMatchServer( false ); + + if ( !m_pRejoinPrompt && bHasMatch && !bInLiveMatch ) + { + if ( enginevgui == NULL || GetClientModeTFNormal()->GameUI() == NULL ) + return; + + // Check if this player is in Abandon territory, if so warn them + EAbandonGameStatus eAbandonStatus = GTFGCClientSystem()->GetAssignedMatchAbandonStatus(); + const char* pszTitle = "#TF_MM_Rejoin_Title"; + const char* pszBody = NULL; + const char* pszConfirm = "#TF_MM_Rejoin_Confirm"; + const char* pszCancel = NULL; + + switch ( eAbandonStatus ) + { + case k_EAbandonGameStatus_Safe: + pszBody = "#TF_MM_Rejoin_BaseText"; + pszCancel = "#TF_MM_Rejoin_Leave"; + break; + case k_EAbandonGameStatus_AbandonWithoutPenalty: + pszBody = "#TF_MM_Rejoin_AbandonText_NoPenalty"; + pszCancel = "#TF_MM_Rejoin_Abandon"; + break; + case k_EAbandonGameStatus_AbandonWithPenalty: + pszBody = "#TF_MM_Rejoin_AbandonText"; + pszCancel = "#TF_MM_Rejoin_Abandon"; + break; + } + + m_pRejoinPrompt = vgui::SETUP_PANEL( new CTFRejoinConfirmDialog( + pszTitle, + pszBody, + pszConfirm, + pszCancel, + &OnJoinLobbyInProgressCallback, + NULL + )); + + if ( m_pRejoinPrompt ) + { + m_pRejoinPrompt->Show(); + // VGUI is being dumb so I need to manually calculate this windows position + int sW, sT, dW, dT; + vgui::surface()->GetScreenSize( sW, sT ); + m_pRejoinPrompt->GetSize( dW, dT ); + m_pRejoinPrompt->SetPos( (sW - dW) / 2, (sT - dT) / 2 ); + } + } +} diff --git a/game/client/tf/vgui/tf_match_join_handlers.h b/game/client/tf/vgui/tf_match_join_handlers.h new file mode 100644 index 0000000..efe0153 --- /dev/null +++ b/game/client/tf/vgui/tf_match_join_handlers.h @@ -0,0 +1,58 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_MATCH_JOIN_HANDLERS_H +#define TF_MATCH_JOIN_HANDLERS_H +#ifdef _WIN32 +#pragma once +#endif + +#include <vgui_controls/EditablePanel.h> +#include "tf_controls.h" + +class IMatchJoiningHandler +{ +public: + IMatchJoiningHandler(); + virtual ~IMatchJoiningHandler(); + + virtual void MatchFound() = 0; + +protected: + + void JoinMatch(); +}; + +class CTFImmediateAutoJoinHandler : public IMatchJoiningHandler +{ +public: + CTFImmediateAutoJoinHandler(); + virtual void MatchFound() OVERRIDE; + +private: + + float m_flNextAutoJoinTime; +}; + +class CTFRejoinConfirmDialog; +class CTFMatchMakingPopupPrompJoinHandler : public IMatchJoiningHandler +{ +public: + CTFMatchMakingPopupPrompJoinHandler(); + + virtual void MatchFound() OVERRIDE; + + static void OnJoinLobbyInProgressCallback( bool bConfirmed, void *pContext ); +private: + + void UpdatePromptState(); + + static CTFRejoinConfirmDialog* m_pRejoinPrompt; + float m_flNextRejoinThinkTime; +}; + +#endif // TF_MATCH_JOIN_HANDLERS_H diff --git a/game/client/tf/vgui/tf_match_summary.cpp b/game/client/tf/vgui/tf_match_summary.cpp new file mode 100644 index 0000000..4f5e4be --- /dev/null +++ b/game/client/tf/vgui/tf_match_summary.cpp @@ -0,0 +1,1481 @@ + +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "hud.h" +#include "tf_match_summary.h" +#include "tf_hud_statpanel.h" +#include "tf_spectatorgui.h" +#include "vgui_controls/AnimationController.h" +#include "iclientmode.h" +#include "engine/IEngineSound.h" +#include "c_tf_playerresource.h" +#include "c_team.h" +#include "tf_clientscoreboard.h" +#include <vgui_controls/Label.h> +#include <vgui_controls/ImagePanel.h> +#include <vgui/ILocalize.h> +#include <vgui/ISurface.h> +#include "vgui_avatarimage.h" +#include "fmtstr.h" +#include "teamplayroundbased_gamerules.h" +#include "tf_gamerules.h" +#include "tf_logic_halloween_2014.h" +#include "tf_playermodelpanel.h" +#include "tf_mapinfo.h" +#include "c_tf_team.h" +#include "tf_pvp_rank_panel.h" +#include "tf_badge_panel.h" +#include "tf_survey_questions.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +void AddSubKeyNamed( KeyValues *pKeys, const char *pszName ); +extern ISoundEmitterSystemBase *soundemitterbase; + +#define MAX_PLAYER_MODELS 6 + +#define MS_STATE_TRANSITION_TO_STATS 17.0f +#define MS_STATE_TRANSITION_TO_MEDALS 3.0f +#define MS_STATE_TIME_BETWEEN_MEDALS 0.1f +#define MS_STATE_TIME_BETWEEN_MEDALS_CATEGORIES 0.1f + +extern ConVar tf_scoreboard_alt_class_icons; + +DECLARE_BUILD_FACTORY( TFSectionedListPanel ); + +DECLARE_HUDELEMENT( CTFMatchSummary ); + +#ifdef STAGING_ONLY +static void cc_tf_restart_match_summary() +{ + CTFMatchSummary *pMatchSummary = GET_HUDELEMENT( CTFMatchSummary ); + if (pMatchSummary) + { + pMatchSummary->InvalidateLayout(true, true); + pMatchSummary->SetVisible( false ); + pMatchSummary->SetVisible( true ); + } +} +ConCommand tf_restart_match_summary("tf_restart_match_summary", cc_tf_restart_match_summary); +#endif + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CTFMatchSummary::CTFMatchSummary( const char *pElementName ) + : CHudElement( pElementName ) + , EditablePanel( NULL, "MatchSummary" ) + , m_bXPShown( false ) +{ + vgui::Panel *pParent = g_pClientMode->GetViewport(); + SetParent( pParent ); + + m_pMainStatsContainer = new EditablePanel( this, "MainStatsContainer" ); + m_pDrawingPanel = new CDrawingPanel( this, "DrawingPanel" ); + m_pStatsBgPanel = new EditablePanel( this, "StatsBgPanel" ); + + m_pTeamScoresPanel = new EditablePanel( m_pMainStatsContainer, "TeamScoresPanel" ); + m_pParticlePanel = new CTFParticlePanel( m_pMainStatsContainer, "ParticlePanel" ); + m_pStatsLabelPanel = new EditablePanel( m_pMainStatsContainer, "StatsLabelPanel" ); + + m_pBlueTeamPanel = new EditablePanel( m_pTeamScoresPanel, "BlueTeamPanel" ); + m_pRedTeamPanel = new EditablePanel( m_pTeamScoresPanel, "RedTeamPanel" ); + + m_pPlayerListBlueParent = new EditablePanel( m_pBlueTeamPanel, "BluePlayerListParent" ); + m_pPlayerListBlue = new TFSectionedListPanel( m_pPlayerListBlueParent, "BluePlayerList" ); + m_pPlayerListRedParent = new EditablePanel( m_pRedTeamPanel, "RedPlayerListParent" ); + m_pPlayerListRed = new TFSectionedListPanel( m_pPlayerListRedParent, "RedPlayerList" ); + + m_pBlueTeamScore = new CExLabel( m_pBlueTeamPanel, "BlueTeamScore", "" ); + m_pBlueTeamScoreDropshadow = new CExLabel( m_pBlueTeamPanel, "BlueTeamScoreDropshadow", "" ); + m_pBlueTeamScoreBG = new EditablePanel( m_pBlueTeamPanel, "BlueTeamScoreBG" ); + m_pBluePlayerListBG = new EditablePanel( m_pBlueTeamPanel, "BluePlayerListBG" ); + m_pRedTeamScore = new CExLabel( m_pRedTeamPanel, "RedTeamScore", "" ); + m_pRedTeamScoreDropshadow = new CExLabel( m_pRedTeamPanel, "RedTeamScoreDropshadow", "" ); + m_pRedTeamScoreBG = new EditablePanel( m_pRedTeamPanel, "RedTeamScoreBG" ); + m_pRedPlayerListBG = new EditablePanel( m_pRedTeamPanel, "RedPlayerListBG" ); + m_pBlueMedalsPanel = new EditablePanel( m_pTeamScoresPanel, "BlueMedals" ); + m_pRedMedalsPanel = new EditablePanel( m_pTeamScoresPanel, "RedMedals" ); + m_pRedTeamImage = new vgui::ImagePanel( m_pRedTeamPanel, "RedTeamImage" ); + m_pBlueTeamImage = new vgui::ImagePanel( m_pBlueTeamPanel, "BlueTeamImage" ); + m_pRedLeaderAvatarImage = new CAvatarImagePanel( m_pRedTeamPanel, "RedLeaderAvatar" ); + m_pBlueLeaderAvatarImage = new CAvatarImagePanel( m_pBlueTeamPanel, "BlueLeaderAvatar" ); + m_pRedLeaderAvatarBG = new EditablePanel( m_pRedTeamPanel, "RedLeaderAvatarBG" ); + m_pBlueLeaderAvatarBG = new EditablePanel( m_pBlueTeamPanel, "BlueLeaderAvatarBG" ); + m_pStatsAndMedals = new CExLabel( m_pStatsLabelPanel, "StatsAndMedals", "" ); + m_pStatsAndMedalsShadow = new CExLabel( m_pStatsLabelPanel, "StatsAndMedalsShadow", "" ); + m_pRedTeamName = new CExLabel( m_pRedTeamPanel, "RedTeamLabel", "" ); + m_pBlueTeamName = new CExLabel( m_pBlueTeamPanel, "BlueTeamLabel", "" ); + m_pRedTeamWinner = new CExLabel( m_pRedTeamPanel, "RedTeamWinner", "" ); + m_pRedTeamWinnerDropshadow = new CExLabel( m_pRedTeamPanel, "RedTeamWinnerDropshadow", "" ); + m_pBlueTeamWinner = new CExLabel( m_pBlueTeamPanel, "BlueTeamWinner", "" ); + m_pBlueTeamWinnerDropshadow = new CExLabel( m_pBlueTeamPanel, "BlueTeamWinnerDropshadow", "" ); + + m_pImageList = NULL; + + m_mapAvatarsToImageList.SetLessFunc( DefLessFunc( CSteamID ) ); + m_mapAvatarsToImageList.RemoveAll(); + + Q_memset( m_SkillRatings, 0, sizeof( m_SkillRatings ) ); + + m_iCurrentState = MS_STATE_INITIAL; + m_flNextActionTime = -1; + + m_nMedalsToAward_Bronze_Blue = 0; + m_nMedalsToAward_Silver_Blue = 0; + m_nMedalsToAward_Gold_Blue = 0; + m_nMedalsToAward_Bronze_Red = 0; + m_nMedalsToAward_Silver_Red = 0; + m_nMedalsToAward_Gold_Red = 0; + + m_nMedalsRevealed = 0; + m_nNumMedalsThisUpdate = 0; + + m_bBlueGoldValueRevealed = false; + m_bBlueSilverValueRevealed = false; + m_bBlueBronzeValueRevealed = false; + m_bRedGoldValueRevealed = false; + m_bRedSilverValueRevealed = false; + m_bRedBronzeValueRevealed = false; + m_bPlayerAbandoned = false; + + m_flMedalSoundTime = -1.f; + + m_bLargeMatchGroup = false; + + Q_memset( m_iImageClass, NULL, sizeof( m_iImageClass ) ); + Q_memset( m_iImageClassAlt, NULL, sizeof( m_iImageClassAlt ) ); + + ListenForGameEvent( "competitive_victory" ); + ListenForGameEvent( "competitive_stats_update" ); + ListenForGameEvent( "player_abandoned_match" ); + ListenForGameEvent( "client_disconnect" ); + ListenForGameEvent( "show_match_summary" ); + + vgui::ivgui()->AddTickSignal( GetVPanel(), 50 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CTFMatchSummary::~CTFMatchSummary() +{ + if ( NULL != m_pImageList ) + { + delete m_pImageList; + m_pImageList = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Applies scheme settings +//----------------------------------------------------------------------------- +void CTFMatchSummary::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + m_bLargeMatchGroup = false; + + KeyValues *pConditions = NULL; + if ( TFGameRules() ) + { + const IMatchGroupDescription* pMatch = GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() ); + if ( pMatch ) + { + if ( pMatch->m_params.m_pmm_match_group_size->GetInt() > 12 ) + { + pConditions = new KeyValues( "conditions" ); + AddSubKeyNamed( pConditions, "if_large" ); + + m_bLargeMatchGroup = true; + } + } + } + + LoadControlSettings( "resource/UI/HudMatchSummary.res", NULL, NULL, pConditions ); + + if ( pConditions ) + { + pConditions->deleteThis(); + } + + if ( m_pDrawingPanel ) + { + m_pDrawingPanel->ClearAllLines(); + m_pDrawingPanel->SetType( DRAWING_PANEL_TYPE_MATCH_SUMMARY ); + m_pDrawingPanel->MakePopup(); + } + + + if ( m_pImageList ) + delete m_pImageList; + m_pImageList = new ImageList( false ); + + m_mapAvatarsToImageList.RemoveAll(); + + for ( int i = 1; i < (int)StatMedal_Max; i++ ) + { + m_iImageMedals[i] = m_pImageList->AddImage( scheme()->GetImage( g_pszCompetitiveMedalImages[i], true ) ); + } + + for ( int i = 1; i < SCOREBOARD_CLASS_ICONS; i++ ) + { + m_iImageClass[i] = m_pImageList->AddImage( scheme()->GetImage( g_pszClassIcons[i], true ) ); + m_iImageClassAlt[i] = m_pImageList->AddImage( scheme()->GetImage( g_pszClassIconsAlt[i], true ) ); + } + + int iCurrentCount = m_pImageList->GetImageCount(); + + // resize the images to our resolution + for ( int i = 0; i < iCurrentCount; i++ ) + { + int wide = 13, tall = 13; + m_pImageList->GetImage( i )->SetSize( scheme()->GetProportionalScaledValueEx( GetScheme(), wide ), scheme()->GetProportionalScaledValueEx( GetScheme(), tall ) ); + } + + // resize the images to our resolution + for ( int i = iCurrentCount; i < m_pImageList->GetImageCount(); i++ ) + { + int wide = 26, tall = 26; + m_pImageList->GetImage( i )->SetSize( scheme()->GetProportionalScaledValueEx( GetScheme(), wide ), scheme()->GetProportionalScaledValueEx( GetScheme(), tall ) ); + } + + SetPaintBackgroundEnabled( false ); + m_pTeamScoresPanel->SetPaintBackgroundEnabled( false ); + m_pPlayerListBlueParent->SetPaintBackgroundEnabled( false ); + m_pPlayerListRedParent->SetPaintBackgroundEnabled( false ); + m_pPlayerListBlue->SetPaintBackgroundEnabled( false ); + m_pPlayerListRed->SetPaintBackgroundEnabled( false ); + + m_pPlayerListBlue->SetImageList( m_pImageList, false ); + m_pPlayerListBlue->SetVisible( true ); + + m_pPlayerListRed->SetImageList( m_pImageList, false ); + m_pPlayerListRed->SetVisible( true ); + + InitPlayerList( m_pPlayerListBlue, TF_TEAM_BLUE ); + InitPlayerList( m_pPlayerListRed, TF_TEAM_RED ); + + m_hFont = pScheme->GetFont( "ScoreboardVerySmall", true ); + + m_iCurrentState = MS_STATE_INITIAL; + m_flNextActionTime = -1; + + RecalculateMedalCounts(); + + m_nMedalsRevealed = 0; + + m_bBlueGoldValueRevealed = false; + m_bBlueSilverValueRevealed = false; + m_bBlueBronzeValueRevealed = false; + m_bRedGoldValueRevealed = false; + m_bRedSilverValueRevealed = false; + m_bRedBronzeValueRevealed = false; + + m_flMedalSoundTime = -1.f; + m_flDrawingPanelTime = -1.f; + + m_pBlueMedalsPanel->SetDialogVariable( "blueteammedals_gold", "?" ); + m_pBlueMedalsPanel->SetDialogVariable( "blueteammedals_silver", "?" ); + m_pBlueMedalsPanel->SetDialogVariable( "blueteammedals_bronze", "?" ); + m_pRedMedalsPanel->SetDialogVariable( "redteammedals_gold", "?" ); + m_pRedMedalsPanel->SetDialogVariable( "redteammedals_silver", "?" ); + m_pRedMedalsPanel->SetDialogVariable( "redteammedals_bronze", "?" ); + + Update(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMatchSummary::PerformLayout() +{ + BaseClass::PerformLayout(); + + EditablePanel* pStatsContainer = FindControl< EditablePanel >( "MainStatsContainer" ); + if ( pStatsContainer && m_bLargeMatchGroup ) + { + pStatsContainer->SetPos( pStatsContainer->GetXPos(), m_iAnimStatsContainer12v12YPos ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFMatchSummary::ShouldDraw( void ) +{ + return IsVisible(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMatchSummary::SetVisible( bool state ) +{ + int iRenderGroup = gHUD.LookupRenderGroupIndexByName( "mid" ); + + if ( state ) + { + gHUD.LockRenderGroup( iRenderGroup ); + + InvalidateLayout( true, true ); + + m_iCurrentState = MS_STATE_INITIAL; + + m_flDrawingPanelTime = gpGlobals->curtime + 4.5f; + + CPvPRankPanel* pPvPRankPanel = FindControl< CPvPRankPanel >( "RankPanel" ); + if ( pPvPRankPanel ) + { + pPvPRankPanel->SetMatchGroup( TFGameRules()->GetCurrentMatchGroup() ); + } + + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "CompetitiveGame_LowerChatWindow", false ); + } + else + { + gHUD.UnlockRenderGroup( iRenderGroup ); + } + + BaseClass::SetVisible( state ); +} + +//----------------------------------------------------------------------------- +// Purpose: Used for sorting players +//----------------------------------------------------------------------------- +bool CTFMatchSummary::TFPlayerSortFunc( vgui::SectionedListPanel *list, int itemID1, int itemID2 ) +{ + KeyValues *it1 = list->GetItemData( itemID1 ); + KeyValues *it2 = list->GetItemData( itemID2 ); + Assert( it1 && it2 ); + + // first compare score + int v1 = it1->GetInt( "score" ); + int v2 = it2->GetInt( "score" ); + if ( v1 > v2 ) + return true; + else if ( v1 < v2 ) + return false; + + // if score is the same, use player index to get deterministic sort + int iPlayerIndex1 = it1->GetInt( "playerIndex" ); + int iPlayerIndex2 = it2->GetInt( "playerIndex" ); + return ( iPlayerIndex1 > iPlayerIndex2 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Inits the player list in a list panel +//----------------------------------------------------------------------------- +void CTFMatchSummary::InitPlayerList( TFSectionedListPanel *pPlayerList, int nTeam ) +{ + float flAspectRatio = engine->GetScreenAspectRatio(); + bool bStandard = flAspectRatio < 1.6f; + + pPlayerList->SetVerticalScrollbar( false ); + pPlayerList->RemoveAll(); + pPlayerList->RemoveAllSections(); + pPlayerList->AddSection( 0, "Players", TFPlayerSortFunc ); + pPlayerList->SetSectionAlwaysVisible( 0, true ); + pPlayerList->SetSectionDrawDividerBar( 0, false ); + pPlayerList->SetBorder( NULL ); + pPlayerList->SetMouseInputEnabled( false ); + pPlayerList->SetClickable( false ); + + pPlayerList->AddColumnToSection( 0, "medal", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_CENTER, pPlayerList->m_iMedalWidth ); + pPlayerList->AddColumnToSection( 0, "avatar", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iAvatarWidth ); + pPlayerList->AddColumnToSection( 0, "spacer", "", 0, pPlayerList->m_iSpacerWidth ); + pPlayerList->AddColumnToSection( 0, "name", "", 0, pPlayerList->m_iNameWidth ); + pPlayerList->AddColumnToSection( 0, "class", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iClassWidth ); + pPlayerList->AddColumnToSection( 0, "score", bStandard ? "#TF_Comp_Scoreboard_Score_Standard" : "#TF_Comp_Scoreboard_Score", SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iStatsWidth ); + pPlayerList->AddColumnToSection( 0, "score_medal", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iAwardWidth ); + pPlayerList->AddColumnToSection( 0, "kills", bStandard ? "#TF_Comp_Scoreboard_Kills_Standard" : "#TF_Comp_Scoreboard_Kills", SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iStatsWidth ); + pPlayerList->AddColumnToSection( 0, "kills_medal", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iAwardWidth ); + pPlayerList->AddColumnToSection( 0, "damage", bStandard ? "#TF_Comp_Scoreboard_Damage_Standard" : "#TF_Comp_Scoreboard_Damage", SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iStatsWidth ); + pPlayerList->AddColumnToSection( 0, "damage_medal", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iAwardWidth ); + pPlayerList->AddColumnToSection( 0, "healing", bStandard ? "#TF_Comp_Scoreboard_Healing_Standard" : "#TF_Comp_Scoreboard_Healing", SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iStatsWidth ); + pPlayerList->AddColumnToSection( 0, "healing_medal", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iAwardWidth ); + pPlayerList->AddColumnToSection( 0, "support", bStandard ? "#TF_Comp_Scoreboard_Support_Standard" : "#TF_Comp_Scoreboard_Support", SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iStatsWidth ); + pPlayerList->AddColumnToSection( 0, "support_medal", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iAwardWidth ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMatchSummary::Update( void ) +{ + UpdateTeamInfo(); + UpdatePlayerList(); + UpdateBadgePanels( m_pRedBadgePanels, m_pPlayerListRed ); + UpdateBadgePanels( m_pBlueBadgePanels, m_pPlayerListBlue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Updates information about teams +//----------------------------------------------------------------------------- +void CTFMatchSummary::UpdateTeamInfo() +{ + bool bUseWinnerLabel = false; + if ( GetGlobalTFTeam( TF_TEAM_RED ) && GetGlobalTFTeam( TF_TEAM_BLUE ) ) + { + if ( GetGlobalTFTeam( TF_TEAM_RED )->Get_Score() == GetGlobalTFTeam( TF_TEAM_BLUE )->Get_Score() ) + { + bUseWinnerLabel = true; + } + } + + int nWinningTeam = TEAM_INVALID; + if ( TFGameRules() ) + { + nWinningTeam = TFGameRules()->GetWinningTeam(); + } + + for ( int teamIndex = TF_TEAM_RED; teamIndex <= TF_TEAM_BLUE; teamIndex++ ) + { + C_TFTeam *team = GetGlobalTFTeam( teamIndex ); + if ( team ) + { + // choose dialog variables to set depending on team + const char *pDialogVarTeamName = ""; + const char *pDialogVarTeamScore = ""; + const char *pDialogVarWinner = ""; + vgui::EditablePanel *pOwner = NULL; + + switch ( teamIndex ) + { + case TF_TEAM_RED: + pDialogVarTeamName = "redteamname"; + pDialogVarTeamScore = "redteamscore"; + pDialogVarWinner = "redteamwinner"; + pOwner = m_pRedTeamPanel; + break; + case TF_TEAM_BLUE: + pDialogVarTeamName = "blueteamname"; + pDialogVarTeamScore = "blueteamscore"; + pDialogVarWinner = "blueteamwinner"; + pOwner = m_pBlueTeamPanel; + break; + default: + Assert( false ); + break; + } + + if ( !pOwner ) + return; + + // set the team name + pOwner->SetDialogVariable( pDialogVarTeamName, team->Get_Localized_Name() ); + + if ( bUseWinnerLabel ) + { + const char *pszLabel = ""; + if ( teamIndex == nWinningTeam ) + { + if ( team->GetNumPlayers() > 1 ) + { + pszLabel = "#TF_Winners"; + } + else + { + pszLabel = "#TF_Winner"; + } + } + + pOwner->SetDialogVariable( pDialogVarTeamScore, "" ); + pOwner->SetDialogVariable( pDialogVarWinner, g_pVGuiLocalize->Find( pszLabel ) ); + } + else + { + pOwner->SetDialogVariable( pDialogVarTeamScore, team->Get_Score() ); + pOwner->SetDialogVariable( pDialogVarWinner, "" ); + } + } + } + + bool bShowAvatars = g_TF_PR && g_TF_PR->HasPremadeParties(); + + if ( bShowAvatars ) + { + m_pRedLeaderAvatarImage->SetPlayer( GetSteamIDForPlayerIndex( g_TF_PR->GetPartyLeaderRedTeamIndex() ), k_EAvatarSize64x64 ); + m_pRedLeaderAvatarImage->SetShouldDrawFriendIcon( false ); + m_pBlueLeaderAvatarImage->SetPlayer( GetSteamIDForPlayerIndex( g_TF_PR->GetPartyLeaderBlueTeamIndex() ), k_EAvatarSize64x64 ); + m_pBlueLeaderAvatarImage->SetShouldDrawFriendIcon( false ); + } + + m_pRedLeaderAvatarImage->SetVisible( bShowAvatars ); + m_pRedLeaderAvatarBG->SetVisible( bShowAvatars ); + m_pRedTeamName->SetVisible( bShowAvatars ); + m_pRedTeamImage->SetVisible( !bShowAvatars ); + + m_pBlueLeaderAvatarImage->SetVisible( bShowAvatars ); + m_pBlueLeaderAvatarBG->SetVisible( bShowAvatars ); + m_pBlueTeamName->SetVisible( bShowAvatars ); + m_pBlueTeamImage->SetVisible( !bShowAvatars ); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the last medal (column) added so we can display some effects +//----------------------------------------------------------------------------- +matchsummary_columns_t CTFMatchSummary::InternalAddMedalKeyValues( int iIndex, StatMedal_t eMedal, KeyValues *pKeyValues, int nTotalMedals /*= -1*/ ) +{ + int nMedal = (int)eMedal; + matchsummary_columns_t retVal = MS_COLUMN_INVALID; + + if ( ( nTotalMedals < 0 ) || ( m_nNumMedalsThisUpdate <= nTotalMedals ) ) + { + if ( m_SkillRatings[iIndex].nScoreRank == nMedal ) + { + pKeyValues->SetInt( "score_medal", m_iImageMedals[nMedal] ); + if ( nTotalMedals >= 0 ) + { + m_nNumMedalsThisUpdate++; + } + + if ( ( nTotalMedals >= 0 ) && ( m_nNumMedalsThisUpdate > nTotalMedals ) ) + { + retVal = MS_COLUMN_SCORE_MEDAL; + } + } + } + + if ( ( nTotalMedals < 0 ) || ( m_nNumMedalsThisUpdate <= nTotalMedals ) ) + { + if ( m_SkillRatings[iIndex].nKillsRank == nMedal ) + { + pKeyValues->SetInt( "kills_medal", m_iImageMedals[nMedal] ); + if ( nTotalMedals >= 0 ) + { + m_nNumMedalsThisUpdate++; + } + + if ( ( nTotalMedals >= 0 ) && ( m_nNumMedalsThisUpdate > nTotalMedals ) ) + { + retVal = MS_COLUMN_KILLS_MEDAL; + } + } + } + + if ( ( nTotalMedals < 0 ) || ( m_nNumMedalsThisUpdate <= nTotalMedals ) ) + { + if ( m_SkillRatings[iIndex].nDamageRank == nMedal ) + { + pKeyValues->SetInt( "damage_medal", m_iImageMedals[nMedal] ); + if ( nTotalMedals >= 0 ) + { + m_nNumMedalsThisUpdate++; + } + + if ( ( nTotalMedals >= 0 ) && ( m_nNumMedalsThisUpdate > nTotalMedals ) ) + { + retVal = MS_COLUMN_DAMAGE_MEDAL; + } + } + } + + if ( ( nTotalMedals < 0 ) || ( m_nNumMedalsThisUpdate <= nTotalMedals ) ) + { + if ( m_SkillRatings[iIndex].nHealingRank == nMedal ) + { + pKeyValues->SetInt( "healing_medal", m_iImageMedals[nMedal] ); + if ( nTotalMedals >= 0 ) + { + m_nNumMedalsThisUpdate++; + } + + if ( ( nTotalMedals >= 0 ) && ( m_nNumMedalsThisUpdate > nTotalMedals ) ) + { + retVal = MS_COLUMN_HEALING_MEDAL; + } + } + } + + if ( ( nTotalMedals < 0 ) || ( m_nNumMedalsThisUpdate <= nTotalMedals ) ) + { + if ( m_SkillRatings[iIndex].nSupportRank == nMedal ) + { + pKeyValues->SetInt( "support_medal", m_iImageMedals[nMedal] ); + if ( nTotalMedals >= 0 ) + { + m_nNumMedalsThisUpdate++; + } + + if ( ( nTotalMedals >= 0 ) && ( m_nNumMedalsThisUpdate > nTotalMedals ) ) + { + retVal = MS_COLUMN_SUPPORT_MEDAL; + } + } + } + + return retVal; +} + +//----------------------------------------------------------------------------- +// Purpose: Updates the player list +//----------------------------------------------------------------------------- +void CTFMatchSummary::UpdatePlayerList() +{ + m_pPlayerListRed->RemoveAll(); + m_pPlayerListRed->ClearAllColorOverrideForCell(); + + m_pPlayerListBlue->RemoveAll(); + m_pPlayerListBlue->ClearAllColorOverrideForCell(); + + if ( !g_TF_PR ) + return; + + m_nNumMedalsThisUpdate = 0; + + for ( int playerIndex = 1; playerIndex <= MAX_PLAYERS; playerIndex++ ) + { + if ( g_PR->IsConnected( playerIndex ) || g_PR->IsValid( playerIndex ) ) + { + TFSectionedListPanel *pPlayerList = NULL; + int nTeam = g_PR->GetTeam( playerIndex ); + switch ( nTeam ) + { + case TF_TEAM_BLUE: + pPlayerList = m_pPlayerListBlue; + break; + case TF_TEAM_RED: + pPlayerList = m_pPlayerListRed; + break; + } + if ( null == pPlayerList ) + continue; + + KeyValues *pKeyValues = new KeyValues( "data" ); + pKeyValues->SetInt( "playerIndex", playerIndex ); + + // this is just a placeholder in the sectioned list panel + pKeyValues->SetInt( "medal", 0 ); + + pKeyValues->SetString( "name", g_TF_PR->GetPlayerName( playerIndex ) ); + pKeyValues->SetInt( "score", g_TF_PR->GetTotalScore( playerIndex ) ); + + int iClass = g_TF_PR->GetPlayerClass( playerIndex ); + if ( iClass >= TF_FIRST_NORMAL_CLASS && iClass <= TF_LAST_NORMAL_CLASS ) + { + pKeyValues->SetInt( "class", tf_scoreboard_alt_class_icons.GetBool() ? m_iImageClassAlt[iClass] : m_iImageClass[iClass] ); + } + else + { + pKeyValues->SetInt( "class", 0 ); + } + + pKeyValues->SetInt( "kills", g_TF_PR->GetPlayerScore( playerIndex ) ); + pKeyValues->SetInt( "damage", g_TF_PR->GetDamage( playerIndex ) ); + pKeyValues->SetInt( "healing", g_TF_PR->GetHealing( playerIndex ) ); + + int nSupport = g_TF_PR->GetDamageAssist( playerIndex ) + + g_TF_PR->GetHealingAssist( playerIndex ) + + g_TF_PR->GetDamageBlocked( playerIndex ) + + ( g_TF_PR->GetBonusPoints( playerIndex ) * 25 ); + pKeyValues->SetInt( "support", nSupport ); + + matchsummary_columns_t eParticleColumn = MS_COLUMN_INVALID; + StatMedal_t eParticleMedal = StatMedal_None; + + if ( m_iCurrentState == MS_STATE_GOLD_MEDALS ) + { + // we can add the bronze and silver since we've already processed those + InternalAddMedalKeyValues( playerIndex, StatMedal_Bronze, pKeyValues ); + InternalAddMedalKeyValues( playerIndex, StatMedal_Silver, pKeyValues ); + eParticleColumn = InternalAddMedalKeyValues( playerIndex, StatMedal_Gold, pKeyValues, m_nMedalsRevealed ); + eParticleMedal = StatMedal_Gold; + } + else if ( m_iCurrentState == MS_STATE_SILVER_MEDALS ) + { + // we can add the bronze since we've already processed those + InternalAddMedalKeyValues( playerIndex, StatMedal_Bronze, pKeyValues ); + eParticleColumn = InternalAddMedalKeyValues( playerIndex, StatMedal_Silver, pKeyValues, m_nMedalsRevealed ); + eParticleMedal = StatMedal_Silver; + } + else if ( m_iCurrentState == MS_STATE_BRONZE_MEDALS ) + { + eParticleColumn = InternalAddMedalKeyValues( playerIndex, StatMedal_Bronze, pKeyValues, m_nMedalsRevealed ); + eParticleMedal = StatMedal_Bronze; + } + + UpdatePlayerAvatar( playerIndex, pKeyValues ); + + int itemID = pPlayerList->AddItem( 0, pKeyValues ); + pPlayerList->SetItemFgColor( itemID, g_PR->GetTeamColor( nTeam ) ); + // Green background for rematch folks + pPlayerList->SetItemBgColor( itemID, Color( 80, 80, 80, 80 ) ); + pPlayerList->SetItemBgHorizFillInset( itemID, pPlayerList->m_iHorizFillInset ); + pPlayerList->SetItemFont( itemID, m_hFont ); + + // This highlights the local player in grey + if ( playerIndex == GetLocalPlayerIndex() ) + { + pPlayerList->SetSelectedItem( itemID ); + } + + // loop through and setup our medal color overrides + KeyValues *pKey = pKeyValues->FindKey( "score_medal" ); + if ( pKey && pKey->GetInt() ) + { + int nMedal = pKey->GetInt(); + pPlayerList->SetColorOverrideForCell( 0, itemID, MS_COLUMN_SCORE, ( nMedal == (int)StatMedal_Bronze ) ? m_clrBronzeMedal : ( nMedal == (int)StatMedal_Silver ) ? m_clrSilverMedal : m_clrGoldMedal ); + } + pKey = pKeyValues->FindKey( "kills_medal" ); + if ( pKey && pKey->GetInt() ) + { + int nMedal = pKey->GetInt(); + pPlayerList->SetColorOverrideForCell( 0, itemID, MS_COLUMN_KILLS, ( nMedal == (int)StatMedal_Bronze ) ? m_clrBronzeMedal : ( nMedal == (int)StatMedal_Silver ) ? m_clrSilverMedal : m_clrGoldMedal ); + } + pKey = pKeyValues->FindKey( "damage_medal" ); + if ( pKey && pKey->GetInt() ) + { + int nMedal = pKey->GetInt(); + pPlayerList->SetColorOverrideForCell( 0, itemID, MS_COLUMN_DAMAGE, ( nMedal == (int)StatMedal_Bronze ) ? m_clrBronzeMedal : ( nMedal == (int)StatMedal_Silver ) ? m_clrSilverMedal : m_clrGoldMedal ); + } + pKey = pKeyValues->FindKey( "healing_medal" ); + if ( pKey && pKey->GetInt() ) + { + int nMedal = pKey->GetInt(); + pPlayerList->SetColorOverrideForCell( 0, itemID, MS_COLUMN_HEALING, ( nMedal == (int)StatMedal_Bronze ) ? m_clrBronzeMedal : ( nMedal == (int)StatMedal_Silver ) ? m_clrSilverMedal : m_clrGoldMedal ); + } + pKey = pKeyValues->FindKey( "support_medal" ); + if ( pKey && pKey->GetInt() ) + { + int nMedal = pKey->GetInt(); + pPlayerList->SetColorOverrideForCell( 0, itemID, MS_COLUMN_SUPPORT, ( nMedal == (int)StatMedal_Bronze ) ? m_clrBronzeMedal : ( nMedal == (int)StatMedal_Silver ) ? m_clrSilverMedal : m_clrGoldMedal ); + } + + pKeyValues->deleteThis(); + + if ( ( eParticleColumn > MS_COLUMN_INVALID ) && ( eParticleMedal > StatMedal_None ) ) + { + int nPanelXPos, nPanelYPos, nPanelWide, nPanelTall; + pPlayerList->GetMaxCellBounds( itemID, eParticleColumn, nPanelXPos, nPanelYPos, nPanelWide, nPanelTall ); + + FireMedalEffects( pPlayerList, nPanelXPos, nPanelYPos, nPanelWide, nPanelTall, eParticleMedal ); + } + } + } + + m_pPlayerListRed->SetSectionFgColor( 0, g_PR->GetTeamColor( TF_TEAM_RED ) ); + m_pPlayerListBlue->SetSectionFgColor( 0, g_PR->GetTeamColor( TF_TEAM_BLUE ) ); + + // force the lists to PerformLayout() now so we can update our rank images after we return + m_pPlayerListRed->InvalidateLayout( true ); + m_pPlayerListBlue->InvalidateLayout( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMatchSummary::FireMedalEffects( Panel *pPanel, int nPanelXPos, int nPanelYPos, int nPanelWide, int nPanelTall, StatMedal_t eParticleMedal ) +{ + if ( !pPanel ) + return; + + int nPanelCenterX = nPanelXPos + ( nPanelWide / 2 ); + int nPanelCenterY = nPanelYPos + ( nPanelTall / 2 ); + + int iItemAbsX, iItemAbsY; + vgui::ipanel()->GetAbsPos( pPanel->GetParent()->GetVPanel(), iItemAbsX, iItemAbsY ); + + int x = iItemAbsX + nPanelCenterX; + int y = iItemAbsY + nPanelCenterY; + + const char *pszSoundEffect = "MatchMaking.None"; + const char *pszParticleEffect = "mvm_loot_smoke"; + if ( eParticleMedal == StatMedal_Bronze ) + { + pszSoundEffect = "MatchMaking.Bronze"; + pszParticleEffect = "mvm_loot_explosion"; + } + else if ( eParticleMedal == StatMedal_Silver ) + { + pszSoundEffect = "MatchMaking.Silver"; + pszParticleEffect = "mvm_loot_explosion"; + } + else if ( eParticleMedal == StatMedal_Gold ) + { + pszSoundEffect = "MatchMaking.Gold"; + pszParticleEffect = "mvm_pow_gold_seq_firework_mid"; + } + + m_pParticlePanel->FireParticleEffect( pszParticleEffect, x, y, 0.2f, false ); + + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pLocalPlayer ) + { + pLocalPlayer->EmitSound( pszSoundEffect ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMatchSummary::UpdateBadgePanels( CUtlVector<CTFBadgePanel*> &pBadgePanels, TFSectionedListPanel *pPlayerList ) +{ + if ( !TFGameRules() ) + return; + + const IMatchGroupDescription *pMatchDesc = GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() ); + const IProgressionDesc *pProgressionDesc = pMatchDesc ? pMatchDesc->m_pProgressionDesc : NULL; + if ( pProgressionDesc ) + { + if ( pPlayerList ) + { + int iNumPanels = 0; + int parentTall = pPlayerList->GetTall(); + CTFBadgePanel *pPanel = NULL; + + for ( int i = 0; i < pPlayerList->GetItemCount(); i++ ) + { + KeyValues *pKeyValues = pPlayerList->GetItemData( i ); + if ( !pKeyValues ) + continue; + + const CSteamID steamID = GetSteamIDForPlayerIndex( pKeyValues->GetInt( "playerIndex" ) ); +#ifdef STAGING_ONLY + if ( steamID.IsValid() || tf_test_match_summary.GetBool() ) +#else + if ( steamID.IsValid() ) +#endif // STAGING_ONLY + { + if ( iNumPanels >= pBadgePanels.Count() ) + { + pPanel = new CTFBadgePanel( m_pMainStatsContainer, "BadgePanel" ); + pPanel->MakeReadyForUse(); + pPanel->SetVisible( true ); + pPanel->SetZPos( 9999 ); + pBadgePanels.AddToTail( pPanel ); + } + else + { + pPanel = pBadgePanels[iNumPanels]; + } + + int x, y, wide, tall; + pPlayerList->GetMaxCellBounds( i, 0, x, y, wide, tall ); + + if ( y + tall > parentTall ) + continue; + + if ( !pPanel->IsVisible() ) + { + pPanel->SetVisible( true ); + } + + int xParent = 0, yParent = 0; + if ( pPlayerList->GetParent() ) + { + pPlayerList->GetParent()->GetPos( xParent, yParent ); + } + + int xGrandParent = 0, yGrandParent = 0; + if ( pPlayerList->GetParent()->GetParent() ) + { + pPlayerList->GetParent()->GetParent()->GetPos( xGrandParent, yGrandParent ); + } + + int nPanelXPos, nPanelYPos, nPanelWide, nPanelTall; + pPanel->GetBounds( nPanelXPos, nPanelYPos, nPanelWide, nPanelTall ); + + if ( ( nPanelXPos != xGrandParent + xParent + x ) + || ( nPanelYPos != yGrandParent + yParent + y ) + || ( nPanelWide != wide ) + || ( nPanelTall != tall ) ) + { + pPanel->SetBounds( xGrandParent + xParent + x, yGrandParent + yParent + y, wide, tall ); + pPanel->InvalidateLayout( true, true ); + } + + pPanel->SetupBadge( pProgressionDesc, steamID ); + iNumPanels++; + } + } + + // hide any unused images + for ( int i = iNumPanels; i < pBadgePanels.Count(); i++ ) + { + if ( pBadgePanels[i]->IsVisible() ) + { + pBadgePanels[i]->SetVisible( false ); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMatchSummary::UpdatePlayerAvatar( int playerIndex, KeyValues *kv ) +{ + if ( !g_PR ) + return; + + uint32 iAccountID = g_PR->GetAccountID( playerIndex ); + if ( iAccountID > 0 ) + { + // Update their avatar + if ( kv && steamapicontext->SteamFriends() && steamapicontext->SteamUtils() ) + { + CSteamID steamIDForPlayer( iAccountID, 1, GetUniverse(), k_EAccountTypeIndividual ); + + // See if we already have that avatar in our list + int iMapIndex = m_mapAvatarsToImageList.Find( steamIDForPlayer ); + int iImageIndex; + if ( iMapIndex == m_mapAvatarsToImageList.InvalidIndex() ) + { + CAvatarImage *pImage = new CAvatarImage(); + pImage->SetAvatarSteamID( steamIDForPlayer ); + pImage->SetAvatarSize( 32, 32 ); // Deliberately non scaling + iImageIndex = m_pImageList->AddImage( pImage ); + + m_mapAvatarsToImageList.Insert( steamIDForPlayer, iImageIndex ); + } + else + { + iImageIndex = m_mapAvatarsToImageList[iMapIndex]; + } + + kv->SetInt( "avatar", iImageIndex ); + + CAvatarImage *pAvIm = (CAvatarImage *)m_pImageList->GetImage( iImageIndex ); + pAvIm->UpdateFriendStatus(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Event handler +//----------------------------------------------------------------------------- +void CTFMatchSummary::FireGameEvent( IGameEvent *event ) +{ + const char *type = event->GetName(); + + if ( FStrEq( type, "competitive_victory" ) ) + { + Q_memset( m_SkillRatings, 0, sizeof( m_SkillRatings ) ); + Leaderboards_LadderRefresh(); + } + else if ( FStrEq( type, "competitive_stats_update" ) ) + { + int iIndex = event->GetInt( "index" ); + Assert( iIndex > 0 && iIndex <= MAX_PLAYERS ); + if ( iIndex > 0 && iIndex <= MAX_PLAYERS ) + { + m_SkillRatings[iIndex].unRating = event->GetInt( "rating" ); // Rank + m_SkillRatings[iIndex].nDelta = event->GetInt( "delta" ); + m_SkillRatings[iIndex].nScoreRank = event->GetInt( "score_rank" ); // Medal for Score (Gold, Silver, Bronze, or nothing) + m_SkillRatings[iIndex].nKillsRank = event->GetInt( "kills_rank" ); // Medal for Kills + m_SkillRatings[iIndex].nDamageRank = event->GetInt( "damage_rank" ); // Medal for Damage + m_SkillRatings[iIndex].nHealingRank = event->GetInt( "healing_rank" ); // Medal for Healing + m_SkillRatings[iIndex].nSupportRank = event->GetInt( "support_rank" ); // Medal for Rank + + RecalculateMedalCounts(); + } + } + else if ( FStrEq( type, "player_abandoned_match" ) ) + { + m_bPlayerAbandoned = true; + } + else if ( FStrEq( type, "client_disconnect" ) ) + { + m_bPlayerAbandoned = false; + } + else if ( FStrEq( type, "show_match_summary" ) ) + { + SetVisible( true ); + + if ( m_pPlayerListRedParent->GetParent() ) + { + int nRedBadgeOffset = m_pPlayerListRedParent->GetParent()->GetXPos(); + FOR_EACH_VEC( m_pRedBadgePanels, i ) + { + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedBadgePanels[i], "xpos", m_pRedBadgePanels[i]->GetXPos() - nRedBadgeOffset, 0.25, 0.25, vgui::AnimationController::INTERPOLATOR_ACCEL ); + } + } + + if ( m_pPlayerListBlueParent->GetParent() ) + { + int nBlueBadgeOffset = m_pPlayerListBlueParent->GetParent()->GetXPos(); + FOR_EACH_VEC( m_pBlueBadgePanels, i ) + { + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pBlueBadgePanels[i], "xpos", m_pBlueBadgePanels[i]->GetXPos() - nBlueBadgeOffset, 0.25, 0.25, vgui::AnimationController::INTERPOLATOR_ACCEL ); + } + } + + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_pTeamScoresPanel, "HudMatchSummary_SlideInPanels", false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMatchSummary::InternalUpdateMedalCountForType( int iTeam, StatMedal_t eMedal ) +{ + switch ( eMedal ) + { + case StatMedal_Bronze: + if ( iTeam == TF_TEAM_RED ) + { + m_nMedalsToAward_Bronze_Red++; + } + else if ( iTeam == TF_TEAM_BLUE ) + { + m_nMedalsToAward_Bronze_Blue++; + } + break; + case StatMedal_Silver: + if ( iTeam == TF_TEAM_RED ) + { + m_nMedalsToAward_Silver_Red++; + } + else if ( iTeam == TF_TEAM_BLUE ) + { + m_nMedalsToAward_Silver_Blue++; + } + break; + case StatMedal_Gold: + if ( iTeam == TF_TEAM_RED ) + { + m_nMedalsToAward_Gold_Red++; + } + else if ( iTeam == TF_TEAM_BLUE ) + { + m_nMedalsToAward_Gold_Blue++; + } + break; + case StatMedal_None: + default: + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMatchSummary::RecalculateMedalCounts() +{ + m_nMedalsToAward_Bronze_Blue = 0; + m_nMedalsToAward_Silver_Blue = 0; + m_nMedalsToAward_Gold_Blue = 0; + m_nMedalsToAward_Bronze_Red = 0; + m_nMedalsToAward_Silver_Red = 0; + m_nMedalsToAward_Gold_Red = 0; + + if ( !g_PR ) + return; + + for ( int playerIndex = 1; playerIndex <= MAX_PLAYERS; playerIndex++ ) + { + if ( g_PR->IsConnected( playerIndex ) || g_PR->IsValid( playerIndex ) ) + { + int nTeam = g_PR->GetTeam( playerIndex ); + if ( nTeam >= FIRST_GAME_TEAM ) + { + InternalUpdateMedalCountForType( nTeam, (StatMedal_t)( m_SkillRatings[playerIndex].nScoreRank ) ); + InternalUpdateMedalCountForType( nTeam, (StatMedal_t)( m_SkillRatings[playerIndex].nKillsRank ) ); + InternalUpdateMedalCountForType( nTeam, (StatMedal_t)( m_SkillRatings[playerIndex].nDamageRank ) ); + InternalUpdateMedalCountForType( nTeam, (StatMedal_t)( m_SkillRatings[playerIndex].nHealingRank ) ); + InternalUpdateMedalCountForType( nTeam, (StatMedal_t)( m_SkillRatings[playerIndex].nSupportRank ) ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMatchSummary::OnTick() +{ + BaseClass::OnTick(); + + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + + if ( !IsVisible() || !pLocalPlayer ) + return; + + const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() ); + if ( !pMatchDesc ) + return; + + if ( pMatchDesc->m_params.m_bAllowDrawingAtMatchSummary + && m_pDrawingPanel + && ( m_flDrawingPanelTime > 0 ) + && ( m_flDrawingPanelTime < gpGlobals->curtime ) ) + { + m_pDrawingPanel->SetVisible( true ); + m_pDrawingPanel->SetKeyBoardInputEnabled( false ); + m_flDrawingPanelTime = -1.f; + + if ( pLocalPlayer ) + { + pLocalPlayer->EmitSound( "Announcer.SummaryScreenWinners" ); + } + } + + int nMedalsToAward_Bronze_Total = m_nMedalsToAward_Bronze_Blue + m_nMedalsToAward_Bronze_Red; + int nMedalsToAward_Silver_Total = m_nMedalsToAward_Silver_Blue + m_nMedalsToAward_Silver_Red; + int nMedalsToAward_Gold_Total = m_nMedalsToAward_Gold_Blue + m_nMedalsToAward_Gold_Red; + + bool bShowPerformanceMedals = ShowPerformanceMedals(); + bool bMapHasMatchSummaryStage = ( TFGameRules() && TFGameRules()->MapHasMatchSummaryStage() ); + + +#ifdef STAGING_ONLY + bool bUseMatchSummaryStage = tf_test_match_summary.GetBool() || ( pMatchDesc && pMatchDesc->m_params.m_bUseMatchSummaryStage ); +#else + bool bUseMatchSummaryStage = ( pMatchDesc && pMatchDesc->m_params.m_bUseMatchSummaryStage ); +#endif + + switch ( m_iCurrentState ) + { + case MS_STATE_INITIAL: + { + bool bUseStage = ( bMapHasMatchSummaryStage && bUseMatchSummaryStage ); + + if ( GTFGCClientSystem()->GetSurveyRequest().has_match_id() ) + { + Panel* pSurveyPanel = CreateSurveyQuestionPanel( this, GTFGCClientSystem()->GetSurveyRequest() ); + pSurveyPanel->MakePopup(); + } + + m_iCurrentState = MS_STATE_DRAWING; + m_flNextActionTime = bUseStage ? gpGlobals->curtime + MS_STATE_TRANSITION_TO_STATS : gpGlobals->curtime + 2.f; + m_bXPShown = false; + + if ( !bUseStage ) + { + // if we're not using the stage we'll just show the doors and then skip to stats with no drawing + if ( m_pDrawingPanel ) + { + m_pDrawingPanel->SetVisible( false ); + } + } + break; + } + case MS_STATE_DRAWING: + { + if ( gpGlobals->curtime > m_flNextActionTime ) + { + if ( m_pDrawingPanel ) + { + m_pDrawingPanel->SetVisible( false ); + } + + if ( m_pStatsBgPanel ) + { + m_pStatsBgPanel->SetVisible( true ); + } + + if ( m_pStatsLabelPanel ) + { + m_pStatsLabelPanel->SetVisible( true ); + } + + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pStatsLabelPanel, "ypos", m_bLargeMatchGroup ? m_iAnimStatsLabelPanel12v12YPos : m_iAnimStatsLabelPanel6v6YPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pBlueMedalsPanel, "ypos", m_iAnimBlueMedalsYPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedMedalsPanel, "ypos", m_iAnimRedMedalsYPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pBlueTeamName, "ypos", m_bLargeMatchGroup ? m_iAnimBlueTeamLabel12v12YPos : m_iAnimBlueTeamLabel6v6YPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedTeamName, "ypos", m_bLargeMatchGroup ? m_iAnimRedTeamLabel12v12YPos : m_iAnimRedTeamLabel6v6YPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pPlayerListBlueParent, "wide", m_iAnimBluePlayerListParent, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pBlueTeamScore, "wide", m_iAnimBlueTeamScore, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pBlueTeamScoreDropshadow, "wide", m_iAnimBlueTeamScoreDropshadow, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pBlueTeamScoreBG, "wide", m_iAnimBlueTeamScoreBG, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pBluePlayerListBG, "wide", m_iAnimBluePlayerListBG, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedTeamScore, "wide", m_iAnimRedTeamScoreWide, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedTeamScore, "xpos", m_iAnimRedTeamScoreXPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedTeamScoreDropshadow, "wide", m_iAnimRedTeamScoreDropshadowWide, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedTeamScoreDropshadow, "xpos", m_iAnimRedTeamScoreDropshadowXPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedTeamScoreBG, "wide", m_iAnimRedTeamScoreBGWide, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedTeamScoreBG, "xpos", m_iAnimRedTeamScoreBGXPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pPlayerListRedParent, "wide", m_iAnimRedPlayerListParentWide, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pPlayerListRedParent, "xpos", m_iAnimRedPlayerListParentXPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedPlayerListBG, "wide", m_iAnimRedPlayerListBGWide, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedPlayerListBG, "xpos", m_iAnimRedPlayerListBGXPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pBlueTeamWinner, "wide", m_iAnimBlueTeamScore, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pBlueTeamWinnerDropshadow, "wide", m_iAnimBlueTeamScoreDropshadow, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedTeamWinner, "wide", m_iAnimRedTeamScoreWide, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedTeamWinner, "xpos", m_iAnimRedTeamScoreXPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedTeamWinnerDropshadow, "wide", m_iAnimRedTeamScoreDropshadowWide, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedTeamWinnerDropshadow, "xpos", m_iAnimRedTeamScoreDropshadowXPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + + int nRedBadgeOffset = m_pPlayerListRedParent->GetXPos() - m_iAnimRedPlayerListParentXPos; + FOR_EACH_VEC( m_pRedBadgePanels, i ) + { + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedBadgePanels[i], "xpos", m_pRedBadgePanels[i]->GetXPos() - nRedBadgeOffset, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL ); + + } + + float flDelay = MS_STATE_TRANSITION_TO_MEDALS; + + if ( pLocalPlayer ) + { + if ( bShowPerformanceMedals ) + { + const char *pszEntryName = UTIL_GetRandomSoundFromEntry( "Announcer.CompSummaryScreenOutlierQuestion" ); + if ( pszEntryName && pszEntryName[0] ) + { + flDelay = enginesound->GetSoundDuration( pszEntryName ); + pLocalPlayer->EmitSound( pszEntryName ); + } + } + pLocalPlayer->EmitSound( "MatchMaking.ScoreboardPanelSlide" ); + } + + m_iCurrentState = MS_STATE_STATS; + m_flNextActionTime = gpGlobals->curtime + flDelay; + + m_pRedMedalsPanel->SetVisible( bShowPerformanceMedals ); + m_pBlueMedalsPanel->SetVisible( bShowPerformanceMedals ); + + m_pStatsAndMedals->SetText( bShowPerformanceMedals ? g_pVGuiLocalize->Find( "#TF_CompSummary_StatsAndMedals" ) : L"" ); + m_pStatsAndMedalsShadow->SetText( bShowPerformanceMedals ? g_pVGuiLocalize->Find( "#TF_CompSummary_StatsAndMedals" ) : L"" ); + } + break; + } + case MS_STATE_STATS: + { + if ( gpGlobals->curtime > m_flNextActionTime ) + { + m_iCurrentState = bShowPerformanceMedals ? MS_STATE_BRONZE_MEDALS : MS_STATE_FINAL; + m_nMedalsRevealed = 0; + m_flNextActionTime = -1; + } + break; + } + case MS_STATE_BRONZE_MEDALS: + { + if ( gpGlobals->curtime > m_flNextActionTime ) + { + if ( !m_bBlueBronzeValueRevealed && !m_bRedBronzeValueRevealed ) + { + m_pBlueMedalsPanel->SetDialogVariable( "blueteammedals_bronze", m_nMedalsToAward_Bronze_Blue ); + m_bBlueBronzeValueRevealed = true; + Panel *pChild = m_pBlueMedalsPanel->FindChildByName( "BlueBronzeMedalValue" ); + if ( pChild ) + { + int nXPos, nYPos, nWide, nTall; + pChild->GetBounds( nXPos, nYPos, nWide, nTall ); + FireMedalEffects( pChild, nXPos, nYPos, nWide, nTall, ( m_nMedalsToAward_Bronze_Blue > 0 ) ? StatMedal_Bronze : StatMedal_None ); + } + + m_pRedMedalsPanel->SetDialogVariable( "redteammedals_bronze", m_nMedalsToAward_Bronze_Red ); + m_bRedBronzeValueRevealed = true; + pChild = m_pRedMedalsPanel->FindChildByName( "RedBronzeMedalValue" ); + if ( pChild ) + { + int nXPos, nYPos, nWide, nTall; + pChild->GetBounds( nXPos, nYPos, nWide, nTall ); + FireMedalEffects( pChild, nXPos, nYPos, nWide, nTall, ( m_nMedalsToAward_Bronze_Red > 0 ) ? StatMedal_Bronze : StatMedal_None ); + } + + m_flNextActionTime = gpGlobals->curtime + MS_STATE_TIME_BETWEEN_MEDALS_CATEGORIES; + } + else + { + if ( ( nMedalsToAward_Bronze_Total > 0 ) && ( m_nMedalsRevealed < nMedalsToAward_Bronze_Total ) ) + { + Update(); + m_nMedalsRevealed++; + m_flNextActionTime = gpGlobals->curtime + MS_STATE_TIME_BETWEEN_MEDALS; + } + else + { + m_iCurrentState = MS_STATE_SILVER_MEDALS; + m_nMedalsRevealed = 0; + m_flNextActionTime = MS_STATE_TIME_BETWEEN_MEDALS_CATEGORIES; + } + } + } + break; + } + case MS_STATE_SILVER_MEDALS: + { + if ( gpGlobals->curtime > m_flNextActionTime ) + { + if ( !m_bBlueSilverValueRevealed && !m_bRedSilverValueRevealed ) + { + m_pBlueMedalsPanel->SetDialogVariable( "blueteammedals_silver", m_nMedalsToAward_Silver_Blue ); + m_bBlueSilverValueRevealed = true; + Panel *pChild = m_pBlueMedalsPanel->FindChildByName( "BlueSilverMedalValue" ); + if ( pChild ) + { + int nXPos, nYPos, nWide, nTall; + pChild->GetBounds( nXPos, nYPos, nWide, nTall ); + FireMedalEffects( pChild, nXPos, nYPos, nWide, nTall, ( m_nMedalsToAward_Silver_Blue > 0 ) ? StatMedal_Silver : StatMedal_None ); + } + + m_pRedMedalsPanel->SetDialogVariable( "redteammedals_silver", m_nMedalsToAward_Silver_Red ); + m_bRedSilverValueRevealed = true; + pChild = m_pRedMedalsPanel->FindChildByName( "RedSilverMedalValue" ); + if ( pChild ) + { + int nXPos, nYPos, nWide, nTall; + pChild->GetBounds( nXPos, nYPos, nWide, nTall ); + FireMedalEffects( pChild, nXPos, nYPos, nWide, nTall, ( m_nMedalsToAward_Silver_Red > 0 ) ? StatMedal_Silver : StatMedal_None ); + } + + m_flNextActionTime = gpGlobals->curtime + MS_STATE_TIME_BETWEEN_MEDALS_CATEGORIES; + } + else + { + if ( ( nMedalsToAward_Silver_Total > 0 ) && ( m_nMedalsRevealed < nMedalsToAward_Silver_Total ) ) + { + Update(); + m_nMedalsRevealed++; + m_flNextActionTime = gpGlobals->curtime + MS_STATE_TIME_BETWEEN_MEDALS; + } + else + { + m_iCurrentState = MS_STATE_GOLD_MEDALS; + m_nMedalsRevealed = 0; + m_flNextActionTime = MS_STATE_TIME_BETWEEN_MEDALS_CATEGORIES; + } + } + } + break; + } + case MS_STATE_GOLD_MEDALS: + { + if ( gpGlobals->curtime > m_flNextActionTime ) + { + if ( !m_bBlueGoldValueRevealed && !m_bRedGoldValueRevealed ) + { + m_pBlueMedalsPanel->SetDialogVariable( "blueteammedals_gold", m_nMedalsToAward_Gold_Blue ); + m_bBlueGoldValueRevealed = true; + Panel *pChild = m_pBlueMedalsPanel->FindChildByName( "BlueGoldMedalValue" ); + if ( pChild ) + { + int nXPos, nYPos, nWide, nTall; + pChild->GetBounds( nXPos, nYPos, nWide, nTall ); + FireMedalEffects( pChild, nXPos, nYPos, nWide, nTall, ( m_nMedalsToAward_Gold_Blue > 0 ) ? StatMedal_Gold : StatMedal_None ); + } + + m_pRedMedalsPanel->SetDialogVariable( "redteammedals_gold", m_nMedalsToAward_Gold_Red ); + m_bRedGoldValueRevealed = true; + pChild = m_pRedMedalsPanel->FindChildByName( "RedGoldMedalValue" ); + if ( pChild ) + { + int nXPos, nYPos, nWide, nTall; + pChild->GetBounds( nXPos, nYPos, nWide, nTall ); + FireMedalEffects( pChild, nXPos, nYPos, nWide, nTall, ( m_nMedalsToAward_Gold_Red > 0 ) ? StatMedal_Gold : StatMedal_None ); + } + + m_flNextActionTime = gpGlobals->curtime + MS_STATE_TIME_BETWEEN_MEDALS_CATEGORIES; + } + else + { + if ( ( nMedalsToAward_Gold_Total > 0 ) && ( m_nMedalsRevealed < nMedalsToAward_Gold_Total ) ) + { + Update(); + m_nMedalsRevealed++; + m_flNextActionTime = gpGlobals->curtime + MS_STATE_TIME_BETWEEN_MEDALS; + } + else + { + m_iCurrentState = MS_STATE_FINAL; + m_nMedalsRevealed = 0; + m_flNextActionTime = -1; + + if ( pLocalPlayer ) + { + const char *pszSoundScriptEntry = "Announcer.CompSummaryScreenOutlierNo"; + if ( nMedalsToAward_Bronze_Total || nMedalsToAward_Silver_Total || nMedalsToAward_Gold_Total ) + { + pszSoundScriptEntry = "Announcer.CompSummaryScreenOutlierYes"; + } + + const char *pszSoundName = UTIL_GetRandomSoundFromEntry( pszSoundScriptEntry ); + m_flMedalSoundTime = gpGlobals->curtime + enginesound->GetSoundDuration( pszSoundName ) + 0.5f; + pLocalPlayer->EmitSound( pszSoundName ); + + IGameEvent *event = gameeventmanager->CreateEvent( "ds_screenshot" ); + if ( event ) + { + event->SetFloat( "delay", 0.5f ); + gameeventmanager->FireEventClientSide( event ); + } + } + } + } + } + break; + } + default: + case MS_STATE_FINAL: + { + bool bMedalSoundTimeComplete = ( m_flMedalSoundTime > 0 ) && ( m_flMedalSoundTime < gpGlobals->curtime ); + + if ( !m_bXPShown /*&& ( !bShowMedals || bMedalSoundTimeComplete ) */) + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "CompetitiveGame_ShowPvPRankPanel", false ); + m_bXPShown = true; + } + + if ( bShowPerformanceMedals ) + { + if ( bMedalSoundTimeComplete ) + { + m_flMedalSoundTime = -1.f; + int iLocalPlayerIndex = GetLocalPlayerIndex(); + + if ( ( m_SkillRatings[iLocalPlayerIndex].nScoreRank != StatMedal_None ) || + ( m_SkillRatings[iLocalPlayerIndex].nKillsRank != StatMedal_None ) || + ( m_SkillRatings[iLocalPlayerIndex].nDamageRank != StatMedal_None ) || + ( m_SkillRatings[iLocalPlayerIndex].nHealingRank != StatMedal_None ) || + ( m_SkillRatings[iLocalPlayerIndex].nSupportRank != StatMedal_None ) ) + { + if ( pLocalPlayer ) + { + int iClass = RandomInt( TF_CLASS_SCOUT, TF_CLASS_ENGINEER ); + if ( pLocalPlayer->GetPlayerClass() && ( pLocalPlayer->GetPlayerClass()->GetClassIndex() > TF_CLASS_UNDEFINED ) ) + { + iClass = pLocalPlayer->GetPlayerClass()->GetClassIndex(); + } + + pLocalPlayer->EmitSound( VarArgs( "%s.CompSummaryScreenOutlier", g_aPlayerClassNames_NonLocalized[iClass] ) ); + } + } + } + } + + break; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMatchSummary::LevelInit( void ) +{ + SetVisible( false ); +} + +void CTFMatchSummary::LevelShutdown( void ) +{ + SetVisible( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFMatchSummary::ShowPerformanceMedals( void ) +{ + bool bDistributePerformanceMedals = false; + + const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() ); + if ( pMatchDesc ) + { + bDistributePerformanceMedals = pMatchDesc->m_params.m_bDistributePerformanceMedals; + } + + return ( bDistributePerformanceMedals && !m_bPlayerAbandoned ); +} diff --git a/game/client/tf/vgui/tf_match_summary.h b/game/client/tf/vgui/tf_match_summary.h new file mode 100644 index 0000000..80e7a70 --- /dev/null +++ b/game/client/tf/vgui/tf_match_summary.h @@ -0,0 +1,248 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_MATCH_SUMMARY_H +#define TF_MATCH_SUMMARY_H +#ifdef _WIN32 +#pragma once +#endif + +#include "hudelement.h" +#include <vgui_controls/EditablePanel.h> +#include <vgui_controls/SectionedListPanel.h> +#include "tf_imagepanel.h" +#include "econ_controls.h" +#include "drawing_panel.h" +#include "tf_particlepanel.h" +#include "tf_matchmaking_shared.h" +#include "tf_gamerules.h" +#include "tf_match_join_handlers.h" + +using namespace vgui; + +class CAvatarImagePanel; +class CTFBadgePanel; + +enum matchsummary_displaystate_t +{ + MS_STATE_INITIAL = 0, + MS_STATE_DRAWING, + MS_STATE_STATS, + MS_STATE_BRONZE_MEDALS, + MS_STATE_SILVER_MEDALS, + MS_STATE_GOLD_MEDALS, + MS_STATE_FINAL, + + MS_NUM_STATES +}; + +enum matchsummary_columns_t +{ + MS_COLUMN_INVALID = -1, + + MS_COLUMN_MEDAL = 0, + MS_COLUMN_AVATAR, + MS_COLUMN_SPACER, + MS_COLUMN_NAME, + MS_COLUMN_CLASS, + MS_COLUMN_SCORE, + MS_COLUMN_SCORE_MEDAL, + MS_COLUMN_KILLS, + MS_COLUMN_KILLS_MEDAL, + MS_COLUMN_DAMAGE, + MS_COLUMN_DAMAGE_MEDAL, + MS_COLUMN_HEALING, + MS_COLUMN_HEALING_MEDAL, + MS_COLUMN_SUPPORT, + MS_COLUMN_SUPPORT_MEDAL, + + MS_NUM_COLUMNS +}; + +class TFSectionedListPanel : public vgui::SectionedListPanel +{ +private: + DECLARE_CLASS_SIMPLE( TFSectionedListPanel, vgui::SectionedListPanel ); + +public: + TFSectionedListPanel( Panel *parent, const char *panelName ) : BaseClass( parent, panelName ){} + virtual ~TFSectionedListPanel(){} + + CPanelAnimationVarAliasType( int, m_iMedalWidth, "medal_width", "s.05", "proportional_width" ); + CPanelAnimationVarAliasType( int, m_iAvatarWidth, "avatar_width", "s.1", "proportional_width" ); // Avatar width doesn't scale with resolution + CPanelAnimationVarAliasType( int, m_iSpacerWidth, "spacer", "s.1", "proportional_width" ); + CPanelAnimationVarAliasType( int, m_iNameWidth, "name_width", "s.1", "proportional_width" ); + CPanelAnimationVarAliasType( int, m_iClassWidth, "class_width", "s.1", "proportional_width" ); + CPanelAnimationVarAliasType( int, m_iAwardWidth, "award_width", "s.1", "proportional_width" ); + CPanelAnimationVarAliasType( int, m_iStatsWidth, "stats_width", "s.1", "proportional_width" ); + CPanelAnimationVarAliasType( int, m_iHorizFillInset, "horiz_inset", "5", "proportional_int" ); +}; + +class CTFMatchSummary : public CHudElement, public vgui::EditablePanel +{ +private: + DECLARE_CLASS_SIMPLE( CTFMatchSummary, vgui::EditablePanel ); + +public: + CTFMatchSummary( const char *pElementName ); + virtual ~CTFMatchSummary(); + + virtual bool ShouldDraw( void ) OVERRIDE; + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE; + virtual void PerformLayout() OVERRIDE; + virtual void SetVisible( bool state ) OVERRIDE; + virtual void OnTick() OVERRIDE; + virtual void LevelInit( void ) OVERRIDE; + virtual void LevelShutdown( void ) OVERRIDE; + + virtual GameActionSet_t GetPreferredActionSet() OVERRIDE { return GAME_ACTION_SET_IN_GAME_HUD; } + virtual void FireGameEvent( IGameEvent *event ) OVERRIDE; + + bool ShowPerformanceMedals( void ); + +private: + + void Update( void ); + void InitPlayerList( TFSectionedListPanel *pPlayerList, int nTeam ); + static bool TFPlayerSortFunc( vgui::SectionedListPanel *list, int itemID1, int itemID2 ); + void UpdateTeamInfo(); + void UpdatePlayerList(); + void UpdatePlayerAvatar( int playerIndex, KeyValues *kv ); + void RecalculateMedalCounts(); + void UpdateBadgePanels( CUtlVector<CTFBadgePanel*> &pBadgePanels, TFSectionedListPanel *pPlayerList ); + + void InternalUpdateMedalCountForType( int iTeam, StatMedal_t eMedal ); + matchsummary_columns_t InternalAddMedalKeyValues( int iIndex, StatMedal_t eMedal, KeyValues *pKeyValues, int nTotalMedals = -1 ); + void FireMedalEffects( Panel *pPanel, int nPanelXPos, int nPanelYPos, int nPanelWide, int nPanelTall, StatMedal_t eParticleMedal ); + +private: + EditablePanel *m_pTeamScoresPanel; + + int m_iImageClass[SCOREBOARD_CLASS_ICONS]; + int m_iImageClassAlt[SCOREBOARD_CLASS_ICONS]; + int m_iImageMedals[StatMedal_Max]; + + CDrawingPanel *m_pDrawingPanel; + + vgui::EditablePanel *m_pBlueTeamPanel; + vgui::EditablePanel *m_pRedTeamPanel; + + vgui::EditablePanel *m_pMainStatsContainer; + vgui::EditablePanel *m_pPlayerListBlueParent; + TFSectionedListPanel *m_pPlayerListBlue; + vgui::EditablePanel *m_pPlayerListRedParent; + TFSectionedListPanel *m_pPlayerListRed; + CExLabel *m_pBlueTeamScore; + CExLabel *m_pBlueTeamScoreDropshadow; + EditablePanel *m_pBlueTeamScoreBG; + EditablePanel *m_pBluePlayerListBG; + CExLabel *m_pRedTeamScore; + CExLabel *m_pRedTeamScoreDropshadow; + EditablePanel *m_pRedTeamScoreBG; + EditablePanel *m_pRedPlayerListBG; + EditablePanel *m_pBlueMedalsPanel; + EditablePanel *m_pRedMedalsPanel; + vgui::ImagePanel *m_pRedTeamImage; + vgui::ImagePanel *m_pBlueTeamImage; + CAvatarImagePanel *m_pRedLeaderAvatarImage; + CAvatarImagePanel *m_pBlueLeaderAvatarImage; + EditablePanel *m_pRedLeaderAvatarBG; + EditablePanel *m_pBlueLeaderAvatarBG; + EditablePanel *m_pStatsLabelPanel; + CExLabel *m_pStatsAndMedals; + CExLabel *m_pStatsAndMedalsShadow; + CExLabel *m_pBlueTeamName; + CExLabel *m_pRedTeamName; + CExLabel *m_pRedTeamWinner; + CExLabel *m_pRedTeamWinnerDropshadow; + CExLabel *m_pBlueTeamWinner; + CExLabel *m_pBlueTeamWinnerDropshadow; + + CTFParticlePanel *m_pParticlePanel; + + vgui::EditablePanel *m_pStatsBgPanel; + + vgui::ImageList *m_pImageList; + CUtlMap<CSteamID,int> m_mapAvatarsToImageList; + + vgui::HFont m_hFont; + bool m_bLargeMatchGroup; + bool m_bXPShown; + + float m_flDrawingPanelTime; + + CPanelAnimationVar( Color, m_clrGoldMedal, "GoldMedalText", "214 186 24 255" ); + CPanelAnimationVar( Color, m_clrSilverMedal, "SilverMedalText", "222 218 222 255" ); + CPanelAnimationVar( Color, m_clrBronzeMedal, "BronzeMedalText", "214 125 57 255" ); + + CPanelAnimationVarAliasType( int, m_iAnimBluePlayerListParent, "AnimBluePlayerListParent", "0", "proportional_width" ); + CPanelAnimationVarAliasType( int, m_iAnimBlueTeamScore, "AnimBlueTeamScore", "0", "proportional_width" ); + CPanelAnimationVarAliasType( int, m_iAnimBlueTeamScoreDropshadow, "AnimBlueTeamScoreDropshadow", "0", "proportional_width" ); + CPanelAnimationVarAliasType( int, m_iAnimBlueTeamScoreBG, "AnimBlueTeamScoreBG", "0", "proportional_width" ); + CPanelAnimationVarAliasType( int, m_iAnimBluePlayerListBG, "AnimBluePlayerListBG", "0", "proportional_width" ); + + CPanelAnimationVarAliasType( int, m_iAnimRedTeamScoreBGWide, "AnimRedTeamScoreBGWide", "0", "proportional_width" ); + CPanelAnimationVarAliasType( int, m_iAnimRedTeamScoreBGXPos, "AnimRedTeamScoreBGXPos", "0", "proportional_xpos" ); + CPanelAnimationVarAliasType( int, m_iAnimRedTeamScoreWide, "AnimRedTeamScoreWide", "0", "proportional_width" ); + CPanelAnimationVarAliasType( int, m_iAnimRedTeamScoreXPos, "AnimRedTeamScoreXPos", "0", "proportional_xpos" ); + CPanelAnimationVarAliasType( int, m_iAnimRedTeamScoreDropshadowWide, "AnimRedTeamScoreDropshadowWide", "0", "proportional_width" ); + CPanelAnimationVarAliasType( int, m_iAnimRedTeamScoreDropshadowXPos, "AnimRedTeamScoreDropshadowXPos", "0", "proportional_xpos" ); + CPanelAnimationVarAliasType( int, m_iAnimRedPlayerListParentWide, "AnimRedPlayerListParentWide", "0", "proportional_width" ); + CPanelAnimationVarAliasType( int, m_iAnimRedPlayerListParentXPos, "AnimRedPlayerListParentXPos", "0", "proportional_xpos" ); + CPanelAnimationVarAliasType( int, m_iAnimRedPlayerListBGWide, "AnimRedPlayerListBGWide", "0", "proportional_width" ); + CPanelAnimationVarAliasType( int, m_iAnimRedPlayerListBGXPos, "AnimRedPlayerListBGXPos", "0", "proportional_xpos" ); + CPanelAnimationVarAliasType( int, m_iAnimBlueMedalsYPos, "AnimBlueMedalsYPos", "0", "proportional_ypos" ); + CPanelAnimationVarAliasType( int, m_iAnimRedMedalsYPos, "AnimRedMedalsYPos", "0", "proportional_ypos" ); + CPanelAnimationVarAliasType( int, m_iAnimStatsLabelPanel6v6YPos, "AnimStatsLabelPanel6v6YPos", "0", "proportional_ypos" ); + CPanelAnimationVarAliasType( int, m_iAnimBlueTeamLabel6v6YPos, "AnimBlueTeamLabel6v6YPos", "0", "proportional_ypos" ); + CPanelAnimationVarAliasType( int, m_iAnimRedTeamLabel6v6YPos, "AnimRedTeamLabel6v6YPos", "0", "proportional_ypos" ); + CPanelAnimationVarAliasType( int, m_iAnimStatsLabelPanel12v12YPos, "AnimStatsLabelPanel12v12YPos", "0", "proportional_ypos" ); + CPanelAnimationVarAliasType( int, m_iAnimBlueTeamLabel12v12YPos, "AnimBlueTeamLabel12v12YPos", "0", "proportional_ypos" ); + CPanelAnimationVarAliasType( int, m_iAnimRedTeamLabel12v12YPos, "AnimRedTeamLabel12v12YPos", "0", "proportional_ypos" ); + CPanelAnimationVarAliasType( int, m_iAnimStatsContainer12v12YPos, "AnimStatsContainer12v12YPos", "0", "proportional_ypos" ); + + struct MatchDataUpdate_t + { + uint32 unRating; + int nDelta; + int nScoreRank; + int nKillsRank; + int nDamageRank; + int nHealingRank; + int nSupportRank; + }; + MatchDataUpdate_t m_SkillRatings[MAX_PLAYERS + 1]; + + int m_iCurrentState; + float m_flNextActionTime; + + int m_nMedalsToAward_Bronze_Blue; + int m_nMedalsToAward_Silver_Blue; + int m_nMedalsToAward_Gold_Blue; + int m_nMedalsToAward_Bronze_Red; + int m_nMedalsToAward_Silver_Red; + int m_nMedalsToAward_Gold_Red; + + int m_nMedalsRevealed; + + int m_nNumMedalsThisUpdate; + + bool m_bBlueGoldValueRevealed; + bool m_bBlueSilverValueRevealed; + bool m_bBlueBronzeValueRevealed; + bool m_bRedGoldValueRevealed; + bool m_bRedSilverValueRevealed; + bool m_bRedBronzeValueRevealed; + bool m_bPlayerAbandoned; + + float m_flMedalSoundTime; + + CUtlVector< CTFBadgePanel* > m_pBlueBadgePanels; + CUtlVector< CTFBadgePanel* > m_pRedBadgePanels; +}; + +#endif //TF_MATCH_SUMMARY_H diff --git a/game/client/tf/vgui/tf_matchmaking_dashboard.cpp b/game/client/tf/vgui/tf_matchmaking_dashboard.cpp new file mode 100644 index 0000000..ca10e8b --- /dev/null +++ b/game/client/tf/vgui/tf_matchmaking_dashboard.cpp @@ -0,0 +1,594 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "tf_shareddefs.h" +#include "tf_matchmaking_dashboard.h" +#include "tf_gamerules.h" +#include "ienginevgui.h" +#include "clientmode_tf.h" +#include "tf_hud_disconnect_prompt.h" +#include "tf_gc_client.h" +#include "tf_party.h" +#include "../vgui2/src/VPanel.h" + +using namespace vgui; +using namespace GCSDK; + +ConVar tf_mm_dashboard_spew_enabled( "tf_mm_dashboard_spew_enabled", "0", FCVAR_ARCHIVE ); +#define MMDashboardSpew(...) \ + do { \ + if ( tf_mm_dashboard_spew_enabled.GetBool() ) \ + { \ + ConColorMsg( Color( 187, 80, 255, 255 ), "MMDashboard:" __VA_ARGS__ ); \ + } \ + } while(false) \ + +extern ConVar tf_mm_next_map_vote_time; + +#ifdef STAGING_ONLY +ConVar tf_mm_dashboard_force_show( "tf_mm_dashboard_force_show", "0", 0, "Force the mm dashboard to show" ); +ConVar tf_mm_popup_state_override( "tf_mm_popup_state_override", "", 0, "Force state on mm dashboard popup" ); +#endif + + + +bool BInEndOfMatch() +{ + const bool bInEndOfMatch = TFGameRules() && + TFGameRules()->State_Get() == GR_STATE_GAME_OVER && + GTFGCClientSystem()->BConnectedToMatchServer( false ); + + return bInEndOfMatch; +} + +//----------------------------------------------------------------------------- +// Purpose: Pnael that lives on the viewport that is a popup that we parent +// the MM dashboard panels to +//----------------------------------------------------------------------------- +class CMatchMakingHUDPopupContainer : public Panel +{ +public: + DECLARE_CLASS_SIMPLE( CMatchMakingHUDPopupContainer, Panel ); + CMatchMakingHUDPopupContainer() + : Panel( g_pClientMode->GetViewport(), "MMDashboardPopupContainer" ) + { + SetProportional( true ); + SetBounds( 0, 0, g_pClientMode->GetViewport()->GetWide(), g_pClientMode->GetViewport()->GetTall() ); + MakePopup(); + SetMouseInputEnabled( true ); + SetKeyBoardInputEnabled( false ); // This can never be true + SetVisible( false ); + ivgui()->AddTickSignal( GetVPanel(), 100 ); + } + + virtual void OnTick() + { + BaseClass::OnThink(); + + bool bChildrenVisible = false; + int nCount = GetChildCount(); + for( int i=0; i < nCount && !bChildrenVisible; ++i ) + { + CExpandablePanel* pChild = assert_cast< CExpandablePanel* >( GetChild( i ) ); + bChildrenVisible = bChildrenVisible || pChild->BIsExpanded() || ( !pChild->BIsExpanded() && pChild->GetPercentAnimated() != 1.f ); + } + + SetVisible( bChildrenVisible ); + } +}; + +CMMDashboardParentManager::CMMDashboardParentManager() + : m_bAttachedToGameUI( false ) +{ + ListenForGameEvent( "gameui_activated" ); + ListenForGameEvent( "gameui_hidden" ); + + m_pHUDPopup = new CMatchMakingHUDPopupContainer(); +} + +//----------------------------------------------------------------------------- +// Purpose: Update who we need to parent the MM dashboard panels to +//----------------------------------------------------------------------------- +void CMMDashboardParentManager::FireGameEvent( IGameEvent *event ) +{ + if ( FStrEq( event->GetName(), "gameui_activated" ) ) + { + m_bAttachedToGameUI = false; + } + else if ( FStrEq( event->GetName(), "gameui_hidden" ) ) + { + m_bAttachedToGameUI = true; + } + + UpdateParenting(); +} + +void CMMDashboardParentManager::AddPanel( CExpandablePanel* pPanel ) +{ + m_vecPanels.Insert( pPanel ); + UpdateParenting(); +} + +void CMMDashboardParentManager::RemovePanel( CExpandablePanel* pPanel ) +{ + m_vecPanels.FindAndRemove( pPanel ); + m_vecPanels.RedoSort(); + UpdateParenting(); +} + +void CMMDashboardParentManager::PushModalFullscreenPopup( vgui::Panel* pPanel ) +{ + m_vecFullscreenPopups.AddToTail( pPanel ); + UpdateParenting(); +} + +void CMMDashboardParentManager::PopModalFullscreenPopup( vgui::Panel* pPanel ) +{ + m_vecFullscreenPopups.FindAndRemove( pPanel ); + UpdateParenting(); +} + +void CMMDashboardParentManager::UpdateParenting() +{ + m_bAttachedToGameUI ? AttachToGameUI() : AttachToTopMostPopup(); +} + +//----------------------------------------------------------------------------- +// Purpose: Parent the MM dashboard panels to the right panels +//----------------------------------------------------------------------------- +void CMMDashboardParentManager::AttachToGameUI() +{ + if ( !m_pHUDPopup ) + { + return; + } + + FOR_EACH_VEC( m_vecPanels, i ) + { + CExpandablePanel *pPanel = m_vecPanels[ i ]; + bool bKBInput = pPanel->IsKeyBoardInputEnabled(); + bool bMouseInput = pPanel->IsMouseInputEnabled(); + + pPanel->SetParent( (Panel*)m_pHUDPopup ); + + // Restore mouse, KV input sensitivity because MakePopup forces both to true + pPanel->SetKeyBoardInputEnabled( bKBInput ); + pPanel->SetMouseInputEnabled( bMouseInput ); + // Don't adopt the parent's proportionalness + pPanel->SetProportional( true ); + } +} + +void CMMDashboardParentManager::AttachToTopMostPopup() +{ + // Not being used. Hide it. + if ( m_pHUDPopup ) + { + m_pHUDPopup->SetVisible( false ); + } + + FOR_EACH_VEC( m_vecPanels, i ) + { + Panel *pPanel = m_vecPanels[ i ]; + // No longer a popup + surface()->ReleasePanel( pPanel->GetVPanel() ); + ((VPanel*)pPanel->GetVPanel())->SetPopup( false ); + + if ( m_vecFullscreenPopups.Count() ) + { + pPanel->SetParent( m_vecFullscreenPopups.Tail() ); + } + else + { + pPanel->SetParent( enginevgui->GetPanel( PANEL_GAMEUIDLL ) ); + } + pPanel->MoveToFront(); + // Don't adopt the parent's proportionalness + pPanel->SetProportional( true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Snag the singleton CMMDashboardParentManager +//----------------------------------------------------------------------------- +CMMDashboardParentManager* GetMMDashboardParentManager() +{ + static CMMDashboardParentManager* s_pParentManager = NULL; + if ( !s_pParentManager ) + { + s_pParentManager = new CMMDashboardParentManager(); + } + + return s_pParentManager; +} + + +void CTFMatchmakingPopup::OnEnter() +{ + MMDashboardSpew( "Entering state %s\n", GetName() ); + + Update(); + + SetCollapsed( false ); +} + +void CTFMatchmakingPopup::OnUpdate() +{ +} + +void CTFMatchmakingPopup::OnExit() +{ + MMDashboardSpew( "Exiting state %s\n", GetName() ); + SetCollapsed( true ); +} + + +void CTFMatchmakingPopup::Update() +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFMatchmakingPopup::CTFMatchmakingPopup( const char* pszName, const char* pszResFile ) + : CExpandablePanel( NULL, pszName ) + , m_pszResFile( pszResFile ) + , m_bActive( false ) +{ + vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme"); + SetScheme(scheme); + ivgui()->AddTickSignal( GetVPanel(), 100 ); + + GetMMDashboardParentManager()->AddPanel( this ); + SetKeyBoardInputEnabled( false ); + + SetProportional( true ); + + ListenForGameEvent( "rematch_failed_to_create" ); + ListenForGameEvent( "party_updated" ); +} + +CTFMatchmakingPopup::~CTFMatchmakingPopup() +{ + GetMMDashboardParentManager()->RemovePanel( this ); +} + +void CTFMatchmakingPopup::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + SetMouseInputEnabled( true ); + LoadControlSettings( m_pszResFile ); + + // This cannot ever be true or else things get weird when in-game + SetKeyBoardInputEnabled( false ); + + if ( m_bActive ) + { + OnEnter(); + } + else + { + OnExit(); + } + + GetMMDashboardParentManager()->UpdateParenting(); +} + +void CTFMatchmakingPopup::OnThink() +{ + BaseClass::OnThink(); + + // Move us to be touching the bottom of the dashboard panel + // These panels have no relation whatsoever, so we're doing this manually + Panel* pDashboard = GetMMDashboard(); + int nNewYPos = Max( pDashboard->GetYPos() + pDashboard->GetTall() - YRES(10), YRES(-5) ); + SetPos( GetXPos(), nNewYPos ); + + if ( m_bActive ) + { + OnUpdate(); + } +} + +void CTFMatchmakingPopup::OnTick() +{ + BaseClass::OnTick(); + + bool bShouldBeActive = ShouldBeActve(); + if ( bShouldBeActive != m_bActive ) + { + if ( bShouldBeActive ) + { + m_bActive = true; + OnEnter(); + } + else + { + m_bActive = false; + OnExit(); + } + } + + SetMouseInputEnabled( ShouldBeActve() ); + SetKeyBoardInputEnabled( false ); // Never +} + + +void CTFMatchmakingPopup::OnCommand( const char *command ) +{ + if ( FStrEq( "join_match", command ) ) + { + JoinMatch(); + } + else if ( FStrEq( "abandon_match", command ) ) + { + GTFGCClientSystem()->RejoinLobby( false ); + } + else if ( FStrEq( "leave_queue", command ) ) + { + // Only the leader can leave the queue + if ( GTFGCClientSystem()->BIsPartyLeader() ) + { + switch( GTFGCClientSystem()->GetSearchMode() ) + { + case TF_Matchmaking_LADDER: + GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_LADDER ); + break; + + case TF_Matchmaking_CASUAL: + GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_CASUAL ); + break; + + default: + // Unhandled + Assert( false ); + break; + }; + } + } + else if( FStrEq( "rematch", command ) ) + { + if ( !GTFGCClientSystem()->BIsPartyLeader() ) + return; + + engine->ClientCmd( "rematch_vote 2" ); + } + else if ( FStrEq( "new_match", command ) ) + { + if ( !GTFGCClientSystem()->BIsPartyLeader() ) + return; + + GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_SEARCHING ); + engine->ClientCmd( "rematch_vote 1" ); + } + else if ( FStrEq( "leave_party", command ) ) + { + // Leave current party and create a new one! + GTFGCClientSystem()->SendExitMatchmaking( false ); + return; + } +} + + +void CTFMatchmakingPopup::FireGameEvent( IGameEvent *pEvent ) +{ + if ( FStrEq( pEvent->GetName(), "rematch_failed_to_create" ) ) + { + // If the GC failed to create our rematch, then go ahead and requeue + if ( !GTFGCClientSystem()->BIsPartyLeader() ) + return; + + GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_SEARCHING ); + } + else if ( FStrEq( pEvent->GetName(), "party_updated" ) ) + { + if ( ShouldBeActve() ) + { + Update(); + } + } +} + + +#ifdef STAGING_ONLY +CON_COMMAND( reload_mm_popup, "Reload the mm popup panel. Pass any 2nd argument to recreate the panel." ) +{ + bool bRecreate = false; + if ( args.ArgC() == 2 ) + { + bRecreate = true; + } + + auto& vecPopups = CreateMMPopupPanels( bRecreate ); + + if ( !bRecreate ) + { + FOR_EACH_VEC( vecPopups, i ) + { + vecPopups[ i ]->InvalidateLayout( true, true ); + } + } +} + +CON_COMMAND( reload_mm_dashboard, "Reload the mm join panel." ) +{ + GetMMDashboard()->InvalidateLayout( true, true ); +} +#endif + + +CTFMatchmakingDashboard::CTFMatchmakingDashboard() + : CExpandablePanel( NULL, "MMDashboard" ) +{ + SetKeyBoardInputEnabled( false ); + ivgui()->AddTickSignal( GetVPanel(), 100 ); + + GetMMDashboardParentManager()->AddPanel( this ); + + CreateMMPopupPanels(); + + vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme"); + SetScheme(scheme); + SetProportional( true ); +} + +CTFMatchmakingDashboard::~CTFMatchmakingDashboard() +{ + GetMMDashboardParentManager()->RemovePanel( this ); +} + +void CTFMatchmakingDashboard::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + SetMouseInputEnabled( true ); + LoadControlSettings( "resource/UI/MatchMakingDashboard.res" ); + + // This cannot ever be true or else things get weird when in-game + SetKeyBoardInputEnabled( false ); + + GetMMDashboardParentManager()->UpdateParenting(); +} + +void CTFMatchmakingDashboard::OnCommand( const char *command ) +{ + if ( FStrEq( command, "disconnect" ) ) + { + CTFParty* pParty = GTFGCClientSystem()->GetParty(); + bool bInPartyOfMany = pParty && pParty->GetNumMembers() > 1; + + const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() ); + if ( pMatchDesc && pMatchDesc->BShouldAutomaticallyRequeueOnMatchEnd() && !bInPartyOfMany ) + { + // If this is an auto-requeue match type, then assume hitting the "Disconnect" button + // means you are done playing entirely with MM. Close out to the main menu. + bool bNoPenalty = ( GTFGCClientSystem()->GetAssignedMatchAbandonStatus() != k_EAbandonGameStatus_AbandonWithPenalty ); + if ( bNoPenalty ) + { + GTFGCClientSystem()->EndMatchmaking( true ); + } + else + { + // Prompt if this would be considered an abandon with a penalty + CTFDisconnectConfirmDialog *pDialog = BuildDisconnectConfirmDialog(); + if ( pDialog ) + { + pDialog->Show(); + } + } + } + else + { + // Go back to the MM screens. The party leader will request the right wizard state + switch( GTFGCClientSystem()->GetSearchMode() ) + { + case TF_Matchmaking_LADDER: + if ( GTFGCClientSystem()->BIsPartyLeader() ) + { + GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_LADDER ); + } + engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby ladder" ); + break; + + case TF_Matchmaking_CASUAL: + if ( GTFGCClientSystem()->BIsPartyLeader() ) + { + GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_CASUAL ); + } + engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby casual" ); + break; + + default: + // Unhandled + GTFGCClientSystem()->EndMatchmaking(); + Assert( false ); + break; + }; + } + + // Disconnect from the server + engine->DisconnectInternal(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Figure out if we should be visible and require mouse input +//----------------------------------------------------------------------------- +void CTFMatchmakingDashboard::OnTick() +{ + bool bInEndOfMatch = TFGameRules() && TFGameRules()->State_Get() == GR_STATE_GAME_OVER; + + const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( TFGameRules() ? TFGameRules()->GetCurrentMatchGroup() : k_nMatchGroup_Invalid ); + bool bShouldBeVisible = GTFGCClientSystem()->BConnectedToMatchServer( false ) + && bInEndOfMatch + && pMatchDesc + && pMatchDesc->BUsesDashboard(); + +#ifdef STAGING_ONLY + bShouldBeVisible |= tf_mm_dashboard_force_show.GetBool(); +#endif + + if ( BIsExpanded() && !bShouldBeVisible ) + { + SetCollapsed( true ); + } + else if ( !BIsExpanded() && bShouldBeVisible ) + { + SetCollapsed( false ); + } + + SetKeyBoardInputEnabled( false ); + SetMouseInputEnabled( BIsExpanded() ); +} + +CUtlVector< IMMPopupFactory* > IMMPopupFactory::s_vecPopupFactories; + +//----------------------------------------------------------------------------- +// Purpose: Snag the singleton CTFMatchmakingPopup +//----------------------------------------------------------------------------- +CUtlVector< CTFMatchmakingPopup* >& CreateMMPopupPanels( bool bRecreate /*= false*/ ) +{ + static CUtlVector< CTFMatchmakingPopup* > s_vecPopups; + + if ( bRecreate && s_vecPopups.Count() ) + { + FOR_EACH_VEC( s_vecPopups, i ) + { + s_vecPopups[ i ]->MarkForDeletion(); + } + s_vecPopups.Purge(); + } + + + if ( s_vecPopups.IsEmpty() ) + { + FOR_EACH_VEC( IMMPopupFactory::s_vecPopupFactories, i ) + { + s_vecPopups.AddToTail( IMMPopupFactory::s_vecPopupFactories[i]->Create() ); + } + } + + return s_vecPopups; +} + + +//----------------------------------------------------------------------------- +// Purpose: Snag the singleton CTFMatchmakingDashboard +//----------------------------------------------------------------------------- +CTFMatchmakingDashboard* GetMMDashboard() +{ + static CTFMatchmakingDashboard* s_pDashboardPanel = NULL; + if ( !s_pDashboardPanel ) + { + s_pDashboardPanel = new CTFMatchmakingDashboard(); + } + + return s_pDashboardPanel; +} diff --git a/game/client/tf/vgui/tf_matchmaking_dashboard.h b/game/client/tf/vgui/tf_matchmaking_dashboard.h new file mode 100644 index 0000000..57a54e1 --- /dev/null +++ b/game/client/tf/vgui/tf_matchmaking_dashboard.h @@ -0,0 +1,158 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_MATCHMAKING_DASHBOARD_H +#define TF_MATCHMAKING_DASHBOARD_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_match_join_handlers.h" +#include <vgui_controls/EditablePanel.h> +#include "tf_controls.h" +#include <vgui_controls/PHandle.h> +#include "local_steam_shared_object_listener.h" + +CUtlVector< class CTFMatchmakingPopup* >& CreateMMPopupPanels( bool bRecreate = false ); +class CTFMatchmakingDashboard* GetMMDashboard(); +class CMMDashboardParentManager* GetMMDashboardParentManager(); + +bool BInEndOfMatch(); + +//----------------------------------------------------------------------------- +// Purpose: Popup that goes underneath the dashboard and displays anything +// important the user needs to know about +//----------------------------------------------------------------------------- +class CTFMatchmakingPopup : public CExpandablePanel + , public CGameEventListener + , public IMatchJoiningHandler +{ + friend class CTFMatchmakingPopupState; + friend class CTFMatchmakingDashboard; + DECLARE_CLASS_SIMPLE( CTFMatchmakingPopup, CExpandablePanel ); +public: + + CTFMatchmakingPopup( const char* pszName, const char* pszResFile ); + virtual ~CTFMatchmakingPopup(); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE; + virtual void OnThink() OVERRIDE; + virtual void OnCommand( const char *command ) OVERRIDE; + virtual void OnTick() OVERRIDE; + + virtual void OnEnter(); + virtual void OnUpdate(); + virtual void OnExit(); + virtual void Update(); + + virtual void FireGameEvent( IGameEvent *pEvent ) OVERRIDE; + +private: + + virtual void MatchFound() {} // We dont need to do anything special + virtual bool ShouldBeActve() const = 0; + void UpdateRematchtime(); + void UpdateAutoJoinTime(); + + bool m_bActive; + const char* m_pszResFile; +}; + +//----------------------------------------------------------------------------- +// Purpose: Matchmaking panel that contains controls for matchmaking +//----------------------------------------------------------------------------- +class CTFMatchmakingDashboard : public CExpandablePanel +{ +public: + DECLARE_CLASS_SIMPLE( CTFMatchmakingDashboard, CExpandablePanel ); + CTFMatchmakingDashboard(); + virtual ~CTFMatchmakingDashboard(); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE; + virtual void OnCommand( const char *command ) OVERRIDE; + virtual void OnTick() OVERRIDE; +}; + +//----------------------------------------------------------------------------- +// CMMDashboardParentManager +// Purpose: This guy keeps the MM dashboard as the top-most panel but does so +// *without making it a popup*. This is important because popups look +// awful whenever they overlap and transparency is involved. This class +// does its dirty work by keeping track of the top-most fullscreen popup +// and setting that panel as the MM dashboard's parent. When that popup +// goes away, we set the parent to the next popup on the stack, or to +// the GameUI if none are active. If we're in-game, then we parent to +// the our special popup container. Why not always just parent to that +// single popup container? Because we want the MINIMUM mouse focus area +// possible because the dashboard is not a rectangle (it grows/shrinks). +// +// +// If anything draws on top of the MM dashboard and you dont want it to +// have that panel add itself to this class using PushModalFullscreenPopup +// when it goes visible and PopModalFullscreenPopup when it hides itself +//----------------------------------------------------------------------------- +class CMMDashboardParentManager : public CGameEventListener +{ +public: + friend class CTFMatchmakingDashboard; + friend class CTFMatchmakingPopup; + + CMMDashboardParentManager(); + + virtual void FireGameEvent( IGameEvent *event ) OVERRIDE; + + void PushModalFullscreenPopup( vgui::Panel* pPanel ); + void PopModalFullscreenPopup( vgui::Panel* pPanel ); + void UpdateParenting(); +private: + + void AddPanel( CExpandablePanel* pPanel ); + void RemovePanel( CExpandablePanel* pPanel ); + + void AttachToGameUI(); + void AttachToTopMostPopup(); + + bool m_bAttachedToGameUI; + + class CUtlSortVectorPanelZPos + { + public: + bool Less( const vgui::Panel* lhs, const vgui::Panel* rhs, void * ) + { + return lhs->GetZPos() < rhs->GetZPos(); + } + }; + + CUtlSortVector< CExpandablePanel*, CUtlSortVectorPanelZPos > m_vecPanels; + CUtlVector< vgui::Panel* > m_vecFullscreenPopups; + + vgui::PHandle m_pHUDPopup; +}; + +class IMMPopupFactory +{ +public: + virtual CTFMatchmakingPopup* Create() const = 0; + static CUtlVector< IMMPopupFactory* > s_vecPopupFactories; +}; + +template< typename Type > +class CMMPopupFactoryImplementation : public IMMPopupFactory +{ +public: + CMMPopupFactoryImplementation( const char* pszName, const char* pszResFile ) : m_pszName( pszName ), m_pszResFile( pszResFile ) + { s_vecPopupFactories.AddToTail( this ); } + + virtual CTFMatchmakingPopup* Create() const OVERRIDE { return new Type( m_pszName, m_pszResFile ); } +private: + const char* m_pszName; + const char* m_pszResFile; +}; + +#define REG_MM_POPUP_FACTORY( type, name, resfile ) CMMPopupFactoryImplementation< type > g_##type##Factory( name, resfile ); + +#endif // TF_MATCHMAKING_DASHBOARD_H diff --git a/game/client/tf/vgui/tf_matchmaking_dashboard_new_match_found.cpp b/game/client/tf/vgui/tf_matchmaking_dashboard_new_match_found.cpp new file mode 100644 index 0000000..b280a21 --- /dev/null +++ b/game/client/tf/vgui/tf_matchmaking_dashboard_new_match_found.cpp @@ -0,0 +1,99 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "tf_matchmaking_dashboard.h" +#include "tf_gc_client.h" +#include "tf_gamerules.h" + +using namespace vgui; +using namespace GCSDK; + +#ifdef STAGING_ONLY +extern ConVar tf_mm_popup_state_override; +#endif + +class CNewMatchFoundDashboardState : public CTFMatchmakingPopup +{ +public: + CNewMatchFoundDashboardState( const char* pszName, const char* pszResFile ) + : CTFMatchmakingPopup( pszName, pszResFile ) + , m_flAutoJoinTime( 0.f ) + {} + + virtual void OnEnter() OVERRIDE + { + CTFMatchmakingPopup::OnEnter(); + + // We want to coincide the auto-join time with a short pause after the match-summary sequence if we're in + // a match. + if ( GTFGCClientSystem()->BConnectedToMatchServer( false ) ) + { + Assert( TFGameRules()->State_Get() == GR_STATE_GAME_OVER || TFGameRules()->State_Get() == GR_STATE_TEAM_WIN ); + + const double flNow = gpGlobals->curtime; + + // There's a point that's the earliest we want to autojoin and also a point that's the latest we want to autojoin. + const double flDesiredAutoJoinTime = flNow + 10.; + const double flLatestJoinTime = TFGameRules()->State_Get() == GR_STATE_GAME_OVER ? TFGameRules()->GetStateTransitionTime() - 1. : TFGameRules()->GetStateTransitionTime() + TFGameRules()->GetPostMatchPeriod(); + const double flEarliestJoinTime = TFGameRules()->State_Get() == GR_STATE_GAME_OVER ? 0. : TFGameRules()->GetStateTransitionTime() + 10.; + + // We also want the minimum time of the autojoin to be 10 seconds long. If the earliest join time is greater than + // the now + 10, go with that. If now + 10 is beyond the latest join time, go with the latest join time. + m_flAutoJoinTime = Max( flEarliestJoinTime, flDesiredAutoJoinTime ); + m_flAutoJoinTime = Min( m_flAutoJoinTime, flLatestJoinTime ); + } + } + + virtual void OnUpdate() OVERRIDE + { + CTFMatchmakingPopup::OnUpdate(); + + // Autojoin time + wchar_t *pwszAutoJoinTime = NULL; + + // Update the countdown label + double flTimeUntilAutoJoin = Max( 0., m_flAutoJoinTime - gpGlobals->curtime ); + pwszAutoJoinTime = LocalizeNumberWithToken( "TF_Matchmaking_RollingQueue_AutojoinWarning", ceil( flTimeUntilAutoJoin ) ); + + SetDialogVariable( "auto_join", pwszAutoJoinTime ); + + + if ( m_flAutoJoinTime != 0.f && gpGlobals->curtime > m_flAutoJoinTime ) + { + GTFGCClientSystem()->JoinMMMatch(); + } + } + + virtual void OnExit() OVERRIDE + { + CTFMatchmakingPopup::OnExit(); + + // No more auto join timer + m_flAutoJoinTime = 0.f; + } + + virtual bool ShouldBeActve() const OVERRIDE + { +#ifdef STAGING_ONLY + if ( FStrEq( const_cast<CNewMatchFoundDashboardState*>(this)->GetName(), tf_mm_popup_state_override.GetString() ) ) + return true; +#endif + + if ( BInEndOfMatch() && !GTFGCClientSystem()->BConnectedToMatchServer( true ) && GTFGCClientSystem()->BHaveLiveMatch() ) + { + return true; + } + + return false; + } + +private: + double m_flAutoJoinTime; +}; + +REG_MM_POPUP_FACTORY( CNewMatchFoundDashboardState, "NewMatchFound", "resource/UI/MatchMakingDashboardPopup_NewMatch.res" )
\ No newline at end of file diff --git a/game/client/tf/vgui/tf_matchmaking_dashboard_next_map_voting.cpp b/game/client/tf/vgui/tf_matchmaking_dashboard_next_map_voting.cpp new file mode 100644 index 0000000..e8b86ac --- /dev/null +++ b/game/client/tf/vgui/tf_matchmaking_dashboard_next_map_voting.cpp @@ -0,0 +1,293 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "tf_matchmaking_dashboard.h" +#include "tf_gamerules.h" +#include "tf_gc_client.h" +#include "clientmode_tf.h" +#include <vgui_controls/AnimationController.h> +#include <vgui_controls/CircularProgressBar.h> + +using namespace vgui; +using namespace GCSDK; + +extern ConVar tf_mm_next_map_vote_time; + +#ifdef STAGING_ONLY +extern ConVar tf_mm_popup_state_override; +#endif + +#ifdef STAGING_ONLY +CON_COMMAND( test_next_map_vote, "Fakes a player voting" ) +{ + IGameEvent *event = gameeventmanager->CreateEvent( "player_next_map_vote_change" ); + if ( event ) + { + event->SetInt( "map_index", RandomInt( 0, 2 ) ); + // Client-side once it's actually happened + gameeventmanager->FireEventClientSide( event ); + } +} +#endif + +class CNextMapVotingDashboardState : public CTFMatchmakingPopup +{ +public: + CNextMapVotingDashboardState( const char* pszName, const char* pszResFile ) + : CTFMatchmakingPopup( pszName, pszResFile ) + , m_pTimerProgressBar( NULL ) + { + memset( m_arMapPanels, 0, sizeof( m_arMapPanels ) ); + ListenForGameEvent( "player_next_map_vote_change" ); + ListenForGameEvent( "vote_maps_changed" ); + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + CTFMatchmakingPopup::ApplySchemeSettings( pScheme ); + + m_pTimerProgressBar = FindControl< CircularProgressBar >( "TimeRemainingProgressBar", true ); + if ( m_pTimerProgressBar ) + { + m_pTimerProgressBar->SetProgressDirection( CircularProgressBar::PROGRESS_CCW ); + m_pTimerProgressBar->SetFgImage( GetLocalPlayerTeam() == TF_TEAM_RED ? "progress_bar_red" : "progress_bar_blu" ); + } + + for ( int i=0; i < NEXT_MAP_VOTE_OPTIONS; ++i ) + { + EditablePanel* pMapChoice = FindControl< EditablePanel >( CFmtStr( "MapChoice%d", i ), true ); + if ( pMapChoice ) + { + pMapChoice->LoadControlSettings( "resource/UI/MatchMakingDashboardPopup_MapVotePanel.res" ); + } + } + } + + virtual void PerformLayout() OVERRIDE + { + CTFMatchmakingPopup::PerformLayout(); + + SetMapChoiceSettings(); + UpdateVoteCounts(); + } + + virtual void OnUpdate() OVERRIDE + { + CTFMatchmakingPopup::OnUpdate(); + + // Default to looping 30 sec cycle for debugging + float flVoteEndTime = ( 30 + ( ( int( Plat_FloatTime() ) / 30 ) * 30 ) - Plat_FloatTime() ) / 30.f; + + if ( TFGameRules() ) + { + // Get the actual countdown if we have gamerules + flVoteEndTime = ( tf_mm_next_map_vote_time.GetInt() - ( gpGlobals->curtime - TFGameRules()->GetLastRoundStateChangeTime() ) ) / tf_mm_next_map_vote_time.GetFloat(); + } + + if ( m_pTimerProgressBar ) + { + m_pTimerProgressBar->SetProgress( flVoteEndTime ); + } + } + + virtual bool ShouldBeActve() const OVERRIDE + { +#ifdef STAGING_ONLY + if ( FStrEq( const_cast<CNextMapVotingDashboardState*>(this)->GetName(), tf_mm_popup_state_override.GetString() ) ) + return true; +#endif + + if ( BInEndOfMatch() && + TFGameRules() && + TFGameRules()->GetCurrentNextMapVotingState() == CTFGameRules::NEXT_MAP_VOTE_STATE_WAITING_FOR_USERS_TO_VOTE && + GTFGCClientSystem()->BConnectedToMatchServer( false ) ) + { + return true; + } + + return false; + } + + virtual void OnCommand( const char *pszCommand ) + { + if ( Q_strnicmp( pszCommand, "choice", 6 ) == 0 && + GetPlayerVoteState() == CTFGameRules::USER_NEXT_MAP_VOTE_UNDECIDED ) + { + int nIndex = atoi( pszCommand + 6 ); + Assert( nIndex >= 0 && nIndex <= 2 ); + if ( nIndex < 0 || nIndex > 2 ) + return; + + engine->ClientCmd( CFmtStr( "next_map_vote %d", nIndex ) ); + } + } + + virtual void FireGameEvent( IGameEvent *pEvent ) + { + if ( FStrEq( pEvent->GetName(), "player_next_map_vote_change" ) && + TFGameRules()->GetCurrentNextMapVotingState() == CTFGameRules::NEXT_MAP_VOTE_STATE_WAITING_FOR_USERS_TO_VOTE ) + { + ShowVoteByOtherPlayer( pEvent->GetInt( "map_index" ) ); + InvalidateLayout(); + surface()->PlaySound( UTIL_GetRandomSoundFromEntry( "Vote.Cast.Yes" ) ); + + return; + } + else if ( FStrEq( pEvent->GetName(), "vote_maps_changed" ) ) + { + InvalidateLayout( false, true ); + } + } + + virtual void OnEnter() OVERRIDE + { + // To get the voting options setup how they're supposed to be + InvalidateLayout( true, false); + + CTFMatchmakingPopup::OnEnter(); + } + +private: + + void SetMapChoiceSettings() + { + for ( int nIndex = 0; nIndex < NEXT_MAP_VOTE_OPTIONS; ++nIndex ) + { + const MapDef_t* pMapDef = NULL; + + if ( TFGameRules() ) + { + pMapDef = GetItemSchema()->GetMasterMapDefByIndex( TFGameRules()->GetNextMapVoteOption( nIndex ) ); + } + else + { + pMapDef = GetItemSchema()->GetMasterMapDefByIndex( RandomInt( 1, GetItemSchema()->GetMapCount() - 1 ) ); + } + + Assert( pMapDef ); + if ( !pMapDef ) + return; + + EditablePanel* pMapChoice = FindControl< EditablePanel >( CFmtStr( "MapChoice%d", nIndex ), true ); + if ( pMapChoice ) + { + ScalableImagePanel* pMapImage = pMapChoice->FindControl< ScalableImagePanel >( "MapImage", true ); + + // The image + if ( pMapImage ) + { + m_arMapPanels[ nIndex ].pMapImage = pMapImage; + char imagename[ 512 ]; + Q_snprintf( imagename, sizeof( imagename ), "..\\vgui\\maps\\menu_thumb_%s", pMapDef->pszMapName ); + pMapImage->SetImage( imagename ); + } + + // Label text + pMapChoice->SetDialogVariable( "mapname", g_pVGuiLocalize->Find( pMapDef->pszMapNameLocKey ) ); + m_arMapPanels[ nIndex ].pMapNameLabel = pMapChoice->FindControl< Label >( "NameLabel" ); + + // Fixup the button + Button* pButton = pMapChoice->FindControl< Button >( "SelectButton" ); + if ( pButton ) + { + m_arMapPanels[ nIndex ].pChooseButton = pButton; + pButton->SetCommand( CFmtStr( "choice%d", nIndex ) ); + // Dont let people click anymore if the've already voted + pButton->SetEnabled( GetPlayerVoteState() == CTFGameRules::USER_NEXT_MAP_VOTE_UNDECIDED ); + pButton->SetMouseInputEnabled( GetPlayerVoteState() == CTFGameRules::USER_NEXT_MAP_VOTE_UNDECIDED ); + + // Give the one the user selected a green border + if ( GetPlayerVoteState() == nIndex ) + { + pButton->SetArmed( true ); + pButton->MakeReadyForUse(); + pButton->SetArmedColor( pButton->GetButtonArmedFgColor(), scheme()->GetIScheme( GetScheme() )->GetColor( "CreditsGreen", Color( 94, 150, 49, 255 ) ) ); + } + } + } + } + } + + void ShowVoteByOtherPlayer( int nIndex ) + { + EditablePanel* pMapChoice = FindControl< EditablePanel >( CFmtStr( "MapChoice%d", nIndex ), true ); + if ( pMapChoice ) + { + // Play animation on the map that got voted on + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pMapChoice, "MapVoted" ); + } + } + + void UpdateVoteCounts() + { + int nVotes[ CTFGameRules::EUserNextMapVote::NUM_VOTE_STATES ]; + memset( nVotes, 0, sizeof( nVotes ) ); + int nTotalVotes = 0; + + CTFGameRules::EUserNextMapVote eWinningVote = CTFGameRules::USER_NEXT_MAP_VOTE_UNDECIDED; + if ( TFGameRules() ) + { + TFGameRules()->GetWinningVote( nVotes ); + } + else + { + // For testing on the main menu + for ( int i=0; i < NEXT_MAP_VOTE_OPTIONS; ++i ) + { + nVotes[ i ] += RandomInt( 0, 10 ); + eWinningVote = (CTFGameRules::EUserNextMapVote)( nVotes[ i ] >= nVotes[ eWinningVote ] ? i : eWinningVote ); + } + } + + // Calculate the total so we can do a % breakdown + for ( int i=0; i < NEXT_MAP_VOTE_OPTIONS; ++i ) + { + nTotalVotes += nVotes[ i ]; + } + + for ( int i=0; i < NEXT_MAP_VOTE_OPTIONS; ++i ) + { + float flPercent = nTotalVotes ? (float)nVotes[ i ] / nTotalVotes * 100.f : 0.f; + EditablePanel* pMapChoicePanel = FindControl< EditablePanel >( CFmtStr( "MapChoice%d", i ), true ); + if ( pMapChoicePanel ) + { + // Update the label with the % total + pMapChoicePanel->SetDialogVariable( "votes", CFmtStr( "%3.0f%%", flPercent ) ); + // Do a color change animation + if ( g_pClientMode && g_pClientMode->GetViewport() ) + { + g_pClientMode->GetViewportAnimationController()->StopAnimationSequence( pMapChoicePanel, i == eWinningVote ? "LosingNextMapVote" : "WinningNextMapVote" ); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pMapChoicePanel, i == eWinningVote ? "WinningNextMapVote" : "LosingNextMapVote" ); + } + } + } + } + + CTFGameRules::EUserNextMapVote GetPlayerVoteState() + { + if ( TFGameRules() ) + { + int nPlayerIndex = GetLocalPlayerIndex(); + return TFGameRules()->PlayerNextMapVoteState( nPlayerIndex ); + } + + return CTFGameRules::USER_NEXT_MAP_VOTE_UNDECIDED; + } + + CircularProgressBar* m_pTimerProgressBar; + + struct MapChoice_t + { + ScalableImagePanel* pMapImage; + Label* pMapNameLabel; + Button* pChooseButton; + }; + MapChoice_t m_arMapPanels[3]; +}; + +REG_MM_POPUP_FACTORY( CNextMapVotingDashboardState, "NextMapVoting", "resource/UI/MatchMakingDashboardPopup_NextMapVoting.res" )
\ No newline at end of file diff --git a/game/client/tf/vgui/tf_matchmaking_dashboard_next_map_winner.cpp b/game/client/tf/vgui/tf_matchmaking_dashboard_next_map_winner.cpp new file mode 100644 index 0000000..1658fb0 --- /dev/null +++ b/game/client/tf/vgui/tf_matchmaking_dashboard_next_map_winner.cpp @@ -0,0 +1,94 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "tf_matchmaking_dashboard.h" +#include "tf_gamerules.h" +#include "tf_gc_client.h" +#include <vgui/ISurface.h> + +using namespace vgui; +using namespace GCSDK; + +#ifdef STAGING_ONLY +extern ConVar tf_mm_popup_state_override; +#endif + +class CNextMapWinnerDashboardState : public CTFMatchmakingPopup +{ +public: + + CNextMapWinnerDashboardState( const char* pszName, const char* pszResFile ) + : CTFMatchmakingPopup( pszName, pszResFile ) + { + } + + virtual bool ShouldBeActve() const OVERRIDE + { +#ifdef STAGING_ONLY + if ( FStrEq( const_cast<CNextMapWinnerDashboardState*>(this)->GetName(), tf_mm_popup_state_override.GetString() ) ) + return true; +#endif + + int nVotes[ CTFGameRules::EUserNextMapVote::NUM_VOTE_STATES ]; + CTFGameRules::EUserNextMapVote eWinningVote = TFGameRules()->GetWinningVote( nVotes ); + + if ( BInEndOfMatch() && + TFGameRules() && + TFGameRules()->GetCurrentNextMapVotingState() == CTFGameRules::NEXT_MAP_VOTE_STATE_MAP_CHOSEN_PAUSE && + eWinningVote != CTFGameRules::USER_NEXT_MAP_VOTE_UNDECIDED && + GTFGCClientSystem()->BConnectedToMatchServer( false ) ) + { + return true; + } + + return false; + } + +private: + + virtual void OnEnter() OVERRIDE + { + CTFMatchmakingPopup::OnEnter(); + + MapDefIndex_t nWinningDefIndex; + if ( TFGameRules() ) + { + int nVotes[ CTFGameRules::EUserNextMapVote::NUM_VOTE_STATES ]; + CTFGameRules::EUserNextMapVote eWinningVote = TFGameRules()->GetWinningVote( nVotes ); + nWinningDefIndex = TFGameRules()->GetNextMapVoteOption( eWinningVote ); + } + else + { + Assert( false ); + nWinningDefIndex = RandomInt( 1, GetItemSchema()->GetMapCount() - 1 ); + } + + // Sound effect for success + surface()->PlaySound( UTIL_GetRandomSoundFromEntry( "Vote.Passed" ) ); + + const MapDef_t *pMapDef = GetItemSchema()->GetMasterMapDefByIndex( nWinningDefIndex ); + if ( pMapDef ) + { + Label* pMapNameLabel = FindControl< Label >( "NameLabel", true ); + if ( pMapNameLabel ) + { + pMapNameLabel->SetText( g_pVGuiLocalize->Find( pMapDef->pszMapNameLocKey ) ); + } + + ScalableImagePanel* pMapImage = FindControl< ScalableImagePanel >( "MapImage", true ); + if ( pMapImage ) + { + char imagename[ 512 ]; + Q_snprintf( imagename, sizeof( imagename ), "..\\vgui\\maps\\menu_thumb_%s", pMapDef->pszMapName ); + pMapImage->SetImage( imagename ); + } + } + } +}; + +REG_MM_POPUP_FACTORY( CNextMapWinnerDashboardState, "NextMapWinner", "resource/UI/MatchMakingDashboardPopup_NextMapWinner.res" )
\ No newline at end of file diff --git a/game/client/tf/vgui/tf_matchmaking_panel.cpp b/game/client/tf/vgui/tf_matchmaking_panel.cpp new file mode 100644 index 0000000..fae794a --- /dev/null +++ b/game/client/tf/vgui/tf_matchmaking_panel.cpp @@ -0,0 +1,580 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "tf_matchmaking_panel.h" +#include "ienginevgui.h" +#include "c_tf_gamestats.h" +#include "clientmode_tf.h" +#include "tf_hud_mainmenuoverride.h" +#include "vgui_int.h" +#include "IGameUIFuncs.h" // for key bindings +#include <vgui_controls/AnimationController.h> +#include "vgui/IInput.h" +#include "tf_gc_client.h" +#include "tf_party.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +void AddSubKeyNamed( KeyValues *pKeys, const char *pszName ); +extern void ShowEconRequirementDialog( const char *pTitle, const char *pText, const char *pItemDefName ); + +CMatchMakingPanel *GetMatchMakingPanel() +{ + CMatchMakingPanel *pMatchMakingPanel = (CMatchMakingPanel*)gViewPortInterface->FindPanelByName( PANEL_MATCHMAKING ); + return pMatchMakingPanel; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CMatchMakingPanel::CMatchMakingPanel( IViewPort *pViewPort ) : EditablePanel( NULL, PANEL_MATCHMAKING ), m_iMMPanelKey( BUTTON_CODE_INVALID ) +{ + vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme"); + SetScheme(scheme); + SetProportional( true ); + + ListenForGameEvent( "gameui_hidden" ); + ListenForGameEvent( "client_beginconnect" ); + + EditablePanel *m_pMainContainer = new EditablePanel( this, "MainContainer" ); + Assert( m_pMainContainer ); + + m_pCompetitiveModeGroupPanel = new vgui::EditablePanel( m_pMainContainer, "CompetitiveModeGroupBox" ); + m_pModeLabel = new vgui::Label( m_pCompetitiveModeGroupPanel, "LadderLabel", "" ); + m_pModeComboBox = new vgui::ComboBox( m_pCompetitiveModeGroupPanel, "ModeComboBox", 3, false ); + m_pModeComboBox->AddActionSignalTarget( this ); + m_pStopSearchButton = new vgui::Button( m_pCompetitiveModeGroupPanel, "StopSearchButton", "" ); + m_pStopSearchButton->AddActionSignalTarget( this ); + m_pSearchButton = new vgui::Button( m_pCompetitiveModeGroupPanel, "SearchButton", "" ); + m_pSearchButton->AddActionSignalTarget( this ); + m_pSearchActiveGroupBox = new vgui::EditablePanel( m_pMainContainer, "SearchActiveGroupBox" ); + m_pSearchActiveTitleLabel = new vgui::Label( m_pSearchActiveGroupBox, "SearchActiveTitle", "" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CMatchMakingPanel::~CMatchMakingPanel() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMatchMakingPanel::AttachToGameUI( void ) +{ + C_CTFGameStats::ImmediateWriteInterfaceEvent( "interface_open", "tf_matchmaking_panel" ); + + if ( GetClientModeTFNormal()->GameUI() ) + { + GetClientModeTFNormal()->GameUI()->SetMainMenuOverride( GetVPanel() ); + } + + SetKeyBoardInputEnabled( true ); + SetMouseInputEnabled( true ); + SetCursor(dc_arrow); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CMatchMakingPanel::GetName( void ) +{ + return PANEL_MATCHMAKING; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMatchMakingPanel::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "Resource/UI/MatchMakingPanel.res" ); + + // The outer dim / close button + { + Button *pButton = FindControl< Button >( "OutsideCloseButton" ); + if ( pButton ) + { + pButton->AddActionSignalTarget( this ); + pButton->SetPaintBackgroundEnabled( false ); + } + } + + if ( m_pModeComboBox && m_pModeComboBox->IsVisible() ) + { + // Placeholder + m_pModeComboBox->RemoveAll(); + vgui::HFont hFont = pScheme->GetFont( "HudFontSmallestBold", true ); + m_pModeComboBox->SetFont( hFont ); + KeyValues *pKeyValues = new KeyValues( "data" ); + pKeyValues->SetInt( "bracket", k_nMatchGroup_Ladder_6v6 ); + m_pModeComboBox->AddItem( "6v6", pKeyValues ); + pKeyValues->SetInt( "bracket", k_nMatchGroup_Ladder_9v9 ); + m_pModeComboBox->AddItem( "9v9", pKeyValues ); + pKeyValues->deleteThis(); + m_pModeComboBox->ActivateItemByRow( 0 ); + } + + if ( m_pSearchButton ) + { + m_pSearchButton->AddActionSignalTarget( this ); + } + if ( m_pStopSearchButton ) + { + m_pStopSearchButton->AddActionSignalTarget( this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMatchMakingPanel::PerformLayout() +{ + BaseClass::PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMatchMakingPanel::OnCommand( const char *pCommand ) +{ + if ( FStrEq( pCommand, "close" ) ) + { + if ( enginevgui->IsGameUIVisible() ) + { + ShowPanel( false ); + } + else + { + IViewPortPanel *pMMPanel = ( gViewPortInterface->FindPanelByName( PANEL_MATCHMAKING ) ); + if ( pMMPanel ) + { + gViewPortInterface->ShowPanel( pMMPanel, false ); + } + } + } + else if ( FStrEq( pCommand, "search" ) ) + { + StartSearch(); + } + else if ( FStrEq( pCommand, "stopsearch" ) ) + { + StopSearch(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMatchMakingPanel::OnTextChanged( KeyValues *data ) +{ + if ( !IsVisible() ) + return; + + if ( !GCClientSystem()->BConnectedtoGC() ) + return; + + Panel *pPanel = reinterpret_cast< vgui::Panel* >( data->GetPtr( "panel" ) ); + + vgui::ComboBox *pComboBox = dynamic_cast< vgui::ComboBox* >( pPanel ); + if ( pComboBox ) + { + if ( pComboBox == m_pModeComboBox ) + { + KeyValues *pUserData = m_pModeComboBox->GetActiveItemUserData(); + if ( !pUserData ) + return; + + uint32 unModeType = pUserData->GetInt( "bracket", (uint32)-1 ); + + if ( unModeType >= k_nMatchGroup_Ladder_First && unModeType <= k_nMatchGroup_Ladder_Last ) + { + GTFGCClientSystem()->SetLadderType( unModeType ); + } +// else if ( unModeType >= k_nMatchGroup_Quickplay_First && unModeType <= k_nMatchGroup_Quickplay_Last ) +// { +// GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_QUICKPLAY ); +// } + + return; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMatchMakingPanel::FireGameEvent( IGameEvent *event ) +{ + if ( FStrEq( event->GetName(), "gameui_hidden" ) ) + { + ShowPanel( false ); + return; + } + else if ( FStrEq( event->GetName(), "client_beginconnect" ) ) + { + ShowPanel( false ); + return; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMatchMakingPanel::ShowPanel( bool bShow ) +{ + // Snag this so we know what to listen for + m_iMMPanelKey = gameuifuncs->GetButtonCodeForBind( "show_matchmaking" ); + + if ( bShow + && ( !steamapicontext + || !steamapicontext->SteamUtils() + || !steamapicontext->SteamMatchmakingServers() + || !steamapicontext->SteamUser() + || !steamapicontext->SteamUser()->BLoggedOn() ) ) + { + Warning( "Steam not properly initialized or connected.\n" ); + ShowMessageBox( "#TF_MM_GenericFailure_Title", "#TF_MM_GenericFailure", "#GameUI_OK" ); + ShowPanel( false ); + } + + if ( bShow && !GCClientSystem()->BConnectedtoGC() ) + { + Warning( "Not connected to GC.\n" ); + ShowMessageBox( "#TF_MM_NoGC_Title", "#TF_MM_NoGC", "#GameUI_OK" ); + ShowPanel( false ); + } + + bool bInMMGame = GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_InGame && + GTFGCClientSystem()->GetAssignedMatchAbandonStatus() == k_EAbandonGameStatus_AbandonWithPenalty; + bool bSearching = GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_InQueue; + + // DevMsg( "CMatchmakingPanel shown, bInMMGame %d bSearching %d\n", bInMMGame, bSearching ); + + if ( m_pModeLabel ) + { + m_pModeLabel->SetVisible( true ); + } + if ( m_pModeComboBox ) + { + m_pModeComboBox->SetEnabled( !bSearching && !bInMMGame ); + m_pModeComboBox->SetVisible( !bSearching ); + } + if ( m_pSearchButton ) + { + m_pSearchButton->SetEnabled( !bSearching && !bInMMGame ); + m_pSearchButton->SetVisible( !bSearching && !bInMMGame ); + } + if ( m_pStopSearchButton ) + { + m_pStopSearchButton->SetEnabled( bSearching && !bInMMGame ); + m_pStopSearchButton->SetVisible( bSearching && !bInMMGame ); + } + if ( m_pSearchActiveGroupBox ) + { + m_pSearchActiveGroupBox->SetVisible( bSearching && !bInMMGame ); + } + + if ( bShow ) + { + KeyValues *pUserData = m_pModeComboBox->GetActiveItemUserData(); + if ( pUserData ) + { + uint32 unModeType = pUserData->GetInt( "bracket", (uint32)-1 ); + + if ( unModeType >= k_nMatchGroup_Ladder_First && unModeType <= k_nMatchGroup_Ladder_Last ) + { + GTFGCClientSystem()->SetLadderType( unModeType ); + } + } + } + + SetVisible( bShow ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMatchMakingPanel::OnKeyCodePressed( KeyCode code ) +{ + if ( code == m_iMMPanelKey ) + { + ShowPanel( false ); + return; + } + + BaseClass::OnKeyCodePressed( code ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMatchMakingPanel::OnKeyCodeTyped( KeyCode code ) +{ + if ( code == KEY_ESCAPE ) + { + if ( IsVisible() ) + { + SetVisible( false ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMatchMakingPanel::OnThink() +{ + if ( GTFGCClientSystem()->GetWizardStep() == TF_Matchmaking_WizardStep_SEARCHING ) + { + const CMsgMatchmakingProgress &progress = GTFGCClientSystem()->m_msgMatchmakingProgress; + wchar_t wszCount[32]; + CUtlVector< vgui::Label* > vecNearbyFields; + vgui::Label *pNearbyColumnHead = dynamic_cast< vgui::Label* >( FindChildByName( "NearbyColumnHead", true ) ); + Assert( pNearbyColumnHead ); + vecNearbyFields.AddToTail( pNearbyColumnHead ); + +#define DO_FIELD( protobufname, labelname, bNearby ) \ + vgui::Label *p##labelname = dynamic_cast< vgui::Label* >( FindChildByName( #labelname, true ) ); \ + Assert( p##labelname ); \ + if ( p##labelname ) \ + { \ + if ( progress.has_##protobufname() ) \ + { \ + _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", progress.protobufname() ); \ + p##labelname->SetText( wszCount ); \ + if ( bNearby ) bHasAnyNearbyData = true; \ + } \ + else \ + { \ + p##labelname->SetText( "#TF_Matchmaking_NoData" ); \ + } \ + if ( bNearby ) vecNearbyFields.AddToTail( p##labelname ); \ + } + + bool bHasAnyNearbyData = false; + DO_FIELD( matching_worldwide_searching_players, PlayersSearchingMatchingWorldwideValue, false ) + DO_FIELD( matching_near_you_searching_players, PlayersSearchingMatchingNearbyValue, true ) + DO_FIELD( matching_worldwide_active_players, PlayersInGameMatchingWorldwideValue, false ) + DO_FIELD( matching_near_you_active_players, PlayersInGameMatchingNearbyValue, true ) + DO_FIELD( matching_worldwide_empty_gameservers, EmptyGameserversMatchingWorldwideValue, false ) + DO_FIELD( matching_near_you_empty_gameservers, EmptyGameserversMatchingNearbyValue, true ) + DO_FIELD( total_worldwide_searching_players, PlayersSearchingTotalWorldwideValue, false ) + DO_FIELD( total_near_you_searching_players, PlayersSearchingTotalNearbyValue, true ) + DO_FIELD( total_worldwide_active_players, PlayersInGameTotalWorldwideValue, false ) + DO_FIELD( total_near_you_active_players, PlayersInGameTotalNearbyValue, true ) + + FOR_EACH_VEC( vecNearbyFields, i ) + { + vecNearbyFields[i]->SetVisible( bHasAnyNearbyData ); + } + + // HOLY CHEESEBALL BUSY INDICATOR + const wchar_t *pwszEllipses = &L"....."[4 - ( (unsigned)Plat_FloatTime() % 5U )]; + wchar_t wszLocalized[512]; + g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_Searching" ), 1, pwszEllipses ); + m_pSearchActiveTitleLabel->SetText( wszLocalized ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMatchMakingPanel::SetVisible( bool bState ) +{ + if ( bState == true ) + { +// IGameEvent *event = gameeventmanager->CreateEvent( "questlog_opened" ); +// if ( event ) +// { +// gameeventmanager->FireEventClientSide( event ); +// } + + if ( enginevgui->IsGameUIVisible() ) + { + AttachToGameUI(); + } + else + { + ipanel()->SetParent( GetVPanel(), VGui_GetClientDLLRootPanel() ); + + MakePopup( false, true ); + SetKeyBoardInputEnabled( true ); + SetMouseInputEnabled( true ); + MoveToFront(); + } + + engine->ClientCmd_Unrestricted( "gameui_preventescapetoshow\n" ); + vgui::surface()->PlaySound( "ui/panel_open.wav" ); + } + else if ( IsVisible() ) + { + // Detach from the GameUI when we hide + IViewPortPanel *pMMOverride = gViewPortInterface->FindPanelByName( PANEL_MAINMENUOVERRIDE ); + if ( pMMOverride ) + { + ((CHudMainMenuOverride*)pMMOverride)->AttachToGameUI(); + } + + engine->ClientCmd_Unrestricted( "gameui_allowescapetoshow\n" ); + vgui::surface()->PlaySound( "ui/panel_close.wav" ); + } + + BaseClass::SetVisible( bState ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMatchMakingPanel::StartSearch( void ) +{ +#ifdef STAGING_ONLY + GTFGCClientSystem()->EndMatchmaking(); + + bool bCompetitive = GTFGCClientSystem()->GetWizardStep() == TF_Matchmaking_WizardStep_LADDER; + + // Solo + CTFParty *pParty = GTFGCClientSystem()->GetParty(); + if ( !pParty || pParty->GetNumMembers() <= 1 ) + { + if ( bCompetitive && !GTFGCClientSystem()->BHasCompetitiveAccess() ) + { + ShowEconRequirementDialog( "#TF_Competitive_RequiresPass_Title", "#TF_Competitive_RequiresPass", CTFItemSchema::k_rchLadderPassItemDefName ); + return; + } + else if ( bCompetitive && !CheckCompetitiveConvars() ) + { + ShowMessageBox( "#TF_Competitive_Convars_CantProceed_Title", "#TF_Competitive_Convars_CantProceed", "#GameUI_OK" ); + return; + } + } + // Group + else + { + wchar_t wszLocalized[512]; + char szLocalized[512]; + wchar_t wszCharPlayerName[128]; + + bool bAnyMembersWithoutAuth = false; + + for ( int i = 0; i < pParty->GetNumMembers(); ++i ) + { + if ( bCompetitive ) + { + if ( !pParty->Obj().members( i ).competitive_access() ) + { + bAnyMembersWithoutAuth = true; + V_UTF8ToUnicode( steamapicontext->SteamFriends()->GetFriendPersonaName( pParty->GetMember( i ) ), wszCharPlayerName, sizeof( wszCharPlayerName ) ); + g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_MissingPass" ), 1, wszCharPlayerName ); + g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalized, szLocalized, sizeof( szLocalized ) ); + + GTFGCClientSystem()->SendSteamLobbyChat( CTFGCClientSystem::k_eLobbyMsg_SystemMsgFromLeader, szLocalized ); + } + } + } + + if ( bAnyMembersWithoutAuth ) + { + ShowMessageBox( "#TF_Competitive_RequiresPass_Title", "#TF_Competitive_RequiresPass", "#GameUI_OK" ); + return; + } + } + + { + KeyValues *pUserData = m_pModeComboBox->GetActiveItemUserData(); + if ( !pUserData ) + return; + + uint32 unModeType = pUserData->GetInt( "bracket", (uint32)-1 ); + if ( unModeType >= k_nMatchGroup_Ladder_First && unModeType <= k_nMatchGroup_Ladder_Last ) + { + GTFGCClientSystem()->BeginMatchmaking( TF_Matchmaking_LADDER ); + } + else if ( unModeType >= k_nMatchGroup_Quickplay_First && unModeType <= k_nMatchGroup_Quickplay_Last ) + { + // No longer support quickplay + Assert( false ); + } + } + + + GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_SEARCHING ); + + if ( m_pModeLabel ) + { + m_pModeLabel->SetVisible( false ); + } + if ( m_pModeComboBox ) + { + m_pModeComboBox->SetEnabled( false ); + m_pModeComboBox->SetVisible( false ); + } + if ( m_pSearchButton ) + { + m_pSearchButton->SetEnabled( false ); + m_pSearchButton->SetVisible( false ); + } + if ( m_pStopSearchButton ) + { + m_pStopSearchButton->SetEnabled( true ); + m_pStopSearchButton->SetVisible( true ); + } + if ( m_pSearchActiveGroupBox ) + { + m_pSearchActiveGroupBox->SetVisible( true ); + } +#endif // STAGING_ONLY +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMatchMakingPanel::StopSearch( void ) +{ + if ( m_pModeLabel ) + { + m_pModeLabel->SetVisible( true ); + } + if ( m_pModeComboBox ) + { + m_pModeComboBox->SetEnabled( true ); + m_pModeComboBox->SetVisible( true ); + } + if ( m_pSearchButton ) + { + m_pSearchButton->SetEnabled( true ); + m_pSearchButton->SetVisible( true ); + } + if ( m_pStopSearchButton ) + { + m_pStopSearchButton->SetEnabled( false ); + m_pStopSearchButton->SetVisible( false ); + } + if ( m_pSearchActiveGroupBox ) + { + m_pSearchActiveGroupBox->SetVisible( false ); + } + + GTFGCClientSystem()->EndMatchmaking(); +} + +#ifdef STAGING_ONLY +// Just VGUI things... +static void cc_tf_mm_panel_reload() +{ + CMatchMakingPanel *pPanel = GetMatchMakingPanel(); + if ( pPanel ) + { + pPanel->InvalidateLayout( true, true ); + gViewPortInterface->ShowPanel( pPanel, true ); + } +} +ConCommand tf_mm_panel_reload( "tf_mm_panel_reload", cc_tf_mm_panel_reload ); +#endif diff --git a/game/client/tf/vgui/tf_matchmaking_panel.h b/game/client/tf/vgui/tf_matchmaking_panel.h new file mode 100644 index 0000000..fa7ae40 --- /dev/null +++ b/game/client/tf/vgui/tf_matchmaking_panel.h @@ -0,0 +1,75 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef MATCH_MAKING_PANEL_H +#define MATCH_MAKING_PANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "vgui_controls/EditablePanel.h" +#include <game/client/iviewport.h> +#include "quest_log_panel.h" + +using namespace vgui; + +class CBaseLobbyPanel; +class CMatchMakingPanel *GetMatchMakingPanel(); + +//----------------------------------------------------------------------------- +// The default quest log panel +//----------------------------------------------------------------------------- +class CMatchMakingPanel : public EditablePanel, public IViewPortPanel, public CGameEventListener +{ + DECLARE_CLASS_SIMPLE( CMatchMakingPanel, EditablePanel ); +public: + CMatchMakingPanel( IViewPort *pViewPort ); + virtual ~CMatchMakingPanel(); + + void AttachToGameUI(); + virtual const char *GetName( void ) OVERRIDE; + virtual void SetData( KeyValues *data ) OVERRIDE {} + virtual void Reset() OVERRIDE { Update(); SetVisible( true ); } + virtual void Update() OVERRIDE { return; } + virtual bool NeedsUpdate( void ) OVERRIDE { return false; } + virtual bool HasInputElements( void ) OVERRIDE { return true; } + virtual void ShowPanel( bool bShow ) OVERRIDE; + + // both vgui::Frame and IViewPortPanel define these, so explicitly define them here as passthroughs to vgui + vgui::VPANEL GetVPanel( void ){ return BaseClass::GetVPanel(); } + virtual bool IsVisible() OVERRIDE { return BaseClass::IsVisible(); } + virtual void SetParent( vgui::VPANEL parent ) OVERRIDE { BaseClass::SetParent( parent ); } + + virtual GameActionSet_t GetPreferredActionSet() { return GAME_ACTION_SET_MENUCONTROLS; } + + virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE; + virtual void PerformLayout() OVERRIDE; + virtual void OnCommand( const char *pCommand ) OVERRIDE; + virtual void FireGameEvent( IGameEvent *event ) OVERRIDE; + virtual void SetVisible( bool bState ) OVERRIDE; + virtual void OnKeyCodePressed( KeyCode code ) OVERRIDE; + virtual void OnKeyCodeTyped(KeyCode code) OVERRIDE; + virtual void OnThink() OVERRIDE; + + MESSAGE_FUNC_PARAMS( OnTextChanged, "TextChanged", data ); + +private: + void StartSearch( void ); + void StopSearch( void ); + + ButtonCode_t m_iMMPanelKey; + vgui::EditablePanel *m_pMainContainer; + vgui::EditablePanel *m_pCompetitiveModeGroupPanel; + vgui::Label *m_pModeLabel; + vgui::ComboBox *m_pModeComboBox; + vgui::Button *m_pStopSearchButton; + vgui::Button *m_pSearchButton; + vgui::EditablePanel *m_pSearchActiveGroupBox; + vgui::Label *m_pSearchActiveTitleLabel; +}; + +#endif // MATCH_MAKING_PANEL_H diff --git a/game/client/tf/vgui/tf_mouseforwardingpanel.cpp b/game/client/tf/vgui/tf_mouseforwardingpanel.cpp new file mode 100644 index 0000000..e9a47fa --- /dev/null +++ b/game/client/tf/vgui/tf_mouseforwardingpanel.cpp @@ -0,0 +1,81 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" + +#include "tf_mouseforwardingpanel.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +//=============================================================================// + +DECLARE_BUILD_FACTORY( CMouseMessageForwardingPanel ); + +CMouseMessageForwardingPanel::CMouseMessageForwardingPanel( Panel *parent, const char *name ) : BaseClass( parent, name ) +{ + // don't draw an + SetPaintEnabled(false); + SetPaintBackgroundEnabled(false); + SetPaintBorderEnabled(false); +} + +void CMouseMessageForwardingPanel::PerformLayout() +{ + // fill out the whole area + int w, t; + GetParent()->GetSize(w, t); + SetBounds(0, 0, w, t); +} + +void CMouseMessageForwardingPanel::OnCursorEntered() +{ + if ( GetParent() ) + { + GetParent()->OnCursorEntered(); + } +} + +void CMouseMessageForwardingPanel::OnCursorExited() +{ + if ( GetParent() ) + { + GetParent()->OnCursorExited(); + } +} + +void CMouseMessageForwardingPanel::OnMousePressed( vgui::MouseCode code ) +{ + if ( GetParent() ) + { + GetParent()->OnMousePressed( code ); + } +} + +void CMouseMessageForwardingPanel::OnMouseReleased( vgui::MouseCode code ) +{ + if ( GetParent() ) + { + GetParent()->OnMouseReleased( code ); + } +} + +void CMouseMessageForwardingPanel::OnMouseDoublePressed( vgui::MouseCode code ) +{ + if ( GetParent() ) + { + GetParent()->OnMouseDoublePressed( code ); + } +} + +void CMouseMessageForwardingPanel::OnMouseWheeled(int delta) +{ + if ( GetParent() ) + { + GetParent()->OnMouseWheeled( delta ); + } +} + diff --git a/game/client/tf/vgui/tf_mouseforwardingpanel.h b/game/client/tf/vgui/tf_mouseforwardingpanel.h new file mode 100644 index 0000000..e260a2e --- /dev/null +++ b/game/client/tf/vgui/tf_mouseforwardingpanel.h @@ -0,0 +1,34 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef TF_MOUSEFORWARDINGPANEL_H +#define TF_MOUSEFORWARDINGPANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include <vgui_controls/EditablePanel.h> + +//----------------------------------------------------------------------------- +// Purpose: Invisible panel that forwards up mouse movement +//----------------------------------------------------------------------------- +class CMouseMessageForwardingPanel : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CMouseMessageForwardingPanel, vgui::EditablePanel ); +public: + CMouseMessageForwardingPanel( Panel *parent, const char *name ); + + virtual void PerformLayout( void ); + virtual void OnCursorEntered(); + virtual void OnCursorExited(); + virtual void OnMousePressed( vgui::MouseCode code ); + virtual void OnMouseReleased( vgui::MouseCode code ); + virtual void OnMouseDoublePressed( vgui::MouseCode code ); + virtual void OnMouseWheeled(int delta); +}; + +#endif // TF_MOUSEFORWARDINGPANEL_H diff --git a/game/client/tf/vgui/tf_overview.cpp b/game/client/tf/vgui/tf_overview.cpp new file mode 100644 index 0000000..96b626b --- /dev/null +++ b/game/client/tf/vgui/tf_overview.cpp @@ -0,0 +1,859 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include <vgui/ISurface.h> +#include <vgui/ILocalize.h> +#include "tf_shareddefs.h" +#include "tf_overview.h" +#include "c_playerresource.h" +#include "c_tf_objective_resource.h" +#include "usermessages.h" +#include "coordsize.h" +#include "clientmode.h" +#include <vgui_controls/AnimationController.h> +#include "voice_status.h" +#include "spectatorgui.h" +#include "c_team_objectiveresource.h" + +using namespace vgui; + +void __MsgFunc_UpdateRadar( bf_read &msg ) +{ + if ( !g_pMapOverview ) + return; + + int iPlayerEntity = msg.ReadByte(); + + while ( iPlayerEntity > 0 ) + { + int x = msg.ReadSBitLong( COORD_INTEGER_BITS-1 ) * 4; + int y = msg.ReadSBitLong( COORD_INTEGER_BITS-1 ) * 4; + int a = msg.ReadSBitLong( 9 ); + + Vector origin( x, y, 0 ); + QAngle angles( 0, a, 0 ); + + g_pMapOverview->SetPlayerPositions( iPlayerEntity-1, origin, angles ); + + iPlayerEntity = msg.ReadByte(); // read index for next player + } +} + +extern ConVar _overview_mode; +ConVar _cl_minimapzoom( "_cl_minimapzoom", "1", FCVAR_ARCHIVE ); +ConVar _overview_mode( "_overview_mode", "1", FCVAR_ARCHIVE, "Overview mode - 0=off, 1=inset, 2=full\n", true, 0, true, 2 ); + + +CTFMapOverview *GetTFOverview( void ) +{ + return dynamic_cast<CTFMapOverview *>( g_pMapOverview ); +} + +// overview_togglezoom rotates through 3 levels of zoom for the small map +//----------------------------------------------------------------------- +void ToggleZoom( void ) +{ + if ( !GetTFOverview() ) + return; + + GetTFOverview()->ToggleZoom(); +} +static ConCommand overview_togglezoom( "overview_togglezoom", ToggleZoom ); + +// overview_largemap toggles showing the large map +//------------------------------------------------ +void ShowLargeMap( void ) +{ + if ( !GetTFOverview() ) + return; + + GetTFOverview()->ShowLargeMap(); +} +static ConCommand overview_showlargemap( "+overview_largemap", ShowLargeMap ); + +void HideLargeMap( void ) +{ + if ( !GetTFOverview() ) + return; + + GetTFOverview()->HideLargeMap(); +} +static ConCommand overview_hidelargemap( "-overview_largemap", HideLargeMap ); + +//-------------------------------- +// map border ? +// icon minimum zoom +// flag swipes +// chatting icon +// voice com icon +//--------------------------------- + +DECLARE_HUDELEMENT( CTFMapOverview ); + +ConVar tf_overview_voice_icon_size( "tf_overview_voice_icon_size", "64", FCVAR_ARCHIVE ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFMapOverview::CTFMapOverview( const char *pElementName ) : BaseClass( pElementName ) +{ + InitTeamColorsAndIcons(); + m_flIconSize = 96.0f; + m_iLastMode = MAP_MODE_OFF; + m_bDisabled = false; + m_nMapTextureOverlayID = -1; + usermessages->HookMessage( "UpdateRadar", __MsgFunc_UpdateRadar ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapOverview::Update() +{ + UpdateCapturePoints(); + + BaseClass::Update(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapOverview::VidInit( void ) +{ + BaseClass::VidInit(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapOverview::UpdateCapturePoints() +{ + if ( !g_pObjectiveResource ) + return; + + Color colorGreen( 0,255,0,255 ); + + for( int i = 0 ; i < g_pObjectiveResource->GetNumControlPoints() ; i++ ) + { + // check if CP is visible at all + if( !g_pObjectiveResource->IsCPVisible( i ) ) + { + if ( m_CapturePoints[i] != 0 ) + { + // remove capture point from map + RemoveObject( m_CapturePoints[i] ); + m_CapturePoints[i] = 0; + } + + continue; + } + + // ok, show CP + int iOwningTeam = g_pObjectiveResource->GetOwningTeam(i); + int iCappingTeam = g_pObjectiveResource->GetCappingTeam(i); + + int iOwningIcon = g_pObjectiveResource->GetIconForTeam( i, iOwningTeam ); + if ( iOwningIcon <= 0 ) + continue; // baah + + const char *textureName = GetMaterialNameFromIndex( iOwningIcon ); + + int objID = m_CapturePoints[i]; + + if ( objID == 0 ) + { + // add object if not already there + objID = m_CapturePoints[i] = AddObject( textureName, 0, -1 ); + + // objective positions never change (so far) + SetObjectPosition( objID, g_pObjectiveResource->GetCPPosition(i), vec3_angle ); + + AddObjectFlags( objID, MAP_OBJECT_ALIGN_TO_MAP ); + } + + SetObjectIcon( objID, textureName, 128.0 ); + + // draw cap percentage + if( iCappingTeam != TEAM_UNASSIGNED ) + { + SetObjectStatus( objID, g_pObjectiveResource->GetCPCapPercentage(i), colorGreen ); + } + else + { + SetObjectStatus( objID, -1, colorGreen ); // turn it off + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapOverview::InitTeamColorsAndIcons() +{ + BaseClass::InitTeamColorsAndIcons(); + + m_TeamColors[TF_TEAM_RED] = COLOR_TF_RED; + m_TeamIcons[TF_TEAM_RED] = AddIconTexture( "sprites/minimap_icons/red_player" ); + m_CameraIcons[TF_TEAM_RED] = AddIconTexture( "sprites/minimap_icons/red_camera" ); + + m_TeamColors[TF_TEAM_BLUE] = COLOR_TF_BLUE; + m_TeamIcons[TF_TEAM_BLUE] = AddIconTexture( "sprites/minimap_icons/blue_player" ); + m_CameraIcons[TF_TEAM_BLUE] = AddIconTexture( "sprites/minimap_icons/blue_camera" ); + + Q_memset( m_flPlayerChatTime, 0, sizeof(m_flPlayerChatTime ) ); + m_iVoiceIcon = AddIconTexture( "voice/icntlk_pl" ); + m_iChatIcon = AddIconTexture( "sprites/minimap_icons/voiceIcon" ); + + Q_memset( m_CapturePoints, 0, sizeof(m_CapturePoints) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapOverview::DrawCamera() +{ + C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer(); + + if ( !localPlayer ) + return; + + int iTexture = m_CameraIcons[localPlayer->GetTeamNumber()]; + + if ( localPlayer->IsObserver() || iTexture <= 0 ) + { + BaseClass::DrawCamera(); + } + else + { + MapObject_t obj; + memset( &obj, 0, sizeof(MapObject_t) ); + + obj.icon = iTexture; + obj.position = localPlayer->GetAbsOrigin(); + obj.size = m_flIconSize * 1.5; + obj.angle = localPlayer->EyeAngles(); + obj.status = -1; + + DrawIcon( &obj ); + + DrawVoiceIconForPlayer( localPlayer->entindex() - 1 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapOverview::FireGameEvent( IGameEvent *event ) +{ + const char * type = event->GetName(); + + if ( Q_strcmp( type, "player_death" ) == 0 ) + { + MapPlayer_t *player = GetPlayerByUserID( event->GetInt( "userid" ) ); + + if ( player && CanPlayerBeSeen( player ) ) + { + // create skull icon for 3 seconds + int handle = AddObject( "sprites/minimap_icons/death", 0, 3 ); + SetObjectText( handle, player->name, player->color ); + SetObjectPosition( handle, player->position, player->angle ); + } + } + else if ( Q_strcmp( type, "game_newmap" ) == 0 ) + { + SetMode( _overview_mode.GetInt() ); + } + + BaseClass::FireGameEvent( event ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFMapOverview::CanPlayerBeSeen( MapPlayer_t *player ) +{ + // rules that define if you can see a player on the overview or not + C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer(); + + if ( !localPlayer || !player ) + return false; + + // don't draw ourselves + if ( localPlayer->entindex() == (player->index+1) ) + return false; + + // if local player is on spectator team, he can see everyone + if ( localPlayer->GetTeamNumber() <= TEAM_SPECTATOR ) + return true; + + // we never track unassigned or real spectators + if ( player->team <= TEAM_SPECTATOR ) + return false; + + // ingame and as dead player we can only see our own teammates + return ( localPlayer->GetTeamNumber() == player->team ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapOverview::ShowLargeMap( void ) +{ + if ( IsDisabled() ) + { + return; + } + + // remember old mode + m_iLastMode = GetMode(); + + // if we hit the toggle while full, set to disappear when we release + if ( m_iLastMode == MAP_MODE_FULL ) + { + m_iLastMode = MAP_MODE_OFF; + } + + SetMode( MAP_MODE_FULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapOverview::HideLargeMap( void ) +{ + if ( IsDisabled() ) + { + return; + } + + SetMode( m_iLastMode ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapOverview::ToggleZoom( void ) +{ + if ( IsDisabled() ) + { + return; + } + + if ( GetMode() != MAP_MODE_INSET ) + return; + + int iZoomLevel = ( _cl_minimapzoom.GetInt() + 1 ) % TF_MAP_ZOOM_LEVELS; + + _cl_minimapzoom.SetValue( iZoomLevel ); + + switch( _cl_minimapzoom.GetInt() ) + { + case 0: + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "MapZoomLevel1" ); + break; + case 1: + default: + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "MapZoomLevel2" ); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapOverview::SetMode(int mode) +{ + if ( IsDisabled() ) + { + return; + } + + m_flChangeSpeed = 0; // change size instantly + + if ( mode == MAP_MODE_OFF ) + { + ShowPanel( false ); + + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "MapOff" ); + } + else if ( mode == MAP_MODE_INSET ) + { + switch( _cl_minimapzoom.GetInt() ) + { + case 0: + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "MapZoomLevel1" ); + break; + case 1: + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "MapZoomLevel2" ); + break; + case 2: + default: + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "MapZoomLevel3" ); + break; + } + + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + + if ( pPlayer ) + SetFollowEntity( pPlayer->entindex() ); + + ShowPanel( true ); + + if ( m_nMode == MAP_MODE_FULL ) + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "MapScaleToSmall" ); + else + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "SnapToSmall" ); + } + else if ( mode == MAP_MODE_FULL ) + { + SetFollowEntity( 0 ); + + ShowPanel( true ); + + if ( m_nMode == MAP_MODE_INSET ) + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "ZoomToLarge" ); + else + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "SnapToLarge" ); + } + + // finally set mode + m_nMode = mode; + + // save in a cvar for archive + _overview_mode.SetValue( m_nMode ); + + UpdateSizeAndPosition(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapOverview::UpdateSizeAndPosition() +{ + // move back up if the spectator menu is not visible + if ( !g_pSpectatorGUI || ( !g_pSpectatorGUI->IsVisible() && GetMode() == MAP_MODE_INSET ) ) + { + int x,y,w,h; + + GetBounds( x,y,w,h ); + + y = YRES(5); // hax, align to top of the screen + + SetBounds( x,y,w,h ); + } + + BaseClass::UpdateSizeAndPosition(); +} + +ConVar cl_voicetest( "cl_voicetest", "0", FCVAR_CHEAT ); +ConVar cl_overview_chat_time( "cl_overview_chat_time", "2.0", FCVAR_ARCHIVE ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapOverview::PlayerChat( int index ) +{ + m_flPlayerChatTime[index-1] = gpGlobals->curtime + cl_overview_chat_time.GetFloat(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapOverview::DrawMapPlayers() +{ + BaseClass::DrawMapPlayers(); + + C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer(); + + Assert( localPlayer ); + + int iLocalPlayer = localPlayer->entindex() - 1; + + for ( int i = 0 ; i < MAX_PLAYERS ; i++ ) + { + if ( i == iLocalPlayer ) + continue; + + MapPlayer_t *player = &m_Players[i]; + + if ( !CanPlayerBeSeen( player ) ) + continue; + + if ( player->health <= 0 ) // don't draw dead players / spectators + continue; + + DrawVoiceIconForPlayer( i ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapOverview::DrawVoiceIconForPlayer( int playerIndex ) +{ + Assert( playerIndex >= 0 && playerIndex < MAX_PLAYERS ); + + MapPlayer_t *player = &m_Players[playerIndex]; + + // if they just sent a chat msg, or are using voice, or did a hand signal or voice command + // draw a chat icon + + if ( cl_voicetest.GetInt() || GetClientVoiceMgr()->IsPlayerSpeaking( player->index+1 ) ) + { + MapObject_t obj; + memset( &obj, 0, sizeof(MapObject_t) ); + + obj.icon = m_iVoiceIcon; + obj.position = player->position; + obj.size = tf_overview_voice_icon_size.GetFloat(); + obj.status = -1; + + DrawIcon( &obj ); + } + else if ( m_flPlayerChatTime[player->index] > gpGlobals->curtime ) + { + MapObject_t obj; + memset( &obj, 0, sizeof(MapObject_t) ); + + obj.icon = m_iChatIcon; + obj.position = player->position; + obj.size = tf_overview_voice_icon_size.GetFloat(); + obj.status = -1; + + DrawIcon( &obj ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFMapOverview::DrawIcon( MapObject_t *obj ) +{ + for ( int i = 0 ; i < MAX_CONTROL_POINTS ; i++ ) + { + if ( obj->objectID == m_CapturePoints[i] && obj->objectID != 0 ) + { + return DrawCapturePoint( i, obj ); + } + } + + return BaseClass::DrawIcon( obj ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapOverview::DrawQuad( Vector pos, int scale, float angle, int textureID, int alpha ) +{ + Vector offset; + offset.z = 0; + + offset.x = -scale; offset.y = scale; + VectorYawRotate( offset, angle, offset ); + Vector2D pos1 = WorldToMap( pos + offset ); + + offset.x = scale; offset.y = scale; + VectorYawRotate( offset, angle, offset ); + Vector2D pos2 = WorldToMap( pos + offset ); + + offset.x = scale; offset.y = -scale; + VectorYawRotate( offset, angle, offset ); + Vector2D pos3 = WorldToMap( pos + offset ); + + offset.x = -scale; offset.y = -scale; + VectorYawRotate( offset, angle, offset ); + Vector2D pos4 = WorldToMap( pos + offset ); + + Vertex_t points[4] = + { + Vertex_t( MapToPanel ( pos1 ), Vector2D(0,0) ), + Vertex_t( MapToPanel ( pos2 ), Vector2D(1,0) ), + Vertex_t( MapToPanel ( pos3 ), Vector2D(1,1) ), + Vertex_t( MapToPanel ( pos4 ), Vector2D(0,1) ) + }; + + surface()->DrawSetColor( 255, 255, 255, alpha ); + surface()->DrawSetTexture( textureID ); + surface()->DrawTexturedPolygon( 4, points ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFMapOverview::DrawCapturePoint( int iCP, MapObject_t *obj ) +{ + int textureID = obj->icon; + Vector pos = obj->position; + float scale = obj->size; + + Vector2D pospanel = WorldToMap( pos ); + pospanel = MapToPanel( pospanel ); + + if ( !IsInPanel( pospanel ) ) + return false; // player is not within overview panel + + // draw capture swipe + DrawQuad( pos, scale, 0, textureID, 255 ); + + int iCappingTeam = g_pObjectiveResource->GetCappingTeam( iCP ); + + if ( iCappingTeam != TEAM_UNASSIGNED ) + { + int iCapperIcon = g_pObjectiveResource->GetCPCappingIcon( iCP ); + const char *textureName = GetMaterialNameFromIndex( iCapperIcon ); + + float flCapPercent = g_pObjectiveResource->GetCPCapPercentage(iCP); + bool bSwipeLeft = ( iCappingTeam == TF_TEAM_RED ) ? true : false; + + DrawHorizontalSwipe( pos, scale, AddIconTexture( textureName ), flCapPercent, bSwipeLeft ); + } + + // fixup for noone is capping, but someone is in the area + int iNumBlue = g_pObjectiveResource->GetNumPlayersInArea( iCP, TF_TEAM_BLUE ); + int iNumRed = g_pObjectiveResource->GetNumPlayersInArea( iCP, TF_TEAM_RED ); + + int iOwningTeam = g_pObjectiveResource->GetOwningTeam( iCP ); + if ( iCappingTeam == TEAM_UNASSIGNED ) + { + if ( iNumBlue > 0 && iNumRed == 0 && iOwningTeam != TF_TEAM_BLUE ) + { + iCappingTeam = TF_TEAM_BLUE; + } + else if ( iNumRed > 0 && iNumBlue == 0 && iOwningTeam != TF_TEAM_RED ) + { + iCappingTeam = TF_TEAM_RED; + } + } + + if ( iCappingTeam != TEAM_UNASSIGNED ) + { + // Draw the number of cappers below the icon + int numPlayers = g_pObjectiveResource->GetNumPlayersInArea( iCP, iCappingTeam ); + int requiredPlayers = g_pObjectiveResource->GetRequiredCappers( iCP, iCappingTeam ); + + if ( requiredPlayers > 1 ) + { + numPlayers = MIN( numPlayers, requiredPlayers ); + + wchar_t wText[6]; + _snwprintf( wText, sizeof(wText)/sizeof(wchar_t), L"%d", numPlayers ); + + int wide, tall; + surface()->GetTextSize( m_hIconFont, wText, wide, tall ); + + int x = pospanel.x-(wide/2); + int y = pospanel.y; + + // match the offset that MapOverview uses + y += GetPixelOffset( scale ) + 4; + + // draw black shadow text + surface()->DrawSetTextColor( 0, 0, 0, 255 ); + surface()->DrawSetTextPos( x+1, y ); + surface()->DrawPrintText( wText, wcslen(wText) ); + + // draw name in color + surface()->DrawSetTextColor( g_PR->GetTeamColor( iCappingTeam ) ); + surface()->DrawSetTextPos( x, y ); + surface()->DrawPrintText( wText, wcslen(wText) ); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapOverview::DrawHorizontalSwipe( Vector pos, int scale, int textureID, float flCapPercentage, bool bSwipeLeft ) +{ + float flIconSize = scale * 2; + float width = ( flIconSize * flCapPercentage ); + + float uv1 = 0.0f; + float uv2 = 1.0f; + + Vector2D uv11( uv1, uv2 ); + Vector2D uv21( flCapPercentage, uv2 ); + Vector2D uv22( flCapPercentage, uv1 ); + Vector2D uv12( uv1, uv1 ); + + // reversing the direction of the swipe effect + if ( bSwipeLeft ) + { + uv11.x = uv2 - flCapPercentage; + uv21.x = uv2; + uv22.x = uv2; + uv12.x = uv2 - flCapPercentage; + } + + float flXPos = pos.x - scale; + float flYPos = pos.y - scale; + + Vector upperLeft( flXPos, flYPos, 0 ); + Vector upperRight( flXPos + width, flYPos, 0 ); + Vector lowerRight( flXPos + width, flYPos + flIconSize, 0 ); + Vector lowerLeft ( flXPos, flYPos + flIconSize, 0 ); + + /// reversing the direction of the swipe effect + if ( bSwipeLeft ) + { + upperLeft.x = flXPos + flIconSize - width; + upperRight.x = flXPos + flIconSize; + lowerRight.x = flXPos + flIconSize; + lowerLeft.x = flXPos + flIconSize - width; + } + + vgui::Vertex_t vert[4]; + + Vector2D pos0 = WorldToMap( upperLeft ); + vert[0].Init( MapToPanel( pos0 ), uv11 ); + + Vector2D pos3 = WorldToMap( lowerLeft ); + vert[1].Init( MapToPanel( pos3 ), uv12 ); + + Vector2D pos2 = WorldToMap( lowerRight ); + vert[2].Init( MapToPanel( pos2 ), uv22 ); + + Vector2D pos1 = WorldToMap( upperRight ); + vert[3].Init( MapToPanel( pos1 ), uv21 ); + + surface()->DrawSetColor( 255, 255, 255, 255 ); + surface()->DrawSetTexture( textureID ); + surface()->DrawTexturedPolygon( 4, vert ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapOverview::SetMap( const char * levelname ) +{ + BaseClass::SetMap( levelname ); + + if ( m_nMapTextureID != -1 ) + { + // we found a texture for this map + SetDisabled( false ); + SetMode( m_nMode ); + } + else + { + // we failed to load a map image + SetDisabled( true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFMapOverview::ShouldDraw( void ) +{ + if ( IsDisabled() ) + { + return false; + } + + return BaseClass::ShouldDraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapOverview::Paint() +{ + UpdateMapOverlayTexture(); + + UpdateSizeAndPosition(); + + UpdateFollowEntity(); + + UpdateObjects(); + + UpdatePlayers(); + +// UpdatePlayerTrails(); + + DrawMapTexture(); + + DrawMapOverlayTexture(); + +// DrawMapPlayerTrails(); + + DrawObjects(); + + DrawMapPlayers(); + + DrawCamera(); + + Panel::Paint(); +} + +extern ConVar overview_alpha; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapOverview::DrawMapOverlayTexture() +{ + // now draw a box around the outside of this panel + int x0, y0, x1, y1; + int wide, tall; + + GetSize( wide, tall ); + x0 = 0; y0 = 0; x1 = wide - 2; y1 = tall - 2; + + if ( m_nMapTextureOverlayID < 0 ) + { + return; + } + + Vertex_t points[4] = + { + Vertex_t( MapToPanel ( Vector2D(0,0) ), Vector2D(0,0) ), + Vertex_t( MapToPanel ( Vector2D(OVERVIEW_MAP_SIZE-1,0) ), Vector2D(1,0) ), + Vertex_t( MapToPanel ( Vector2D(OVERVIEW_MAP_SIZE-1,OVERVIEW_MAP_SIZE-1) ), Vector2D(1,1) ), + Vertex_t( MapToPanel ( Vector2D(0,OVERVIEW_MAP_SIZE-1) ), Vector2D(0,1) ) + }; + + int alpha = 255.0f * overview_alpha.GetFloat(); clamp( alpha, 1, 255 ); + + surface()->DrawSetColor( 255,255,255, alpha ); + surface()->DrawSetTexture( m_nMapTextureOverlayID ); + surface()->DrawTexturedPolygon( 4, points ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMapOverview::UpdateMapOverlayTexture() +{ + + +/* + char tempfile[MAX_PATH]; + Q_snprintf( tempfile, sizeof( tempfile ), "overviews/%s_%s_%s_%s", levelname, roundname, capname, teamname ); + + // TODO release old texture ? + + m_nMapTextureOverlayID = surface()->CreateNewTextureID(); + + //if we have not uploaded yet, lets go ahead and do so + surface()->DrawSetTextureFile( m_nMapTextureOverlayID, tempfile, true, false ); + + int wide, tall; + + surface()->DrawGetTextureSize( m_nMapTextureOverlayID, wide, tall ); + + if ( wide != tall ) + { + DevMsg( 1, "Error! CTFMapOverview::UpdateMapOverlayTexture: map overlay image must be a square.\n" ); + m_nMapTextureOverlayID = -1; + return; + } +*/ +}
\ No newline at end of file diff --git a/game/client/tf/vgui/tf_overview.h b/game/client/tf/vgui/tf_overview.h new file mode 100644 index 0000000..e7c3a37 --- /dev/null +++ b/game/client/tf/vgui/tf_overview.h @@ -0,0 +1,80 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_OVERVIEW_H +#define TF_OVERVIEW_H + +#include <mapoverview.h> +#include "tf_shareddefs.h" + +class CTFMapOverview : public CMapOverview +{ + DECLARE_CLASS_SIMPLE( CTFMapOverview, CMapOverview ); + + CTFMapOverview( const char *pElementName ); + + int m_CameraIcons[MAX_TEAMS]; + int m_CapturePoints[MAX_CONTROL_POINTS]; + + void ShowLargeMap( void ); + void HideLargeMap( void ); + void ToggleZoom( void ); + + void PlayerChat( int index ); + + void DrawQuad( Vector pos, int scale, float angle, int textureID, int alpha ); + void DrawHorizontalSwipe( Vector pos, int scale, int textureID, float flCapPercentage, bool bSwipeLeft ); + bool DrawCapturePoint( int iCP, MapObject_t *obj ); + + void SetDisabled( bool disabled ){ m_bDisabled = disabled; } + bool IsDisabled( void ){ return m_bDisabled; } + + virtual void Paint(); + virtual bool ShouldDraw( void ); + virtual void VidInit( void ); + virtual void SetMap( const char * map ); + +protected: + virtual void SetMode(int mode); + virtual void InitTeamColorsAndIcons(); + virtual void FireGameEvent( IGameEvent *event ); + virtual void DrawCamera(); + virtual void DrawMapPlayers(); + virtual void Update(); + virtual void UpdateSizeAndPosition(); + virtual void UpdateMapOverlayTexture(); + + virtual void DrawMapOverlayTexture(); + + // rules that define if you can see a player on the overview or not + virtual bool CanPlayerBeSeen( MapPlayer_t *player ); + + void DrawVoiceIconForPlayer( int playerIndex ); + + virtual bool DrawIcon( MapObject_t *obj ); + +protected: + void UpdateCapturePoints(); + +private: + int m_iLastMode; + + int m_iVoiceIcon; + int m_iChatIcon; + + bool m_bDisabled; + + float m_flPlayerChatTime[MAX_PLAYERS]; + + int m_nMapTextureOverlayID; // texture id for current overlay image (shown over the current overview image) + +#define TF_MAP_ZOOM_LEVELS 2 +}; + +extern CTFMapOverview *GetTFOverview( void ); + +#endif // TF_OVERVIEW_H diff --git a/game/client/tf/vgui/tf_particlepanel.cpp b/game/client/tf/vgui/tf_particlepanel.cpp new file mode 100644 index 0000000..8513dd1 --- /dev/null +++ b/game/client/tf/vgui/tf_particlepanel.cpp @@ -0,0 +1,556 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A panel that display particle systems +// +//=============================================================================// + +#include "cbase.h" +#include <KeyValues.h> +#include <vgui/IScheme.h> +#include <vgui/ISurface.h> +#include <vgui_controls/EditablePanel.h> +#include "vgui/IVGui.h" + +#include "tf_particlepanel.h" +#include "matsys_controls/matsyscontrols.h" +#include "VGuiMatSurface/IMatSystemSurface.h" +#include "tier2/renderutils.h" +#include "renderparm.h" + +using namespace vgui; + + +CTFParticlePanel::ParticleEffect_t::ParticleEffect_t() + : m_bLoop( true ) + , m_pParticleSystem( NULL ) + , m_flLastTime( FLT_MAX ) + , m_ParticleSystemName( NULL ) + , m_bStartActivated( true ) + , m_flScale( 1.f ) + , m_flEndTime( FLT_MAX ) + , m_nXPos( 0 ) + , m_nYPos( 0 ) + , m_Angles( 0.f, 0.f, 0.f ) + , m_pParent( NULL ) + , m_bForceStopped( false ) + , m_bAutoDelete( false ) + , m_bStarted( false ) +{} + + +DECLARE_BUILD_FACTORY( CTFParticlePanel ); +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +CTFParticlePanel::CTFParticlePanel( vgui::Panel *pParent, const char *pName ) + : BaseClass( pParent, pName ) +{ + m_Camera.m_flZNear = 3.0f; + m_Camera.m_flZFar = 16384.0f * 1.73205080757f; + m_Camera.m_flFOV = 30.0f; + m_Camera.m_origin = Vector(0,0,0); + m_Camera.m_angles = QAngle(0,0,0); + + m_pLightmapTexture.Init( "//platform/materials/debug/defaultlightmap", "editor" ); + m_DefaultEnvCubemap.Init( "editor/cubemap", "editor", true ); +} + +CTFParticlePanel::~CTFParticlePanel() +{ + m_pLightmapTexture.Shutdown(); + m_DefaultEnvCubemap.Shutdown(); + m_vecParticleEffects.PurgeAndDeleteElements(); +} + + +void CTFParticlePanel::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + KeyValues *pKVParticleEffects = inResourceData->FindKey( "ParticleEffects" ); + if ( pKVParticleEffects ) + { + FOR_EACH_SUBKEY( pKVParticleEffects, pKVEffect ) + { + m_vecParticleEffects[ m_vecParticleEffects.AddToTail() ] = new ParticleEffect_t(); + ParticleEffect_t* pEffect = m_vecParticleEffects.Tail(); + + // get the position + int alignScreenWide = GetWide(), alignScreenTall = GetTall(); // screen dimensions used for pinning in splitscreen + + int x, y; + GetPos(x, y); + const char *xstr = pKVEffect->GetString( "particle_xpos", NULL ); + const char *ystr = pKVEffect->GetString( "particle_ypos", NULL ); + + if (xstr) + { + bool bRightAlign = false; + bool bCenterAlign = false; + // look for alignment flags + if (xstr[0] == 'r' || xstr[0] == 'R') + { + bRightAlign = true; + xstr++; + } + else if (xstr[0] == 'c' || xstr[0] == 'C') + { + bCenterAlign = true; + xstr++; + } + + // get the value + x = atoi(xstr); + // scale the x up to our screen co-ords + if ( IsProportional() ) + { + x = scheme()->GetProportionalScaledValueEx(GetScheme(), x); + } + // now correct the alignment + if ( bRightAlign ) + { + x = alignScreenWide - x; + } + else if ( bCenterAlign ) + { + x = (alignScreenWide / 2) + x; + } + } + + if (ystr) + { + bool bBottomAlign = false; + bool bCenterAlign = false; + // look for alignment flags + if (ystr[0] == 'r' || ystr[0] == 'R') + { + bBottomAlign = true; + ystr++; + } + else if (ystr[0] == 'c' || ystr[0] == 'C') + { + bCenterAlign = true; + ystr++; + } + y = atoi(ystr); + if (IsProportional()) + { + // scale the y up to our screen co-ords + y = scheme()->GetProportionalScaledValueEx(GetScheme(), y); + } + // now correct the alignment + if ( bBottomAlign ) + { + y = alignScreenTall - y; + } + else if ( bCenterAlign ) + { + y = (alignScreenTall / 2) + y; + } + } + + pEffect->m_nXPos = x; + pEffect->m_nYPos = y; + + pEffect->m_flScale = pKVEffect->GetFloat( "particle_scale", 1.f ); + // Scale the scale factor the same way we do the XY position coordinates + if( IsProportional() ) + { + int wide, tall; + surface()->GetScreenSize( wide, tall ); + + int proH, proW; + surface()->GetProportionalBase( proW, proH ); + double scale = (double)tall / (double)proH; + pEffect->m_flScale *= scale; + } + + pEffect->m_pParent = this; + pEffect->m_bLoop = pKVEffect->GetBool( "loop", true ); + pEffect->m_bStartActivated = pKVEffect->GetBool( "start_activated", true ); + pEffect->SetParticleSystem( pKVEffect->GetString( "particleName" ) ); + + // Read angles for the particle system + { + float x1,y1,z1; + const char* pszAngles = pKVEffect->GetString( "angles" ); + if( *pszAngles ) + { + if( pEffect->m_pParticleSystem && sscanf( pszAngles, "%f %f %f", &x1, &y1, &z1 ) == 3 ) + { + pEffect->m_Angles = QAngle( x1, y1, z1 ); + Quaternion q; + AngleQuaternion( pEffect->m_Angles , q ); + pEffect->m_pParticleSystem->SetControlPointOrientation( 0, q ); + } + } + } + + pEffect->SetControlPointValue( 0, Vector(0,0,0) ); + // Read all control point values + const char* pszControlPoint = NULL; + int nControlPointNumber = 0; + do + { + pszControlPoint = pKVEffect->GetString( VarArgs("control_point%d", nControlPointNumber), "" ); + if ( *pszControlPoint ) + { + float x2,y2,z2; + if (sscanf(pszControlPoint, "%f %f %f", &x2, &y2, &z2 ) == 3) + { + pEffect->SetControlPointValue( nControlPointNumber, Vector( x2, y2, z2 ) ); + } + } + + ++nControlPointNumber; + } + while( *pszControlPoint ); + } + } +} + +//----------------------------------------------------------------------------- +// Scheme +//----------------------------------------------------------------------------- +void CTFParticlePanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + SetMouseInputEnabled( false ); + SetKeyBoardInputEnabled( false ); +} + +void CTFParticlePanel::OnCommand( const char *command ) +{ + if ( !Q_strnicmp( command, "start", ARRAYSIZE("start") - 1 ) ) + { + // Check if they specified a particular one to turn on + const char* pszNum = command + ARRAYSIZE( "start" ) - 1; + int nIndex = m_vecParticleEffects.InvalidIndex(); + + if( pszNum && pszNum[0] ) + { + nIndex = atoi(pszNum); + Assert( nIndex >= 0 && nIndex < m_vecParticleEffects.Count() ); + } + + FOR_EACH_VEC( m_vecParticleEffects, i ) + { + if ( nIndex != m_vecParticleEffects.InvalidIndex() && i != nIndex ) + continue; + + ParticleEffect_t* pEffect = m_vecParticleEffects[ i ]; + + if( !pEffect->m_pParticleSystem ) + { + pEffect->SetParticleSystem( pEffect->m_ParticleSystemName ); + } + + if( !pEffect->m_pParticleSystem ) + continue; + + pEffect->StartupParticleCollection(); + pEffect->m_pParticleSystem->StartEmission(); + pEffect->m_bForceStopped = false; + } + } + else if ( !Q_strnicmp( command, "stop", ARRAYSIZE("stop") - 1 ) ) + { + // Check if they specified a specific one to turn off + const char* pszNum = command + ARRAYSIZE( "start" ) - 1; + if( pszNum && pszNum[0] ) + { + int iIndex = atoi(pszNum); + Assert( iIndex >= 0 && iIndex < m_vecParticleEffects.Count() ); + + ParticleEffect_t* pEffect = m_vecParticleEffects[iIndex]; + if( pEffect->m_pParticleSystem ) + { + pEffect->m_pParticleSystem->StopEmission(); + pEffect->m_bForceStopped = true; + } + } + else + { + // Turn them ALL off + FOR_EACH_VEC( m_vecParticleEffects, i ) + { + ParticleEffect_t* pEffect = m_vecParticleEffects[ i ]; + if( pEffect->m_pParticleSystem ) + { + pEffect->m_pParticleSystem->StopEmission(); + pEffect->m_bForceStopped = true; + } + } + } + } +} + +void CTFParticlePanel::FireParticleEffect( const char *pszName, int xPos, int yPos, float flScale, bool bLoop, float flEndTime ) +{ + m_vecParticleEffects[ m_vecParticleEffects.AddToTail() ] = new ParticleEffect_t(); + ParticleEffect_t* pEffect = m_vecParticleEffects.Tail(); + + int iParentAbsX, iParentAbsY; + vgui::ipanel()->GetAbsPos( GetParent()->GetVPanel(), iParentAbsX, iParentAbsY ); + + pEffect->m_pParent = this; + pEffect->m_nXPos = xPos - iParentAbsX; + pEffect->m_nYPos = yPos - iParentAbsY; + pEffect->m_flScale = flScale; + pEffect->m_bLoop = bLoop; + pEffect->m_bAutoDelete = true; // This will get automatically deleted once it stops + pEffect->m_bStartActivated = true; + pEffect->m_flEndTime = gpGlobals->curtime + flEndTime; + + if( IsProportional() ) + { + int wide, tall; + surface()->GetScreenSize( wide, tall ); + + int proH, proW; + surface()->GetProportionalBase( proW, proH ); + double scale = (double)tall / (double)proH; + pEffect->m_flScale *= scale; + } + + for ( int i = 0; i < MAX_PARTICLE_CONTROL_POINTS; ++i ) + { + pEffect->SetControlPointValue( i, Vector( 0, 0, 10.0f * i ) ); + } + pEffect->SetParticleSystem( pszName ); +} + + +static bool IsValidHierarchy( CParticleCollection *pCollection ) +{ + if ( !pCollection->IsValid() ) + return false; + + for( CParticleCollection *pChild = pCollection->m_Children.m_pHead; pChild; pChild = pChild->m_pNext ) + { + if ( !IsValidHierarchy( pChild ) ) + return false; + } + return true; +} + + +//----------------------------------------------------------------------------- +// Simulate the particle system +//----------------------------------------------------------------------------- +void CTFParticlePanel::OnTick() +{ + BaseClass::OnTick(); + + float flTime = engine->Time(); + + bool bAnyActive = false; + // Update all particles + FOR_EACH_VEC_BACK( m_vecParticleEffects, i ) + { + bAnyActive |= m_vecParticleEffects[i]->Update( flTime ); + // If this effect is done and should auto-delete, then now is when we delete + if( m_vecParticleEffects[i]->m_pParticleSystem == NULL && m_vecParticleEffects[i]->m_bAutoDelete ) + { + delete m_vecParticleEffects[i]; + m_vecParticleEffects.FastRemove( i ); + } + } + + if ( !bAnyActive ) + { + vgui::ivgui()->RemoveTickSignal( GetVPanel() ); + } +} + + +void CTFParticlePanel::Paint() +{ + // This needs calling to reset various counters. + g_pParticleSystemMgr->SetLastSimulationTime( gpGlobals->curtime ); + + // No particles? Do nothing. + if( m_vecParticleEffects.Count() == 0 ) + return; + + int screenW, screenH; + vgui::surface()->GetScreenSize( screenW, screenH ); + + vgui::MatSystemSurface()->Begin3DPaint( 0, 0, screenW, screenH, false ); + + VMatrix view, projection; + ComputeViewMatrix( &view, m_Camera ); + ComputeProjectionMatrix( &projection, m_Camera, screenW, screenH ); + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + + pRenderContext->CullMode( MATERIAL_CULLMODE_CCW ); + pRenderContext->SetIntRenderingParameter( INT_RENDERPARM_WRITE_DEPTH_TO_DESTALPHA, false ); + + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->LoadIdentity( ); + + pRenderContext->MatrixMode( MATERIAL_VIEW ); + pRenderContext->LoadMatrix( view ); + + pRenderContext->MatrixMode( MATERIAL_PROJECTION ); + pRenderContext->LoadMatrix( projection ); + + int iXOffset, iYOffset; + vgui::ipanel()->GetAbsPos( GetVPanel(), iXOffset, iYOffset ); + if ( iXOffset > 0 ) + iXOffset = 0; + if ( iYOffset > 0 ) + iYOffset = 0; + + float flXScale = 1.f; + if ( GetWide() > screenW ) + flXScale = (float)screenW / GetWide(); + float flYScale = 1.f; + if ( GetTall() > screenH ) + flYScale = (float)screenH / GetTall(); + + FOR_EACH_VEC( m_vecParticleEffects, i ) + { + m_vecParticleEffects[i]->Paint( pRenderContext, iXOffset, iYOffset, flXScale, flYScale, screenW, screenH ); + } + + pRenderContext->CullMode( MATERIAL_CULLMODE_CW ); + + vgui::MatSystemSurface()->End3DPaint(); +} + +bool CTFParticlePanel::ParticleEffect_t::Update( float flTime ) +{ + if ( !m_pParticleSystem || !m_bStarted ) + return false; + + if ( m_flLastTime == FLT_MAX ) + { + m_flLastTime = flTime; + } + + float flDt = flTime - m_flLastTime; + m_flLastTime = flTime; + + Quaternion q; + AngleQuaternion( m_Angles, q ); + + for ( int i = 0; i < MAX_PARTICLE_CONTROL_POINTS; ++i ) + { + if ( !m_pParticleSystem->ReadsControlPoint( i ) ) + continue; + + m_pParticleSystem->SetControlPoint( i, m_pControlPointValue[i] ); + m_pParticleSystem->SetControlPointOrientation( i, q ); + m_pParticleSystem->SetControlPointParent( i, i ); + } + + // Restart the particle system if it's finished + bool bIsInvalid = !IsValidHierarchy( m_pParticleSystem ); + + if ( !bIsInvalid ) + { + m_pParticleSystem->Simulate( flDt, false ); + } + + // Past our end time? + bool bEnd = gpGlobals->curtime >= m_flEndTime; + + if ( m_pParticleSystem->IsFinished() || bIsInvalid || bEnd ) + { + delete m_pParticleSystem; + m_pParticleSystem = NULL; + + // Loop if we're supposed to + if ( m_bLoop && m_ParticleSystemName.Length() && !m_bForceStopped ) + { + m_pParticleSystem = g_pParticleSystemMgr->CreateParticleCollection( m_ParticleSystemName ); + } + + if ( bIsInvalid && m_pParent ) + { + m_pParent->PostActionSignal( new KeyValues( "ParticleSystemReconstructed" ) ); + } + m_flLastTime = FLT_MAX; + } + + return m_pParticleSystem != NULL; +} + + +//----------------------------------------------------------------------------- +// Startup, shutdown particle collection +//----------------------------------------------------------------------------- +void CTFParticlePanel::ParticleEffect_t::StartupParticleCollection() +{ + if ( m_pParticleSystem && m_pParent ) + { + vgui::ivgui()->AddTickSignal( m_pParent->GetVPanel(), 0 ); + } + m_flLastTime = FLT_MAX; + m_bStarted = true; +} + +void CTFParticlePanel::ParticleEffect_t::ShutdownParticleCollection() +{ + if ( m_pParticleSystem && m_pParent ) + { + delete m_pParticleSystem; + m_pParticleSystem = NULL; + } + m_bStarted = false; +} + +//----------------------------------------------------------------------------- +// Set the particle system to draw +//----------------------------------------------------------------------------- +void CTFParticlePanel::ParticleEffect_t::SetParticleSystem( const char* pszParticleSystemName ) +{ + ShutdownParticleCollection(); + + if( !g_pParticleSystemMgr->IsParticleSystemDefined( pszParticleSystemName ) ) + { + AssertMsg1( false, "%s is not a valid particle system name", pszParticleSystemName ); + return; + } + m_pParticleSystem = g_pParticleSystemMgr->CreateParticleCollection( pszParticleSystemName ); + m_ParticleSystemName = pszParticleSystemName; + + m_pParent->PostActionSignal( new KeyValues( "ParticleSystemReconstructed" ) ); + + if( m_bStartActivated ) + { + StartupParticleCollection(); + } +} + + +void CTFParticlePanel::ParticleEffect_t::Paint( CMatRenderContextPtr& pRenderContext, int iXOffset, int iYOffset, float flXScale, float flYScale, int screenW, int screenH ) +{ + if ( !m_pParticleSystem || !m_bStarted ) + return; + + pRenderContext->MatrixMode( MATERIAL_PROJECTION ); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity(); + + pRenderContext->Ortho( 0, 0, screenW, screenH, -9999, 9999 ); + + pRenderContext->Translate( flXScale * ( m_nXPos + iXOffset ), screenH - flYScale * ( m_nYPos + iYOffset ), 0.f ); + pRenderContext->Scale( m_flScale, m_flScale, m_flScale ); + + // Render Particles + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity( ); + + m_pParticleSystem->Render( pRenderContext ); + + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->PopMatrix(); + + pRenderContext->MatrixMode( MATERIAL_PROJECTION ); + pRenderContext->PopMatrix(); +}
\ No newline at end of file diff --git a/game/client/tf/vgui/tf_particlepanel.h b/game/client/tf/vgui/tf_particlepanel.h new file mode 100644 index 0000000..9eccae4 --- /dev/null +++ b/game/client/tf/vgui/tf_particlepanel.h @@ -0,0 +1,84 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A panel that display particle systems +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_PARTICLEPANEL_H +#define TF_PARTICLEPANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tier2/camerautils.h" + +class CTFParticlePanel : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CTFParticlePanel, vgui::EditablePanel ); +public: + // constructor, destructor + CTFParticlePanel( vgui::Panel *pParent, const char *pName ); + virtual ~CTFParticlePanel(); + + virtual void OnTick(); + virtual void ApplySettings( KeyValues *inResourceData ); + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void OnCommand( const char *command ) OVERRIDE; + + void FireParticleEffect( const char *pszName, int xPos, int yPos, float flScale, bool bLoop, float flEndTime = FLT_MAX ); +private: + + // paint it! + virtual void Paint() OVERRIDE; + +private: + + // Class to contain all of the per-instance particle system data + struct ParticleEffect_t + { + ParticleEffect_t(); + + // Shutdown, startup particle collection + void StartupParticleCollection(); + void ShutdownParticleCollection(); + + // Accessor for control point values + const Vector& GetControlPointValue( int nControlPoint ) const { return m_pControlPointValue[ nControlPoint ]; }; + void SetControlPointValue( int nControlPoint, const Vector &value ) { m_pControlPointValue[ nControlPoint ] = value; } + // Set the particle system to draw + void SetParticleSystem( const char* pszParticleSystemName ); + + bool Update( float flTime ); + void Paint( CMatRenderContextPtr& pRenderContext, int iXOffset, int iYOffset, float flXScale, float flYScale, int screenW, int screenH ); + + Vector m_pControlPointValue[MAX_PARTICLE_CONTROL_POINTS]; + CParticleCollection *m_pParticleSystem; + CUtlString m_ParticleSystemName; + float m_flLastTime; + float m_flScale; + float m_flEndTime; + int m_nXPos; + int m_nYPos; + QAngle m_Angles; + bool m_bStartActivated; // Start the effect immediately? + bool m_bLoop; // Loop the effect? + bool m_bForceStopped; + bool m_bAutoDelete; + bool m_bStarted; + + Panel* m_pParent; + }; + + CUtlVector< ParticleEffect_t* > m_vecParticleEffects; + + // A texture to use for a lightmap + CTextureReference m_pLightmapTexture; + + // The default env_cubemap + CTextureReference m_DefaultEnvCubemap; + + Camera_t m_Camera; +}; + +#endif // TF_PARTICLEPANEL_H diff --git a/game/client/tf/vgui/tf_ping_panel.cpp b/game/client/tf/vgui/tf_ping_panel.cpp new file mode 100644 index 0000000..359deaa --- /dev/null +++ b/game/client/tf/vgui/tf_ping_panel.cpp @@ -0,0 +1,286 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "tf_ping_panel.h" + +#include "vgui_controls/ProgressBar.h" +#include "vgui_controls/AnimationController.h" + +#include "tf_gc_client.h" + +#include "clientmode_tf.h" + +#include "tf_matchmaking_shared.h" + +static void OnConVarChangeCustomPingTolerance( IConVar *pConVar, const char *pOldString, float flOldValue ) +{ + if ( GTFGCClientSystem() ) + { + // Otherwise the client system will do this when it starts + GTFGCClientSystem()->UpdateCustomPingTolerance(); + } +} + +ConVar tf_custom_ping_enabled( "tf_custom_ping_enabled", "0", FCVAR_ARCHIVE, "", + false, 0.f, false, 0.f, OnConVarChangeCustomPingTolerance ); +ConVar tf_custom_ping( "tf_custom_ping", "100", FCVAR_ARCHIVE, "", + true, (float)CUSTOM_PING_TOLERANCE_MIN, true, (float)CUSTOM_PING_TOLERANCE_MAX, + OnConVarChangeCustomPingTolerance ); + +#ifdef STAGING_ONLY +ConVar tf_custom_ping_add_random_datacenters( "tf_custom_ping_add_random_datacenters", "0" ); +#endif // STAGING_ONLY + +CTFPingPanel::CTFPingPanel( Panel* pPanel, const char *pszName, EMatchGroup eMatchGroup ) + : EditablePanel( pPanel, pszName ), + m_eMatchGroup( eMatchGroup ) +{ + SetProportional( true ); + + m_pMainContainer = new EditablePanel( this, "MainContainer" ); + m_pCheckButton = new CheckButton( m_pMainContainer, "CheckButton", "" ); + m_pCurrentPingLabel = new Label( m_pMainContainer, "CurrentPingLabel", "" ); + m_pPingSlider = new CCvarSlider( m_pMainContainer, "PingSlider" ); + + ListenForGameEvent( "ping_updated" ); +} + + +CTFPingPanel::~CTFPingPanel() +{ + CleanupPingPanels(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPingPanel::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "resource/ui/MatchMakingPingPanel.res" ); + + CleanupPingPanels(); + + m_pCheckButton->AddActionSignalTarget( this ); + m_pPingSlider->AddActionSignalTarget( this ); + + CScrollableList *pDataCenterList = FindControl< CScrollableList >( "DataCenterList", true ); + + static const wchar_t *s_pwszPingFormat = L"%ls (%d ms)"; + + if ( pDataCenterList && GTFGCClientSystem()->BHavePingData() ) + { + auto pingData = GTFGCClientSystem()->GetPingData(); + const auto& dictDataCenterPopulations = GTFGCClientSystem()->GetDataCenterPopulationRatioDict( m_eMatchGroup ); + + bool bTesting = false; +#ifdef STAGING_ONLY + if ( tf_custom_ping_add_random_datacenters.GetBool() ) + { + bTesting = true; + const char* pszDataCenterNames[] = { "eat", "lax","iad","atl","gru","scl","lim","lux","vie","sto", + "mad","sgp","hkg","tyo","syd","dxb","bom","maa","ord","waw","jhb" }; + + CUniformRandomStream randomstream; + randomstream.SetSeed( tf_custom_ping_add_random_datacenters.GetInt() ); + for( int i=0; i < ARRAYSIZE( pszDataCenterNames ); ++i ) + { + auto pNewPingData = pingData.add_pingdata(); + pNewPingData->set_name( pszDataCenterNames[ i ] ); + pNewPingData->set_ping( randomstream.RandomInt( 0, 250 ) ); + pNewPingData->set_ping_status( CMsgGCDataCenterPing_Update_Status_Normal ); + } + } +#endif + + // for each ping data, check for intersection with data center population from MMStats + for ( int iPing=0; iPing<pingData.pingdata_size(); ++iPing ) + { + auto pingEntry = pingData.pingdata( iPing ); + if ( pingEntry.ping_status() != CMsgGCDataCenterPing_Update_Status_Normal ) + continue; + + const char *pszPingFromDataCenterName = pingEntry.name().c_str(); + auto dictIndex = dictDataCenterPopulations.Find( pszPingFromDataCenterName ); + // found intersection. add a population health panel + if ( dictIndex != dictDataCenterPopulations.InvalidIndex() || bTesting) + { + // Load control settings + EditablePanel* pDataCenterPopulationPanel = new EditablePanel( pDataCenterList, "DataCenterPopulationPanel" ); + pDataCenterPopulationPanel->LoadControlSettings( "resource/ui/MatchMakingDataCenterPopulationPanel.res" ); + pDataCenterPopulationPanel->SetAutoDelete( false ); + pDataCenterList->ResetScrollAmount(); + pDataCenterList->InvalidateLayout(); + + // Update label + wchar_t wszDataCenterName[ 128 ]; + wchar_t* pwszLocalizedDataCenterName = g_pVGuiLocalize->Find( CFmtStr( "#TF_DataCenter_%s", pszPingFromDataCenterName ) ); + if ( pwszLocalizedDataCenterName ) + { + V_wcsncpy( wszDataCenterName, pwszLocalizedDataCenterName, sizeof( wszDataCenterName ) ); + } + else + { + // Fallback is no token. If you hit this, go add a string for this data center in tf_english! + Assert( false ); + g_pVGuiLocalize->ConvertANSIToUnicode( pszPingFromDataCenterName, wszDataCenterName, sizeof( wszDataCenterName ) ); + } + + wchar_t wszLabelText[ 128 ]; + V_snwprintf( wszLabelText, sizeof( wszLabelText ), s_pwszPingFormat, wszDataCenterName, pingEntry.ping() ); + + pDataCenterPopulationPanel->SetDialogVariable( "datacenter_name", wszLabelText ); + + PingPanelInfo panelInfo; + panelInfo.m_pPanel = pDataCenterPopulationPanel; + panelInfo.m_flPopulationRatio = bTesting ? RandomFloat( 0.f, 1.f ) : dictDataCenterPopulations[dictIndex]; + panelInfo.m_nPing = pingEntry.ping(); + m_vecDataCenterPingPanels.AddToTail( panelInfo ); + } + } + } + + struct PingPanelInfoSorter + { + static int SortPingPanelInfo( const PingPanelInfo* a, const PingPanelInfo* b ) + { + return a->m_nPing - b->m_nPing; + } + }; + m_vecDataCenterPingPanels.Sort( &PingPanelInfoSorter::SortPingPanelInfo ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPingPanel::PerformLayout() +{ + BaseClass::PerformLayout(); + + m_pCheckButton->SetSelected( tf_custom_ping_enabled.GetBool() ); + + FOR_EACH_VEC( m_vecDataCenterPingPanels, i ) + { + const PingPanelInfo& info = m_vecDataCenterPingPanels[i]; + int iTall = info.m_pPanel->GetTall(); + int iYGap = i > 0 ? m_iDataCenterYSpace : 0; + int iXPos = info.m_pPanel->GetXPos(); + info.m_pPanel->SetPos( iXPos, m_iDataCenterY + iYGap + i * iTall ); + + // Update bars with latest health data + ProgressBar* pProgress = info.m_pPanel->FindControl< ProgressBar >( "HealthProgressBar", true ); + if ( pProgress ) + { + auto healthData = GTFGCClientSystem()->GetHealthBracketForRatio( info.m_flPopulationRatio ); + + pProgress->MakeReadyForUse(); + pProgress->SetProgress( healthData.m_flRatio ); + pProgress->SetFgColor( healthData.m_colorBar ); + } + } + + UpdateCurrentPing(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPingPanel::OnCommand( const char *command ) +{ + if ( FStrEq( command, "close" ) ) + { + MarkForDeletion(); + return; + } + + BaseClass::OnCommand( command ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPingPanel::FireGameEvent( IGameEvent *event ) +{ + const char *pszEventName = event->GetName(); + if ( FStrEq( pszEventName, "ping_updated" ) ) + { + InvalidateLayout( true, true ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPingPanel::CleanupPingPanels() +{ + FOR_EACH_VEC( m_vecDataCenterPingPanels, i ) + { + m_vecDataCenterPingPanels[i].m_pPanel->MarkForDeletion(); + } + + m_vecDataCenterPingPanels.Purge(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPingPanel::UpdateCurrentPing() +{ + bool bUsePingLimit = m_pCheckButton->IsSelected(); + int nLowestDataCenterPing = m_vecDataCenterPingPanels.Count() ? m_vecDataCenterPingPanels[0].m_nPing : 0; + int nCurrentPingLimit = MAX( (int)m_pPingSlider->GetSliderValue(), nLowestDataCenterPing ); + m_pCurrentPingLabel->SetText( bUsePingLimit ? CFmtStr( "Ping Limit: %d", nCurrentPingLimit ) : "Ping Limit: AUTO" ); + + FOR_EACH_VEC( m_vecDataCenterPingPanels, i ) + { + const PingPanelInfo& info = m_vecDataCenterPingPanels[i]; + + int bHighLight = !bUsePingLimit || info.m_nPing <= nCurrentPingLimit; + if ( bHighLight ) + { + g_pClientMode->GetViewportAnimationController()->StopAnimationSequence( info.m_pPanel, "HealthProgressBar_NotSelected" ); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( info.m_pPanel, "HealthProgressBar_Selected" ); + } + else + { + g_pClientMode->GetViewportAnimationController()->StopAnimationSequence( info.m_pPanel, "HealthProgressBar_Selected" ); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( info.m_pPanel, "HealthProgressBar_NotSelected" ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPingPanel::OnCheckButtonChecked( vgui::Panel *panel ) +{ + if ( m_pCheckButton == panel ) + { + tf_custom_ping_enabled.SetValue( m_pCheckButton->IsSelected() ); + } + + m_pPingSlider->SetVisible( tf_custom_ping_enabled.GetBool() ); + + UpdateCurrentPing(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPingPanel::OnSliderMoved() +{ + UpdateCurrentPing(); +} diff --git a/game/client/tf/vgui/tf_ping_panel.h b/game/client/tf/vgui/tf_ping_panel.h new file mode 100644 index 0000000..e9e6102 --- /dev/null +++ b/game/client/tf/vgui/tf_ping_panel.h @@ -0,0 +1,58 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_PING_PANEL_H +#define TF_PING_PANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include <vgui_controls/EditablePanel.h> +#include <../common/GameUI/cvarslider.h> + +using namespace vgui; + +class CTFPingPanel : public EditablePanel, public CGameEventListener +{ + DECLARE_CLASS_SIMPLE( CTFPingPanel, EditablePanel ) +public: + CTFPingPanel( Panel* pPanel, const char *pszName, EMatchGroup eMatchGroup ); + ~CTFPingPanel(); + + virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE; + virtual void PerformLayout() OVERRIDE; + virtual void OnCommand( const char *command ) OVERRIDE; + + virtual void FireGameEvent( IGameEvent *event ) OVERRIDE; + +private: + void CleanupPingPanels(); + void UpdateCurrentPing(); + + MESSAGE_FUNC_PTR( OnCheckButtonChecked, "CheckButtonChecked", panel ); + MESSAGE_FUNC( OnSliderMoved, "SliderMoved" ); + + CPanelAnimationVarAliasType( int, m_iDataCenterY, "datacenter_y", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iDataCenterYSpace, "datacenter_y_space", "0", "proportional_int" ); + + EditablePanel *m_pMainContainer; + CheckButton *m_pCheckButton; + Label *m_pCurrentPingLabel; + CCvarSlider *m_pPingSlider; + + struct PingPanelInfo + { + EditablePanel *m_pPanel; + float m_flPopulationRatio; + int m_nPing; + }; + CUtlVector< PingPanelInfo > m_vecDataCenterPingPanels; + + EMatchGroup m_eMatchGroup; +}; + +#endif // TF_PING_PANEL_H diff --git a/game/client/tf/vgui/tf_playermodelpanel.cpp b/game/client/tf/vgui/tf_playermodelpanel.cpp new file mode 100644 index 0000000..cb49453 --- /dev/null +++ b/game/client/tf/vgui/tf_playermodelpanel.cpp @@ -0,0 +1,2893 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tf_shareddefs.h" +#include "tf_playermodelpanel.h" +#include "tf_classdata.h" +#include "tf_item_inventory.h" +#include "vgui/IVGui.h" +#include "game_item_schema.h" +#include "econ_item_system.h" +#include "animation.h" +#include "choreoscene.h" +#include "choreoevent.h" +#include "choreoactor.h" +#include "choreochannel.h" +#include "scenefilecache/ISceneFileCache.h" +#include "c_sceneentity.h" +#include "c_baseflex.h" +#include "sentence.h" +#include "engine/IEngineSound.h" +#include "c_tf_player.h" +#include "tier2/renderutils.h" +#include "bone_setup.h" +#include "halloween/tf_weapon_spellbook.h" +#include "matsys_controls/matsyscontrols.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +DECLARE_BUILD_FACTORY( CTFPlayerModelPanel ); + +char g_szSceneTmpName[256]; + +static bool IsTauntItem( GameItemDefinition_t *pItemDef, const int iTeam, const int iClass, const char **ppSequence = NULL, const char **ppRequiredItem = NULL, const char **ppScene = NULL ) +{ + CTFTauntInfo *pTauntData = pItemDef->GetTauntData(); + if ( pTauntData ) + { + if ( ppScene ) + { + int iTauntIndex = RandomInt( 0, pTauntData->GetIntroSceneCount( iClass ) - 1 ); + *ppScene = pTauntData->GetIntroScene( iClass, iTauntIndex ); + } + + if ( ppRequiredItem ) + { + *ppRequiredItem = pTauntData->GetProp( iClass ); + } + + return true; + } + + for ( int i=0; i<pItemDef->GetNumAnimations( iTeam ); ++i ) + { + animation_on_wearable_t* pAnim = pItemDef->GetAnimationData( iTeam, i ); + if ( pAnim && pAnim->pszActivity && !Q_stricmp( pAnim->pszActivity, "taunt_concept" ) ) + { + // If we have a scene, use it first + const char *pszScene = pAnim->pszScene; + if ( pszScene && (iClass >= TF_FIRST_NORMAL_CLASS && iClass < TF_LAST_NORMAL_CLASS) ) + { + if ( ppScene ) + { + Q_snprintf( g_szSceneTmpName, sizeof(g_szSceneTmpName), "scenes/player/%s/low/%s", g_aPlayerClassNames_NonLocalized[iClass], pszScene ); + *ppScene = g_szSceneTmpName; + } + } + + const char *pszSequence = pAnim->pszSequence; + if ( pszSequence ) + { + if ( ppSequence ) + { + *ppSequence = pszSequence; + } + if ( ppRequiredItem ) + { + *ppRequiredItem = pAnim->pszRequiredItem; + } + } + + return true; + } + } + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFPlayerModelPanel::CTFPlayerModelPanel( vgui::Panel *pParent, const char *pName ) : BaseClass( pParent, pName ), + m_LocalToGlobal( 0, 0, FlexSettingLessFunc ) +{ + m_iCurrentClassIndex = TF_CLASS_UNDEFINED; + m_iCurrentSlotIndex = -1; + + m_nBody = 0; + m_pHeldItem = NULL; + m_iTeam = TF_TEAM_RED; + m_bZoomedToHead = false; + m_pszVCD = NULL; + m_pszWeaponEntityRequired = NULL; + m_bLoopVCD = true; + m_bVCDFileNameOnly = true; + + InitPhonemeMappings(); + + m_pScene = NULL; + ClearScene(); + memset( m_flexWeight, 0, sizeof( m_flexWeight ) ); + + SetIgnoreDoubleClick( true ); + + for ( int i = 0; i < ARRAYSIZE( m_aParticleSystems ); i++ ) + { + m_aParticleSystems[i] = NULL; + } + + m_bPlaySparks = false; + m_pszEyeGlowParticleName[0] = '\0'; + m_bDrawActionSlotEffects = false; + m_bDrawTauntParticles = false; + m_bIsRobot = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFPlayerModelPanel::~CTFPlayerModelPanel( void ) +{ + m_vecItemsLoaded.PurgeAndDeleteElements(); + m_ItemsToCarry.PurgeAndDeleteElements(); + + for ( int i = 0; i < ARRAYSIZE( m_aParticleSystems ); i++ ) + { + SafeDeleteParticleData( &m_aParticleSystems[i] ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + m_angPlayerOrg = m_angPlayer; + + static ConVarRef cl_hud_minmode( "cl_hud_minmode", true ); + if ( cl_hud_minmode.IsValid() && cl_hud_minmode.GetBool() ) + { + inResourceData->ProcessResolutionKeys( "_minmode" ); + } + + // custom class data + m_customClassData.Purge(); + KeyValues *pCustomData = inResourceData->FindKey( "customclassdata" ); + if ( pCustomData ) + { + for ( KeyValues *pData = pCustomData->GetFirstSubKey(); pData != NULL; pData = pData->GetNextKey() ) + { + CustomClassData_t data; + data.m_flFOV = pData->GetFloat( "fov" ); + data.m_vPosition.Init( pData->GetFloat( "origin_x" ), pData->GetFloat( "origin_y" ), pData->GetFloat( "origin_z" ) ); + data.m_vAngles.Init( pData->GetFloat( "angles_x" ), pData->GetFloat( "angles_y" ), pData->GetFloat( "angles_z" ) ); + m_customClassData.AddToTail( data ); + } + + Assert( m_customClassData.Count() == TF_LAST_NORMAL_CLASS ); + } + + // always allow particle for this panel + m_bUseParticle = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::SetToPlayerClass( int iClass, bool bIsRobot, bool bForceRefresh /*= false*/ ) +{ + if ( m_bIsRobot != bIsRobot ) + { + bForceRefresh = true; + } + m_bIsRobot = bIsRobot; + + if ( m_iCurrentClassIndex == iClass && !bForceRefresh ) + return; + + if ( m_bZoomedToHead ) + { + ToggleZoom(); + } + + m_iCurrentClassIndex = iClass; + ClearScene(); + + if ( IsValidTFPlayerClass( m_iCurrentClassIndex ) ) + { + if ( bIsRobot ) + { + SetMDL( g_szPlayerRobotModels[ m_iCurrentClassIndex ] ); + } + else + { + TFPlayerClassData_t *pData = GetPlayerClassData( m_iCurrentClassIndex ); + SetMDL( pData->GetModelName() ); + } + + HoldFirstValidItem(); + + // set custom class data + if ( m_customClassData.IsValidIndex( m_iCurrentClassIndex ) ) + { + SetCameraFOV( m_customClassData[m_iCurrentClassIndex].m_flFOV ); + m_vecPlayerPos = m_customClassData[m_iCurrentClassIndex].m_vPosition; + m_angPlayer = m_customClassData[m_iCurrentClassIndex].m_vAngles; + } + else + { + m_angPlayer = m_angPlayerOrg; + } + } + else + { + SetMDL( MDLHANDLE_INVALID ); + RemoveAdditionalModels(); + } + + InitPhonemeMappings(); + + SetTeam( TF_TEAM_RED ); + + m_nBody = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::HoldFirstValidItem( void ) +{ + RemoveAdditionalModels(); + + if ( m_iCurrentClassIndex == TF_CLASS_UNDEFINED ) + return; + + int iDesiredSlot = -1; + + FOR_EACH_VEC( m_ItemsToCarry, i ) + { + CEconItemView *pItem = m_ItemsToCarry[i]; + bool bIsTauntItem = IsTauntItem( pItem->GetStaticData(), GetTeam(), m_iCurrentClassIndex ); + if ( !bIsTauntItem ) + { + if ( pItem->GetStaticData()->IsAWearable() ) + continue; + if ( pItem->GetAnimationSlot() == -2 ) + continue; + } + + // Found a weapon. Wield it. + iDesiredSlot = pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex ); + break; + } + + if ( iDesiredSlot != -1 ) + { + UpdateHeldItem( iDesiredSlot ); + return; + } + + // If we didn't find a weapon to wield, we wield the class's base primary weapon + CEconItemView *pItem = TFInventoryManager()->GetBaseItemForClass( m_iCurrentClassIndex, LOADOUT_POSITION_PRIMARY ); + if ( !pItem || !pItem->IsValid() ) + { + // Some classes only have secondary weapons. Fall back to that. + pItem = TFInventoryManager()->GetBaseItemForClass( m_iCurrentClassIndex, LOADOUT_POSITION_SECONDARY ); + } + + if ( pItem && pItem->IsValid() ) + { + SwitchHeldItemTo( pItem ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFPlayerModelPanel::HoldItemInSlot( int iSlot ) +{ + if ( m_iCurrentClassIndex == TF_CLASS_UNDEFINED ) + return false; + + return UpdateHeldItem( iSlot ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFPlayerModelPanel::HoldItem( int iItemNumber ) +{ + if ( m_iCurrentClassIndex == TF_CLASS_UNDEFINED ) + return false; + + if ( iItemNumber >= m_ItemsToCarry.Count() ) + return false; + + CEconItemView *pItem = m_ItemsToCarry[iItemNumber]; + + bool bIsTauntitem = IsTauntItem( pItem->GetStaticData(), GetTeam(), m_iCurrentClassIndex ); + + // Ignore requests to equip wearables, because they're always equipped + // Also ignore requests to equip non-wearables that are never actively equipped + if ( bIsTauntitem || ( !pItem->GetStaticData()->IsAWearable() && pItem->GetAnimationSlot() != -2 ) ) + { + SwitchHeldItemTo( pItem ); + return true; + } + + // If we were trying to switch to a new item, and it's not valid, stick to our current + if ( pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex ) != m_iCurrentSlotIndex ) + { + UpdateHeldItem( m_iCurrentSlotIndex ); + return false; + } + + // We were trying to stay on the current weapon, and it's not valid. Find anything. + HoldFirstValidItem(); + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFPlayerModelPanel::UpdateHeldItem( int iDesiredSlot ) +{ + m_pHeldItem = NULL; + + CEconItemView *pItem = GetItemInSlot( iDesiredSlot ); + if ( pItem ) + { + bool bIsTauntItem = IsTauntItem( pItem->GetStaticData(), GetTeam(), m_iCurrentClassIndex ); + // Ignore requests to equip wearables, because they're always equipped + // Also ignore requests to equip non-wearables that are never actively equipped + if ( bIsTauntItem || ( !pItem->GetStaticData()->IsAWearable() && pItem->GetAnimationSlot() != -2 ) ) + { + SwitchHeldItemTo( pItem ); + m_pHeldItem = pItem; + return true; + } + } + + // If we were trying to switch to a new item, and it's not valid, stick to our current + if ( iDesiredSlot != m_iCurrentSlotIndex ) + { + UpdateHeldItem( m_iCurrentSlotIndex ); + return false; + } + + // We were trying to stay on the current weapon, and it's not valid. Find anything. + HoldFirstValidItem(); + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::ClearScene( void ) +{ + if ( m_pScene ) + { + delete m_pScene; + } + + m_pScene = NULL; + m_flSceneTime = 0; + m_flSceneEndTime = 0; + m_flLastTickTime = 0; + m_bLoopScene = true; + //memset( m_flexWeight, 0, sizeof( m_flexWeight ) ); +} + +extern CChoreoStringPool g_ChoreoStringPool; +CChoreoScene *LoadSceneForModel( const char *filename, IChoreoEventCallback *pCallback, float *flSceneEndTime ) +{ + char loadfile[ 512 ]; + V_strcpy_safe( loadfile, filename ); + V_SetExtension( loadfile, ".vcd", sizeof( loadfile ) ); + V_FixSlashes( loadfile ); + + char *pBuffer = NULL; + size_t bufsize = scenefilecache->GetSceneBufferSize( loadfile ); + if ( bufsize <= 0 ) + return NULL; + + pBuffer = new char[ bufsize ]; + if ( !scenefilecache->GetSceneData( filename, (byte *)pBuffer, bufsize ) ) + { + delete[] pBuffer; + return NULL; + } + + CChoreoScene *pScene; + if ( IsBufferBinaryVCD( pBuffer, bufsize ) ) + { + pScene = new CChoreoScene( pCallback ); + CUtlBuffer buf( pBuffer, bufsize, CUtlBuffer::READ_ONLY ); + if ( !pScene->RestoreFromBinaryBuffer( buf, loadfile, &g_ChoreoStringPool ) ) + { + Warning( "Unable to restore binary scene '%s'\n", loadfile ); + delete pScene; + pScene = NULL; + } + else + { + pScene->SetPrintFunc( Scene_Printf ); + pScene->SetEventCallbackInterface( pCallback ); + } + } + else + { + g_TokenProcessor.SetBuffer( pBuffer ); + pScene = ChoreoLoadScene( loadfile, pCallback, &g_TokenProcessor, Scene_Printf ); + } + + delete[] pBuffer; + + if ( flSceneEndTime != NULL ) + { + // find the scene length + // The scene is as long as the end point for the last event unless one of the events is a loop + *flSceneEndTime = 0.0f; + bool bSetEndTime = false; + for ( int i = 0; i < pScene->GetNumEvents(); i++ ) + { + CChoreoEvent *pEvent = pScene->GetEvent( i ); + if ( pEvent->GetType() == CChoreoEvent::LOOP ) + { + *flSceneEndTime = -1.0f; + bSetEndTime = false; + break; + } + + if ( pEvent->GetEndTime() > *flSceneEndTime ) + { + *flSceneEndTime = pEvent->GetEndTime(); + bSetEndTime = true; + } + } + + if ( bSetEndTime ) + { + *flSceneEndTime += 0.1f; // give time for lerp to idle pose + } + } + + return pScene; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::PlayVCD( const char *pszVCD, const char *pszWeaponEntityRequired /*= NULL*/, bool bLoopVCD /*= true*/, bool bFileNameOnly /*= true*/ ) +{ + m_pszVCD = pszVCD; + m_pszWeaponEntityRequired = pszWeaponEntityRequired; + m_bLoopVCD = bLoopVCD; + m_bVCDFileNameOnly = bFileNameOnly; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::FireEvent( const char *pszEventName, const char *pszEventOptions ) +{ + //Plat_DebugString( CFmtStr( "********* ANIM EVENT: %s\n", pszEventName ) ); + + if ( V_strcmp( pszEventName, "AE_WPN_HIDE" ) == 0 ) + { + int nWeaponIndex = GetMergeMDLIndex( static_cast<IClientRenderable*>(m_pHeldItem) ); + if ( nWeaponIndex >= 0 ) + { + m_aMergeMDLs[nWeaponIndex].m_bDisabled = true; + } + } + else if ( V_strcmp( pszEventName, "AE_WPN_UNHIDE" ) == 0 ) + { + int nWeaponIndex = GetMergeMDLIndex( static_cast<IClientRenderable*>(m_pHeldItem) ); + if ( nWeaponIndex >= 0 ) + { + m_aMergeMDLs[nWeaponIndex].m_bDisabled = false; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::SwitchHeldItemTo( CEconItemView *pItem ) +{ + m_nBody = 0; + + ClearScene(); + + // Clear out visible items, and re-equip out wearables + RemoveAdditionalModels(); + EquipAllWearables( pItem ); + + // Then equip the held item + EquipItem( pItem ); + m_iCurrentSlotIndex = pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex ); + m_pHeldItem = pItem; + + m_StatTrackModel.m_bDisabled = true; + m_StatTrackModel.m_MDL.SetMDL( MDLHANDLE_INVALID ); + CAttribute_String attrModule; + static CSchemaAttributeDefHandle pAttr_module( "weapon_uses_stattrak_module" ); + if ( m_pHeldItem->FindAttribute( pAttr_module, &attrModule ) && attrModule.has_value() ) + { + // Allow for already strange items + bool bIsStrange = false; + if ( m_pHeldItem->GetQuality() == AE_STRANGE ) + { + bIsStrange = true; + } + + if ( !bIsStrange ) + { + // Go over the attributes of the item, if it has any strange attributes the item is strange and don't apply + for ( int i = 0; i < GetKillEaterAttrCount(); i++ ) + { + if ( m_pHeldItem->FindAttribute( GetKillEaterAttr_Score( i ) ) ) + { + bIsStrange = true; + break; + } + } + } + + if ( bIsStrange ) + { + static CSchemaAttributeDefHandle pAttr_moduleScale( "weapon_stattrak_module_scale" ); + // Does it have a stat track module + m_flStatTrackScale = 1.0f; + uint32 unFloatAsUint32 = 1; + if ( m_pHeldItem->FindAttribute( pAttr_moduleScale, &unFloatAsUint32 ) ) + { + m_flStatTrackScale = (float&)unFloatAsUint32; + } + + MDLHandle_t hStatTrackMDL = mdlcache->FindMDL( "models/weapons/c_models/stattrack.mdl" ); + if ( mdlcache->IsErrorModel( hStatTrackMDL ) ) + { + hStatTrackMDL = MDLHANDLE_INVALID; + } + m_StatTrackModel.m_MDL.SetMDL( hStatTrackMDL ); + mdlcache->Release( hStatTrackMDL ); // counterbalance addref from within FindMDL + + m_StatTrackModel.m_MDL.m_pProxyData = static_cast<IClientRenderable*>(pItem); + m_StatTrackModel.m_bDisabled = false; + m_StatTrackModel.m_MDL.m_nSequence = ACT_IDLE; + SetIdentityMatrix( m_StatTrackModel.m_MDLToWorld ); + } + } + + SetSequenceLayers( NULL, 0 ); + + // See if our VCD is overridden + if ( m_pszVCD ) + { + // Make sure we're holding the weapon, if it's required + bool bCanRunScene = true; + if ( m_pszWeaponEntityRequired && *m_pszWeaponEntityRequired ) + { + bCanRunScene = false; + + if ( pItem && pItem->IsValid() ) + { + const char *pszClassName = pItem->GetStaticData()->GetItemClass(); + if ( pszClassName && *pszClassName ) + { + bCanRunScene = V_stricmp( pszClassName, m_pszWeaponEntityRequired ) == 0; + } + } + } + + if ( bCanRunScene ) + { + if ( m_bVCDFileNameOnly ) + { + // auto complete relative path for the vcd file + V_sprintf_safe( g_szSceneTmpName, "scenes/player/%s/low/%s", g_aPlayerClassNames_NonLocalized[m_iCurrentClassIndex], m_pszVCD ); + } + else + { + // m_pszVCD should be a valid relative path + V_strcpy_safe( g_szSceneTmpName, m_pszVCD ); + } + + m_pScene = LoadSceneForModel( g_szSceneTmpName, this, &m_flSceneEndTime ); + m_bLoopScene = m_bLoopVCD; + + return; + } + } + + const char *pScene = NULL; + const char *pSequence = NULL; + const char *pRequiredItem = NULL; + bool bRemoveTauntParticles = true; + + if ( IsTauntItem( pItem->GetStaticData(), GetTeam(), m_iCurrentClassIndex, &pSequence, &pRequiredItem, &pScene ) ) + { + MDLCACHE_CRITICAL_SECTION(); + + if ( pScene ) + { + m_pScene = LoadSceneForModel( pScene, this, &m_flSceneEndTime ); + + // load custom prop for taunt + const char *pszProp = pItem->GetStaticData()->GetTauntData()->GetProp( m_iCurrentClassIndex ); + if ( pszProp ) + { + LoadAndAttachAdditionalModel( pszProp, pItem ); + } + + // force taunt to equip certain slot + static CSchemaAttributeDefHandle pAttrDef_TauntForceWeaponSlot( "taunt force weapon slot" ); + const char* pszTauntForceWeaponSlotName = NULL; + if ( FindAttribute_UnsafeBitwiseCast<CAttribute_String>( pItem, pAttrDef_TauntForceWeaponSlot, &pszTauntForceWeaponSlotName ) ) + { + int iForceWeaponSlot = StringFieldToInt( pszTauntForceWeaponSlotName, GetItemSchema()->GetWeaponTypeSubstrings() ); + EquipRequiredLoadoutSlot( iForceWeaponSlot ); + } + } + else + { + ClearScene(); + + CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache ); + int iSequence = LookupSequence( &studioHdr, pSequence ); + if ( iSequence >= 0 ) + { + // does a weapon need to be equipped? + loadout_positions_t requiredLoadoutItem = LOADOUT_POSITION_INVALID; + if ( pRequiredItem ) + { + requiredLoadoutItem = (loadout_positions_t)StringFieldToInt( pRequiredItem, ItemSystem()->GetItemSchema()->GetLoadoutStrings( pItem->GetItemDefinition()->GetEquipType() ) ); + } + + EquipRequiredLoadoutSlot( requiredLoadoutItem ); + + // finally, set the sequence layers + MDLSquenceLayer_t tmpSequenceLayers[1]; + tmpSequenceLayers[0].m_nSequenceIndex = iSequence; + tmpSequenceLayers[0].m_flWeight = 1.0; + tmpSequenceLayers[0].m_bNoLoop = false; + tmpSequenceLayers[0].m_flCycleBeganAt = m_RootMDL.m_MDL.m_flTime; + SetSequenceLayers( tmpSequenceLayers, 1 ); + } + } + + // Taunt Particles + static CSchemaAttributeDefHandle pAttrDef_OnTauntAttachParticleIndex( "on taunt attach particle index" ); + uint32 unUnusualEffectIndex = 0; + if ( pItem->FindAttribute( pAttrDef_OnTauntAttachParticleIndex, &unUnusualEffectIndex ) && unUnusualEffectIndex > 0 ) + { + const attachedparticlesystem_t *pParticleSystem = GetItemSchema()->GetAttributeControlledParticleSystem( unUnusualEffectIndex ); + if ( pParticleSystem ) + { + SafeDeleteParticleData( &m_aParticleSystems[ SYSTEM_TAUNT ] ); + m_aParticleSystems[ SYSTEM_TAUNT ] = CreateParticleData( pParticleSystem->pszSystemName ); + m_flTauntParticleRefireTime = gpGlobals->curtime + pParticleSystem->fRefireTime; + m_flTauntParticleRefireRate = pParticleSystem->fRefireTime; + m_bDrawTauntParticles = true; + bRemoveTauntParticles = false; + } + } + } + + // Clear out taunt particles + if ( bRemoveTauntParticles && m_aParticleSystems[SYSTEM_TAUNT] ) + { + m_bDrawTauntParticles = false; + SafeDeleteParticleData( &m_aParticleSystems[SYSTEM_TAUNT] ); + } + + // Check if it has a PoseParameter Attributes (r_hand_grip) + float flPose = 0; + static CSchemaAttributeDefHandle pAttrDef_RightHandPose( "righthand pose parameter" ); + uint32 iValue = 0; + if ( pItem->FindAttribute( pAttrDef_RightHandPose, &iValue ) ) + { + flPose = (float&)iValue; + } + SetPoseParameterByName( "r_hand_grip", flPose ); + + // Check for hand particles (spell book) + // always nuke + if ( m_aParticleSystems[ SYSTEM_ACTIONSLOT ] ) + { + SafeDeleteParticleData( &m_aParticleSystems[ SYSTEM_ACTIONSLOT ] ); + } + m_bDrawActionSlotEffects = false; + if ( pItem->GetStaticData()->GetItemClass() ) + { + m_bDrawActionSlotEffects = FStrEq( pItem->GetStaticData()->GetItemClass(), "tf_weapon_spellbook" ); + } + + // update eyeglows + m_bUpdateEyeGlows = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::EquipRequiredLoadoutSlot( int iRequiredLoadoutSlot ) +{ + if ( iRequiredLoadoutSlot != LOADOUT_POSITION_INVALID ) + { + int iDesiredSlot = -1; + FOR_EACH_VEC( m_ItemsToCarry, i ) + { + CEconItemView *pItem = m_ItemsToCarry[i]; + if ( pItem->GetStaticData()->IsAWearable() ) + continue; + if ( pItem->GetAnimationSlot() == -2 ) + continue; + + // Found a weapon. Wield it. + if ( iRequiredLoadoutSlot == pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex ) ) + { + iDesiredSlot = i; + break; + } + } + + if ( iDesiredSlot >= 0 ) + { + EquipItem( m_ItemsToCarry[iDesiredSlot] ); + } + else + { + // If we didn't find a weapon in the appropriate slot, get the base item + CEconItemView *pWeapon = TFInventoryManager()->GetBaseItemForClass( m_iCurrentClassIndex, iRequiredLoadoutSlot ); + if ( pWeapon && pWeapon->IsValid() ) + { + EquipItem( pWeapon ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::UpdateWeaponBodygroups( bool bModifyDeployedOnlyBodygroups ) +{ + for ( int i=0; i<MAX_WEAPON_SLOTS; i++ ) + { + CEconItemView *pItem = GetItemInSlot( i ); + if ( !pItem ) + continue; + + if ( pItem->GetStaticData()->GetHideBodyGroupsDeployedOnly() != bModifyDeployedOnlyBodygroups ) + continue; + + if ( !(m_pHeldItem == pItem || !pItem->GetStaticData()->GetHideBodyGroupsDeployedOnly()) ) + continue; + + UpdateHiddenBodyGroups( pItem ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::UpdateHiddenBodyGroups( CEconItemView* pItem ) +{ + MDLCACHE_CRITICAL_SECTION(); + CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache ); + + int iNumBodyGroups = pItem->GetStaticData()->GetNumModifiedBodyGroups( 0 ); + for ( int i=0; i<iNumBodyGroups; ++i ) + { + int iState = 0; + const char *pszBodyGroup = pItem->GetStaticData()->GetModifiedBodyGroup( 0, i, iState ); + int iBodyGroup = FindBodygroupByName( &studioHdr, pszBodyGroup ); + + if ( iBodyGroup == -1 ) + continue; + + ::SetBodygroup( &studioHdr, m_nBody, iBodyGroup, iState ); + SetBody( m_nBody ); + } + + // Handle style-based bodygroups + const CEconItemDefinition *pItemDef = pItem->GetItemDefinition(); + const CEconStyleInfo *pStyle = pItemDef ? pItemDef->GetStyleInfo( pItem->GetStyle() ) : NULL; + if ( pStyle ) + { + FOR_EACH_VEC( pStyle->GetAdditionalHideBodygroups(), i ) + { + int iBodyGroup = FindBodygroupByName( &studioHdr, pStyle->GetAdditionalHideBodygroups()[i] ); + + if ( iBodyGroup == -1 ) + continue; + + ::SetBodygroup( &studioHdr, m_nBody, iBodyGroup, 1 ); // force state to '1' here to mean hidden + SetBody( m_nBody ); + } + } + + // Handle world model bodygroup overrides + int iBodyOverride = pItem->GetStaticData()->GetWorldmodelBodygroupOverride( m_iTeam ); + int iBodyStateOverride = pItem->GetStaticData()->GetWorldmodelBodygroupStateOverride( m_iTeam ); + if ( iBodyOverride > -1 && iBodyStateOverride > -1 ) + { + ::SetBodygroup( &studioHdr, m_nBody, iBodyOverride, iBodyStateOverride ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconItemView *CTFPlayerModelPanel::GetItemInSlot( int iSlot ) +{ + CEconItemView *pOwnedItemInSlot = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, iSlot ); + + FOR_EACH_VEC( m_ItemsToCarry, i ) + { + CEconItemView *pItem = m_ItemsToCarry[i]; + int iLoadoutSlot = pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex ); + if ( iSlot == iLoadoutSlot ) + return pItem; + + // GetLoadoutSlot will not work for misc2, taunt2-8 because it will always return misc/taunt + if ( pOwnedItemInSlot && pOwnedItemInSlot->GetItemDefIndex() == pItem->GetItemDefIndex() ) + return pItem; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::EquipAllWearables( CEconItemView *pHeldItem ) +{ + // First, reset all our bodygroups + MDLCACHE_CRITICAL_SECTION(); + CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache ); + + const CEconItemSchema::BodygroupStateMap_t& mapBodygroupState = GetItemSchema()->GetDefaultBodygroupStateMap(); + + FOR_EACH_MAP_FAST( mapBodygroupState, i ) + { + const char *pszBodygroupName = mapBodygroupState.Key(i); + int iBodyGroup = FindBodygroupByName( &studioHdr, pszBodygroupName ); + if ( iBodyGroup > -1 ) + { + int iState = mapBodygroupState[i]; + ::SetBodygroup( &studioHdr, m_nBody, iBodyGroup, iState ); + } + } + + SetBody( m_nBody ); + + UpdateWeaponBodygroups( false ); + + // Now equip each of our wearables + FOR_EACH_VEC( m_ItemsToCarry, i ) + { + CEconItemView *pItem = m_ItemsToCarry[i]; + // If it's a wearable item, we put it on. + if ( pItem->GetStaticData()->IsAWearable() ) + { + EquipItem( pItem ); + } + + // Then see if there's an extra wearable we need to attach for this item + const char *pszAttached = pItem->GetExtraWearableModel(); + if ( pszAttached && pszAttached[ 0 ] ) + { + const char *pszViewModelAttached = pItem->GetExtraWearableViewModel(); + if ( pHeldItem == pItem || pszViewModelAttached == NULL || pszViewModelAttached[ 0 ] == '\0' || pszViewModelAttached[ 0 ] == '?' ) + { + LoadAndAttachAdditionalModel( pszAttached, pItem ); + } + } + } + + UpdateWeaponBodygroups( true ); + + SetBody( m_nBody ); + + UpdatePreviewVisuals(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::EquipItem( CEconItemView *pItem ) +{ + if ( m_iCurrentClassIndex == TF_CLASS_UNDEFINED ) + return; + + const GameItemDefinition_t *pItemDef = pItem->GetItemDefinition(); + Assert( pItemDef ); + + // Change team number so skins composite correctly + pItem->SetTeamNumber( m_iTeam ); + + // Non wearables can modify the animation + if ( !pItemDef->IsAWearable() ) + { + int iAnimSlot = pItem->GetAnimationSlot(); + + // Ignore items that don't want to control player animation + if ( iAnimSlot == -2 ) + return; + + if ( iAnimSlot == -1 ) + { + iAnimSlot = pItemDef->GetLoadoutSlot( m_iCurrentClassIndex ); + } + + const CUtlVector<const char *>& vecWeaponTypeSubstrings = GetItemSchema()->GetWeaponTypeSubstrings(); + if ( vecWeaponTypeSubstrings.IsValidIndex( iAnimSlot ) ) + { + int iAnim = FindAnimByName( vecWeaponTypeSubstrings[iAnimSlot] ); + SetModelAnim( iAnim ); + } + } + + // Attach the models for the item + const char *pszAttached = pItem->GetWorldDisplayModel(); + if ( !pszAttached ) + { + pszAttached = pItem->GetPlayerDisplayModel( m_iCurrentClassIndex, m_iTeam ); + } + + if ( pszAttached && pszAttached[0] ) + { + LoadAndAttachAdditionalModel( pszAttached, pItem ); + + int iTeam = pItemDef->GetBestVisualTeamData( m_iTeam ); + // Set attached models if viewable third-person. + { + const int iNumAttachedModels = pItemDef->GetNumAttachedModels( iTeam ); + for ( int i = 0; i < iNumAttachedModels; ++i ) + { + attachedmodel_t *pModel = pItemDef->GetAttachedModelData( iTeam, i ); + + if ( !( pModel->m_iModelDisplayFlags & kAttachedModelDisplayFlag_WorldModel ) ) + continue; + + if ( !pModel->m_pszModelName ) + { + Warning( "econ item definition '%s' attachment (team %d idx %d) has no model\n", pItemDef->GetDefinitionName(), iTeam, i ); + continue; + } + + LoadAndAttachAdditionalModel( pModel->m_pszModelName, pItem ); + } + } + + // Festive + // Set attached models if viewable third-person. + static CSchemaAttributeDefHandle pAttr_is_festivized( "is_festivized" ); + if ( pAttr_is_festivized && pItem->FindAttribute( pAttr_is_festivized ) ) + { + const int iNumAttachedModels = pItemDef->GetNumAttachedModelsFestivized( iTeam ); + for ( int i = 0; i < iNumAttachedModels; ++i ) + { + attachedmodel_t *pModel = pItemDef->GetAttachedModelDataFestivized( iTeam, i ); + + if ( !( pModel->m_iModelDisplayFlags & kAttachedModelDisplayFlag_WorldModel ) ) + continue; + + if ( !pModel->m_pszModelName ) + { + Warning( "econ item definition '%s' attachment (team %d idx %d) has no model\n", pItemDef->GetDefinitionName(), iTeam, i ); + continue; + } + + LoadAndAttachAdditionalModel( pModel->m_pszModelName, pItem ); + } + } + } + + // Hide any item associated groups. + UpdateHiddenBodyGroups( pItem ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CTFPlayerModelPanel::AddCarriedItem( CEconItemView *pItem ) +{ + CEconItemView *pNewItem = new CEconItemView; + *pNewItem = *pItem; + int iIdx = m_ItemsToCarry.AddToTail( pNewItem ); + + // This is a terrible hack. If we have team paint, we need an entity to find out what team + // we're on, but in this panel we don't have one. Instead, we force a flag all the way through + // the system on the CEconItemView so that the low-level paint code can pull from it if necessary. + if ( GetTeam() == TF_TEAM_BLUE ) + { + pNewItem->SetClientItemFlags( kEconItemFlagClient_ForceBlueTeam ); + } + + return iIdx; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::ClearCarriedItems( void ) +{ + RemoveAdditionalModels(); + m_ItemsToCarry.PurgeAndDeleteElements(); + m_pHeldItem = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::RemoveAdditionalModels( void ) +{ + ClearMergeMDLs(); + + // Unregister for all callbacks + modelinfo->UnregisterModelLoadCallback( -1, this ); + m_vecDynamicAssetsLoaded.Purge(); + m_vecItemsLoaded.PurgeAndDeleteElements(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::LoadAndAttachAdditionalModel( const char *pMDLName, CEconItemView *pItem ) +{ + int nModelIndex = -1; + + if ( pItem->GetStaticData()->IsContentStreamable() ) + { + // Get the client-only dynamic model index. The auto-addref + // of vecDynamicAssetsLoaded will actually trigger the load. + nModelIndex = modelinfo->RegisterDynamicModel( pMDLName, true ); + // Dynamic models never fail to register in this engine. + Assert( nModelIndex != -1 ); + } + else + { + // Is the (non-streamable) model already precached? If so, use it. + nModelIndex = modelinfo->GetModelIndex( pMDLName ); + } + + if ( nModelIndex == -1 ) + { + MDLHandle_t hMDL = vgui::MDLCache()->FindMDL( pMDLName ); + Assert( hMDL != MDLHANDLE_INVALID ); + if ( hMDL != MDLHANDLE_INVALID ) + { + // Model not loaded, not dynamic. Hard load and exit out. + SetMergeMDL( hMDL, static_cast<IClientRenderable*>(pItem), pItem->GetSkin( m_iTeam ) ); + } + m_MergeMDL = hMDL; + return; + } + + CEconItemView *pClone = new CEconItemView; + *pClone = *pItem; + m_vecDynamicAssetsLoaded[ m_vecDynamicAssetsLoaded.AddToTail() ] = nModelIndex; + m_vecItemsLoaded.AddToTail( pClone ); + + // callback triggers immediately if not dynamic + modelinfo->RegisterModelLoadCallback( nModelIndex, this, true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +static void SetMDLSkinForTeam( CMDL *pMDL, const CEconItemView *pItem, int iTeam ) +{ + Assert( pItem ); + + if ( !pMDL ) + return; + + // Ask the item for a skin... + int nSkin = pItem->GetSkin( iTeam ); + + if ( nSkin == -1 ) + { + // ... if not, use the team skin. + nSkin = iTeam == TF_TEAM_RED ? 0 : 1; + } + + pMDL->m_nSkin = nSkin; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::OnModelLoadComplete( const model_t *pModel ) +{ + CEconItemView *pItem = NULL; + FOR_EACH_VEC_BACK( m_vecDynamicAssetsLoaded, i ) + { + if ( modelinfo->GetModel( m_vecDynamicAssetsLoaded[ i ] ) == pModel ) + { + pItem = GetPreviewItem( m_vecItemsLoaded[ i ] ); + break; + } + } + + Assert( pItem ); + if ( pItem ) + { + MDLHandle_t hMDL = modelinfo->GetCacheHandle( pModel ); + Assert( hMDL != MDLHANDLE_INVALID ); + if ( hMDL != MDLHANDLE_INVALID ) + { + SetMergeMDL( hMDL, static_cast<IClientRenderable*>(pItem) ); + + int nBody = 0; + if ( pItem->GetStaticData()->UsesPerClassBodygroups( m_iTeam ) ) + { + CMDL *pMDL = GetMergeMDL(hMDL); + if ( pMDL ) + { + // Classes start at 1, bodygroups at 0, so we shift them all back 1. + MDLCACHE_CRITICAL_SECTION(); + CStudioHdr sHDR( pMDL->GetStudioHdr(), g_pMDLCache ); + ::SetBodygroup( &sHDR, nBody, 1, m_iCurrentClassIndex-1 ); + pMDL->m_nBody = nBody; + } + } + + // Set the custom skin. + SetMDLSkinForTeam( GetMergeMDL( hMDL ), pItem, m_iTeam ); + } + } +} + +void CTFPlayerModelPanel::SetTeam( int iTeam ) +{ + m_iTeam = iTeam; + + UpdatePreviewVisuals(); +} + +void CTFPlayerModelPanel::UpdatePreviewVisuals() +{ + // Assume skin will be chosen based only on the preview team + int iSkin = m_iTeam == TF_TEAM_RED ? 0 : 1; + + // Check if any of the items we're carrying should override this + static CSchemaAttributeDefHandle pAttrDef_PlayerSkinOverride( "player skin override" ); + Assert( pAttrDef_PlayerSkinOverride ); + FOR_EACH_VEC( m_ItemsToCarry, i ) + { + CEconItemView *pItem = m_ItemsToCarry[i]; + if ( !pItem ) + continue; + float fSkinOverride = 0.0f; + if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pItem, pAttrDef_PlayerSkinOverride, &fSkinOverride ) && fSkinOverride == 1.0f ) + { + C_TFPlayer::AdjustSkinIndexForZombie( m_iCurrentClassIndex, iSkin ); + break; + } + Assert( fSkinOverride == 0.0f ); + } + + // Set the player model skin. + SetSkin( iSkin ); + + // Set the weapon's skin. + if ( m_MergeMDL && m_pHeldItem ) + { + SetMDLSkinForTeam( GetMergeMDL( m_MergeMDL ), GetPreviewItem( m_pHeldItem ), m_iTeam ); + } + + // Set the skin for all other equipped items (wearables, etc). + for ( int i=0; i<m_vecDynamicAssetsLoaded.Count(); i++ ) + { + const model_t *pModel = modelinfo->GetModel( m_vecDynamicAssetsLoaded[i] ); + if ( pModel ) + { + MDLHandle_t hMDL = modelinfo->GetCacheHandle( pModel ); + + // We're iterating over a list of the dynamic assets that we've completed streaming in, but + // we want to set the style based on the "preview item" definition if possible. + SetMDLSkinForTeam( GetMergeMDL( hMDL ), GetPreviewItem( m_vecItemsLoaded[i] ), m_iTeam ); + } + } +} + +CEconItemView *CTFPlayerModelPanel::GetPreviewItem( CEconItemView *pMatchItem ) +{ + Assert( pMatchItem ); + if ( !pMatchItem ) + return NULL; + + FOR_EACH_VEC( m_ItemsToCarry, i ) + { + CEconItemView *pItem = m_ItemsToCarry[i]; + if ( *pMatchItem == *pItem ) + return pItem; + } + + return pMatchItem; +} + +int ClassZoomZ[] = +{ + 0, + 20, // TF_CLASS_SCOUT, + 25, // TF_CLASS_SNIPER, + 20, // TF_CLASS_SOLDIER, + 22, // TF_CLASS_DEMOMAN, + 30, // TF_CLASS_MEDIC, + 30, // TF_CLASS_HEAVYWEAPONS, + 22, // TF_CLASS_PYRO, + 27, // TF_CLASS_SPY, + 20, // TF_CLASS_ENGINEER, +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::ToggleZoom() +{ + m_bZoomedToHead = !m_bZoomedToHead; + + // NOTE: GetZoomOffset() relies on m_bZoomedToHead being up to date + m_vecPlayerPos += GetZoomOffset(); + + SetModelAnglesAndPosition( m_angPlayer, m_vecPlayerPos ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Vector CTFPlayerModelPanel::GetZoomOffset() +{ + const Vector vecOffset( 100, 0, ClassZoomZ[m_iCurrentClassIndex] ); + return m_bZoomedToHead ? -vecOffset : vecOffset; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::PrePaint3D( IMatRenderContext *pRenderContext ) +{ + if ( g_PlayerPreviewEffect.GetEffect() == C_TFPlayerPreviewEffect::PREVIEW_EFFECT_UBER ) + { + modelrender->ForcedMaterialOverride( *g_PlayerPreviewEffect.GetInvulnMaterialRef() ); + } + + BaseClass::PrePaint3D( pRenderContext ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::PostPaint3D( IMatRenderContext *pRenderContext ) +{ + if ( g_PlayerPreviewEffect.GetEffect() == C_TFPlayerPreviewEffect::PREVIEW_EFFECT_UBER ) + { + modelrender->ForcedMaterialOverride( NULL ); + } + + static bool bAlternate = false; + Vector vColor = bAlternate ? m_vEyeGlowColor1 : m_vEyeGlowColor2; + bAlternate = !bAlternate; + // Eye glows + if ( m_aParticleSystems[ SYSTEM_EYEGLOW_RIGHT ] ) + { + m_aParticleSystems[ SYSTEM_EYEGLOW_RIGHT ]->m_pParticleSystem->SetControlPoint( CUSTOM_COLOR_CP1, vColor ); + } + if ( m_aParticleSystems[ SYSTEM_EYESPARK_RIGHT ] ) + { + m_aParticleSystems[ SYSTEM_EYESPARK_RIGHT ]->m_pParticleSystem->SetControlPoint( CUSTOM_COLOR_CP1, vColor ); + } + + if ( m_aParticleSystems[ SYSTEM_EYEGLOW_LEFT ] ) + { + m_aParticleSystems[ SYSTEM_EYEGLOW_LEFT ]->m_pParticleSystem->SetControlPoint( CUSTOM_COLOR_CP1, vColor ); + } + if ( m_aParticleSystems[ SYSTEM_EYESPARK_LEFT ]) + { + m_aParticleSystems[ SYSTEM_EYESPARK_LEFT ]->m_pParticleSystem->SetControlPoint( CUSTOM_COLOR_CP1, vColor ); + } + + m_bUpdateEyeGlows = false; + m_bPlaySparks = false; + + // remove all particles that are not up-to-date before simulating the updated ones in the base + for ( int i = 0; i < ARRAYSIZE( m_aParticleSystems ); i++ ) + { + if ( m_aParticleSystems[i] && !m_aParticleSystems[i]->m_bIsUpdateToDate ) + { + SafeDeleteParticleData( &m_aParticleSystems[i] ); + } + } + + BaseClass::PostPaint3D( pRenderContext ); +} + +//----------------------------------------------------------------------------- +// Purpose : Called by base Mdlpanel when a merged mdl has been drawn +// For TF we use this as a way to render effects on top of model as appropriate (ie Unusual effects) +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::RenderingRootModel( IMatRenderContext *pRenderContext, CStudioHdr *pStudioHdr, MDLHandle_t mdlHandle, matrix3x4_t *pWorldMatrix ) +{ + if ( !m_bUseParticle ) + return; + + // Eye Glows + UpdateEyeGlows( pRenderContext, pStudioHdr, mdlHandle, pWorldMatrix, true ); + UpdateEyeGlows( pRenderContext, pStudioHdr, mdlHandle, pWorldMatrix, false ); + + // Right hand + UpdateActionSlotEffects( pRenderContext, pStudioHdr, mdlHandle, pWorldMatrix ); + + // Taunt Effects + UpdateTauntEffects( pRenderContext, pStudioHdr, mdlHandle, pWorldMatrix ); +} + +CEconItemView *CTFPlayerModelPanel::GetLoadoutItemFromMDLHandle( loadout_positions_t iPosition, MDLHandle_t mdlHandle ) +{ + // Check if we have a particle hat, if not ignore + CEconItemView *pEconItem = NULL; + + // Find this item + FOR_EACH_VEC( m_ItemsToCarry, i ) + { + CEconItemView *pItem = m_ItemsToCarry[i]; + int iLoadoutSlot = pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex ); + if ( ( IsMiscSlot( iLoadoutSlot ) && IsMiscSlot( iPosition ) ) || + ( IsValidPickupWeaponSlot( iLoadoutSlot ) && iLoadoutSlot == iPosition ) ) + { + const char * pDisplayModel = pItem->GetPlayerDisplayModel( m_iCurrentClassIndex, m_iTeam ); + if ( pDisplayModel ) + { + MDLHandle_t hMDLFindResult = vgui::MDLCache()->FindMDL( pDisplayModel ); + // compare the model to make sure that this is the same item + if ( hMDLFindResult == mdlHandle ) + { + pEconItem = pItem; + vgui::MDLCache()->Release(hMDLFindResult); // counterbalance addref from within FindMDL + break; + } + vgui::MDLCache()->Release(hMDLFindResult); + } + } + } + + return pEconItem; +} + +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::RenderingMergedModel( IMatRenderContext *pRenderContext, CStudioHdr *pStudioHdr, MDLHandle_t mdlHandle, matrix3x4_t *pWorldMatrix ) +{ + if ( !m_bUseParticle ) + return; + + static struct MergeModelSlot_t + { + loadout_positions_t iPosition; + modelpanel_particle_system_t iSystem; + } s_mergeModelSlot[] = + { + { LOADOUT_POSITION_HEAD, SYSTEM_HEAD }, + { LOADOUT_POSITION_MISC, SYSTEM_MISC1 }, + { LOADOUT_POSITION_MISC2, SYSTEM_MISC2 }, + { LOADOUT_POSITION_PRIMARY, SYSTEM_WEAPON }, + { LOADOUT_POSITION_SECONDARY, SYSTEM_WEAPON }, + { LOADOUT_POSITION_MELEE, SYSTEM_WEAPON }, + }; + + modelpanel_particle_system_t iSystem = SYSTEM_HEAD; + loadout_positions_t iPosition = LOADOUT_POSITION_INVALID; + CEconItemView *pEconItem = NULL; + int count = ARRAYSIZE( s_mergeModelSlot ); + for ( int i=0; i<count; ++i ) + { + // find the item for this model + pEconItem = GetLoadoutItemFromMDLHandle( s_mergeModelSlot[i].iPosition, mdlHandle ); + if ( pEconItem ) + { + iPosition = s_mergeModelSlot[i].iPosition; + iSystem = s_mergeModelSlot[i].iSystem; + + // this is horrible but this fixes multiple unusual cosmetics with same default loadout to update their particles + if ( m_aParticleSystems[ iSystem ] && m_aParticleSystems[ iSystem ]->m_bIsUpdateToDate ) + continue; + + break; + } + } + + // couldn't find matching item for this model, do nothing + if ( !pEconItem ) + return; + + // Unusual Particles + // Update Misc Particles 1 by 1, Unfortunately the equip location is generic (MISC_SLOT) and not the specific slot + // so we have to test each slot individually + UpdateCosmeticParticles( pRenderContext, pStudioHdr, mdlHandle, pWorldMatrix, iSystem, pEconItem ); + + if ( m_iCurrentSlotIndex == iPosition ) + { + RenderStatTrack( pStudioHdr, pWorldMatrix ); + } +} + +IMaterial* CTFPlayerModelPanel::GetOverrideMaterial( MDLHandle_t mdlHandle ) +{ + loadout_positions_t s_iPosition[] = { + LOADOUT_POSITION_HEAD, + LOADOUT_POSITION_MISC, + LOADOUT_POSITION_MISC2, + LOADOUT_POSITION_PRIMARY, + LOADOUT_POSITION_SECONDARY, + LOADOUT_POSITION_MELEE + }; + + int count = ARRAYSIZE( s_iPosition ); + for ( int i = 0; i < count; ++i ) + { + CEconItemView *pEconItem = GetLoadoutItemFromMDLHandle( s_iPosition[ i ], mdlHandle ); + if ( pEconItem ) + return pEconItem->GetMaterialOverride( m_iTeam ); + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFPlayerModelPanel::RenderStatTrack( CStudioHdr *pStudioHdr, matrix3x4_t *pWorldMatrix ) +{ + // Draw the merge MDLs. + if ( !m_StatTrackModel.m_bDisabled ) + { + matrix3x4_t matMergeBoneToWorld[MAXSTUDIOBONES]; + + // Get the merge studio header. + studiohdr_t *pStatTrackStudioHdr = m_StatTrackModel.m_MDL.GetStudioHdr(); + matrix3x4_t *pMergeBoneToWorld = &matMergeBoneToWorld[0]; + + // If we have a valid mesh, bonemerge it. If we have an invalid mesh we can't bonemerge because + // it'll crash trying to pull data from the missing header. + if ( pStatTrackStudioHdr != NULL ) + { + CStudioHdr mergeHdr( pStatTrackStudioHdr, g_pMDLCache ); + m_StatTrackModel.m_MDL.SetupBonesWithBoneMerge( &mergeHdr, pMergeBoneToWorld, pStudioHdr, pWorldMatrix, m_StatTrackModel.m_MDLToWorld ); + for ( int i=0; i<mergeHdr.numbones(); ++i ) + { + MatrixScaleBy( m_flStatTrackScale, pMergeBoneToWorld[i] ); + } + m_StatTrackModel.m_MDL.Draw( m_StatTrackModel.m_MDLToWorld, pMergeBoneToWorld ); + } + + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +bool CTFPlayerModelPanel::UpdateCosmeticParticles( + IMatRenderContext *pRenderContext, + CStudioHdr *pStudioHdr, + MDLHandle_t mdlHandle, + matrix3x4_t *pWorldMatrix, + modelpanel_particle_system_t iSystem, + CEconItemView *pEconItem +) +{ + if ( m_aParticleSystems[ iSystem ] && m_aParticleSystems[ iSystem ]->m_bIsUpdateToDate ) + return false; + + attachedparticlesystem_t *pParticleSystem = NULL; + + // do community_sparkle effect if this is a community item? + const int iQualityParticleType = pEconItem->GetQualityParticleType(); + if ( iQualityParticleType > 0 ) + { + pParticleSystem = GetItemSchema()->GetAttributeControlledParticleSystem( iQualityParticleType ); + } + + if ( !pParticleSystem ) + { + // does this hat even have a particle effect + static CSchemaAttributeDefHandle pAttrDef_AttachParticleEffect( "attach particle effect" ); + uint32 iValue = 0; + if ( !pEconItem->FindAttribute( pAttrDef_AttachParticleEffect, &iValue ) ) + { + return false; + } + + const float& value_as_float = (float&)iValue; + pParticleSystem = GetItemSchema()->GetAttributeControlledParticleSystem( value_as_float ); + } + + // failed to find any particle effect + if ( !pParticleSystem ) + { + return false; + } + + // Team Color + if ( GetTeam() == TF_TEAM_BLUE && V_stristr( pParticleSystem->pszSystemName, "_teamcolor_red" )) + { + static char pBlue[256]; + V_StrSubst( pParticleSystem->pszSystemName, "_teamcolor_red", "_teamcolor_blue", pBlue, 256 ); + pParticleSystem = GetItemSchema()->FindAttributeControlledParticleSystem( pBlue ); + if ( !pParticleSystem ) + { + return false; + } + } + + // if this thing has a bip_head or prp_helmet (aka a hat) + int iBone = Studio_BoneIndexByName( pStudioHdr, "bip_head" ); + if ( iBone < 0 ) + { + iBone = Studio_BoneIndexByName( pStudioHdr, "prp_helmet" ); + if ( iBone < 0 ) + { + iBone = Studio_BoneIndexByName( pStudioHdr, "prp_hat" ); + } + } + + // default to root + if ( iBone < 0 ) + { + iBone = 0; + } + + // Get Use Head Origin + CUtlVector< int > vecAttachments; + static CSchemaAttributeDefHandle pAttrDef_UseHead( "particle effect use head origin" ); + uint32 iUseHead = 0; + if ( !pEconItem->FindAttribute( pAttrDef_UseHead, &iUseHead ) || !iUseHead == 0 ) + { + // not using head? try searching for attachment points + for ( int i=0; i<ARRAYSIZE( pParticleSystem->pszControlPoints ); ++i ) + { + const char *pszAttachmentName = pParticleSystem->pszControlPoints[i]; + if ( pszAttachmentName && pszAttachmentName[0] ) + { + int iAttachment = Studio_FindAttachment( pStudioHdr, pszAttachmentName ); + if ( iAttachment < 0 ) + continue; + + vecAttachments.AddToTail( iAttachment ); + } + } + } + + static char pszFullname[256]; + const char* pszSystemName = pParticleSystem->pszSystemName; + // Weapon Remap for a Base Effect to be used on a specific weapon + if ( pParticleSystem->bUseSuffixName && pEconItem && pEconItem->GetItemDefinition()->GetParticleSuffix() ) + { + V_strcpy_safe( pszFullname, pParticleSystem->pszSystemName ); + V_strcat_safe( pszFullname, "_" ); + V_strcat_safe( pszFullname, pEconItem->GetItemDefinition()->GetParticleSuffix() ); + pszSystemName = pszFullname; + } + + // Update the Particles and render them + if ( m_aParticleSystems[ iSystem ] ) + { + // Check if its a new particle system + if ( V_strcmp( m_aParticleSystems[ iSystem ]->m_pParticleSystem->GetName(), pszSystemName ) ) + { + SafeDeleteParticleData( &m_aParticleSystems[ iSystem ] ); + m_aParticleSystems[ iSystem ] = CreateParticleData( pszSystemName ); + } + } + else + { + // create + m_aParticleSystems[ iSystem ] = CreateParticleData( pszSystemName ); + } + + // Particle system does not exist + if ( !m_aParticleSystems[ iSystem ] ) + return false; + + // Get offset if it exists (and if we're using head offset) + static CSchemaAttributeDefHandle pAttrDef_VerticalOffset( "particle effect vertical offset" ); + uint32 iOffset = 0; + Vector vecParticleOffset( 0, 0, 0 ); + if ( iUseHead > 0 && pEconItem->FindAttribute( pAttrDef_VerticalOffset, &iOffset ) ) + { + vecParticleOffset.z = (float&)iOffset; + } + + m_aParticleSystems[ iSystem ]->UpdateControlPoints( pStudioHdr, pWorldMatrix, vecAttachments, iBone, vecParticleOffset ); + return true; +} + + +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::UpdateEyeGlows( + IMatRenderContext *pRenderContext, + CStudioHdr *pStudioHdr, + MDLHandle_t mdlHandle, + matrix3x4_t *pWorldMatrix, + bool bIsRightEye +) { + float flOffset = 0; + modelpanel_particle_system_t eyeSystem = bIsRightEye ? SYSTEM_EYEGLOW_RIGHT : SYSTEM_EYEGLOW_LEFT; + modelpanel_particle_system_t sparkSystem = bIsRightEye ? SYSTEM_EYESPARK_RIGHT : SYSTEM_EYESPARK_LEFT; + const char* pszAttach = bIsRightEye ? "eyeglow_R" : "eyeglow_L"; + + // is this a model we care about? + int iAttachment = Studio_FindAttachment( pStudioHdr, pszAttach ); + if ( iAttachment == INVALID_PARTICLE_ATTACHMENT || iAttachment == -1 ) + return; + + if ( m_bUpdateEyeGlows ) + { + const char* pszGlowEffectName = m_pszEyeGlowParticleName; + + // kill old effects + SafeDeleteParticleData( &m_aParticleSystems[ eyeSystem ] ); + + if ( !bIsRightEye && GetPlayerClass() == TF_CLASS_DEMOMAN ) + { + // demo man has a green eyeglow for eyelander if applicable + C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( pPlayer ) + { + int iDecaps = pPlayer->m_Shared.GetDecapitations(); + pszGlowEffectName = pPlayer->GetDemomanEyeEffectName( iDecaps ); + } + } + + if ( pszGlowEffectName && pszGlowEffectName[0] != '\0' ) + { + m_aParticleSystems[ eyeSystem ] = CreateParticleData( pszGlowEffectName ); + } + } + + if ( m_bPlaySparks && !m_vEyeGlowColor1.IsZero() ) + { + SafeDeleteParticleData( &m_aParticleSystems[ sparkSystem ] ); + + // Generate an eye spark as well not for demo + m_aParticleSystems[ sparkSystem ] = CreateParticleData( "killstreak_t0_lvl1_flash" ); + } + + // Tick Update on position + if ( m_aParticleSystems[ eyeSystem ] || m_aParticleSystems[ sparkSystem ] ) + { + // Figure out where our attach point is + matrix3x4_t matAttachToWorld; + + CUtlVector< int > vecAttachments; + vecAttachments.AddToTail( iAttachment ); + + // Update control points which is updating the position of the particles + Vector vecForward; + MatrixGetColumn( matAttachToWorld, 0, vecForward ); + + Vector vecParticleOffset = vecForward * flOffset; + if ( m_aParticleSystems[ eyeSystem ] ) + { + m_aParticleSystems[ eyeSystem ]->UpdateControlPoints( pStudioHdr, pWorldMatrix, vecAttachments, 0, vecParticleOffset ); + } + + if ( m_aParticleSystems[ sparkSystem ] ) + { + m_aParticleSystems[ sparkSystem ]->UpdateControlPoints( pStudioHdr, pWorldMatrix, vecAttachments, 0, vecParticleOffset ); + } + } +} + +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::UpdateActionSlotEffects( + IMatRenderContext *pRenderContext, + CStudioHdr *pStudioHdr, + MDLHandle_t mdlHandle, + matrix3x4_t *pWorldMatrix +) { + // is this a model we care about? + int iAttachment = Studio_FindAttachment( pStudioHdr, "effect_hand_R" ); + if ( iAttachment == INVALID_PARTICLE_ATTACHMENT || iAttachment == -1 ) + return; + + if ( !m_bDrawActionSlotEffects ) + return; + + if ( !m_aParticleSystems[ SYSTEM_ACTIONSLOT ] ) + { + m_aParticleSystems[ SYSTEM_ACTIONSLOT ] = CreateParticleData( CTFSpellBook::GetHandEffect( m_pHeldItem, 0 ) ); + } + + if ( !m_aParticleSystems[ SYSTEM_ACTIONSLOT ] ) + return; + + CUtlVector< int > vecAttachments; + vecAttachments.AddToTail( iAttachment ); + + m_aParticleSystems[ SYSTEM_ACTIONSLOT ]->UpdateControlPoints( pStudioHdr, pWorldMatrix, vecAttachments ); +} + +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::UpdateTauntEffects( + IMatRenderContext *pRenderContext, + CStudioHdr *pStudioHdr, + MDLHandle_t mdlHandle, + matrix3x4_t *pWorldMatrix + ) { + if ( !m_bDrawTauntParticles ) + return; + + if ( !m_aParticleSystems[SYSTEM_TAUNT] ) + return; + + // Check if refire is needed + if ( m_flTauntParticleRefireRate > 0 && m_flTauntParticleRefireTime < gpGlobals->curtime ) + { + m_flTauntParticleRefireTime = gpGlobals->curtime + m_flTauntParticleRefireRate; + + // safe off current particle name + CUtlString strParticleName = m_aParticleSystems[SYSTEM_TAUNT]->m_pParticleSystem->GetName(); + + // remove old particle + SafeDeleteParticleData( &m_aParticleSystems[SYSTEM_TAUNT] ); + + // create new particle + m_aParticleSystems[SYSTEM_TAUNT] = CreateParticleData( strParticleName.String() ); + } + + matrix3x4_t matAttachToWorld; + SetIdentityMatrix( matAttachToWorld ); + + CUtlVector< int > vecAttachments; + m_aParticleSystems[SYSTEM_TAUNT]->UpdateControlPoints( pStudioHdr, &matAttachToWorld, vecAttachments, 0, m_vecPlayerPos ); +} + +//----------------------------------------------------------------------------- +// Called Externally +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::SetEyeGlowEffect( const char *pEffectName, Vector vColor1, Vector vColor2, bool bForceUpdate, bool bPlaySparks ) +{ + m_vEyeGlowColor1 = vColor1; + m_vEyeGlowColor2 = vColor2; + m_bPlaySparks = bPlaySparks; + + if ( bForceUpdate ) + { + m_bUpdateEyeGlows = true; + } + + if ( !pEffectName ) + { + if ( m_pszEyeGlowParticleName[0] != '\0' ) + { + m_bUpdateEyeGlows = true; + } + m_pszEyeGlowParticleName[0] = '\0'; + } + else if ( !FStrEq( m_pszEyeGlowParticleName, pEffectName) ) + { + V_strcpy_safe( m_pszEyeGlowParticleName, pEffectName ); + m_bUpdateEyeGlows = true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: clear all particles +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::InvalidateParticleEffects() +{ + for ( int i=0; i<ARRAYSIZE(m_aParticleSystems); ++i ) + { + if ( m_aParticleSystems[i] ) + { + SafeDeleteParticleData( &m_aParticleSystems[i] ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::StartEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) +{ + if ( !event || !event->GetActive() ) + return; + + CChoreoActor *actor = event->GetActor(); + if ( actor && !actor->GetActive() ) + return; + + CChoreoChannel *channel = event->GetChannel(); + if ( channel && !channel->GetActive() ) + return; + + //Msg( "Got STARTEVENT at %.2f\n", currenttime ); + //Msg( "%8.4f: start %s\n", currenttime, event->GetDescription() ); + + switch ( event->GetType() ) + { + case CChoreoEvent::SEQUENCE: + ProcessSequence( scene, event ); + break; + + case CChoreoEvent::SPEAK: + { + if ( m_bDisableSpeakEvent ) + return; + + // FIXME: dB hack. soundlevel needs to be moved into inside of wav? + soundlevel_t iSoundlevel = SNDLVL_TALKING; + if ( event->GetParameters2() ) + { + iSoundlevel = (soundlevel_t)atoi( event->GetParameters2() ); + if ( iSoundlevel == SNDLVL_NONE ) + { + iSoundlevel = SNDLVL_TALKING; + } + } + + float time_in_past = currenttime - event->GetStartTime() ; + float soundtime = gpGlobals->curtime - time_in_past; + + EmitSound_t es; + es.m_nChannel = CHAN_VOICE; + es.m_flVolume = 1; + es.m_SoundLevel = iSoundlevel; + es.m_flSoundTime = soundtime; + es.m_bEmitCloseCaption = false; + es.m_pSoundName = event->GetParameters(); + + C_RecipientFilter filter; + C_BaseEntity::EmitSound( filter, SOUND_FROM_UI_PANEL, es ); + } + break; + + case CChoreoEvent::STOPPOINT: + { + // Nothing, this is a symbolic event for keeping the vcd alive for ramping out after the last true event + //ClearScene(); + } + break; + + case CChoreoEvent::LOOP: + ProcessLoop( scene, event ); + break; + + // Not supported in TF2's model previews + case CChoreoEvent::SUBSCENE: + case CChoreoEvent::SECTION: + { + Assert(0); + } + break; + + default: + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::EndEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) +{ + if ( !event || !event->GetActive() ) + return; + + CChoreoActor *actor = event->GetActor(); + if ( actor && !actor->GetActive() ) + return; + + CChoreoChannel *channel = event->GetChannel(); + if ( channel && !channel->GetActive() ) + return; + + //Msg( "Got ENDEVENT at %.2f\n", currenttime ); + //Msg( "%8.4f: end %s %i\n", currenttime, event->GetDescription(), event->GetType() ); + + switch ( event->GetType() ) + { + case CChoreoEvent::SUBSCENE: + { + // Not supported in TF2's model previews + Assert(0); + } + break; + case CChoreoEvent::SPEAK: + { + } + break; + case CChoreoEvent::STOPPOINT: + { + //SetSequenceLayers( NULL, 0 ); + //ClearScene(); + } + break; + default: + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::ProcessEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) +{ + if ( !event || !event->GetActive() ) + return; + + CChoreoActor *actor = event->GetActor(); + if ( actor && !actor->GetActive() ) + return; + + CChoreoChannel *channel = event->GetChannel(); + if ( channel && !channel->GetActive() ) + return; + + //Msg("PROCESSEVENT at %.2f\n", currenttime ); + + switch( event->GetType() ) + { + case CChoreoEvent::EXPRESSION: + if ( !m_bShouldRunFlexEvents ) + { + ProcessFlexSettingSceneEvent( scene, event ); + } + break; + + case CChoreoEvent::FLEXANIMATION: + if ( m_bShouldRunFlexEvents ) + { + ProcessFlexAnimation( scene, event ); + } + break; + + case CChoreoEvent::SEQUENCE: + case CChoreoEvent::SPEAK: + case CChoreoEvent::STOPPOINT: + // Nothing + break; + + // Not supported in TF2's model previews + case CChoreoEvent::LOOKAT: + case CChoreoEvent::FACE: + case CChoreoEvent::SUBSCENE: + case CChoreoEvent::MOVETO: + case CChoreoEvent::INTERRUPT: + case CChoreoEvent::PERMIT_RESPONSES: + case CChoreoEvent::GESTURE: + Assert(0); + break; + + default: + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFPlayerModelPanel::CheckEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) +{ + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Apply a sequence +// Input : *event - +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::ProcessSequence( CChoreoScene *scene, CChoreoEvent *event ) +{ + Assert( event->GetType() == CChoreoEvent::SEQUENCE ); + + CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache ); + + if ( !event->GetActor() ) + return; + + int iSequence = LookupSequence( &studioHdr, event->GetParameters() ); + if (iSequence < 0) + return; + + // making sure the mdl has correct playback rate + mstudioseqdesc_t &seqdesc = studioHdr.pSeqdesc( iSequence ); + mstudioanimdesc_t &animdesc = studioHdr.pAnimdesc( studioHdr.iRelativeAnim( iSequence, seqdesc.anim(0,0) ) ); + m_RootMDL.m_MDL.m_flPlaybackRate = animdesc.fps; + + MDLSquenceLayer_t tmpSequenceLayers[1]; + tmpSequenceLayers[0].m_nSequenceIndex = iSequence; + tmpSequenceLayers[0].m_flWeight = 1.0; + tmpSequenceLayers[0].m_bNoLoop = true; + tmpSequenceLayers[0].m_flCycleBeganAt = m_RootMDL.m_MDL.m_flTime; + SetSequenceLayers( tmpSequenceLayers, 1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *scene - +// *event - +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::ProcessLoop( CChoreoScene *scene, CChoreoEvent *event ) +{ + Assert( event->GetType() == CChoreoEvent::LOOP ); + + float backtime = (float)atof( event->GetParameters() ); + + bool process = true; + int counter = event->GetLoopCount(); + if ( counter != -1 ) + { + int remaining = event->GetNumLoopsRemaining(); + if ( remaining <= 0 ) + { + process = false; + } + else + { + event->SetNumLoopsRemaining( --remaining ); + } + } + + if ( !process ) + return; + + //Msg("LOOP: %.2f (%.2f)\n", m_flSceneTime, scene->GetTime() ); + + float flPrevTime = m_flSceneTime; + scene->LoopToTime( backtime ); + m_flSceneTime = backtime; + + //Msg(" -> %.2f (%.2f)\n", m_flSceneTime, scene->GetTime() ); + + float flDelta = flPrevTime - backtime; + + //Msg(" -> Delta %.2f\n", flDelta ); + + // If we're running noloop sequences, we need to push out their begin time, so they keep playing + for ( int i = 0; i < m_nNumSequenceLayers; i++ ) + { + if ( m_SequenceLayers[i].m_bNoLoop ) + { + m_SequenceLayers[i].m_flCycleBeganAt += flDelta; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +LocalFlexController_t CTFPlayerModelPanel::GetNumFlexControllers( void ) +{ + CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache ); + return studioHdr.numflexcontrollers(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CTFPlayerModelPanel::GetFlexDescFacs( int iFlexDesc ) +{ + CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache ); + + mstudioflexdesc_t *pflexdesc = studioHdr.pFlexdesc( iFlexDesc ); + + return pflexdesc->pszFACS( ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CTFPlayerModelPanel::GetFlexControllerName( LocalFlexController_t iFlexController ) +{ + CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache ); + + mstudioflexcontroller_t *pflexcontroller = studioHdr.pFlexcontroller( iFlexController ); + + return pflexcontroller->pszName( ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CTFPlayerModelPanel::GetFlexControllerType( LocalFlexController_t iFlexController ) +{ + CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache ); + + mstudioflexcontroller_t *pflexcontroller = studioHdr.pFlexcontroller( iFlexController ); + + return pflexcontroller->pszType( ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +LocalFlexController_t CTFPlayerModelPanel::FindFlexController( const char *szName ) +{ + for (LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++) + { + if (stricmp( GetFlexControllerName( i ), szName ) == 0) + { + return i; + } + } + + // AssertMsg( 0, UTIL_VarArgs( "flexcontroller %s couldn't be mapped!!!\n", szName ) ); + return LocalFlexController_t(-1); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::SetFlexWeight( LocalFlexController_t index, float value ) +{ + if (index >= 0 && index < GetNumFlexControllers()) + { + CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache ); + + mstudioflexcontroller_t *pflexcontroller = studioHdr.pFlexcontroller( index ); + + if (pflexcontroller->max != pflexcontroller->min) + { + value = (value - pflexcontroller->min) / (pflexcontroller->max - pflexcontroller->min); + value = clamp( value, 0.0f, 1.0f ); + } + + m_flexWeight[ index ] = value; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CTFPlayerModelPanel::GetFlexWeight( LocalFlexController_t index ) +{ + if (index >= 0 && index < GetNumFlexControllers()) + { + CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache ); + + mstudioflexcontroller_t *pflexcontroller = studioHdr.pFlexcontroller( index ); + + if (pflexcontroller->max != pflexcontroller->min) + { + return m_flexWeight[index] * (pflexcontroller->max - pflexcontroller->min) + pflexcontroller->min; + } + + return m_flexWeight[index]; + } + return 0.0; +} + +//----------------------------------------------------------------------------- +// Purpose: During paint, apply the flex weights to the model +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::SetupFlexWeights( void ) +{ + if ( m_RootMDL.m_MDL.GetMDL() == MDLHANDLE_INVALID ) + return; + + // initialize the models local to global flex controller mappings + CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache ); + if (studioHdr.pFlexcontroller( LocalFlexController_t(0) )->localToGlobal == -1) + { + for ( LocalFlexController_t i = LocalFlexController_t(0); i < studioHdr.numflexcontrollers(); i++) + { + int j = C_BaseFlex::AddGlobalFlexController( studioHdr.pFlexcontroller( i )->pszName() ); + studioHdr.pFlexcontroller( i )->localToGlobal = j; + } + } + + int iControllers = GetNumFlexControllers(); + for ( int j = 0; j < iControllers; j++ ) + { + m_RootMDL.m_MDL.m_pFlexControls[j] = 0; + } + + LocalFlexController_t i; + + // Decay to neutral + for ( i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++) + { + SetFlexWeight( i, GetFlexWeight( i ) * 0.95 ); + } + + // Run scene + if ( m_pScene ) + { + m_bShouldRunFlexEvents = true; + m_pScene->Think( m_flSceneTime ); + } + + // get the networked flexweights and convert them from 0..1 to real dynamic range + for (i = LocalFlexController_t(0); i < studioHdr.numflexcontrollers(); i++) + { + mstudioflexcontroller_t *pflex = studioHdr.pFlexcontroller( i ); + + m_RootMDL.m_MDL.m_pFlexControls[pflex->localToGlobal] = m_flexWeight[i]; + // rescale + m_RootMDL.m_MDL.m_pFlexControls[pflex->localToGlobal] = m_RootMDL.m_MDL.m_pFlexControls[pflex->localToGlobal] * (pflex->max - pflex->min) + pflex->min; + } + + if ( m_pScene ) + { + m_bShouldRunFlexEvents = false; + m_pScene->Think( m_flSceneTime ); + } + + ProcessVisemes( m_PhonemeClasses ); + + if ( m_pScene ) + { + // Advance time + if ( m_flLastTickTime < FLT_EPSILON ) + { + m_flLastTickTime = m_RootMDL.m_MDL.m_flTime - 0.1; + } + + m_flSceneTime += (m_RootMDL.m_MDL.m_flTime - m_flLastTickTime); + m_flLastTickTime = m_RootMDL.m_MDL.m_flTime; + + if ( m_flSceneEndTime > FLT_EPSILON && m_flSceneTime > m_flSceneEndTime ) + { + bool bLoopScene = m_bLoopScene; + char filename[MAX_PATH]; + V_strcpy_safe( filename, m_pScene->GetFilename() ); + + SetSequenceLayers( NULL, 0 ); + ClearScene(); + + if ( bLoopScene ) + { + m_pScene = LoadSceneForModel( filename, this, &m_flSceneEndTime ); + } + else + { + m_pszVCD = NULL; + } + } + } +} + +extern CFlexSceneFileManager g_FlexSceneFileManager; +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::ProcessExpression( CChoreoScene *scene, CChoreoEvent *event ) +{ + // Flexanimations have to have an end time!!! + if ( !event->HasEndTime() ) + return; + + // Look up the actual strings + const char *scenefile = event->GetParameters(); + const char *name = event->GetParameters2(); + + // Have to find both strings + if ( scenefile && name ) + { + // Find the scene file + const flexsettinghdr_t *pExpHdr = ( const flexsettinghdr_t * )g_FlexSceneFileManager.FindSceneFile( this, scenefile, true ); + if ( pExpHdr ) + { + float scenetime = scene->GetTime(); + + float flIntensity = event->GetIntensity( scenetime ); + + int i; + const flexsetting_t *pSetting = NULL; + + // Find the named setting in the base + for ( i = 0; i < pExpHdr->numflexsettings; i++ ) + { + pSetting = pExpHdr->pSetting( i ); + if ( !pSetting ) + continue; + + if ( !V_stricmp( pSetting->pszName(), name ) ) + break; + } + + if ( i>=pExpHdr->numflexsettings ) + return; + + flexweight_t *pWeights = NULL; + int truecount = pSetting->psetting( (byte *)pExpHdr, 0, &pWeights ); + if ( !pWeights ) + return; + + for (i = 0; i < truecount; i++, pWeights++) + { + int j = FlexControllerLocalToGlobal( pExpHdr, pWeights->key ); + + float s = clamp( pWeights->influence * flIntensity, 0.0f, 1.0f ); + m_RootMDL.m_MDL.m_pFlexControls[ j ] = m_RootMDL.m_MDL.m_pFlexControls[j] * (1.0f - s) + pWeights->weight * s; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::ProcessFlexSettingSceneEvent( CChoreoScene *scene, CChoreoEvent *event ) +{ + // Flexanimations have to have an end time!!! + if ( !event->HasEndTime() ) + return; + + VPROF( "C_BaseFlex::ProcessFlexSettingSceneEvent" ); + + // Look up the actual strings + const char *scenefile = event->GetParameters(); + const char *name = event->GetParameters2(); + + // Have to find both strings + if ( scenefile && name ) + { + // Find the scene file + const flexsettinghdr_t *pExpHdr = ( const flexsettinghdr_t * )g_FlexSceneFileManager.FindSceneFile( this, scenefile, true ); + if ( pExpHdr ) + { + float scenetime = scene->GetTime(); + + float scale = event->GetIntensity( scenetime ); + + // Add the named expression + AddFlexSetting( name, scale, pExpHdr ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *expr - +// scale - +// *pSettinghdr - +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::AddFlexSetting( const char *expr, float scale, const flexsettinghdr_t *pSettinghdr ) +{ + int i; + const flexsetting_t *pSetting = NULL; + + // Find the named setting in the base + for ( i = 0; i < pSettinghdr->numflexsettings; i++ ) + { + pSetting = pSettinghdr->pSetting( i ); + if ( !pSetting ) + continue; + + const char *name = pSetting->pszName(); + + if ( !V_stricmp( name, expr ) ) + break; + } + + if ( i>=pSettinghdr->numflexsettings ) + { + return; + } + + flexweight_t *pWeights = NULL; + int truecount = pSetting->psetting( (byte *)pSettinghdr, 0, &pWeights ); + if ( !pWeights ) + return; + + for (i = 0; i < truecount; i++, pWeights++) + { + // Translate to local flex controller + // this is translating from the settings's local index to the models local index + int index = FlexControllerLocalToGlobal( pSettinghdr, pWeights->key ); + + // blend scaled weighting in to total (post networking g_flexweight!!!!) + float s = clamp( scale * pWeights->influence, 0.0f, 1.0f ); + m_RootMDL.m_MDL.m_pFlexControls[index] = m_RootMDL.m_MDL.m_pFlexControls[index] * (1.0f - s) + pWeights->weight * s; + + for ( int iMergeMDL=0; iMergeMDL<m_aMergeMDLs.Count(); ++iMergeMDL ) + { + m_aMergeMDLs[iMergeMDL].m_MDL.m_pFlexControls[index] = m_aMergeMDLs[iMergeMDL].m_MDL.m_pFlexControls[index] * (1.0f - s) + pWeights->weight * s; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Apply flexanimation to actor's face +// Input : *event - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::ProcessFlexAnimation( CChoreoScene *scene, CChoreoEvent *event ) +{ + Assert( event->GetType() == CChoreoEvent::FLEXANIMATION ); + + CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache ); + CStudioHdr *hdr = &studioHdr; + if ( !hdr ) + return; + + if ( !event->GetTrackLookupSet() ) + { + // Create lookup data + for ( int i = 0; i < event->GetNumFlexAnimationTracks(); i++ ) + { + CFlexAnimationTrack *track = event->GetFlexAnimationTrack( i ); + if ( !track ) + continue; + + if ( track->IsComboType() ) + { + char name[ 512 ]; + Q_strncpy( name, "right_" ,sizeof(name)); + Q_strncat( name, track->GetFlexControllerName(),sizeof(name), COPY_ALL_CHARACTERS ); + + track->SetFlexControllerIndex( MAX( FindFlexController( name ), LocalFlexController_t(0) ), 0, 0 ); + + Q_strncpy( name, "left_" ,sizeof(name)); + Q_strncat( name, track->GetFlexControllerName(),sizeof(name), COPY_ALL_CHARACTERS ); + + track->SetFlexControllerIndex( MAX( FindFlexController( name ), LocalFlexController_t(0) ), 0, 1 ); + } + else + { + track->SetFlexControllerIndex( MAX( FindFlexController( (char *)track->GetFlexControllerName() ), LocalFlexController_t(0)), 0 ); + } + } + + event->SetTrackLookupSet( true ); + } + + float scenetime = scene->GetTime(); + + float weight = event->GetIntensity( scenetime ); + + // Iterate animation tracks + for ( int i = 0; i < event->GetNumFlexAnimationTracks(); i++ ) + { + CFlexAnimationTrack *track = event->GetFlexAnimationTrack( i ); + if ( !track ) + continue; + + // Disabled + if ( !track->IsTrackActive() ) + continue; + + // Map track flex controller to global name + if ( track->IsComboType() ) + { + for ( int side = 0; side < 2; side++ ) + { + LocalFlexController_t controller = track->GetRawFlexControllerIndex( side ); + + // Get spline intensity for controller + float flIntensity = track->GetIntensity( scenetime, side ); + if ( controller >= LocalFlexController_t(0) ) + { + float orig = GetFlexWeight( controller ); + float value = orig * (1 - weight) + flIntensity * weight; + SetFlexWeight( controller, value ); + } + } + } + else + { + LocalFlexController_t controller = track->GetRawFlexControllerIndex( 0 ); + + // Get spline intensity for controller + float flIntensity = track->GetIntensity( scenetime, 0 ); + if ( controller >= LocalFlexController_t(0) ) + { + float orig = GetFlexWeight( controller ); + float value = orig * (1 - weight) + flIntensity * weight; + SetFlexWeight( controller, value ); + } + } + } +} + +extern ConVar g_CV_PhonemeDelay; +extern ConVar g_CV_PhonemeFilter; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::ProcessVisemes( Emphasized_Phoneme *classes ) +{ + // Any sounds being played? + if ( !MouthInfo().IsActive() ) + return; + + // Multiple phoneme tracks can overlap, look across all such tracks. + for ( int source = 0 ; source < MouthInfo().GetNumVoiceSources(); source++ ) + { + CVoiceData *vd = MouthInfo().GetVoiceSource( source ); + if ( !vd || vd->ShouldIgnorePhonemes() ) + continue; + + CSentence *sentence = engine->GetSentence( vd->GetSource() ); + if ( !sentence ) + continue; + + float sentence_length = engine->GetSentenceLength( vd->GetSource() ); + float timesincestart = vd->GetElapsedTime(); + + // This sound should be done...why hasn't it been removed yet??? + if ( timesincestart >= ( sentence_length + 2.0f ) ) + continue; + + // Adjust actual time + float t = timesincestart - g_CV_PhonemeDelay.GetFloat(); + + // Get box filter duration + float dt = g_CV_PhonemeFilter.GetFloat(); + + // Streaming sounds get an additional delay... + /* + // Tracker 20534: Probably not needed any more with the async sound stuff that + // we now have (we don't have a disk i/o hitch on startup which might have been + // messing up the startup timing a bit ) + bool streaming = engine->IsStreaming( vd->m_pAudioSource ); + if ( streaming ) + { + t -= g_CV_PhonemeDelayStreaming.GetFloat(); + } + */ + + // Assume sound has been playing for a while... + bool juststarted = false; + + // Get intensity setting for this time (from spline) + float emphasis_intensity = sentence->GetIntensity( t, sentence_length ); + + // Blend and add visemes together + AddVisemesForSentence( classes, emphasis_intensity, sentence, t, dt, juststarted ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::AddVisemesForSentence( Emphasized_Phoneme *classes, float emphasis_intensity, CSentence *sentence, float t, float dt, bool juststarted ) +{ + int pcount = sentence->GetRuntimePhonemeCount(); + for ( int k = 0; k < pcount; k++ ) + { + const CBasePhonemeTag *phoneme = sentence->GetRuntimePhoneme( k ); + + if (t > phoneme->GetStartTime() && t < phoneme->GetEndTime()) + { + bool bCrossfade = true; + if (bCrossfade) + { + if (k < pcount-1) + { + const CBasePhonemeTag *next = sentence->GetRuntimePhoneme( k + 1 ); + // if I have a neighbor + if ( next ) + { + // and they're touching + if (next->GetStartTime() == phoneme->GetEndTime() ) + { + // no gap, so increase the blend length to the end of the next phoneme, as long as it's not longer than the current phoneme + dt = MAX( dt, MIN( next->GetEndTime() - t, phoneme->GetEndTime() - phoneme->GetStartTime() ) ); + } + else + { + // dead space, so increase the blend length to the start of the next phoneme, as long as it's not longer than the current phoneme + dt = MAX( dt, MIN( next->GetStartTime() - t, phoneme->GetEndTime() - phoneme->GetStartTime() ) ); + } + } + else + { + // last phoneme in list, increase the blend length to the length of the current phoneme + dt = MAX( dt, phoneme->GetEndTime() - phoneme->GetStartTime() ); + } + } + } + } + + float t1 = ( phoneme->GetStartTime() - t) / dt; + float t2 = ( phoneme->GetEndTime() - t) / dt; + + if (t1 < 1.0 && t2 > 0) + { + float scale; + + // clamp + if (t2 > 1) + t2 = 1; + if (t1 < 0) + t1 = 0; + + // FIXME: simple box filter. Should use something fancier + scale = (t2 - t1); + + AddViseme( classes, emphasis_intensity, phoneme->GetPhonemeCode(), scale, juststarted ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *classes - +// phoneme - +// scale - +// newexpression - +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::AddViseme( Emphasized_Phoneme *classes, float emphasis_intensity, int phoneme, float scale, bool newexpression ) +{ + CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache ); + CStudioHdr *hdr = &studioHdr; + if ( !hdr ) + return; + + int type; + + // Setup weights for any emphasis blends + bool skip = SetupEmphasisBlend( classes, phoneme ); + + phoneme = 230; + scale = 1.0; + + // Uh-oh, missing or unknown phoneme??? + if ( skip ) + { + return; + } + + // Compute blend weights + ComputeBlendedSetting( classes, emphasis_intensity ); + + for ( type = 0; type < NUM_PHONEME_CLASSES; type++ ) + { + Emphasized_Phoneme *info = &classes[ type ]; + if ( !info->valid || info->amount == 0.0f ) + continue; + + const flexsettinghdr_t *actual_flexsetting_header = info->base; + const flexsetting_t *pSetting = actual_flexsetting_header->pIndexedSetting( phoneme ); + if (!pSetting) + { + continue; + } + + flexweight_t *pWeights = NULL; + + int truecount = pSetting->psetting( (byte *)actual_flexsetting_header, 0, &pWeights ); + if ( pWeights ) + { + for ( int i = 0; i < truecount; i++) + { + // Translate to global controller number + int j = FlexControllerLocalToGlobal( actual_flexsetting_header, pWeights->key ); + // Add scaled weighting in + if ( pWeights->weight > 0 ) + { + m_RootMDL.m_MDL.m_pFlexControls[j] += info->amount * scale * pWeights->weight; + } + // Go to next setting + pWeights++; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: A lot of the one time setup and also resets amount to 0.0f default +// for strong/weak/normal tracks +// Returning true == skip this phoneme +// Input : *classes - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CTFPlayerModelPanel::SetupEmphasisBlend( Emphasized_Phoneme *classes, int phoneme ) +{ + int i; + + bool skip = false; + + for ( i = 0; i < NUM_PHONEME_CLASSES; i++ ) + { + Emphasized_Phoneme *info = &classes[ i ]; + + // Assume it's bogus + info->valid = false; + info->amount = 0.0f; + + // One time setup + if ( !info->basechecked ) + { + info->basechecked = true; + info->base = (flexsettinghdr_t *)g_FlexSceneFileManager.FindSceneFile( this, info->classname, false ); + } + info->exp = NULL; + if ( info->base ) + { + Assert( info->base->id == ('V' << 16) + ('F' << 8) + ('E') ); + info->exp = info->base->pIndexedSetting( phoneme ); + } + + if ( info->required && ( !info->base || !info->exp ) ) + { + skip = true; + break; + } + + if ( info->exp ) + { + info->valid = true; + } + } + + return skip; +} + +#define STRONG_CROSSFADE_START 0.60f +#define WEAK_CROSSFADE_START 0.40f + +//----------------------------------------------------------------------------- +// Purpose: +// Here's the formula +// 0.5 is neutral 100 % of the default setting +// Crossfade starts at STRONG_CROSSFADE_START and is full at STRONG_CROSSFADE_END +// If there isn't a strong then the intensity of the underlying phoneme is fixed at 2 x STRONG_CROSSFADE_START +// so we don't get huge numbers +// Input : *classes - +// emphasis_intensity - +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::ComputeBlendedSetting( Emphasized_Phoneme *classes, float emphasis_intensity ) +{ + // See which blends are available for the current phoneme + bool has_weak = classes[ PHONEME_CLASS_WEAK ].valid; + bool has_strong = classes[ PHONEME_CLASS_STRONG ].valid; + + // Better have phonemes in general + Assert( classes[ PHONEME_CLASS_NORMAL ].valid ); + + if ( emphasis_intensity > STRONG_CROSSFADE_START ) + { + if ( has_strong ) + { + // Blend in some of strong + float dist_remaining = 1.0f - emphasis_intensity; + float frac = dist_remaining / ( 1.0f - STRONG_CROSSFADE_START ); + + classes[ PHONEME_CLASS_NORMAL ].amount = (frac) * 2.0f * STRONG_CROSSFADE_START; + classes[ PHONEME_CLASS_STRONG ].amount = 1.0f - frac; + } + else + { + emphasis_intensity = MIN( emphasis_intensity, STRONG_CROSSFADE_START ); + classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity; + } + } + else if ( emphasis_intensity < WEAK_CROSSFADE_START ) + { + if ( has_weak ) + { + // Blend in some weak + float dist_remaining = WEAK_CROSSFADE_START - emphasis_intensity; + float frac = dist_remaining / ( WEAK_CROSSFADE_START ); + + classes[ PHONEME_CLASS_NORMAL ].amount = (1.0f - frac) * 2.0f * WEAK_CROSSFADE_START; + classes[ PHONEME_CLASS_WEAK ].amount = frac; + } + else + { + emphasis_intensity = MAX( emphasis_intensity, WEAK_CROSSFADE_START ); + classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity; + } + } + else + { + // Assume 0.5 (neutral) becomes a scaling of 1.0f + classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::InitPhonemeMappings( void ) +{ + CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache ); + if ( studioHdr.IsValid() ) + { + char szBasename[MAX_PATH]; + Q_StripExtension( studioHdr.pszName(), szBasename, sizeof( szBasename ) ); + + char szExpressionName[MAX_PATH]; + Q_snprintf( szExpressionName, sizeof( szExpressionName ), "%s/phonemes/phonemes", szBasename ); + if ( g_FlexSceneFileManager.FindSceneFile( this, szExpressionName, false ) ) + { + SetupMappings( szExpressionName ); + return; + } + } + + SetupMappings( "phonemes" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::SetupMappings( char const *pchFileRoot ) +{ + // Fill in phoneme class lookup + memset( m_PhonemeClasses, 0, sizeof( m_PhonemeClasses ) ); + + Emphasized_Phoneme *normal = &m_PhonemeClasses[ PHONEME_CLASS_NORMAL ]; + Q_snprintf( normal->classname, sizeof( normal->classname ), "%s", pchFileRoot ); + normal->required = true; + + Emphasized_Phoneme *weak = &m_PhonemeClasses[ PHONEME_CLASS_WEAK ]; + Q_snprintf( weak->classname, sizeof( weak->classname ), "%s_weak", pchFileRoot ); + Emphasized_Phoneme *strong = &m_PhonemeClasses[ PHONEME_CLASS_STRONG ]; + Q_snprintf( strong->classname, sizeof( strong->classname ), "%s_strong", pchFileRoot ); +} + +//----------------------------------------------------------------------------- +// Purpose: Since everyone shared a pSettinghdr now, we need to set up the localtoglobal mapping per entity, but +// we just do this in memory with an array of integers (could be shorts, I suppose) +// Input : *pSettinghdr - +//----------------------------------------------------------------------------- +void CTFPlayerModelPanel::EnsureTranslations( const flexsettinghdr_t *pSettinghdr ) +{ + Assert( pSettinghdr ); + + FS_LocalToGlobal_t entry( pSettinghdr ); + + unsigned short idx = m_LocalToGlobal.Find( entry ); + if ( idx != m_LocalToGlobal.InvalidIndex() ) + return; + + entry.SetCount( pSettinghdr->numkeys ); + + for ( int i = 0; i < pSettinghdr->numkeys; ++i ) + { + entry.m_Mapping[ i ] = C_BaseFlex::AddGlobalFlexController( pSettinghdr->pLocalName( i ) ); + } + + m_LocalToGlobal.Insert( entry ); +} + +//----------------------------------------------------------------------------- +// Purpose: Look up instance specific mapping +// Input : *pSettinghdr - +// key - +// Output : int +//----------------------------------------------------------------------------- +int CTFPlayerModelPanel::FlexControllerLocalToGlobal( const flexsettinghdr_t *pSettinghdr, int key ) +{ + FS_LocalToGlobal_t entry( pSettinghdr ); + + int idx = m_LocalToGlobal.Find( entry ); + if ( idx == m_LocalToGlobal.InvalidIndex() ) + { + // This should never happen!!! + Assert( 0 ); + Warning( "Unable to find mapping for flexcontroller %i, settings %p on CTFPlayerModelPanel\n", key, pSettinghdr ); + EnsureTranslations( pSettinghdr ); + idx = m_LocalToGlobal.Find( entry ); + if ( idx == m_LocalToGlobal.InvalidIndex() ) + { + Error( "CTFPlayerModelPanel::FlexControllerLocalToGlobal failed!\n" ); + } + } + + FS_LocalToGlobal_t& result = m_LocalToGlobal[ idx ]; + // Validate lookup + Assert( result.m_nCount != 0 && key < result.m_nCount ); + int index = result.m_Mapping[ key ]; + return index; +} + diff --git a/game/client/tf/vgui/tf_playermodelpanel.h b/game/client/tf/vgui/tf_playermodelpanel.h new file mode 100644 index 0000000..e14375b --- /dev/null +++ b/game/client/tf/vgui/tf_playermodelpanel.h @@ -0,0 +1,222 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_PLAYERMODELPANEL_H +#define TF_PLAYERMODELPANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "basemodel_panel.h" +#include "ichoreoeventcallback.h" + +class CChoreoScene; + +extern CMouthInfo g_ClientUIMouth; + +// A model panel that knows how to imitate a TF2 player, including wielding/wearing unlockable items. +class CTFPlayerModelPanel : public CBaseModelPanel, public IChoreoEventCallback, public IHasLocalToGlobalFlexSettings, public IModelLoadCallback +{ + DECLARE_CLASS_SIMPLE( CTFPlayerModelPanel, CBaseModelPanel ); +public: + CTFPlayerModelPanel( vgui::Panel *pParent, const char *pName ); + ~CTFPlayerModelPanel( void ); + + void ApplySettings( KeyValues *inResourceData ); + + void SetToPlayerClass( int iClass, bool bIsRobot, bool bForceRefresh = false ); + bool HoldItemInSlot( int iSlot ); + bool HoldItem( int iItemNumber ); + void SwitchHeldItemTo( CEconItemView *pItem ); + void EquipRequiredLoadoutSlot( int iRequiredLoadoutSlot ); + CEconItemView *GetHeldItem() { return m_pHeldItem; } + + int AddCarriedItem( CEconItemView *pItem ); + void ClearCarriedItems( void ); + + void PlayVCD( const char *pszVCD, const char *pszWeaponEntityRequired = NULL, bool bLoopVCD = true, bool bFileNameOnly = true ); + + // Handle animation events + virtual void FireEvent( const char *pszEventName, const char *pszEventOptions ); + + const CUtlVector<CEconItemView*> &GetCarriedItems() { return m_ItemsToCarry; } + int GetNumCarriedItems() const { return m_ItemsToCarry.Count(); } + int GetPlayerClass() const { return m_iCurrentClassIndex; } + Vector GetZoomOffset(); + + void ToggleZoom(); + bool IsZoomed() { return m_bZoomedToHead; } + + void SetTeam( int iTeam ); + int GetTeam( void ) { return m_iTeam; } + + void UpdatePreviewVisuals( void ); + + // From IChoreoEventCallback + virtual void StartEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ); + virtual void EndEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ); + virtual void ProcessEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ); + virtual bool CheckEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ); + virtual void SetupFlexWeights( void ); + + // IHasLocalToGlobalFlexSettings + virtual void EnsureTranslations( const flexsettinghdr_t *pSettinghdr ); + int FlexControllerLocalToGlobal( const flexsettinghdr_t *pSettinghdr, int key ); + + // IModelLoadCallback + virtual void OnModelLoadComplete( const model_t *pModel ); + + void SetEyeGlowEffect ( const char *pEffectName, Vector vColor1, Vector vColor2, bool bForceUpdate, bool bPlaySparks ); + + void InvalidateParticleEffects(); + +protected: + // From CBaseModelPanel + virtual void PrePaint3D( IMatRenderContext *pRenderContext ) OVERRIDE; + virtual void PostPaint3D( IMatRenderContext *pRenderContext ) OVERRIDE; + virtual void RenderingRootModel( IMatRenderContext *pRenderContext, CStudioHdr *pStudioHdr, MDLHandle_t mdlHandle, matrix3x4_t *pWorldMatrix ); + virtual void RenderingMergedModel( IMatRenderContext *pRenderContext, CStudioHdr *pStudioHdr, MDLHandle_t mdlHandle, matrix3x4_t *pWorldMatrix ); + virtual IMaterial* GetOverrideMaterial( MDLHandle_t mdlHandle ) OVERRIDE; + +private: + + enum modelpanel_particle_system_t + { + SYSTEM_HEAD = 0, + SYSTEM_MISC1, + SYSTEM_MISC2, + SYSTEM_WEAPON, // there can only be one weapon equipped + SYSTEM_ACTIONSLOT, + SYSTEM_EYEGLOW_LEFT, + SYSTEM_EYEGLOW_RIGHT, + SYSTEM_EYESPARK_LEFT, + SYSTEM_EYESPARK_RIGHT, + SYSTEM_TAUNT, + SYSTEM_COUNT, + }; + + // Choreo Scene handling + void ClearScene( void ); + void ProcessSequence( CChoreoScene *scene, CChoreoEvent *event ); + void ProcessExpression( CChoreoScene *scene, CChoreoEvent *event ); + void ProcessFlexSettingSceneEvent( CChoreoScene *scene, CChoreoEvent *event ); + void ProcessLoop( CChoreoScene *scene, CChoreoEvent *event ); + void AddFlexSetting( const char *expr, float scale, const flexsettinghdr_t *pSettinghdr ); + void ProcessFlexAnimation( CChoreoScene *scene, CChoreoEvent *event ); + void SetFlexWeight( LocalFlexController_t index, float value ); + float GetFlexWeight( LocalFlexController_t index ); + + LocalFlexController_t GetNumFlexControllers( void ); + const char *GetFlexDescFacs( int iFlexDesc ); + const char *GetFlexControllerName( LocalFlexController_t iFlexController ); + const char *GetFlexControllerType( LocalFlexController_t iFlexController ); + LocalFlexController_t FindFlexController( const char *szName ); + + // Mouth processing + CMouthInfo& MouthInfo() { return g_ClientUIMouth; } + void ProcessVisemes( Emphasized_Phoneme *classes ); + void AddVisemesForSentence( Emphasized_Phoneme *classes, float emphasis_intensity, CSentence *sentence, float t, float dt, bool juststarted ); + void AddViseme( Emphasized_Phoneme *classes, float emphasis_intensity, int phoneme, float scale, bool newexpression ); + bool SetupEmphasisBlend( Emphasized_Phoneme *classes, int phoneme ); + void ComputeBlendedSetting( Emphasized_Phoneme *classes, float emphasis_intensity ); + void InitPhonemeMappings( void ); + void SetupMappings( char const *pchFileRoot ); + + void HoldFirstValidItem( void ); + void EquipAllWearables( CEconItemView *pHeldItem ); + void EquipItem( CEconItemView *pItem ); + bool UpdateHeldItem( int iDesiredSlot ); + void UpdateWeaponBodygroups( bool bModifyDeployedOnlyBodygroups ); + void UpdateHiddenBodyGroups( CEconItemView* pItem ); + CEconItemView *GetItemInSlot( int iSlot ); + CEconItemView *GetPreviewItem( CEconItemView *pMatchItem ); + + // Use this instead of SetMergeModel() - handles dynamic asset allocation + void LoadAndAttachAdditionalModel( const char *pMDLName, CEconItemView *pItem ); + bool FinishAttachAdditionalModel( const model_t *pModel ); + void RemoveAdditionalModels( void ); + + bool UpdateCosmeticParticles( + IMatRenderContext *pRenderContext, + CStudioHdr *pStudioHdr, + MDLHandle_t mdlHandle, + matrix3x4_t *pWorldMatrix, + modelpanel_particle_system_t iSystem, + CEconItemView *pEconItem + ); + + void UpdateEyeGlows( IMatRenderContext *pRenderContext, CStudioHdr *pStudioHdr, MDLHandle_t mdlHandle, matrix3x4_t *pWorldMatrix, bool bIsRightEye ); + void UpdateActionSlotEffects( IMatRenderContext *pRenderContext, CStudioHdr *pStudioHdr, MDLHandle_t mdlHandle, matrix3x4_t *pWorldMatrix ); + void UpdateTauntEffects( IMatRenderContext *pRenderContext, CStudioHdr *pStudioHdr, MDLHandle_t mdlHandle, matrix3x4_t *pWorldMatrix ); + + int m_iCurrentClassIndex; + int m_iCurrentSlotIndex; + CUtlVector<CEconItemView*> m_ItemsToCarry; // Items that our player should be seen carrying + QAngle m_angPlayerOrg; + + int m_nBody; + int m_iTeam; + bool m_bZoomedToHead; + + MDLHandle_t m_MergeMDL; + + CEconItemView *m_pHeldItem; + + const char *m_pszVCD; + const char *m_pszWeaponEntityRequired; + bool m_bLoopVCD; + bool m_bVCDFileNameOnly; + + CChoreoScene *m_pScene; + float m_flSceneTime; + float m_flSceneEndTime; + float m_flLastTickTime; + bool m_bLoopScene; + + CUtlVector< CRefCountedModelIndex > m_vecDynamicAssetsLoaded; + CUtlVector< CEconItemView* > m_vecItemsLoaded; + + struct CustomClassData_t + { + float m_flFOV; + Vector m_vPosition; + QAngle m_vAngles; + }; + CUtlVector< CustomClassData_t > m_customClassData; + + // Choreo scenes + bool m_bShouldRunFlexEvents; + float m_flexWeight[ MAXSTUDIOFLEXCTRL ]; + Emphasized_Phoneme m_PhonemeClasses[ NUM_PHONEME_CLASSES ]; + CUtlRBTree< FS_LocalToGlobal_t, unsigned short > m_LocalToGlobal; + + // The particle system to draw + particle_data_t *m_aParticleSystems[ SYSTEM_COUNT ]; + + bool m_bUpdateEyeGlows; + bool m_bPlaySparks; + + char m_pszEyeGlowParticleName[MAX_PATH]; + Vector m_vEyeGlowColor1; + Vector m_vEyeGlowColor2; + bool m_bDrawActionSlotEffects; + bool m_bDrawTauntParticles; + + float m_flTauntParticleRefireTime; + float m_flTauntParticleRefireRate; + + bool m_bIsRobot; + + CPanelAnimationVar( bool, m_bDisableSpeakEvent, "disable_speak_event", "0" ); + + CEconItemView *GetLoadoutItemFromMDLHandle( loadout_positions_t iPosition, MDLHandle_t mdlHandle ); + bool RenderStatTrack( CStudioHdr *pStudioHdr, matrix3x4_t *pWorldMatrix ); + MDLData_t m_StatTrackModel; + float m_flStatTrackScale; +}; + +#endif // TF_PLAYERMODELPANEL_H diff --git a/game/client/tf/vgui/tf_playerpanel.cpp b/game/client/tf/vgui/tf_playerpanel.cpp new file mode 100644 index 0000000..c541922 --- /dev/null +++ b/game/client/tf/vgui/tf_playerpanel.cpp @@ -0,0 +1,411 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "hud.h" +#include "c_team.h" +#include "tf_shareddefs.h" +#include "tf_gamerules.h" +#include "tf_hud_objectivestatus.h" +#include "tf_hud_statpanel.h" +#include "iclientmode.h" +#include "c_playerresource.h" +#include "tf_hud_building_status.h" +#include "tf_hud_mann_vs_machine_status.h" +#include "tf_hud_tournament.h" +#include "tf_hud_winpanel.h" +#include "tf_tips.h" +#include "tf_mapinfomenu.h" +#include "econ_wearable.h" +#include "c_tf_playerresource.h" +#include "playerspawncache.h" +#include "econ_notifications.h" +#include <spectatorgui.h> +#include "hudelement.h" +#include "tf_spectatorgui.h" +#include "tf_hud_playerstatus.h" +#include "item_model_panel.h" +#include "tf_gamerules.h" +#include "tf_playerpanel.h" + +#include "tf_gc_client.h" +#include "tf_lobby_server.h" + +#include <vgui/ILocalize.h> +#include <vgui/ISurface.h> +#include <VGuiMatSurface/IMatSystemSurface.h> +#include "vgui_avatarimage.h" + +using namespace vgui; + +extern ConVar tf_max_health_boost; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFPlayerPanel::CTFPlayerPanel( vgui::Panel *parent, const char *name ) : vgui::EditablePanel( parent, name ) +{ + m_pHealthIcon = new CTFPlayerPanelGUIHealth( this, "HealthIcon" ); + m_pClassImage = NULL; + m_bPlayerReadyModeActive = false; + m_pReadyBG = new ScalableImagePanel( this , "ReadyBG" ); + m_pReadyImage = new ImagePanel( this, "ReadyImage" ); + + SetDialogVariable( "chargeamount", "" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerPanel::Reset( void ) +{ + m_pHealthIcon->Reset(); + m_iPrevHealth = -999; + m_iPrevClass = -999; + m_bPrevAlive = false; + m_iPrevRespawnWait = -999; + m_iPrevCharge = -1; + m_bPrevReady = true; + m_iPrevState = GR_STATE_PREGAME; + m_bPlayerReadyModeActive = false; + m_nGCTeam = TEAM_INVALID; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFPlayerPanel::Update( void ) +{ + if ( !g_TF_PR || !TFGameRules() ) + return false; + + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if ( !pLocalPlayer ) + return false; + + bool bChanged = false; + bool bObserver = pLocalPlayer->GetObserverMode() != OBS_MODE_NONE; + bool bVisible = GetTeam() >= FIRST_GAME_TEAM; + int iRespawnWait = -1; + m_bPlayerReadyModeActive = ( !bObserver && + TFGameRules()->UsePlayerReadyStatusMode() && + TFGameRules()->State_Get() == GR_STATE_BETWEEN_RNDS ); + + CTFGSLobby *pLobby = GTFGCClientSystem()->GetLobby(); + if ( pLobby ) + { + const CTFLobbyMember *pMember = pLobby->GetMemberDetails( m_steamID ); + if ( pMember ) + { + // Keep this updated + m_nGCTeam = pMember->team(); + + if ( !m_iPlayerIndex && pMember->has_last_connect_time() ) + { + iRespawnWait = CRTime::RTime32DateAdd( pMember->last_connect_time(), 180, k_ETimeUnitSecond ) - CRTime::RTime32TimeCur(); + if ( iRespawnWait <= 0 ) + iRespawnWait = -1; + } + } + } + + if ( IsVisible() != bVisible ) + { + SetVisible( bVisible ); + bChanged = true; + } + + if ( bVisible ) + { + if ( g_TF_PR ) + { + // Are we connected with a class? + Assert( TF_CLASS_UNDEFINED == 0 ); + int iClass = -1; + bool bAlive = false; + const int k_Health_Dead = -1; + const int k_Health_NotINGame = -2; + int iHealth = k_Health_NotINGame; + if ( m_iPlayerIndex > 0 ) + { + iClass = g_TF_PR->GetPlayerClass( m_iPlayerIndex ); + + if ( iClass != TF_CLASS_UNDEFINED ) + { + bAlive = g_TF_PR->IsAlive( m_iPlayerIndex ); + iHealth = k_Health_Dead; + } + if ( bAlive ) + iHealth = g_TF_PR->GetHealth( m_iPlayerIndex ); + + // Calc respawn time remaining + if ( iClass != TF_CLASS_UNDEFINED && !bAlive ) + { + float flRespawnAt = g_TF_PR->GetNextRespawnTime( m_iPlayerIndex ); + iRespawnWait = (flRespawnAt - gpGlobals->curtime); + if ( iRespawnWait <= 0 ) + iRespawnWait = -1; + } + + // Hide class info from the other team? + if ( !bObserver && + TFGameRules()->IsCompetitiveMode() && + GetTeam() != g_TF_PR->GetTeam( pLocalPlayer->entindex() ) ) + { + iClass = TF_CLASS_UNDEFINED; + } + } + + // Update live state + if ( m_pClassImage ) + { + if ( m_bPrevAlive != bAlive ) + { + bChanged = true; + m_bPrevAlive = bAlive; + if ( bAlive ) + { + m_pClassImage->SetDrawColor( Color( 255, 255, 255, 255 ) ); + } + else + { + m_pClassImage->SetDrawColor( Color( 96, 96, 96, 255 ) ); + } + + UpdateBorder(); + } + + // Update class image + if ( m_iPrevClass != iClass ) + { + bChanged = true; + m_iPrevClass = iClass; + if ( iClass < 0 ) + { + m_pClassImage->SetImage( "hud_connecting" ); + } + else if ( iClass == TF_CLASS_UNDEFINED ) + { + m_pClassImage->SetImage( "hud_class_not_chosen" ); + } + else + { + m_pClassImage->SetImage( VarArgs( "%s_alpha", ( GetTeam() == TF_TEAM_RED ) ? g_pszItemClassImagesRed[iClass] : g_pszItemClassImagesBlue[iClass] ) ); + } + } + } + + // update health indicator + if ( iHealth != m_iPrevHealth && m_pHealthIcon ) + { + m_iPrevHealth = iHealth; + if ( iHealth == k_Health_NotINGame ) + { + m_pHealthIcon->SetVisible( false ); + } + else + { + m_pHealthIcon->SetVisible( true ); + if ( iHealth < 0 ) + { + Assert( iHealth == k_Health_Dead ); + m_pHealthIcon->SetHealth( -1, 1, 1 ); + } + else + { + float flMaxHealth = g_TF_PR->GetMaxHealth( m_iPlayerIndex ); + float iMaxBuffedHealth = flMaxHealth * tf_max_health_boost.GetFloat(); // Hacky, but it'll work. + m_pHealthIcon->SetHealth( iHealth, flMaxHealth, iMaxBuffedHealth ); + bChanged = true; + } + } + } + + // Update respawn time text + if ( iRespawnWait != m_iPrevRespawnWait ) + { + m_iPrevRespawnWait = iRespawnWait; + if ( iRespawnWait < 0 ) + { + SetDialogVariable( "respawntime", "" ); + } + else + { + SetDialogVariable( "respawntime", VarArgs( "%d s", iRespawnWait ) ); + bChanged = true; + } + } + + bool bReadyMode = TFGameRules()->UsePlayerReadyStatusMode(); + + int iCharge = ( iClass == TF_CLASS_MEDIC ) ? g_TF_PR->GetChargeLevel( m_iPlayerIndex ) : 0; + if ( iCharge != m_iPrevCharge ) + { + if ( iCharge > 0 && !( bReadyMode && !bObserver ) ) + { + SetDialogVariable( "chargeamount", VarArgs( "%d%%", iCharge ) ); + bChanged = true; + } + else + { + SetDialogVariable( "chargeamount", "" ); + } + m_iPrevCharge = iCharge; + } + + if ( bReadyMode ) + { + bool bPlayerReady = false; + + if ( m_bPlayerReadyModeActive ) + { + if ( m_iPlayerIndex && g_TF_PR->IsConnected( m_iPlayerIndex ) ) + { + bPlayerReady = TFGameRules()->IsPlayerReady( m_iPlayerIndex ); + } + } + + if ( m_pReadyImage && ( m_pReadyImage->IsVisible() != bPlayerReady ) ) + { + m_pReadyImage->SetVisible( bPlayerReady ); + } + + if ( m_pHealthIcon && ( m_pHealthIcon->IsVisible() == m_bPlayerReadyModeActive ) ) + { + m_pHealthIcon->SetVisible( !m_bPlayerReadyModeActive ); + } + + if ( m_pReadyBG && ( m_pReadyBG->IsVisible() != m_bPlayerReadyModeActive ) ) + { + m_pReadyBG->SetVisible( m_bPlayerReadyModeActive ); + } + + if ( TFGameRules()->State_Get() != m_iPrevState ) + { + bChanged = true; + } + m_iPrevState = TFGameRules()->State_Get(); + } + } + } + + return bChanged; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + m_pClassImage = dynamic_cast<CTFClassImage*>( FindChildByName( "classimage" ) ); + + Reset(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerPanel::SetPlayerIndex( int iIndex ) +{ + if ( iIndex <= 0 ) + { + Setup( iIndex, CSteamID(), "" ); + } + else + { + Setup( iIndex, GetSteamIDForPlayerIndex( iIndex ), g_TF_PR->GetPlayerName( iIndex ) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerPanel::Setup( int iPlayerIndex, CSteamID steamID, const char *pszPlayerName, int nLobbyTeam /*= TEAM_INVALID*/ ) +{ + if ( pszPlayerName == NULL ) + pszPlayerName = ""; + if ( m_iPlayerIndex != iPlayerIndex + || m_steamID != steamID + || Q_strcmp( m_sPlayerName, pszPlayerName ) ) + { + Reset(); + m_iPlayerIndex = iPlayerIndex; + m_steamID = steamID; + m_sPlayerName = pszPlayerName; + SetDialogVariable( "playername", m_sPlayerName ); + m_nGCTeam = nLobbyTeam; + } + + if ( m_iPlayerIndex > 0 || m_steamID.IsValid() ) + { + UpdateBorder(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerPanel::SetSpecIndex( int iIndex ) +{ + m_iSpecIndex = iIndex; + + if ( m_iSpecIndex > 0 && m_iSpecIndex <= 12 && !m_bPlayerReadyModeActive ) + { + SetDialogVariable( "specindex", VarArgs( "%d", m_iSpecIndex ) ); + } + else + { + SetDialogVariable( "specindex", "" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFPlayerPanel::UpdateBorder( void ) +{ + if ( !g_TF_PR ) + return; + + vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); + if ( !m_bPrevAlive ) + { + SetBorder( pScheme->GetBorder("TFFatLineBorder") ); + } + else + { + if ( GetTeam() == TF_TEAM_RED ) + { + SetBorder( pScheme->GetBorder( "TFFatLineBorderRedBG" ) ); + } + else + { + SetBorder( pScheme->GetBorder( "TFFatLineBorderBlueBG" ) ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CTFPlayerPanel::GetTeam( void ) +{ + if ( m_nGCTeam != TEAM_INVALID && TFGameRules() ) + { + return TFGameRules()->GetGameTeamForGCTeam( (TF_GC_TEAM)m_nGCTeam ); + } + else if ( GetPlayerIndex() && g_TF_PR ) + { + return g_TF_PR->GetTeam( GetPlayerIndex() ); + } + + return TEAM_INVALID; +} + + diff --git a/game/client/tf/vgui/tf_playerpanel.h b/game/client/tf/vgui/tf_playerpanel.h new file mode 100644 index 0000000..96bc3ab --- /dev/null +++ b/game/client/tf/vgui/tf_playerpanel.h @@ -0,0 +1,78 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef TF_PLAYERPANEL_H +#define TF_PLAYERPANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_spectatorgui.h" + +//----------------------------------------------------------------------------- +// Purpose: Custom health panel used to show spectator target's health in Tournament HUD +//----------------------------------------------------------------------------- +class CTFPlayerPanelGUIHealth : public CTFSpectatorGUIHealth +{ +public: + CTFPlayerPanelGUIHealth( Panel *parent, const char *name ) : CTFSpectatorGUIHealth( parent, name ) + { + } + + virtual const char *GetResFilename( void ) + { + return "resource/UI/SpectatorTournamentGUIHealth.res"; + } +}; + +//----------------------------------------------------------------------------- +// Purpose: A panel representing a player, shown in tournament mode Spec GUI +//----------------------------------------------------------------------------- +class CTFPlayerPanel : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CTFPlayerPanel, vgui::EditablePanel ); +public: + CTFPlayerPanel( vgui::Panel *parent, const char *name ); + + virtual void Reset( void ); + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + + virtual bool Update( void ); + void SetPlayerIndex( int iIndex ); + int GetPlayerIndex( void ) { return m_iPlayerIndex; } + void Setup( int iPlayerIndex, CSteamID steamID, const char *pszPlayerName, int nLobbyTeam = TEAM_INVALID ); + void SetSpecIndex( int iIndex ); + int GetSpecIndex( void ) { return m_iSpecIndex; } + virtual void UpdateBorder( void ); + int GetTeam( void ); + + inline CSteamID GetSteamID() const { return m_steamID; } + +protected: + int m_iPlayerIndex; + CUtlString m_sPlayerName; + CSteamID m_steamID; + int m_iSpecIndex; + CTFClassImage *m_pClassImage; + CTFPlayerPanelGUIHealth *m_pHealthIcon; + int m_iPrevHealth; + bool m_bPrevAlive; + int m_iPrevClass; + int m_iPrevRespawnWait; + int m_iPrevCharge; + vgui::ScalableImagePanel *m_pReadyBG; + vgui::ImagePanel *m_pReadyImage; + + // Ready state + bool m_bPrevReady; + int m_iPrevState; + bool m_bPlayerReadyModeActive; + int m_nGCTeam; +}; + + + +#endif // TF_PLAYERPANEL_H diff --git a/game/client/tf/vgui/tf_pvp_rank_panel.cpp b/game/client/tf/vgui/tf_pvp_rank_panel.cpp new file mode 100644 index 0000000..70b6249 --- /dev/null +++ b/game/client/tf/vgui/tf_pvp_rank_panel.cpp @@ -0,0 +1,762 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#include "cbase.h" + +#include "tf_pvp_rank_panel.h" +#include "tf_matchmaking_shared.h" +#include "basemodel_panel.h" +#include "vgui_controls/ProgressBar.h" +#include "tf_ladder_data.h" +#include "iclientmode.h" +#include <vgui_controls/AnimationController.h> +#include "tf_controls.h" +#include "vgui/ISurface.h" +#include "animation.h" +#include "clientmode_tf.h" +#include "vgui/IInput.h" +#include "vgui_controls/MenuItem.h" +#include "tf_gc_client.h" +#include "tf_xp_source.h" + +using namespace vgui; + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +#ifdef STAGING_ONLY + extern ConVar tf_test_pvp_rank_xp_change; +#endif + +ConVar tf_xp_breakdown_interval( "tf_xp_breakdown_interval", "1.85", FCVAR_DEVELOPMENTONLY ); +ConVar tf_xp_breakdown_lifetime( "tf_xp_breakdown_lifetime", "3.5", FCVAR_DEVELOPMENTONLY ); + +extern const char *s_pszMatchGroups[]; + +CPvPRankPanel::XPState_t::XPState_t( EMatchGroup eMatchGroup ) + : m_nStartXP( 0u ) + , m_nTargetXP( 0u ) + , m_nActualXP( 0u ) + , m_bCurrentDeltaViewed( true ) + , m_eMatchGroup( eMatchGroup ) +{ + Assert( m_eMatchGroup != k_nMatchGroup_Invalid ); + + // Default to level 1's starting XP value + const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( m_eMatchGroup ); + if ( pMatchDesc && pMatchDesc->m_pProgressionDesc ) + { + auto level = pMatchDesc->m_pProgressionDesc->GetLevelByNumber( 1 ); + m_nStartXP = level.m_nStartXP; + m_nActualXP = m_nStartXP; + m_nTargetXP = m_nStartXP; + } + + ListenForGameEvent( "experience_changed" ); + ListenForGameEvent( "server_spawn" ); +} + +void CPvPRankPanel::XPState_t::FireGameEvent( IGameEvent *pEvent ) +{ + if ( FStrEq( pEvent->GetName(), "experience_changed" ) ) // For changing tf_progression_set_xp_to_level + { + UpdateXP( false ); + } + else if ( FStrEq( pEvent->GetName(), "server_spawn" ) && GTFGCClientSystem()->BHaveLiveMatch() + && GTFGCClientSystem()->GetLiveMatchGroup() == m_eMatchGroup ) + { + UpdateXP( false ); + // Acknowledge any outstanding XP sources when we start a match. It looks really weird when + // you see old match xp sources at the end of a match. + GTFGCClientSystem()->AcknowledgePendingXPSources( m_eMatchGroup ); + } +} + +void CPvPRankPanel::XPState_t::UpdateXP( bool bInitial ) +{ + uint32 nStartXP = 0u; + uint32 nNewXP = 0u; + + const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( m_eMatchGroup ); + // Should only be creating this object for matches that have progressions + Assert( pMatchDesc && pMatchDesc->m_pProgressionDesc ); + if ( pMatchDesc && pMatchDesc->m_pProgressionDesc ) + { + // Default to level 1's lowest XP value + auto level = pMatchDesc->m_pProgressionDesc->GetLevelByNumber( 1 ); + nNewXP = level.m_nStartXP; + nStartXP = level.m_nStartXP; + + if ( steamapicontext && steamapicontext->SteamUser() ) + { + nStartXP = pMatchDesc->m_pProgressionDesc->GetLocalPlayerLastAckdExperience(); + // Get the actual user's XP + nNewXP = pMatchDesc->m_pProgressionDesc->GetPlayerExperienceBySteamID( steamapicontext->SteamUser()->GetSteamID() ); + } + } + + // If there's no change, don't do anything + if ( nNewXP != m_nActualXP || bInitial ) + { + m_nActualXP = nNewXP; + m_nStartXP = nStartXP; + m_bCurrentDeltaViewed = false; + } + + if ( bInitial ) + { + m_progressTimer.Start( 1.f ); + m_bCurrentDeltaViewed = true; + m_nTargetXP = m_nStartXP; + } +} + +uint32 CPvPRankPanel::XPState_t::GetCurrentXP() const +{ + float flTimeProgress = 0.f; + if ( m_progressTimer.HasStarted() ) + { + flTimeProgress = Clamp( m_progressTimer.GetElapsedTime(), 0.f, m_progressTimer.GetCountdownDuration() ); + flTimeProgress = Gain( flTimeProgress / m_progressTimer.GetCountdownDuration(), 0.9f ); + } + + return RemapValClamped( flTimeProgress, 0.f, 1.f, m_nStartXP, m_nTargetXP ); +} + +bool CPvPRankPanel::XPState_t::BeginXPDeltaLerp() +{ + if ( !m_bCurrentDeltaViewed ) + { + m_bCurrentDeltaViewed = true; + // Change our target + m_nTargetXP = m_nActualXP; + m_progressTimer.Start( 5.f ); + + return true; + } + + return false; +} + +void CPvPRankPanel::XPState_t::SOCreated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) +{ + if ( pObject->GetTypeID() == CSOTFLadderData::k_nTypeID ) + { + CSOTFLadderData* pLadderObject = (CSOTFLadderData*)pObject; + if( (EMatchGroup)pLadderObject->Obj().match_group() != m_eMatchGroup ) + return; + + // We'll get a eSOCacheEvent_Incremental when we're actually creating the + // first CSOTFLadderData object. We dont want that to come through as + // "initializing" because it'll skip the leveling effects + UpdateXP( eEvent == eSOCacheEvent_ListenerAdded ); + } + + if ( pObject->GetTypeID() == CXPSource::k_nTypeID ) + { + CXPSource *pXPSource = (CXPSource*)pObject; + if ( pXPSource->Obj().match_group() == m_eMatchGroup ) + { + UpdateXP( false ); + } + } +} + +void CPvPRankPanel::XPState_t::SOUpdated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) +{ + if ( pObject->GetTypeID() == CSOTFLadderData::k_nTypeID ) + { + CSOTFLadderData* pLadderObject = (CSOTFLadderData*)pObject; + if( (EMatchGroup)pLadderObject->Obj().match_group() != m_eMatchGroup ) + return; + + // If the GC comes on after we've already subscribed to the cache, + // eSOCacheEvent_Subscribed when the object is created. This one + // we want to skip the effects. + UpdateXP( eEvent == eSOCacheEvent_Subscribed ); + } + + if ( pObject->GetTypeID() == CXPSource::k_nTypeID ) + { + CXPSource *pXPSource = (CXPSource*)pObject; + if ( pXPSource->Obj().match_group() == m_eMatchGroup ) + { + UpdateXP( false ); + } + } +} + +class CMiniPvPRankPanel : public CPvPRankPanel +{ +public: + CMiniPvPRankPanel( Panel* pParent, const char* pszPanelName ) : CPvPRankPanel( pParent, pszPanelName ) + {} + +private: + virtual KeyValues* GetConditions() const OVERRIDE + { + KeyValues* pConditions = new KeyValues( "conditions" ); + pConditions->AddSubKey( new KeyValues( "if_mini" ) ); + + return pConditions; + } +}; + +class CXPSourcePanel : public vgui::EditablePanel +{ +public: + DECLARE_CLASS_SIMPLE( CXPSourcePanel , vgui::EditablePanel); + CXPSourcePanel( Panel *pParent, const char* panelName, const CMsgTFXPSource& source ) + : BaseClass( pParent, panelName ) + , m_source( source ) + {} + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE + { + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "resource/ui/XPSourcePanel.res" ); + } + + virtual void PerformLayout() OVERRIDE + { + BaseClass::PerformLayout(); + + static wchar_t wszOutString[ 128 ]; + wchar_t wszCount[ 16 ]; + _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", m_source.amount() ); + const wchar_t *wpszFormat = g_pVGuiLocalize->Find( g_XPSourceDefs[ m_source.type() ].m_pszFormattingLocToken ); + g_pVGuiLocalize->ConstructString_safe( wszOutString, wpszFormat, 2, g_pVGuiLocalize->Find( g_XPSourceDefs[ m_source.type() ].m_pszTypeLocToken ), wszCount ); + + SetDialogVariable( "source", wszOutString ); + } + +protected: + CMsgTFXPSource m_source; +}; + +class CScrollingXPSourcePanel : public CXPSourcePanel +{ +public: + DECLARE_CLASS_SIMPLE( CScrollingXPSourcePanel , CXPSourcePanel ); + CScrollingXPSourcePanel( Panel *pParent, const char* panelName, const CMsgTFXPSource& source, float flStartTime ) + : BaseClass( pParent, panelName, source ) + , m_flStartTime( flStartTime ) + , m_bStarted( false ) + { + vgui::ivgui()->AddTickSignal( GetVPanel() ); + + SetAutoDelete( false ); + } + + virtual ~CScrollingXPSourcePanel() + { + } + + virtual void OnTick() OVERRIDE + { + BaseClass::OnTick(); + + if ( Plat_FloatTime() > m_flStartTime && !m_bStarted ) + { + m_bStarted = true; + SetVisible( true ); + + // Do starting stuff + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, m_source.amount() >= 0 ? "XPSourceShow_Positive" : "XPSourceShow_Negative", false ); + + if ( g_XPSourceDefs[ m_source.type() ].m_pszSoundName ) + { + PlaySoundEntry( g_XPSourceDefs[ m_source.type() ].m_pszSoundName ); + } + } + + SetVisible( m_bStarted ); + + if ( Plat_FloatTime() > ( m_flStartTime + tf_xp_breakdown_lifetime.GetFloat() ) ) + { + // We're done! Delete ourselves + MarkForDeletion(); + } + } + +private: + + float m_flStartTime; + bool m_bStarted; +}; + +DECLARE_BUILD_FACTORY( CMiniPvPRankPanel ); +DECLARE_BUILD_FACTORY( CPvPRankPanel ); + +CPvPRankPanel::CPvPRankPanel( Panel *parent, const char *panelName ) + : BaseClass( parent, panelName ) + , m_eMatchGroup( k_nMatchGroup_Invalid ) + , m_pProgressionDesc( NULL ) + , m_pContinuousProgressBar( NULL ) + , m_pModelPanel( NULL ) + , m_pXPBar( NULL ) + , m_pBGPanel( NULL ) + , m_nLastLerpXP( 0 ) + , m_nLastSeenLevel( 0 ) + , m_bClicked( false ) +{ + ListenForGameEvent( "begin_xp_lerp" ); +} + +void CPvPRankPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + KeyValues* pConditions = GetConditions(); + + LoadControlSettings( GetResFile(), NULL, NULL, pConditions ); + + if ( pConditions ) + { + pConditions->deleteThis(); + pConditions = NULL; + } + + m_pBGPanel = FindControl< EditablePanel >( "BGPanel", true ); + m_pContinuousProgressBar = FindControl< ContinuousProgressBar >( "ContinuousProgressBar", true ); + m_pXPBar = FindControl< EditablePanel >( "XPBar", true ); + m_pModelPanel = FindControl< CBaseModelPanel >( "RankModel", true ); + m_pModelPanel->AddActionSignalTarget( this ); + + m_pModelButton = FindChildByName( "MedalButton", true ); + +#ifdef STAGING_ONLY + if ( m_pBGPanel ) + { + Panel* pDebugButton = m_pBGPanel->FindChildByName( "TestLevelDownButton", true ); + if ( pDebugButton ) { pDebugButton->SetVisible( true ); } + pDebugButton = m_pBGPanel->FindChildByName( "LevelDebugButton", true ); + if ( pDebugButton ) { pDebugButton->SetVisible( true ); } + pDebugButton = m_pBGPanel->FindChildByName( "TestLevelUpButton", true ); + if ( pDebugButton ) { pDebugButton->SetVisible( true ); } + } +#endif +} + +void CPvPRankPanel::ApplySettings(KeyValues *inResourceData) +{ + BaseClass::ApplySettings( inResourceData ); + + SetMatchGroup( (EMatchGroup)StringFieldToInt( inResourceData->GetString( "matchgroup" ), s_pszMatchGroups, (int)k_nMatchGroup_Count, false ) ); +} + +void CPvPRankPanel::PerformLayout() +{ + BaseClass::PerformLayout(); + + if ( !m_pProgressionDesc ) + return; + + EditablePanel* pStatsContainer = FindControl< EditablePanel >( "Stats", true ); + if ( !pStatsContainer ) + return; + + if ( !m_pModelPanel ) + return; + + if ( !m_pXPBar ) + return; + + ProgressBar* pProgressBar = FindControl< ProgressBar > ( "ProgressBar", true ); + if ( !pProgressBar ) + return; + + if ( !m_pContinuousProgressBar ) + return; + + // Chop up the progress bar into 10th's and use it as a mask overtop + pProgressBar->SetSegmentInfo( ( pProgressBar->GetWide() / 10 ) - ( ( 9 * 4 ) / 10 ) , 4 ); + + const XPState_t& xpstate = GetXPState(); + uint32 nCurrentXP = xpstate.GetCurrentXP(); + const LevelInfo_t& levelCur = m_pProgressionDesc->GetLevelForExperience( nCurrentXP ); + m_pProgressionDesc->SetupBadgePanel( m_pModelPanel, levelCur ); + + // Update labels + UpdateControls( xpstate.GetStartXP(), nCurrentXP, levelCur ); + + SetMatchStats(); +} + +void CPvPRankPanel::OnThink() +{ + BaseClass::OnThink(); + + if ( m_pContinuousProgressBar && m_pXPBar && m_pModelPanel && m_pProgressionDesc && m_pBGPanel ) + { + + // SUPER HACKS. I dont have time to figure out popups + if ( m_pModelButton && vgui::input()->IsMouseDown( MOUSE_LEFT ) ) + { + int nMouseX, nMouseY; + input()->GetCursorPos( nMouseX, nMouseY ); + if ( !m_bClicked && m_pModelButton->IsWithin( nMouseX, nMouseY ) ) + { + OnCommand( "medal_clicked" ); + } + + m_bClicked = true; + } + else + { + m_bClicked = false; + } + + const XPState_t& xpstate = GetXPState(); + uint32 nCurrentXP = xpstate.GetCurrentXP(); + + // Check if the last XP we lerp'd to isn't the current XP. If it's not, then we want to animate over to it. + if ( m_nLastLerpXP != nCurrentXP ) + { + uint32 nPrevXP = xpstate.GetStartXP(); + const LevelInfo_t& levelCur = m_pProgressionDesc->GetLevelForExperience( nCurrentXP ); + + UpdateControls( nPrevXP, nCurrentXP, levelCur ); + + // We only want to do level up effects if we are thinking (visible) when the current XP passes the level boundary + if ( m_nLastLerpXP != xpstate.GetStartXP() ) + { + const LevelInfo_t& levelPrevThink = m_pProgressionDesc->GetLevelForExperience( m_nLastLerpXP ); + if ( levelCur.m_nLevelNum > levelPrevThink.m_nLevelNum ) // Level up :) + { + PlayLevelUpEffects( levelCur ); + } + else if ( levelCur.m_nLevelNum < levelPrevThink.m_nLevelNum ) // Level down :( + { + PlayLevelDownEffects( levelCur ); + } + } + + m_nLastLerpXP = nCurrentXP; + } + else + { + // Our last lerp XP is caught up with current XP, but our last seen level is not up to date with what + // our current level actually is. This can happen if we haven't been thinking, but we did level up somewhere + // else. In this case, we need to update our badge. + const LevelInfo_t& levelCur = m_pProgressionDesc->GetLevelForExperience( nCurrentXP ); + + if ( m_nLastSeenLevel != levelCur.m_nLevelNum ) + { + m_nLastSeenLevel = levelCur.m_nLevelNum; + m_pProgressionDesc->SetupBadgePanel( m_pModelPanel, levelCur ); + } + } + } +} + + +void CPvPRankPanel::UpdateControls( uint32 nPreviousXP, uint32 nCurrentXP, const LevelInfo_t& levelCurrent ) +{ + if ( m_pContinuousProgressBar ) + { + // Calculate progress bar percentages. PrevBarProgress is the bar that shows where you started. + // CurrentBarProgress is the bar that lerps from the start to your actual, current XP + float flPrevBarProgress = RemapValClamped( (float)nPreviousXP, (float)levelCurrent.m_nStartXP, (float)levelCurrent.m_nEndXP, 0.f, 1.f ); + float flCurrentBarProgress = RemapValClamped( (float)nCurrentXP, (float)levelCurrent.m_nStartXP, (float)levelCurrent.m_nEndXP, 0.f, 1.f ); + + m_pContinuousProgressBar->SetPrevProgress( flPrevBarProgress ); + m_pContinuousProgressBar->SetProgress( flCurrentBarProgress ); + } + + if ( m_pXPBar ) + { + m_pXPBar->SetDialogVariable( "current_xp", LocalizeNumberWithToken( "TF_Competitive_XP_Current", nCurrentXP ) ); + + if ( levelCurrent.m_nLevelNum < m_pProgressionDesc->GetNumLevels() ) + { + m_pXPBar->SetDialogVariable( "next_level_xp", LocalizeNumberWithToken( "TF_Competitive_XP", levelCurrent.m_nEndXP ) ); + } + else // Hide the next level XP value at max level + { + m_pXPBar->SetDialogVariable( "next_level_xp", "" ); + } + + static wchar_t wszOutString[ 128 ]; + wchar_t wszCount[ 16 ]; + _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", levelCurrent.m_nLevelNum ); + const wchar_t *wpszFormat = g_pVGuiLocalize->Find( m_pProgressionDesc->m_pszLevelToken ); + g_pVGuiLocalize->ConstructString_safe( wszOutString, wpszFormat, 2, wszCount, g_pVGuiLocalize->Find( levelCurrent.m_pszLevelTitle ) ); + + m_pXPBar->SetDialogVariable( "level", wszOutString ); + } +} + +void CPvPRankPanel::OnCommand( const char *command ) +{ + if ( FStrEq( "medal_clicked", command ) ) + { + // Default effects + const char *pszSeqName = "click_A"; + const char *pszSoundName = "ui/mm_medal_click.wav"; + + // Roll for a crit + int nRandomRoll = RandomInt( 0, 9 ); + if ( nRandomRoll == 0 ) + { + // CRIT! + pszSeqName = "click_B"; + pszSoundName = "MatchMaking.MedalClickRare"; + } + + m_pModelPanel->PlaySequence( pszSeqName ); + PlaySoundEntry( pszSoundName ); + + EditablePanel* pModelContainer = FindControl< EditablePanel >( "ModelContainer" ); + if ( pModelContainer ) + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pModelContainer, "PvPRankModelClicked", false); + } + + return; + } + else if ( FStrEq( "begin_xp_lerp", command ) ) + { + BeginXPLerp(); + return; + } + else if ( FStrEq( "update_base_state", command ) ) + { + UpdateBaseState(); + return; + } + BaseClass::OnCommand( command ); +} + +void CPvPRankPanel::FireGameEvent( IGameEvent *pEvent ) +{ + // This is really only for tf_test_pvp_rank_xp_change + if ( FStrEq( pEvent->GetName(), "begin_xp_lerp" ) ) + { + BeginXPLerp(); + } +} + +void CPvPRankPanel::SetVisible( bool bVisible ) +{ + if ( IsVisible() != bVisible ) + { + UpdateBaseState(); + } + + BaseClass::SetVisible( bVisible ); +} + +int SortXPSources( CXPSource* const* pLeft, CXPSource* const* pRight ) +{ + return (*pLeft)->Obj().type() - (*pRight)->Obj().type(); +} + +void CPvPRankPanel::BeginXPLerp() +{ + XPState_t& xpstate = GetXPState(); + if ( xpstate.BeginXPDeltaLerp() ) + { + // Play sounds if this is a change + if ( xpstate.GetTargetXP() > xpstate.GetStartXP() ) + { + PlaySoundEntry( "MatchMaking.RankProgressTickUp" ); + } + else if ( xpstate.GetTargetXP() < xpstate.GetStartXP() ) + { + PlaySoundEntry( "MatchMaking.RankProgressTickDown" ); + } + } + + if ( steamapicontext && steamapicontext->SteamUser() ) + { + GCSDK::CGCClientSharedObjectCache *pSOCache = GCClientSystem()->GetSOCache( steamapicontext->SteamUser()->GetSteamID() ); + + if ( pSOCache ) + { + GCSDK::CGCClientSharedObjectTypeCache *pTypeCache = pSOCache->FindTypeCache( CXPSource::k_nTypeID ); + + if ( pTypeCache ) + { + CUtlVector< CXPSource* > vecSources; + + // Grab all the XP sources we want to show + for ( uint32 i = 0; i < pTypeCache->GetCount(); ++i ) + { + CXPSource *pXPSource = (CXPSource*)pTypeCache->GetObject( i ); + + if ( pXPSource->Obj().match_group() == m_eMatchGroup ) + { + vecSources.AddToTail( pXPSource ); + } + } + + // Sort them so users get a consistent experience + vecSources.Sort( &SortXPSources ); + + // Show the sources + FOR_EACH_VEC( vecSources, i ) + { + CXPSource *pXPSource = vecSources[ i ]; + CScrollingXPSourcePanel* pSourcePanel = new CScrollingXPSourcePanel( this, "XPSourcePanel", pXPSource->Obj(), Plat_FloatTime() + ( i * tf_xp_breakdown_interval.GetFloat() ) ); + pSourcePanel->MakeReadyForUse(); + + // Offset from the BGPanel. + pSourcePanel->SetPos( m_pBGPanel->GetXPos() + m_iXPSourceNotificationCenterX - ( pSourcePanel->GetWide() * 0.5f ), m_pBGPanel->GetYPos() + m_pXPBar->GetYPos() + YRES( 22 ) - pSourcePanel->GetTall() ); + } + } + } + } + + GTFGCClientSystem()->AcknowledgePendingXPSources( m_eMatchGroup ); +} + +void CPvPRankPanel::UpdateBaseState() +{ + if ( !m_pProgressionDesc || m_pModelPanel == NULL ) + return; + + // Set our "last seen" variables so things dont try to interpolate + m_nLastLerpXP = GetXPState().GetCurrentXP(); + LevelInfo_t levelCur = m_pProgressionDesc->GetLevelForExperience( m_nLastLerpXP ); + m_nLastSeenLevel = levelCur.m_nLevelNum; + + m_pProgressionDesc->SetupBadgePanel( m_pModelPanel, levelCur ); + + // Update progress bars and labels + UpdateControls( GetXPState().GetStartXP(), m_nLastLerpXP, levelCur ); +} + +void CPvPRankPanel::OnAnimEvent( KeyValues *pParams ) +{ + // This gets fired by the model panel that has the badge model in it when we want + // to do our bodygroup changes. This is so we can time it to when the model is doing + // a flashy maneuver to mask the bodygroup change pop + if ( FStrEq( pParams->GetString( "name" ), "AE_CL_BODYGROUP_SET_VALUE" ) && m_pProgressionDesc ) + { + const LevelInfo_t& levelCur = m_pProgressionDesc->GetLevelForExperience( m_nLastLerpXP ); + m_pProgressionDesc->SetupBadgePanel( m_pModelPanel, levelCur ); + } +} + +void CPvPRankPanel::PlayLevelUpEffects( const LevelInfo_t& level ) const +{ + m_pModelPanel->PlaySequence( "level_up" ); + PlaySoundEntry( level.m_pszLevelUpSound ); + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_pXPBar, "PvPRankLevelUpXPBar", false); + + EditablePanel* pModelContainer = const_cast< CPvPRankPanel* >( this )->FindControl< EditablePanel >( "ModelContainer" ); + if ( pModelContainer ) + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pModelContainer, "PvPRankLevelUpModel", false ); + } +} + +void CPvPRankPanel::PlayLevelDownEffects( const LevelInfo_t& level ) const +{ + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_pXPBar, "PvPRankLevelDownXPBar", false); + m_pModelPanel->PlaySequence( "level_down" ); + + EditablePanel* pModelContainer = const_cast< CPvPRankPanel* >( this )->FindControl< EditablePanel >( "ModelContainer" ); + if ( pModelContainer ) + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pModelContainer, "PvPRankLevelDownModel", false ); + } +} + +void CPvPRankPanel::SetMatchGroup( EMatchGroup eMatchGroup ) +{ + if ( m_eMatchGroup != eMatchGroup ) + { + m_eMatchGroup = eMatchGroup; + m_pProgressionDesc = NULL; + + const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( m_eMatchGroup ); + if ( pMatchDesc && pMatchDesc->m_pProgressionDesc ) + { + // Snag the progression desc. We use it often + m_pProgressionDesc = pMatchDesc->m_pProgressionDesc; + UpdateBaseState(); + } + + Assert( m_pProgressionDesc ); + + // Many things needs to change + InvalidateLayout( true, true ); + UpdateBaseState(); + } +} + +void CPvPRankPanel::SOCreated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) +{ + if ( pObject->GetTypeID() != CSOTFLadderData::k_nTypeID ) + return; + + SetMatchStats(); +} + +void CPvPRankPanel::SOUpdated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) +{ + if ( pObject->GetTypeID() != CSOTFLadderData::k_nTypeID ) + return; + + SetMatchStats(); +} + +void CPvPRankPanel::SetMatchStats( void ) +{ + EditablePanel* pStatsContainer = FindControl< EditablePanel >( "Stats", true ); + if ( !pStatsContainer ) + return; + + CSOTFLadderData *pData = GetLocalPlayerLadderData( m_eMatchGroup ); + + // Update all stats. Default to 0 incase we dont have ladder data + int nGames = 0, nKills = 0, nDeaths = 0, nDamage = 0, nHealing = 0, nSupport = 0, nScore = 0; + + if ( pData ) + { + nGames = pData->Obj().games(); + nKills = pData->Obj().kills(); + nDeaths = pData->Obj().deaths(); + nDamage = pData->Obj().damage(); + nHealing = pData->Obj().healing(); + nSupport = pData->Obj().support(); + nScore = pData->Obj().score(); + } + + pStatsContainer->SetDialogVariable( "stat_games", LocalizeNumberWithToken( "TF_Competitive_Games", nGames ) ); + pStatsContainer->SetDialogVariable( "stat_kills", LocalizeNumberWithToken( "TF_Competitive_Kills", nKills ) ); + pStatsContainer->SetDialogVariable( "stat_deaths", LocalizeNumberWithToken( "TF_Competitive_Deaths", nDeaths ) ); + pStatsContainer->SetDialogVariable( "stat_damage", LocalizeNumberWithToken( "TF_Competitive_Damage", nDamage ) ); + pStatsContainer->SetDialogVariable( "stat_healing", LocalizeNumberWithToken( "TF_Competitive_Healing", nHealing ) ); + pStatsContainer->SetDialogVariable( "stat_support", LocalizeNumberWithToken( "TF_Competitive_Support", nSupport ) ); + pStatsContainer->SetDialogVariable( "stat_score", LocalizeNumberWithToken( "TF_Competitive_Score", nScore ) ); +} + +CPvPRankPanel::XPState_t& CPvPRankPanel::GetXPState() const +{ + // Singletons for each XPState_t + static CUtlMap< EMatchGroup, CPvPRankPanel::XPState_t* > s_mapXPStates( DefLessFunc( EMatchGroup ) ); + + auto idx = s_mapXPStates.Find( m_eMatchGroup ); + if ( idx == s_mapXPStates.InvalidIndex() ) + { + idx = s_mapXPStates.Insert( m_eMatchGroup, new CPvPRankPanel::XPState_t( m_eMatchGroup ) ); + } + + return *s_mapXPStates[ idx ]; +} + +const char* CPvPRankPanel::GetResFile() const +{ + return m_pProgressionDesc ? m_pProgressionDesc->m_pszProgressionResFile : "resource/ui/PvPCompRankPanel.res"; +} + +KeyValues* CPvPRankPanel::GetConditions() const +{ + return NULL; +} diff --git a/game/client/tf/vgui/tf_pvp_rank_panel.h b/game/client/tf/vgui/tf_pvp_rank_panel.h new file mode 100644 index 0000000..1af4daf --- /dev/null +++ b/game/client/tf/vgui/tf_pvp_rank_panel.h @@ -0,0 +1,114 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#ifndef TF_PVP_RANK_PANEL_H +#define TF_PVP_RANK_PANEL_H + +#include "cbase.h" +#include "vgui_controls/EditablePanel.h" +#include "tf_match_description.h" +#include "GameEventListener.h" +#include "local_steam_shared_object_listener.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +class CBaseModelPanel; +namespace vgui +{ + class ContinuousProgressBar; +}; + +namespace GCSDK +{ + class CSharedObject; +}; + +using namespace GCSDK; + +class CPvPRankPanel : public vgui::EditablePanel, public CLocalSteamSharedObjectListener, public CGameEventListener +{ +public: + DECLARE_CLASS_SIMPLE( CPvPRankPanel, vgui::EditablePanel ); + + CPvPRankPanel( Panel *parent, const char *panelName ); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE; + virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE; + virtual void PerformLayout() OVERRIDE; + virtual void OnThink() OVERRIDE; + virtual void OnCommand( const char *command ) OVERRIDE; + virtual void FireGameEvent( IGameEvent *pEvent ) OVERRIDE; + virtual void SetVisible( bool bVisible ) OVERRIDE; + + void SetMatchGroup( EMatchGroup eMatchGroup ); + void SetMatchStats( void ); + + virtual void SOCreated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) OVERRIDE; + virtual void SOUpdated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) OVERRIDE; + + MESSAGE_FUNC_PARAMS( OnAnimEvent, "AnimEvent", pParams ); + +protected: + + virtual void PlayLevelUpEffects( const LevelInfo_t& level ) const; + virtual void PlayLevelDownEffects( const LevelInfo_t& level ) const; + +private: + + struct XPState_t : public CGameEventListener, public CLocalSteamSharedObjectListener + { + XPState_t( EMatchGroup eMatchGroup ); + + virtual void FireGameEvent( IGameEvent *pEvent ) OVERRIDE; + // Get the current XP value + uint32 GetCurrentXP() const; + // Get the previous XP value before any changes occurred + uint32 GetStartXP() const { return m_nStartXP; } + // Get the target XP that the delta lerp is headed to + uint32 GetTargetXP() const { return m_nTargetXP; } + uint32 GetActualXP() const { return m_nActualXP; } + // Start the timer that causes GetCurrentXP to lerp from m_nStartXP to m_nTargetXP + bool BeginXPDeltaLerp(); + + virtual void SOCreated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) OVERRIDE; + virtual void SOUpdated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) OVERRIDE; + + private: + + void UpdateXP( bool bInitial ); + + const EMatchGroup m_eMatchGroup; + uint32 m_nStartXP; // The XP we show to the user that was the last state they saw + uint32 m_nTargetXP; // The XP the user believes is their current value + uint32 m_nActualXP; // The actual latest XP value + RealTimeCountdownTimer m_progressTimer; + bool m_bCurrentDeltaViewed; + }; + + XPState_t& GetXPState() const; + void UpdateControls( uint32 nPreviousXP, uint32 nCurrentXP, const LevelInfo_t& levelCurrent ); + void UpdateBaseState(); + + virtual const char* GetResFile() const; + virtual KeyValues* GetConditions() const; + void BeginXPLerp(); + + EMatchGroup m_eMatchGroup; + vgui::ContinuousProgressBar* m_pContinuousProgressBar; + vgui::EditablePanel* m_pXPBar; + CBaseModelPanel* m_pModelPanel; + const IProgressionDesc* m_pProgressionDesc; + EditablePanel* m_pBGPanel; + bool m_bClicked; + Panel* m_pModelButton; + + uint32 m_nLastLerpXP; + uint32 m_nLastSeenLevel; + + CPanelAnimationVarAliasType( int, m_iXPSourceNotificationCenterX, "xp_source_notification_center_x", "19", "proportional_int" ); +}; + +#endif //TF_PVP_RANK_PANEL_H
\ No newline at end of file diff --git a/game/client/tf/vgui/tf_roundinfo.cpp b/game/client/tf/vgui/tf_roundinfo.cpp new file mode 100644 index 0000000..ccfba71 --- /dev/null +++ b/game/client/tf/vgui/tf_roundinfo.cpp @@ -0,0 +1,623 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include <vgui_controls/Label.h> +#include <vgui_controls/Button.h> +#include <vgui_controls/ImagePanel.h> +#include <vgui_controls/RichText.h> +#include <vgui_controls/Frame.h> +#include <game/client/iviewport.h> +#include <KeyValues.h> +#include <filesystem.h> +#include "materialsystem/imaterialvar.h" +#include "IGameUIFuncs.h" // for key bindings + +#include "tf_controls.h" +#include "tf_imagepanel.h" +#include "c_team_objectiveresource.h" +#include "c_tf_objective_resource.h" +#include "c_tf_player.h" + +#include "tf_shareddefs.h" +#include "tf_roundinfo.h" + + +#include "vgui/ISurface.h" +#include <vgui/ILocalize.h> +#include <vgui/IVGui.h> +#include "engine/IEngineSound.h" + +using namespace vgui; + +const char *GetMapDisplayName( const char *mapName ); + +class RoundInfoOverlay : public vgui::EditablePanel +{ +public: + DECLARE_CLASS_SIMPLE( RoundInfoOverlay, vgui::EditablePanel ); + + RoundInfoOverlay( Panel *parent, const char *panelName ) : EditablePanel( parent, panelName ) + { + m_iMode = 0; + m_flModeChangeTime = -1; + + vgui::ivgui()->AddTickSignal( GetVPanel(), 100 ); + + m_iBlueTeamTexture = vgui::surface()->CreateNewTextureID(); + vgui::surface()->DrawSetTextureFile(m_iBlueTeamTexture, "overviews/blueteam", true, false); + + m_iRedTeamTexture = vgui::surface()->CreateNewTextureID(); + vgui::surface()->DrawSetTextureFile(m_iRedTeamTexture, "overviews/redteam", true, false); + + m_iCapArrowTexture = vgui::surface()->CreateNewTextureID(); + vgui::surface()->DrawSetTextureFile(m_iCapArrowTexture, "overviews/caparrows", true, false); + + m_iFoundPoints = 0; + m_iNextRoundPoints[0] = -1; + m_iNextRoundPoints[1] = -1; + + m_iLastCappedPoint = -1; + } + + virtual ~RoundInfoOverlay( void ) + { + if ( vgui::surface() ) + { + if ( m_iBlueTeamTexture != -1 ) + { + vgui::surface()->DestroyTextureID( m_iBlueTeamTexture ); + m_iBlueTeamTexture = -1; + } + + if ( m_iRedTeamTexture != -1 ) + { + vgui::surface()->DestroyTextureID( m_iRedTeamTexture ); + m_iRedTeamTexture = -1; + } + + if ( m_iCapArrowTexture != -1 ) + { + vgui::surface()->DestroyTextureID( m_iCapArrowTexture ); + m_iCapArrowTexture = -1; + } + } + } + + void Update( const char *szMapName ); + + virtual void Paint(); + + void SetState( int iPrevState, int iCurrentState, int iNextBattles ); + + virtual void OnTick( void ); + + void DrawTeamIcon( int x, int y, bool bBlueTeam, float flBloat = 1.0f ); + void DrawCapArrows( int x0, int y0, int x1, int y1 ); + +private: + + // structure to hold a single control point + typedef struct + { + char m_szName[64]; + int m_iXPos; + int m_iYPos; + bool m_bHideIcon; + } roundinfo_control_point_t; + + CUtlVector < roundinfo_control_point_t > m_ControlPoints; + + int m_iPrevState; + int m_iCurrentState; + int m_iMiniRoundMask; + + // Time when we should change the text to the attack directive + int m_iMode; // 0 - start, 1 - previous round victory anim, 2 - attack directive, new round + float m_flModeChangeTime; + + int m_iBlueTeamTexture; + int m_iRedTeamTexture; + int m_iCapArrowTexture; + + int m_iLastCappedPoint; + + int m_iFoundPoints; + int m_iNextRoundPoints[2]; +}; + +DECLARE_BUILD_FACTORY( RoundInfoOverlay ); + +void RoundInfoOverlay::Paint( void ) +{ + BaseClass::Paint(); + + if ( m_ControlPoints.Count() <= 0 ) + { + return; + } + + // Draw the Cap Icons + + for ( int i=0; i<m_ControlPoints.Count(); i++ ) + { + if ( m_ControlPoints[i].m_bHideIcon ) + continue; + + int x = m_ControlPoints[i].m_iXPos; + int y = m_ControlPoints[i].m_iYPos; + + switch( m_iMode ) + { + case 0: // Show previous state + { + if ( i != m_iLastCappedPoint ) + { + bool bBlueTeam = ( m_iPrevState & (1<<i) ); + DrawTeamIcon( x, y, bBlueTeam ); + } + } + break; + + case 1: // Animate the point being capped + { + bool bWasBlueTeam = ( m_iPrevState & (1<<i) ); + + if ( i == m_iLastCappedPoint ) + { + float flTimeUntilChange = m_flModeChangeTime - gpGlobals->curtime; + + if ( flTimeUntilChange < 0.4f ) + { + float flBloat = RemapVal( flTimeUntilChange, 0.0f, 0.4f, 1.0f, 2.5f ); + + DrawTeamIcon( x, y, !bWasBlueTeam, flBloat ); + } + } + else + { + DrawTeamIcon( x, y, bWasBlueTeam ); + } + } + break; + + case 2: // Draw the current state and the next battle arrows + { + bool bPointInContention = (m_iNextRoundPoints[0] == i || m_iNextRoundPoints[1] == i ); + + bool bBlueTeam = ( m_iCurrentState & (1<<i) ); + DrawTeamIcon( x, y, bBlueTeam, bPointInContention ? 1.4 : 1.0 ); // rescale? pop looks weird + } + break; + } + } + + if ( m_iMode == 2 ) + { + if ( m_iFoundPoints == 2 ) + { + if ( ( m_flModeChangeTime - gpGlobals->curtime ) < 3.5f ) + { + DrawCapArrows( m_ControlPoints[m_iNextRoundPoints[0]].m_iXPos, + m_ControlPoints[m_iNextRoundPoints[0]].m_iYPos, + m_ControlPoints[m_iNextRoundPoints[1]].m_iXPos, + m_ControlPoints[m_iNextRoundPoints[1]].m_iYPos ); + } + } + } +} + +void RoundInfoOverlay::DrawCapArrows( int x0, int y0, int x1, int y1 ) +{ + vgui::surface()->DrawSetColor( Color(255,255,255,255) ); + + vgui::surface()->DrawSetTexture( m_iCapArrowTexture ); + + Vector2D a( x0, y0 ); + Vector2D b( x1, y1 ); + + Vector2D dir = b - a; + + Vector2D perp( -dir.y, dir.x ); + perp.NormalizeInPlace(); + perp *= YRES(50); + + float bloat = sin(4*gpGlobals->curtime) * 0.1f; + + Vector2D edgepoint = a + dir * 0.25f; + Vector2D edgepoint2 = b - dir * 0.25f; + + edgepoint -= 0.25f * dir * bloat; + edgepoint2 += 0.25f * dir * bloat; + + float uv1 = 0.0f, uv2 = 1.0f; + Vector2D uv12( uv1, uv2 ); + Vector2D uv11( uv1, uv1 ); + Vector2D uv21( uv2, uv1 ); + Vector2D uv22( uv2, uv2 ); + + vgui::Vertex_t verts[4]; + verts[0].Init( edgepoint - perp * 0.5f, uv12 ); + verts[1].Init( edgepoint2 - perp * 0.5f, uv11 ); + verts[2].Init( edgepoint2 + perp * 0.5f, uv21 ); + verts[3].Init( edgepoint + perp * 0.5f, uv22 ); + + vgui::surface()->DrawTexturedPolygon( 4, verts ); +} + +void RoundInfoOverlay::DrawTeamIcon( int x, int y, bool bBlueTeam, float flBloat /* = 1.0f */ ) +{ + float flWide = YRES(45) * flBloat; + + int xpos = x - flWide * 0.5f; + int ypos = y - flWide * 0.5f; + + vgui::surface()->DrawSetColor( Color(255,255,255,255) ); + vgui::surface()->DrawSetTexture( bBlueTeam ? m_iBlueTeamTexture : m_iRedTeamTexture ); + vgui::surface()->DrawTexturedRect( xpos, ypos, xpos + flWide, ypos + flWide ); +} + +void RoundInfoOverlay::Update( const char *szMapName ) +{ + KeyValues *kvCapPoints = NULL; + + char strFullpath[MAX_PATH]; + Q_strncpy( strFullpath, "resource/roundinfo/", MAX_PATH ); // Assume we must play out of the media directory + Q_strncat( strFullpath, szMapName, MAX_PATH ); + +#ifdef _X360 + char *pExt = Q_stristr( strFullpath, ".360" ); + if ( pExt ) + { + *pExt = '\0'; + } +#endif + + Q_strncat( strFullpath, ".res", MAX_PATH ); // Assume we're a .res extension type + + if ( g_pFullFileSystem->FileExists( strFullpath ), "MOD" ) + { + kvCapPoints = new KeyValues( strFullpath ); + + if ( kvCapPoints ) + { + if ( kvCapPoints->LoadFromFile( g_pFullFileSystem, strFullpath ) ) + { + m_ControlPoints.RemoveAll(); + + for ( KeyValues *pData = kvCapPoints->GetFirstSubKey(); pData != NULL; pData = pData->GetNextKey() ) + { + roundinfo_control_point_t point; + + Q_snprintf( point.m_szName, sizeof(point.m_szName), "%s", pData->GetName() ); + + // These x,y coords are relative to a 640x480 parent panel. + int wide, tall; + GetSize( wide, tall ); + + // can't use XRES, YRES because of widescreen + point.m_iXPos = (int)( (float)pData->GetInt( "x", 0 ) * ( ( float )wide / 560.0f ) ); + point.m_iYPos = (int)( (float)pData->GetInt( "y", 0 ) * ( ( float )tall / 280.0f ) ); + + point.m_bHideIcon = ( pData->GetInt( "hideicon", 0 ) > 0 ); + + m_ControlPoints.AddToTail( point ); + } + } + + kvCapPoints->deleteThis(); + } + } +} + +void RoundInfoOverlay::SetState( int iPrevState, int iCurrentState, int iNextBattles ) +{ + m_iPrevState = iPrevState; + m_iCurrentState = iCurrentState; + m_iMiniRoundMask = iNextBattles; + + m_iMode = 0; + m_flModeChangeTime = gpGlobals->curtime + 0.5f; + + // Find the two points that are being fought over + + m_iFoundPoints = 0; + + for ( int i=0;i<8 && m_iFoundPoints<2;i++ ) + { + if ( m_iMiniRoundMask & (1<<i) ) + { + m_iNextRoundPoints[m_iFoundPoints] = i; + m_iFoundPoints++; + } + } + + // Make sure the blue point is in m_iNextRoundPoints[0] + if ( m_iFoundPoints >= 2 ) + { + if ( !( m_iCurrentState & (1<<m_iNextRoundPoints[0]) ) ) + { + // The first point is red! swap them + int temp = m_iNextRoundPoints[0]; + m_iNextRoundPoints[0] = m_iNextRoundPoints[1]; + m_iNextRoundPoints[1] = temp; + } + } + + m_iLastCappedPoint = -1; + + // Find the index of the point that was just capped + int iMaskedCappedPoint = m_iCurrentState ^ m_iPrevState; + + if ( iMaskedCappedPoint != 0 ) + { + int iIndex = 0; + + // Find the index of the point that changed + while ( !( iMaskedCappedPoint & 0x1 ) ) + { + iMaskedCappedPoint = iMaskedCappedPoint>>1; + iIndex++; + } + m_iLastCappedPoint = iIndex; + } +} + + +ConVar tf_roundinfo_pause( "tf_roundinfo_pause", "0", FCVAR_DEVELOPMENTONLY ); + +void RoundInfoOverlay::OnTick( void ) +{ + // Stop ticking when our parent is invisible + Panel *parent = GetParent(); + if ( m_iMode >= 0 && ( !parent || !parent->IsVisible() ) ) + { + m_iMode = -1; + return; + } + + BaseClass::OnTick(); + + if ( tf_roundinfo_pause.GetBool() == false && m_flModeChangeTime <= gpGlobals->curtime ) + { + switch( m_iMode ) + { + case 0: + { + // start showing previous round anim + if ( m_iCurrentState != m_iPrevState ) + { + m_iMode = 1; + m_flModeChangeTime = gpGlobals->curtime + 1.5f; + } + else + { + m_iMode = 2; + m_flModeChangeTime = gpGlobals->curtime + 4.0f; + } + } + break; + + case 1: + { + // start showing next round plan + m_iMode = 2; + m_flModeChangeTime = gpGlobals->curtime + 4.0f; + + CLocalPlayerFilter filter; + C_BaseEntity::EmitSound( filter, SOUND_FROM_LOCAL_PLAYER, "Hud.EndRoundScored" ); + } + break; + + case 2: + { + // we're done, hide the panel + //GetParent()->OnCommand( "continue" ); + //m_iMode = -1; + } + break; + + default: + break; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CTFRoundInfo::CTFRoundInfo( IViewPort *pViewPort ) : Frame( NULL, PANEL_ROUNDINFO ) +{ + m_pViewPort = pViewPort; + + // load the new scheme early!! + SetScheme( "ClientScheme" ); + + SetTitleBarVisible( false ); + SetMinimizeButtonVisible( false ); + SetMaximizeButtonVisible( false ); + SetCloseButtonVisible( false ); + SetSizeable( false ); + SetMoveable( false ); + SetProportional( true ); + SetVisible( false ); + SetKeyBoardInputEnabled( true ); + + m_pTitle = new CExLabel( this, "RoundTitle", " " ); + m_pMapImage = new ImagePanel( this, "MapImage" ); + +#ifdef _X360 + m_pFooter = new CTFFooter( this, "Footer" ); +#else + m_pContinue = new CExButton( this, "RoundContinue", "#TF_Continue" ); +#endif + + m_pOverlay = new RoundInfoOverlay( this, "Overlay" ); + + ListenForGameEvent( "game_newmap" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFRoundInfo::PerformLayout() +{ + BaseClass::PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFRoundInfo::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + LoadControlSettings( "Resource/UI/RoundInfo.res" ); + + BaseClass::ApplySchemeSettings( pScheme ); + + Update(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFRoundInfo::ShowPanel( bool bShow ) +{ + if ( IsVisible() == bShow ) + return; + + if ( bShow ) + { + // look for the textures we want to use and don't show the roundinfo panel if any are missing + char temp[255]; + Q_snprintf( temp, sizeof( temp ), "VGUI/%s", m_szMapImage ); + IMaterial *pMapMaterial = materials->FindMaterial( temp, TEXTURE_GROUP_VGUI, false ); + + // are we missing any of the images we want to show? + if ( pMapMaterial && !IsErrorMaterial( pMapMaterial ) ) + { + Activate(); + } + else + { + SetVisible( false ); + } + } + else + { + SetVisible( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFRoundInfo::OnCommand( const char *command ) +{ + if ( !Q_strcmp( command, "continue" ) ) + { + m_pViewPort->ShowPanel( this, false ); + } + else + { + BaseClass::OnCommand( command ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFRoundInfo::UpdateImage( ImagePanel *pImagePanel, const char *pszImageName ) +{ + if ( pImagePanel && ( Q_strlen( pszImageName ) > 0 ) ) + { + char szTemp[255]; + Q_snprintf( szTemp, sizeof( szTemp ), "VGUI/%s", pszImageName ); + + IMaterial *pTemp = materials->FindMaterial( szTemp, TEXTURE_GROUP_VGUI, false ); + if ( pTemp && !IsErrorMaterial( pTemp ) ) + { + pImagePanel->SetImage( pszImageName ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFRoundInfo::Update() +{ + char szMapName[MAX_MAP_NAME]; + Q_FileBase( engine->GetLevelName(), szMapName, sizeof(szMapName) ); + Q_strlower( szMapName ); + + SetDialogVariable( "mapname", GetMapDisplayName( szMapName ) ); + + if ( m_pMapImage ) + { + char temp[255]; + Q_snprintf( temp, sizeof(temp), "../overviews/%s", szMapName ); + Q_strncpy( m_szMapImage, temp, sizeof( m_szMapImage ) ); + + UpdateImage( m_pMapImage, m_szMapImage ); + } + + if ( m_pOverlay ) + { + m_pOverlay->Update( szMapName ); + } + +#ifndef _X360 + if ( m_pContinue ) + { + m_pContinue->RequestFocus(); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFRoundInfo::OnKeyCodePressed( KeyCode code ) +{ + if( code == KEY_SPACE || + code == KEY_ENTER || + code == KEY_XBUTTON_A || + code == KEY_XBUTTON_B || + code == STEAMCONTROLLER_A || + code == STEAMCONTROLLER_B ) + { + OnCommand( "continue" ); + } + else + { + BaseClass::OnKeyCodePressed( code ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFRoundInfo::SetData( KeyValues *data ) +{ + if ( m_pOverlay ) + { + m_pOverlay->SetState( data->GetInt( "prev" ), data->GetInt( "cur" ), data->GetInt( "round" ) ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFRoundInfo::FireGameEvent( IGameEvent *event ) +{ + if ( Q_strcmp( event->GetName(), "game_newmap" ) == 0 ) + { + Update(); + } +}
\ No newline at end of file diff --git a/game/client/tf/vgui/tf_roundinfo.h b/game/client/tf/vgui/tf_roundinfo.h new file mode 100644 index 0000000..6d30e23 --- /dev/null +++ b/game/client/tf/vgui/tf_roundinfo.h @@ -0,0 +1,73 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_ROUNDINFO_H +#define TF_ROUNDINFO_H +#ifdef _WIN32 +#pragma once +#endif + +//----------------------------------------------------------------------------- +// Purpose: displays the RoundInfo menu +//----------------------------------------------------------------------------- +class RoundInfoOverlay; + +class CTFRoundInfo : public vgui::Frame, public IViewPortPanel, public CGameEventListener +{ +private: + DECLARE_CLASS_SIMPLE( CTFRoundInfo, vgui::Frame ); + +public: + CTFRoundInfo( IViewPort *pViewPort ); + + virtual const char *GetName( void ){ return PANEL_ROUNDINFO; } + virtual void SetData( KeyValues *data ); + virtual void Reset(){ Update(); } + virtual void Update(); + virtual bool NeedsUpdate( void ){ return false; } + virtual bool HasInputElements( void ){ return true; } + virtual void ShowPanel( bool bShow ); + + // both vgui::Frame and IViewPortPanel define these, so explicitly define them here as passthroughs to vgui + vgui::VPANEL GetVPanel( void ){ return BaseClass::GetVPanel(); } + virtual bool IsVisible(){ return BaseClass::IsVisible(); } + virtual void SetParent( vgui::VPANEL parent ){ BaseClass::SetParent( parent ); } + + virtual void FireGameEvent( IGameEvent *event ); + + virtual GameActionSet_t GetPreferredActionSet() { return GAME_ACTION_SET_IN_GAME_HUD; } + +protected: + virtual void OnKeyCodePressed( vgui::KeyCode code ); + virtual void PerformLayout(); + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void OnCommand( const char *command ); + + void UpdateImage( vgui::ImagePanel *pImagePanel, const char *pszImageName ); + +protected: + IViewPort *m_pViewPort; + + CExLabel *m_pTitle; + vgui::ImagePanel *m_pMapImage; + +#ifdef _X360 + CTFFooter *m_pFooter; +#else + CExButton *m_pContinue; +#endif + + char m_szMapImage[MAX_ROUND_IMAGE_NAME]; + + RoundInfoOverlay *m_pOverlay; + + int m_iFoundPoints; + int m_iNextRoundPoints[2]; +}; + + +#endif // TF_ROUNDINFO_H diff --git a/game/client/tf/vgui/tf_spectatorgui.cpp b/game/client/tf/vgui/tf_spectatorgui.cpp new file mode 100644 index 0000000..019944c --- /dev/null +++ b/game/client/tf/vgui/tf_spectatorgui.cpp @@ -0,0 +1,1279 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "hud.h" +#include "c_team.h" +#include "tf_playerpanel.h" +#include "tf_spectatorgui.h" +#include "tf_shareddefs.h" +#include "tf_gamerules.h" +#include "tf_hud_objectivestatus.h" +#include "tf_hud_statpanel.h" +#include "iclientmode.h" +#include "c_playerresource.h" +#include "tf_hud_building_status.h" +#include "tf_hud_tournament.h" +#include "tf_hud_winpanel.h" +#include "tf_tips.h" +#include "tf_mapinfomenu.h" +#include "econ_wearable.h" +#include "c_tf_playerresource.h" +#include "playerspawncache.h" +#include "econ_notifications.h" +#include "tf_hud_item_progress_tracker.h" +#include "tf_hud_target_id.h" +#include "c_baseobject.h" +#include "inputsystem/iinputsystem.h" + +#if defined( REPLAY_ENABLED ) +#include "replay/replay.h" +#include "replay/ireplaysystem.h" +#include "replay/ireplaymanager.h" +#endif // REPLAY_ENABLED + +#include <vgui/ILocalize.h> +#include <vgui/ISurface.h> +#include "vgui_avatarimage.h" + +using namespace vgui; + +extern ConVar _cl_classmenuopen; +extern ConVar tf_max_health_boost; +extern const char *g_pszItemClassImages[]; +extern int g_ClassDefinesRemap[]; +extern ConVar tf_mvm_buybacks_method; + +const char *GetMapDisplayName( const char *mapName ); + +static const wchar_t* GetSCGlyph( const char* action ) +{ + auto origin = g_pInputSystem->GetSteamControllerActionOrigin( action, GAME_ACTION_SET_SPECTATOR ); + return g_pInputSystem->GetSteamControllerFontCharacterForActionOrigin( origin ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFSpectatorGUI *GetTFSpectatorGUI() +{ + extern CSpectatorGUI *g_pSpectatorGUI; + return dynamic_cast< CTFSpectatorGUI * >( g_pSpectatorGUI ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ConVar cl_spec_carrieditems( "cl_spec_carrieditems", "1", FCVAR_ARCHIVE, "Show non-standard items being carried by player you're spectating." ); + +void HUDTournamentSpecChangedCallBack( IConVar *var, const char *pOldString, float flOldValue ) +{ + CTFSpectatorGUI *pPanel = (CTFSpectatorGUI*)gViewPortInterface->FindPanelByName( PANEL_SPECGUI ); + if ( pPanel ) + { + pPanel->InvalidateLayout( true, true ); + } +} +ConVar cl_use_tournament_specgui( "cl_use_tournament_specgui", "0", FCVAR_ARCHIVE, "When in tournament mode, use the advanced tournament spectator UI.", HUDTournamentSpecChangedCallBack ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CTFSpectatorGUI::CTFSpectatorGUI(IViewPort *pViewPort) : CSpectatorGUI(pViewPort) +{ + m_flNextTipChangeTime = 0; + m_iTipClass = TF_CLASS_UNDEFINED; + + m_nEngBuilds_xpos = m_nEngBuilds_ypos = 0; + m_nSpyBuilds_xpos = m_nSpyBuilds_ypos = 0; + + m_nMannVsMachineStatus_xpos = m_nMannVsMachineStatus_ypos = 0; + m_pBuyBackLabel = new CExLabel( this, "BuyBackLabel", "" ); + m_pReinforcementsLabel = new Label( this, "ReinforcementsLabel", "" ); + m_pClassOrTeamLabel = new Label( this, "ClassOrTeamLabel", "" ); + // m_pSwitchCamModeKeyLabel = new Label( this, "SwitchCamModeKeyLabel", "" ); + m_pSwitchCamModeKeyLabel = nullptr; + m_pClassOrTeamKeyLabel = nullptr; + m_pCycleTargetFwdKeyLabel = new Label( this, "CycleTargetFwdKeyLabel", "" ); + m_pCycleTargetRevKeyLabel = new Label( this, "CycleTargetRevKeyLabel", "" ); + m_pMapLabel = new Label( this, "MapLabel", "" ); + m_pItemPanel = new CItemModelPanel( this, "itempanel" ); + + m_pStudentHealth = new CTFSpectatorGUIHealth( this, "StudentGUIHealth" ); + m_pAvatar = NULL; + + m_flNextItemPanelUpdate = 0; + m_flNextPlayerPanelUpdate = 0; + m_iPrevItemShown = 0; + m_iFirstItemShown = 0; + m_bShownItems = false; + m_hPrevItemPlayer = NULL; + m_pPlayerPanelKVs = NULL; + m_bReapplyPlayerPanelKVs = false; + m_bCoaching = false; + + ListenForGameEvent( "spec_target_updated" ); + ListenForGameEvent( "player_death" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFSpectatorGUI::~CTFSpectatorGUI() +{ + if ( m_pPlayerPanelKVs ) + { + m_pPlayerPanelKVs->deleteThis(); + m_pPlayerPanelKVs = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::Reset( void ) +{ + BaseClass::Reset(); + + for ( int i = 0; i < m_PlayerPanels.Count(); i++ ) + { + m_PlayerPanels[i]->Reset(); + } + + m_pStudentHealth->Reset(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CTFSpectatorGUI::GetTopBarHeight() +{ + int iPlayerPanelHeight = 0; + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) + { + if ( GetLocalPlayerTeam() == TF_TEAM_PVE_DEFENDERS ) + { + if ( m_PlayerPanels.Count() > 0 ) + { + iPlayerPanelHeight = m_PlayerPanels[0]->GetTall(); + } + } + } + + return m_pTopBar->GetTall() + iPlayerPanelHeight; +} + +//----------------------------------------------------------------------------- +// Purpose: makes the GUI fill the screen +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::PerformLayout( void ) +{ + BaseClass::PerformLayout(); + + if ( m_bReapplyPlayerPanelKVs ) + { + m_bReapplyPlayerPanelKVs = false; + + if ( m_pPlayerPanelKVs ) + { + for ( int i = 0; i < m_PlayerPanels.Count(); i++ ) + { + m_PlayerPanels[i]->ApplySettings( m_pPlayerPanelKVs ); + m_PlayerPanels[i]->InvalidateLayout( false, true ); + } + } + } + + if ( m_pStudentHealth ) + { + Panel* pHealthPosPanel = FindChildByName( "HealthPositioning" ); + if ( pHealthPosPanel ) + { + int xPos, yPos, iWide, iTall; + pHealthPosPanel->GetBounds( xPos, yPos, iWide, iTall ); + m_pStudentHealth->SetBounds( xPos, yPos, iWide, iTall ); + m_pStudentHealth->SetZPos( pHealthPosPanel->GetZPos() ); + } + } + + UpdatePlayerPanels(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + bool bVisible = IsVisible(); + BaseClass::ApplySchemeSettings( pScheme ); + + m_bReapplyPlayerPanelKVs = true; + m_bPrevTournamentMode = InTournamentGUI(); + + m_pSwitchCamModeKeyLabel = dynamic_cast< Label* >( FindChildByName( "SwitchCamModeKeyLabel" ) ); + + m_pAvatar = dynamic_cast<CAvatarImagePanel *>( FindChildByName("AvatarImage") ); + if ( ::input->IsSteamControllerActive() ) + { + m_pClassOrTeamKeyLabel = dynamic_cast< CExLabel* >( FindChildByName( "ClassOrTeamKeyLabel" ) ); + } + else + { + m_pClassOrTeamKeyLabel = nullptr; + } + + if ( m_bCoaching ) + { + if ( m_pTopBar ) + { + m_pTopBar->SetBgColor( Color( 255, 255, 255, 0 ) ); + } + if ( m_pBottomBarBlank ) + { + m_pBottomBarBlank->SetVisible( false ); + } + } + + if ( m_bCoaching ) + { + if ( m_pClassOrTeamLabel && m_pClassOrTeamLabel->IsVisible() ) + { + m_pClassOrTeamLabel->SetVisible( false ); + } + + if ( m_pClassOrTeamKeyLabel && m_pClassOrTeamKeyLabel->IsVisible() ) + { + m_pClassOrTeamKeyLabel->SetVisible( false ); + } + } + + // Stay the same visibility as before the scheme reload. + SetVisible( bVisible ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + KeyValues *pItemKV = inResourceData->FindKey( "playerpanels_kv" ); + if ( pItemKV ) + { + if ( m_pPlayerPanelKVs ) + { + m_pPlayerPanelKVs->deleteThis(); + } + m_pPlayerPanelKVs = new KeyValues("playerpanels_kv"); + pItemKV->CopySubkeys( m_pPlayerPanelKVs ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFSpectatorGUI::NeedsUpdate( void ) +{ + if ( !C_BasePlayer::GetLocalPlayer() ) + return false; + + if( IsVisible() ) + return true; + + return BaseClass::NeedsUpdate(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::Update() +{ + BaseClass::Update(); + + UpdateReinforcements(); + UpdateKeyLabels(); + + if ( m_flNextItemPanelUpdate < gpGlobals->curtime ) + { + UpdateItemPanel(); + } + + // If we need to flip tournament mode, do it now + if ( m_bPrevTournamentMode != InTournamentGUI() ) + { + InvalidateLayout( false, true ); + } + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( pLocalPlayer && m_bCoaching != pLocalPlayer->m_bIsCoaching ) + { + m_bCoaching = pLocalPlayer->m_bIsCoaching; + InvalidateLayout( false, true ); + } + if ( pLocalPlayer && pLocalPlayer->m_hStudent && m_bCoaching ) + { + Vector vecTarget = pLocalPlayer->m_hStudent->GetAbsOrigin(); + Vector vecDelta = pLocalPlayer->GetAbsOrigin() - vecTarget; + float flDistance = vecDelta.Length(); + const float kInchesToMeters = 0.0254f; + int distance = RoundFloatToInt( flDistance * kInchesToMeters ); + wchar_t wzValue[32]; + _snwprintf( wzValue, ARRAYSIZE( wzValue ), L"%u", distance ); + wchar_t wzText[256]; + g_pVGuiLocalize->ConstructString_safe( wzText, g_pVGuiLocalize->Find( "#TR_DistanceToStudent" ), 1, wzValue ); + SetDialogVariable( "student_distance", wzText ); + } + + if ( m_flNextPlayerPanelUpdate < gpGlobals->curtime ) + { + RecalculatePlayerPanels(); + m_flNextPlayerPanelUpdate = gpGlobals->curtime + 0.1f; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::UpdateReinforcements( void ) +{ + if( !m_pReinforcementsLabel ) + return; + + C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( !pPlayer || pPlayer->IsHLTV() || + ( pPlayer->GetTeamNumber() != TF_TEAM_RED && pPlayer->GetTeamNumber() != TF_TEAM_BLUE ) || + ( pPlayer->m_Shared.GetState() != TF_STATE_OBSERVER && pPlayer->m_Shared.GetState() != TF_STATE_DYING ) || + ( pPlayer->GetObserverMode() == OBS_MODE_FREEZECAM ) ) + { + m_pReinforcementsLabel->SetVisible( false ); + m_pBuyBackLabel->SetVisible( false ); + + return; + } + + bool bBuyBackVisible = false; + wchar_t wLabel[256]; + + if ( TFGameRules()->InStalemate() ) + { + if ( TFGameRules()->IsInArenaMode() == true ) + { + g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( "#TF_Arena_NoRespawning" ), 0 ); + } + else + { + g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( "#game_respawntime_stalemate" ), 0 ); + } + } + else if ( TFGameRules()->State_Get() == GR_STATE_TEAM_WIN ) + { + // a team has won the round + g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( "#game_respawntime_next_round" ), 0 ); + } + else + { + float flNextRespawn = 0.f; + bool bQuickSpawn = TFGameRules() && TFGameRules()->IsMannVsMachineMode() && pPlayer->IsPlayerClass( TF_CLASS_SCOUT ); + + if ( g_TF_PR ) + { + flNextRespawn = g_TF_PR->GetNextRespawnTime( pPlayer->entindex() ); + } + else if ( !bQuickSpawn ) + { + flNextRespawn = TFGameRules()->GetNextRespawnWave( pPlayer->GetTeamNumber(), pPlayer ); + } + + if ( !flNextRespawn ) + { + m_pReinforcementsLabel->SetVisible( false ); + m_pBuyBackLabel->SetVisible( false ); + return; + } + + int iRespawnWait = (flNextRespawn - gpGlobals->curtime); + if ( iRespawnWait <= 0 ) + { + g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find("#game_respawntime_now" ), 0 ); + } + else if ( iRespawnWait <= 1.0 ) + { + g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find("#game_respawntime_in_sec" ), 0 ); + } + else + { + char szSecs[6]; + wchar_t wSecs[4]; + + if ( TFGameRules()->IsMannVsMachineMode() ) + { + bool bNewMethod = tf_mvm_buybacks_method.GetBool(); + bBuyBackVisible = ( bNewMethod ) ? g_TF_PR->GetNumBuybackCredits( pPlayer->entindex() ) : true; + + if ( bBuyBackVisible ) + { + // When using the new system, we display "Hit '%use_action_slot_item%' to RESPAWN INSTANTLY! (%s1 remaining this wave)" + // When using the old system, we display "Hit '%use_action_slot_item%' to pay %s1 credits and RESPAWN INSTANTLY!" + int nCost = ( bNewMethod ) ? g_TF_PR->GetNumBuybackCredits( pPlayer->entindex() ) : iRespawnWait * MVM_BUYBACK_COST_PER_SEC; + const char *pszString = ( bNewMethod ) ? "#TF_PVE_Buyback_Fixed" : "#TF_PVE_Buyback"; + + Q_snprintf( szSecs, sizeof( szSecs ), "%d", nCost ); + g_pVGuiLocalize->ConvertANSIToUnicode( szSecs, wSecs, sizeof( wSecs ) ); + g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( pszString ), 1, wSecs ); + + wchar_t wBuyBack[256]; + UTIL_ReplaceKeyBindings( wLabel, 0, wBuyBack, sizeof( wBuyBack ), GAME_ACTION_SET_SPECTATOR ); + + m_pBuyBackLabel->SetText( wBuyBack, true ); + } + } + + Q_snprintf( szSecs, sizeof(szSecs), "%d", iRespawnWait ); + + g_pVGuiLocalize->ConvertANSIToUnicode(szSecs, wSecs, sizeof(wSecs)); + g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find("#game_respawntime_in_secs" ), 1, wSecs ); + } + } + + m_pReinforcementsLabel->SetVisible( true ); + m_pReinforcementsLabel->SetText( wLabel, true ); + m_pBuyBackLabel->SetVisible( bBuyBackVisible ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::UpdateKeyLabels( void ) +{ + C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer(); + + bool bSteamController = ::input->IsSteamControllerActive(); + + if ( InTournamentGUI() == false ) + { + // get the desired player class + int iClass = TF_CLASS_UNDEFINED; + bool bIsHLTV = engine->IsHLTV(); + + if ( pPlayer ) + { + iClass = pPlayer->m_Shared.GetDesiredPlayerClassIndex(); + } + + // if it's time to change the tip, or the player has changed desired class, update the tip + if ( ( gpGlobals->curtime >= m_flNextTipChangeTime ) || ( iClass != m_iTipClass ) ) + { + if ( bIsHLTV ) + { + const wchar_t *wzTip = g_pVGuiLocalize->Find( "#Tip_HLTV" ); + + if ( wzTip ) + { + SetDialogVariable( "tip", wzTip ); + } + } + else + { + wchar_t wzTipLabel[512]=L""; + const wchar_t *wzTip = g_TFTips.GetNextClassTip( iClass ); + Assert( wzTip && wzTip[0] ); + g_pVGuiLocalize->ConstructString_safe( wzTipLabel, g_pVGuiLocalize->Find( "#Tip_Fmt" ), 1, wzTip ); + SetDialogVariable( "tip", wzTipLabel ); + } + + m_flNextTipChangeTime = gpGlobals->curtime + 10.0f; + m_iTipClass = iClass; + } + + if ( m_pClassOrTeamLabel ) + { + if ( pPlayer ) + { + static wchar_t wzFinal[512] = L""; + const wchar_t *wzTemp = NULL; + const wchar_t *wzIcon = nullptr; + + if ( TFGameRules() && TFGameRules()->IsInTraining() ) + { + wzTemp = L""; + wzIcon = L""; + } + else if ( bIsHLTV ) + { + wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_AutoDirector" ); + } + else if ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR && TFGameRules()->IsInArenaMode() == false ) + { + if ( bSteamController ) + { + wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_ChangeTeam_NoKey" ); + wzIcon = GetSCGlyph( "changeteam" ); + } + else + { + wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_ChangeTeam" ); + } + } + else + { + if ( bSteamController ) + { + wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_ChangeClass_NoKey" ); + wzIcon = GetSCGlyph( "changeclass" ); + } + else + { + wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_ChangeClass" ); + } + } + + if ( wzTemp ) + { + UTIL_ReplaceKeyBindings( wzTemp, 0, wzFinal, sizeof( wzFinal ) ); + } + + m_pClassOrTeamLabel->SetText( wzFinal, true ); + + if ( m_pClassOrTeamKeyLabel ) + { + if ( wzIcon && m_pClassOrTeamLabel->IsVisible() ) + { + m_pClassOrTeamKeyLabel->SetText( wzIcon ); + m_pClassOrTeamKeyLabel->SetVisible( true ); + } + else + { + m_pClassOrTeamKeyLabel->SetVisible( false ); + } + } + } + } + + static ConVarRef cl_hud_minmode( "cl_hud_minmode", true ); + if ( m_bCoaching == true || ( cl_hud_minmode.IsValid() && ( cl_hud_minmode.GetBool() == false ) ) ) + { + if ( m_pSwitchCamModeKeyLabel ) + { + if ( ( pPlayer && pPlayer->GetTeamNumber() > TEAM_SPECTATOR ) && ( ( mp_forcecamera.GetInt() == OBS_ALLOW_TEAM ) || ( mp_forcecamera.GetInt() == OBS_ALLOW_NONE ) || mp_fadetoblack.GetBool() ) ) + { + if ( m_pSwitchCamModeKeyLabel->IsVisible() ) + { + m_pSwitchCamModeKeyLabel->SetVisible( false ); + + Label *pLabel = dynamic_cast<Label *>( FindChildByName( "SwitchCamModeLabel" ) ); + if ( pLabel ) + { + pLabel->SetVisible( false ); + } + } + } + else + { + if ( !m_pSwitchCamModeKeyLabel->IsVisible() ) + { + m_pSwitchCamModeKeyLabel->SetVisible( true ); + + Label *pLabel = dynamic_cast<Label *>( FindChildByName( "SwitchCamModeLabel" ) ); + if ( pLabel ) + { + pLabel->SetVisible( true ); + } + } + + wchar_t wLabel[256] = L""; + const wchar_t *wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_SwitchCamModeKey" ); + UTIL_ReplaceKeyBindings( wzTemp, 0, wLabel, sizeof( wLabel ) ); + m_pSwitchCamModeKeyLabel->SetText( wLabel, true ); + } + } + + if ( m_pCycleTargetFwdKeyLabel ) + { + if ( ( pPlayer && pPlayer->GetTeamNumber() > TEAM_SPECTATOR ) && ( mp_fadetoblack.GetBool() || ( mp_forcecamera.GetInt() == OBS_ALLOW_NONE ) ) ) + { + if ( m_pCycleTargetFwdKeyLabel->IsVisible() ) + { + m_pCycleTargetFwdKeyLabel->SetVisible( false ); + + Label *pLabel = dynamic_cast<Label *>( FindChildByName( "CycleTargetFwdLabel" ) ); + if ( pLabel ) + { + pLabel->SetVisible( false ); + } + } + } + else + { + if ( !m_pCycleTargetFwdKeyLabel->IsVisible() ) + { + m_pCycleTargetFwdKeyLabel->SetVisible( true ); + + Label *pLabel = dynamic_cast<Label *>( FindChildByName( "CycleTargetFwdLabel" ) ); + if ( pLabel ) + { + pLabel->SetVisible( true ); + } + } + + if ( !bSteamController ) + { + wchar_t wLabel[256] = L""; + const wchar_t *wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_CycleTargetFwdKey" ); + UTIL_ReplaceKeyBindings( wzTemp, 0, wLabel, sizeof( wLabel ) ); + m_pCycleTargetFwdKeyLabel->SetText( wLabel, true ); + } + else + { + m_pCycleTargetFwdKeyLabel->SetText( GetSCGlyph( "next_target" ) ); + } + } + } + + if ( m_pCycleTargetRevKeyLabel ) + { + if ( ( pPlayer && pPlayer->GetTeamNumber() > TEAM_SPECTATOR ) && ( mp_fadetoblack.GetBool() || ( mp_forcecamera.GetInt() == OBS_ALLOW_NONE ) ) ) + { + if ( m_pCycleTargetRevKeyLabel->IsVisible() ) + { + m_pCycleTargetRevKeyLabel->SetVisible( false ); + + Label *pLabel = dynamic_cast<Label *>( FindChildByName( "CycleTargetRevLabel" ) ); + if ( pLabel ) + { + pLabel->SetVisible( false ); + } + } + } + else + { + if ( !m_pCycleTargetRevKeyLabel->IsVisible() ) + { + m_pCycleTargetRevKeyLabel->SetVisible( true ); + + Label *pLabel = dynamic_cast<Label *>( FindChildByName( "CycleTargetRevLabel" ) ); + if ( pLabel ) + { + pLabel->SetVisible( true ); + } + } + + if ( !bSteamController ) + { + wchar_t wLabel[256] = L""; + const wchar_t *wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_CycleTargetRevKey" ); + UTIL_ReplaceKeyBindings( wzTemp, 0, wLabel, sizeof( wLabel ) ); + m_pCycleTargetRevKeyLabel->SetText( wLabel, true ); + } + else + { + m_pCycleTargetRevKeyLabel->SetText( GetSCGlyph( "prev_target" ) ); + } + } + } + + if ( m_pMapLabel ) + { + wchar_t wMapName[32]; + wchar_t wLabel[256]; + char szMapName[32]; + + char tempname[128]; + Q_FileBase( engine->GetLevelName(), tempname, sizeof( tempname ) ); + Q_strlower( tempname ); + + if ( IsX360() ) + { + char *pExt = Q_stristr( tempname, ".360" ); + if ( pExt ) + { + *pExt = '\0'; + } + } + + Q_strncpy( szMapName, GetMapDisplayName( tempname ), sizeof( szMapName ) ); + + g_pVGuiLocalize->ConvertANSIToUnicode( szMapName, wMapName, sizeof(wMapName)); + g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( "#Spec_Map" ), 1, wMapName ); + + m_pMapLabel->SetText( wLabel ); + } + } + } + + // coaching stuff + if ( pPlayer && pPlayer->m_hStudent ) + { + int iHealth = 0; + int iMaxHealth = 1; + int iMaxBuffedHealth = 0; + + C_TFPlayer *pStudent = pPlayer->m_hStudent; + { + wchar_t wPlayerName[MAX_PLAYER_NAME_LENGTH]; + wchar_t wLabel[256]; + const char* pStudentName = g_TF_PR->GetPlayerName( pStudent->entindex() ); + + g_pVGuiLocalize->ConvertANSIToUnicode( pStudentName, wPlayerName, sizeof(wPlayerName)); + g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( "#TF_Coach_Student_Prefix" ), 1, wPlayerName ); + + SetDialogVariable( "student_name", wLabel ); + } + for ( int i = 1; i <= 2; ++i ) + { + wchar_t wLabel[256] = L""; + const wchar_t *wzTemp = g_pVGuiLocalize->Find( CFmtStr1024( "#TF_Coach_Slot%uLabel", i ) ); + UTIL_ReplaceKeyBindings( wzTemp, 0, wLabel, sizeof( wLabel ) ); + SetDialogVariable( CFmtStr1024( "coach_command_%u", i ), wLabel ); + } + if ( m_pAvatar ) + { + m_pAvatar->SetShouldDrawFriendIcon( false ); + + if ( steamapicontext && steamapicontext->SteamUser() ) + { + CSteamID studentSteamID; + if ( pStudent->GetSteamID( &studentSteamID ) ) + { + m_pAvatar->SetPlayer( studentSteamID, k_EAvatarSize64x64 ); + } + else + { + m_pAvatar->ClearAvatar(); + } + } + } + + // don't show crosshair when viewing the world from the student's POV + bool bShowCrosshair = pPlayer->GetObserverMode() != OBS_MODE_IN_EYE; + vgui::Panel *pCrosshair = FindChildByName( "Crosshair" ); + if ( pCrosshair && pCrosshair->IsVisible() != bShowCrosshair ) + { + pCrosshair->SetVisible( bShowCrosshair ); + } + + iHealth = pStudent->GetHealth(); + iMaxHealth = pStudent->GetMaxHealth(); + iMaxBuffedHealth = pStudent->m_Shared.GetMaxBuffedHealth(); + + if ( m_pStudentHealth ) + { + m_pStudentHealth->SetHealth( iHealth, iMaxHealth, iMaxBuffedHealth ); + + if ( !m_pStudentHealth->IsVisible() ) + { + m_pStudentHealth->SetVisible( true ); + } + } + } + else + { + if ( m_pStudentHealth && m_pStudentHealth->IsVisible() ) + { + m_pStudentHealth->SetVisible( false ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::ShowPanel(bool bShow) +{ + if ( bShow != IsVisible() ) + { + CTFHudObjectiveStatus *pStatus = GET_HUDELEMENT( CTFHudObjectiveStatus ); + CHudBuildingStatusContainer_Engineer *pEngBuilds = GET_NAMED_HUDELEMENT( CHudBuildingStatusContainer_Engineer, BuildingStatus_Engineer ); + CHudBuildingStatusContainer_Spy *pSpyBuilds = GET_NAMED_HUDELEMENT( CHudBuildingStatusContainer_Spy, BuildingStatus_Spy ); + CHudItemAttributeTracker *pAttribTrackers = GET_HUDELEMENT( CHudItemAttributeTracker ); + + if ( pAttribTrackers ) + { + pAttribTrackers->InvalidateLayout(); + } + + if ( bShow ) + { + int xPos = 0, yPos = 0; + + if ( pStatus ) + { + pStatus->SetParent( this ); + pStatus->SetProportional( true ); + } + + if ( pEngBuilds ) + { + pEngBuilds->GetPos( xPos, yPos ); + m_nEngBuilds_xpos = xPos; + m_nEngBuilds_ypos = yPos; + pEngBuilds->SetPos( xPos, GetTopBarHeight() ); + } + + if ( pSpyBuilds ) + { + pSpyBuilds->GetPos( xPos, yPos ); + m_nSpyBuilds_xpos = xPos; + m_nSpyBuilds_ypos = yPos; + pSpyBuilds->SetPos( xPos, GetTopBarHeight() ); + } + +#if defined( REPLAY_ENABLED ) + // We don't want to display this message the first time the spectator GUI is shown, since that is right + // when the class menu is shown. We use the player spawn cache here - which is a terrible name for something + // very useful - which will nuke m_nDisplaySaveReplay every time a new map is loaded, which is exactly what + // we want. + int &nDisplaySaveReplay = CPlayerSpawnCache::Instance().m_Data.m_nDisplaySaveReplay; + extern IReplayManager *g_pReplayManager; + CReplay *pCurLifeReplay = ( g_pReplayManager ) ? g_pReplayManager->GetReplayForCurrentLife() : NULL; + if ( g_pReplay->IsRecording() && + !engine->IsPlayingDemo() && + !::input->IsSteamControllerActive() && + nDisplaySaveReplay && + ( pCurLifeReplay && !pCurLifeReplay->m_bRequestedByUser && !pCurLifeReplay->m_bSaved ) ) + { + wchar_t wText[256]; + wchar wKeyBind[80]; + char szText[256 * sizeof(wchar_t)]; + + const char *pSaveReplayKey = engine->Key_LookupBinding( "save_replay" ); + if ( !pSaveReplayKey ) + { + pSaveReplayKey = "< not bound >"; + } + g_pVGuiLocalize->ConvertANSIToUnicode( pSaveReplayKey, wKeyBind, sizeof( wKeyBind ) ); + g_pVGuiLocalize->ConstructString_safe( wText, g_pVGuiLocalize->Find( "#Replay_SaveThisLifeMsg" ), 1, wKeyBind ); + g_pVGuiLocalize->ConvertUnicodeToANSI( wText, szText, sizeof( szText ) ); + + g_pClientMode->DisplayReplayMessage( szText, -1.0f, false, NULL, false ); + } + ++nDisplaySaveReplay; +#endif + + m_flNextTipChangeTime = 0; // force a new tip immediately + + InvalidateLayout(); + } + else + { + if ( pStatus ) + { + pStatus->SetParent( g_pClientMode->GetViewport() ); + } + + if ( pEngBuilds ) + { + pEngBuilds->SetPos( m_nEngBuilds_xpos, m_nEngBuilds_ypos ); + } + + if ( pSpyBuilds ) + { + pSpyBuilds->SetPos( m_nSpyBuilds_xpos, m_nSpyBuilds_ypos ); + } + } + + UpdateKeyLabels(); + + if ( bShow ) + { + m_flNextPlayerPanelUpdate = 0; + m_flNextItemPanelUpdate = 0; + UpdateItemPanel(); + RecalculatePlayerPanels(); + } + } + + BaseClass::ShowPanel( bShow ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::FireGameEvent( IGameEvent *event ) +{ + const char *pEventName = event->GetName(); + + if ( Q_strcmp( "spec_target_updated", pEventName ) == 0 ) + { + UpdateItemPanel(); + } + else if ( Q_strcmp( "player_death", pEventName ) == 0 && m_bCoaching ) + { + CBaseEntity *pVictim = ClientEntityList().GetEnt( engine->GetPlayerForUserID( event->GetInt("userid") ) ); + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( pLocalPlayer && ( pVictim == pLocalPlayer->m_hStudent ) ) + { + CEconNotification *pNotification = new CEconNotification(); + pNotification->SetText( "#TF_Coach_StudentHasDied" ); + pNotification->SetLifetime( 10.0f ); + pNotification->SetSoundFilename( "coach/coach_student_died.wav" ); + NotificationQueue_Add( pNotification ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::UpdateItemPanel( bool bForce ) +{ + bool bVisible = false; + + // Stat panel prevents the item panel from showing. + CTFStatPanel *pStatPanel = GET_HUDELEMENT( CTFStatPanel ); + if ( ( pStatPanel && pStatPanel->IsVisible() ) || ( TFGameRules() && TFGameRules()->State_Get() == GR_STATE_TEAM_WIN ) || (!cl_spec_carrieditems.GetBool() && !bForce) ) + { + bVisible = false; + } + else + { + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( pLocalPlayer ) + { + C_TFPlayer *pPlayer = ToTFPlayer( pLocalPlayer->GetObserverTarget() ); + if ( pPlayer && pPlayer != pLocalPlayer ) + { + if ( m_flNextItemPanelUpdate && m_flNextItemPanelUpdate > gpGlobals->curtime && m_hPrevItemPlayer == pPlayer ) + return; + + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && ( pPlayer->GetTeamNumber() == TF_TEAM_PVE_INVADERS ) ) + return; + + if ( m_hPrevItemPlayer != pPlayer ) + { + m_iPrevItemShown = 0; + m_iFirstItemShown = 0; + m_bShownItems = false; + m_hPrevItemPlayer = pPlayer; + } + m_flNextItemPanelUpdate = gpGlobals->curtime + 8.0; + + // Don't reshow the items for a player unless we're being forced to + if ( !m_bShownItems || bForce ) + { + // If our killer is using a non-standard item, display its stats. + // Loop through all items and pick one at random, so that we show non-active weapons as well. + CEconItemView *pItemToShow = pPlayer->GetInspectItem( &m_iPrevItemShown ); + + // If we've looped, we're done. Hide the item. + if ( m_iFirstItemShown && m_iFirstItemShown == m_iPrevItemShown ) + { + m_iFirstItemShown = 0; + m_iPrevItemShown = 0; + pItemToShow = NULL; + m_bShownItems = true; + } + + if ( pItemToShow ) + { + if ( !m_iFirstItemShown ) + { + m_iFirstItemShown = m_iPrevItemShown; + } + + Label* pItemLabel = m_pItemPanel->FindControl<Label>( "ItemLabel" ); + + // Change the label text depending on if the original owner is holding the weapon + if ( pItemLabel ) + { + CSteamID steamIDOwner; + pPlayer->GetSteamID( &steamIDOwner ); + bool bOriginalOwner = steamIDOwner.GetAccountID() == pItemToShow->GetAccountID(); + pItemLabel->SetText( bOriginalOwner ? "#FreezePanel_Item" : "#FreezePanel_ItemOtherOwner" ); + } + + + bVisible = true; + m_pItemPanel->SetDialogVariable( "killername", g_TF_PR->GetPlayerName( pPlayer->entindex() ) ); + + // Set the item owner's name + CBasePlayer *pOriginalOwner = GetPlayerByAccountID( pItemToShow->GetAccountID() ); + if ( pOriginalOwner ) + { + m_pItemPanel->SetDialogVariable( "ownername", g_TF_PR->GetPlayerName( pOriginalOwner->entindex() ) ); + } + + m_pItemPanel->SetItem( pItemToShow ); + + // force update description to get the correct panel size + m_pItemPanel->UpdateDescription(); + m_pItemPanel->SetPos( ScreenWidth() - XRES( 10 ) - m_pItemPanel->GetWide(), ScreenHeight() - YRES( 12 ) - m_pItemPanel->GetTall() ); + } + } + } + } + } + + if ( m_pItemPanel->IsVisible() != bVisible ) + { + m_pItemPanel->SetVisible( bVisible ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::ForceItemPanelCycle( void ) +{ + m_flNextItemPanelUpdate = 0; + UpdateItemPanel( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CTFSpectatorGUI::GetResFile( void ) +{ + if ( m_bCoaching ) + { + return "Resource/UI/SpectatorCoach.res"; + } + else if ( InTournamentGUI() ) + { + return "Resource/UI/SpectatorTournament.res"; + } + else if ( ::input->IsSteamControllerActive() ) + { + return "Resource/UI/Spectator_SC.res"; + } + else + { + return "Resource/UI/Spectator.res"; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFSpectatorGUI::InTournamentGUI( void ) +{ + bool bOverride = false; + + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) + { + bOverride = true; + } + + return ( TFGameRules()->IsInTournamentMode() && !TFGameRules()->IsCompetitiveMode() && ( cl_use_tournament_specgui.GetBool() || bOverride ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::RecalculatePlayerPanels( void ) +{ + if ( !InTournamentGUI() ) + { + if ( m_PlayerPanels.Count() > 0 ) + { + // Delete any player panels we have, we've turned off the tourney GUI. + for ( int i = m_PlayerPanels.Count()-1; i >= 0; i-- ) + { + m_PlayerPanels[i]->MarkForDeletion(); + } + m_PlayerPanels.Purge(); + } + + return; + } + + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if ( !pPlayer || !g_TF_PR || !TFGameRules() ) + return; + + int iLocalTeam = pPlayer->GetTeamNumber(); + bool bMvM = TFGameRules()->IsMannVsMachineMode(); + + // Calculate the number of players that must be shown. Spectators see all players (except in MvM), team members only see their team. + int iPanel = 0; + + for ( int nClass = TF_FIRST_NORMAL_CLASS; nClass <= TF_LAST_NORMAL_CLASS; nClass++ ) + { + // we want to sort the images to match the class menu selections + int nCurrentClass = g_ClassDefinesRemap[nClass]; + + for ( int i = 0; i < MAX_PLAYERS; i++ ) + { + int iPlayer = i+1; + if ( !g_TF_PR->IsConnected( iPlayer ) ) + continue; + + bool bHideBots = false; + + #ifndef _DEBUG + if ( bMvM ) + { + bHideBots = true; + } + #endif + int iTeam = g_TF_PR->GetTeam( iPlayer ); + if ( iTeam != iLocalTeam && iLocalTeam != TEAM_SPECTATOR ) + continue; + if ( iTeam != TF_TEAM_RED && iTeam != TF_TEAM_BLUE ) + continue; + if ( g_TF_PR->IsFakePlayer( iPlayer ) && bHideBots ) + continue; + if ( bMvM && ( iTeam == TF_TEAM_PVE_INVADERS ) ) + continue; + if ( g_TF_PR->GetPlayerClass( iPlayer ) != nCurrentClass ) + continue; + + if ( m_PlayerPanels.Count() <= iPanel ) + { + CTFPlayerPanel *pPanel = new CTFPlayerPanel( this, VarArgs("playerpanel%d", i) ); + if ( m_pPlayerPanelKVs ) + { + pPanel->ApplySettings( m_pPlayerPanelKVs ); + } + m_PlayerPanels.AddToTail( pPanel ); + } + + m_PlayerPanels[iPanel]->SetPlayerIndex( iPlayer ); + iPanel++; + } + } + + for ( int i = iPanel; i < m_PlayerPanels.Count(); i++ ) + { + m_PlayerPanels[i]->SetPlayerIndex( 0 ); + } + + UpdatePlayerPanels(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::UpdatePlayerPanels( void ) +{ + if ( !g_TF_PR ) + return; + + uint nVisible = 0; + bool bNeedsPlayerLayout = false; + for ( int i = 0; i < m_PlayerPanels.Count(); i++ ) + { + if ( m_PlayerPanels[i]->Update() ) + { + bNeedsPlayerLayout = true; + } + + if ( m_PlayerPanels[i]->IsVisible() ) + { + nVisible++; + } + } + + if ( !bNeedsPlayerLayout ) + return; + + // Try and always put the local player's team on team1, if he's in a team + int iTeam1 = TF_TEAM_BLUE; + int iTeam2 = TF_TEAM_RED; + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if ( !pLocalPlayer ) + return; + + int iLocalTeam = g_TF_PR->GetTeam( pLocalPlayer->entindex() ); + + if ( iLocalTeam == TF_TEAM_RED || iLocalTeam == TF_TEAM_BLUE ) + { + iTeam1 = iLocalTeam; + iTeam2 = ( iTeam1 == TF_TEAM_BLUE ) ? TF_TEAM_RED : TF_TEAM_BLUE; + } + + int iTeam1Count = 0; + int iTeam2Count = 0; + int iCenter = GetWide() * 0.5; + int iYPosOverride = 0; + + // We only want to draw the player panels for the defenders in MvM + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) + { + iTeam1 = TF_TEAM_PVE_DEFENDERS; + iTeam2 = TF_TEAM_PVE_INVADERS; + + int iNumValidPanels = 0; + for ( int i = 0; i < m_PlayerPanels.Count(); i++ ) + { + if ( m_PlayerPanels[i]->GetPlayerIndex() <= 0 ) + { + continue; + } + + iNumValidPanels++; + } + + // we'll center the group of players in MvM + m_iTeam1PlayerBaseOffsetX = ( iNumValidPanels * m_iTeam1PlayerDeltaX ) * -0.5; + + if ( iLocalTeam > LAST_SHARED_TEAM ) + { + if ( m_pTopBar ) + { + iYPosOverride = m_pTopBar->GetTall(); + } + } + } + + for ( int i = 0; i < m_PlayerPanels.Count(); i++ ) + { + int iXPos = 0; + int iYPos = 0; + + if ( m_PlayerPanels[i]->GetPlayerIndex() <= 0 ) + { + m_PlayerPanels[i]->SetVisible( false ); + continue; + } + + int iTeam = g_TF_PR->GetTeam( m_PlayerPanels[i]->GetPlayerIndex() ); + if ( iTeam == iTeam1 ) + { + iXPos = m_iTeam1PlayerBaseOffsetX ? (iCenter + m_iTeam1PlayerBaseOffsetX) : m_iTeam1PlayerBaseX; + iXPos += (iTeam1Count * m_iTeam1PlayerDeltaX); + iYPos = iYPosOverride ? iYPosOverride : m_iTeam1PlayerBaseY + (iTeam1Count * m_iTeam1PlayerDeltaY); + m_PlayerPanels[i]->SetSpecIndex( 6 - iTeam1Count ); + m_PlayerPanels[i]->SetPos( iXPos, iYPos ); + m_PlayerPanels[i]->SetVisible( true ); + iTeam1Count++; + } + else if ( iTeam == iTeam2 ) + { + iXPos = m_iTeam2PlayerBaseOffsetX ? (iCenter + m_iTeam2PlayerBaseOffsetX) : m_iTeam2PlayerBaseX; + iXPos += (iTeam2Count * m_iTeam2PlayerDeltaX); + iYPos = iYPosOverride ? iYPosOverride : m_iTeam2PlayerBaseY + (iTeam2Count * m_iTeam2PlayerDeltaY); + m_PlayerPanels[i]->SetSpecIndex( 7 + iTeam2Count ); + m_PlayerPanels[i]->SetPos( iXPos, iYPos ); + m_PlayerPanels[i]->SetVisible( true ); + iTeam2Count++; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::SelectSpec( int iSlot ) +{ + if ( m_bCoaching ) + { + engine->ClientCmd_Unrestricted( CFmtStr1024( "coach_command %u", iSlot ) ); + return; + } + + if ( !InTournamentGUI() ) + return; + + for ( int i = 0; i < m_PlayerPanels.Count(); i++ ) + { + if ( m_PlayerPanels[i]->GetSpecIndex() == iSlot ) + { + engine->ClientCmd_Unrestricted( VarArgs( "spec_player %d\n", m_PlayerPanels[i]->GetPlayerIndex() ) ); + return; + } + } +} diff --git a/game/client/tf/vgui/tf_spectatorgui.h b/game/client/tf/vgui/tf_spectatorgui.h new file mode 100644 index 0000000..813276e --- /dev/null +++ b/game/client/tf/vgui/tf_spectatorgui.h @@ -0,0 +1,140 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef TF_SPECTATORGUI_H +#define TF_SPECTATORGUI_H +#ifdef _WIN32 +#pragma once +#endif + +#include <spectatorgui.h> +#include "hudelement.h" +#include "tf_hud_playerstatus.h" +#include "item_model_panel.h" +#include "tf_gamerules.h" +#include <vgui_controls/EditablePanel.h> + +extern ConVar cl_use_tournament_specgui; +class CAvatarImagePanel; +class CTFPlayerPanel; + +//----------------------------------------------------------------------------- +// Purpose: Custom health panel used to show spectator target's health +//----------------------------------------------------------------------------- +class CTFSpectatorGUIHealth : public CTFHudPlayerHealth +{ +public: + CTFSpectatorGUIHealth( Panel *parent, const char *name ) : CTFHudPlayerHealth( parent, name ) + { + } + + virtual const char *GetResFilename( void ) + { + return "resource/UI/SpectatorGUIHealth.res"; + } + virtual void OnThink() + { + // Do nothing. We're just preventing the base health panel from updating. + } +}; + + +//----------------------------------------------------------------------------- +// Purpose: TF Spectator UI +//----------------------------------------------------------------------------- +class CTFSpectatorGUI : public CSpectatorGUI, public CGameEventListener +{ +private: + DECLARE_CLASS_SIMPLE( CTFSpectatorGUI, CSpectatorGUI ); + +public: + CTFSpectatorGUI( IViewPort *pViewPort ); + ~CTFSpectatorGUI( void ); + + virtual void Reset( void ); + virtual void PerformLayout( void ); + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void ApplySettings( KeyValues *inResourceData ); + + virtual void Update( void ); + virtual bool NeedsUpdate( void ); + virtual bool ShouldShowPlayerLabel( int specmode ) { return false; } + void UpdateReinforcements( void ); + virtual void ShowPanel(bool bShow); + virtual Color GetBlackBarColor( void ) { return Color(52,48,45, 255); } + + void UpdateKeyLabels( void ); + + virtual void FireGameEvent( IGameEvent *event ); + void UpdateItemPanel( bool bForce = false ); + void ForceItemPanelCycle( void ); + virtual const char *GetResFile( void ); + + // Tournament mode handling + bool InTournamentGUI( void ); + void RecalculatePlayerPanels( void ); + void UpdatePlayerPanels( void ); + void SelectSpec( int iSlot ); + + virtual int GetTopBarHeight(); + + virtual GameActionSet_t GetPreferredActionSet() { return GAME_ACTION_SET_SPECTATOR; } + +protected: + int m_nLastSpecMode; + float m_flNextTipChangeTime; // time at which to next change the tip + int m_iTipClass; // class that current tip is for + + // used to store the x and y position of the Engy and Spy build panels so we can reset them when the spec panel goes away + int m_nEngBuilds_xpos; + int m_nEngBuilds_ypos; + int m_nSpyBuilds_xpos; + int m_nSpyBuilds_ypos; + + int m_nMannVsMachineStatus_xpos; + int m_nMannVsMachineStatus_ypos; + + vgui::Label *m_pReinforcementsLabel; + CExLabel *m_pBuyBackLabel; + vgui::Label *m_pClassOrTeamLabel; + CExLabel *m_pClassOrTeamKeyLabel; + vgui::Label *m_pSwitchCamModeKeyLabel; + vgui::Label *m_pCycleTargetFwdKeyLabel; + vgui::Label *m_pCycleTargetRevKeyLabel; + vgui::Label *m_pMapLabel; + CItemModelPanel *m_pItemPanel; + + float m_flNextItemPanelUpdate; + EHANDLE m_hPrevItemPlayer; + int m_iPrevItemShown; + int m_iFirstItemShown; + bool m_bShownItems; + + // Tournament mode player panel handling + CUtlVector<CTFPlayerPanel*> m_PlayerPanels; + KeyValues *m_pPlayerPanelKVs; + bool m_bReapplyPlayerPanelKVs; + bool m_bPrevTournamentMode; + float m_flNextPlayerPanelUpdate; + + // Coaching + bool m_bCoaching; + CAvatarImagePanel *m_pAvatar; + CTFSpectatorGUIHealth *m_pStudentHealth; + + CPanelAnimationVarAliasType( int, m_iTeam1PlayerBaseOffsetX, "team1_player_base_offset_x", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTeam1PlayerBaseX, "team1_player_base_x", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTeam1PlayerBaseY, "team1_player_base_y", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTeam2PlayerBaseX, "team2_player_base_x", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTeam2PlayerBaseOffsetX, "team2_player_base_offset_x", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTeam2PlayerBaseY, "team2_player_base_y", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTeam1PlayerDeltaX, "team1_player_delta_x", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTeam1PlayerDeltaY, "team1_player_delta_y", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTeam2PlayerDeltaX, "team2_player_delta_x", "0", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTeam2PlayerDeltaY, "team2_player_delta_y", "0", "proportional_int" ); +}; + +#endif // TF_SPECTATORGUI_H diff --git a/game/client/tf/vgui/tf_statsummary.cpp b/game/client/tf/vgui/tf_statsummary.cpp new file mode 100644 index 0000000..8e96829 --- /dev/null +++ b/game/client/tf/vgui/tf_statsummary.cpp @@ -0,0 +1,1506 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "cbase.h" + +#include <vgui_controls/Label.h> +#include <vgui_controls/Button.h> +#include <vgui_controls/ComboBox.h> +#include <vgui_controls/ImagePanel.h> +#include <vgui_controls/RichText.h> +#include <vgui_controls/Frame.h> +#include <vgui_controls/QueryBox.h> +#include <vgui/IScheme.h> +#include <vgui/ILocalize.h> +#include <vgui/ISurface.h> +#include "ienginevgui.h" +#include <game/client/iviewport.h> +#include "tf_tips.h" +#include "tf_mapinfo.h" +#include "vgui_avatarimage.h" +#include "VGuiMatSurface/IMatSystemSurface.h" + +#include "tf_statsummary.h" +#include <convar.h> +#include "fmtstr.h" +#include "tf_gamerules.h" +#include "tf_gc_client.h" + +using namespace vgui; + +#if defined( REPLAY_ENABLED ) +extern bool g_bIsReplayRewinding; +#else +bool g_bIsReplayRewinding = false; +#endif + +const char *g_pszTipsClassImages[] = +{ + "", // TF_CLASS_UNDEFINED = 0, + "class_portraits/scout", // TF_CLASS_SCOUT, + "class_portraits/sniper",// TF_CLASS_SNIPER, + "class_portraits/soldier", // TF_CLASS_SOLDIER, + "class_portraits/demoman", // TF_CLASS_DEMOMAN, + "class_portraits/medic", // TF_CLASS_MEDIC, + "class_portraits/heavy", // TF_CLASS_HEAVYWEAPONS, + "class_portraits/pyro", // TF_CLASS_PYRO, + "class_portraits/spy", // TF_CLASS_SPY, + "class_portraits/engineer", // TF_CLASS_ENGINEER, +}; + +ClassDetails_t g_PerClassStatDetails[15] = +{ + { TFSTAT_POINTSSCORED, ALL_CLASSES, "#TF_ClassRecord_MostPoints", "#TF_ClassRecord_Alt_MostPoints" }, + { TFSTAT_KILLS, ALL_CLASSES, "#TF_ClassRecord_MostKills", "#TF_ClassRecord_Alt_MostKills" }, + { TFSTAT_KILLASSISTS, ALL_CLASSES, "#TF_ClassRecord_MostAssists", "#TF_ClassRecord_Alt_MostAssists" }, + { TFSTAT_CAPTURES, ALL_CLASSES, "#TF_ClassRecord_MostCaptures", "#TF_ClassRecord_Alt_MostCaptures" }, + { TFSTAT_DEFENSES, ALL_CLASSES, "#TF_ClassRecord_MostDefenses", "#TF_ClassRecord_Alt_MostDefenses" }, + { TFSTAT_DAMAGE, ALL_CLASSES, "#TF_ClassRecord_MostDamage", "#TF_ClassRecord_Alt_MostDamage" }, + { TFSTAT_BUILDINGSDESTROYED, ALL_CLASSES, "#TF_ClassRecord_MostDestruction", "#TF_ClassRecord_Alt_MostDestruction" }, + { TFSTAT_DOMINATIONS, ALL_CLASSES, "#TF_ClassRecord_MostDominations", "#TF_ClassRecord_Alt_MostDominations" }, + { TFSTAT_PLAYTIME, ALL_CLASSES, "#TF_ClassRecord_LongestLife", "#TF_ClassRecord_Alt_LongestLife" }, + { TFSTAT_HEALING, MAKESTATFLAG(TF_CLASS_MEDIC) | MAKESTATFLAG(TF_CLASS_ENGINEER) | MAKESTATFLAG(TF_CLASS_HEAVYWEAPONS), "#TF_ClassRecord_MostHealing", "#TF_ClassRecord_Alt_MostHealing" }, + { TFSTAT_INVULNS, MAKESTATFLAG(TF_CLASS_MEDIC), "#TF_ClassRecord_MostInvulns", "#TF_ClassRecord_Alt_MostInvulns" }, + { TFSTAT_MAXSENTRYKILLS, MAKESTATFLAG(TF_CLASS_ENGINEER), "#TF_ClassRecord_MostSentryKills", "#TF_ClassRecord_Alt_MostSentryKills" }, + { TFSTAT_TELEPORTS, MAKESTATFLAG(TF_CLASS_ENGINEER), "#TF_ClassRecord_MostTeleports", "#TF_ClassRecord_Alt_MostTeleports" }, + { TFSTAT_HEADSHOTS, MAKESTATFLAG(TF_CLASS_SNIPER) | MAKESTATFLAG(TF_CLASS_SPY), "#TF_ClassRecord_MostHeadshots", "#TF_ClassRecord_Alt_MostHeadshots" }, + { TFSTAT_BACKSTABS, MAKESTATFLAG(TF_CLASS_SPY), "#TF_ClassRecord_MostBackstabs", "#TF_ClassRecord_Alt_MostBackstabs" }, +}; + +ClassDetails_t g_PerClassMVMStatDetails[12] = +{ + { TFSTAT_POINTSSCORED, ALL_CLASSES, "#TF_ClassRecord_MostPoints", "#TF_ClassRecord_Alt_MostPoints" }, + { TFSTAT_KILLS, ALL_CLASSES, "#TF_ClassRecord_MostKills", "#TF_ClassRecord_Alt_MostKills" }, + { TFSTAT_KILLASSISTS, ALL_CLASSES, "#TF_ClassRecord_MostAssists", "#TF_ClassRecord_Alt_MostAssists" }, + { TFSTAT_DEFENSES, ALL_CLASSES, "#TF_ClassRecord_MostDefenses", "#TF_ClassRecord_Alt_MostDefenses" }, + { TFSTAT_DAMAGE, ALL_CLASSES, "#TF_ClassRecord_MostDamage", "#TF_ClassRecord_Alt_MostDamage" }, + { TFSTAT_PLAYTIME, ALL_CLASSES, "#TF_ClassRecord_LongestLife", "#TF_ClassRecord_Alt_LongestLife" }, + { TFSTAT_HEALING, MAKESTATFLAG(TF_CLASS_MEDIC) | MAKESTATFLAG(TF_CLASS_ENGINEER) | MAKESTATFLAG(TF_CLASS_HEAVYWEAPONS), "#TF_ClassRecord_MostHealing", "#TF_ClassRecord_Alt_MostHealing" }, + { TFSTAT_INVULNS, MAKESTATFLAG(TF_CLASS_MEDIC), "#TF_ClassRecord_MostInvulns", "#TF_ClassRecord_Alt_MostInvulns" }, + { TFSTAT_MAXSENTRYKILLS, MAKESTATFLAG(TF_CLASS_ENGINEER), "#TF_ClassRecord_MostSentryKills", "#TF_ClassRecord_Alt_MostSentryKills" }, + { TFSTAT_TELEPORTS, MAKESTATFLAG(TF_CLASS_ENGINEER), "#TF_ClassRecord_MostTeleports", "#TF_ClassRecord_Alt_MostTeleports" }, + { TFSTAT_HEADSHOTS, MAKESTATFLAG(TF_CLASS_SNIPER) | MAKESTATFLAG(TF_CLASS_SPY), "#TF_ClassRecord_MostHeadshots", "#TF_ClassRecord_Alt_MostHeadshots" }, + { TFSTAT_BACKSTABS, MAKESTATFLAG(TF_CLASS_SPY), "#TF_ClassRecord_MostBackstabs", "#TF_ClassRecord_Alt_MostBackstabs" }, +}; + +CTFStatsSummaryPanel *g_pTFStatsSummaryPanel = NULL; + +CUtlVector<CTFStatsSummaryPanel *> g_vecStatPanels; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void UpdateStatSummaryPanels( CUtlVector<ClassStats_t> &vecClassStats ) +{ + for ( int i = 0; i < g_vecStatPanels.Count(); i++ ) + { + g_vecStatPanels[i]->SetStats( vecClassStats ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the global stats summary panel +//----------------------------------------------------------------------------- +CTFStatsSummaryPanel *GStatsSummaryPanel() +{ + if ( NULL == g_pTFStatsSummaryPanel ) + { + g_pTFStatsSummaryPanel = new CTFStatsSummaryPanel(); + } + return g_pTFStatsSummaryPanel; +} + +//----------------------------------------------------------------------------- +// Purpose: Destroys the global stats summary panel +//----------------------------------------------------------------------------- +void DestroyStatsSummaryPanel() +{ + if ( NULL != g_pTFStatsSummaryPanel ) + { + g_pTFStatsSummaryPanel->MarkForDeletion(); + g_pTFStatsSummaryPanel = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CTFStatsSummaryPanel::CTFStatsSummaryPanel() + : BaseClass( NULL, "TFStatsSummary", vgui::scheme()->LoadSchemeFromFile( "Resource/ClientScheme.res", "ClientScheme" ) ) + , m_bShowingLeaderboard( false ) + , m_bLoadingCommunityMap( false ) +{ + Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::Init( void ) +{ + m_bControlsLoaded = false; + m_bInteractive = false; + m_bEmbedded = false; + m_xStartLHBar = 0; + m_xStartRHBar = 0; + m_iBarHeight = 1; + m_iBarMaxWidth = 1; + + m_pPlayerData = new vgui::EditablePanel( this, "statdata" ); + m_pInteractiveHeaders = new vgui::EditablePanel( m_pPlayerData, "InteractiveHeaders" ); + m_pNonInteractiveHeaders = new vgui::EditablePanel( m_pPlayerData, "NonInteractiveHeaders" ); + m_pBarChartComboBoxA = new vgui::ComboBox( m_pInteractiveHeaders, "BarChartComboA", 10, false ); + m_pBarChartComboBoxB = new vgui::ComboBox( m_pInteractiveHeaders, "BarChartComboB", 10, false ); + m_pClassComboBox = new vgui::ComboBox( m_pInteractiveHeaders, "ClassCombo", 10, false ); + m_pTipImage = new CTFImagePanel( this, "TipImage" ); + m_pTipText = new vgui::Label( this, "TipText", "" ); + m_pMapInfoPanel = NULL; + m_pMainBackground = NULL; + m_pLeaderboardTitle = NULL; + m_pContributedPanel = NULL; + +#ifdef _X360 + m_pFooter = new CTFFooter( this, "Footer" ); + m_bShowBackButton = false; +#else + m_pNextTipButton = new vgui::Button( this, "NextTipButton", "" ); + m_pResetStatsButton = new vgui::Button( this, "ResetStatsButton", "" ); + m_pCloseButton = new vgui::Button( this, "CloseButton", "" ); +#endif + + m_pBarChartComboBoxA->AddActionSignalTarget( this ); + m_pBarChartComboBoxB->AddActionSignalTarget( this ); + m_pClassComboBox->AddActionSignalTarget( this ); + + ListenForGameEvent( "server_spawn" ); + + Reset(); + + g_vecStatPanels.AddToTail( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CTFStatsSummaryPanel::CTFStatsSummaryPanel( vgui::Panel *parent ) : BaseClass( parent, "TFStatsSummary", + vgui::scheme()->LoadSchemeFromFile( "Resource/ClientScheme.res", "ClientScheme" ) ) +{ + Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFStatsSummaryPanel::~CTFStatsSummaryPanel() +{ + g_vecStatPanels.FindAndRemove( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Shows this dialog as a modal dialog +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::ShowModal() +{ +#ifdef _X360 + m_bInteractive = false; + m_bShowBackButton = true; +#else + // we are in interactive mode, enable controls + m_bInteractive = true; +#endif + + SetParent( enginevgui->GetPanel( PANEL_GAMEUIDLL ) ); + UpdateDialog(); + SetVisible( true ); + MoveToFront(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::SetupForEmbedded( void ) +{ + m_bInteractive = true; + m_bEmbedded = true; + + UpdateDialog(); + + InvalidateLayout( true, true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::PerformLayout() +{ + BaseClass::PerformLayout(); + +#ifndef _X360 + if ( m_pTipImage && m_pTipText ) + { + int iX,iY; + m_pTipImage->GetPos(iX,iY); + int iTX, iTY; + m_pTipText->GetPos(iTX, iTY); + m_pTipText->SetPos( iX + m_pTipImage->GetWide() + XRES(8), iTY ); + } + + if ( m_pNextTipButton ) + { + m_pNextTipButton->SizeToContents(); + } + + if ( m_pResetStatsButton ) + { + m_pResetStatsButton->SizeToContents(); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::OnThink() +{ + BaseClass::OnThink(); + + if ( m_bShowingLeaderboard ) + { + UpdateLeaderboard(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Command handler +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::OnCommand( const char *command ) +{ + if ( 0 == Q_stricmp( command, "vguicancel" ) ) + { + m_bInteractive = false; + UpdateDialog(); + SetVisible( false ); + SetParent( (VPANEL) NULL ); + +#ifdef _X360 + SetDefaultSelections(); + m_bShowBackButton = true; +#endif + } +#ifndef _X360 + else if ( 0 == Q_stricmp( command, "resetstatsbutton" ) ) + { + QueryBox *qb = new QueryBox( "#GameUI_Confirm", "#TF_ConfirmResetStats" ); + if (qb != NULL) + { + qb->SetOKCommand(new KeyValues("DoResetStats") ); + qb->AddActionSignalTarget(this); + qb->MoveToFront(); + qb->DoModal(); + } + } +#endif + else if ( 0 == Q_stricmp( command, "nexttip" ) ) + { + UpdateTip(); + } + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: Resets the dialog +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::Reset() +{ + m_aClassStats.RemoveAll(); + + SetDefaultSelections(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets all user-controllable dialog settings to default values +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::SetDefaultSelections() +{ + m_iSelectedClass = TF_CLASS_UNDEFINED; + m_statBarGraph[0] = TFSTAT_POINTSSCORED; + m_displayBarGraph[0]= SHOW_MAX; + m_statBarGraph[1] = TFSTAT_PLAYTIME; + m_displayBarGraph[1] = SHOW_TOTAL; + + m_pBarChartComboBoxA->ActivateItemByRow( 0 ); + m_pBarChartComboBoxB->ActivateItemByRow( 10 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Set the background image based on the current mode +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::UpdateMainBackground( void ) +{ + if ( IsPC() ) + { + m_pMainBackground = dynamic_cast<ImagePanel *>( FindChildByName( "MainBackground" ) ); + if ( m_pMainBackground ) + { + const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GTFGCClientSystem()->GetLiveMatchGroup() ); + + // determine if we're in widescreen or not and select the appropriate image + int screenWide, screenTall; + surface()->GetScreenSize( screenWide, screenTall ); + float aspectRatio = (float)screenWide/(float)screenTall; + bool bIsWidescreen = aspectRatio >= 1.5999f; + + if ( g_bIsReplayRewinding ) + { + m_pMainBackground->SetImage( bIsWidescreen ? "../console/rewind_background_widescreen" : "../console/rewind_background" ); + } + else if ( engine->IsLoadingDemo() || engine->IsPlayingDemo() || engine->IsSkippingPlayback() ) + { + m_pMainBackground->SetImage( bIsWidescreen ? "../console/replay_loading_widescreen" : "../console/replay_loading" ); + } + else if ( pMatchDesc && pMatchDesc->GetMapLoadBackgroundOverride( bIsWidescreen ) ) // Use match override if we have one + { + m_pMainBackground->SetImage( pMatchDesc->GetMapLoadBackgroundOverride( bIsWidescreen ) ); + } + else + { + m_pMainBackground->SetImage( bIsWidescreen ? "../console/background01_widescreen" : "../console/background01" ); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Applies scheme settings +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::ApplySchemeSettings(vgui::IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + SetProportional( true ); + + if ( m_bEmbedded ) + { + LoadControlSettings( "Resource/UI/StatSummary_Embedded.res" ); + } + else + { + LoadControlSettings( "Resource/UI/StatSummary.res" ); + } + m_bControlsLoaded = true; + + // set the background image + UpdateMainBackground(); + + m_pMapInfoPanel = dynamic_cast< EditablePanel *>( FindChildByName( "MapInfo" ) ); + m_vecLeaderboardEntries.RemoveAll(); + if ( m_pMapInfoPanel ) + { + for ( int i = 0; i < 10; ++ i ) + { + vgui::EditablePanel *pEntryUI = new vgui::EditablePanel( m_pMapInfoPanel, "LeaderboardEntry" ); + pEntryUI->ApplySchemeSettings( pScheme ); + pEntryUI->LoadControlSettings( "Resource/UI/LeaderboardEntry.res" ); + m_vecLeaderboardEntries.AddToTail( pEntryUI ); + } + } + + // get the dimensions and position of a left-hand bar and a right-hand bar so we can do bar sizing later + Panel *pLHBar = m_pPlayerData->FindChildByName( "ClassBar1A" ); + Panel *pRHBar = m_pPlayerData->FindChildByName( "ClassBar1B" ); + if ( pLHBar && pRHBar ) + { + int y; + pLHBar->GetBounds( m_xStartLHBar, y, m_iBarMaxWidth, m_iBarHeight ); + pRHBar->GetBounds( m_xStartRHBar, y, m_iBarMaxWidth, m_iBarHeight ); + } + + // fill the combo box selections appropriately + InitBarChartComboBox( m_pBarChartComboBoxA ); + InitBarChartComboBox( m_pBarChartComboBoxB ); + + // fill the class names in the class combo box + HFont hFont = scheme()->GetIScheme( GetScheme() )->GetFont( "ScoreboardSmall", true ); + m_pClassComboBox->SetFont( hFont ); + m_pClassComboBox->RemoveAll(); + KeyValues *pKeyValues = new KeyValues( "data" ); + pKeyValues->SetInt( "class", TF_CLASS_UNDEFINED ); + m_pClassComboBox->AddItem( "#StatSummary_Label_AsAnyClass", pKeyValues ); + for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass <= TF_LAST_NORMAL_CLASS; iClass++ ) + { + if ( iClass == TF_CLASS_CIVILIAN ) + continue; + pKeyValues = new KeyValues( "data" ); + pKeyValues->SetInt( "class", iClass ); + m_pClassComboBox->AddItem( g_aPlayerClassNames[iClass], pKeyValues ); + } + m_pClassComboBox->ActivateItemByRow( 0 ); + + if ( m_pMapInfoPanel ) + { + m_pContributedPanel = dynamic_cast< vgui::EditablePanel* >( m_pMapInfoPanel->FindChildByName( "ContributedLabel" ) ); + } + + SetDefaultSelections(); + UpdateDialog(); + + if ( !m_bEmbedded ) + { + SetVisible( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::OnKeyCodePressed( KeyCode code ) +{ + if ( IsX360() ) + { + if ( code == KEY_XBUTTON_A || code == STEAMCONTROLLER_A ) + { + OnCommand( "nexttip" ) ; + } + else if ( code == KEY_XBUTTON_B || code == STEAMCONTROLLER_B ) + { + OnCommand( "vguicancel" ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Sets stats to use +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::SetStats( CUtlVector<ClassStats_t> &vecClassStats ) +{ + m_aClassStats = vecClassStats; + if ( m_bControlsLoaded ) + { + UpdateDialog(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Updates the dialog +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::ClearMapLabel() +{ + SetDialogVariable( "maplabel", "" ); + SetDialogVariable( "maptype", "" ); + + vgui::Label *pLabel = dynamic_cast<Label *>( FindChildByName( "OnYourWayLabel" ) ); + if ( pLabel && pLabel->IsVisible() ) + { + pLabel->SetVisible( false ); + } + + pLabel = dynamic_cast<Label *>( FindChildByName( "MapType" ) ); + if ( pLabel && pLabel->IsVisible() ) + { + pLabel->SetVisible( false ); + } + + if ( m_pContributedPanel ) + { + m_pContributedPanel->SetVisible( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::ShowMapInfo( bool bShowMapInfo, bool bIsMVM /*= false*/, bool bBackgroundOverride /*= false*/ ) +{ + if ( m_pMainBackground ) + { + m_pMainBackground->SetVisible( !bShowMapInfo ); + } + m_pPlayerData->SetVisible( bIsMVM || !bShowMapInfo ); + m_pNextTipButton->SetVisible( m_bInteractive && !bShowMapInfo ); + m_pResetStatsButton->SetVisible( m_bInteractive && !bShowMapInfo ); + + if ( m_pMapInfoPanel ) + { + m_pMapInfoPanel->SetVisible( bShowMapInfo ); + vgui::Panel* pInfoBG = m_pMapInfoPanel->FindChildByName( "InfoBG" ); + if ( pInfoBG ) + { + pInfoBG->SetVisible( bShowMapInfo && !bIsMVM && !bBackgroundOverride ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::OnMapLoad( const char *pMapName ) +{ + if ( g_bIsReplayRewinding || engine->IsLoadingDemo() || engine->IsPlayingDemo() || engine->IsSkippingPlayback() ) + return; + + bool bWidescreenBackground = false; + + bool bIsMVM = ( pMapName && !Q_strncmp( pMapName, "mvm_", 4 ) ); + const char *pszBackgroundOverride = NULL; + const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GTFGCClientSystem()->GetLiveMatchGroup() ); + if ( pMatchDesc ) + { + int screenWide, screenTall; + surface()->GetScreenSize( screenWide, screenTall ); + float aspectRatio = (float)screenWide/(float)screenTall; + bool bWideScreen = aspectRatio >= 1.5999f; + + // Check if there's a widescreen override + if( bWideScreen ) + { + pszBackgroundOverride = pMatchDesc->GetMapLoadBackgroundOverride( true ); + if ( pszBackgroundOverride ) + { + // Success! We're done + bWidescreenBackground = true; + } + } + + if ( !bWideScreen && !pszBackgroundOverride ) + { + pszBackgroundOverride = pMatchDesc->GetMapLoadBackgroundOverride( false ); + } + + } + else if ( bIsMVM ) + { + // this will preserve the current behavior for non-matchmaking servers + pszBackgroundOverride = "mvm_background_map"; + } + + bool bIsCommunityMap = false; + const char *pAuthors = NULL; + + const MapDef_t *pMapInfo = GetItemSchema()->GetMasterMapDefByName( pMapName ); + if ( pMapInfo ) + { + bIsCommunityMap = pMapInfo->IsCommunityMap(); + pAuthors = pMapInfo->pszAuthorsLocKey; + } + + ShowMapInfo( true, bIsMVM, ( pszBackgroundOverride != NULL ) ); + + m_xStartLeaderboard = 0; + m_yStartLeaderboard = 0; + + // If we're loading a background map, don't display anything + // HACK: Client doesn't get gpGlobals->eLoadType, so just do string compare for now. + if ( Q_stristr( pMapName, "background") ) + { + ClearMapLabel(); + } + else + { + // set the map name in the UI + wchar_t wzMapName[255]=L""; + g_pVGuiLocalize->ConvertANSIToUnicode( GetMapDisplayName( pMapName ), wzMapName, sizeof( wzMapName ) ); + + SetDialogVariable( "maplabel", wzMapName ); + SetDialogVariable( "maptype", g_pVGuiLocalize->Find( GetMapType( pMapName ) ) ); + + vgui::Label *pLabel = dynamic_cast<Label *>( FindChildByName( "OnYourWayLabel" ) ); + if ( pLabel && !pLabel->IsVisible() ) + { + pLabel->SetVisible( true ); + } + + pLabel = dynamic_cast<Label *>( FindChildByName( "MapType" ) ); + if ( pLabel && !pLabel->IsVisible() ) + { + pLabel->SetVisible( true ); + } + + ImagePanel *pMapImage = m_pMapInfoPanel ? dynamic_cast< ImagePanel *>( m_pMapInfoPanel->FindChildByName( "MapImage" ) ) : NULL; + if ( pMapImage ) + { + // load the map image (if it exists for the current map) + char szMapImage[ MAX_PATH ]; + Q_snprintf( szMapImage, sizeof( szMapImage ), "VGUI/maps/menu_photos_%s", pMapName ); + Q_strlower( szMapImage ); + + IMaterial *pMapMaterial = materials->FindMaterial( szMapImage, TEXTURE_GROUP_VGUI, false ); + if ( pMapMaterial && !IsErrorMaterial( pMapMaterial ) && !pszBackgroundOverride ) + { + // take off the vgui/ at the beginning when we set the image + Q_snprintf( szMapImage, sizeof( szMapImage ), "maps/menu_photos_%s", pMapName ); + Q_strlower( szMapImage ); + pMapImage->SetImage( szMapImage ); + pMapImage->SetVisible( true ); + } + else + { + pMapImage->SetVisible( false ); + } + } + + ImagePanel *pBackgroundImage = m_pMapInfoPanel ? dynamic_cast< ImagePanel *>( m_pMapInfoPanel->FindChildByName( "Background" ) ) : NULL; + if ( pBackgroundImage ) + { + const char* pszBackgroundImage = pszBackgroundOverride ? pszBackgroundOverride : "stamp_background_map"; + + pBackgroundImage->SetImage( pszBackgroundImage ); + + // Resize to accomodate the background image coming in + if ( bWidescreenBackground ) + { + pBackgroundImage->SetWide( GetWide() ); + } + else + { + pBackgroundImage->SetWide( GetTall() * ( 4.f / 3.f ) ); + } + + } + + if ( bIsMVM ) + { + UpdateClassDetails( true ); + m_pMapInfoPanel->SetDialogVariable( "map_leaderboard_title", "" ); + m_pMapInfoPanel->SetDialogVariable( "title", "" ); + m_pMapInfoPanel->SetDialogVariable( "authors", "" ); + + FOR_EACH_VEC( m_vecLeaderboardEntries, i ) + { + EditablePanel *pContainer = dynamic_cast< EditablePanel* >( m_vecLeaderboardEntries[i] ); + if ( pContainer ) + { + pContainer->SetVisible( false ); + } + } + } + else + { + m_pLeaderboardTitle = NULL; + // add authors + if ( m_pMapInfoPanel ) + { + if ( bIsCommunityMap ) + { + m_pMapInfoPanel->SetDialogVariable( "title", g_pVGuiLocalize->Find( "#TF_MapAuthors_Community_Title" ) ); + m_pMapInfoPanel->SetDialogVariable( "map_leaderboard_title", "" ); + m_pMapInfoPanel->SetDialogVariable( "authors", g_pVGuiLocalize->Find( pAuthors ) ); + m_pLeaderboardTitle = m_pMapInfoPanel->FindChildByName( "MapLeaderboardTitle" ); + } + else + { + m_pMapInfoPanel->SetDialogVariable( "title", g_pVGuiLocalize->Find( "#TF_DuelLeaderboard_Title" ) ); + m_pMapInfoPanel->SetDialogVariable( "map_leaderboard_title", "" ); + m_pMapInfoPanel->SetDialogVariable( "authors", "" ); + m_pLeaderboardTitle = m_pMapInfoPanel->FindChildByName( "Title" ); + } + } + if ( m_pLeaderboardTitle ) + { + m_pLeaderboardTitle->GetPos( m_xStartLeaderboard, m_yStartLeaderboard ); + m_yStartLeaderboard += m_pLeaderboardTitle->GetTall(); + } + + // request leaderboard data + m_bShowingLeaderboard = true; + if ( bIsCommunityMap ) + { + MapInfo_RefreshLeaderboard( pMapName ); + } + else + { + Leaderboards_Refresh(); + } + m_bLoadingCommunityMap = bIsCommunityMap; + + if ( m_pContributedPanel && steamapicontext && steamapicontext->SteamUser() && steamapicontext->SteamFriends() ) + { + int iDonationAmount = MapInfo_GetDonationAmount( steamapicontext->SteamUser()->GetSteamID().GetAccountID(), pMapName ); + m_pContributedPanel->SetVisible( iDonationAmount != 0 ); + if ( iDonationAmount != 0 ) + { + m_pContributedPanel->SetDialogVariable( "playername", steamapicontext->SteamFriends()->GetPersonaName() ); + } + } + + UpdateLeaderboard(); + } + } +} + +void CTFStatsSummaryPanel::UpdateLeaderboard() +{ + if ( m_pMapInfoPanel == NULL || steamapicontext == NULL || steamapicontext->SteamUserStats() == NULL || steamapicontext->SteamUser() == NULL ) + return; + + const int kMaxVisible_Supporters = 5; + const int kIdeallyNumVisible_Supporters = 3; + const int kMaxVisible_DuelWins = 10; + const int kIdeallyNumVisible_DuelWins = 5; + + // retrieve scores + CUtlVector< LeaderboardEntry_t* > scores; + bool bVisible = true; + int iNumLeaderboardEntries = 0; + if ( m_bLoadingCommunityMap ) + { + bVisible = MapInfo_GetLeaderboardInfo( engine->GetLevelName(), scores, iNumLeaderboardEntries, kIdeallyNumVisible_Supporters ); + wchar_t wzNumEntriesString[256]; + _snwprintf( wzNumEntriesString, ARRAYSIZE( wzNumEntriesString ), L"%i", iNumLeaderboardEntries ); + wchar_t wzTitle[256]; + g_pVGuiLocalize->ConstructString_safe( wzTitle, g_pVGuiLocalize->Find( "#TF_MapDonators_Title" ), 1, wzNumEntriesString ); + m_pMapInfoPanel->SetDialogVariable( "map_leaderboard_title", wzTitle ); + } + else + { + bVisible = Leaderboards_GetDuelWins( scores, false ); + if ( bVisible && scores.Count() < kIdeallyNumVisible_DuelWins ) + { + bVisible = Leaderboards_GetDuelWins( scores, true ) && scores.Count() > 0; + } + // show old stats + m_pPlayerData->SetVisible( bVisible == false ); + if ( m_pMapInfoPanel ) + { + vgui::Panel* pInfoBG = m_pMapInfoPanel->FindChildByName( "InfoBG" ); + if ( pInfoBG ) + { + pInfoBG->SetVisible( bVisible ); + } + } + } + + const int kMaxVisible = m_bLoadingCommunityMap ? kMaxVisible_Supporters : kMaxVisible_DuelWins; + + // try to show local player in relation to the people in the list + if ( bVisible && scores.Count() > 0 && steamapicontext && steamapicontext->SteamUser() && steamapicontext->SteamUserStats() ) + { + int iLocalPlayerIdx = -1; + CSteamID localSteamID = steamapicontext->SteamUser()->GetSteamID(); + FOR_EACH_VEC( scores, i ) + { + const LeaderboardEntry_t *leaderboardEntry = scores[i]; + if ( leaderboardEntry->m_steamIDUser == localSteamID ) + { + iLocalPlayerIdx = i; + break; + } + } + // local player is in the list, but is outside the visible range + // so we want to move them to the last spot + // and move the closest person above them as well + if ( iLocalPlayerIdx >= kMaxVisible ) + { + LeaderboardEntry_t *entryLocalPlayer = scores[iLocalPlayerIdx]; + LeaderboardEntry_t *closestPlayer = scores[iLocalPlayerIdx - 1]; + scores[kMaxVisible - 1] = entryLocalPlayer; + scores[kMaxVisible - 2] = closestPlayer; + } + } + + // set avatars and names + int x = m_xStartLeaderboard; + int y = m_yStartLeaderboard; + FOR_EACH_VEC( m_vecLeaderboardEntries, i ) + { + EditablePanel *pContainer = dynamic_cast< EditablePanel* >( m_vecLeaderboardEntries[i] ); + if ( pContainer ) + { + bool bIsEntryVisible = bVisible && i < scores.Count() && i < kMaxVisible; + pContainer->SetVisible( bIsEntryVisible ); + pContainer->SetPos( x, y ); + y += pContainer->GetTall(); + if ( bIsEntryVisible ) + { + const LeaderboardEntry_t *leaderboardEntry = scores[i]; + const CSteamID &steamID = leaderboardEntry->m_steamIDUser; + pContainer->SetDialogVariable( "username", CFmtStr( "%d. %s - %d", leaderboardEntry->m_nGlobalRank, InventoryManager()->PersonaName_Get( steamID.GetAccountID() ), leaderboardEntry->m_nScore ) ); + CAvatarImagePanel *pAvatar = dynamic_cast< CAvatarImagePanel* >( pContainer->FindChildByName( "AvatarImage" ) ); + if ( pAvatar ) + { + pAvatar->SetShouldDrawFriendIcon( false ); + pAvatar->SetPlayer( steamID, k_EAvatarSize32x32 ); + } + } + } + } + + if ( m_pLeaderboardTitle ) + { + bool bShowTitle = bVisible && scores.Count() > 0; + if ( m_pLeaderboardTitle->IsVisible() != bShowTitle ) + { + m_pLeaderboardTitle->SetVisible( bShowTitle ); + } + } + + m_bShowingLeaderboard = bVisible; +} + +//----------------------------------------------------------------------------- +// Purpose: Updates the dialog +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::UpdateDialog() +{ + UpdateMainBackground(); + + if ( g_bIsReplayRewinding || engine->IsLoadingDemo() || engine->IsPlayingDemo() || engine->IsSkippingPlayback() ) + { + // hide all of the various panels for the other loadscreen modes + if ( IsPC() ) + { + ClearMapLabel(); + + m_pPlayerData->SetVisible( false ); + m_pNextTipButton->SetVisible( false ); + m_pResetStatsButton->SetVisible( false ); + m_pInteractiveHeaders->SetVisible( false ); + m_pNonInteractiveHeaders->SetVisible( false ); + m_pTipText->SetVisible( false ); + m_pTipImage->SetVisible( false ); + + if ( m_pMapInfoPanel ) + { + m_pMapInfoPanel->SetVisible( false ); + + vgui::Panel* pInfoBG = m_pMapInfoPanel->FindChildByName( "InfoBG" ); + if ( pInfoBG ) + { + pInfoBG->SetVisible( false ); + } + } + } + + return; + } + + RandomSeed( Plat_MSTime() ); + + m_iTotalSpawns = 0; + + // if we don't have stats for any class, add empty stat entries for them + for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass <= TF_LAST_NORMAL_CLASS; iClass++ ) + { + if ( iClass == TF_CLASS_CIVILIAN ) + continue; // Ignore the civilian. + + int j; + for ( j = 0; j < m_aClassStats.Count(); j++ ) + { + if ( m_aClassStats[j].iPlayerClass == iClass ) + { + m_iTotalSpawns += m_aClassStats[j].iNumberOfRounds; + break; + } + } + if ( j == m_aClassStats.Count() ) + { + ClassStats_t stats; + stats.iPlayerClass = iClass; + m_aClassStats.AddToTail( stats ); + } + } + + ClearMapLabel(); + +#ifdef _X360 + if ( m_pFooter ) + { + m_pFooter->ShowButtonLabel( "nexttip", m_bShowBackButton ); + m_pFooter->ShowButtonLabel( "back", m_bShowBackButton ); + } +#endif + + // fill out bar charts + UpdateBarCharts(); + // fill out class details + UpdateClassDetails(); + // update the tip + UpdateTip(); + // show or hide controls depending on if we're interactive or not + UpdateControls(); +} + +//----------------------------------------------------------------------------- +// Purpose: Updates bar charts +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::UpdateBarCharts() +{ + // sort the class stats by the selected stat for right-hand bar chart + m_aClassStats.Sort( &CTFStatsSummaryPanel::CompareClassStats ); + + // loop for left & right hand charts + for ( int iChart = 0; iChart < 2; iChart++ ) + { + float flMax = 0; + for ( int i = 0; i < m_aClassStats.Count(); i++ ) + { + // get max value of stat being charted so we know how to scale the graph + float flVal = GetDisplayValue( m_aClassStats[i], m_statBarGraph[iChart], m_displayBarGraph[iChart] ); + flMax = MAX( flVal, flMax ); + } + + // draw the bar chart value for each player class + // TODO: Fix up after the civilian becomes playable. + int iChartBar = 0; + for ( int i = 0; i < m_aClassStats.Count(); i++ ) + { + int iClass = m_aClassStats[i].iPlayerClass; + if ( iClass == TF_CLASS_CIVILIAN ) + { + continue; + } + if ( 0 == iChart ) + { + // if this is the first chart, set the class label for each class + m_pPlayerData->SetDialogVariable( CFmtStr( "class%d", iChartBar+1 ), g_pVGuiLocalize->Find( g_aPlayerClassNames[iClass] ) ); + } + // draw the bar for this class + DisplayBarValue( iChart, iChartBar++, m_aClassStats[i], m_statBarGraph[iChart], m_displayBarGraph[iChart], flMax ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Updates class details +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::UpdateClassDetails( bool bIsMVM ) +{ + vgui::Label *pTitle = assert_cast< vgui::Label* >( FindChildByName( "RecordsLabel1", true ) ); + if ( pTitle ) + { + pTitle->SetText( bIsMVM ? "#StatSummary_Label_BestMVMMoments" : "#StatSummary_Label_BestMoments" ); + } + + const wchar_t *wzWithClassFmt = g_pVGuiLocalize->Find( "#StatSummary_ScoreAsClassFmt" ); + const wchar_t *wzWithoutClassFmt = L"%s1"; + + ClassDetails_t *pStatDetails = ( bIsMVM ? g_PerClassMVMStatDetails : g_PerClassStatDetails ); + int nArraySize = ( bIsMVM ? ARRAYSIZE( g_PerClassMVMStatDetails ) : ARRAYSIZE( g_PerClassStatDetails ) ); + + // display the record for each stat + int iRow = 0; + for ( int i = 0; i < nArraySize; i++ ) + { + TFStatType_t statType = pStatDetails[i].statType; + + int iClass = TF_CLASS_UNDEFINED; + int iMaxVal = 0; + + // if there is a selected class, and if this stat should not be shown for this class, skip this stat + if ( m_iSelectedClass != TF_CLASS_UNDEFINED && ( 0 == ( pStatDetails[i].iFlagsClass & MAKESTATFLAG( m_iSelectedClass ) ) ) ) + continue; + + if ( m_iSelectedClass == TF_CLASS_UNDEFINED ) + { + // if showing best from any class, look through all player classes to determine the max value of this stat + for ( int j = 0; j < m_aClassStats.Count(); j++ ) + { + RoundStats_t *pRoundStats = &( bIsMVM ? m_aClassStats[j].maxMVM : m_aClassStats[j].max ); + if ( pRoundStats->m_iStat[statType] > iMaxVal ) + { + // remember max value and class that has max value + iMaxVal = pRoundStats->m_iStat[statType]; + iClass = m_aClassStats[j].iPlayerClass; + } + } + } + else + { + // show best from selected class + iClass = m_iSelectedClass; + for ( int j = 0; j < m_aClassStats.Count(); j++ ) + { + if ( m_aClassStats[j].iPlayerClass == iClass ) + { + RoundStats_t *pRoundStats = &( bIsMVM ? m_aClassStats[j].maxMVM : m_aClassStats[j].max ); + iMaxVal = pRoundStats->m_iStat[statType]; + break; + } + } + } + + wchar_t wzStatNum[32]; + wchar_t wzStatVal[128]; + if ( TFSTAT_PLAYTIME == statType ) + { + // playtime gets displayed as a time string + g_pVGuiLocalize->ConvertANSIToUnicode( FormatSeconds( iMaxVal ), wzStatNum, sizeof( wzStatNum ) ); + } + else + { + // all other stats are just shown as a # + swprintf_s( wzStatNum, ARRAYSIZE( wzStatNum ), L"%d", iMaxVal ); + } + + if ( TF_CLASS_UNDEFINED == m_iSelectedClass && iMaxVal > 0 ) + { + // if we are doing a cross-class view (no single selected class) and the max value is non-zero, show "# (as <class>)" + wchar_t *wzLocalizedClassName = g_pVGuiLocalize->Find( g_aPlayerClassNames[iClass] ); + g_pVGuiLocalize->ConstructString_safe( wzStatVal, wzWithClassFmt, 2, wzStatNum, wzLocalizedClassName ); + } + else + { + // just show the value + g_pVGuiLocalize->ConstructString_safe( wzStatVal, wzWithoutClassFmt, 1, wzStatNum ); + } + + // set the label + m_pPlayerData->SetDialogVariable( CFmtStr( "classrecord%dlabel", iRow+1 ), g_pVGuiLocalize->Find( pStatDetails[i].szResourceName ) ); + // set the value + m_pPlayerData->SetDialogVariable( CFmtStr( "classrecord%dvalue", iRow+1 ), wzStatVal ); + + iRow++; + } + + // if there are any leftover rows for the selected class, fill out the remaining rows with blank labels and values + for ( ; iRow < 15; iRow ++ ) + { + m_pPlayerData->SetDialogVariable( CFmtStr( "classrecord%dlabel", iRow+1 ), "" ); + m_pPlayerData->SetDialogVariable( CFmtStr( "classrecord%dvalue", iRow+1 ), "" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Updates the tip +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::UpdateTip() +{ + int iTipClass = TF_CLASS_UNDEFINED; + + SetDialogVariable( "tiptext", g_TFTips.GetRandomTip( iTipClass ) ); + + if ( m_pTipImage ) + { + if ( iTipClass > TF_CLASS_UNDEFINED && iTipClass <= TF_CLASS_ENGINEER ) + { + m_pTipImage->SetVisible( true ); + m_pTipImage->SetImage( g_pszTipsClassImages[iTipClass] ); + } + else + { + m_pTipImage->SetVisible( false ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Shows or hides controls +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::UpdateControls() +{ + // show or hide controls depending on what mode we're in +#ifndef _X360 + bool bShowPlayerData = ( m_bInteractive || m_iTotalSpawns > 0 ); +#else + bool bShowPlayerData = ( m_bInteractive || m_bShowBackButton || m_iTotalSpawns > 0 ); +#endif + m_pPlayerData->SetVisible( bShowPlayerData ); + m_pInteractiveHeaders->SetVisible( m_bInteractive ); + m_pNonInteractiveHeaders->SetVisible( !m_bInteractive ); + m_pTipText->SetVisible( bShowPlayerData ); + m_pTipImage->SetVisible( bShowPlayerData ); + + if ( !IsX360() ) + { + if ( !m_bInteractive ) + { + char szTemp[128]; + + // update our non-interactive headers to match the current combo box selections + Label *pLabel = dynamic_cast<Label *>( m_pNonInteractiveHeaders->FindChildByName( "BarChartLabelA" ) ); + if ( pLabel && m_pBarChartComboBoxA ) + { + m_pBarChartComboBoxA->GetItemText( m_pBarChartComboBoxA->GetActiveItem(), szTemp, sizeof( szTemp ) ); + pLabel->SetText( szTemp ); + } + + pLabel = dynamic_cast<Label *>( m_pNonInteractiveHeaders->FindChildByName( "BarChartLabelB" ) ); + if ( pLabel && m_pBarChartComboBoxB ) + { + m_pBarChartComboBoxB->GetItemText( m_pBarChartComboBoxB->GetActiveItem(), szTemp, sizeof( szTemp ) ); + pLabel->SetText( szTemp ); + } + + pLabel = dynamic_cast<Label *>( m_pNonInteractiveHeaders->FindChildByName( "OverallRecordLabel" ) ); + if ( pLabel && m_pClassComboBox ) + { + m_pClassComboBox->GetItemText( m_pClassComboBox->GetActiveItem(), szTemp, sizeof( szTemp ) ); + pLabel->SetText( szTemp ); + } + } + } + +#ifndef _X360 + m_pNextTipButton->SetVisible( m_bInteractive ); + m_pResetStatsButton->SetVisible( m_bInteractive ); + m_pCloseButton->SetVisible( m_bInteractive && !m_bEmbedded ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Initializes a bar chart combo box +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::InitBarChartComboBox( ComboBox *pComboBox ) +{ + struct BarChartComboInit_t + { + TFStatType_t statType; + StatDisplay_t statDisplay; + const char *szName; + }; + + BarChartComboInit_t initData[] = + { + { TFSTAT_POINTSSCORED, SHOW_MAX, "#StatSummary_StatTitle_MostPoints" }, + { TFSTAT_POINTSSCORED, SHOW_AVG, "#StatSummary_StatTitle_AvgPoints" }, + { TFSTAT_KILLS, SHOW_MAX, "#StatSummary_StatTitle_MostKills" }, + { TFSTAT_KILLS, SHOW_AVG, "#StatSummary_StatTitle_AvgKills" }, + { TFSTAT_CAPTURES, SHOW_MAX, "#StatSummary_StatTitle_MostCaptures" }, + { TFSTAT_CAPTURES, SHOW_AVG, "#StatSummary_StatTitle_AvgCaptures" }, + { TFSTAT_KILLASSISTS, SHOW_MAX, "#StatSummary_StatTitle_MostAssists" }, + { TFSTAT_KILLASSISTS, SHOW_AVG, "#StatSummary_StatTitle_AvgAssists" }, + { TFSTAT_DAMAGE, SHOW_MAX, "#StatSummary_StatTitle_MostDamage" }, + { TFSTAT_DAMAGE, SHOW_AVG, "#StatSummary_StatTitle_AvgDamage" }, + { TFSTAT_PLAYTIME, SHOW_TOTAL, "#StatSummary_StatTitle_TotalPlaytime" }, + { TFSTAT_PLAYTIME, SHOW_MAX, "#StatSummary_StatTitle_LongestLife" }, + }; + + // set the font + HFont hFont = scheme()->GetIScheme( GetScheme() )->GetFont( "ScoreboardVerySmall", true ); + pComboBox->SetFont( hFont ); + pComboBox->RemoveAll(); + // add all the options to the combo box + for ( int i=0; i < ARRAYSIZE( initData ); i++ ) + { + KeyValues *pKeyValues = new KeyValues( "data" ); + pKeyValues->SetInt( "stattype", initData[i].statType ); + pKeyValues->SetInt( "statdisplay", initData[i].statDisplay ); + pComboBox->AddItem( g_pVGuiLocalize->Find( initData[i].szName ), pKeyValues ); + } + pComboBox->SetNumberOfEditLines( ARRAYSIZE( initData ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Helper function that sets the specified dialog variable to +// "<value> (as <localized class name>)" +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::SetValueAsClass( const char *pDialogVariable, int iValue, int iPlayerClass ) +{ + if ( iValue > 0 ) + { + wchar_t *wzScoreAsClassFmt = g_pVGuiLocalize->Find( "#StatSummary_ScoreAsClassFmt" ); + wchar_t *wzLocalizedClassName = g_pVGuiLocalize->Find( g_aPlayerClassNames[iPlayerClass] ); + wchar_t wzVal[16]; + wchar_t wzMsg[128]; + swprintf( wzVal, ARRAYSIZE( wzVal ), L"%d", iValue ); + g_pVGuiLocalize->ConstructString_safe( wzMsg, wzScoreAsClassFmt, 2, wzVal, wzLocalizedClassName ); + m_pPlayerData->SetDialogVariable( pDialogVariable, wzMsg ); + } + else + { + m_pPlayerData->SetDialogVariable( pDialogVariable, "0" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the specified bar chart item to the specified value, in range 0->1 +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::DisplayBarValue( int iChart, int iBar, ClassStats_t &stats, TFStatType_t statType, StatDisplay_t statDisplay, float flMaxValue ) +{ + const char *szControlSuffix = ( 0 == iChart ? "A" : "B" ); + Panel *pBar = m_pPlayerData->FindChildByName( CFmtStr( "ClassBar%d%s", iBar+1, szControlSuffix ) ); + Label *pLabel = dynamic_cast<Label*>( m_pPlayerData->FindChildByName( CFmtStr( "classbarlabel%d%s", iBar+1, szControlSuffix ) ) ); + if ( !pBar || !pLabel ) + return; + + // get the stat value + float flValue = GetDisplayValue( stats, statType, statDisplay ); + // calculate the bar size to draw, in the range of 0.0->1.0 + float flBarRange = SafeCalcFraction( flValue, flMaxValue ); + // calculate the # of pixels of bar width to draw + int iBarWidth = MAX( (int) ( flBarRange * (float) m_iBarMaxWidth ), 1 ); + + // Get the text label to draw for this bar. For values of 0, draw nothing, to minimize clutter + const char *szLabel = ( flValue > 0 ? RenderValue( flValue, statType, statDisplay ) : "" ); + // draw the label outside the bar if there's room + bool bLabelOutsideBar = true; + const int iLabelSpacing = 4; + HFont hFont = pLabel->GetFont(); + int iLabelWidth = UTIL_ComputeStringWidth( hFont, szLabel ); + if ( iBarWidth + iLabelWidth + iLabelSpacing > m_iBarMaxWidth ) + { + // if there's not room outside the bar for the label, draw it inside the bar + bLabelOutsideBar = false; + } + + int xBar,yBar,xLabel,yLabel; + pBar->GetPos( xBar,yBar ); + pLabel->GetPos( xLabel,yLabel ); + + m_pPlayerData->SetDialogVariable( CFmtStr( "classbarlabel%d%s", iBar+1, szControlSuffix ), szLabel ); + if ( 1 == iChart ) + { + // drawing code for RH bar chart + xBar = m_xStartRHBar; + pBar->SetBounds( xBar, yBar, iBarWidth, m_iBarHeight ); + if ( bLabelOutsideBar ) + { + pLabel->SetPos( xBar + iBarWidth + iLabelSpacing, yLabel ); + } + else + { + pLabel->SetPos( xBar + iBarWidth - ( iLabelWidth + iLabelSpacing ), yLabel ); + } + } + else + { + // drawing code for LH bar chart + xBar = m_xStartLHBar + m_iBarMaxWidth - iBarWidth; + pBar->SetBounds( xBar, yBar, iBarWidth, m_iBarHeight ); + if ( bLabelOutsideBar ) + { + pLabel->SetPos( xBar - ( iLabelWidth + iLabelSpacing ), yLabel ); + } + else + { + pLabel->SetPos( xBar + iLabelSpacing, yLabel ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Calculates a fraction and guards from divide by 0. (Returns 0 if +// denominator is 0.) +//----------------------------------------------------------------------------- +float CTFStatsSummaryPanel::SafeCalcFraction( float flNumerator, float flDemoninator ) +{ + if ( 0 == flDemoninator ) + return 0; + return flNumerator / flDemoninator; +} + +//----------------------------------------------------------------------------- +// Purpose: Formats # of seconds into a string +//----------------------------------------------------------------------------- +const char *FormatSeconds( int seconds ) +{ + static char string[64]; + + int hours = 0; + int minutes = seconds / 60; + + if ( minutes > 0 ) + { + seconds -= (minutes * 60); + hours = minutes / 60; + + if ( hours > 0 ) + { + minutes -= (hours * 60); + } + } + + if ( hours > 0 ) + { + Q_snprintf( string, sizeof(string), "%2i:%02i:%02i", hours, minutes, seconds ); + } + else + { + Q_snprintf( string, sizeof(string), "%02i:%02i", minutes, seconds ); + } + + return string; +} + +//----------------------------------------------------------------------------- +// Purpose: Static sort function that sorts in descending order by play time +//----------------------------------------------------------------------------- +int __cdecl CTFStatsSummaryPanel::CompareClassStats( const ClassStats_t *pStats0, const ClassStats_t *pStats1 ) +{ + // sort stats first by right-hand bar graph + TFStatType_t statTypePrimary = GStatsSummaryPanel()->m_statBarGraph[1]; + StatDisplay_t statDisplayPrimary = GStatsSummaryPanel()->m_displayBarGraph[1]; + // then by left-hand bar graph + TFStatType_t statTypeSecondary = GStatsSummaryPanel()->m_statBarGraph[0]; + StatDisplay_t statDisplaySecondary = GStatsSummaryPanel()->m_displayBarGraph[0]; + + float flValPrimary0 = GetDisplayValue( (ClassStats_t &) *pStats0, statTypePrimary, statDisplayPrimary ); + float flValPrimary1 = GetDisplayValue( (ClassStats_t &) *pStats1, statTypePrimary, statDisplayPrimary ); + float flValSecondary0 = GetDisplayValue( (ClassStats_t &) *pStats0, statTypeSecondary, statDisplaySecondary ); + float flValSecondary1 = GetDisplayValue( (ClassStats_t &) *pStats1, statTypeSecondary, statDisplaySecondary ); + + // sort in descending order by primary stat value + if ( flValPrimary1 > flValPrimary0 ) + return 1; + if ( flValPrimary1 < flValPrimary0 ) + return -1; + + // if primary stat values are equal, sort in descending order by secondary stat value + if ( flValSecondary1 > flValSecondary0 ) + return 1; + if ( flValSecondary1 < flValSecondary0 ) + return -1; + + // if primary & secondary stats are equal, sort by class for consistent sort order + return ( pStats1->iPlayerClass - pStats0->iPlayerClass ); +} + +//----------------------------------------------------------------------------- +// Purpose: Called when text changes in combo box +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::OnTextChanged( KeyValues *data ) +{ + Panel *pPanel = reinterpret_cast<vgui::Panel *>( data->GetPtr("panel") ); + vgui::ComboBox *pComboBox = dynamic_cast<vgui::ComboBox *>( pPanel ); + + if ( m_pBarChartComboBoxA == pComboBox || m_pBarChartComboBoxB == pComboBox ) + { + // a bar chart combo box changed, update the bar charts + + KeyValues *pUserDataA = m_pBarChartComboBoxA->GetActiveItemUserData(); + KeyValues *pUserDataB = m_pBarChartComboBoxB->GetActiveItemUserData(); + if ( !pUserDataA || !pUserDataB ) + return; + m_statBarGraph[0] = (TFStatType_t) pUserDataA->GetInt( "stattype" ); + m_displayBarGraph[0] = (StatDisplay_t) pUserDataA->GetInt( "statdisplay" ); + m_statBarGraph[1] = (TFStatType_t) pUserDataB->GetInt( "stattype" ); + m_displayBarGraph[1] = (StatDisplay_t) pUserDataB->GetInt( "statdisplay" ); + UpdateBarCharts(); + } + else if ( m_pClassComboBox == pComboBox ) + { + // the class selection combo box changed, update class details + + KeyValues *pUserData = m_pClassComboBox->GetActiveItemUserData(); + if ( !pUserData ) + return; + + m_iSelectedClass = pUserData->GetInt( "class", TF_CLASS_UNDEFINED ); + + UpdateClassDetails(); + } + +} + +//----------------------------------------------------------------------------- +// Purpose: Command target handler called from reset stats confirmation query box +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::DoResetStats() +{ +#ifndef _X360 + // reset the stats + engine->ClientCmd( "resetplayerstats" ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the stat value for specified display type +//----------------------------------------------------------------------------- +float CTFStatsSummaryPanel::GetDisplayValue( ClassStats_t &stats, TFStatType_t statType, StatDisplay_t statDisplay ) +{ + switch ( statDisplay ) + { + case SHOW_MAX: + return stats.max.m_iStat[statType]; + break; + case SHOW_TOTAL: + return stats.accumulated.m_iStat[statType]; + break; + case SHOW_AVG: + return SafeCalcFraction( stats.accumulated.m_iStat[statType], stats.iNumberOfRounds ); + break; + default: + AssertOnce( false ); + return 0; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Gets the text representation of this value +//----------------------------------------------------------------------------- +const char *CTFStatsSummaryPanel::RenderValue( float flValue, TFStatType_t statType, StatDisplay_t statDisplay ) +{ + static char szValue[64]; + if ( TFSTAT_PLAYTIME == statType ) + { + // the playtime stat is shown in seconds + return FormatSeconds( (int) flValue ); + } + else if ( SHOW_AVG == statDisplay ) + { + // if it's an average, render as a float w/2 decimal places + Q_snprintf( szValue, ARRAYSIZE( szValue ), "%.2f", flValue ); + } + else + { + // otherwise, render as an integer + Q_snprintf( szValue, ARRAYSIZE( szValue ), "%d", (int) flValue ); + } + + return szValue; +} + +//----------------------------------------------------------------------------- +// Purpose: Event handler +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::FireGameEvent( IGameEvent *event ) +{ + const char *pEventName = event->GetName(); + + // when we are changing levels and + if ( 0 == Q_strcmp( pEventName, "server_spawn" ) ) + { + if ( !m_bInteractive ) + { + const char *pMapName = event->GetString( "mapname" ); + if ( pMapName ) + { + OnMapLoad( pMapName ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called when we are activated during level load +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::OnActivate() +{ + ClearMapLabel(); + + m_bShowingLeaderboard = false; + m_bLoadingCommunityMap = false; + ShowMapInfo( false ); + +#ifdef _X360 + m_bShowBackButton = false; +#endif + + UpdateDialog(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called when we are deactivated at end of level load +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::OnDeactivate() +{ + ClearMapLabel(); +} + +CON_COMMAND( showstatsdlg, "Shows the player stats dialog" ) +{ +#ifdef _DEBUG + GStatsSummaryPanel()->InvalidateLayout( false, true ); +#endif + GStatsSummaryPanel()->ShowModal(); +#ifdef _DEBUG + GStatsSummaryPanel()->OnMapLoad( "cp_coldfront" ); +#endif +} diff --git a/game/client/tf/vgui/tf_statsummary.h b/game/client/tf/vgui/tf_statsummary.h new file mode 100644 index 0000000..eca7c72 --- /dev/null +++ b/game/client/tf/vgui/tf_statsummary.h @@ -0,0 +1,142 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef TF_STATSSUMMARY_H +#define TF_STATSSUMMARY_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_hud_statpanel.h" +#include "GameEventListener.h" + +struct ClassDetails_t +{ + TFStatType_t statType; // type of stat + uint iFlagsClass; // bit mask of classes to show this stat for + const char * szResourceName; // name of label resource (format "Most damage:" + const char * szAltResourceName; // name of alternative label resource "damage" +}; +extern ClassDetails_t g_PerClassStatDetails[15]; + +#define MAKESTATFLAG(x) ( 1 << x ) +#define ALL_CLASSES 0xFFFFFFFF + +class CTFStatsSummaryPanel : public vgui::EditablePanel, public CGameEventListener +{ +private: + DECLARE_CLASS_SIMPLE( CTFStatsSummaryPanel, vgui::EditablePanel ); + +public: + CTFStatsSummaryPanel(); + CTFStatsSummaryPanel( vgui::Panel *parent ); + ~CTFStatsSummaryPanel(); + + void Init( void ); + + virtual void ApplySchemeSettings(vgui::IScheme *pScheme); + virtual void OnCommand( const char *command ); + virtual void OnKeyCodePressed( KeyCode code ); + virtual void PerformLayout(); + virtual void OnThink(); + void SetStats( CUtlVector<ClassStats_t> &vecClassStats ); + void ShowModal(); + + void SetupForEmbedded( void ); + + void OnMapLoad( const char *pMapName ); + + virtual void FireGameEvent( IGameEvent *event ); +private: + MESSAGE_FUNC( OnActivate, "activate" ); + MESSAGE_FUNC( OnDeactivate, "deactivate" ); + + enum StatDisplay_t + { + SHOW_MAX = 1, + SHOW_TOTAL, + SHOW_AVG + }; + + void Reset(); + void SetDefaultSelections(); + void UpdateDialog(); + void UpdateBarCharts(); + void UpdateClassDetails( bool bIsMVM = false ); + void UpdateTip(); + void UpdateControls(); + void ClearMapLabel(); + void ShowMapInfo( bool bShowMapInfo, bool bIsMVM = false, bool bBackgroundOverride = false ); + void UpdateLeaderboard(); + void InitBarChartComboBox( vgui::ComboBox *pComboBox ); + void SetValueAsClass( const char *pDialogVariable, int iValue, int iPlayerClass ); + void DisplayBarValue( int iChart, int iClass, ClassStats_t &stats, TFStatType_t statType, StatDisplay_t flags, float flMaxValue ); + static float GetDisplayValue( ClassStats_t &stats, TFStatType_t statType, StatDisplay_t statDisplay ); + const char *RenderValue( float flValue, TFStatType_t statType, StatDisplay_t statDisplay ); + static float SafeCalcFraction( float flNumerator, float flDemoninator ); + static int __cdecl CompareClassStats( const ClassStats_t *pStats0, const ClassStats_t *pStats1 ); + MESSAGE_FUNC( DoResetStats, "DoResetStats" ); + MESSAGE_FUNC_PARAMS( OnTextChanged, "TextChanged", data ); + + vgui::EditablePanel *m_pPlayerData; + + vgui::EditablePanel *m_pInteractiveHeaders; + vgui::EditablePanel *m_pNonInteractiveHeaders; + vgui::ComboBox *m_pBarChartComboBoxA; + vgui::ComboBox *m_pBarChartComboBoxB; + vgui::ComboBox *m_pClassComboBox; + CTFImagePanel *m_pTipImage; + vgui::Label *m_pTipText; + vgui::EditablePanel *m_pMapInfoPanel; + vgui::Panel *m_pLeaderboardTitle; + + vgui::ImagePanel *m_pMainBackground; + void UpdateMainBackground( void ); + + vgui::EditablePanel *m_pContributedPanel; + +#ifdef _X360 + CTFFooter *m_pFooter; +#else + vgui::Button *m_pNextTipButton; + vgui::Button *m_pCloseButton; + vgui::Button *m_pResetStatsButton; +#endif + + bool m_bInteractive; // are we in interactive mode + bool m_bEmbedded; // are we embedded in a property sheet? + bool m_bControlsLoaded; // have we loaded controls yet + CUtlVector<ClassStats_t> m_aClassStats; // stats data + int m_xStartLHBar; // x min of bars in left hand bar chart + int m_xStartRHBar; // x min of bars in right hand bar chart + int m_iBarMaxWidth; // width of bars in bar charts + int m_iBarHeight; // height of bars in bar charts + + int m_iSelectedClass; // what class is selected, if any + int m_iTotalSpawns; // how many spawns of all classes does this player have + TFStatType_t m_statBarGraph[2]; // what stat is displayed in the left hand and right hand bar graphs + StatDisplay_t m_displayBarGraph[2]; // the display type for the left hand and right hand bar graphs + + bool m_bShowingLeaderboard; + bool m_bLoadingCommunityMap; + int m_xStartLeaderboard; + int m_yStartLeaderboard; + CUtlVector< vgui::EditablePanel* > m_vecLeaderboardEntries; + +#ifdef _X360 + bool m_bShowBackButton; +#endif +}; + + +CTFStatsSummaryPanel *GStatsSummaryPanel(); +void DestroyStatsSummaryPanel(); +const char *FormatSeconds( int seconds ); + +void UpdateStatSummaryPanels( CUtlVector<ClassStats_t> &vecClassStats ); + +#endif // TF_STATSSUMMARY_H diff --git a/game/client/tf/vgui/tf_teammenu.cpp b/game/client/tf/vgui/tf_teammenu.cpp new file mode 100644 index 0000000..10be493 --- /dev/null +++ b/game/client/tf/vgui/tf_teammenu.cpp @@ -0,0 +1,901 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "cbase.h" + +#include <vgui_controls/Label.h> +#include <vgui_controls/Button.h> +#include <vgui_controls/ImagePanel.h> +#include <vgui_controls/RichText.h> +#include <vgui_controls/Frame.h> +#include <vgui/IScheme.h> +#include <game/client/iviewport.h> +#include <vgui/IVGui.h> +#include <KeyValues.h> +#include <filesystem.h> +#include <vgui_controls/AnimationController.h> +#include "iclientmode.h" +#include "clientmode_shared.h" +#include "inputsystem/iinputsystem.h" + +#include "vguicenterprint.h" +#include "tf_controls.h" +#include "basemodelpanel.h" +#include "tf_teammenu.h" +#include <convar.h> +#include "IGameUIFuncs.h" // for key bindings +#include "hud.h" // for gEngfuncs +#include "c_tf_player.h" +#include "tf_gamerules.h" +#include "c_team.h" +#include "tf_hud_notification_panel.h" +#include "iinput.h" + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CTFTeamButton::CTFTeamButton( vgui::Panel *parent, const char *panelName ) : CExButton( parent, panelName, "" ) +{ + m_szModelPanel[0] = '\0'; + m_iTeam = TEAM_UNASSIGNED; + m_flHoverTimeToWait = -1; + m_flHoverTime = -1; + m_bMouseEntered = false; + m_bTeamDisabled = false; + + vgui::ivgui()->AddTickSignal( GetVPanel(), 100 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTeamButton::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + Q_strncpy( m_szModelPanel, inResourceData->GetString( "associated_model", "" ), sizeof( m_szModelPanel ) ); + m_iTeam = inResourceData->GetInt( "team", TEAM_UNASSIGNED ); + m_flHoverTimeToWait = inResourceData->GetFloat( "hover", -1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTeamButton::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + SetDefaultColor( GetFgColor(), Color( 0, 0, 0, 0 ) ); + SetArmedColor( GetButtonFgColor(), Color( 0, 0, 0, 0 ) ); + SetDepressedColor( GetButtonFgColor(), Color( 0, 0, 0, 0 ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTeamButton::SendAnimation( const char *pszAnimation ) +{ + Panel *pParent = GetParent(); + if ( pParent ) + { + CModelPanel *pModel = dynamic_cast< CModelPanel* >( pParent->FindChildByName( m_szModelPanel ) ); + if ( pModel ) + { + KeyValues *kvParms = new KeyValues( "SetAnimation" ); + if ( kvParms ) + { + kvParms->SetString( "animation", pszAnimation ); + PostMessage( pModel, kvParms ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTeamButton::SetDefaultAnimation( const char *pszName ) +{ + Panel *pParent = GetParent(); + if ( pParent ) + { + CModelPanel *pModel = dynamic_cast< CModelPanel* >( pParent->FindChildByName( m_szModelPanel ) ); + if ( pModel ) + { + pModel->SetDefaultAnimation( pszName ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFTeamButton::IsTeamFull() +{ + if ( TFGameRules() && TFGameRules()->IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true ) + return false; + + bool bRetVal = false; + + if ( ( m_iTeam > TEAM_UNASSIGNED ) && GetParent() ) + { + CTFTeamMenu *pTeamMenu = dynamic_cast< CTFTeamMenu* >( GetParent() ); + if ( pTeamMenu ) + { + bRetVal = ( m_iTeam == TF_TEAM_BLUE ) ? pTeamMenu->IsBlueTeamDisabled() : pTeamMenu->IsRedTeamDisabled(); + } + } + + return bRetVal; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTeamButton::OnCursorEntered() +{ + BaseClass::OnCursorEntered(); + + SetMouseEnteredState( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTeamButton::OnCursorExited() +{ + BaseClass::OnCursorExited(); + + SetMouseEnteredState( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTeamButton::SetMouseEnteredState( bool state ) +{ + if ( state ) + { + m_bMouseEntered = true; + + if ( m_flHoverTimeToWait > 0 ) + { + m_flHoverTime = gpGlobals->curtime + m_flHoverTimeToWait; + } + else + { + m_flHoverTime = -1; + } + + if ( m_bTeamDisabled ) + { + SendAnimation( "enter_disabled" ); + } + else + { + SendAnimation( "enter_enabled" ); + } + } + else + { + m_bMouseEntered = false; + m_flHoverTime = -1; + + if ( m_bTeamDisabled ) + { + SendAnimation( "exit_disabled" ); + } + else + { + SendAnimation( "exit_enabled" ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTeamButton::OnTick() +{ + if ( GetParent() && !GetParent()->IsVisible() ) + return; + + // check to see if our state has changed + bool bDisabled = IsTeamFull(); + + if ( bDisabled != m_bTeamDisabled ) + { + m_bTeamDisabled = bDisabled; + + if ( m_bMouseEntered ) + { + // something has changed, so reset our state + SetMouseEnteredState( true ); + } + else + { + // the mouse isn't currently over the button, but we should update the status + if ( m_bTeamDisabled ) + { + SendAnimation( "idle_disabled" ); + } + else + { + SendAnimation( "idle_enabled" ); + } + } + } + + if ( ( m_flHoverTime > 0 ) && ( m_flHoverTime < gpGlobals->curtime ) ) + { + m_flHoverTime = -1; + + if ( m_bTeamDisabled ) + { + SendAnimation( "hover_disabled" ); + } + else + { + SendAnimation( "hover_enabled" ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CTFTeamMenu::CTFTeamMenu( IViewPort *pViewPort ) : CTeamMenu( pViewPort ) +{ + SetMinimizeButtonVisible( false ); + SetMaximizeButtonVisible( false ); + SetCloseButtonVisible( false ); + SetVisible( false ); + SetKeyBoardInputEnabled( true ); + + m_iTeamMenuKey = BUTTON_CODE_INVALID; + + m_pBlueTeamButton = new CTFTeamButton( this, "teambutton0" ); + m_pRedTeamButton = new CTFTeamButton( this, "teambutton1" ); + m_pAutoTeamButton = new CTFTeamButton( this, "teambutton2" ); + m_pSpecTeamButton = new CTFTeamButton( this, "teambutton3" ); + m_pSpecLabel = new CExLabel( this, "TeamMenuSpectate", "" ); + +#ifdef _X360 + m_pFooter = new CTFFooter( this, "Footer" ); +#else + m_pCancelButton = new CExButton( this, "CancelButton", "#TF_Cancel" ); + + m_pHighlanderLabel = new CExLabel( this, "HighlanderLabel", "" ); + m_pHighlanderLabelShadow = new CExLabel( this, "HighlanderLabelShadow", "" ); + m_pTeamsFullLabel = new CExLabel( this, "TeamsFullLabel", "" ); + m_pTeamsFullLabelShadow = new CExLabel( this, "TeamsFullLabelShadow", "" ); + m_pTeamsFullArrow = new CTFImagePanel( this, "TeamsFullArrow" ); + +#endif + + vgui::ivgui()->AddTickSignal( GetVPanel(), 100 ); + + m_bRedDisabled = false; + m_bBlueDisabled = false; + + if ( g_pInputSystem && ::input->IsSteamControllerActive() ) + { + LoadControlSettings( "Resource/UI/Teammenu_SC.res" ); + SetMouseInputEnabled( false ); + } + else + { + LoadControlSettings( "Resource/UI/Teammenu.res" ); + SetMouseInputEnabled( true ); + } + + ListenForGameEvent( "server_spawn" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CTFTeamMenu::~CTFTeamMenu() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +void CTFTeamMenu::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + if ( ::input->IsSteamControllerActive() ) + { + LoadControlSettings( "Resource/UI/Teammenu_SC.res" ); + m_pCancelHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "CancelHintIcon" ) ); + m_pJoinAutoHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "JoinAutoHintIcon" ) ); + m_pJoinBluHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "JoinBluHintIcon" ) ); + m_pJoinRedHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "JoinRedHintIcon" ) ); + m_pJoinSpectatorsHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "JoinSpectatorsHintIcon" ) ); + + SetMouseInputEnabled( false ); + } + else + { + LoadControlSettings( "Resource/UI/Teammenu.res" ); + + m_pCancelHintIcon = nullptr; + m_pJoinAutoHintIcon = m_pJoinRedHintIcon = m_pJoinBluHintIcon = m_pJoinSpectatorsHintIcon = nullptr; + + SetMouseInputEnabled( true ); + } + + Update(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTeamMenu::ShowPanel( bool bShow ) +{ + if ( BaseClass::IsVisible() == bShow ) + return; + + if ( !gameuifuncs || !gViewPortInterface || !engine ) + return; + + if ( bShow ) + { + if ( !C_TFPlayer::GetLocalTFPlayer() ) + return; + + bool bDisallowChange = false; + if ( C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() >= FIRST_GAME_TEAM ) + { + const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() ); + if ( pMatchDesc && !pMatchDesc->m_params.m_bAllowTeamChange ) + { + bDisallowChange = true; + } + } + + if ( ( TFGameRules()->State_Get() == GR_STATE_TEAM_WIN + && C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TFGameRules()->GetWinningTeam() + && C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TEAM_SPECTATOR + && C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TEAM_UNASSIGNED ) + || TFGameRules()->State_Get() == GR_STATE_GAME_OVER + // [msmith] Don't allow the player to switch teams when in training. + || TFGameRules()->IsInTraining() + // or if they are coaching + || C_TFPlayer::GetLocalTFPlayer()->m_bIsCoaching + || bDisallowChange + ) + { + SetVisible( false ); + + CHudNotificationPanel *pNotifyPanel = GET_HUDELEMENT( CHudNotificationPanel ); + if ( pNotifyPanel ) + { + pNotifyPanel->SetupNotifyCustom( "#TF_CantChangeTeamNow", "ico_notify_flag_moving", C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() ); + } + + return; + } + + extern void Coaching_CheckIfEligibleForCoaching(); + Coaching_CheckIfEligibleForCoaching(); + + gViewPortInterface->ShowPanel( PANEL_CLASS_RED, false ); + gViewPortInterface->ShowPanel( PANEL_CLASS_BLUE, false ); + + engine->CheckPoint( "TeamMenu" ); + + // Force us to reload our scheme, in case Steam Controller stuff has changed. + InvalidateLayout( true, true ); + + Activate(); + + // get key bindings if shown + m_iTeamMenuKey = gameuifuncs->GetButtonCodeForBind( "changeteam" ); + m_iScoreBoardKey = gameuifuncs->GetButtonCodeForBind( "showscores" ); + + switch ( C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() ) + { + case TF_TEAM_BLUE: + if ( ::input->EnableJoystickMode() ) + { + m_pBlueTeamButton->OnCursorEntered(); + m_pBlueTeamButton->SetDefaultAnimation( "enter_enabled" ); + } + GetFocusNavGroup().SetCurrentFocus( m_pBlueTeamButton->GetVPanel(), m_pBlueTeamButton->GetVPanel() ); + break; + + case TF_TEAM_RED: + if ( ::input->EnableJoystickMode() ) + { + m_pRedTeamButton->OnCursorEntered(); + m_pRedTeamButton->SetDefaultAnimation( "enter_enabled" ); + } + GetFocusNavGroup().SetCurrentFocus( m_pRedTeamButton->GetVPanel(), m_pRedTeamButton->GetVPanel() ); + break; + + default: + if ( ::input->EnableJoystickMode() ) + { + m_pAutoTeamButton->OnCursorEntered(); + m_pAutoTeamButton->SetDefaultAnimation( "enter_enabled" ); + } + GetFocusNavGroup().SetCurrentFocus( m_pAutoTeamButton->GetVPanel(), m_pAutoTeamButton->GetVPanel() ); + break; + } + + ActivateSelectIconHint( GetFocusNavGroup().GetCurrentFocus() ? GetFocusNavGroup().GetCurrentFocus()->GetTabPosition() : -1 ); + } + else + { + SetVisible( false ); + + SetHighlanderTeamsFullPanels( false, true ); + + if ( ::input->EnableJoystickMode() ) + { + // Close the door behind us + CTFTeamButton *pButton = dynamic_cast< CTFTeamButton *> ( GetFocusNavGroup().GetCurrentFocus() ); + if ( pButton ) + { + pButton->OnCursorExited(); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: called to update the menu with new information +//----------------------------------------------------------------------------- +void CTFTeamMenu::Update( void ) +{ + BaseClass::Update(); + + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + + if ( pLocalPlayer && ( pLocalPlayer->GetTeamNumber() != TEAM_UNASSIGNED ) ) + { +#ifdef _X360 + if ( m_pFooter ) + { + m_pFooter->ShowButtonLabel( "cancel", true ); + } +#else + if ( m_pCancelButton ) + { + m_pCancelButton->SetVisible( true ); + if ( m_pCancelHintIcon ) + { + m_pCancelHintIcon->SetVisible( true ); + } + } +#endif + } + else + { +#ifdef _X360 + if ( m_pFooter ) + { + m_pFooter->ShowButtonLabel( "cancel", false ); + } +#else + if ( m_pCancelButton && m_pCancelButton->IsVisible() ) + { + m_pCancelButton->SetVisible( false ); + if ( m_pCancelHintIcon ) + { + m_pCancelHintIcon->SetVisible( false ); + } + } +#endif + } +} + +#ifdef _X360 +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTeamMenu::Join_Team( const CCommand &args ) +{ + if ( args.ArgC() > 1 ) + { + char cmd[256]; + Q_snprintf( cmd, sizeof( cmd ), "jointeam_nomenus %s", args.Arg( 1 ) ); + OnCommand( cmd ); + } +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: chooses and loads the text page to display that describes mapName map +//----------------------------------------------------------------------------- +void CTFTeamMenu::LoadMapPage( const char *mapName ) +{ + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTeamMenu::OnKeyCodePressed( KeyCode code ) +{ + if ( ( m_iTeamMenuKey != BUTTON_CODE_INVALID && m_iTeamMenuKey == code ) || + code == KEY_XBUTTON_BACK || + code == KEY_XBUTTON_B || + code == STEAMCONTROLLER_B ) + { + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + + if ( pLocalPlayer && ( pLocalPlayer->GetTeamNumber() != TEAM_UNASSIGNED ) ) + { + ShowPanel( false ); + } + } + else if( code == KEY_SPACE || code == STEAMCONTROLLER_Y ) + { + engine->ClientCmd( "jointeam auto" ); + + ShowPanel( false ); + OnClose(); + } + else if( code == KEY_XBUTTON_A || code == KEY_XBUTTON_RTRIGGER || code == STEAMCONTROLLER_A ) + { + // select the active focus + if ( GetFocusNavGroup().GetCurrentFocus() ) + { + ipanel()->SendMessage( GetFocusNavGroup().GetCurrentFocus()->GetVPanel(), new KeyValues( "PressButton" ), GetVPanel() ); + } + } + else if( code == KEY_XBUTTON_RIGHT || code == KEY_XSTICK1_RIGHT || code == STEAMCONTROLLER_DPAD_RIGHT ) + { + CTFTeamButton *pButton; + + pButton = dynamic_cast< CTFTeamButton *> ( GetFocusNavGroup().GetCurrentFocus() ); + if ( pButton ) + { + pButton->OnCursorExited(); + GetFocusNavGroup().RequestFocusNext( pButton->GetVPanel() ); + } + else + { + GetFocusNavGroup().RequestFocusNext( NULL ); + } + + pButton = dynamic_cast< CTFTeamButton * > ( GetFocusNavGroup().GetCurrentFocus() ); + if ( pButton ) + { + pButton->OnCursorEntered(); + } + + ActivateSelectIconHint( GetFocusNavGroup().GetCurrentFocus() ? GetFocusNavGroup().GetCurrentFocus()->GetTabPosition() : -1 ); + } + else if( code == KEY_XBUTTON_LEFT || code == KEY_XSTICK1_LEFT || code == STEAMCONTROLLER_DPAD_LEFT ) + { + CTFTeamButton *pButton; + + pButton = dynamic_cast< CTFTeamButton *> ( GetFocusNavGroup().GetCurrentFocus() ); + if ( pButton ) + { + pButton->OnCursorExited(); + GetFocusNavGroup().RequestFocusPrev( pButton->GetVPanel() ); + } + else + { + GetFocusNavGroup().RequestFocusPrev( NULL ); + } + + pButton = dynamic_cast< CTFTeamButton * > ( GetFocusNavGroup().GetCurrentFocus() ); + if ( pButton ) + { + pButton->OnCursorEntered(); + } + + ActivateSelectIconHint( GetFocusNavGroup().GetCurrentFocus() ? GetFocusNavGroup().GetCurrentFocus()->GetTabPosition() : -1 ); + } + else if ( m_iScoreBoardKey != BUTTON_CODE_INVALID && m_iScoreBoardKey == code ) + { + gViewPortInterface->ShowPanel( PANEL_SCOREBOARD, true ); + gViewPortInterface->PostMessageToPanel( PANEL_SCOREBOARD, new KeyValues( "PollHideCode", "code", code ) ); + } + else + { + BaseClass::OnKeyCodePressed( code ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called when the user picks a team +//----------------------------------------------------------------------------- +void CTFTeamMenu::OnCommand( const char *command ) +{ + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + + if ( Q_stricmp( command, "vguicancel" ) ) + { + // we're selecting a team, so make sure it's not the team we're already on before sending to the server + if ( pLocalPlayer && ( Q_strstr( command, "jointeam " ) ) ) + { + const char *pTeam = command + Q_strlen( "jointeam " ); + int iTeam = TEAM_INVALID; + + if ( Q_stricmp( pTeam, "spectate" ) == 0 ) + { + iTeam = TEAM_SPECTATOR; + } + else if ( Q_stricmp( pTeam, "red" ) == 0 ) + { + iTeam = TF_TEAM_RED; + } + else if ( Q_stricmp( pTeam, "blue" ) == 0 ) + { + iTeam = TF_TEAM_BLUE; + } + + if ( iTeam == TF_TEAM_RED && m_bRedDisabled ) + { + return; + } + + if ( iTeam == TF_TEAM_BLUE && m_bBlueDisabled ) + { + return; + } + + // are we selecting the team we're already on? + if ( pLocalPlayer->GetTeamNumber() != iTeam ) + { + engine->ClientCmd( command ); + } + } + else if ( pLocalPlayer && ( Q_strstr( command, "jointeam_nomenus " ) ) ) + { + engine->ClientCmd( command ); + } + } + + BaseClass::OnCommand( command ); + ShowPanel( false ); + OnClose(); +} + +void CTFTeamMenu::OnClose() +{ + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pLocalPlayer ) + { + // Clear the HIDEHUD_HEALTH bit we hackily added. Turns out prediction + // was restoring these bits every frame. Unfortunately, prediction + // is off for karts which means the spell hud item would disappear if you + // brought up this menu and returned. + pLocalPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_HEALTH; + } + + BaseClass::OnClose(); +} + +//----------------------------------------------------------------------------- +// Purpose: Activate the right selection hint icon, depending on the focus group number selected +//----------------------------------------------------------------------------- +void CTFTeamMenu::ActivateSelectIconHint( int focus_group_number ) +{ + if ( m_pJoinAutoHintIcon ) m_pJoinAutoHintIcon->SetVisible( false ); + if ( m_pJoinBluHintIcon ) m_pJoinBluHintIcon->SetVisible( false ); + if ( m_pJoinRedHintIcon ) m_pJoinRedHintIcon->SetVisible( false ); + if ( m_pJoinSpectatorsHintIcon ) m_pJoinSpectatorsHintIcon->SetVisible( false ); + + CSCHintIcon* icon = nullptr; + switch ( focus_group_number ) + { + case 1: icon = m_pJoinAutoHintIcon; break; + case 2: icon = m_pJoinSpectatorsHintIcon; break; + case 3: icon = m_pJoinBluHintIcon; break; + case 4: icon = m_pJoinRedHintIcon; break; + } + + if ( icon ) + { + icon->SetVisible( true ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTeamMenu::SetHighlanderTeamsFullPanels( bool bTeamsFull, bool bForce /* = false */ ) +{ + if ( m_pTeamsFullLabel ) + { + if ( bForce || ( m_pTeamsFullLabel->IsVisible() != bTeamsFull ) ) + { + m_pTeamsFullLabel->SetVisible( bTeamsFull ); + } + } + + if ( m_pTeamsFullLabelShadow ) + { + if ( bForce || ( m_pTeamsFullLabelShadow->IsVisible() != bTeamsFull ) ) + { + m_pTeamsFullLabelShadow->SetVisible( bTeamsFull ); + } + } + + if ( !mp_allowspectators.GetBool() ) + { + // don't show the arrow if the server doesn't allow spectators + bTeamsFull = false; + } + + if ( m_pTeamsFullArrow ) + { + if ( bForce || ( m_pTeamsFullArrow->IsVisible() != bTeamsFull ) ) + { + m_pTeamsFullArrow->SetVisible( bTeamsFull ); + + if ( bTeamsFull ) + { + // turn on animation + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "TeamsFullArrowAnimate" ); + } + else + { + // turn off animation + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "TeamsFullArrowAnimateEnd" ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Frame-based update +//----------------------------------------------------------------------------- +void CTFTeamMenu::OnTick() +{ + // update the number of players on each team + + // enable or disable buttons based on team limit + + if ( !IsVisible() ) + return; + + C_Team *pRed = GetGlobalTeam( TF_TEAM_RED ); + C_Team *pBlue = GetGlobalTeam( TF_TEAM_BLUE ); + + if ( !pRed || !pBlue ) + return; + + // set our team counts + SetDialogVariable( "bluecount", pBlue->Get_Number_Players() ); + SetDialogVariable( "redcount", pRed->Get_Number_Players() ); + + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + + if ( !pLocalPlayer ) + return; + + CTFGameRules *pRules = TFGameRules(); + + if ( !pRules ) + return; + + bool bHighlander = pRules->IsInHighlanderMode(); + + if ( m_pHighlanderLabel ) + { + if ( m_pHighlanderLabel->IsVisible() != bHighlander ) + { + m_pHighlanderLabel->SetVisible( bHighlander ); + } + } + + if ( m_pHighlanderLabelShadow ) + { + if ( m_pHighlanderLabelShadow->IsVisible() != bHighlander ) + { + m_pHighlanderLabelShadow->SetVisible( bHighlander ); + } + } + + // check if teams are unbalanced + m_bRedDisabled = m_bBlueDisabled = false; + + int iHeavyTeam, iLightTeam; + + bool bUnbalanced = pRules->AreTeamsUnbalanced( iHeavyTeam, iLightTeam ); + + int iCurrentTeam = pLocalPlayer->GetTeamNumber(); + + if ( ( bUnbalanced && iHeavyTeam == TF_TEAM_RED ) || + ( pRules->WouldChangeUnbalanceTeams( TF_TEAM_RED, iCurrentTeam ) ) || + ( bHighlander && GetGlobalTeam( TF_TEAM_RED )->GetNumPlayers() >= TF_LAST_NORMAL_CLASS - 1 ) || + ( pRules->IsMannVsMachineMode() && ( GetGlobalTeam( TF_TEAM_RED )->GetNumPlayers() >= kMVM_DefendersTeamSize ) ) ) + { + m_bRedDisabled = true; + } + + if ( ( bUnbalanced && iHeavyTeam == TF_TEAM_BLUE ) || + ( pRules->WouldChangeUnbalanceTeams( TF_TEAM_BLUE, iCurrentTeam ) ) || + ( bHighlander && GetGlobalTeam( TF_TEAM_BLUE )->GetNumPlayers() >= TF_LAST_NORMAL_CLASS - 1 ) || + ( pRules->IsMannVsMachineMode() ) ) + { + m_bBlueDisabled = true; + } + + bool bTeamsFull = m_bRedDisabled && m_bBlueDisabled; + SetHighlanderTeamsFullPanels( bHighlander && bTeamsFull ); + + if ( m_pSpecTeamButton && m_pSpecLabel && m_pAutoTeamButton ) + { + { + if ( mp_allowspectators.GetBool() ) + { + if ( !m_pSpecTeamButton->IsVisible() ) + { + m_pSpecTeamButton->SetVisible( true ); + m_pSpecLabel->SetVisible( true ); + } + + if ( !m_pAutoTeamButton->IsVisible() ) + { + m_pAutoTeamButton->SetVisible( true ); + } + } + else + { + if ( m_pSpecTeamButton->IsVisible() ) + { + m_pSpecTeamButton->SetVisible( false ); + m_pSpecLabel->SetVisible( false ); + } + + if ( bHighlander ) + { + if ( bTeamsFull ) + { + if ( m_pAutoTeamButton->IsVisible() ) + { + m_pAutoTeamButton->SetVisible( false ); + } + } + else + { + if ( !m_pAutoTeamButton->IsVisible() ) + { + m_pAutoTeamButton->SetVisible( true ); + } + } + } + } + } + } +} + +void CTFTeamMenu::OnThink() +{ + //Always hide the health... this needs to be done every frame because a message from the server keeps resetting this. + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pLocalPlayer ) + { + pLocalPlayer->m_Local.m_iHideHUD |= HIDEHUD_HEALTH; + } + + BaseClass::OnThink(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTeamMenu::FireGameEvent( IGameEvent *event ) +{ + // when we are changing levels + if ( FStrEq( event->GetName(), "server_spawn" ) ) + { + SetHighlanderTeamsFullPanels( false, true ); + } +} diff --git a/game/client/tf/vgui/tf_teammenu.h b/game/client/tf/vgui/tf_teammenu.h new file mode 100644 index 0000000..9b5fca0 --- /dev/null +++ b/game/client/tf/vgui/tf_teammenu.h @@ -0,0 +1,136 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef TF_TEAMMENU_H +#define TF_TEAMMENU_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_controls.h" +#include "tf_imagepanel.h" +#include <teammenu.h> + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CTFTeamButton : public CExButton +{ +private: + DECLARE_CLASS_SIMPLE( CTFTeamButton, CExButton ); + +public: + CTFTeamButton( vgui::Panel *parent, const char *panelName ); + + void ApplySettings( KeyValues *inResourceData ); + void ApplySchemeSettings( vgui::IScheme *pScheme ); + + void OnCursorExited(); + void OnCursorEntered(); + + void OnTick( void ); + + void SetDefaultAnimation( const char *pszName ); + +private: + bool IsTeamFull(); + void SendAnimation( const char *pszAnimation ); + void SetMouseEnteredState( bool state ); + +private: + char m_szModelPanel[64]; // the panel we'll send messages to + int m_iTeam; // the team we're associated with (if any) + + float m_flHoverTimeToWait; // length of time to wait before reporting a "hover" message (-1 = no hover) + float m_flHoverTime; // when should a "hover" message be sent? + bool m_bMouseEntered; // used to track when the mouse is over a button + bool m_bTeamDisabled; // used to keep track of whether our team is a valid team for selection +}; + +//----------------------------------------------------------------------------- +// Purpose: Displays the team menu +//----------------------------------------------------------------------------- +class CTFTeamMenu : public CTeamMenu, public CGameEventListener +{ +private: + DECLARE_CLASS_SIMPLE( CTFTeamMenu, CTeamMenu ); + +public: + CTFTeamMenu( IViewPort *pViewPort ); + ~CTFTeamMenu(); + + void Update(); + void ShowPanel( bool bShow ); + +#ifdef _X360 + CON_COMMAND_MEMBER_F( CTFTeamMenu, "join_team", Join_Team, "Send a jointeam command", 0 ); +#endif + + + bool IsBlueTeamDisabled(){ return m_bBlueDisabled; } + bool IsRedTeamDisabled(){ return m_bRedDisabled; } + + // IGameEventListener interface: + virtual void FireGameEvent( IGameEvent *event ); + +protected: + virtual void ApplySchemeSettings(vgui::IScheme *pScheme); + virtual void OnKeyCodePressed( vgui::KeyCode code ); + + // command callbacks + virtual void OnCommand( const char *command ); + virtual void OnClose(); + + virtual void LoadMapPage( const char *mapName ); + + virtual void OnTick( void ); + + virtual void OnThink() OVERRIDE; + +private: + + void SetHighlanderTeamsFullPanels( bool bTeamsFull, bool bForce = false ); + void ActivateSelectIconHint( int focus_group_number ); + +private: + + CTFTeamButton *m_pBlueTeamButton; + CTFTeamButton *m_pRedTeamButton; + CTFTeamButton *m_pAutoTeamButton; + CTFTeamButton *m_pSpecTeamButton; + CExLabel *m_pSpecLabel; + +#ifdef _X360 + CTFFooter *m_pFooter; +#else + CExButton *m_pCancelButton; + + CExLabel *m_pHighlanderLabel; + CExLabel *m_pHighlanderLabelShadow; + CExLabel *m_pTeamsFullLabel; + CExLabel *m_pTeamsFullLabelShadow; + CTFImagePanel *m_pTeamsFullArrow; + + CSCHintIcon *m_pCancelHintIcon; + CSCHintIcon *m_pJoinBluHintIcon; + CSCHintIcon *m_pJoinRedHintIcon; + CSCHintIcon *m_pJoinAutoHintIcon; + CSCHintIcon *m_pJoinSpectatorsHintIcon; + +#endif + + bool m_bRedDisabled; + bool m_bBlueDisabled; + + +private: + enum { NUM_TEAMS = 3 }; + + ButtonCode_t m_iTeamMenuKey; +}; + +#endif // TF_TEAMMENU_H diff --git a/game/client/tf/vgui/tf_textwindow.cpp b/game/client/tf/vgui/tf_textwindow.cpp new file mode 100644 index 0000000..2734860 --- /dev/null +++ b/game/client/tf/vgui/tf_textwindow.cpp @@ -0,0 +1,274 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "inputsystem/iinputsystem.h" +#include "input.h" + +#include "tf_textwindow.h" +#include <cdll_client_int.h> + +#include <vgui/IScheme.h> +#include <vgui/ILocalize.h> +#include <vgui/ISurface.h> +#include <filesystem.h> +#include <KeyValues.h> +#include <convar.h> +#include <vgui_controls/ImageList.h> + +#include <vgui_controls/Panel.h> +#include <vgui_controls/TextEntry.h> +#include <vgui_controls/Button.h> +#include <vgui_controls/BuildGroup.h> +#include <vgui_controls/ImagePanel.h> + +#include "tf_controls.h" +#include "tf_shareddefs.h" + +#include "IGameUIFuncs.h" // for key bindings +#include <igameresources.h> +extern IGameUIFuncs *gameuifuncs; // for key binding details + +#include <game/client/iviewport.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CTFTextWindow::CTFTextWindow( IViewPort *pViewPort ) : CTextWindow( pViewPort ) +{ + m_pTFTextMessage = new CExRichText( this, "TFTextMessage" ); + + SetProportional( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CTFTextWindow::~CTFTextWindow() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTextWindow::ApplySchemeSettings( IScheme *pScheme ) +{ + Frame::ApplySchemeSettings( pScheme ); // purposely skipping the CTextWindow version + + if ( ::input->IsSteamControllerActive() ) + { + if ( m_bCustomSvrPage ) + { + LoadControlSettings( "Resource/UI/TextWindowCustomServer_SC.res" ); + } + else + { + LoadControlSettings( "Resource/UI/TextWindow_SC.res" ); + } + + SetMouseInputEnabled( false ); + } + else + { + if ( m_bCustomSvrPage ) + { + LoadControlSettings( "Resource/UI/TextWindowCustomServer.res" ); + } + else + { + LoadControlSettings( "Resource/UI/TextWindow.res" ); + } + SetMouseInputEnabled( true ); + } + + + if ( m_pHTMLMessage ) + { + m_pHTMLMessage->SetBgColor( pScheme->GetColor( "HTMLBackground", Color( 255, 0, 0, 255 ) ) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTextWindow::Reset( void ) +{ + Update(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTextWindow::OnThink() +{ + //Always hide the health... this needs to be done every frame because a message from the server keeps resetting this. + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pLocalPlayer ) + { + pLocalPlayer->m_Local.m_iHideHUD |= HIDEHUD_HEALTH; + } + + BaseClass::OnThink(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTextWindow::SetData(KeyValues *data) +{ + m_bCustomSvrPage = data->GetBool( "customsvr" ); + InvalidateLayout( false, true ); + BaseClass::SetData( data ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTextWindow::Update() +{ + CExLabel *pTitle = dynamic_cast<CExLabel *>( FindChildByName( "TFMessageTitle" ) ); + if ( pTitle ) + { + pTitle->SetText( m_szTitle ); + } + + if ( m_pTFTextMessage ) + { + m_pTFTextMessage->SetVisible( false ); + } + + BaseClass::Update(); + + Panel *pOK = FindChildByName( "ok" ); + if ( pOK ) + { + pOK->RequestFocus(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------- +void CTFTextWindow::SetVisible( bool state ) +{ + BaseClass::SetVisible( state ); + + if ( state ) + { + Panel *pOK = FindChildByName( "ok" ); + if ( pOK ) + { + pOK->RequestFocus(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: shows the text window +//----------------------------------------------------------------------------- +void CTFTextWindow::ShowPanel( bool bShow ) +{ + if ( IsVisible() == bShow ) + return; + + // Force use to reevaluate our scheme, in case Steam Controller stuff has changed. + InvalidateLayout( true, true ); + + BaseClass::ShowPanel( bShow ); + + if ( m_pViewPort ) + { + m_pViewPort->ShowBackGround( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTextWindow::OnKeyCodePressed( KeyCode code ) +{ + if ( code == KEY_XBUTTON_A || code == STEAMCONTROLLER_A ) + { + OnCommand( "okay" ); + } + else + { + BaseClass::OnKeyCodePressed( code ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: The background is painted elsewhere, so we should do nothing +//----------------------------------------------------------------------------- +void CTFTextWindow::PaintBackground() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTextWindow::OnCommand( const char *command ) +{ + BaseClass::OnCommand( command ); + + // Don't open up the mapinfo if it was a custom server html page + if ( !Q_strcmp( command, "okay" ) && !m_bCustomSvrPage ) + { + m_pViewPort->ShowPanel( PANEL_MAPINFO, true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTextWindow::ShowText( const char *text ) +{ + ShowTitleLabel( true ); + + if ( m_pTFTextMessage ) + { + m_pTFTextMessage->SetVisible( true ); + m_pTFTextMessage->SetText( text ); + m_pTFTextMessage->GotoTextStart(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTextWindow::ShowURL( const char *URL, bool bAllowUserToDisable ) +{ + ShowTitleLabel( false ); + BaseClass::ShowURL( URL, bAllowUserToDisable ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTextWindow::ShowFile( const char *filename ) +{ + ShowTitleLabel( false ) ; + BaseClass::ShowFile( filename ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFTextWindow::ShowTitleLabel( bool show ) +{ + CExLabel *pTitle = dynamic_cast<CExLabel *>( FindChildByName( "TFMessageTitle" ) ); + if ( pTitle ) + { + pTitle->SetVisible( show ); + } +} diff --git a/game/client/tf/vgui/tf_textwindow.h b/game/client/tf/vgui/tf_textwindow.h new file mode 100644 index 0000000..abb2958 --- /dev/null +++ b/game/client/tf/vgui/tf_textwindow.h @@ -0,0 +1,62 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TFTEXTWINDOW_H +#define TFTEXTWINDOW_H +#ifdef _WIN32 +#pragma once +#endif + +#include <vgui_controls/Panel.h> +#include "vguitextwindow.h" +#include "tf_controls.h" +#include "IconPanel.h" + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: displays the MOTD +//----------------------------------------------------------------------------- + +class CTFTextWindow : public CTextWindow +{ +private: + DECLARE_CLASS_SIMPLE( CTFTextWindow, CTextWindow ); + +public: + CTFTextWindow( IViewPort *pViewPort ); + virtual ~CTFTextWindow(); + + virtual void SetData(KeyValues *data); + virtual void Update(); + virtual void Reset(); + virtual void SetVisible(bool state); + virtual void ShowPanel( bool bShow ); + virtual void OnKeyCodePressed( vgui::KeyCode code ); + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void ShowFile( const char *filename ); + virtual void ShowURL( const char *URL, bool bAllowUserToDisable = true ); + virtual void ShowText( const char *text ); + void ShowTitleLabel( bool show ); + virtual void OnThink(); + + virtual GameActionSet_t GetPreferredActionSet() { return GAME_ACTION_SET_IN_GAME_HUD; } + +public: + virtual void PaintBackground(); + +protected: + // vgui overrides + virtual void OnCommand( const char *command ); + +private: + CExRichText *m_pTFTextMessage; + bool m_bCustomSvrPage; +}; + + +#endif // TFTEXTWINDOW_H diff --git a/game/client/tf/vgui/tf_training_ui.cpp b/game/client/tf/vgui/tf_training_ui.cpp new file mode 100644 index 0000000..808c423 --- /dev/null +++ b/game/client/tf/vgui/tf_training_ui.cpp @@ -0,0 +1,2205 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include <filesystem.h> +#include "ienginevgui.h" +#include "tf_gcmessages.h" +#include "tf_mouseforwardingpanel.h" +#include "gc_clientsystem.h" +#include "c_tf_gamestats.h" +#include "tf_hud_mainmenuoverride.h" +#include "tf_gamerules.h" +#include "econ/confirm_dialog.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +//------------------------------------------------------------------------------------------------------ + +#define TRAINING_DIALOG_NAME "TrainingDialog" +#define TRAINING_PROGRESS_FILE "trainingprogress.txt" + +//------------------------------------------------------------------------------------------------------ + +#ifdef _DEBUG +#define PRINT_KEY_VALUES( kv_ ) { CUtlBuffer buf(0, 0, CUtlBuffer::TEXT_BUFFER); kv_->RecursiveSaveToFile( buf, 0 ); ConMsg( "---\n%s\n---\n", buf.String() ); } +#else +#define PRINT_KEY_VALUES( kv_ ) { } +#endif + +//------------------------------------------------------------------------------------------------------ + +enum GameMode_t // Supported game modes for offline practice +{ + MODE_INVALID = -1, + + MODE_CP, + MODE_KOTH, + MODE_PL, + + NUM_GAME_MODES +}; + +static const char *gs_pGameModeTokens[ NUM_GAME_MODES ] = { + "#Gametype_CP", + "#Gametype_Koth", + "#Gametype_Escort", +}; + +//------------------------------------------------------------------------------------------------------ + +ConVar cl_training_completed_with_classes( "cl_training_completed_with_classes", "0", FCVAR_ARCHIVE, "Bitfield representing what classes have been used to complete training." ); + +bool Training_TrainingProgressFileExists() +{ + const char *pFilename = TRAINING_PROGRESS_FILE; + return g_pFullFileSystem->FileExists( pFilename, NULL ); +} + +//------------------------------------------------------------------------------------------------------ + +static int Training_GetClassProgress( int iClass ) // Returns a percent, in the range [0,100] +{ + Assert( iClass >= TF_FIRST_NORMAL_CLASS && iClass <= TF_LAST_NORMAL_CLASS ); + + const int nDefaultResult = iClass == TF_CLASS_SOLDIER ? 0 : -1; + + KeyValuesAD pTrainingProgressData( "TrainingProgress" ); + if ( !pTrainingProgressData ) + { + Warning( "Failed to save training progress!\n" ); + AssertMsg( 0, "Failed to save training progress!\n" ); + return nDefaultResult; + } + + const char *pFilename = TRAINING_PROGRESS_FILE; + if ( !pTrainingProgressData->LoadFromFile( g_pFullFileSystem, pFilename, "MOD" ) ) + return nDefaultResult; + + const char *pClassName = g_aPlayerClassNames_NonLocalized[ iClass ]; + KeyValues *pClassSubKey = pTrainingProgressData->FindKey( pClassName ); + if ( !pClassSubKey ) + return nDefaultResult; + + return pClassSubKey->GetInt( "progress", nDefaultResult ); +} + +static void Training_GetProgress( int pClass[TF_CLASS_COUNT] ) // Returns a percent, in the range [0,100] +{ + KeyValuesAD pTrainingProgressData( "TrainingProgress" ); + const char *pFilename = TRAINING_PROGRESS_FILE; + + bool bLoadedFileOk = pTrainingProgressData->LoadFromFile( g_pFullFileSystem, pFilename, "MOD" ); + + for ( int i = 0; i < TF_CLASS_COUNT; ++i ) + { + const int iClass = i; + const int nDefaultResult = ( bLoadedFileOk && iClass == TF_CLASS_SOLDIER ) ? 0 : -1; + + const char *pClassName = g_aPlayerClassNames_NonLocalized[ iClass ]; + KeyValues *pClassSubKey = pTrainingProgressData->FindKey( pClassName ); + if ( !pClassSubKey ) + { + pClass[ i ] = nDefaultResult; + continue; + } + + pClass[ i ] = pClassSubKey->GetInt( "progress", nDefaultResult ); + } +} + +KeyValues *Training_LoadProgressFile() +{ + KeyValues *pTrainingProgressData = new KeyValues( "TrainingProgress" ); + if ( !pTrainingProgressData ) + { + Warning( "Failed to save training progress!\n" ); + AssertMsg( 0, "Failed to save training progress!\n" ); + return NULL; + } + + // Attempt to load any existing progress from disk + const char *pFilename = TRAINING_PROGRESS_FILE; + if ( !pTrainingProgressData->LoadFromFile( g_pFullFileSystem, pFilename, "MOD" ) ) + { + // File didn't exist - create from defaults + for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; ++i ) + { + KeyValues *pClassSubKey = new KeyValues( g_aPlayerClassNames_NonLocalized[ i ] ); + if ( !pClassSubKey ) + continue; + + pClassSubKey->SetInt( "progress", -1 ); // -1 means they haven't beat anything + pTrainingProgressData->AddSubKey( pClassSubKey ); + } + } + + return pTrainingProgressData; +} + +void Training_SaveProgress( KeyValues *pTrainingProgressData ) +{ + const char *pFilename = TRAINING_PROGRESS_FILE; + if ( !pTrainingProgressData->SaveToFile( g_pFullFileSystem, pFilename, "MOD" ) ) + { + Warning( "Failed to save progress!\n" ); + AssertMsg( 0, "Failed to save progress!" ); + } +} + +KeyValues *Training_FindClassData( KeyValues *pTrainingProgressData, int iClass ) +{ + const char *pClassName = g_aPlayerClassNames_NonLocalized[ iClass ]; + return pTrainingProgressData->FindKey( pClassName ); +} + +void Training_SaveProgress( int pProgress[ TF_CLASS_COUNT ] ) +{ + KeyValues *pTrainingProgressData = Training_LoadProgressFile(); + if ( !pTrainingProgressData ) + return; + + for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; ++i ) + { + KeyValues *pClassSubKey = Training_FindClassData( pTrainingProgressData, i ); + if ( !pClassSubKey ) + { + AssertMsg( 0, "All classes should have been created on load if they didn't exist - this should not happen!" ); + continue; + } + + Assert( pProgress[ i ] >= -1 ); + pClassSubKey->SetInt( "progress", pProgress[ i ] ); + } + + Training_SaveProgress( pTrainingProgressData ); +} + +void Training_MarkClassComplete( int iClass, int iStage ) +{ + Assert( iClass >= TF_FIRST_NORMAL_CLASS && iClass < TF_LAST_NORMAL_CLASS ); + Assert( iStage >= 0 ); + + KeyValues *pTrainingProgressData = Training_LoadProgressFile(); + if ( !pTrainingProgressData ) + return; + + // Find the data for the corresponding class + KeyValues *pClassSubKey = Training_FindClassData( pTrainingProgressData, iClass ); + if ( pClassSubKey ) + { + pClassSubKey->SetInt( "progress", iStage ); + } + else + { + Warning( "Failed to load data for class %s!\n", g_aPlayerClassNames_NonLocalized[ iClass ] ); + } + + // Unlock next class if necessary + const int iLastTrainingClass = TF_CLASS_ENGINEER; + if ( iClass != iLastTrainingClass ) + { + const int aNextClasses[ TF_CLASS_COUNT ] = { + -1, // TF_CLASS_UNDEFINED + -1, // TF_CLASS_SCOUT + -1, // TF_CLASS_SNIPER + TF_CLASS_DEMOMAN, // TF_CLASS_SOLDIER + TF_CLASS_SPY, // TF_CLASS_DEMOMAN + -1, // TF_CLASS_MEDIC + -1, // TF_CLASS_HEAVYWEAPONS + -1, // TF_CLASS_PYRO + TF_CLASS_ENGINEER, // TF_CLASS_SPY + -1, // TF_CLASS_ENGINEER + }; + const int aUnlockRequirements[ TF_CLASS_COUNT ] = { + -1, // TF_CLASS_UNDEFINED + -1, // TF_CLASS_SCOUT + -1, // TF_CLASS_SNIPER + 2, // TF_CLASS_SOLDIER - must beat 2 stages to complete soldier training + 1, // TF_CLASS_DEMOMAN + -1, // TF_CLASS_MEDIC + -1, // TF_CLASS_HEAVYWEAPONS + -1, // TF_CLASS_PYRO + 1, // TF_CLASS_SPY + -1, // TF_CLASS_ENGINEER + }; + const int iNextClass = aNextClasses[ iClass ]; + const bool bCurrentClassCompleted = iStage >= aUnlockRequirements[ iClass ]; + if ( iNextClass >= TF_FIRST_NORMAL_CLASS && bCurrentClassCompleted ) + { + // Find the data for the given class and unlock it + KeyValues *pNextClassData = pTrainingProgressData->FindKey( g_aPlayerClassNames_NonLocalized[ iNextClass ] ); + if ( pNextClassData ) + { + pNextClassData->SetInt( "progress", 0 ); + } + else + { + AssertMsg( 0, "This class data should have been filled out above" ); + } + } + } + + // Attempt to save + Training_SaveProgress( pTrainingProgressData ); + + // Free + pTrainingProgressData->deleteThis(); +} + +static ConVar training_map_video( "training_map_video", "", 0, "Video to show for training" ); + +void CL_Training_LevelShutdown() +{ + training_map_video.Revert(); +} + +int Training_GetNumCoursesForClass( int iClass ) +{ + static int s_aClassCourses[ TF_CLASS_COUNT ] = { + 0, // TF_CLASS_UNDEFINED + 0, // TF_CLASS_SCOUT + 0, // TF_CLASS_SNIPER + 2, // TF_CLASS_SOLDIER + 1, // TF_CLASS_DEMOMAN + 0, // TF_CLASS_MEDIC + 0, // TF_CLASS_HEAVYWEAPONS + 0, // TF_CLASS_PYRO + 1, // TF_CLASS_SPY + 1, // TF_CLASS_ENGINEER + }; + + AssertMsg( iClass >= 0 && iClass < TF_CLASS_COUNT, "Training_GetNumCoursesForClass(): Class out of range!" ); + return s_aClassCourses[ iClass ]; +} + +int Training_GetNumCourses() +{ + static bool s_bComputed = false; + static int s_nTotal = 0; + + if ( !s_bComputed ) + { + for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; ++i ) + { + s_nTotal += Training_GetNumCoursesForClass( i ); + } + s_bComputed = true; + } + + AssertMsg( s_nTotal == 5, "Number of total courses is incorrect - should be soldier (2) + demo (1) + spy (1) + engy (1)" ); + + return s_nTotal; +} + +int Training_GetProgressCount() +{ + int aProgress[ TF_CLASS_COUNT ]; + Training_GetProgress( aProgress ); + + int nTotalProgress = 0; + for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; ++i ) + { + int nClassProgress = Training_GetClassProgress( i ); + if ( nClassProgress > 0 ) + { + nTotalProgress += nClassProgress; + } + } + + return nTotalProgress; +} + +bool Training_IsComplete() +{ + return Training_GetProgressCount() == Training_GetNumCourses(); +} + +void Training_Init() +{ + // If the progress file already exists, early out as we only do conversation from the old system to the new here. + if ( Training_TrainingProgressFileExists() ) + return; + + int aProgress[ TF_CLASS_COUNT ]; + + int fProgressOld = cl_training_completed_with_classes.GetInt(); + for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; ++i ) + { + if ( ( fProgressOld & ( 1 << i ) ) != 0 ) + { + aProgress[ i ] = 1; + } + else + { + aProgress[ i ] = -1; + } + } + + const int TRAINING_CLASS_ATTACK_DEFEND = 15; + + // Add an explicit check for attack/defend + if ( ( fProgressOld & ( 1 << TRAINING_CLASS_ATTACK_DEFEND ) ) != 0 ) + { + aProgress[ TF_CLASS_SOLDIER ] = 2; + } + + // Soldier should always be at least 0 + aProgress[ TF_CLASS_SOLDIER ] = MAX( aProgress[ TF_CLASS_SOLDIER ], 0 ); + + // Save a file with the given progress settings + Training_SaveProgress( aProgress ); +} + +//------------------------------------------------------------------------------------------------------ + +Panel *FindAncestorByName( Panel *pChild, const char *pName ) +{ + if ( !pChild ) + return NULL; + + Panel *pCurrent = pChild->GetParent(); + while ( pCurrent ) + { + if ( FStrEq( pCurrent->GetName(), pName ) ) + return pCurrent; + + pCurrent = pCurrent->GetParent(); + } + + return NULL; +} + +CExButton *SetupButtonActionSignalTarget( Panel *pParent, const char *pButtonName, const char *pCommand = NULL ) +{ + CExButton *pButton = dynamic_cast< CExButton * >( pParent->FindChildByName( pButtonName ) ); + EditablePanel *pTrainingDialog = static_cast< EditablePanel * >( FindAncestorByName( pParent, TRAINING_DIALOG_NAME ) ); Assert( pTrainingDialog ); + + if ( pButton && pTrainingDialog ) + { + if ( pCommand ) + { + pButton->SetCommand( pCommand ); + } + + pButton->AddActionSignalTarget( pTrainingDialog ); + } + + return pButton; +} + +//------------------------------------------------------------------------------------------------------ + +// +// Sets dialog title/subtitle and sets up cancel/back buttons +// +class CTrainingBasePanel : public EditablePanel +{ + DECLARE_CLASS_SIMPLE( CTrainingBasePanel, EditablePanel ); +public: + CTrainingBasePanel( Panel *pParent, const char *pName ) + : EditablePanel( pParent, pName ), + m_pPrevPagePanel( NULL ) + { + } + + virtual void ApplySettings( KeyValues *pInResourceData ) + { + BaseClass::ApplySettings( pInResourceData ); + + m_strTitleToken = pInResourceData->GetString( "TrainingTitle", NULL ); + m_strSubTitleToken = pInResourceData->GetString( "TrainingSubTitle", NULL ); + } + + inline bool FindCharInWideString( const wchar_t *pStr, wchar_t c ) + { + if ( !pStr ) + return false; + + const int nLen = V_wcslen( pStr ); + for ( int i = 0; i < nLen; ++i ) + { + if ( pStr[ i ] == c ) + return true; + } + + return false; + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + EditablePanel *pTrainingDialog = static_cast< EditablePanel * >( FindAncestorByName( this, TRAINING_DIALOG_NAME ) ); Assert( pTrainingDialog ); + if ( pTrainingDialog ) + { + const wchar_t *pTitleString = g_pVGuiLocalize->Find( m_strTitleToken.Get() ); + if ( FindCharInWideString( pTitleString, L'%' ) ) + { + KeyValues *pTitleFormatData = GetTitleFormatData(); AssertMsg( pTitleFormatData, "Should get valid data here." ); + if ( pTitleFormatData ) + { + wchar_t wszTitle[ 1024 ]; + g_pVGuiLocalize->ConstructString_safe( wszTitle, m_strTitleToken.Get(), pTitleFormatData ); + pTitleFormatData->deleteThis(); + + pTrainingDialog->SetDialogVariable( "title", wszTitle ); + } + } + else + { + pTrainingDialog->SetDialogVariable( "title", g_pVGuiLocalize->Find( m_strTitleToken.Get() ) ); + } + + pTrainingDialog->SetDialogVariable( "subtitle", g_pVGuiLocalize->Find( m_strSubTitleToken ) ); + } + } + + virtual void OnCommand( const char *pCommand ) + { + if ( FStrEq( pCommand, "goprev" ) ) + { + GoPrev(); + } + else if ( FStrEq( pCommand, "gonext" ) ) + { + GoNext(); + } + else + { + BaseClass::OnCommand( pCommand ); + } + } + + virtual void OnKeyCodePressed( KeyCode nCode ) + { + ButtonCode_t nButtonCode = GetBaseButtonCode( nCode ); + + if ( nCode == KEY_SPACE || nCode == KEY_ENTER || nCode == KEY_XBUTTON_A || nCode == STEAMCONTROLLER_A ) + { + Go(); + } + else if ( nButtonCode == KEY_XBUTTON_LEFT || + nButtonCode == KEY_XSTICK1_LEFT || + nButtonCode == KEY_XSTICK2_LEFT || + nButtonCode == STEAMCONTROLLER_DPAD_LEFT || + nButtonCode == KEY_LEFT ) + { + GoPrev(); + } + else if ( nButtonCode == KEY_XBUTTON_RIGHT || + nButtonCode == KEY_XSTICK1_RIGHT || + nButtonCode == KEY_XSTICK2_RIGHT || + nButtonCode == STEAMCONTROLLER_DPAD_RIGHT || + nButtonCode == KEY_RIGHT ) + { + GoNext(); + } + else + { + BaseClass::OnKeyCodePressed( nCode ); + } + } + + void Go() + { + EditablePanel *pTrainingDialog = static_cast< EditablePanel * >( FindAncestorByName( this, TRAINING_DIALOG_NAME ) ); Assert( pTrainingDialog ); + if ( pTrainingDialog ) + { + const char *pGoCommand = GetGoCommand(); + if ( pGoCommand ) + { + pTrainingDialog->OnCommand( pGoCommand ); + } + } + } + + virtual void GoPrev() + { + } + + virtual void GoNext() + { + } + + virtual void OnBackPressed() + { + } + + virtual KeyValues *GetTitleFormatData() const + { + return NULL; + } + + virtual const char *GetGoCommand() const + { + return NULL; + } + + void SetPrevPage( CTrainingBasePanel *pPanel ) + { + m_pPrevPagePanel = pPanel; + } + + CTrainingBasePanel *GetPrevPage() + { + return m_pPrevPagePanel; + } + + virtual bool IsFirstPage() const + { + return false; + } + + virtual bool ShouldShowGradient() const + { + return false; + } + +protected: + CUtlString m_strTitleToken; + CUtlString m_strSubTitleToken; + CTrainingBasePanel *m_pPrevPagePanel; +}; + +//------------------------------------------------------------------------------------------------------ + +class CTrainingBaseCarouselPanel : public CTrainingBasePanel +{ + DECLARE_CLASS_SIMPLE( CTrainingBaseCarouselPanel, CTrainingBasePanel ); +public: + CTrainingBaseCarouselPanel( Panel *pParent, const char *pName ) + : CTrainingBasePanel( pParent, pName ), + m_iPage( 0 ) + { + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + CFmtStr fmtCurPageLabelText( "%i/%i", m_iPage + 1, GetNumPages() ); + SetDialogVariable( "curpage", fmtCurPageLabelText.Access() ); + + const int nNumPages = GetNumPages(); + + // Set visibility on buttons and current page based on the number of pages. + CExLabel *pCurPageLabel = dynamic_cast< CExLabel * >( FindChildByName( "CurPageLabel" ) ); + if ( pCurPageLabel ) + { + pCurPageLabel->SetVisible( nNumPages > 1 ); + } + + const char *pNavButtonNames[2] = { "PrevButton", "NextButton" }; + for ( int i = 0; i < 2; ++i ) + { + CExButton *pCurButton = dynamic_cast< CExButton * >( FindChildByName( pNavButtonNames[ i ] ) ); + if ( !pCurButton ) + continue; + pCurButton->SetVisible( nNumPages > 1 ); + } + } + + virtual void OnCommand( const char *pCommand ) + { + if ( FStrEq( pCommand, "goprev" ) ) + { + GoPrev(); + } + else if ( FStrEq( pCommand, "gonext" ) ) + { + GoNext(); + } + else + { + BaseClass::OnCommand( pCommand ); + } + } + + virtual void OnKeyCodePressed( KeyCode nCode ) + { + ButtonCode_t nButtonCode = GetBaseButtonCode( nCode ); + + if ( nButtonCode == KEY_XBUTTON_LEFT || + nButtonCode == KEY_XSTICK1_LEFT || + nButtonCode == KEY_XSTICK2_LEFT || + nButtonCode == KEY_LEFT ) + { + GoPrev(); + } + else if ( nButtonCode == KEY_XBUTTON_RIGHT || + nButtonCode == KEY_XSTICK1_RIGHT || + nButtonCode == KEY_XSTICK2_RIGHT || + nButtonCode == KEY_RIGHT ) + { + GoNext(); + } + else + { + BaseClass::OnKeyCodePressed( nCode ); + } + } + + void GoPrev() + { + --m_iPage; + + if ( m_iPage < 0 ) + { + m_iPage += GetNumPages(); + } + + InvalidateLayout( false, true ); + } + + void GoNext() + { + m_iPage = ( m_iPage + 1 ) % GetNumPages(); + + InvalidateLayout( false, true ); + } + + virtual int GetNumPages() const = 0; + +protected: + int m_iPage; +}; + +//------------------------------------------------------------------------------------------------------ + +class CModePanel : public EditablePanel +{ + DECLARE_CLASS_SIMPLE( CModePanel, EditablePanel ); +public: + CModePanel( Panel *pParent, const char *pName ) + : EditablePanel( pParent, pName ) + { + HScheme hScheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme" ); + SetScheme( hScheme ); + SetProportional( true ); + } + + ~CModePanel() + { + } + + virtual void ApplySettings( KeyValues *pInResourceData ) + { + BaseClass::ApplySettings( pInResourceData ); + + m_strModeNameToken = pInResourceData->GetString( "modename", NULL ); + m_strDescriptionToken = pInResourceData->GetString( "description", NULL ); + m_strImageToken = pInResourceData->GetString( "image", NULL ); + m_strStartCommand = pInResourceData->GetString( "startcommand", NULL ); + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "resource/ui/training/modeselection/modepanel.res" ); + + EditablePanel *pContainer = static_cast< EditablePanel * >( FindChildByName( "ModeInfoContainer" ) ); + if ( pContainer ) + { + pContainer->SetDialogVariable( "modename", g_pVGuiLocalize->Find( m_strModeNameToken.Get() ) ); + pContainer->SetDialogVariable( "description", g_pVGuiLocalize->Find( m_strDescriptionToken.Get() ) ); + + EditablePanel *pImageFrame = static_cast< EditablePanel * >( pContainer->FindChildByName( "ImageFrame" ) ); + if ( pImageFrame ) + { + ImagePanel *pImage = dynamic_cast< ImagePanel * >( pContainer->FindChildByName( "Image" ) ); + if ( pImage ) + { + pImage->SetImage( m_strImageToken ); + pImage->SetParent( pImageFrame ); + } + } + } + + SetupButtonActionSignalTarget( this, "StartButton", m_strStartCommand.Get() ); + } + + virtual void PerformLayout( void ) + { + BaseClass::PerformLayout(); + + GetParent()->NavigateTo(); + } + +private: + CUtlString m_strModeNameToken; + CUtlString m_strDescriptionToken; + CUtlString m_strImageToken; + CUtlString m_strStartCommand; +}; + +DECLARE_BUILD_FACTORY( CModePanel ); + +//------------------------------------------------------------------------------------------------------ + +class CModeSelectionPanel : public CTrainingBasePanel +{ + DECLARE_CLASS_SIMPLE( CModeSelectionPanel, CTrainingBasePanel ); +public: + CModeSelectionPanel( Panel *pParent, const char *pName ) + : CTrainingBasePanel( pParent, pName ) + { + SetProportional( true ); + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "resource/ui/training/modeselection/modeselection.res" ); + } + + virtual bool IsFirstPage() const + { + return true; + } + + virtual void PerformLayout() + { + BaseClass::PerformLayout(); + + Panel *pPanel = FindChildByName( "BasicTrainingPanel" ); + if ( pPanel ) + { + pPanel->SetNavToRelay( "StartButton" ); + pPanel->SetNavRight( "<<OfflinePracticePanel" ); + pPanel->InvalidateLayout(); + } + + pPanel = FindChildByName( "OfflinePracticePanel" ); + if ( pPanel ) + { + pPanel->SetNavToRelay( "StartButton" ); + pPanel->SetNavLeft( "<<BasicTrainingPanel" ); + pPanel->InvalidateLayout(); + } + + SetNavToRelay( "BasicTrainingPanel" ); + } +}; + +DECLARE_BUILD_FACTORY( CModeSelectionPanel ); + +//------------------------------------------------------------------------------------------------------ + +class CBasicTraining_ClassPanel : public EditablePanel +{ + DECLARE_CLASS_SIMPLE( CBasicTraining_ClassPanel, EditablePanel ); +public: + CBasicTraining_ClassPanel( Panel *pParent, const char *pName ) + : EditablePanel( pParent, pName ), + m_pImagePanel( NULL ), + m_pSelectButton( NULL ) + { + SetProportional( true ); + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "resource/ui/training/basictraining/classpanel.res" ); + + m_pImagePanel = dynamic_cast< ImagePanel * >( FindChildByName( "Image" ) ); Assert( m_pImagePanel ); + m_pSelectButton = SetupButtonActionSignalTarget( this, "SelectButton" ); Assert( m_pSelectButton ); + + if ( m_pSelectButton ) + { + m_pSelectButton->SetDefaultBorder( pScheme->GetBorder( m_pSelectButton->IsEnabled() ? "MainMenuButtonDefault" : "MainMenuButtonDisabled" ) ); + } + + m_pProgressLabel = dynamic_cast< CExLabel * >( FindChildByName( "ProgressLabel" ) ); + } + + virtual void PerformLayout() + { + BaseClass::PerformLayout(); + + if ( m_pImagePanel && m_pSelectButton ) + { + const int nMargin = XRES( 10 ); + const int nButtonStartY = YRES( 215 ); + + m_pImagePanel->SetBounds( nMargin, YRES( 20 ), GetWide() - 2 * nMargin, nButtonStartY - YRES( 40 ) ); + + int aButtonBounds[4] = { + nMargin, nButtonStartY, GetWide() - nMargin * 2, (int)YRES( 25 ) + }; + + m_pSelectButton->SetBounds( aButtonBounds[0], aButtonBounds[1], aButtonBounds[2], aButtonBounds[3] ); + + CExLabel *pProgressLabel = dynamic_cast< CExLabel * >( FindChildByName( "ProgressLabel" ) ); + if ( pProgressLabel ) + { + int aPos[2]; + pProgressLabel->GetPos( aPos[0], aPos[1] ); + pProgressLabel->SetPos( aButtonBounds[0], aPos[1] ); + pProgressLabel->SetWide( aButtonBounds[2] ); + } + } + + GetParent()->NavigateTo(); + } + + void SetClassData( int iClass, int nProgress, const char *pImageBase ) + { + const bool bLocked = nProgress < 0; + + if ( m_pImagePanel ) + { + CFmtStr fmtImagePath( "%s_%s", pImageBase, bLocked ? "off" : "on" ); + m_pImagePanel->SetImage( fmtImagePath.Access() ); + } + + if ( m_pSelectButton ) + { + m_pSelectButton->SetEnabled( !bLocked ); + } + + if ( m_pProgressLabel ) + { + const int nPercent = (int)( 100.0f * nProgress / Training_GetNumCoursesForClass( iClass ) ); + wchar_t wszLocalized[256]; + if ( nPercent > 0 ) + { + if ( nPercent < 100 ) + { + wchar_t wszNum[16] = L""; + V_snwprintf( wszNum, ARRAYSIZE( wszNum ), L"%i", nPercent ); + g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TR_Progress" ), 1, wszNum ); + } + else + { + V_wcsncpy( wszLocalized, g_pVGuiLocalize->Find( "#TR_ProgressDone" ), sizeof( wszLocalized ) ); + } + + m_pProgressLabel->SetText( wszLocalized ); + m_pProgressLabel->SetVisible( true ); + } + else + { + m_pProgressLabel->SetVisible( false ); + } + } + + InvalidateLayout( true, false ); + } + + void SetSelectCommand( const char *pCommand ) + { + if ( m_pSelectButton ) + { + m_pSelectButton->SetCommand( pCommand ); + } + } + +private: + ImagePanel *m_pImagePanel; + CExButton *m_pSelectButton; + CExLabel *m_pProgressLabel; +}; + +DECLARE_BUILD_FACTORY( CBasicTraining_ClassPanel ); + +//------------------------------------------------------------------------------------------------------ + +enum Consts_t +{ + NUM_CLASS_PANELS = 4, +}; + +const char *g_pClassPanelNames[ NUM_CLASS_PANELS ] = +{ + "SoldierPanel", + "DemoPanel", + "SpyPanel", + "EngineerPanel" +}; + +class CBasicTraining_ClassSelectionPanel : public CTrainingBasePanel +{ + DECLARE_CLASS_SIMPLE( CBasicTraining_ClassSelectionPanel, CTrainingBasePanel ); +public: + CBasicTraining_ClassSelectionPanel( Panel *pParent, const char *pName ) + : CTrainingBasePanel( pParent, pName ) + { + SetProportional( true ); + + for ( int i = 0; i < NUM_CLASS_PANELS; ++i ) + { + m_PanelInfos[ i ].m_pPanel = new CBasicTraining_ClassPanel( this, g_pClassPanelNames[ i ] ); + } + } + + virtual void ApplySettings( KeyValues *pInResourceData ) + { + BaseClass::ApplySettings( pInResourceData ); + + for ( int i = 0; i < NUM_CLASS_PANELS; ++i ) + { + CFmtStr fmtToken( "Class%iToken", i ); + m_PanelInfos[ i ].m_strSelectButtonToken = pInResourceData->GetString( fmtToken.Access(), NULL ); + + CFmtStr fmtImage( "Class%iImage", i ); + m_PanelInfos[ i ].m_strClassImage = pInResourceData->GetString( fmtImage.Access(), NULL ); + + CFmtStr fmtCommand( "Class%iCommand", i ); + m_PanelInfos[ i ].m_strCommand = pInResourceData->GetString( fmtCommand.Access(), NULL ); + } + + PRINT_KEY_VALUES( pInResourceData ); + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "resource/ui/training/basictraining/classselection.res" ); + } + + virtual void PerformLayout() + { + BaseClass::PerformLayout(); + + const int nWidth = GetWide(); + const int nClassPanelW = nWidth / NUM_CLASS_PANELS; + const int nClassPanelH = YRES( 260 ); + + const int aTrainingClasses[ NUM_CLASS_PANELS ] = { + TF_CLASS_SOLDIER, TF_CLASS_DEMOMAN, TF_CLASS_SPY, TF_CLASS_ENGINEER + }; + + + for ( int i = 0; i < NUM_CLASS_PANELS; ++i ) + { + CBasicTraining_ClassPanel *pCurClassPanel = m_PanelInfos[ i ].m_pPanel; + + pCurClassPanel->SetBounds( + i * nClassPanelW, + 0, + nClassPanelW, + nClassPanelH + ); + + pCurClassPanel->SetDialogVariable( "selectbuttontext", g_pVGuiLocalize->Find( m_PanelInfos[ i ].m_strSelectButtonToken.Get() ) ); + + const int nProgress = Training_GetClassProgress( aTrainingClasses[ i ] ); + pCurClassPanel->SetClassData( aTrainingClasses[ i ], nProgress, m_PanelInfos[ i ].m_strClassImage.Get() ); + + pCurClassPanel->SetSelectCommand( m_PanelInfos[ i ].m_strCommand.Get() ); + + pCurClassPanel->SetNavToRelay( "SelectButton" ); + + char szName[ 64 ]; + if ( i > 0 ) + { + Panel *pPrevPanel = m_PanelInfos[ i - 1 ].m_pPanel; + if ( pPrevPanel ) + { + V_snprintf( szName, sizeof( szName ), "<%s", pPrevPanel->GetName() ); + pCurClassPanel->SetNavLeft( szName ); + + V_snprintf( szName, sizeof( szName ), "<%s", pCurClassPanel->GetName() ); + pPrevPanel->SetNavRight( szName ); + } + } + + pCurClassPanel->InvalidateLayout(); + } + + SetNavToRelay( g_pClassPanelNames[ 0 ] ); + } + + virtual bool ShouldShowGradient() const + { + return true; + } + +private: + struct ClassPanelInfo_t + { + CBasicTraining_ClassPanel *m_pPanel; + CUtlString m_strSelectButtonToken; + CUtlString m_strClassImage; + CUtlString m_strCommand; + } + m_PanelInfos[ NUM_CLASS_PANELS ]; +}; + +DECLARE_BUILD_FACTORY( CBasicTraining_ClassSelectionPanel ); + +//------------------------------------------------------------------------------------------------------ + +class CBasicTraining_ClassDetailsPanel : public CTrainingBasePanel +{ + DECLARE_CLASS_SIMPLE( CBasicTraining_ClassDetailsPanel, CTrainingBasePanel ); +public: + CBasicTraining_ClassDetailsPanel( Panel *pParent, const char *pName ) + : CTrainingBasePanel( pParent, pName ), + m_iClass( TF_CLASS_UNDEFINED ), + m_pStartTrainingButton( NULL ) + { + SetProportional( true ); + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "resource/ui/training/basictraining/classdetails.res" ); + + EditablePanel *pOverlayPanel = dynamic_cast< EditablePanel * >( FindChildByName( "OverlayPanel" ) ); + if ( pOverlayPanel && m_iClass >= TF_FIRST_NORMAL_CLASS && m_iClass < TF_LAST_NORMAL_CLASS ) + { + pOverlayPanel->SetDialogVariable( "classname", g_pVGuiLocalize->Find( g_aPlayerClassNames[ m_iClass ] ) ); + + CFmtStr fmtDescToken( "TR_ClassInfo_%s", m_szClassName ); + pOverlayPanel->SetDialogVariable( "description", g_pVGuiLocalize->Find( fmtDescToken.Access() ) ); + + for ( int i = 0; i < 3; ++i ) + { + CFmtStr fmtWeaponImageName( "WeaponImage%i", i ); + ImagePanel *pCurImage = dynamic_cast< ImagePanel * >( pOverlayPanel->FindChildByName( fmtWeaponImageName.Access() ) ); + + if ( pCurImage ) + { + CFmtStr fmtWeaponImagePath; + GetWeaponPath( m_iClass, i, fmtWeaponImagePath ); + pCurImage->SetImage( fmtWeaponImagePath.Access() ); + } + } + } + + ImagePanel *pClassImage = dynamic_cast< ImagePanel * >( FindChildByName( "ClassImage" ) ); + if ( pClassImage ) + { + CFmtStr fmtImageName( "training/class_%s_on", m_szClassName ); + pClassImage->SetImage( fmtImageName.Access() ); + } + + ImagePanel *pClassIconImage = dynamic_cast< ImagePanel * >( FindChildByName( "ClassIconImage" ) ); + if ( pClassIconImage ) + { + CFmtStr fmtImageName( "training/class_icon_%s", m_szClassName ); + pClassIconImage->SetImage( fmtImageName.Access() ); + } + + m_pStartTrainingButton = SetupButtonActionSignalTarget( this, "StartTrainingButton" ); + } + + virtual void PerformLayout() + { + BaseClass::PerformLayout(); + + SetNavToRelay( "StartTrainingButton" ); + NavigateTo(); + } + + void GetWeaponPath( int iClass, int iWeapon, CFmtStr &fmtOut ) // iWeapon is in [0,2] + { + static const char *s_pWeaponNames[ TF_CLASS_COUNT ][ 3 ] = { + { NULL, NULL, NULL }, // TF_CLASS_UNDEFINED + { NULL, NULL, NULL }, // TF_CLASS_SCOUT + { NULL, NULL, NULL }, // TF_CLASS_SNIPER + { "rocketlauncher", "shotgun", "shovel" }, // TF_CLASS_SOLDIER + { "grenadelauncher", "stickybomb_launcher", "bottle" }, // TF_CLASS_DEMOMAN, + { NULL, NULL, NULL }, // TF_CLASS_MEDIC + { NULL, NULL, NULL }, // TF_CLASS_HEAVYWEAPONS + { NULL, NULL, NULL }, // TF_CLASS_PYRO + { "revolver", "c_spy_watch", "knife", }, // TF_CLASS_SPY, + { "shotgun", "pistol", "wrench" }, // TF_CLASS_ENGINEER, + }; + + Assert( iClass >= TF_FIRST_NORMAL_CLASS && iClass < TF_CLASS_COUNT ); + Assert( iWeapon >= 0 && iWeapon < 3 ); + + if ( iClass == TF_CLASS_SPY && iWeapon == 1 ) + { + fmtOut.sprintf( "../backpack/weapons/c_models/c_spy_watch/parts/c_spy_watch" ); + } + else + { + fmtOut.sprintf( "../backpack/weapons/w_models/w_%s", s_pWeaponNames[ iClass ][ iWeapon ] ); + } + } + + virtual bool ShouldShowGradient() const + { + return true; + } + + virtual const char *GetGoCommand() const + { + if ( !m_pStartTrainingButton ) + return NULL; + + KeyValues *pCommand = m_pStartTrainingButton->GetCommand(); + if ( !pCommand ) + return NULL; + + return pCommand->GetString( "command", NULL ); + } + + void SetClass( const char *pClassName ) + { + V_strcpy_safe( m_szClassName, pClassName ); + + // Setup class details panel + if ( FStrEq( pClassName, "soldier" ) ) + { + m_iClass = TF_CLASS_SOLDIER; + } + else if ( FStrEq( pClassName, "demoman" ) ) + { + m_iClass = TF_CLASS_DEMOMAN; + } + else if ( FStrEq( pClassName, "spy" ) ) + { + m_iClass = TF_CLASS_SPY; + } + else if ( FStrEq( pClassName, "engineer" ) ) + { + m_iClass = TF_CLASS_ENGINEER; + } + else + { + AssertMsg( 0, "Bad class name." ); + } + } + +private: + char m_szClassName[16]; + int m_iClass; + CExButton *m_pStartTrainingButton; +}; + +DECLARE_BUILD_FACTORY( CBasicTraining_ClassDetailsPanel ); + +//------------------------------------------------------------------------------------------------------ + +class COfflinePractice_ModeSelectionPanel : public CTrainingBaseCarouselPanel +{ + DECLARE_CLASS_SIMPLE( COfflinePractice_ModeSelectionPanel, CTrainingBaseCarouselPanel ); +public: + COfflinePractice_ModeSelectionPanel( Panel *pParent, const char *pName ) + : CTrainingBaseCarouselPanel( pParent, pName ), + m_pGameModeImagePanel( NULL ) + { + SetProportional( true ); + } + + virtual void ApplySettings( KeyValues *pInResourceData ) + { + BaseClass::ApplySettings( pInResourceData ); + + for ( int i = 0; i < NUM_PRACTICE_MODES; ++i ) + { + CFmtStr fmtModeToken( "Mode%iToken", i ); + m_ModeInfos[ i ].m_strModeToken = pInResourceData->GetString( fmtModeToken.Access(), NULL ); + + CFmtStr fmtDescToken( "Desc%iToken", i ); + m_ModeInfos[ i ].m_strDescToken = pInResourceData->GetString( fmtDescToken.Access(), NULL ); + + CFmtStr fmtImagePath( "Image%iPath", i ); + m_ModeInfos[ i ].m_strImage = pInResourceData->GetString( fmtImagePath.Access(), NULL ); + + CFmtStr fmtModeId( "Mode%iId", i ); + m_ModeInfos[ i ].m_nId = ( GameMode_t )pInResourceData->GetInt( fmtModeId.Access(), MODE_INVALID ); Assert( m_ModeInfos[ i ].m_nId != MODE_INVALID ); + } + + PRINT_KEY_VALUES( pInResourceData ); + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "resource/ui/training/offlinepractice/practicemodeselection.res" ); + + m_pGameModeImagePanel = dynamic_cast< ImagePanel * >( FindChildByName( "GameModeImagePanel" ) ); + if ( m_pGameModeImagePanel ) + { + Assert( m_iPage >= 0 && m_iPage < NUM_PRACTICE_MODES ); + m_pGameModeImagePanel->SetImage( m_ModeInfos[ m_iPage ].m_strImage.Get() ); + } + + SetupButtonActionSignalTarget( this, "SelectCurrentGameModeButton" ); + + SetDialogVariable( "description", g_pVGuiLocalize->Find( m_ModeInfos[ m_iPage ].m_strDescToken.Get() ) ); + SetDialogVariable( "gamemode", g_pVGuiLocalize->Find( m_ModeInfos[ m_iPage ].m_strModeToken.Get() ) ); + + CFmtStr fmtCurPageLabelText( "%i/%i", m_iPage + 1, NUM_PRACTICE_MODES ); + SetDialogVariable( "curpage", fmtCurPageLabelText.Access() ); + } + + virtual void PerformLayout() + { + BaseClass::PerformLayout(); + + if ( m_pGameModeImagePanel ) + { + // Use .res file ypos + int aPos[2]; + m_pGameModeImagePanel->GetPos( aPos[0], aPos[1] ); + + // Center + m_pGameModeImagePanel->SetPos( ( GetWide() - m_pGameModeImagePanel->GetWide() ) / 2, aPos[1] ); + } + + SetNavToRelay( "SelectCurrentGameModeButton" ); + NavigateTo(); + } + + virtual int GetNumPages() const + { + return NUM_PRACTICE_MODES; + } + + GameMode_t GetMode() const + { + return m_ModeInfos[ m_iPage ].m_nId; + } + +private: + enum Consts_t + { + NUM_PRACTICE_MODES = 3, + }; + + struct PracticeModeInfo_t + { + CUtlString m_strModeToken; + CUtlString m_strDescToken; + CUtlString m_strImage; + GameMode_t m_nId; + } + m_ModeInfos[ NUM_PRACTICE_MODES ]; + + ImagePanel *m_pGameModeImagePanel; +}; + +DECLARE_BUILD_FACTORY( COfflinePractice_ModeSelectionPanel ); + + +const char *g_pDifficultyModes[ 4 ] = { "Easy", "Normal", "Hard", "Expert" }; + +//------------------------------------------------------------------------------------------------------ + +class COfflinePractice_MapSelectionPanel : public CTrainingBaseCarouselPanel +{ + DECLARE_CLASS_SIMPLE( COfflinePractice_MapSelectionPanel, CTrainingBaseCarouselPanel ); + + struct MapInfo_t + { + CUtlString m_strDisplayName; + CUtlString m_strName; + int m_aPlayerRange[2]; + }; + +public: + COfflinePractice_MapSelectionPanel( Panel *pParent, const char *pName ) + : CTrainingBaseCarouselPanel( pParent, pName ), + m_pMapImagePanel( NULL ), + m_pDefaultsData( NULL ), + m_pDifficultyComboBox( NULL ), + m_pSavedData( NULL ), + m_iGameMode( MODE_INVALID ) + { + SetProportional( true ); + LoadMapData(); + } + + ~COfflinePractice_MapSelectionPanel() + { + for ( int i = 0; i < NUM_GAME_MODES; ++i ) + { + m_vecMapData[i].PurgeAndDeleteElements(); + } + + if ( m_pDefaultsData ) + { + m_pDefaultsData->deleteThis(); + } + } + + void SetGameMode( int iGameMode ) + { + m_iGameMode = iGameMode; + m_iPage = 0; + InvalidateLayout( false, true ); + } + + const MapInfo_t *GetSelectedMapInfo() const + { + return m_iGameMode < 0 ? NULL : m_vecMapData[ m_iGameMode ][ m_iPage ]; + } + + int GetMaxPlayers() const + { + return GetSelectedMapInfo()->m_aPlayerRange[1]; + } + + const char *GetMapName() const + { + return GetSelectedMapInfo()->m_strName.Get(); + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + LoadControlSettings( "resource/ui/training/offlinepractice/mapselection.res" ); + + BaseClass::ApplySchemeSettings( pScheme ); + + const MapInfo_t *pCurMapInfo = GetSelectedMapInfo(); + if ( !pCurMapInfo ) + return; + + m_pMapImagePanel = dynamic_cast< ImagePanel * >( FindChildByName( "MapImagePanel" ) ); + if ( m_pMapImagePanel ) + { + Assert( m_iPage >= 0 && m_iPage < GetMapCount() ); + + CFmtStr fmtMapImageBasePath( "training/screenshots/%s.vmt", pCurMapInfo->m_strName.Get() ); + m_pMapImagePanel->SetImage( fmtMapImageBasePath.Access() ); + } + + // Send the 'select' button's command to the actual dialog + SetupButtonActionSignalTarget( this, "SelectCurrentMapButton" ); + + // update recommended number of players + CExLabel *pSuggestedPlayerCountLabel = dynamic_cast< CExLabel * >( FindChildByName( "SuggestedPlayerCountLabel" ) ); + if ( pSuggestedPlayerCountLabel ) + { + wchar_t wszLocalized[256]; + wchar_t wszNum1[16]=L""; + wchar_t wszNum2[16]=L""; + V_snwprintf( wszNum1, ARRAYSIZE( wszNum1 ), L"%i", pCurMapInfo->m_aPlayerRange[0] ); + V_snwprintf( wszNum2, ARRAYSIZE( wszNum2 ), L"%i", pCurMapInfo->m_aPlayerRange[1] ); + g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_OfflinePractice_NumPlayers" ), 2, wszNum1, wszNum2 ); + pSuggestedPlayerCountLabel->SetText( wszLocalized ); + } + + m_pDifficultyComboBox = dynamic_cast< ComboBox * >( FindChildByName( "DifficultyComboBox" ) ); + if ( m_pDifficultyComboBox ) + { + for ( int i = 0; i < ARRAYSIZE( g_pDifficultyModes ); ++i ) + { + m_pDifficultyComboBox->AddItem( g_pDifficultyModes[i], NULL ); + } + } + + TextEntry *pNumPlayersTextEntry = dynamic_cast< TextEntry * >( FindChildByName( "NumPlayersTextEntry" ) ); + if ( pNumPlayersTextEntry ) + { + pNumPlayersTextEntry->SetBorder( pScheme->GetBorder( "ComboBoxBorder" ) ); + } + + SetupButtonActionSignalTarget( this, "StartOfflinePracticeButton" ); + + SetDialogVariable( "mapname", pCurMapInfo->m_strDisplayName.Get() ); + + UpdateControlsFromSavedData( m_pDifficultyComboBox, pNumPlayersTextEntry ); + } + + virtual void PerformLayout() + { + BaseClass::PerformLayout(); + + if ( m_pMapImagePanel ) + { + // Use .res file ypos + int aPos[2]; + m_pMapImagePanel->GetPos( aPos[0], aPos[1] ); + + // Center + m_pMapImagePanel->SetPos( ( GetWide() - m_pMapImagePanel->GetWide() ) / 2, aPos[1] ); + } + + SetNavToRelay( "StartOfflinePracticeButton" ); + NavigateTo(); + } + + virtual void OnKeyCodePressed( KeyCode nCode ) + { + ButtonCode_t nButtonCode = GetBaseButtonCode( nCode ); + + if ( nButtonCode == KEY_XBUTTON_X ) + { + if ( m_pDifficultyComboBox ) + { + m_pDifficultyComboBox->SilentActivateItemByRow( ( m_pDifficultyComboBox->GetActiveItem() + 1 ) % ARRAYSIZE( g_pDifficultyModes ) ); + } + } + else if ( nButtonCode == KEY_XBUTTON_UP || + nButtonCode == KEY_XSTICK1_UP || + nButtonCode == KEY_XSTICK2_UP || + nButtonCode == KEY_UP ) + { + SetControlInt( "NumPlayersTextEntry", clamp( GetControlInt( "NumPlayersTextEntry", 0 ) + 1, 1, 31 ) ); + } + else if ( nButtonCode == KEY_XBUTTON_DOWN || + nButtonCode == KEY_XSTICK1_DOWN || + nButtonCode == KEY_XSTICK2_DOWN || + nButtonCode == KEY_RIGHT ) + { + SetControlInt( "NumPlayersTextEntry", clamp( GetControlInt( "NumPlayersTextEntry", 0 ) - 1, 1, 31 ) ); + } + else + { + BaseClass::OnKeyCodePressed( nCode ); + } + } + + virtual int GetNumPages() const + { + return GetMapCount(); + } + + int GetMapCount() const + { + return m_vecMapData[ m_iGameMode ].Count(); + } + + void GetControlValues( int *pOutNumPlayers, int *pOutDiff, CUtlString *pOutMap = NULL ) + { + const MapInfo_t *pSelectedMapInfo = GetSelectedMapInfo(); + if ( !pSelectedMapInfo ) + return; + + *pOutNumPlayers = clamp( GetControlInt( "NumPlayersTextEntry", 0 ), 1, 31 ); + + *pOutDiff = clamp( GetBotDifficulty(), 0, 3 ); + + if ( pOutMap ) + { + *pOutMap = pSelectedMapInfo->m_strName; + } + } + + bool DoSetup() + { + // @note Tom Bui: if you add any other convars that get set, please revert them + // in CTFBotManager::RevertOfflinePracticeConvars() + + const MapInfo_t *pSelectedMapInfo = GetSelectedMapInfo(); + if ( !pSelectedMapInfo ) + return false; + + int nQuota = 1; + int iDifficulty = 0; + GetControlValues( &nQuota, &iDifficulty ); + + // the player count in the dialog includes the human player, so decrease the bot count by one + ConVarRef tf_bot_quota( "tf_bot_quota" ); + tf_bot_quota.SetValue( nQuota - 1 ); + + ConVarRef tf_bot_quota_mode( "tf_bot_quota_mode" ); + tf_bot_quota_mode.SetValue( "normal" ); + + ConVarRef tf_bot_auto_vacate( "tf_bot_auto_vacate" ); + tf_bot_auto_vacate.SetValue( 0 ); + + ConVarRef tf_bot_difficulty( "tf_bot_difficulty" ); + tf_bot_difficulty.SetValue( iDifficulty ); + + ConVarRef tf_bot_offline_practice( "tf_bot_offline_practice" ); + tf_bot_offline_practice.SetValue( 1 ); + + tf_training_client_message.SetValue( TRAINING_CLIENT_MESSAGE_WATCHING_INTRO_MOVIE ); + + SaveSettings(); + + return true; + } + +private: + virtual KeyValues *GetTitleFormatData() const + { + KeyValues *pResult = new KeyValues( "data" ); + if ( pResult ) + { + const char *pGameModeToken = ( m_iGameMode >= 0 && m_iGameMode < NUM_GAME_MODES ) ? gs_pGameModeTokens[ m_iGameMode ] : ""; + pResult->SetWString( "gametype", g_pVGuiLocalize->Find( pGameModeToken ) ); + } + return pResult; + } + + virtual void OnBackPressed() + { + SaveSettings(); + } + + void SaveSettings() + { + // Save settings + if ( m_pSavedData ) + { + int nNumPlayers = 1; + int iDifficulty = 0; + CUtlString strMap; + GetControlValues( &nNumPlayers, &iDifficulty, &strMap ); + + m_pSavedData->SetInt( "tf_bot_quota", nNumPlayers ); + m_pSavedData->SetInt( "tf_bot_difficulty", iDifficulty ); + m_pSavedData->SetString( "map", strMap.Get() ); + } + + if ( !m_pSavedData->SaveToFile( g_pFullFileSystem, "OfflinePracticeConfig.vdf", "MOD" ) ) + { + Warning( "Failed to write save data to OfflinePracticeConfig.vdf!\n" ); + } + } + + int GetBotDifficulty() const + { + if ( m_pDifficultyComboBox ) + { + return m_pDifficultyComboBox->GetActiveItem(); + } + + AssertMsg( 0, "Shouldn't get here." ); + return 0; + } + + void UpdateControlsFromSavedData( ComboBox *pDifficultyComboBox, TextEntry *pNumPlayersTextEntry ) + { + if ( !pDifficultyComboBox ) + return; + + if ( !pNumPlayersTextEntry ) + return; + + int iDifficulty = -1; + int nQuota = 0; + const char *defaultMap = ""; + + if ( m_pSavedData ) + { + m_pSavedData->deleteThis(); + m_pSavedData = NULL; + } + + m_pSavedData = new KeyValues( "OfflinePracticeConfig" ); + + // load the config data + if ( m_pSavedData ) + { + // this is game-specific data, so it should live in GAME, not CONFIG + if ( m_pSavedData->LoadFromFile( g_pFullFileSystem, "OfflinePracticeConfig.vdf", "MOD" ) ) + { + iDifficulty = m_pSavedData->GetInt( "tf_bot_difficulty", -1 ); + nQuota = m_pSavedData->GetInt( "tf_bot_quota", 0 ); + defaultMap = m_pSavedData->GetString( "map", "" ); + } + } + + if ( m_pDefaultsData ) + { + const int nMaxPlayers = m_pDefaultsData->GetInt( "max_players" ); + + if ( FStrEq( defaultMap, "" ) ) + { + defaultMap = m_pDefaultsData->GetString( "map", "" ); + } + + if ( nQuota == 0 ) + { + nQuota = m_pDefaultsData->GetInt( "suggested_players", nMaxPlayers ); + } + + if ( iDifficulty == -1 ) + { + const char *pDifficultyString = m_pDefaultsData->GetString( "difficulty" ); + if ( pDifficultyString ) + { + static const char* difficulties [] = { "easy", "normal", "hard", "expert" }; + for ( int i = 0, n = ARRAYSIZE(difficulties); i < n; ++i ) + { + if ( Q_strcmp( difficulties[i], pDifficultyString ) == 0) + { + iDifficulty = i; + break; + } + } + } + } + } + + // Set values in controls + m_pDifficultyComboBox->SilentActivateItemByRow( iDifficulty ); + + CFmtStr fmtNumPlayers( "%i", nQuota ); + pNumPlayersTextEntry->SetText( fmtNumPlayers.Access() ); + } + + void LoadMapData() + { + m_pOfflinePracticeData = new KeyValues( "offline_practice.res" ); + const char *pFilename = "resource/offline_practice.res"; + if ( !m_pOfflinePracticeData->LoadFromFile( g_pFullFileSystem, pFilename, "MOD" ) ) + { + Warning( "Could not load %s!\n", pFilename ); + return; + } + + // Save defaults + KeyValues *pDefaultsData = m_pOfflinePracticeData->FindKey( "defaults" ); + if ( pDefaultsData ) + { + m_pDefaultsData = pDefaultsData->MakeCopy(); + } + + KeyValues *pMapData = m_pOfflinePracticeData->FindKey( "maps" ); + if ( pMapData ) + { + FOR_EACH_TRUE_SUBKEY( pMapData, pCurMap ) + { + MapInfo_t *pMapInfo = new MapInfo_t; + + pMapInfo->m_strName = pCurMap->GetName(); + pMapInfo->m_strDisplayName = pCurMap->GetString( "name" ); + pMapInfo->m_aPlayerRange[0] = pCurMap->GetInt( "min_players" ); + pMapInfo->m_aPlayerRange[1] = pCurMap->GetInt( "max_players" ); + + // Figure out which bucket to add to + const GameMode_t iGameMode = GetGameModeFromMapName( pMapInfo->m_strName.Get() ); + if ( iGameMode != MODE_INVALID ) + { + AddMapInfo( pMapInfo, iGameMode ); + } + } + } + + pMapData->deleteThis(); + } + + GameMode_t GetGameModeFromMapName( const char *pMapName ) + { + if ( !V_strnicmp( pMapName, "cp", 2 ) ) + { + return MODE_CP; + } + else if ( !V_strnicmp( pMapName, "koth", 4 ) ) + { + return MODE_KOTH; + } + else if ( !V_strnicmp( pMapName, "pl", 2 ) ) + { + return MODE_PL; + } + + AssertMsg( 0, "Should never get here!" ); + + return MODE_INVALID; + } + + void AddMapInfo( MapInfo_t *pMapInfo, GameMode_t iGameMode ) + { + m_vecMapData[ iGameMode ].AddToTail( pMapInfo ); + } + + int m_iGameMode; + KeyValues *m_pSavedData; + KeyValues *m_pDefaultsData; + ImagePanel *m_pMapImagePanel; + ComboBox *m_pDifficultyComboBox; + KeyValues *m_pOfflinePracticeData; + CUtlVector< MapInfo_t * > m_vecMapData[ NUM_GAME_MODES ]; +}; + +DECLARE_BUILD_FACTORY( COfflinePractice_MapSelectionPanel ); + +//------------------------------------------------------------------------------------------------------ + +class CTrainingDialog : public EditablePanel +{ + DECLARE_CLASS_SIMPLE( CTrainingDialog, EditablePanel ); +public: + CTrainingDialog( Panel *parent ) + : EditablePanel( parent, TRAINING_DIALOG_NAME ), + m_pBackButton( NULL ), + m_pCancelButton( NULL ), + m_pGradientBgPanel( NULL ), + m_pModeSelectionPanel( NULL ), + m_pCurrentPagePanel( NULL ), + m_pBasicTraining_ClassSelectionPanel( NULL ), + m_pBasicTraining_ClassDetailsPanel( NULL ), + m_pOfflinePractice_ModeSelectionPanel( NULL ), + m_pOfflinePractice_MapSelectionPanel( NULL ), + m_pTrainingData( NULL ), + m_bStartTraining( false ), + m_bContinue( false ) + { + HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme" ); + SetScheme(scheme); + SetProportional( true ); + m_pContainer = new EditablePanel( this, "Container" ); + + // load configuration + const char *filename = "resource/training.res"; + m_pTrainingData = new KeyValues( "training.res" ); + Assert( m_pTrainingData ); + if ( !m_pTrainingData->LoadFromFile( g_pFullFileSystem, filename, "MOD" ) ) + { + Warning( "Unable to load '%s'\n", filename ); + AssertMsg( 0, "Couldn't load training data!" ); + } + + C_CTFGameStats::ImmediateWriteInterfaceEvent( "interface_open", "training" ); + } + + virtual ~CTrainingDialog() + { + C_CTFGameStats::ImmediateWriteInterfaceEvent( "interface_close", "training" ); + + ivgui()->RemoveTickSignal( GetVPanel() ); + } + + virtual void SetDialogVariable( const char *pVarName, const char *pValue ) + { + m_pContainer->SetDialogVariable( pVarName, pValue ); + } + + virtual void SetDialogVariable( const char *pVarName, const wchar_t *pValue ) + { + m_pContainer->SetDialogVariable( pVarName, pValue ); + } + + virtual void SetDialogVariable( const char *pVarName, int nValue ) + { + m_pContainer->SetDialogVariable( pVarName, nValue ); + } + + virtual void SetDialogVariable( const char *pVarName, float flValue ) + { + m_pContainer->SetDialogVariable( pVarName, flValue ); + } + + void SetupButton( const char *pPanelName, CExButton **ppOut = NULL ) + { + Panel *pPanel = m_pContainer->FindChildByName( pPanelName ); + if ( pPanel ) + { + pPanel->AddActionSignalTarget( this ); + } + + if ( ppOut ) + { + *ppOut = static_cast< CExButton * >( pPanel ); + } + } + + virtual void Show() + { + SetVisible( true ); + MakePopup(); + MoveToFront(); + SetKeyBoardInputEnabled( true ); + SetMouseInputEnabled( true ); + TFModalStack()->PushModal( this ); + } + + virtual void OnThink() + { + BaseClass::OnThink(); + } + + virtual void OnCommand( const char *pCommand ) + { + C_CTFGameStats::ImmediateWriteInterfaceEvent( "on_command(training)", pCommand ); + + if ( FStrEq( pCommand, "prevpage" ) ) + { + ShowPrevPage(); + } + else if ( FStrEq( pCommand, "cancel" ) ) + { + Close(); + } + else if ( FStrEq( pCommand, "basictrainingselected" ) ) + { + BasicTraining_ShowClassSelection(); + } + else if ( FStrEq( pCommand, "offlinepracticeselected" ) ) + { + OfflinePractice_ShowPracticeMode(); + } + else if ( FStrEq( pCommand, "startbasictraining" ) ) + { + BasicTraining_Start(); + } + else if ( !V_strnicmp( pCommand, "basictraining_classselection_", 29 ) ) + { + BasicTraining_ShowClassDetailsPage( pCommand + 29 ); + } + else if ( FStrEq( pCommand, "selectcurrentgamemode" ) ) + { + OfflinePractice_ShowMapSelection(); + } + else if ( FStrEq( pCommand, "startofflinepractice" ) ) + { + OfflinePractice_Start(); + } + else + { + BaseClass::OnCommand( pCommand ); + } + } + + void SetCurrentPage( CTrainingBasePanel *pPanel, bool bGoingBack = false ) + { + AssertMsg( pPanel, "Setting current page to NULL!" ); + + pPanel->SetVisible( true ); + + if ( !bGoingBack ) + { + pPanel->SetPrevPage( m_pCurrentPagePanel ); + } + + m_pCurrentPagePanel = pPanel; + m_pCurrentPagePanel->InvalidateLayout( false, true ); + + InvalidateLayout( true, false ); + } + + void ShowPrevPage() + { + CTrainingBasePanel *pPrevPagePanel = m_pCurrentPagePanel->GetPrevPage(); + if ( pPrevPagePanel ) + { + if ( m_pCurrentPagePanel == pPrevPagePanel ) + return; + + m_pCurrentPagePanel->SetVisible( false ); + m_pCurrentPagePanel->OnBackPressed(); + SetCurrentPage( pPrevPagePanel, true ); + } + else + { + OnCommand( "cancel" ); + } + } + + void HideCurrentPage() + { + m_pCurrentPagePanel->SetVisible( false ); + } + + void BasicTraining_ShowClassSelection() + { + if ( m_pCurrentPagePanel == m_pBasicTraining_ClassSelectionPanel ) + return; + + HideCurrentPage(); + SetCurrentPage( m_pBasicTraining_ClassSelectionPanel ); + } + + int GetClassFromData( KeyValues *pClassData ) + { + const int iClass = pClassData->GetInt( "class", TF_CLASS_SOLDIER ); + if ( iClass < TF_FIRST_NORMAL_CLASS || iClass >= TF_LAST_NORMAL_CLASS ) + { + return TF_CLASS_SOLDIER; + } + return iClass; + } + + static void ConfirmDialogCallback( bool bConfirmed, void *pContext ) + { + CTrainingDialog *pDialog = ( CTrainingDialog * )pContext; + if ( pDialog ) + { + pDialog->m_bContinue = bConfirmed; + pDialog->m_bStartTraining = true; + } + } + + void ConfirmContinue() + { + ShowConfirmDialog( "#TR_ContinueTitle", "#TR_ContinueMsg", "#TR_Continue", "#TR_StartOver", &ConfirmDialogCallback, NULL, this ); + } + + void BasicTraining_Start() + { + if ( !m_pTrainingData ) + return; + + KeyValues *pData = m_pTrainingData->FindKey( m_strBasicTrainingClassName.Get() ); + if ( !pData ) + return; + + // Override for soldier - if target practice is complete, start from + const int iClass = GetClassFromData( pData ); + int nProgress = Training_GetClassProgress( iClass ); + if ( iClass == TF_CLASS_SOLDIER && nProgress >= 1 ) + { + ConfirmContinue(); + return; + } + + m_bStartTraining = true; + m_bContinue = false; + } + + virtual void Think() + { + if ( !m_bStartTraining ) + return; + + KeyValues *pData = m_pTrainingData->FindKey( m_strBasicTrainingClassName.Get() ); + if ( !pData ) + return; + + // Override map if user has selected to continue + const char *pMapName = pData->GetString( "map", NULL ); + if ( m_bContinue ) + { + pMapName = "tr_dustbowl"; + } + + if ( pMapName ) + { + const int iClass = GetClassFromData( pData ); + + ConVarRef training_class( "training_class" ); + training_class.SetValue( iClass ); + + const char* pMapVideo = pData->GetString( "video", "" ); + training_map_video.SetValue( pMapVideo ); + + // create the command to execute + CFmtStr fmtMapCommand( "disconnect\nwait\nwait\n\nprogress_enable\nmap %s\n", pMapName ); + + // exec + engine->ClientCmd_Unrestricted( fmtMapCommand.Access() ); + } + + Close(); + } + + void BasicTraining_ShowClassDetailsPage( const char *pClassName ) + { + if ( m_pCurrentPagePanel == m_pBasicTraining_ClassDetailsPanel ) + return; + + HideCurrentPage(); + + m_pBasicTraining_ClassDetailsPanel->SetClass( pClassName ); + m_pBasicTraining_ClassDetailsPanel->InvalidateLayout( true, true ); + + SetCurrentPage( m_pBasicTraining_ClassDetailsPanel ); + + // Cache class + m_strBasicTrainingClassName = pClassName; + } + + void OfflinePractice_ShowPracticeMode() + { + if ( m_pCurrentPagePanel == m_pOfflinePractice_ModeSelectionPanel ) + return; + + HideCurrentPage(); + SetCurrentPage( m_pOfflinePractice_ModeSelectionPanel ); + } + + void OfflinePractice_ShowMapSelection() + { + if ( m_pCurrentPagePanel == m_pOfflinePractice_MapSelectionPanel ) + return; + + // Pass on the game mode to the map selection panel so it will only show corresponding maps + const GameMode_t nGameMode = m_pOfflinePractice_ModeSelectionPanel->GetMode(); + m_pOfflinePractice_MapSelectionPanel->SetGameMode( nGameMode ); + + HideCurrentPage(); + SetCurrentPage( m_pOfflinePractice_MapSelectionPanel ); + } + + void OfflinePractice_Start() + { + // reset server enforced cvars + g_pCVar->RevertFlaggedConVars( FCVAR_REPLICATED ); + + // Cheats were disabled; revert all cheat cvars to their default values. + // This must be done heading into multiplayer games because people can play + // demos etc and set cheat cvars with sv_cheats 0. + g_pCVar->RevertFlaggedConVars( FCVAR_CHEAT ); + + DevMsg( "FCVAR_CHEAT cvars reverted to defaults.\n" ); + + if ( m_pOfflinePractice_MapSelectionPanel->DoSetup() ) + { + // create the command to execute + CFmtStr1024 fmtMapCommand( + "disconnect\nwait\nwait\nmaxplayers %i\n\nprogress_enable\nmap %s\n", + m_pOfflinePractice_MapSelectionPanel->GetMaxPlayers(), + m_pOfflinePractice_MapSelectionPanel->GetMapName() + ); + + // exec + engine->ClientCmd_Unrestricted( fmtMapCommand.Access() ); + } + + Close(); + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "Resource/ui/training/main.res" ); + + SetupButton( "CancelButton", &m_pCancelButton ); + SetupButton( "BackButton", &m_pBackButton ); + + m_pModeSelectionPanel = dynamic_cast< CModeSelectionPanel * >( m_pContainer->FindChildByName( "ModeSelectionPanel" ) ); Assert( m_pModeSelectionPanel ); + m_pBasicTraining_ClassSelectionPanel = dynamic_cast< CBasicTraining_ClassSelectionPanel * >( m_pContainer->FindChildByName( "BasicTraining_ClassSelectionPanel" ) ); Assert( m_pBasicTraining_ClassSelectionPanel ); + m_pBasicTraining_ClassDetailsPanel = dynamic_cast< CBasicTraining_ClassDetailsPanel * >( m_pContainer->FindChildByName( "BasicTraining_ClassDetailsPanel" ) ); Assert( m_pBasicTraining_ClassDetailsPanel ); + m_pOfflinePractice_ModeSelectionPanel = dynamic_cast< COfflinePractice_ModeSelectionPanel * >( m_pContainer->FindChildByName( "OfflinePractice_ModeSelectionPanel" ) ); Assert( m_pOfflinePractice_ModeSelectionPanel ); + m_pOfflinePractice_MapSelectionPanel = dynamic_cast< COfflinePractice_MapSelectionPanel * >( m_pContainer->FindChildByName( "OfflinePractice_MapSelectionPanel" ) ); Assert( m_pOfflinePractice_MapSelectionPanel ); + m_pGradientBgPanel = dynamic_cast< ImagePanel * >( m_pContainer->FindChildByName( "GradientBgPanel" ) ); + + m_pCurrentPagePanel = m_pModeSelectionPanel; + } + + virtual void PerformLayout() + { + BaseClass::PerformLayout(); + + if ( m_pCurrentPagePanel ) + { + m_pCurrentPagePanel->SetVisible( true ); + + const bool bFirstPage = m_pCurrentPagePanel->IsFirstPage(); + if ( m_pBackButton && m_pCancelButton ) + { + m_pBackButton->SetVisible( !bFirstPage ); + + int w = m_pContainer->GetWide(); + const int nBuffer = XRES( 5 ); + + int cbx, cby; + m_pCancelButton->GetPos( cbx, cby ); + + if ( bFirstPage ) + { + m_pCancelButton->SetPos( ( w - m_pCancelButton->GetWide() ) / 2, cby ); + } + else + { + m_pBackButton->SetPos( w/2 - m_pBackButton->GetWide() - nBuffer, cby ); + m_pCancelButton->SetPos( w/2 + nBuffer, cby ); + } + + if ( m_pGradientBgPanel ) + { + m_pGradientBgPanel->SetVisible( m_pCurrentPagePanel->ShouldShowGradient() ); + } + } + } + } + + virtual void OnKeyCodePressed( KeyCode code ) + { + ButtonCode_t nButtonCode = GetBaseButtonCode( code ); + if ( code == KEY_ESCAPE ) + { + OnCommand( "cancel" ); + } + else if ( nButtonCode == KEY_XBUTTON_B || nButtonCode == STEAMCONTROLLER_B ) + { + OnCommand( "prevpage" ); + } + else if ( code == KEY_ENTER || code == KEY_SPACE || nButtonCode == KEY_XBUTTON_A || nButtonCode == STEAMCONTROLLER_A ) + { + if ( m_pCurrentPagePanel ) + { + m_pCurrentPagePanel->Go(); + } + } + else + { + BaseClass::OnKeyCodePressed( code ); + } + } + +protected: + void Close() + { + SetVisible( false ); + TFModalStack()->PopModal( this ); + MarkForDeletion(); + } + +private: + EditablePanel *m_pContainer; + CModeSelectionPanel *m_pModeSelectionPanel; + CBasicTraining_ClassSelectionPanel *m_pBasicTraining_ClassSelectionPanel; + CBasicTraining_ClassDetailsPanel *m_pBasicTraining_ClassDetailsPanel; + COfflinePractice_ModeSelectionPanel *m_pOfflinePractice_ModeSelectionPanel; + COfflinePractice_MapSelectionPanel *m_pOfflinePractice_MapSelectionPanel; + CTrainingBasePanel *m_pCurrentPagePanel; + CTrainingBasePanel *m_pPrevPagePanel; + CExButton *m_pCancelButton; + CExButton *m_pBackButton; + ImagePanel *m_pGradientBgPanel; + KeyValues *m_pTrainingData; + CUtlString m_strBasicTrainingClassName; + bool m_bStartTraining; + bool m_bContinue; +}; + +static DHANDLE<CTrainingDialog> g_pTrainingDialog; + +//------------------------------------------------------------------------------------------------------ + +void CL_ShowTrainingDialog( const CCommand &args ) +{ + if ( g_pTrainingDialog.Get() == NULL ) + { + IViewPortPanel *pMMOverride = ( gViewPortInterface->FindPanelByName( PANEL_MAINMENUOVERRIDE ) ); + g_pTrainingDialog = new CTrainingDialog( (CHudMainMenuOverride*)pMMOverride ); + g_pTrainingDialog->InvalidateLayout( true, true ); + } + g_pTrainingDialog->Show(); +} + +//------------------------------------------------------------------------------------------------------ + +CON_COMMAND( cl_training_class_unlock_all, "Unlock all training" ) +{ + for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; ++i ) + { + Training_MarkClassComplete( i, 100 ); + } +} + +//------------------------------------------------------------------------------------------------------ + +#ifdef _DEBUG +CON_COMMAND( training_set, 0 ) +{ + if ( args.ArgC() != 3 ) + { + Warning( "Not enough arguments\n" ); + return; + } + + Training_MarkClassComplete( atoi( args[1] ), atoi( args[2] ) ); +} +#endif + +//------------------------------------------------------------------------------------------------------ + +static ConCommand training_showdlg( "training_showdlg", &CL_ShowTrainingDialog, "Displays the training dialog." );
\ No newline at end of file diff --git a/game/client/tf/vgui/tf_vgui_video.cpp b/game/client/tf/vgui/tf_vgui_video.cpp new file mode 100644 index 0000000..35945f8 --- /dev/null +++ b/game/client/tf/vgui/tf_vgui_video.cpp @@ -0,0 +1,112 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: VGUI panel which can play back video, in-engine +// +//============================================================================= + +#include "cbase.h" +#include <vgui/IVGui.h> +#include <vgui/ISurface.h> +#include <KeyValues.h> +#include "vgui_video.h" +#include "tf_vgui_video.h" +#include "engine/IEngineSound.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +DECLARE_BUILD_FACTORY( CTFVideoPanel ); + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CTFVideoPanel::CTFVideoPanel( vgui::Panel *parent, const char *panelName ) : VideoPanel( 0, 0, 50, 50 ) +{ + SetParent( parent ); + SetProportional( true ); + SetKeyBoardInputEnabled( false ); + + SetBlackBackground( false ); + + m_flStartAnimDelay = 0.0f; + m_flEndAnimDelay = 0.0f; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CTFVideoPanel::~CTFVideoPanel() +{ + ReleaseVideo(); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CTFVideoPanel::ReleaseVideo() +{ + enginesound->NotifyEndMoviePlayback(); + + // Destroy any previously allocated video + if ( g_pVideo && m_VideoMaterial != NULL ) + { + g_pVideo->DestroyVideoMaterial( m_VideoMaterial ); + m_VideoMaterial = NULL; + } +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CTFVideoPanel::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + SetExitCommand( inResourceData->GetString( "command", "" ) ); + m_flStartAnimDelay = inResourceData->GetFloat( "start_delay", 0.0 ); + m_flEndAnimDelay = inResourceData->GetFloat( "end_delay", 0.0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFVideoPanel::GetPanelPos( int &xpos, int &ypos ) +{ + vgui::ipanel()->GetAbsPos( GetVPanel(), xpos, ypos ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFVideoPanel::OnVideoOver() +{ + BaseClass::OnVideoOver(); + PostMessage( GetParent(), new KeyValues( "IntroFinished" ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFVideoPanel::OnClose() +{ + // Fire an exit command if we're asked to do so + if ( m_szExitCommand[0] ) + { + engine->ClientCmd( m_szExitCommand ); + } + + // intentionally skipping VideoPanel::OnClose() + EditablePanel::OnClose(); + + SetVisible( false ); +} +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFVideoPanel::Shutdown() +{ + OnClose(); + ReleaseVideo(); +}
\ No newline at end of file diff --git a/game/client/tf/vgui/tf_vgui_video.h b/game/client/tf/vgui/tf_vgui_video.h new file mode 100644 index 0000000..63b93e0 --- /dev/null +++ b/game/client/tf/vgui/tf_vgui_video.h @@ -0,0 +1,42 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: VGUI panel which can play back video, in-engine +// +//============================================================================= + +#ifndef TF_VGUI_VIDEO_H +#define TF_VGUI_VIDEO_H +#ifdef _WIN32 +#pragma once +#endif + +#include "vgui_video.h" + +class CTFVideoPanel : public VideoPanel +{ + DECLARE_CLASS_SIMPLE( CTFVideoPanel, VideoPanel ); +public: + + CTFVideoPanel( vgui::Panel *parent, const char *panelName ); + ~CTFVideoPanel(); + + virtual void OnClose(); + virtual void OnKeyCodePressed( vgui::KeyCode code ){} + virtual void ApplySettings( KeyValues *inResourceData ); + + virtual void GetPanelPos( int &xpos, int &ypos ); + virtual void Shutdown(); + + float GetStartDelay(){ return m_flStartAnimDelay; } + float GetEndDelay(){ return m_flEndAnimDelay; } + +protected: + virtual void ReleaseVideo(); + virtual void OnVideoOver(); + +private: + float m_flStartAnimDelay; + float m_flEndAnimDelay; +}; + +#endif // TF_VGUI_VIDEO_H diff --git a/game/client/tf/vgui/tf_viewport.cpp b/game/client/tf/vgui/tf_viewport.cpp new file mode 100644 index 0000000..bf8e66a --- /dev/null +++ b/game/client/tf/vgui/tf_viewport.cpp @@ -0,0 +1,482 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Client DLL VGUI2 Viewport +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#pragma warning( disable : 4800 ) // disable forcing int to bool performance warning + +// VGUI panel includes +#include <vgui_controls/Panel.h> +#include <vgui/ISurface.h> +#include <KeyValues.h> +#include <vgui/Cursor.h> +#include <vgui/IScheme.h> +#include <vgui/IVGui.h> +#include <vgui/ILocalize.h> +#include <vgui/VGUI.h> +#include "tier0/icommandline.h" + +// client dll/engine defines +#include "hud.h" +#include <voice_status.h> + +// viewport definitions +#include <baseviewport.h> +#include "tf_viewport.h" +#include "tf_teammenu.h" + +#include "vguicenterprint.h" +#include "text_message.h" +#include "tf_classmenu.h" + +#include "tf_textwindow.h" +#include "tf_clientscoreboard.h" +#include "tf_spectatorgui.h" +#include "intromenu.h" +#include "tf_intromenu.h" + +#include "tf_controls.h" +#include "tf_mapinfomenu.h" +#include "tf_roundinfo.h" + +#include "item_pickup_panel.h" +#include "character_info_panel.h" +#include "tf_hud_arena_winpanel.h" +#include "tf_arenateammenu.h" +#include "tf_hud_pve_winpanel.h" +#include "hud_chat.h" +#include "tf_giveawayitempanel.h" +#if defined( REPLAY_ENABLED ) +#include "replay/vgui/replaybrowsermainpanel.h" +#endif +#include "clientmode_tf.h" +#include "ienginevgui.h" +#include "tf_hud_mainmenuoverride.h" +#include "c_tf_objective_resource.h" + +#include "quest_log_panel.h" + +//#include "tf_overview.h" + +/* +CON_COMMAND( spec_help, "Show spectator help screen") +{ + if ( gViewPortInterface ) + gViewPortInterface->ShowPanel( PANEL_INFO, true ); +} + +CON_COMMAND( spec_menu, "Activates spectator menu") +{ + bool bShowIt = true; + + C_CSPlayer *pPlayer = C_CSPlayer::GetLocalCSPlayer(); + + if ( pPlayer && !pPlayer->IsObserver() ) + return; + + if ( args.ArgC() == 2 ) + { + bShowIt = atoi( args[ 1 ] ) == 1; + } + + if ( gViewPortInterface ) + gViewPortInterface->ShowPanel( PANEL_SPECMENU, bShowIt ); +} +*/ + + +CON_COMMAND( showmapinfo, "Show map info panel" ) +{ + if ( !gViewPortInterface ) + return; + + C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer(); + + // don't let the player open the team menu themselves until they're a spectator or they're on a regular team and have picked a class + if ( pPlayer ) + { + if ( ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR ) || + ( ( pPlayer->GetTeamNumber() != TEAM_UNASSIGNED ) && ( pPlayer->GetPlayerClass()->GetClassIndex() != TF_CLASS_UNDEFINED ) ) ) + { + // close all the other panels that could be open + gViewPortInterface->ShowPanel( PANEL_TEAM, false ); + gViewPortInterface->ShowPanel( PANEL_ARENA_TEAM, false ); + gViewPortInterface->ShowPanel( PANEL_ARENA_WIN, false ); + gViewPortInterface->ShowPanel( PANEL_PVE_WIN, false ); + gViewPortInterface->ShowPanel( PANEL_CLASS_RED, false ); + gViewPortInterface->ShowPanel( PANEL_CLASS_BLUE, false ); + gViewPortInterface->ShowPanel( PANEL_INTRO, false ); + gViewPortInterface->ShowPanel( PANEL_ROUNDINFO, false ); + gViewPortInterface->ShowPanel( PANEL_MAPINFO, true ); + gViewPortInterface->ShowPanel( PANEL_GIVEAWAY_ITEM, false ); + } + } +} + +CON_COMMAND( changeteam, "Choose a new team" ) +{ + if ( !gViewPortInterface ) + return; + + C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer(); + + // don't let the player open the team menu themselves until they're on a team + if ( pPlayer && pPlayer->CanShowTeamMenu() ) + { + if ( TFGameRules()->IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true ) + { + gViewPortInterface->ShowPanel( PANEL_ARENA_TEAM, true ); + } + else + { + gViewPortInterface->ShowPanel( PANEL_TEAM, true ); + } + } +} + +CON_COMMAND( changeclass, "Choose a new class" ) +{ + if ( !gViewPortInterface ) + return; + + C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer(); + + if ( pPlayer && pPlayer->CanShowClassMenu() ) + { + if ( TFGameRules() && TFGameRules()->IsInArenaMode() && pPlayer->IsAlive() == true ) + { + if ( pPlayer->GetTeamNumber() > LAST_SHARED_TEAM && ( tf_arena_force_class.GetBool() == true || TFGameRules()->InStalemate() == true ) ) + { + CBaseHudChat *pHUDChat = (CBaseHudChat *)GET_HUDELEMENT( CHudChat ); + + if ( pHUDChat ) + { + char szLocalized[100]; + g_pVGuiLocalize->ConvertUnicodeToANSI( g_pVGuiLocalize->Find( "#TF_Arena_NoClassChange" ), szLocalized, sizeof(szLocalized) ); + + pHUDChat->ChatPrintf( pPlayer->entindex(), CHAT_FILTER_NONE, "%s ", szLocalized ); + } + + return; + } + } + + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) + { + if ( !TFGameRules()->InSetup() ) + { + CBaseHudChat *pHUDChat = (CBaseHudChat *)GET_HUDELEMENT( CHudChat ); + + if ( pHUDChat ) + { + char szLocalized[100]; + g_pVGuiLocalize->ConvertUnicodeToANSI( g_pVGuiLocalize->Find( "#TF_MVM_NoClassChangeAfterSetup" ), szLocalized, sizeof(szLocalized) ); + + pHUDChat->ChatPrintf( pPlayer->entindex(), CHAT_FILTER_NONE, "%s ", szLocalized ); + } + + return; + } + } + + switch( pPlayer->GetTeamNumber() ) + { + case TF_TEAM_RED: + gViewPortInterface->ShowPanel( PANEL_CLASS_RED, true ); + break; + case TF_TEAM_BLUE: + gViewPortInterface->ShowPanel( PANEL_CLASS_BLUE, true ); + break; + case TEAM_SPECTATOR: + { + if( TFGameRules() && TFGameRules()->IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true ) + { + gViewPortInterface->ShowPanel( PANEL_CLASS_BLUE, true ); + } + } + break; + default: + break; + } + } +} + +CON_COMMAND( togglescores, "Toggles score panel") +{ + if ( !gViewPortInterface ) + return; + + IViewPortPanel *scoreboard = gViewPortInterface->FindPanelByName( PANEL_SCOREBOARD ); + + if ( !scoreboard ) + return; + + if ( scoreboard->IsVisible() ) + { + gViewPortInterface->ShowPanel( scoreboard, false ); + GetClientVoiceMgr()->StopSquelchMode(); + } + else + { + gViewPortInterface->ShowPanel( scoreboard, true ); + } +} + +CON_COMMAND( show_quest_log, "Show the quest log panel" ) +{ + if ( !gViewPortInterface ) + return; + + IViewPortPanel *pQuestLog = gViewPortInterface->FindPanelByName( PANEL_QUEST_LOG ); + + if ( !pQuestLog ) + return; + + if ( pQuestLog->IsVisible() ) + { + gViewPortInterface->ShowPanel( pQuestLog, false ); + } + else + { + gViewPortInterface->ShowPanel( pQuestLog, true ); + } +} + +TFViewport::TFViewport() +{ + ivgui()->AddTickSignal( GetVPanel(), 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +TFViewport::~TFViewport() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: called when the VGUI subsystem starts up +// Creates the sub panels and initialises them +//----------------------------------------------------------------------------- +void TFViewport::Start( IGameUIFuncs *pGameUIFuncs, IGameEventManager2 * pGameEventManager ) +{ + BaseClass::Start( pGameUIFuncs, pGameEventManager ); +} + +void TFViewport::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + gHUD.InitColors( pScheme ); + + SetPaintBackgroundEnabled( false ); + + // Precache some font characters for the 360 + if ( IsX360() || CommandLine()->CheckParm( "-precachefontchars" ) || CommandLine()->CheckParm( "-precachefontintlchars" ) ) + { + const wchar_t *pAllChars = L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,.!:"; + const wchar_t *pNumbers = L"0123456789"; + // Try some tricky characters and some international characters with accents, etc. + static const wchar_t IntlChars[] = { 'a', 'b', 'c', 'i', 'o', 'y', 'A', 'B', 'C', 0xbf, 0xc1, 0xd1, 0xd3, 0x00 }; + + if ( CommandLine()->CheckParm( "-precachefontintlchars" ) ) + pAllChars = IntlChars; + + vgui::surface()->PrecacheFontCharacters( pScheme->GetFont( "ScoreboardTeamName" ), pAllChars ); + vgui::surface()->PrecacheFontCharacters( pScheme->GetFont( "ScoreboardMedium" ), pAllChars ); + vgui::surface()->PrecacheFontCharacters( pScheme->GetFont( "ScoreboardSmall" ), pAllChars ); + vgui::surface()->PrecacheFontCharacters( pScheme->GetFont( "ScoreboardVerySmall" ), pAllChars ); + vgui::surface()->PrecacheFontCharacters( pScheme->GetFont( "TFFontMedium" ), pAllChars ); + vgui::surface()->PrecacheFontCharacters( pScheme->GetFont( "TFFontSmall" ), pAllChars ); + vgui::surface()->PrecacheFontCharacters( pScheme->GetFont( "HudFontMedium" ), pAllChars ); + vgui::surface()->PrecacheFontCharacters( pScheme->GetFont( "HudFontMediumSmallSecondary" ), pAllChars ); + vgui::surface()->PrecacheFontCharacters( pScheme->GetFont( "HudFontSmall" ), pAllChars ); + vgui::surface()->PrecacheFontCharacters( pScheme->GetFont( "DefaultSmall" ), pAllChars ); + + vgui::surface()->PrecacheFontCharacters( pScheme->GetFont( "ScoreboardTeamScore" ), pNumbers ); + } +} + +// +// This is the main function of the viewport. Right here is where we create our class menu, +// team menu, and anything else that we want to turn on and off in the UI. +// +IViewPortPanel* TFViewport::CreatePanelByName(const char *szPanelName) +{ + IViewPortPanel* newpanel = NULL; + + // overwrite MOD specific panel creation + + if ( Q_strcmp( PANEL_SCOREBOARD, szPanelName ) == 0 ) + { + newpanel = new CTFClientScoreBoardDialog( this ); + } + else if ( Q_strcmp( PANEL_SPECGUI, szPanelName ) == 0 ) + { + newpanel = new CTFSpectatorGUI( this ); + } + else if ( Q_strcmp( PANEL_SPECMENU, szPanelName ) == 0 ) + { +// newpanel = new CTFSpectatorGUI( this ); + } + else if ( Q_strcmp( PANEL_OVERVIEW, szPanelName ) == 0 ) + { +// newpanel = new CTFMapOverview( this ); + } + else if ( Q_strcmp( PANEL_INFO, szPanelName ) == 0 ) + { + newpanel = new CTFTextWindow( this ); + } + else if ( Q_strcmp( PANEL_MAPINFO, szPanelName ) == 0 ) + { + newpanel = new CTFMapInfoMenu( this ); + } + else if ( Q_strcmp( PANEL_ROUNDINFO, szPanelName ) == 0 ) + { + newpanel = new CTFRoundInfo( this ); + } + else if ( Q_strcmp( PANEL_TEAM, szPanelName ) == 0 ) + { + newpanel = new CTFTeamMenu( this ); + } + else if ( Q_strcmp( PANEL_CLASS_RED, szPanelName ) == 0 ) + { + newpanel = new CTFClassMenu_Red( this ); + } + else if ( Q_strcmp( PANEL_CLASS_BLUE, szPanelName ) == 0 ) + { + newpanel = new CTFClassMenu_Blue( this ); + } + else if ( Q_strcmp( PANEL_INTRO, szPanelName ) == 0 ) + { + newpanel = new CTFIntroMenu( this ); + } + else if ( Q_strcmp( PANEL_ARENA_TEAM, szPanelName ) == 0 ) + { + newpanel = new CTFArenaTeamMenu( this ); + } + else if ( Q_strcmp( PANEL_ARENA_WIN, szPanelName ) == 0 ) + { + newpanel = new CTFArenaWinPanel( this ); + } + else if ( Q_strcmp( PANEL_PVE_WIN, szPanelName ) == 0 ) + { + newpanel = new CTFPVEWinPanel( this ); + } + else if ( Q_strcmp( PANEL_GIVEAWAY_ITEM, szPanelName ) == 0 ) + { + newpanel = new CTFGiveawayItemPanel( this ); + } + else if ( Q_strcmp( PANEL_MAINMENUOVERRIDE, szPanelName ) == 0 ) + { + newpanel = new CHudMainMenuOverride( this ); + } + else if ( V_strcmp( PANEL_QUEST_LOG, szPanelName ) == 0 ) + { + newpanel = new CQuestLogPanel( this ); + } + else + { + // create a generic base panel, don't add twice + newpanel = BaseClass::CreatePanelByName( szPanelName ); + } + + return newpanel; +} + +void TFViewport::CreateDefaultPanels( void ) +{ + AddNewPanel( CreatePanelByName( PANEL_MAPINFO ), "PANEL_MAPINFO" ); + AddNewPanel( CreatePanelByName( PANEL_TEAM ), "PANEL_TEAM" ); + AddNewPanel( CreatePanelByName( PANEL_CLASS_RED ), "PANEL_CLASS_RED" ); + AddNewPanel( CreatePanelByName( PANEL_CLASS_BLUE ), "PANEL_CLASS_BLUE" ); + AddNewPanel( CreatePanelByName( PANEL_INTRO ), "PANEL_INTRO" ); + AddNewPanel( CreatePanelByName( PANEL_ROUNDINFO ), "PANEL_ROUNDINFO" ); + AddNewPanel( CreatePanelByName( PANEL_ARENA_WIN ), "PANEL_ARENA_WIN" ); + AddNewPanel( CreatePanelByName( PANEL_ARENA_TEAM ), "PANEL_ARENA_TEAM" ); + AddNewPanel( CreatePanelByName( PANEL_PVE_WIN ), "PANEL_PVE_WIN" ); + AddNewPanel( CreatePanelByName( PANEL_GIVEAWAY_ITEM ), "PANEL_GIVEAWAY_ITEM" ); + AddNewPanel( CreatePanelByName( PANEL_QUEST_LOG ), "PANEL_QUEST_LOG" ); + + CHudMainMenuOverride *pMMOverride = (CHudMainMenuOverride*)CreatePanelByName( PANEL_MAINMENUOVERRIDE ); + if ( pMMOverride ) + { + AddNewPanel( pMMOverride, "PANEL_MAINMENUOVERRIDE" ); + pMMOverride->AttachToGameUI(); + } + + BaseClass::CreateDefaultPanels(); +} + +int TFViewport::GetDeathMessageStartHeight( void ) +{ + int y = YRES(2); + + if ( IsX360() ) + { + y = YRES(36); + } + + if ( g_pSpectatorGUI && g_pSpectatorGUI->IsVisible() ) + { + y = YRES(2) + g_pSpectatorGUI->GetTopBarHeight(); + } + + return y; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TFViewport::OnScreenSizeChanged( int iOldWide, int iOldTall ) +{ + BaseClass::OnScreenSizeChanged( iOldWide, iOldTall ); + + // we've changed resolution, let's try to figure out if we need to show any of our menus + if ( !gViewPortInterface ) + return; + + C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer(); + + if ( pPlayer ) + { + // are we on a team yet? + if ( pPlayer->GetTeamNumber() == TEAM_UNASSIGNED ) + { + engine->ClientCmd( "show_motd" ); + } + else if ( ( pPlayer->GetTeamNumber() != TEAM_SPECTATOR ) && ( pPlayer->m_Shared.GetDesiredPlayerClassIndex() == TF_CLASS_UNDEFINED ) ) + { + if ( tf_arena_force_class.GetBool() == false ) + { + switch( pPlayer->GetTeamNumber() ) + { + case TF_TEAM_RED: + gViewPortInterface->ShowPanel( PANEL_CLASS_RED, true ); + break; + case TF_TEAM_BLUE: + gViewPortInterface->ShowPanel( PANEL_CLASS_BLUE, true ); + break; + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TFViewport::OnTick() +{ + m_pAnimController->UpdateAnimations( gpGlobals->curtime ); +}
\ No newline at end of file diff --git a/game/client/tf/vgui/tf_viewport.h b/game/client/tf/vgui/tf_viewport.h new file mode 100644 index 0000000..f851cbe --- /dev/null +++ b/game/client/tf/vgui/tf_viewport.h @@ -0,0 +1,54 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TF_VIEWPORT_H +#define TF_VIEWPORT_H + + +#include "tf_shareddefs.h" +#include "baseviewport.h" + + +using namespace vgui; + +namespace vgui +{ + class Panel; + class Label; + class CBitmapImagePanel; +} + +//============================================================================== +class TFViewport : public CBaseViewport +{ + +private: + DECLARE_CLASS_SIMPLE( TFViewport, CBaseViewport ); + +public: + TFViewport(); + ~TFViewport(); + + IViewPortPanel* CreatePanelByName(const char *szPanelName); + void CreateDefaultPanels( void ); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void Start( IGameUIFuncs *pGameUIFuncs, IGameEventManager2 * pGameEventManager ); + + int GetDeathMessageStartHeight( void ); + + virtual void OnScreenSizeChanged( int iOldWide, int iOldTall ); + + virtual void OnTick() OVERRIDE; + +private: + void CenterWindow( vgui::Frame *win ); + +}; + + +#endif // TF_Viewport_H diff --git a/game/client/tf/vgui/tf_warinfopanel.cpp b/game/client/tf/vgui/tf_warinfopanel.cpp new file mode 100644 index 0000000..2458bff --- /dev/null +++ b/game/client/tf/vgui/tf_warinfopanel.cpp @@ -0,0 +1,532 @@ +#include "cbase.h" +#include "tf_warinfopanel.h" +#include "econ_controls.h" +#include "econ_item_inventory.h" +#include "vgui_avatarimage.h" +#include "vgui_controls/ProgressBar.h" +#include <vgui_controls/HTML.h> +#include "c_tf_player.h" +#include "econ_ui.h" +#include "tf_asyncpanel.h" +#include "item_model_panel.h" +#include "tf_wardata.h" +#include "iclientmode.h" +#include <vgui_controls/AnimationController.h> +#include "vgui/ISystem.h" + +#ifdef STAGING_ONLY +ConVar tf_war_override_active_state( "tf_war_override_active_state", "-1" ); +#endif + +bool IsWarActive( war_definition_index_t nDefIndex ) +{ +#ifdef STAGING_ONLY + if ( tf_war_override_active_state.GetInt() != -1 ) + { + if ( tf_war_override_active_state.GetInt() == 0 ) + return false; + + return true; + } +#endif + + const CWarDefinition* pWarDef = GetItemSchema()->GetWarDefinitionByIndex( nDefIndex ); + Assert( pWarDef ); + if ( !pWarDef ) + return false; + + return pWarDef->IsActive(); +} + +template < typename T > +void SetNestedDialogVariable( Panel *pRoot, const char *pszPanel, const char *pszVariable, T val ) +{ + EditablePanel *pPanel = pRoot->FindControl<EditablePanel>( pszPanel, true ); + if ( pPanel ) + { + pPanel->SetDialogVariable( pszVariable, val ); + } +} + +DECLARE_BUILD_FACTORY( CWarStandingPanel ); +CWarStandingPanel::CWarStandingPanel( Panel* pParent, const char* pszPanelName ) + : BaseClass( pParent, pszPanelName ) + , m_flLastUpdateTime( 0.f ) + , m_pTeam0ProgressBar( NULL ) + , m_pTeam1ProgressBar( NULL ) + , m_bNeedsLerp( false ) +{ + ListenForGameEvent( "global_war_data_updated" ); +} + +void CWarStandingPanel::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + LoadControlSettings( "Resource/UI/econ/WarStandingPanel.res" ); + + for( int i=0; i<2; ++i ) + { + m_Scores[i].m_pTeamProgressBar = FindControl< ContinuousProgressBar >( CFmtStr( "Team%dProgressBar", i ), true ); + m_Scores[i].m_pContainerPanel = FindControl< EditablePanel >( CFmtStr( "Team%dContainer", i ), true ); + m_Scores[i].m_pScoreLabel = FindControl< CExLabel >( CFmtStr( "Team%dScore", i ), true ); + m_Scores[i].m_pTeamLabel = FindControl< CExLabel >( CFmtStr( "Team%dName", i ), true ); + } +} + +void CWarStandingPanel::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + m_strWarName = inResourceData->GetString( "war_name", NULL ); +} + +void CWarStandingPanel::OnThink() +{ + BaseClass::OnThink(); + + if ( Plat_FloatTime() - m_flLastUpdateTime > 30.f ) + { + InvalidateLayout(); + } + + if ( m_bNeedsLerp ) + { + int nTotal = 0; + if ( m_Scores[ 0 ].m_nNewScore != 0 || m_Scores[ 1 ].m_nNewScore != 0 ) + { + nTotal = m_Scores[ 0 ].m_nNewScore + m_Scores[ 1 ].m_nNewScore; + } + + float flPercent = GetPercentAnimated(); + + for( int i=0; i<2; ++i ) + { + // Lerp flPercent from [0,1] -> [StartScore,EndScore] + float flCurrentScore = RemapValClamped( flPercent, 0.f, 1.f, (float)m_Scores[i].m_nLastScore, (float)m_Scores[i].m_nNewScore ); + + float flProgressPercent = 0.f; + if ( nTotal != 0 ) + { + flProgressPercent = flCurrentScore / (float)nTotal; + } + + m_Scores[i].m_pTeamProgressBar->SetProgress( flProgressPercent ); + m_Scores[i].m_pContainerPanel->SetDialogVariable( CFmtStr( "team%dscore", i ), CFmtStr( "%.0f%%", flProgressPercent * 100.f ) ); + + int nScoreXpos = RemapValClamped( flProgressPercent + , 0.f + , 1.f + , m_Scores[i].m_pTeamProgressBar->GetXPos() + , m_Scores[i].m_pTeamProgressBar->GetXPos() + m_Scores[i].m_pTeamProgressBar->GetWide() ); + + m_Scores[i].m_pScoreLabel->SetPos( nScoreXpos, m_Scores[i].m_pScoreLabel->GetYPos() ); + } + + if ( flPercent == 1.f ) + { + m_bNeedsLerp = false; + } + } +} + +void CWarStandingPanel::PerformLayout() +{ + BaseClass::PerformLayout(); + + if ( GetPercentAnimated() == 1.f ) + { + m_flLastUpdateTime = Plat_FloatTime(); + } + + const CWarDefinition* pWarDef = GetItemSchema()->GetWarDefinitionByName( m_strWarName ); + Assert( pWarDef->GetSides().Count() == 2 ); // Needs to be 2 sides to the war for this panel to work! + CWarData *pWarData = GetLocalPlayerWarData( pWarDef->GetDefIndex() ); + war_side_t nAffiliation = INVALID_WAR_SIDE; + if ( pWarData ) + { + nAffiliation = pWarData->Obj().affiliation(); + } + + FOR_EACH_MAP_FAST( pWarDef->GetSides(), i ) + { + uint64 nScore = nScore = GetWarData().GetGlobalSideScore( pWarDef->GetDefIndex(), pWarDef->GetSide( i )->m_nSideIndex ); + + m_Scores[ i ].m_nLastScore = m_Scores[ i ].m_nNewScore; + m_Scores[ i ].m_nNewScore = nScore; + + if ( m_Scores[ i ].m_pTeamLabel ) + { + m_Scores[ i ].m_pTeamLabel->SetText( pWarDef->GetSide( i )->m_pszLocalizedName ); + } + + // Check if there's a change to show + m_bNeedsLerp |= m_Scores[ i ].m_nLastScore != m_Scores[ i ].m_nNewScore; + + if ( !m_bNeedsLerp ) + { + m_Scores[i].m_pContainerPanel->SetDialogVariable( CFmtStr( "team%dscore", i ), CFmtStr( "%.0f%%", m_Scores[i].m_pTeamProgressBar->GetProgress() * 100.f ) ); + } + + // Set the "(Your side)" labels + SetControlVisible( CFmtStr( "Team%dYourSide", i ), i == nAffiliation, true ); + } +} + +float CWarStandingPanel::GetPercentAnimated() const +{ + float flTimeSinceLastUpdate = Plat_FloatTime() - m_flLastUpdateTime; + float flPercent = Bias( RemapValClamped( flTimeSinceLastUpdate, 0.f, 1.2f, 0.f, 1.f ), 0.8f ); + return flPercent; +} + +void CWarStandingPanel::FireGameEvent( IGameEvent *event ) +{ + if ( FStrEq( event->GetName(), "global_war_data_updated" ) ) + { + InvalidateLayout(); + } +} + + +DECLARE_BUILD_FACTORY( CWarLandingPanel ); + +CWarLandingPanel::CWarLandingPanel( Panel *pParent, const char *pszPanelName ) + : BaseClass( pParent, pszPanelName ) + , m_flChoseTeamTime( 0.f ) + , m_nLastKnownSide( INVALID_WAR_SIDE ) + , m_nPendingSide( INVALID_WAR_SIDE ) + , m_eJoiningState( NO_ACTION ) +{ +} + +void CWarLandingPanel::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + KeyValues* pKVConditions = new KeyValues( "conditions" ); + + if ( IsWarActive( PYRO_VS_HEAVY_WAR_DEF_INDEX ) ) + { + pKVConditions->AddSubKey( new KeyValues( "if_war_active" ) ); + } + else + { + pKVConditions->AddSubKey( new KeyValues( "if_war_over" ) ); + } + + LoadControlSettings( "Resource/UI/econ/WarJoinPanel.res", NULL, NULL, pKVConditions ); +} + +void CWarLandingPanel::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + m_strSceneAnimName = inResourceData->GetString( "scene_anim_name", NULL ); + Assert( m_strSceneAnimName.Length() ); +} + +void CWarLandingPanel::OnCommand( const char *pCommand ) +{ + if ( FStrEq( pCommand, "close" ) ) + { + if ( m_eJoiningState == NO_ACTION ) + { + SetVisible( false ); + } + } + else if ( V_strncasecmp( pCommand, "join_war", V_strlen( "join_war" ) ) == 0 ) + { + int nSide = V_atoi( &pCommand[ V_strlen( "join_war" ) ] ); + + const CWarDefinition* pWarDef = GetItemSchema()->GetWarDefinitionByIndex( PYRO_VS_HEAVY_WAR_DEF_INDEX ); + + if ( !IsWarActive( PYRO_VS_HEAVY_WAR_DEF_INDEX ) ) + { + DevMsg( "War is inactive" ); + return; + } + + if ( !pWarDef->IsValidSide( nSide ) ) + { + DevMsg( "%d is not a valid side", nSide ); + return; + } + + m_nPendingSide = nSide; + Assert( m_eJoiningState == NO_ACTION ); + m_eJoiningState = CONFIRM_SIDE_SELECTION; + + UpdateUIState(); + return; + } + else if ( FStrEq( pCommand, "confirm_team" ) ) + { + if ( !IsWarActive( PYRO_VS_HEAVY_WAR_DEF_INDEX ) ) + { + DevMsg( "War is inactive" ); + return; + } + + // Join the war! + GCSDK::CProtoBufMsg< CGCMsgGC_War_JoinWar > msg( k_EMsgGC_War_JoinWar ); + + msg.Body().set_affiliation( m_nPendingSide ); + msg.Body().set_war_id( PYRO_VS_HEAVY_WAR_DEF_INDEX ); + + GCClientSystem()->BSendMessage( msg ); + m_flChoseTeamTime = Plat_FloatTime(); + m_eJoiningState = ATTEMPTING_TO_JOIN_AND_WAITING_FOR_RESPONSE; + + UpdateUIState(); + return; + } + else if ( FStrEq( pCommand, "dismiss_joining_result" ) ) + { + m_eJoiningState = NO_ACTION; + m_nPendingSide = INVALID_WAR_SIDE; + UpdateUIState(); + + return; + } + else if ( FStrEq( "view_update_comic", pCommand ) ) + { + const char* pszComicURL = "http://www.teamfortress.com/theshowdown/"; + + if ( steamapicontext && steamapicontext->SteamFriends() && steamapicontext->SteamUtils() && steamapicontext->SteamUtils()->IsOverlayEnabled() ) + { + steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( pszComicURL ); + } + else + { + vgui::system()->ShellExecute( "open", pszComicURL ); + } + + return; + } + + BaseClass::OnCommand( pCommand ); +} + +void CWarLandingPanel::OnThink() +{ + BaseClass::OnThink(); + + if ( ( Plat_FloatTime() - m_flChoseTeamTime ) > 5.f && m_eJoiningState == ATTEMPTING_TO_JOIN_AND_WAITING_FOR_RESPONSE ) + { + m_eJoiningState = FAILED_RESPONSE_RECIEVED_WAITING_FOR_USER_CONFIRMATION; + UpdateUIState(); + } +} + +#ifdef STAGING_ONLY +// 12345? Yea, well, we probably will never have 12,345 sides to a war +ConVar tf_fake_war_side( "tf_fake_war_side", "12345" ); +#endif + +void CWarLandingPanel::PerformLayout() +{ + BaseClass::PerformLayout(); + +#ifdef STAGING_ONLY + if ( tf_fake_war_side.GetInt() != 12345 ) + { + m_nLastKnownSide = tf_fake_war_side.GetInt(); + } +#endif + + UpdateUIState(); +} + + +void CWarLandingPanel::UpdateWarStatus( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) +{ + if ( pObject->GetTypeID() != CWarData::k_nTypeID ) + { + return; + } + + if ( !steamapicontext || !steamapicontext->SteamUser() ) + { + return; + } + + // Make sure this is for us + CSteamID steamID = steamapicontext->SteamUser()->GetSteamID(); + if ( steamIDOwner != steamID ) + { + return; + } + + const CWarData* pWarData = assert_cast< const CWarData* >( pObject ); + if ( pWarData->Obj().affiliation() != m_nLastKnownSide ) + { + if ( m_eJoiningState == ATTEMPTING_TO_JOIN_AND_WAITING_FOR_RESPONSE ) + { + m_eJoiningState = SUCCESS_RESPONSE_RECIEVED_WAITING_FOR_USER_CONFIRMATION; + } + + m_nLastKnownSide = pWarData->Obj().affiliation(); + } + + UpdateUIState(); +} + +void CWarLandingPanel::SetVisible( bool bVisible ) +{ + BaseClass::SetVisible( bVisible ); + + EditablePanel* pSceneContainer = FindControl< EditablePanel >( "SceneContainer", true ); + if ( pSceneContainer ) + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pSceneContainer, m_strSceneAnimName, false ); + } +} + +void CWarLandingPanel::UpdateUIState() +{ + const CWarDefinition* pWarDef = GetItemSchema()->GetWarDefinitionByIndex( PYRO_VS_HEAVY_WAR_DEF_INDEX ); // SLAM + if ( !pWarDef ) + return; + + EditablePanel* pMainContainer = FindControl< EditablePanel >( "MainContainer" ); + if ( pMainContainer ) + { + bool bWarActive = IsWarActive( PYRO_VS_HEAVY_WAR_DEF_INDEX ); + pMainContainer->SetControlVisible( "WarOverContainer", !bWarActive ); + + bool bNeedsToJoin = m_nLastKnownSide == INVALID_WAR_SIDE; + pMainContainer->SetControlVisible( "AffiliatedContainer", bWarActive && !bNeedsToJoin && m_eJoiningState == NO_ACTION ); + pMainContainer->SetControlVisible( "UnaffiliatedContainer", bWarActive && bNeedsToJoin && m_eJoiningState == NO_ACTION ); + + bool bHasGCConnection = GCClientSystem()->BConnectedtoGC(); + pMainContainer->SetControlVisible( "JoinPyroButton", bHasGCConnection, true ); + pMainContainer->SetControlVisible( "JoinHeavyButton", bHasGCConnection, true ); + pMainContainer->SetControlVisible( "NoGContainer", !bHasGCConnection, true ); + } + + static wchar_t wszEndDateOutString[ 128 ]; + char time_buf[k_RTimeRenderBufferSize]; + CRTime timeExpire( pWarDef->GetEndDate() ); + timeExpire.SetToGMT( false ); + timeExpire.Render( time_buf ); + wchar_t wszTemp[ 1024 ]; + V_UTF8ToUnicode( time_buf, wszTemp, sizeof(wszTemp) ); + + const wchar_t *wpszEndDateFormat = g_pVGuiLocalize->Find( IsWarActive( PYRO_VS_HEAVY_WAR_DEF_INDEX ) ? "#TF_War_EndFutureDate" : "#TF_War_EndPastDate" ); + + g_pVGuiLocalize->ConstructString_safe( wszEndDateOutString, wpszEndDateFormat, 1, wszTemp ); + CExLabel* pSceneContainer = FindControl< CExLabel >( "EndDateLabel", true ); + if ( pSceneContainer ) + { + pSceneContainer->SetText( wszEndDateOutString ); + } + + + EditablePanel* pJoiningPopup = FindControl< EditablePanel >( "CommunicatingWithGCPopup", true ); + if ( !pJoiningPopup ) + return; + + switch ( m_eJoiningState ) + { + case NO_ACTION: + break; + case CONFIRM_SIDE_SELECTION: + { + Assert( m_nPendingSide != INVALID_WAR_SIDE ); + const CWarDefinition::CWarSideDefinition_t* pChosenSide = pWarDef->GetSide( m_nPendingSide ); // Can be NULL + Assert( pChosenSide ); + if ( !pChosenSide ) + return; + + static wchar_t wszOutString[ 128 ]; + const wchar_t *wpszFormat = g_pVGuiLocalize->Find( "#TF_War_ConfirmSideSelection" ); + g_pVGuiLocalize->ConstructString_safe( wszOutString, wpszFormat, 1, g_pVGuiLocalize->Find( pChosenSide->m_pszLocalizedName ) ); + + EditablePanel* pJoining = FindControl< EditablePanel >( "ConfirmSelectionContainer", true ); + if ( pJoining ) + { + pJoining->SetDialogVariable( "confirm_selection", wszOutString ); + } + } + break; + case ATTEMPTING_TO_JOIN_AND_WAITING_FOR_RESPONSE: + { + Assert( m_nPendingSide != INVALID_WAR_SIDE ); + const CWarDefinition::CWarSideDefinition_t* pChosenSide = pWarDef->GetSide( m_nPendingSide ); // Can be NULL + Assert( pChosenSide ); + if ( !pChosenSide ) + return; + + static wchar_t wszOutString[ 128 ]; + const wchar_t *wpszFormat = g_pVGuiLocalize->Find( "#TF_War_JoiningTeam" ); + g_pVGuiLocalize->ConstructString_safe( wszOutString, wpszFormat, 1, g_pVGuiLocalize->Find( pChosenSide->m_pszLocalizedName ) ); + + EditablePanel* pJoining = FindControl< EditablePanel >( "JoiningContainer", true ); + if ( pJoining ) + { + pJoining->SetDialogVariable( "joining_team", wszOutString ); + } + } + + break; + + case SUCCESS_RESPONSE_RECIEVED_WAITING_FOR_USER_CONFIRMATION: + { + const CWarDefinition::CWarSideDefinition_t* pChosenSide = pWarDef->GetSide( m_nLastKnownSide ); // Can be NULL + Assert( pChosenSide ); + if ( !pChosenSide ) + return; + + static wchar_t wszOutString[ 128 ]; + const wchar_t *wpszFormat = g_pVGuiLocalize->Find( "#TF_War_JoinedTeam" ); + g_pVGuiLocalize->ConstructString_safe( wszOutString, wpszFormat, 1, g_pVGuiLocalize->Find( pChosenSide->m_pszLocalizedName ) ); + EditablePanel* pJoined = FindControl< EditablePanel >( "TeamJoinedContainer", true ); + if ( pJoined ) + { + pJoined->SetDialogVariable( "team_joined", wszOutString ); + } + } + break; + + case FAILED_RESPONSE_RECIEVED_WAITING_FOR_USER_CONFIRMATION: + + break; + + default: + Assert( false ); + return; + } + + pJoiningPopup->SetVisible( m_eJoiningState != NO_ACTION ); + + pJoiningPopup->SetControlVisible( "ConfirmSelectionContainer", m_eJoiningState == CONFIRM_SIDE_SELECTION, true ); + pJoiningPopup->SetControlVisible( "JoiningContainer", m_eJoiningState == ATTEMPTING_TO_JOIN_AND_WAITING_FOR_RESPONSE, true ); + pJoiningPopup->SetControlVisible( "TeamJoinedContainer", m_eJoiningState == SUCCESS_RESPONSE_RECIEVED_WAITING_FOR_USER_CONFIRMATION, true ); + pJoiningPopup->SetControlVisible( "FailedToJoinContainer", m_eJoiningState == FAILED_RESPONSE_RECIEVED_WAITING_FOR_USER_CONFIRMATION, true ); +} + +#if defined( STAGING_ONLY ) +CON_COMMAND( tf_war_join_side, "Join a specified war on a specified side" ) +{ + if ( args.ArgC() < 2 ) + return; + + war_definition_index_t nWar = atoi( args[1] ); + const CWarDefinition* pWarDef = GetItemSchema()->GetWarDefinitionByIndex( nWar ); + if ( pWarDef == NULL) + return; + + war_side_t nSide = atoi( args[2] ); + // Allow INVALID_WAR_SIDE for testing purposes + if ( pWarDef->GetSide( nSide ) == NULL && nSide != INVALID_WAR_SIDE ) + return; + + // Join the war! + GCSDK::CProtoBufMsg< CGCMsgGC_War_JoinWar > msg( k_EMsgGC_War_JoinWar ); + + msg.Body().set_war_id( nWar ); + msg.Body().set_affiliation( nSide ); + + GCClientSystem()->BSendMessage( msg ); +} +#endif diff --git a/game/client/tf/vgui/tf_warinfopanel.h b/game/client/tf/vgui/tf_warinfopanel.h new file mode 100644 index 0000000..2771b14 --- /dev/null +++ b/game/client/tf/vgui/tf_warinfopanel.h @@ -0,0 +1,100 @@ +#ifndef TF_WARINFOPANEL_H +#define TF_WARINFOPANEL_H + + +#include "vgui_controls/EditablePanel.h" +#include "tf_wardata.h" +#include "vgui_controls/ProgressBar.h" +#include "local_steam_shared_object_listener.h" + +using namespace vgui; +using namespace GCSDK; + +class CExLabel; + +class CWarStandingPanel : public EditablePanel, public CGameEventListener +{ + DECLARE_CLASS_SIMPLE( CWarStandingPanel, EditablePanel ); +public: + CWarStandingPanel( Panel* pParent, const char* pszPanelname ); + + virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE; + virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE; + virtual void OnThink() OVERRIDE; + virtual void PerformLayout() OVERRIDE; + virtual void FireGameEvent( IGameEvent *event ) OVERRIDE; +private: + + float GetPercentAnimated() const; + + struct TeamScore_t + { + TeamScore_t() + : m_nLastScore( 0 ) + , m_nNewScore( 0 ) + , m_pTeamProgressBar( NULL ) + , m_pContainerPanel( NULL ) + , m_pTeamLabel( NULL ) + , m_pScoreLabel( NULL ) + {} + int m_nLastScore; + int m_nNewScore; + ContinuousProgressBar *m_pTeamProgressBar; + EditablePanel* m_pContainerPanel; + CExLabel* m_pTeamLabel; + CExLabel* m_pScoreLabel; + }; + + bool m_bNeedsLerp; + TeamScore_t m_Scores[2]; + float m_flLastUpdateTime; + ContinuousProgressBar *m_pTeam0ProgressBar; + ContinuousProgressBar *m_pTeam1ProgressBar; + CUtlString m_strWarName; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CWarLandingPanel : public EditablePanel, public CLocalSteamSharedObjectListener +{ + DECLARE_CLASS_SIMPLE( CWarLandingPanel, EditablePanel ); +public: + CWarLandingPanel( Panel *pParent, const char *pszPanelName ); + + virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE; + virtual void ApplySettings( KeyValues *inResourceData ); + virtual void OnCommand( const char *pCommand ) OVERRIDE; + virtual void OnThink() OVERRIDE; + virtual void PerformLayout() OVERRIDE; + + virtual void SOCreated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) { UpdateWarStatus( steamIDOwner, pObject, eEvent ); } + virtual void SOUpdated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) { UpdateWarStatus( steamIDOwner, pObject, eEvent ); } + virtual void SODestroyed( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) { UpdateWarStatus( steamIDOwner, pObject, eEvent ); } + + virtual void SetVisible( bool bVisible ) OVERRIDE; + +private: + + enum EJoiningState_t + { + NO_ACTION = 0, + CONFIRM_SIDE_SELECTION, + ATTEMPTING_TO_JOIN_AND_WAITING_FOR_RESPONSE, + SUCCESS_RESPONSE_RECIEVED_WAITING_FOR_USER_CONFIRMATION, + FAILED_RESPONSE_RECIEVED_WAITING_FOR_USER_CONFIRMATION, + }; + + void UpdateWarStatus( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ); + + void UpdateUIState(); + + float m_flChoseTeamTime; + war_side_t m_nPendingSide; + war_side_t m_nLastKnownSide; + CUtlString m_strSceneAnimName; + + EJoiningState_t m_eJoiningState; +}; + +#endif //TF_WARINFOPANEL_H
\ No newline at end of file diff --git a/game/client/tf/vgui/vgui_critpanel.cpp b/game/client/tf/vgui/vgui_critpanel.cpp new file mode 100644 index 0000000..a58d18b --- /dev/null +++ b/game/client/tf/vgui/vgui_critpanel.cpp @@ -0,0 +1,255 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=====================================================================================// + +#include "cbase.h" +#include "hud.h" +#include "hudelement.h" +#include "hud_macros.h" +#include "hud_numericdisplay.h" +#include <KeyValues.h> +#include <vgui/IScheme.h> +#include <vgui/ISurface.h> +#include <vgui/ISystem.h> +#include <vgui_controls/AnimationController.h> +#include "iclientmode.h" +#include "tf_shareddefs.h" +#include <vgui_controls/EditablePanel.h> +#include <vgui_controls/Label.h> +#include <vgui_controls/ImagePanel.h> +#include <vgui/ISurface.h> +#include <vgui/IImage.h> +#include <vgui_controls/Label.h> +#include "VGuiMatSurface/IMatSystemSurface.h" + +#include "c_tf_player.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static ConVar cl_showcrit( "cl_showcrit", "0", FCVAR_DEVELOPMENTONLY, "Debug! Draw crit values above the ammo count." ); + +//----------------------------------------------------------------------------- +// Purpose: Critical meter panel. +//----------------------------------------------------------------------------- +class CCriticalPanel : public CHudElement, public vgui::EditablePanel +{ +public: + DECLARE_CLASS_SIMPLE( CCriticalPanel, vgui::EditablePanel ); + + CCriticalPanel( const char *pElementName ); + virtual ~CCriticalPanel( void ); + + virtual void Reset(); + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual bool ShouldDraw( void ); + +protected: + + virtual void OnThink(); + +private: + + void InitCritData( void ); + + // vgui + vgui::HFont m_hFont; + vgui::Label *m_pTextLabel; + + float m_flNextThink; + + // Critical Data. + struct CriticalData_t + { + float m_flCurrent; + float m_flAverage; + float m_flLow; + float m_flHigh; + }; + + bool m_bInitData; + CriticalData_t m_CritData; +}; + +DECLARE_HUDELEMENT( CCriticalPanel ); + +#define CRITICAL_PANEL_WIDTH 300 +#define CRITICAL_BLEND_WEIGHT 0.1f + +//----------------------------------------------------------------------------- +// Purpose: Constructor. +// Input : *parent - parent VGUI window +//----------------------------------------------------------------------------- +CCriticalPanel::CCriticalPanel( const char *pElementName ) : CHudElement( pElementName ), BaseClass( NULL, "CriticalPanel" ) +{ + Panel *pParent = g_pClientMode->GetViewport(); + SetParent( pParent ); + SetScheme( "ClientScheme" ); + + SetVisible( false ); + SetCursor( null ); + + SetFgColor( Color( 0, 0, 0, 255 ) ); + SetPaintBackgroundEnabled( false ); + + m_pTextLabel = new vgui::Label( this, "CriticalPanelLabel", "" ); + m_hFont = 0; + + // Have we initialize the criticl data yet? + m_bInitData = false; + + m_flNextThink = 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor. +//----------------------------------------------------------------------------- +CCriticalPanel::~CCriticalPanel( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCriticalPanel::Reset() +{ + m_flNextThink = gpGlobals->curtime + 0.05f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCriticalPanel::InitCritData( void ) +{ + m_CritData.m_flCurrent = -1.0f; + m_CritData.m_flAverage = -1.0f; + m_CritData.m_flLow = -1.0f; + m_CritData.m_flHigh = -1.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCriticalPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + m_pTextLabel->SetBounds( 0.0f, 0.0f, GetWide(), GetTall() ); + m_pTextLabel->SetFont( pScheme->GetFont( "DefaultSmall" ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CCriticalPanel::ShouldDraw( void ) +{ + return false; + + if ( ( !cl_showcrit.GetInt() || ( gpGlobals->absoluteframetime <= 0 ) ) && + ( !cl_showcrit.GetInt() ) ) + { + m_bInitData = false; + return false; + } + + if ( !m_bInitData ) + { + m_bInitData = true; + InitCritData(); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void GetCritColor( float flCritMult, unsigned char ucColor[3] ) +{ + ucColor[0] = 255; ucColor[1] = 255; ucColor[2] = 0; + + if ( flCritMult < 5.0f ) + { + ucColor[1] = 0; + } + else if ( flCritMult > 5.0f ) + { + ucColor[0] = 0; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCriticalPanel::OnThink() +{ + // Get the local TF player. + C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( !pPlayer ) + return; + + if ( cl_showcrit.GetInt() ) + { + // Get the current critical multiplier. + float flCritMult = pPlayer->GetCritMult() * TF_DAMAGE_CRIT_CHANCE; + flCritMult *= 100.0f; + + if ( m_CritData.m_flAverage < 0.0f ) + { + // Initial data. + m_CritData.m_flCurrent = flCritMult; + m_CritData.m_flAverage = flCritMult; + m_CritData.m_flLow = m_CritData.m_flAverage; + m_CritData.m_flHigh = m_CritData.m_flAverage; + } + else + { + // Average over time. + m_CritData.m_flCurrent = flCritMult; + m_CritData.m_flAverage *= ( 1.0f - CRITICAL_BLEND_WEIGHT ) ; + m_CritData.m_flAverage += ( flCritMult * CRITICAL_BLEND_WEIGHT ); + } + + // Adjust for highs and lows. + m_CritData.m_flLow = MIN( m_CritData.m_flLow, flCritMult ); + m_CritData.m_flHigh = MAX( m_CritData.m_flHigh, flCritMult ); + + unsigned char ucColor[3]; + GetCritColor( flCritMult, ucColor ); + m_pTextLabel->SetFgColor( Color( ucColor[0], ucColor[1], ucColor[2], 255 ) ); + + char szCriticalText[256]; + //Q_snprintf( szCriticalText, sizeof( szCriticalText ), "Crit: %3.2f (A:%3.2f, L:%3.2f, H:%3.2f)", + // m_CritData.m_flCurrent, m_CritData.m_flAverage, m_CritData.m_flLow, m_CritData.m_flHigh ); + Q_snprintf( szCriticalText, sizeof( szCriticalText ), "Crit Chance: %3.2f", m_CritData.m_flCurrent ); + + m_pTextLabel->SetText( szCriticalText ); + + m_flNextThink = gpGlobals->curtime + 0.01f; + } + else + { + m_flNextThink = gpGlobals->curtime + 0.1f; + } +} + +#if 0 +//----------------------------------------------------------------------------- +// Purpose: +// Input : +//----------------------------------------------------------------------------- +void CCriticalPanel::Paint() +{ + if ( cl_showcrit.GetInt() ) + { + unsigned char ucColor[3]; + GetCritColor( m_CritData.m_flCurrent, ucColor ); + g_pMatSystemSurface->DrawColoredText( m_hFont, 0, 2, ucColor[0], ucColor[1], ucColor[2], 255, + "%2.2f (Avg:%2.2f, Low:%2.2f, High:%2.2f)", + m_CritData.m_flCurrent, m_CritData.m_flAverage, m_CritData.m_flLow, m_CritData.m_flHigh ); + } +} +#endif
\ No newline at end of file diff --git a/game/client/tf/vgui/vgui_pda_panel.cpp b/game/client/tf/vgui/vgui_pda_panel.cpp new file mode 100644 index 0000000..28ae408 --- /dev/null +++ b/game/client/tf/vgui/vgui_pda_panel.cpp @@ -0,0 +1,336 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" +#include "c_vguiscreen.h" +#include <vgui/IVGui.h> +#include "ienginevgui.h" +#include "vgui_bitmapimage.h" +#include "vgui_bitmappanel.h" +#include "vgui_controls/Label.h" +#include "tf_weapon_pda.h" +#include "vgui/ISurface.h" +#include "tf_controls.h" +#include "c_tf_player.h" +#include "tf_weapon_invis.h" +#include <vgui_controls/RadioButton.h> +#include "clientmode.h" +#include <vgui_controls/ProgressBar.h> +#include <vgui_controls/CircularProgressBar.h> + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Control screen +//----------------------------------------------------------------------------- +class CPDAPanel : public CVGuiScreenPanel +{ + DECLARE_CLASS( CPDAPanel, CVGuiScreenPanel ); + +public: + CPDAPanel( vgui::Panel *parent, const char *panelName ); + ~CPDAPanel(); + virtual bool Init( KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData ); + virtual void OnTick(); + +protected: + C_BaseCombatWeapon *GetOwningWeapon(); +}; + +DECLARE_VGUI_SCREEN_FACTORY( CPDAPanel, "pda_panel" ); + +//----------------------------------------------------------------------------- +// Constructor: +//----------------------------------------------------------------------------- +CPDAPanel::CPDAPanel( vgui::Panel *parent, const char *panelName ) +: BaseClass( parent, "CPDAPanel", vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/PDAControlPanelScheme.res", "TFBase" ) ) +{ +} + +CPDAPanel::~CPDAPanel() +{ +} + +//----------------------------------------------------------------------------- +// Initialization +//----------------------------------------------------------------------------- +bool CPDAPanel::Init( KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData ) +{ + // Make sure we get ticked... + vgui::ivgui()->AddTickSignal( GetVPanel() ); + + if (!BaseClass::Init(pKeyValues, pInitData)) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Returns the object it's attached to +//----------------------------------------------------------------------------- +C_BaseCombatWeapon *CPDAPanel::GetOwningWeapon() +{ + C_BaseEntity *pScreenEnt = GetEntity(); + if (!pScreenEnt) + return NULL; + + C_BaseEntity *pOwner = pScreenEnt->GetOwnerEntity(); + if (!pOwner) + return NULL; + + C_BaseViewModel *pViewModel = dynamic_cast< C_BaseViewModel * >( pOwner ); + if ( !pViewModel ) + return NULL; + + return pViewModel->GetOwningWeapon(); +} + +//----------------------------------------------------------------------------- +// Frame-based update +//----------------------------------------------------------------------------- +void CPDAPanel::OnTick() +{ + BaseClass::OnTick(); + + SetVisible( true ); +} + +//----------------------------------------------------------------------------- +// Engineer Destroy PDA +//----------------------------------------------------------------------------- + +class CPDAPanel_Engineer_Destroy : public CPDAPanel +{ + DECLARE_CLASS( CPDAPanel_Engineer_Destroy, CPDAPanel ); + +public: + CPDAPanel_Engineer_Destroy( vgui::Panel *parent, const char *panelName ); +}; + +DECLARE_VGUI_SCREEN_FACTORY( CPDAPanel_Engineer_Destroy, "pda_panel_engineer_destroy" ); + +//----------------------------------------------------------------------------- +// Constructor: +//----------------------------------------------------------------------------- +CPDAPanel_Engineer_Destroy::CPDAPanel_Engineer_Destroy( vgui::Panel *parent, const char *panelName ) +: CPDAPanel( parent, "CPDAPanel_Engineer_Destroy" ) +{ +} + +//----------------------------------------------------------------------------- +// Engineer Build PDA +//----------------------------------------------------------------------------- + +class CPDAPanel_Engineer_Build : public CPDAPanel +{ + DECLARE_CLASS( CPDAPanel_Engineer_Build, CPDAPanel ); + +public: + CPDAPanel_Engineer_Build( vgui::Panel *parent, const char *panelName ); +}; + +DECLARE_VGUI_SCREEN_FACTORY( CPDAPanel_Engineer_Build, "pda_panel_engineer_build" ); + +//----------------------------------------------------------------------------- +// Constructor: +//----------------------------------------------------------------------------- +CPDAPanel_Engineer_Build::CPDAPanel_Engineer_Build( vgui::Panel *parent, const char *panelName ) +: CPDAPanel( parent, "CPDAPanel_Engineer" ) +{ +} + +//----------------------------------------------------------------------------- +// Spy PDA +//----------------------------------------------------------------------------- + +class CPDAPanel_Spy : public CPDAPanel +{ + DECLARE_CLASS( CPDAPanel_Spy, CPDAPanel ); + +public: + CPDAPanel_Spy( vgui::Panel *parent, const char *panelName ); +}; + +DECLARE_VGUI_SCREEN_FACTORY( CPDAPanel_Spy, "pda_panel_spy" ); + +//----------------------------------------------------------------------------- +// Constructor: +//----------------------------------------------------------------------------- +CPDAPanel_Spy::CPDAPanel_Spy( vgui::Panel *parent, const char *panelName ) +: CPDAPanel( parent, "CPDAPanel_Spy" ) +{ +} + +//----------------------------------------------------------------------------- +// Spy Invis PDA +//----------------------------------------------------------------------------- + +class CPDAPanel_Spy_Invis : public CPDAPanel +{ + DECLARE_CLASS( CPDAPanel_Spy_Invis, CPDAPanel ); + +public: + CPDAPanel_Spy_Invis( vgui::Panel *parent, const char *panelName ); + + virtual void OnTick(); + +private: + + ProgressBar *m_pInvisProgress; +}; + +DECLARE_VGUI_SCREEN_FACTORY( CPDAPanel_Spy_Invis, "pda_panel_spy_invis" ); + +//----------------------------------------------------------------------------- +// Constructor: +//----------------------------------------------------------------------------- +CPDAPanel_Spy_Invis::CPDAPanel_Spy_Invis( vgui::Panel *parent, const char *panelName ) +: CPDAPanel( parent, "CPDAPanel_Spy_Invis" ) +{ + vgui::ivgui()->AddTickSignal( GetVPanel() ); + + m_pInvisProgress = new ProgressBar( this, "InvisProgress" ); +} + +//----------------------------------------------------------------------------- +// Update the progress bar with how much invis we have left +//----------------------------------------------------------------------------- +void CPDAPanel_Spy_Invis::OnTick( void ) +{ + C_BaseCombatWeapon *pInvisWeapon = GetOwningWeapon(); + + if ( !pInvisWeapon ) + return; + + C_TFPlayer *pPlayer = ToTFPlayer( pInvisWeapon->GetOwner() ); + + if ( pPlayer && !pPlayer->IsDormant() ) + { + if ( m_pInvisProgress ) + { + float flMeter = pPlayer->m_Shared.GetSpyCloakMeter(); + m_pInvisProgress->SetProgress( flMeter / 100.0f ); + } + } +} + +//----------------------------------------------------------------------------- +// Spy Invis PDA panel for the pocketwatch +//----------------------------------------------------------------------------- +class CPDAPanel_Spy_Invis_Pocket : public CPDAPanel +{ + DECLARE_CLASS( CPDAPanel_Spy_Invis_Pocket, CPDAPanel ); + +public: + CPDAPanel_Spy_Invis_Pocket( vgui::Panel *parent, const char *panelName ); + + virtual void OnTick(); + +protected: + ProgressBar *m_pInvisProgress; + float m_flPrevProgress; +}; + +DECLARE_VGUI_SCREEN_FACTORY( CPDAPanel_Spy_Invis_Pocket, "pda_panel_spy_invis_pocket" ); + +//----------------------------------------------------------------------------- +// Constructor: +//----------------------------------------------------------------------------- +CPDAPanel_Spy_Invis_Pocket::CPDAPanel_Spy_Invis_Pocket( vgui::Panel *parent, const char *panelName ) + : CPDAPanel( parent, "CPDAPanel_Spy_Invis_Pocket" ) +{ + vgui::ivgui()->AddTickSignal( GetVPanel() ); + + CircularProgressBar* pCircularProgressBar = new CircularProgressBar( this, "InvisProgress" ); + pCircularProgressBar->SetReverseProgress( true ); + m_pInvisProgress = pCircularProgressBar; + m_flPrevProgress = -1; +} + +//----------------------------------------------------------------------------- +// Update the progress bar with how much invis we have left +//----------------------------------------------------------------------------- +void CPDAPanel_Spy_Invis_Pocket::OnTick( void ) +{ + C_BaseCombatWeapon *pInvisWeapon = GetOwningWeapon(); + + if ( !pInvisWeapon ) + return; + + C_TFPlayer *pPlayer = ToTFPlayer( pInvisWeapon->GetOwner() ); + + if ( pPlayer && !pPlayer->IsDormant() ) + { + if ( m_pInvisProgress ) + { + float flMeter = pPlayer->m_Shared.GetSpyCloakMeter(); + if ( m_flPrevProgress != flMeter ) + { + m_flPrevProgress = flMeter; + + if ( flMeter == 100.f ) + { + m_pInvisProgress->SetFgColor( COLOR_GREEN ); + } + else if ( flMeter < 40.f ) + { + m_pInvisProgress->SetFgColor( COLOR_RED ); + } + else + { + m_pInvisProgress->SetFgColor( COLOR_YELLOW ); + } + + m_pInvisProgress->SetProgress( flMeter / 100.0f ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Spy Invis PDA panel for the TTG pocketwatch +//----------------------------------------------------------------------------- +class CPDAPanel_Spy_Invis_Pocket_TTG : public CPDAPanel_Spy_Invis_Pocket +{ + DECLARE_CLASS( CPDAPanel_Spy_Invis_Pocket_TTG, CPDAPanel_Spy_Invis_Pocket ); + +public: + CPDAPanel_Spy_Invis_Pocket_TTG( vgui::Panel *parent, const char *panelName ); +}; + +DECLARE_VGUI_SCREEN_FACTORY( CPDAPanel_Spy_Invis_Pocket_TTG, "pda_panel_spy_invis_pocket_ttg" ); + +//----------------------------------------------------------------------------- +// Constructor: +//----------------------------------------------------------------------------- +CPDAPanel_Spy_Invis_Pocket_TTG::CPDAPanel_Spy_Invis_Pocket_TTG( vgui::Panel *parent, const char *panelName ) +: CPDAPanel_Spy_Invis_Pocket( parent, "CPDAPanel_Spy_Invis_Pocket_TTG" ) +{ + dynamic_cast<CircularProgressBar*>(m_pInvisProgress)->SetStartSegment( 7 ); // Do the pellet first. +} + +//----------------------------------------------------------------------------- +// Spy Invis PDA panel for the Hitman pocketwatch +//----------------------------------------------------------------------------- + +class CPDAPanel_Spy_Invis_Pocket_HM : public CPDAPanel_Spy_Invis_Pocket +{ + DECLARE_CLASS( CPDAPanel_Spy_Invis_Pocket_HM, CPDAPanel_Spy_Invis_Pocket ); + +public: + CPDAPanel_Spy_Invis_Pocket_HM( vgui::Panel *parent, const char *panelName ); +}; + +DECLARE_VGUI_SCREEN_FACTORY( CPDAPanel_Spy_Invis_Pocket_HM, "pda_panel_spy_invis_pocket_hm" ); + +//----------------------------------------------------------------------------- +// Constructor: +//----------------------------------------------------------------------------- +CPDAPanel_Spy_Invis_Pocket_HM::CPDAPanel_Spy_Invis_Pocket_HM( vgui::Panel *parent, const char *panelName ) + : CPDAPanel_Spy_Invis_Pocket( parent, "CPDAPanel_Spy_Invis_Pocket_HM" ) +{ +} diff --git a/game/client/tf/vgui/vgui_rootpanel_tf.cpp b/game/client/tf/vgui/vgui_rootpanel_tf.cpp new file mode 100644 index 0000000..fbd0768 --- /dev/null +++ b/game/client/tf/vgui/vgui_rootpanel_tf.cpp @@ -0,0 +1,160 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= +#include "cbase.h" +#include "vgui_int.h" +#include "ienginevgui.h" +#include "vgui_rootpanel_tf.h" +#include "vgui/IVGui.h" +#include "tier2/fileutils.h" +#include "icommandline.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +C_TFRootPanel *g_pRootPanel = NULL; + +static ConVar tf_ui_version( "tf_ui_version", "1", FCVAR_DEVELOPMENTONLY ); + +extern const char *COM_GetModDirectory(); + +void CheckCustomModSearchPaths() +{ + const char *pszCustomPathID = "custom_mod"; + + CUtlVector< CUtlString > searchPaths; + GetSearchPath( searchPaths, pszCustomPathID ); + + FOR_EACH_VEC( searchPaths, i ) + { + const char *pszSearchPath = searchPaths[i].String(); + // check each path for version file + char szVersionFile[MAX_PATH]; + V_ComposeFileName( pszSearchPath, "info.vdf", szVersionFile, sizeof( szVersionFile ) ); + KeyValuesAD versionKV( pszCustomPathID ); + if ( versionKV->LoadFromFile( g_pFullFileSystem, szVersionFile ) ) + { + // mod must declare this ConVar + if ( tf_ui_version.GetInt() == versionKV->GetInt( "ui_version" ) ) + { + continue; + } + + DevMsg( "'ui_version' mismatch. expected version %d. Removed search path '%s' from all pathIDs.\n", tf_ui_version.GetInt(), pszSearchPath ); + // remove from all path ids + g_pFullFileSystem->RemoveSearchPath( pszSearchPath, pszCustomPathID ); + g_pFullFileSystem->RemoveSearchPath( pszSearchPath, "game" ); + g_pFullFileSystem->RemoveSearchPath( pszSearchPath, "mod" ); + } + else + { + DevMsg( "missing 'info.vdf'. Removed search path '%s' from '%s' pathID.\n", pszSearchPath, pszCustomPathID ); + g_pFullFileSystem->RemoveSearchPath( pszSearchPath, pszCustomPathID ); + } + } + + // only allow to load loose files when using insecure mode + if ( CommandLine()->FindParm( "-insecure" ) ) + { + // allow lose files in these search paths + g_pFullFileSystem->AddSearchPath( "tf", "vgui" ); + g_pFullFileSystem->AddSearchPath( "hl2", "vgui" ); + g_pFullFileSystem->AddSearchPath( "platform", "vgui" ); + } +} + + +//----------------------------------------------------------------------------- +// Global functions. +//----------------------------------------------------------------------------- +void VGUI_CreateClientDLLRootPanel( void ) +{ + // do this before creating any vgui panels + CheckCustomModSearchPaths(); + + g_pRootPanel = new C_TFRootPanel( enginevgui->GetPanel( PANEL_CLIENTDLL ) ); +} + +void VGUI_DestroyClientDLLRootPanel( void ) +{ + g_pRootPanel->MarkForDeletion(); + g_pRootPanel = NULL; +} + +vgui::VPANEL VGui_GetClientDLLRootPanel( void ) +{ + return g_pRootPanel->GetVPanel(); +} + + +//----------------------------------------------------------------------------- +// C_TFRootPanel implementation. +//----------------------------------------------------------------------------- +C_TFRootPanel::C_TFRootPanel( vgui::VPANEL parent ) + : BaseClass( NULL, "TF Root Panel" ) +{ + SetParent( parent ); + SetPaintEnabled( false ); + SetPaintBorderEnabled( false ); + SetPaintBackgroundEnabled( false ); + + // This panel does post child painting + SetPostChildPaintEnabled( true ); + + // Make it screen sized + SetBounds( 0, 0, ScreenWidth(), ScreenHeight() ); + + // Ask for OnTick messages + vgui::ivgui()->AddTickSignal( GetVPanel() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TFRootPanel::~C_TFRootPanel( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TFRootPanel::PostChildPaint() +{ + BaseClass::PostChildPaint(); + + // Draw all panel effects + RenderPanelEffects(); +} + +//----------------------------------------------------------------------------- +// Purpose: For each panel effect, check if it wants to draw and draw it on +// this panel/surface if so +//----------------------------------------------------------------------------- +void C_TFRootPanel::RenderPanelEffects( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TFRootPanel::OnTick( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Reset effects on level load/shutdown +//----------------------------------------------------------------------------- +void C_TFRootPanel::LevelInit( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TFRootPanel::LevelShutdown( void ) +{ +} + diff --git a/game/client/tf/vgui/vgui_rootpanel_tf.h b/game/client/tf/vgui/vgui_rootpanel_tf.h new file mode 100644 index 0000000..b1b8c65 --- /dev/null +++ b/game/client/tf/vgui/vgui_rootpanel_tf.h @@ -0,0 +1,56 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef VGUI_ROOTPANEL_TF_H +#define VGUI_ROOTPANEL_TF_H +#ifdef _WIN32 +#pragma once +#endif + + +#include <vgui_controls/Panel.h> +#include <vgui_controls/EditablePanel.h> +#include "utlvector.h" + + +class CPanelEffect; + + +// Serial under of effect, for safe lookup +typedef unsigned int EFFECT_HANDLE; + +//----------------------------------------------------------------------------- +// Purpose: Sits between engine and client .dll panels +// Responsible for drawing screen overlays +//----------------------------------------------------------------------------- +class C_TFRootPanel : public vgui::Panel +{ + typedef vgui::Panel BaseClass; +public: + C_TFRootPanel( vgui::VPANEL parent ); + virtual ~C_TFRootPanel( void ); + + // Draw Panel effects here + virtual void PostChildPaint(); + + // Clear list of Panel Effects + virtual void LevelInit( void ); + virtual void LevelShutdown( void ); + + // Run effects and let them decide whether to remove themselves + void OnTick( void ); + +private: + + // Render all panel effects + void RenderPanelEffects( void ); + + // List of current panel effects + CUtlVector< CPanelEffect *> m_Effects; +}; + + +#endif // VGUI_ROOTPANEL_TF_H diff --git a/game/client/tf/vgui/vgui_rotation_slider.cpp b/game/client/tf/vgui/vgui_rotation_slider.cpp new file mode 100644 index 0000000..8fc8928 --- /dev/null +++ b/game/client/tf/vgui/vgui_rotation_slider.cpp @@ -0,0 +1,74 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include <vgui/MouseCode.h> +#include "vgui_rotation_slider.h" + + +CRotationSlider::CRotationSlider( vgui::Panel *pParent, const char *pName ) : + BaseClass( pParent, pName ) +{ + AddActionSignalTarget( this ); + SetRange( -180, 180 ); + SetTickCaptions("-180", "180"); + SetValue( 0 ); + m_flYaw = 0; +} + +void CRotationSlider::SetControlledObject( C_BaseObject *pObject ) +{ + m_hObject.Set( pObject ); +} + +//----------------------------------------------------------------------------- +// When the slider is activated, deactivated, or moves +//----------------------------------------------------------------------------- +void CRotationSlider::OnMousePressed( vgui::MouseCode code ) +{ + BaseClass::OnMousePressed( code ); + + if (code != MOUSE_LEFT) + return; + + C_BaseObject *pObj = m_hObject.Get(); + if (pObj) + { + m_flInitialYaw = pObj->GetAbsAngles().y; + pObj->PreviewYaw( m_flInitialYaw ); + pObj->ActivateYawPreview( true ); + } +} + +void CRotationSlider::OnSliderMoved( int position ) +{ + C_BaseObject *pObj = m_hObject.Get(); + if (pObj && pObj->IsPreviewingYaw()) + { + m_flYaw = anglemod(position); + pObj->PreviewYaw( m_flInitialYaw - m_flYaw ); + } +} + +void CRotationSlider::OnMouseReleased( vgui::MouseCode code ) +{ + BaseClass::OnMouseReleased( code ); + + if (code != MOUSE_LEFT) + return; + + C_BaseObject *pObj = m_hObject.Get(); + if (pObj) + { + char szbuf[48]; + Q_snprintf( szbuf, sizeof( szbuf ), "yaw %0.2f\n", m_flInitialYaw - m_flYaw ); + pObj->SendClientCommand( szbuf ); + pObj->ActivateYawPreview( false ); + SetValue(0); + m_flYaw = 0; + } +} diff --git a/game/client/tf/vgui/vgui_rotation_slider.h b/game/client/tf/vgui/vgui_rotation_slider.h new file mode 100644 index 0000000..549068a --- /dev/null +++ b/game/client/tf/vgui/vgui_rotation_slider.h @@ -0,0 +1,43 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VGUI_ROTATION_SLIDER_H +#define VGUI_ROTATION_SLIDER_H +#ifdef _WIN32 +#pragma once +#endif + + +#include <vgui_controls/Slider.h> +#include "c_baseobject.h" + + +//----------------------------------------------------------------------------- +// +// Slider for object control +// +//----------------------------------------------------------------------------- +class CRotationSlider : public vgui::Slider +{ + DECLARE_CLASS_SIMPLE( CRotationSlider, vgui::Slider ); + +public: + CRotationSlider( vgui::Panel *pParent, const char *pName ); + void SetControlledObject( C_BaseObject *pObject ); + + virtual void OnMousePressed( vgui::MouseCode code ); + virtual void OnMouseReleased( vgui::MouseCode code ); + MESSAGE_FUNC_INT( OnSliderMoved, "SliderMoved", position ); + +private: + CHandle<C_BaseObject> m_hObject; + float m_flInitialYaw; + float m_flYaw; +}; + + +#endif // VGUI_ROTATION_SLIDER_H |