summaryrefslogtreecommitdiff
path: root/game/client/econ/store
diff options
context:
space:
mode:
Diffstat (limited to 'game/client/econ/store')
-rw-r--r--game/client/econ/store/store_page.cpp2233
-rw-r--r--game/client/econ/store/store_page.h424
-rw-r--r--game/client/econ/store/store_page_halloween.cpp85
-rw-r--r--game/client/econ/store/store_page_halloween.h49
-rw-r--r--game/client/econ/store/store_page_new.cpp232
-rw-r--r--game/client/econ/store/store_page_new.h107
-rw-r--r--game/client/econ/store/store_panel.cpp2175
-rw-r--r--game/client/econ/store/store_panel.h236
-rw-r--r--game/client/econ/store/store_preview_item.cpp418
-rw-r--r--game/client/econ/store/store_preview_item.h100
-rw-r--r--game/client/econ/store/store_viewcart.cpp387
-rw-r--r--game/client/econ/store/store_viewcart.h75
12 files changed, 6521 insertions, 0 deletions
diff --git a/game/client/econ/store/store_page.cpp b/game/client/econ/store/store_page.cpp
new file mode 100644
index 0000000..a60212f
--- /dev/null
+++ b/game/client/econ/store/store_page.cpp
@@ -0,0 +1,2233 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store/store_page.h"
+#include "vgui/ISurface.h"
+#include "vgui/IInput.h"
+#include "vgui/ILocalize.h"
+#include "gamestringpool.h"
+#include "econ_item_inventory.h"
+#include "econ_item_system.h"
+#include "item_model_panel.h"
+#include "store/store_panel.h"
+#include "store/store_preview_item.h"
+#include "store/store_viewcart.h"
+#include "rtime.h"
+#include "econ_ui.h"
+#include "store/store_page_new.h"
+#include "gc_clientsystem.h"
+#include "confirm_dialog.h"
+
+#ifdef TF_CLIENT_DLL
+#include "c_tf_gamestats.h"
+#include "c_tf_freeaccount.h"
+#endif // TF_CLIENT_DLL
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+#ifdef TF_CLIENT_DLL
+void AddSubKeyNamed( KeyValues *pKeys, const char *pszName );
+#endif
+
+DECLARE_BUILD_FACTORY( CStorePreviewItemIcon );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStoreItemControlsPanel::CStoreItemControlsPanel( vgui::Panel *pParent, const char *pPanelName, CItemModelPanel *pItemModelPanel )
+: vgui::EditablePanel( pParent, pPanelName ),
+ m_pItemModelPanel( pItemModelPanel ),
+ m_pEntry( NULL ),
+ m_bItemPanelEntered( false ),
+ m_bButtonsVisible( false )
+{
+}
+
+void CStoreItemControlsPanel::SetMouseHoverHandler( Panel *pHandler )
+{
+ m_pMouseHoverHandler = pHandler;
+}
+
+void CStoreItemControlsPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings(
+ ShouldUseNewStore() ?
+ "Resource/UI/econ/store/v2/StoreItemControls.res" :
+ "Resource/UI/econ/store/v1/StoreItemControls.res"
+ );
+}
+
+const econ_store_entry_t *CStoreItemControlsPanel::GetItem() const
+{
+ return m_pEntry;
+}
+
+void CStoreItemControlsPanel::SetItem( const econ_store_entry_t *pEntry )
+{
+ m_pEntry = pEntry;
+}
+
+void CStoreItemControlsPanel::SetButtonsVisible( bool bVisible )
+{
+ m_bButtonsVisible = bVisible;
+
+ for ( int i = 0; i < GetChildCount(); ++i )
+ {
+ CExButton *pButton = dynamic_cast< CExButton* >( GetChild( i ) );
+ if ( pButton )
+ {
+ pButton->SetVisible( bVisible );
+ pButton->SetArmed( false );
+ }
+ }
+}
+
+void CStoreItemControlsPanel::OnCursorEntered()
+{
+ BaseClass::OnCursorEntered();
+
+ if ( m_pItemModelPanel && m_pItemModelPanel->HasItem() )
+ {
+ SetButtonsVisible( true );
+ }
+}
+
+void CStoreItemControlsPanel::OnCursorExited()
+{
+ BaseClass::OnCursorExited();
+}
+
+void CStoreItemControlsPanel::OnItemPanelEntered()
+{
+ m_bItemPanelEntered = true;
+ SetButtonsVisible( true );
+}
+
+void CStoreItemControlsPanel::OnItemPanelExited()
+{
+ m_bItemPanelEntered = false;
+}
+
+void CStoreItemControlsPanel::OnThink()
+{
+ if ( !m_bItemPanelEntered )
+ {
+ if ( !IsCursorOver() )
+ {
+ SetButtonsVisible( false );
+ }
+ }
+
+ if ( m_pMouseHoverHandler.Get() )
+ {
+ KeyValues *pMsg = new KeyValues( "StoreItemControlsPanelHover", "entered", m_bButtonsVisible );
+ pMsg->SetPtr( "entry", (void *)m_pEntry );
+ PostMessage( m_pMouseHoverHandler.Get(), pMsg );
+ }
+}
+
+void CStoreItemControlsPanel::OnCommand( const char *command )
+{
+ if ( !Q_strnicmp( command, "addtocart", 9 ) )
+ {
+ PostActionSignal( new KeyValues( "ItemAddToCart" ) );
+ }
+ else if ( !Q_strnicmp( command, "preview_item", 12 ) )
+ {
+ PostActionSignal( new KeyValues( "ItemPreview" ) );
+ }
+ else if ( !Q_strnicmp( command, "details", 7 ) )
+ {
+ PostActionSignal( new KeyValues( "ItemDetails" ) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePreviewItemIcon::OnItemPanelMouseReleased( vgui::Panel *panel )
+{
+ PostActionSignal(new KeyValues("ItemIconSelected", "icon", m_iIconIndex));
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePricePanel::CStorePricePanel( vgui::Panel *pParent, const char *pPanelName )
+ : vgui::EditablePanel( pParent, pPanelName )
+{
+ m_bOldDiscountVisibility = false;
+ m_pPrice = NULL;
+ m_pDiscount = NULL;
+ m_pNew = NULL;
+ m_pSale = NULL;
+ m_pSaleBorder = NULL;
+ m_pOGPrice = NULL;
+ m_pCrossout = NULL;
+ m_pLimited = NULL;
+ m_pHighlighted = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePricePanel::~CStorePricePanel()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char* CStorePricePanel::GetPanelResFile()
+{
+ return "Resource/UI/econ/store/v1/StorePrice.res";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( GetPanelResFile() );
+
+ m_pPrice = dynamic_cast< CExLabel* >( FindChildByName( "Price" ) );
+ m_pDiscount = dynamic_cast< CExLabel* >( FindChildByName( "Discount" ) );
+ m_pHighlighted = dynamic_cast< CExLabel* >( FindChildByName( "Highlighted" ) );
+ m_pNew = dynamic_cast< CExLabel* >( FindChildByName( "NewLarge" ) );
+ if ( !m_pNew )
+ {
+ m_pNew = dynamic_cast< CExLabel* >( FindChildByName( "New" ) );
+ }
+ m_pSale = dynamic_cast< CExLabel* >( FindChildByName( "Sale" ) );
+ m_pSaleBorder = dynamic_cast< vgui::EditablePanel* >( FindChildByName( "StorePriceBorder" ) );
+ m_pOGPrice = dynamic_cast< CExLabel* >( FindChildByName( "OG_Price" ) );
+ m_pCrossout = FindChildByName( "OG_Price_CrossOut" );
+
+ // Only support one "limited"
+ m_pLimited = FindChildByName( "LimitedLarge" );
+ if ( !m_pLimited )
+ {
+ m_pLimited = FindChildByName( "Limited" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ if ( m_pPrice )
+ {
+ int contentWidth, contentHeight;
+ m_pPrice->GetContentSize( contentWidth, contentHeight );
+ int iTextInsetX, iTextInsetY;
+ m_pPrice->GetTextInset( &iTextInsetX, &iTextInsetY );
+ m_pPrice->SetWide( contentWidth + iTextInsetX );
+ m_pPrice->SetPos( GetWide() - m_pPrice->GetWide(), GetTall() - m_pPrice->GetTall() );
+ }
+
+ if ( m_pPrice && m_pDiscount && m_pOGPrice )
+ {
+ int contentWidth, contentHeight;
+ m_pDiscount->GetContentSize( contentWidth, contentHeight );
+ int iTextInsetX, iTextInsetY;
+ m_pDiscount->GetTextInset( &iTextInsetX, &iTextInsetY );
+ m_pDiscount->SetWide( contentWidth + iTextInsetX );
+ m_pDiscount->SetPos( 0, GetTall() - m_pDiscount->GetTall() );
+
+ // Place original price in bottom-right corner, above the price label
+ int aPricePos[2];
+ m_pPrice->GetPos( aPricePos[0], aPricePos[1] );
+ m_pOGPrice->SetWide( GetWide() );
+ m_pOGPrice->GetContentSize( contentWidth, contentHeight );
+ int aOGPricePos[2] = { 0, aPricePos[1] - contentHeight };
+ m_pOGPrice->SetPos( aOGPricePos[0], aOGPricePos[1] );
+
+ // Place crossout over original price, halfway down from its vertical starting position
+ m_pCrossout->SetBounds(
+ aOGPricePos[0] + m_pOGPrice->GetWide() - contentWidth,
+ aOGPricePos[1] + contentHeight/2, contentWidth, m_pCrossout->GetTall()
+ );
+ }
+
+ if ( m_pNew )
+ {
+ int contentWidth, contentHeight;
+ m_pNew->GetContentSize( contentWidth, contentHeight );
+ int iTextInsetX, iTextInsetY;
+ m_pNew->GetTextInset( &iTextInsetX, &iTextInsetY );
+ m_pNew->SetWide( contentWidth + iTextInsetX );
+ int iPosX, iPosY;
+ m_pNew->GetPos( iPosX, iPosY );
+ m_pNew->SetPos( GetWide() - m_pNew->GetWide(), iPosY );
+ }
+
+ if ( m_pHighlighted )
+ {
+ int contentWidth, contentHeight;
+ m_pHighlighted->GetContentSize( contentWidth, contentHeight );
+ int iTextInsetX, iTextInsetY;
+ m_pHighlighted->GetTextInset( &iTextInsetX, &iTextInsetY );
+ m_pHighlighted->SetWide( contentWidth + iTextInsetX );
+ int iPosX, iPosY;
+ m_pHighlighted->GetPos( iPosX, iPosY );
+ m_pHighlighted->SetPos( GetWide() - m_pHighlighted->GetWide(), iPosY );
+ }
+
+ if ( m_pSale )
+ {
+ int contentWidth, contentHeight;
+ m_pSale->GetContentSize( contentWidth, contentHeight );
+ int iTextInsetX, iTextInsetY;
+ m_pSale->GetTextInset( &iTextInsetX, &iTextInsetY );
+ m_pSale->SetWide( contentWidth + iTextInsetX );
+ int iPosX, iPosY;
+ m_pSale->GetPos( iPosX, iPosY );
+ m_pSale->SetPos( GetWide() - m_pSale->GetWide(), iPosY );
+ }
+
+ if ( m_pLimited )
+ {
+ int iPosX, iPosY;
+ Panel *pRefPanel = ( m_pSale && m_pSale->IsVisible() ) ? m_pSale : ( m_pNew && m_pNew->IsVisible() ) ? m_pNew : NULL;
+ if ( pRefPanel && pRefPanel->IsVisible() )
+ {
+ pRefPanel->GetPos( iPosX, iPosY );
+ m_pLimited->SetPos( GetWide() - m_pLimited->GetWide() - XRES( 3 ), iPosY + pRefPanel->GetTall() + YRES( 3 ) );
+ }
+ else
+ {
+ m_pLimited->GetPos( iPosX, iPosY );
+ m_pLimited->SetPos( GetWide() - m_pLimited->GetWide() - XRES( 3 ), iPosY );
+ }
+ }
+
+ if ( m_pSaleBorder )
+ {
+ m_pSaleBorder->SetSize( GetWide(), GetTall() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel::SetPriceText( int iPrice, const char *pVariable, const econ_store_entry_t *pEntry )
+{
+ if ( iPrice == 0 )
+ {
+ if ( pEntry->m_bIsMarketItem )
+ {
+ SetDialogVariable( pVariable, g_pVGuiLocalize->Find( "#Store_Market" ) );
+ }
+ else
+ {
+ SetDialogVariable( pVariable, "" );
+ }
+ return;
+ }
+
+ wchar_t wzLocalizedPrice[ kLocalizedPriceSizeInChararacters ];
+ MakeMoneyString( wzLocalizedPrice, ARRAYSIZE( wzLocalizedPrice ), iPrice, EconUI()->GetStorePanel()->GetCurrency() );
+
+ if ( pEntry->m_bIsMarketItem )
+ {
+ wchar_t wzMarketString[96];
+ g_pVGuiLocalize->ConstructString_safe(
+ wzMarketString,
+ LOCCHAR( "%s1 %s2" ),
+ 2,
+ g_pVGuiLocalize->Find( "#Store_Market" ),
+ wzLocalizedPrice );
+
+ SetDialogVariable( pVariable, wzMarketString );
+ }
+ else
+ {
+ SetDialogVariable( pVariable, wzLocalizedPrice );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static bool IsItemPreviewed( const econ_store_entry_t *pEntry, ECurrency eCurrency )
+{
+ return (pEntry->GetItemDefinitionIndex() == InventoryManager()->GetLocalInventory()->GetPreviewItemDef())
+ && !pEntry->IsOnSale( eCurrency );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void AddItemToCartHelper( const char *pszContext, const econ_store_entry_t *pEntry, ECartItemType eSelectedCartItemType )
+{
+ Assert( pEntry );
+
+ CStoreCart *pCart = EconUI()->GetStorePanel()->GetCart();
+ const ECurrency eCurrency = EconUI()->GetStorePanel()->GetCurrency();
+
+ // If this is the item we've previewing *and* it's the first one we've added
+ // to the cart then we note that it's a preview item purchase and so we may
+ // get a discount.
+ ECartItemType eCartItemType = eSelectedCartItemType == kCartItem_Purchase && IsItemPreviewed( pEntry, eCurrency ) && !pCart->ContainsItemDefinition( pEntry->GetItemDefinitionIndex() )
+ ? kCartItem_TryOutUpgrade
+ : eSelectedCartItemType;
+
+ pCart->AddToCart( pEntry, pszContext, eCartItemType );
+ EconUI()->GetStorePanel()->OnAddToCart();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void AddItemToCartHelper( const char *pszContext, item_definition_index_t unItemDef, ECartItemType eSelectedCartItemType )
+{
+ const econ_store_entry_t *pEntry = EconUI()->GetStorePanel()->GetPriceSheet()->GetEntry( unItemDef );
+ if ( pEntry )
+ {
+ AddItemToCartHelper( pszContext, pEntry, eSelectedCartItemType );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel::SetItem( const econ_store_entry_t *pEntry )
+{
+ const ECurrency eCurrency = EconUI()->GetStorePanel()->GetCurrency();
+
+ item_price_t unPrice = pEntry->GetCurrentPrice( eCurrency );
+ SetPriceText( unPrice, "price", pEntry );
+
+ const bool bIsItemPreviewed = IsItemPreviewed( pEntry, eCurrency );
+
+ if ( bIsItemPreviewed )
+ {
+ // Make sure we're doing the math we think we're doing -- the item isn't on sale and so
+ // we'll be setting a new price based on the base price.
+ Assert( pEntry->GetCurrentPrice( eCurrency ) == pEntry->GetBasePrice( eCurrency ) );
+ Assert( unPrice == pEntry->GetBasePrice( eCurrency ) );
+
+ // Apply the preview period discount.
+ unPrice *= EconUI()->GetStorePanel()->GetPriceSheet()->GetPreviewPeriodDiscount();
+ }
+
+ item_price_t unBasePrice;
+ const bool bIsDiscounted = pEntry->HasDiscount( eCurrency, &unBasePrice );
+
+ if ( m_pDiscount && m_pOGPrice )
+ {
+ // and discount
+ if ( bIsDiscounted == false )
+ {
+ m_pDiscount->SetVisible( false );
+ m_pOGPrice->SetVisible( false );
+ }
+ else
+ {
+ SetPriceText( unBasePrice, "og_price", pEntry );
+
+ // set the discount and size
+ float flDiscountPercentage = 1.0f - ( float(unPrice) / float(unBasePrice) );
+ wchar_t wszDiscount[16];
+ _snwprintf( wszDiscount, ARRAYSIZE( wszDiscount ), L"-%.0f%%", flDiscountPercentage * 100.0f );
+ m_pDiscount->SetText( wszDiscount );
+
+ m_pDiscount->SetVisible( true );
+ m_pOGPrice->SetVisible( true );
+ }
+ }
+
+ if ( m_pCrossout && m_pOGPrice )
+ {
+ m_pCrossout->SetVisible( bIsDiscounted );
+ }
+
+ if ( m_pNew )
+ {
+ m_pNew->SetVisible( pEntry->m_bNew );
+ }
+
+ if ( m_pHighlighted )
+ {
+ m_pHighlighted->SetVisible( pEntry->m_bHighlighted );
+
+
+ }
+
+ if ( m_pSale )
+ {
+ bool bSaleVisible = false;
+
+ // We don't check explicitly for "is on sale" here because other things like item previews can
+ // adjust the price we're going to display to the user without adjusting the actual store entry.
+ if ( unPrice != pEntry->GetBasePrice( eCurrency ) && ( m_pNew == NULL || !m_pNew->IsVisible() ) )
+ {
+ if ( bIsItemPreviewed )
+ {
+ m_pSale->SetText( "#TF_PreviewDiscount" );
+ }
+
+ m_pSale->SetVisible( true );
+ bSaleVisible = true;
+ }
+ else
+ {
+ m_pSale->SetVisible( false );
+ }
+
+ if ( m_pSaleBorder )
+ {
+ m_pSaleBorder->SetVisible( !ShouldUseNewStore() && bSaleVisible );
+ }
+ }
+
+ if ( m_pLimited )
+ {
+ if ( pEntry->m_bLimited )
+ {
+ m_pLimited->SetVisible( true );
+ }
+ else
+ {
+ m_pLimited->SetVisible( false );
+ }
+ }
+
+ InvalidateLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel::OnStoreItemControlsPanelHover( KeyValues *data )
+{
+ // We don't care if there's no discount label to deal with
+ if ( !m_pDiscount )
+ return;
+
+ // Should the discount label be visible?
+ const econ_store_entry_t *pEntry = (const econ_store_entry_t *)data->GetPtr( "entry" );
+ if ( !pEntry )
+ return;
+
+ ECurrency eCurrency = EconUI()->GetStorePanel()->GetCurrency();
+ if ( !pEntry->HasDiscount( eCurrency, NULL ) )
+ return;
+
+ bool bEntered = data->GetInt( "entered" ) == 1;
+ m_pDiscount->SetVisible( !bEntered );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePage::CStorePage(Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData, const char *pPreviewItemResFile ) : vgui::PropertyPage(parent, "StorePage")
+{
+ m_pPageData = pPageData;
+
+ m_pItemModelPanelKVs = NULL;
+ m_pModelPanelLabelsKVs = NULL;
+ m_pCartModelPanelKVs = NULL;
+ m_pCartQuantityLabelKVs = NULL;
+
+ m_pFeaturedItemPanel = NULL;
+
+ m_pItemBackdropPanel = new EditablePanel( this, "ItemBackdrop" );
+ m_pMouseOverItemPanel = new CItemModelPanel( this, "mouseoveritempanel" );
+ m_pMouseOverTooltip = new CItemModelPanelToolTip( this );
+ m_pMouseOverTooltip->SetupPanels( this, m_pMouseOverItemPanel );
+ m_pMouseOverTooltip->SetPositioningStrategy( IPTTP_BOTTOM_SIDE );
+
+ if ( IsHomePage() )
+ {
+ if ( !ShouldUseNewStore() )
+ {
+ m_pFeaturedItemPanel = new CItemModelPanel( this, "featured_item_panel" );
+ m_pFeaturedItemPanel->SetActAsButton( true, true );
+ m_pFeaturedItemPanel->SetTooltip( m_pMouseOverTooltip, "" );
+ }
+
+ m_pFilterComboBox = NULL;
+ }
+ else
+ {
+ m_pFilterComboBox = new vgui::ComboBox( this, "ClassFilterComboBox", 11, false );
+ m_pFilterComboBox->SetVisible( false );
+ m_pFilterComboBox->AddActionSignalTarget( this );
+ }
+
+ m_pPreviewItemResFile = pPreviewItemResFile;
+ m_pPreviewPanel = NULL;
+ m_pSelectedPanel = NULL;
+ m_pNextPageButton = NULL;
+ m_pPrevPageButton = NULL;
+ m_pCheckoutButton = NULL;
+ m_pPreviewItemButton = NULL;
+ m_pAddToCartButtonPanel = NULL;
+ m_iCurrentFilter = 0;
+ m_pCartButton = NULL;
+ m_pBackpackLabel = NULL;
+ m_iSelectedItemDef = 0;
+ m_iSelectDefOnPageShow = 0;
+ m_iSelectPageOnPageShow = 0;
+ m_iOldSelectedItemDef = 0;
+ m_bShouldDeletePreviewPanel = false;
+ m_bFilterDirty = true;
+
+ ListenForGameEvent( "cart_updated" );
+
+ REGISTER_COLOR_AS_OVERRIDABLE( m_colItemPanelBG, "item_panel_bgcolor" );
+ REGISTER_COLOR_AS_OVERRIDABLE( m_colItemPanelBGMouseover, "item_panel_bgcolor_mouseover" );
+ REGISTER_COLOR_AS_OVERRIDABLE( m_colItemPanelBGSelected, "item_panel_bgcolor_selected" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePage::~CStorePage()
+{
+ if ( m_pItemModelPanelKVs )
+ {
+ m_pItemModelPanelKVs->deleteThis();
+ m_pItemModelPanelKVs = NULL;
+ }
+ if ( m_pCartModelPanelKVs )
+ {
+ m_pCartModelPanelKVs->deleteThis();
+ m_pCartModelPanelKVs = NULL;
+ }
+ if ( m_pCartQuantityLabelKVs )
+ {
+ m_pCartQuantityLabelKVs->deleteThis();
+ m_pCartQuantityLabelKVs = NULL;
+ }
+ if ( m_pModelPanelLabelsKVs )
+ {
+ m_pModelPanelLabelsKVs->deleteThis();
+ m_pModelPanelLabelsKVs = NULL;
+ }
+ if ( m_bShouldDeletePreviewPanel && m_pPreviewPanel )
+ {
+ delete m_pPreviewPanel;
+ m_pPreviewPanel = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::OnPostCreate()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CStorePage::GetPageResFile( void )
+{
+ return m_pPageData->m_pchPageRes;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePreviewItemPanel *CStorePage::CreatePreviewPanel( void )
+{
+ return new CStorePreviewItemPanel( this, m_pPreviewItemResFile, "storepreviewitem", this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ // First time through, create our preview panel
+ if ( ( ShouldUseNewStore() || !IsHomePage() ) && !m_pPreviewPanel )
+ {
+ m_pPreviewPanel = CreatePreviewPanel();
+
+ // Force it to load it's scheme now, because it needs to be done before we set it's visibility below
+ m_pPreviewPanel->InvalidateLayout( false, true );
+ m_pPreviewPanel->SetVisible( false );
+ }
+
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ KeyValues *pConditions = NULL;
+ #ifdef TF_CLIENT_DLL
+ const char *pszHoliday = UTIL_GetActiveHolidayString();
+ if ( pszHoliday && pszHoliday[0] )
+ {
+ pConditions = new KeyValues( "conditions" );
+
+ char szCondition[64];
+ Q_snprintf( szCondition, sizeof( szCondition ), "if_%s", pszHoliday );
+ AddSubKeyNamed( pConditions, szCondition );
+ }
+#endif
+
+ LoadControlSettings( GetPageResFile(), NULL, NULL, pConditions );
+
+ if ( pConditions )
+ {
+ pConditions->deleteThis();
+ }
+
+ m_bReapplyItemKVs = true;
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ SetBorderForItem( m_vecItemPanels[i].m_pItemModelPanel, false );
+ }
+
+ m_pMouseOverItemPanel->SetBorder( pScheme->GetBorder("LoadoutItemPopupBorder") );
+
+ m_pNextPageButton = dynamic_cast<CExButton*>( FindChildByName("NextPageButton") );
+ m_pPrevPageButton = dynamic_cast<CExButton*>( FindChildByName("PrevPageButton") );
+ m_pCheckoutButton = dynamic_cast<CExButton*>( FindChildByName("CheckoutButton") );
+ m_pPreviewItemButton = dynamic_cast<CExButton*>( FindChildByName("PreviewItemButton") );
+ m_pAddToCartButtonPanel = dynamic_cast<vgui::EditablePanel*>( FindChildByName("AddToCartButton") );
+ if ( m_pAddToCartButtonPanel )
+ {
+ CExButton *pButton = dynamic_cast<CExButton*>( m_pAddToCartButtonPanel->FindChildByName("SubButton") );
+ if ( pButton )
+ {
+ pButton->AddActionSignalTarget( GetVPanel() );
+ }
+ }
+ m_pCurPageLabel = dynamic_cast<vgui::Label*>( FindChildByName("CurPageLabel") );
+ m_pCartButton = dynamic_cast<CExButton*>( FindChildByName("CartButton") );
+ m_pBackpackLabel = dynamic_cast<vgui::Label*>( FindChildByName("BackpackSpaceLabel") );
+ if ( m_pBackpackLabel )
+ {
+ m_colBackpackOrg = m_pBackpackLabel->GetFgColor();
+ }
+
+ m_pItemDetailsButtonPanel = dynamic_cast<vgui::EditablePanel*>( FindChildByName("ItemDetailsButton") );
+ if ( m_pItemDetailsButtonPanel )
+ {
+ CExButton *pButton = dynamic_cast<CExButton*>( m_pItemDetailsButtonPanel->FindChildByName("SubButton") );
+ if ( pButton )
+ {
+ pButton->AddActionSignalTarget( GetVPanel() );
+ }
+ }
+ m_pItemPreviewButtonPanel = dynamic_cast<vgui::EditablePanel*>( FindChildByName("ItemPreviewButton") );
+ if ( m_pItemPreviewButtonPanel )
+ {
+ CExButton *pButton = dynamic_cast<CExButton*>( m_pItemPreviewButtonPanel->FindChildByName("SubButton") );
+ if ( pButton )
+ {
+ pButton->AddActionSignalTarget( GetVPanel() );
+ }
+ }
+
+ m_pCartFeaturedItemImage = dynamic_cast<vgui::ImagePanel*>( FindChildByName("CartFeaturedItemSymbol") );
+ if ( m_pCartFeaturedItemImage )
+ {
+ m_pCartFeaturedItemImage->SetMouseInputEnabled( false );
+ m_pCartFeaturedItemImage->SetKeyBoardInputEnabled( false );
+ }
+
+ vgui::Panel *pPanel = FindChildByName("CartImage");
+ if ( pPanel )
+ {
+ pPanel->SetMouseInputEnabled( false );
+ pPanel->SetKeyBoardInputEnabled( false );
+ }
+ pPanel = FindChildByName("FeaturedItemSymbol");
+ if ( pPanel )
+ {
+ pPanel->SetMouseInputEnabled( false );
+ pPanel->SetKeyBoardInputEnabled( false );
+ }
+ pPanel = FindChildByName("FeaturedItemLabel");
+ if ( pPanel )
+ {
+ pPanel->SetMouseInputEnabled( false );
+ pPanel->SetKeyBoardInputEnabled( false );
+ }
+ pPanel = FindChildByName("FeaturedItemPrice");
+ if ( pPanel )
+ {
+ pPanel->SetMouseInputEnabled( false );
+ pPanel->SetKeyBoardInputEnabled( false );
+ }
+
+ if ( m_pFilterComboBox )
+ {
+ vgui::HFont hFont = pScheme->GetFont( "HudFontSmallestBold", true );
+ m_pFilterComboBox->SetFont( hFont );
+ UpdateFilteredItems();
+ UpdateFilterComboBox();
+
+ // Move to "All items" selected
+ m_pFilterComboBox->SilentActivateItemByRow( 0 );
+ }
+
+ if ( m_pItemBackdropPanel )
+ {
+ m_pItemBackdropPanel->SetBgColor( m_colItemBackdropPanel );
+ }
+
+ SetDetailsVisible( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ m_colItemBackdropPanel = inResourceData->GetColor( "item_backdrop_color" );
+
+ KeyValues *pItemKV = inResourceData->FindKey( "modelpanels_kv" );
+ if ( pItemKV )
+ {
+ if ( m_pItemModelPanelKVs )
+ {
+ m_pItemModelPanelKVs->deleteThis();
+ }
+ m_pItemModelPanelKVs = new KeyValues("modelpanels_kv");
+ pItemKV->CopySubkeys( m_pItemModelPanelKVs );
+ }
+
+ pItemKV = inResourceData->FindKey( "modelpanel_labels_kv" );
+ if ( pItemKV )
+ {
+ if ( m_pModelPanelLabelsKVs )
+ {
+ m_pModelPanelLabelsKVs->deleteThis();
+ }
+ m_pModelPanelLabelsKVs = new KeyValues("modelpanel_labels_kv");
+ pItemKV->CopySubkeys( m_pModelPanelLabelsKVs );
+ }
+
+ pItemKV = inResourceData->FindKey( "cart_modelpanels_kv" );
+ if ( pItemKV )
+ {
+ if ( m_pCartModelPanelKVs )
+ {
+ m_pCartModelPanelKVs->deleteThis();
+ }
+ m_pCartModelPanelKVs = new KeyValues("cart_modelpanels_kv");
+ pItemKV->CopySubkeys( m_pCartModelPanelKVs );
+ }
+
+ pItemKV = inResourceData->FindKey( "cart_labels_kv" );
+ if ( pItemKV )
+ {
+ if ( m_pCartQuantityLabelKVs )
+ {
+ m_pCartQuantityLabelKVs->deleteThis();
+ }
+ m_pCartQuantityLabelKVs = new KeyValues("cart_labels_kv");
+ pItemKV->CopySubkeys( m_pCartQuantityLabelKVs );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::PerformLayout( void )
+{
+ if ( m_bReapplyItemKVs )
+ {
+ m_bReapplyItemKVs = false;
+
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ if ( m_pItemModelPanelKVs )
+ {
+ m_vecItemPanels[i].m_pItemModelPanel->ApplySettings( m_pItemModelPanelKVs );
+ SetBorderForItem( m_vecItemPanels[i].m_pItemModelPanel, false );
+ m_vecItemPanels[i].m_pItemModelPanel->InvalidateLayout();
+ }
+ m_vecItemPanels[i].m_pStorePricePanel->InvalidateLayout();
+ }
+
+ if ( m_pCartModelPanelKVs )
+ {
+ FOR_EACH_VEC( m_pCartModelPanels, i )
+ {
+ m_pCartModelPanels[i]->ApplySettings( m_pCartModelPanelKVs );
+ SetBorderForItem( m_pCartModelPanels[i], false );
+ m_pCartModelPanels[i]->InvalidateLayout();
+ }
+ }
+
+ if ( m_pCartQuantityLabelKVs )
+ {
+ FOR_EACH_VEC( m_pCartQuantityLabels, i )
+ {
+ m_pCartQuantityLabels[i]->ApplySettings( m_pCartQuantityLabelKVs );
+ m_pCartQuantityLabels[i]->InvalidateLayout();
+ }
+ }
+
+ if ( m_pModelPanelLabelsKVs )
+ {
+ FOR_EACH_VEC( m_pCartQuantityLabels, i )
+ {
+ m_pCartQuantityLabels[i]->ApplySettings( m_pModelPanelLabelsKVs );
+ m_pCartQuantityLabels[i]->InvalidateLayout();
+ }
+ }
+ }
+
+ BaseClass::PerformLayout();
+
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ CItemModelPanel *pItemModelPanel = m_vecItemPanels[i].m_pItemModelPanel;
+ CStorePricePanel *pItemPricePanel = m_vecItemPanels[i].m_pStorePricePanel;
+ CStoreItemControlsPanel *pItemControlsPanel = m_vecItemPanels[i].m_pItemControlsPanel;
+ pItemModelPanel->SetVisible( true );
+ pItemModelPanel->SetNoItemText( "#SelectNoItemSlot" );
+
+ PositionItemPanel(pItemModelPanel, i );
+
+ int iX,iY,iW,iH;
+ pItemModelPanel->GetBounds( iX, iY, iW, iH );
+ // Position our price label and controls
+ pItemPricePanel->SetVisible( pItemModelPanel->HasItem() );
+ pItemPricePanel->SetBounds( iX, iY, iW, iH );
+
+ pItemPricePanel->InvalidateLayout( true );
+
+ pItemControlsPanel->SetPos( iX + m_iItemControlsXOffset, iY + iH - pItemControlsPanel->GetTall() - m_iItemControlsYOffset );
+ }
+
+ if ( m_pItemBackdropPanel && m_vecItemPanels.Count() >= 2 )
+ {
+ CItemModelPanel *pTopLeftPanel = m_vecItemPanels.Head().m_pItemModelPanel;
+ CItemModelPanel *pBottomRightPanel = m_vecItemPanels.Tail().m_pItemModelPanel;
+
+ int aItemBackdropBounds[4];
+ if ( pTopLeftPanel && pBottomRightPanel )
+ {
+ int nX, nY;
+ pTopLeftPanel->GetPos( nX, nY );
+
+ aItemBackdropBounds[0] = nX - m_iItemBackdropLeftMargin;
+ aItemBackdropBounds[1] = nY - m_iItemBackdropTopMargin;
+
+ pBottomRightPanel->GetPos( nX, nY );
+ aItemBackdropBounds[2] = nX + pBottomRightPanel->GetWide() + m_iItemBackdropRightMargin - aItemBackdropBounds[0];
+ aItemBackdropBounds[3] = nY + pBottomRightPanel->GetTall() + m_iItemBackdropBottomMargin - aItemBackdropBounds[1];
+
+ m_pItemBackdropPanel->SetBounds( aItemBackdropBounds[0], aItemBackdropBounds[1], aItemBackdropBounds[2], aItemBackdropBounds[3] );
+
+ m_pItemBackdropPanel->SetPaintBackgroundType( m_iItemBackdropPaintBackgroundType );
+ m_pItemBackdropPanel->SetZPos( m_iItemBackdropZPos );
+ }
+ }
+
+ if ( m_pCartModelPanels.Count() > 0 )
+ {
+ bool bFeaturedImagePanelVisible = false;
+ CStoreCart *pCart = EconUI()->GetStorePanel()->GetCart();
+
+ int iCartX, iCartY;
+ m_pCartButton->GetPos( iCartX, iCartY );
+ int iCartModelWide = m_pCartModelPanels[0]->GetWide();
+ FOR_EACH_VEC( m_pCartModelPanels, i )
+ {
+ if ( m_pCartModelPanels[i]->HasItem() )
+ {
+ m_pCartModelPanels[i]->SetVisible( true );
+ m_pCartQuantityLabels[i]->SetVisible( true );
+
+ int iX = iCartX + m_pCartButton->GetWide() + (XRES(4) * (i+1)) + (iCartModelWide * i);
+ m_pCartModelPanels[i]->SetPos( iX, iCartY );
+ int iY = iCartY + m_pCartModelPanels[i]->GetTall() - m_pCartQuantityLabels[i]->GetTall();
+ m_pCartQuantityLabels[i]->SetPos( iX + iCartModelWide - m_pCartQuantityLabels[i]->GetWide(), iY );
+
+ // If we're the featured item, show it
+ cart_item_t *pCartItem = pCart->GetItem(i);
+ if ( pCartItem && ( pCartItem->pEntry == EconUI()->GetStorePanel()->GetFeaturedEntry() ) )
+ {
+ bFeaturedImagePanelVisible = true;
+
+ if ( m_pCartFeaturedItemImage )
+ {
+ m_pCartFeaturedItemImage->SetPos( iX - XRES(4), iY - YRES(10) );
+ }
+ }
+ }
+ }
+
+ if ( m_pCartFeaturedItemImage && m_pCartFeaturedItemImage->IsVisible() != bFeaturedImagePanelVisible )
+ {
+ m_pCartFeaturedItemImage->SetVisible( bFeaturedImagePanelVisible );
+ }
+ }
+
+ if ( m_pCurPageLabel )
+ {
+ bool bMultiplePages = (GetNumPages() > 1);
+ m_pCurPageLabel->SetVisible( bMultiplePages );
+ m_pNextPageButton->SetVisible( bMultiplePages );
+ m_pPrevPageButton->SetVisible( bMultiplePages );
+ if ( bMultiplePages )
+ {
+ m_pNextPageButton->SetEnabled( m_iCurrentPage < (GetNumPages()-1) );
+ m_pPrevPageButton->SetEnabled( m_iCurrentPage > 0 );
+ }
+ }
+
+ if ( IsHomePage() )
+ {
+ const store_promotion_spend_for_free_item_t *pPromotion = EconUI()->GetStorePanel()->GetPriceSheet()->GetStorePromotion_SpendForFreeItem();
+ wchar_t wszText[1024];
+ wchar_t wszPriceThreshold[ kLocalizedPriceSizeInChararacters ];
+ ECurrency eCurrency = EconUI()->GetStorePanel()->GetCurrency();
+ AssertMsg( eCurrency >= k_ECurrencyUSD && eCurrency < k_ECurrencyMax, "Invalid currency!" );
+
+ int iPriceThreshold = pPromotion->m_rgusPriceThreshold[ eCurrency ];
+ MakeMoneyString( wszPriceThreshold, ARRAYSIZE( wszPriceThreshold ), iPriceThreshold, EconUI()->GetStorePanel()->GetCurrency() );
+ bool bIsFreeTrial = false;
+#ifdef TF_CLIENT_DLL
+ bIsFreeTrial = IsFreeTrialAccount();
+#endif
+ const char *pszLocString = bIsFreeTrial ? "#Store_FreeTrial_BonusText" : "#Store_Promotion_SpendForGift";
+ const char *pszElementName = bIsFreeTrial ? "BonusTextLabel" : "PromotionLabel_BonusItem";
+
+ g_pVGuiLocalize->ConstructString_safe( wszText, g_pVGuiLocalize->Find( pszLocString ), 1, wszPriceThreshold );
+ CExLabel *pPromotionText = dynamic_cast< CExLabel* >( FindChildByName( pszElementName, true ) );
+ if ( pPromotionText )
+ {
+ pPromotionText->SetText( wszText );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::PositionItemPanel( CItemModelPanel *pPanel, int iIndex )
+{
+ CItemModelPanel *pRealPanel = m_vecItemPanels[iIndex].m_pItemModelPanel;
+
+ int iOffsetIndex = iIndex;
+ int iYPosOffset = 0;
+ int iCenter = GetWide() * 0.5;
+ int iButtonX = (iOffsetIndex % GetNumColumns());
+ int iButtonY = (iOffsetIndex / GetNumColumns());
+ int iXPos = m_iItemXPos + (iCenter + m_iItemOffcenterX) + (iButtonX * pRealPanel->GetWide()) + (m_iItemXDelta * iButtonX);
+ int iYPos = m_iItemYPos + (iButtonY * pRealPanel->GetTall() ) + (m_iItemYDelta * iButtonY) + iYPosOffset;
+
+ pRealPanel->SetPos( iXPos, iYPos );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::OnPageShow( void )
+{
+ m_iCurrentPage = m_iSelectPageOnPageShow;
+ m_iSelectPageOnPageShow = 0;
+
+ // !KLUDGE!
+ SetDetailsVisible( !ShouldUseNewStore() );
+
+ m_bReapplyItemKVs = true;
+ BaseClass::OnPageShow();
+
+ if ( !IsHomePage() )
+ {
+ EconUI()->Gamestats_Store( IE_STORE_TAB_CHANGED, NULL, GetPageName() );
+ }
+
+ m_pMouseOverItemPanel->SetVisible( false );
+
+ CreateItemPanels();
+
+ if ( m_pFilterComboBox )
+ {
+ SetFilter( 0 );
+ m_pFilterComboBox->SilentActivateItemByRow( 0 );
+// m_pFilterComboBox->SetVisible( !IsHomePage() );
+ }
+
+ // Setup sort by newest
+ if ( m_pPageData && !ShouldUseNewStore() )
+ {
+ eEconStoreSortType iSortType = kEconStoreSortType_DateNewest;
+ 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 );
+
+ UpdateFilteredItems();
+ }
+
+ UpdateModelPanels();
+
+ if ( m_pCheckoutButton )
+ {
+ m_pCheckoutButton->RequestFocus();
+ }
+
+ if ( m_iSelectDefOnPageShow )
+ {
+ m_iSelectDefOnPageShow = 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePricePanel* CStorePage::CreatePricePanel( int iIndex )
+{
+ if ( m_pPageData && !Q_strcmp( m_pPageData->m_pchPageClass, "CStorePage_Popular" ) )
+ return vgui::SETUP_PANEL( new CStorePricePanel_Popular( this, "StorePrice", iIndex + 1 ) );
+
+ if ( m_pPageData && !Q_strcmp( m_pPageData->m_pchPageClass, "CStorePage_New" ) )
+ return vgui::SETUP_PANEL( new CStorePricePanel_New( this, "StorePrice" ) );
+
+ if ( m_pPageData && !Q_strcmp( m_pPageData->m_pchPageClass, "CStorePage_Bundles" ) )
+ return vgui::SETUP_PANEL( new CStorePricePanel_Bundles( this, "StorePrice" ) );
+
+ return vgui::SETUP_PANEL( new CStorePricePanel( this, "StorePrice" ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::OrderItemsForDisplay( CUtlVector<const econ_store_entry_t *>& vecItems ) const
+{
+ /*
+ // See how I tread upon all the holy concepts of OOP.
+ if ( m_pPageData &&
+ !Q_strcmp( m_pPageData->m_pchPageClass, "CStorePage_Bundles" ) &&
+ !ShouldUseNewStore() )
+ {
+ vecItems.Sort( &ItemDisplayOrderSort_UseSortOverride );
+ }
+ */
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::CreateItemPanels( void )
+{
+ int iNumPanels = GetNumItemPanels();
+ if ( m_pPageData && m_vecItemPanels.Count() < iNumPanels )
+ {
+ for ( int i = m_vecItemPanels.Count(); i < iNumPanels; i++ )
+ {
+ int idx = m_vecItemPanels.AddToTail();
+ item_panel &itempanel = m_vecItemPanels[idx];
+ CItemModelPanel *pPanel = vgui::SETUP_PANEL( new CItemModelPanel( this, VarArgs("modelpanel%d", i) ) );
+ pPanel->SetShowQuantity( true );
+ pPanel->SetActAsButton( true, true );
+ itempanel.m_pItemModelPanel = pPanel;
+
+ pPanel->SetTooltip( m_pMouseOverTooltip, "" );
+
+ // Create our price panel too
+ CStorePricePanel *pPricePanel = CreatePricePanel( i );
+ pPricePanel->SetMouseInputEnabled( false );
+ pPricePanel->SetKeyBoardInputEnabled( false );
+ itempanel.m_pStorePricePanel = pPricePanel;
+
+ // and controls
+ CStoreItemControlsPanel *pControlsPanel = vgui::SETUP_PANEL( new CStoreItemControlsPanel( this, "StoreItemControls", pPanel ) );
+ //pControlsPanel->AddActionSignalTarget( this );
+ if ( ShouldUseNewStore() )
+ {
+ pControlsPanel->SetMouseHoverHandler( pPricePanel );
+ }
+ itempanel.m_pItemControlsPanel = pControlsPanel;
+ }
+
+ m_EntryIndices.SetCountNonDestructively( m_vecItemPanels.Count() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::OnCommand( const char *command )
+{
+ if ( !Q_strnicmp( command, "nextpage", 8 ) )
+ {
+ if ( m_iCurrentPage < (GetNumPages()-1) )
+ {
+ m_iCurrentPage++;
+ UpdateModelPanels();
+ }
+ return;
+ }
+ else if ( !Q_strnicmp( command, "prevpage", 8 ) )
+ {
+ if ( m_iCurrentPage > 0 )
+ {
+ m_iCurrentPage--;
+ UpdateModelPanels();
+ }
+ return;
+ }
+ else if ( !Q_strnicmp( command, "preview_item", 12 ) )
+ {
+ PreviewSelectionItem();
+ return;
+ }
+ else if ( !Q_strnicmp( command, "addtocart", 9 ) )
+ {
+ AddSelectionToCart();
+ return;
+ }
+ else if ( !Q_strnicmp( command, "viewcart", 8 ) )
+ {
+ OpenStoreViewCartPanel();
+ return;
+ }
+ else if ( !Q_strnicmp( command, "startshopping", 8 ) )
+ {
+ PostMessage( EconUI()->GetStorePanel(), new KeyValues("StartShopping") );
+ return;
+ }
+ else if ( !Q_strnicmp( command, "checkout", 8 ) )
+ {
+ EconUI()->GetStorePanel()->InitiateCheckout( false );
+ return;
+ }
+ else if ( !Q_stricmp( command, "show_details" ) )
+ {
+ if ( m_pSelectedPanel )
+ {
+ CEconItemView *pItem = m_pSelectedPanel->GetItem();
+ if ( pItem )
+ {
+ SetDetailsVisible( true );
+ }
+ }
+ return;
+ }
+ else if ( !Q_stricmp( command, "show_preview" ) )
+ {
+ SetDetailsVisible( false );
+ return;
+ }
+ else if ( !Q_strnicmp( command, "marketplace", 8 ) )
+ {
+ if ( steamapicontext && steamapicontext->SteamFriends() )
+ {
+ steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( "http://steamcommunity.com/market/search?appid=440" );
+ }
+ return;
+ }
+ else if ( !Q_stricmp( command, "reloadscheme" ) )
+ {
+ InvalidateLayout( false, true );
+ SetVisible( true );
+ UpdateSelectionInfoPanel();
+ return;
+ }
+ else
+ {
+ engine->ClientCmd( const_cast<char *>( command ) );
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::FireGameEvent( IGameEvent *event )
+{
+ const char * type = event->GetName();
+
+ if ( Q_strcmp(type, "cart_updated") == 0 )
+ {
+ UpdateCart();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::OnMouseWheeled( int delta )
+{
+ if ( m_vecItemPanels.Count() == 0 )
+ {
+ // on home page, likely
+ return;
+ }
+
+ int oldSelectionIndex = -1;
+ int currentSelectionIndex = -1;
+
+ // deselect everything
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ if ( m_vecItemPanels[i].m_pItemModelPanel->IsSelected() )
+ {
+ oldSelectionIndex = i;
+ m_vecItemPanels[i].m_pItemModelPanel->SetSelected( false );
+ SetBorderForItem( m_vecItemPanels[i].m_pItemModelPanel, false );
+ }
+ }
+
+ // step selection ahead
+ if ( delta < 0 )
+ {
+ currentSelectionIndex = oldSelectionIndex+1;
+
+ if ( currentSelectionIndex >= m_vecItemPanels.Count() )
+ {
+ if ( m_iCurrentPage < (GetNumPages()-1) )
+ {
+ currentSelectionIndex = 0;
+ m_iCurrentPage++;
+ UpdateModelPanels();
+ }
+ else
+ {
+ currentSelectionIndex = m_vecItemPanels.Count();
+ }
+ }
+ else if ( !m_vecItemPanels[ currentSelectionIndex ].m_pItemModelPanel->HasItem() )
+ {
+ // don't move into empty slots
+ currentSelectionIndex = oldSelectionIndex;
+ }
+ }
+ else if ( delta > 0 )
+ {
+ currentSelectionIndex = oldSelectionIndex-1;
+
+ if ( currentSelectionIndex < 0 )
+ {
+ if ( m_iCurrentPage > 0 )
+ {
+ currentSelectionIndex = m_vecItemPanels.Count()-1;
+ m_iCurrentPage--;
+ UpdateModelPanels();
+ }
+ else
+ {
+ currentSelectionIndex = 0;
+ }
+ }
+ else if ( !m_vecItemPanels[ currentSelectionIndex ].m_pItemModelPanel->HasItem() )
+ {
+ // don't move into empty slots
+ currentSelectionIndex = oldSelectionIndex;
+ }
+ }
+ else
+ {
+ // no actual wheel movement
+ return;
+ }
+
+ // sanity check
+ currentSelectionIndex = clamp( currentSelectionIndex, 0, m_vecItemPanels.Count()-1 );
+
+ m_pSelectedPanel = m_vecItemPanels[ currentSelectionIndex ].m_pItemModelPanel;
+ m_pSelectedPanel->SetSelected( ShouldUseNewStore() );
+ SetBorderForItem( m_pSelectedPanel, false );
+ UpdateSelectionInfoPanel();
+
+ if ( currentSelectionIndex != oldSelectionIndex )
+ {
+ vgui::surface()->PlaySound( "ui/buttonclick.wav" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CStorePage::AssignItemToPanel( CItemModelPanel *pPanel, int iIndex )
+{
+ iIndex += (m_iCurrentPage * GetNumItemPanels());
+ if ( iIndex >= 0 && iIndex < m_FilteredEntries.Count() )
+ {
+ CEconItemView ItemData;
+ ItemData.Init( m_FilteredEntries[iIndex]->GetItemDefinitionIndex(), AE_UNIQUE, AE_USE_SCRIPT_VALUE, true );
+ ItemData.SetItemQuantity( m_FilteredEntries[iIndex]->GetQuantity() );
+ ItemData.SetClientItemFlags( kEconItemFlagClient_Preview | kEconItemFlagClient_StoreItem );
+ pPanel->SetItem( &ItemData );
+
+ return iIndex;
+ }
+
+ pPanel->SetItem( NULL );
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CStorePage::GetNumPages( void )
+{
+ return ceil( (float)m_FilteredEntries.Count() / (float)GetNumItemPanels() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+/* static */ int CStorePage::ItemDisplayOrderSort_UseSortOverride( const econ_store_entry_t *const *ppA, const econ_store_entry_t *const *ppB )
+{
+ static CSchemaAttributeDefHandle pAttribDef_StoreSortOverride( "store sort override" );
+
+ const GameItemDefinition_t *pDefA = ItemSystem()->GetStaticDataForItemByDefIndex( (*ppA)->GetItemDefinitionIndex() ),
+ *pDefB = ItemSystem()->GetStaticDataForItemByDefIndex( (*ppB)->GetItemDefinitionIndex() );
+
+ // We expect only items with valid definition indices to make it into the list to
+ // be sorted.
+ Assert( pDefA );
+ Assert( pDefB );
+
+ // Sort based on: our sort key if we have one; otherwise our definition index.
+ float flValue;
+ int unSortKeyA = ( pAttribDef_StoreSortOverride && FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pDefA, pAttribDef_StoreSortOverride, &flValue ) )
+ ? (int)flValue
+ : pDefA->GetDefinitionIndex(),
+ unSortKeyB = ( pAttribDef_StoreSortOverride && FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pDefB, pAttribDef_StoreSortOverride, &flValue ) )
+ ? (int)flValue
+ : pDefB->GetDefinitionIndex();
+
+ return unSortKeyA - unSortKeyB;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Update our internal list of entries based on our filters, and count items in each filter
+//-----------------------------------------------------------------------------
+void CStorePage::UpdateFilteredItems( void )
+{
+ if ( !m_bFilterDirty )
+ return;
+
+ m_FilteredEntries.Purge();
+ m_vecFilterCounts.SetCount( GetNumPrimaryFilters() );
+ if ( !m_vecFilterCounts.Count() )
+ return;
+
+ FOR_EACH_VEC( m_vecFilterCounts, i )
+ {
+ m_vecFilterCounts[i] = 0;
+ }
+
+ for ( int i = 0; i < m_pPageData->m_vecEntries.Count(); i++ )
+ {
+ const econ_store_entry_t *pEntry = EconUI()->GetStorePanel()->GetPriceSheet()->GetEntry( m_pPageData->m_vecEntries[i] );
+ GameItemDefinition_t *pDef = ItemSystem()->GetStaticDataForItemByDefIndex( pEntry->GetItemDefinitionIndex() );
+ if ( pDef )
+ {
+ // Get a list of applicable filters for the current item definition
+ CUtlVector<int> filterList;
+ GetFiltersForDef( pDef, &filterList );
+
+ bool bPassesClassFilter = false;
+
+ FOR_EACH_VEC( filterList, iFL )
+ {
+ int iFilter = filterList[iFL];
+
+ m_vecFilterCounts[iFilter]++;
+
+ if ( m_iCurrentFilter == iFilter )
+ {
+ bPassesClassFilter = true;
+ }
+ }
+
+ // If the item passes both filters, add it.
+ // NOTE: DoesEntryFilterPassSecondaryFilter() returns true by default.
+ if ( bPassesClassFilter && DoesEntryFilterPassSecondaryFilter( pEntry ) )
+ {
+ m_FilteredEntries.AddToTail( pEntry );
+ }
+ }
+ }
+
+ // Sort our full list of entries however this store page wants it.
+ OrderItemsForDisplay( m_FilteredEntries );
+
+ m_bFilterDirty = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::UpdateModelPanels( void )
+{
+ DeSelectAllItemPanels();
+ UpdateSelectionInfoPanel();
+ UpdateCart();
+
+ if ( m_pPageData != NULL )
+ {
+ UpdateFilteredItems();
+ UpdateFilterComboBox();
+
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ CItemModelPanel *pItemModelPanel = m_vecItemPanels[i].m_pItemModelPanel;
+ pItemModelPanel->SetShowEquipped( true );
+ m_EntryIndices[i] = AssignItemToPanel( pItemModelPanel, i );
+ SetBorderForItem( pItemModelPanel, false );
+
+ int iEntry = m_EntryIndices[i];
+ if ( iEntry >= 0 && iEntry < m_FilteredEntries.Count() )
+ {
+ // Set the price label
+ m_vecItemPanels[i].m_pStorePricePanel->SetItem( m_FilteredEntries[ iEntry ] );
+ m_vecItemPanels[i].m_pItemControlsPanel->SetItem( m_FilteredEntries[ iEntry ] );
+ }
+ }
+ }
+
+ char szTmp[16];
+ Q_snprintf(szTmp, 16, "%d/%d", m_iCurrentPage+1, GetNumPages() );
+ SetDialogVariable( "backpackpage", szTmp );
+
+ UpdateBackpackLabel();
+
+ // Now layout again to position our item buttons
+ InvalidateLayout();
+
+ if ( m_pFilterComboBox )
+ {
+ m_pFilterComboBox->GetComboButton()->SetFgColor( Color( 117,107,94,255 ) );
+ m_pFilterComboBox->GetComboButton()->SetDefaultColor( Color( 117,107,94,255), Color( 0,0,0,0) );
+ m_pFilterComboBox->GetComboButton()->SetArmedColor( Color( 117,107,94,255), Color( 0,0,0,0) );
+ m_pFilterComboBox->GetComboButton()->SetDepressedColor( Color( 117,107,94,255), Color( 0,0,0,0) );
+ }
+
+ // If we're not the home page, start with the first item selected already
+ if ( m_vecItemPanels.Count() )
+ {
+ ToggleSelectItemPanel( m_vecItemPanels[m_iSelectDefOnPageShow].m_pItemModelPanel );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::OnItemPanelMouseReleased( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+
+ if ( pItemPanel && IsVisible() && pItemPanel->HasItem() )
+ {
+ if ( IsHomePage() )
+ {
+ // On the homepage, they've clicked the featured item. Find it in a store tab and move to it.
+ PostMessage( EconUI()->GetStorePanel(), new KeyValues("FindAndSelectFeaturedItem") );
+ }
+ else if ( !pItemPanel->IsSelected() )
+ {
+ ToggleSelectItemPanel( pItemPanel );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::OnItemPanelMouseDoublePressed( vgui::Panel *panel )
+{
+ if ( IsHomePage() )
+ {
+ // On the homepage, they've clicked the featured item. Find it in a store tab and move to it.
+ PostMessage( EconUI()->GetStorePanel(), new KeyValues("FindAndSelectFeaturedItem") );
+ return;
+ }
+
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+
+ if ( pItemPanel && IsVisible() && pItemPanel->HasItem() )
+ {
+ // Make sure this panel is selected
+ if ( !pItemPanel->IsSelected() )
+ {
+ ToggleSelectItemPanel( pItemPanel );
+ }
+
+ // Double clicking on an item in the cart takes you to the view cart page
+ FOR_EACH_VEC( m_pCartModelPanels, i )
+ {
+ if ( m_pCartModelPanels[i] == pItemPanel )
+ {
+ OpenStoreViewCartPanel();
+ return;
+ }
+ }
+
+ // Not a cart panel, so add to cart.
+ OnCommand("addtocart");
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::DeSelectAllItemPanels( void )
+{
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ if ( m_vecItemPanels[i].m_pItemModelPanel->IsSelected() )
+ {
+ m_vecItemPanels[i].m_pItemModelPanel->SetSelected( false );
+ SetBorderForItem( m_vecItemPanels[i].m_pItemModelPanel, false );
+ }
+ }
+ FOR_EACH_VEC( m_pCartModelPanels, i )
+ {
+ if ( m_pCartModelPanels[i]->IsSelected() )
+ {
+ m_pCartModelPanels[i]->SetSelected( false );
+ SetBorderForItem( m_pCartModelPanels[i], false );
+ }
+ }
+
+ m_pSelectedPanel = NULL;
+ if ( m_pFeaturedItemPanel && m_pFeaturedItemPanel->IsSelected() )
+ {
+ m_pFeaturedItemPanel->SetSelected( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::ToggleSelectItemPanel( CItemModelPanel *pPanel )
+{
+ if ( ShouldUseNewStore() )
+ return;
+
+ if ( pPanel->IsSelected() || !pPanel->HasItem() )
+ {
+ pPanel->SetSelected( false );
+ m_pSelectedPanel = NULL;
+ }
+ else
+ {
+ DeSelectAllItemPanels();
+ pPanel->SetSelected( true );
+ m_pSelectedPanel = pPanel;
+ }
+ SetBorderForItem( pPanel, false );
+ UpdateSelectionInfoPanel();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::SelectItemPanel( CItemModelPanel *pPanel )
+{
+ DeSelectAllItemPanels();
+ pPanel->SetSelected( true );
+ m_pSelectedPanel = pPanel;
+ SetBorderForItem( pPanel, false );
+ UpdateSelectionInfoPanel();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::OnItemPanelEntered( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+
+ if ( pItemPanel && IsVisible() )
+ {
+ CEconItemView *pItem = pItemPanel->GetItem();
+ if ( !pItemPanel->IsSelected() )
+ {
+ SetBorderForItem( pItemPanel, pItem != NULL );
+ }
+ if ( pItemPanel->HasItem() )
+ {
+ // make related controls visible
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ item_panel &itempanel = m_vecItemPanels[i];
+ if ( itempanel.m_pItemModelPanel == pItemPanel )
+ {
+ itempanel.m_pItemControlsPanel->OnItemPanelEntered();
+ break;
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::OnItemPanelExited( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+
+ if ( pItemPanel && IsVisible() )
+ {
+ if ( !pItemPanel->IsSelected() )
+ {
+ SetBorderForItem( pItemPanel, false );
+ }
+ if ( pItemPanel->HasItem() )
+ {
+ // make related controls visible
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ item_panel &itempanel = m_vecItemPanels[i];
+ if ( itempanel.m_pItemModelPanel == pItemPanel )
+ {
+ itempanel.m_pItemControlsPanel->OnItemPanelExited();
+ break;
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::OnItemAddToCart( vgui::Panel *panel )
+{
+ CStoreItemControlsPanel *pControlsPanel = dynamic_cast< CStoreItemControlsPanel * >( panel );
+ if ( pControlsPanel )
+ {
+ const econ_store_entry_t *pEntry = pControlsPanel->GetItem();
+ if ( pEntry )
+ {
+ if ( !ShouldUseNewStore() )
+ {
+ SelectItemPanel( pControlsPanel->GetItemModelPanel() );
+ }
+ else
+ {
+#if defined( TF_CLIENT_DLL )
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( "store_add_to_cart", "minibutton" );
+#endif
+ }
+ AddItemToCartHelper( GetPageName(), pEntry, kCartItem_Purchase );
+ UpdateCart();
+ }
+
+ // Turn the free slots indicator red if we can't fit everything.
+ UpdateBackpackLabel();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::SetBorderForItem( CItemModelPanel *pItemPanel, bool bMouseOver )
+{
+ if ( !pItemPanel || pItemPanel == m_pFeaturedItemPanel )
+ return;
+
+ // Store panels use backgrounds instead of borders
+ pItemPanel->SetBorder( NULL );
+ pItemPanel->SetPaintBackgroundEnabled( true );
+
+ if ( pItemPanel->IsSelected() )
+ {
+ pItemPanel->SetBgColor( m_colItemPanelBGSelected );
+ }
+ else if ( bMouseOver )
+ {
+ pItemPanel->SetBgColor( m_colItemPanelBGMouseover );
+ }
+ else
+ {
+ pItemPanel->SetBgColor( m_colItemPanelBG );
+ }
+
+ const CEconStorePriceSheet *pPriceSheet = EconUI()->GetStorePanel()->GetPriceSheet();
+
+ if ( pItemPanel->GetItem() && pPriceSheet )
+ {
+ const econ_store_entry_t *pEntry = pPriceSheet->GetEntry( pItemPanel->GetItem()->GetItemDefIndex() );
+
+ if (pEntry && pEntry->m_bHighlighted && !bMouseOver )
+ {
+ pItemPanel->SetBorder( vgui::scheme()->GetIScheme( GetScheme() )->GetBorder( "StoreHighlightedBackgroundBorder" ) );
+ pItemPanel->SetPaintBorderEnabled( true );
+ pItemPanel->SetPaintBackgroundEnabled( false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::CalculateItemButtonPos( CItemModelPanel *pItemPanel, int x, int y, int *iXPos, int *iYPos )
+{
+ *iXPos = x;
+ *iYPos = (y + pItemPanel->GetTall() + YRES(4));
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::UpdateSelectionInfoPanel( void )
+{
+ // Home page doesn't support item selections
+ if ( IsHomePage() )
+ return;
+
+ if ( m_pPreviewItemButton )
+ {
+ m_pPreviewItemButton->SetVisible( false );
+ }
+
+ if ( m_pSelectedPanel )
+ {
+ const econ_store_entry_t *pEntry = GetSelectedEntry();
+ CEconItemView *pItem = m_pSelectedPanel->GetItem();
+ if ( pItem && pEntry )
+ {
+ if ( m_pPreviewItemButton )
+ {
+ m_pPreviewItemButton->SetVisible( pEntry->CanPreview() );
+ }
+
+ m_iOldSelectedItemDef = m_iSelectedItemDef;
+ m_iSelectedItemDef = pItem->GetItemDefIndex();
+
+ if ( m_iSelectedItemDef != m_iOldSelectedItemDef )
+ {
+ EconUI()->Gamestats_Store( IE_STORE_ITEM_SELECTED, pItem, GetPageName() );
+ }
+
+ CEconItemDefinition *pItemData = pItem->GetStaticData();
+ if ( pItemData )
+ {
+ ShowPreview( 0, pEntry );
+ InvalidateLayout();
+
+ wchar_t wzLocalizedPrice[ kLocalizedPriceSizeInChararacters ];
+ int iPrice = pEntry->GetCurrentPrice( EconUI()->GetStorePanel()->GetCurrency() );
+ MakeMoneyString( wzLocalizedPrice, ARRAYSIZE( wzLocalizedPrice ), iPrice, EconUI()->GetStorePanel()->GetCurrency() );
+ SetDialogVariable("selectionprice", wzLocalizedPrice );
+
+ if ( m_pAddToCartButtonPanel )
+ {
+ m_pAddToCartButtonPanel->SetVisible( true );
+ }
+
+ return;
+ }
+ }
+ }
+
+ SetDialogVariable("selectionprice", "" );
+
+ if ( m_pAddToCartButtonPanel )
+ {
+ m_pAddToCartButtonPanel->SetVisible( false );
+ }
+ m_iSelectedItemDef = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when text changes in combo box
+//-----------------------------------------------------------------------------
+void CStorePage::OnTextChanged( KeyValues *data )
+{
+ Panel *pPanel = reinterpret_cast<vgui::Panel *>( data->GetPtr("panel") );
+ vgui::ComboBox *pComboBox = dynamic_cast<vgui::ComboBox *>( pPanel );
+
+ m_bFilterDirty = true;
+
+ if ( pComboBox == m_pFilterComboBox )
+ {
+ // the class selection combo box changed, update class details
+ KeyValues *pUserData = m_pFilterComboBox->GetActiveItemUserData();
+ if ( !pUserData )
+ return;
+
+ int iFilter = pUserData->GetInt( "filter", 0 );
+
+ // If there are no items for that class, refuse to switch
+ if ( iFilter && m_vecFilterCounts[iFilter] <= 0 )
+ {
+ m_pFilterComboBox->ActivateItemByRow( m_iCurrentFilter ? m_iCurrentFilter+1 : 0 );
+ return;
+ }
+
+ SetFilter( iFilter );
+ m_iCurrentPage = 0;
+ UpdateModelPanels();
+
+ m_pCheckoutButton->RequestFocus();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::SetFilter( int iFilter )
+{
+ if ( iFilter != m_iCurrentFilter )
+ m_bFilterDirty = true;
+
+ m_iCurrentFilter = iFilter;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::ShowPreview( int iClass, const econ_store_entry_t* pEntry )
+{
+ if ( !m_pPreviewPanel )
+ return;
+
+ CEconItemView itemData;
+ itemData.Init( m_iSelectedItemDef, AE_UNIQUE, AE_USE_SCRIPT_VALUE, true );
+ itemData.SetClientItemFlags( kEconItemFlagClient_Preview );
+
+ m_pPreviewPanel->PreviewItem( iClass, &itemData, pEntry );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::SetDetailsVisible( bool bVisible )
+{
+ if ( m_pPreviewPanel )
+ {
+ m_pPreviewPanel->SetState( bVisible ? PS_DETAILS : PS_ITEM );
+ }
+
+ if ( m_pItemPreviewButtonPanel && m_pItemDetailsButtonPanel )
+ {
+ vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
+ if ( bVisible )
+ {
+ m_pItemPreviewButtonPanel->SetBorder( pScheme->GetBorder("StorePreviewTabUnselected") );
+ m_pItemDetailsButtonPanel->SetBorder( pScheme->GetBorder("StorePreviewTabSelected") );
+ }
+ else
+ {
+ m_pItemPreviewButtonPanel->SetBorder( pScheme->GetBorder("StorePreviewTabSelected") );
+ m_pItemDetailsButtonPanel->SetBorder( pScheme->GetBorder("StorePreviewTabUnselected") );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CStorePage::FindAndSelectEntry( const econ_store_entry_t *pEntry )
+{
+ // We can't search if we haven't created our item panels & filtered.
+ CreateItemPanels();
+ SetFilter( FILTER_ALL_ITEMS );
+ UpdateFilteredItems();
+
+ FOR_EACH_VEC( m_FilteredEntries, i )
+ {
+ if ( m_FilteredEntries[i]->GetItemDefinitionIndex() == pEntry->GetItemDefinitionIndex() )
+ {
+ // Figure out what page it'll be on
+ int iPage = floor( (float)i / (float)GetNumItemPanels() );
+
+ // Switch to that page
+ m_iCurrentPage = iPage;
+ UpdateModelPanels();
+ m_iSelectPageOnPageShow = iPage;
+
+ // Then select the item model panel for this item
+ FOR_EACH_VEC( m_vecItemPanels, p )
+ {
+ CEconItemView *pItem = m_vecItemPanels[p].m_pItemModelPanel->GetItem();
+ if ( pItem && pItem->GetItemDefIndex() == pEntry->GetItemDefinitionIndex() )
+ {
+ // We can't select here, because the pageshow will stomp it.
+ // Remember that this is the panel we'd like to have selected.
+ m_iSelectDefOnPageShow = p;
+ break;
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const econ_store_entry_t *CStorePage::GetSelectedEntry( void )
+{
+ // Get the entry for the panel.
+ int iEntry = -1;
+
+ if ( m_pFeaturedItemPanel == m_pSelectedPanel )
+ return EconUI()->GetStorePanel()->GetFeaturedEntry();
+
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ if ( m_vecItemPanels[i].m_pItemModelPanel == m_pSelectedPanel )
+ {
+ iEntry = m_EntryIndices[i];
+ if ( iEntry >= 0 && iEntry < m_FilteredEntries.Count() )
+ return m_FilteredEntries[iEntry];
+ }
+ }
+
+ // It's probably something already in our cart.
+ FOR_EACH_VEC( m_pCartModelPanels, i )
+ {
+ if ( m_pCartModelPanels[i] == m_pSelectedPanel )
+ {
+ CStoreCart *pCart = EconUI()->GetStorePanel()->GetCart();
+ if ( i < pCart->GetNumEntries() )
+ {
+ cart_item_t *pCartItem = pCart->GetItem(i);
+ return pCartItem->pEntry;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::AddSelectionToCart( void )
+{
+ if ( !m_pSelectedPanel )
+ return;
+
+ // Get the entry for the panel.
+ const econ_store_entry_t *pEntry = GetSelectedEntry();
+ if ( pEntry )
+ {
+ AddItemToCartHelper( GetPageName(), pEntry, kCartItem_Purchase );
+ UpdateCart();
+ }
+
+ // Turn the free slots indicator red if we can't fit everything.
+ UpdateBackpackLabel();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::UpdateCart( void )
+{
+ if ( !IsVisible() || ( !ShouldUseNewStore() && IsHomePage() ) )
+ return;
+
+ CStoreCart *pCart = EconUI()->GetStorePanel()->GetCart();
+ int iNumEntriesInCart = pCart->GetNumEntries();
+
+ // Now update the item icons next to the cart.
+ if ( m_pCartModelPanels.Count() < iNumEntriesInCart )
+ {
+ // Support a max of 10 items in the cart quickview right now
+ for ( int i = m_pCartModelPanels.Count(); (i < iNumEntriesInCart) && (i < m_iMaxCartModelPanels); i++ )
+ {
+ CItemModelPanel *pPanel = vgui::SETUP_PANEL( new CItemModelPanel( this, VarArgs("cartmodelpanel%d", i) ) );
+ pPanel->SetActAsButton( true, true );
+ pPanel->ApplySettings( m_pCartModelPanelKVs );
+ SetBorderForItem( pPanel, false );
+ m_pCartModelPanels.AddToTail( pPanel );
+
+ pPanel->SetTooltip( m_pMouseOverTooltip, "" );
+
+ CExLabel *pLabel = vgui::SETUP_PANEL( new CExLabel( this, VarArgs("cartquantitylabel%d", i), "X" ) );
+ pLabel->ApplySettings( m_pCartQuantityLabelKVs );
+ pLabel->SetMouseInputEnabled( false );
+ pLabel->SetKeyBoardInputEnabled( false );
+ m_pCartQuantityLabels.AddToTail( pLabel );
+ }
+ }
+
+ UpdateBackpackLabel();
+
+ InvalidateLayout();
+
+ CEconItemView *pItemData = new CEconItemView();
+
+ // Assign the items in the cart to the panels
+ FOR_EACH_VEC( m_pCartModelPanels, i )
+ {
+ if ( i >= iNumEntriesInCart )
+ {
+ m_pCartModelPanels[i]->SetItem( NULL );
+ m_pCartModelPanels[i]->SetVisible( false );
+ m_pCartQuantityLabels[i]->SetVisible( false );
+ continue;
+ }
+
+ cart_item_t *pCartItem = pCart->GetItem(i);
+ pItemData->Init( pCartItem->pEntry->GetItemDefinitionIndex(), AE_UNIQUE, AE_USE_SCRIPT_VALUE, true );
+ pItemData->SetClientItemFlags( kEconItemFlagClient_Preview | kEconItemFlagClient_StoreItem );
+ m_pCartModelPanels[i]->SetItem( pItemData );
+ m_pCartModelPanels[i]->SetVisible( true );
+
+ m_pCartQuantityLabels[i]->SetVisible( true );
+ m_pCartQuantityLabels[i]->SetText( VarArgs("%d",pCartItem->iQuantity) );
+ }
+
+ delete pItemData;
+
+ // Update the item count
+ wchar_t wszCount[16];
+ wchar_t wzLocalized[512];
+ _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", pCart->GetTotalItems() );
+ g_pVGuiLocalize->ConstructString_safe( wzLocalized, g_pVGuiLocalize->Find( "#Store_Cart" ), 1, wszCount );
+ SetDialogVariable("storecart", wzLocalized );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+ConVar econ_never_show_items_in_cart_count( "econ_never_show_items_in_cart_count", "1", FCVAR_DEVELOPMENTONLY );
+
+void CStorePage::UpdateBackpackLabel( void )
+{
+ wchar_t wszBackpackSlotCount[16];
+ wchar_t wszLocalized[512];
+
+ // How many slots do we have free in our current backpack? This won't take into
+ // consideration expanders, account upgrades, etc.
+ const int iMaxItemCount = InventoryManager()->GetLocalInventory()->GetMaxItemCount(),
+ iCurItemCount = InventoryManager()->GetLocalInventory()->GetItemCount();
+ AssertMsg( iMaxItemCount - iCurItemCount >= 0, "You have a negative number of backpack slots available - fix me!" );
+ const int iBaseFreeSlots = MAX( 0, iMaxItemCount - iCurItemCount );
+ _snwprintf( wszBackpackSlotCount, ARRAYSIZE( wszBackpackSlotCount ), L"%d", iBaseFreeSlots );
+
+ // Breaking out bundles into individual items, etc., how many backpack slots will the
+ // items in our cart take up?
+ const int iItemsInCart = EconUI()->GetStorePanel()->GetCart()->GetTotalConcreteItems();
+
+ if ( iItemsInCart == 0 || econ_never_show_items_in_cart_count.GetBool() )
+ {
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#Store_FreeBackpackSpace" ), 1, wszBackpackSlotCount );
+ }
+ else
+ {
+ wchar_t wszCartCount[16];
+ _snwprintf( wszCartCount, ARRAYSIZE( wszCartCount ), L"%d", iItemsInCart );
+
+#if defined( TF_CLIENT_DLL )
+ if ( IsFreeTrialAccount() )
+ {
+ wchar_t wszUpgradeSlotCount[16];
+ _snwprintf( wszUpgradeSlotCount, ARRAYSIZE( wszUpgradeSlotCount ), L"%d", DEFAULT_NUM_BACKPACK_SLOTS - DEFAULT_NUM_BACKPACK_SLOTS_FREE_TRIAL_ACCOUNT );
+
+ // We're a free trial account so we show the number of backpack slots we really have,
+ // the number of slots we get as a bonus when purchasing, and then the number of items
+ // in our cart.
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#Store_FreeBackpackSpace_WithCartItems_WithUpgrade" ), 3, wszBackpackSlotCount, wszCartCount, wszUpgradeSlotCount );
+ }
+ else
+#endif // defined( TF_CLIENT_DLL )
+ {
+ // We aren't a free trial account, so there is no account upgrade included in
+ // this purchase, so fall back to showing the number of items in our cart.
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#Store_FreeBackpackSpace_WithCartItems" ), 2, wszBackpackSlotCount, wszCartCount );
+ }
+ }
+
+ SetDialogVariable( "freebackpackspace", wszLocalized );
+
+ if ( m_pBackpackLabel )
+ {
+ const Color clrTooMany = ShouldUseNewStore() ? Color(200,80,60,255) : Color(255,0,0,255);
+ m_pBackpackLabel->SetFgColor( InventoryManager()->GetLocalInventory()->CanPurchaseItems( iItemsInCart ) ? m_colBackpackOrg : clrTooMany );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::UpdateFilterComboBox( void )
+{
+ if ( !m_pFilterComboBox )
+ return;
+
+ m_pFilterComboBox->RemoveAll();
+
+ // All items
+ KeyValues *pKeyValues = new KeyValues( "data" );
+ pKeyValues->SetInt( "filter", FILTER_ALL_ITEMS );
+ m_pFilterComboBox->AddItem( "#Store_ClassFilter_None", pKeyValues );
+
+ pKeyValues->deleteThis();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::PreviewSelectionItem( void )
+{
+ if ( !m_pSelectedPanel )
+ return;
+
+ // Get the entry for the panel.
+ const econ_store_entry_t *pEntry = GetSelectedEntry();
+ if ( !pEntry )
+ return;
+
+ if ( !pEntry->CanPreview() )
+ return;
+
+ DoPreviewItem( pEntry->GetItemDefinitionIndex() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::DoPreviewItem( item_definition_index_t usItemDef )
+{
+#ifdef TF_CLIENT_DLL
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( "do_try_out_item", CFmtStr( "%i", usItemDef ).Access() );
+#endif
+
+ if ( usItemDef == InventoryManager()->GetLocalInventory()->GetPreviewItemDef() )
+ {
+ ShowMessageBox( "#ItemPreview_AlreadyPreviewTitle", "#ItemPreview_AlreadyPreviewText", "#GameUI_OK" );
+ return;
+ }
+
+ // Send a message to the GC asking if this player can preview an item.
+ GCSDK::CGCMsg< MsgGCCheckItemPreviewStatus_t > msg( k_EMsgGCItemPreviewCheckStatus );
+ msg.Body().m_unItemDefIndex = usItemDef;
+
+ // OGS LOGGING HERE
+
+ GCClientSystem()->BSendMessage( msg );
+
+ // Response is handled in item_rental_ui.cpp.
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::OnPreviewItem( KeyValues *pData )
+{
+ DoPreviewItem( pData->GetInt( "item_def_index" ) );
+}
diff --git a/game/client/econ/store/store_page.h b/game/client/econ/store/store_page.h
new file mode 100644
index 0000000..49745cb
--- /dev/null
+++ b/game/client/econ/store/store_page.h
@@ -0,0 +1,424 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef STORE_PAGE_H
+#define 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 "econ_storecategory.h"
+
+class CItemModelPanel;
+class CItemModelPanelToolTip;
+class CStorePreviewItemPanel;
+class CStoreItemControlsPanel;
+
+#define FILTER_ALL_ITEMS 0
+
+//-----------------------------------------------------------------------------
+// Purpose: Base class for the preview icons in the store's item preview panel
+//-----------------------------------------------------------------------------
+class CBaseStorePreviewIcon : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CBaseStorePreviewIcon, vgui::EditablePanel );
+public:
+ CBaseStorePreviewIcon( vgui::Panel *parent, const char *name ) : vgui::EditablePanel(parent,name)
+ {
+ REGISTER_COLOR_AS_OVERRIDABLE( m_colPanelBG, "panel_bgcolor" );
+ REGISTER_COLOR_AS_OVERRIDABLE( m_colPanelBGMouseover, "panel_bgcolor_mouseover" );
+ m_bHover = false;
+ m_bSelected = false;
+ }
+
+ void SetSelected( bool bSelected )
+ {
+ m_bSelected = bSelected;
+ UpdateBgColor();
+ }
+
+ void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+ SetBgColor( m_colPanelBG );
+ }
+
+ virtual void PerformLayout( void )
+ {
+ BaseClass::PerformLayout();
+
+ int iWide = GetWide() - (m_iImageIndent * 2);
+ int iTall = GetTall() - (m_iImageIndent * 2);
+ SetInternalImageBounds( m_iImageIndent, m_iImageIndent, iWide, iTall );
+ }
+
+ virtual void OnCursorEntered()
+ {
+ BaseClass::OnCursorEntered();
+ m_bHover = true;
+ UpdateBgColor();
+ }
+ virtual void OnCursorExited()
+ {
+ BaseClass::OnCursorExited();
+ m_bHover = false;
+ UpdateBgColor();
+ }
+
+ virtual void SetInternalImageBounds( int iX, int iY, int iWide, int iTall ) = 0;
+
+private:
+ Color m_colPanelBG;
+ Color m_colPanelBGMouseover;
+ CPanelAnimationVarAliasType( int, m_iImageIndent, "image_indent", "0", "proportional_int" );
+
+ bool m_bHover;
+ bool m_bSelected;
+
+ void UpdateBgColor()
+ {
+ if ( m_bHover || m_bSelected )
+ {
+ SetBgColor( m_colPanelBGMouseover );
+ }
+ else
+ {
+ SetBgColor( m_colPanelBG );
+ }
+ }
+
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: An item preview icon in the store's item preview panel
+//-----------------------------------------------------------------------------
+class CStorePreviewItemIcon : public CBaseStorePreviewIcon
+{
+ DECLARE_CLASS_SIMPLE( CStorePreviewItemIcon, CBaseStorePreviewIcon );
+public:
+ CStorePreviewItemIcon( vgui::Panel *parent, const char *name ) : CBaseStorePreviewIcon(parent,name)
+ {
+ m_pItemPanel = new CItemModelPanel( this, "itempanel" );
+ m_pItemPanel->AddActionSignalTarget( this );
+ m_pItemPanel->SendPanelEnterExits( true );
+ m_pItemPanel->SetActAsButton( true, true );
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+ vgui::EditablePanel *pTmp = dynamic_cast<vgui::EditablePanel*>( FindChildByName("bgblockout") );
+ if ( pTmp )
+ {
+ pTmp->SetMouseInputEnabled( false );
+ }
+ }
+
+ virtual void OnCursorEntered()
+ {
+ BaseClass::OnCursorEntered();
+ PostActionSignal(new KeyValues("ShowItemIconMouseover", "icon", m_iIconIndex));
+ }
+ virtual void OnCursorExited()
+ {
+ BaseClass::OnCursorExited();
+ PostActionSignal(new KeyValues("HideItemIconMouseover"));
+ }
+ virtual void OnMouseReleased(vgui::MouseCode code)
+ {
+ BaseClass::OnMouseReleased(code);
+ PostActionSignal(new KeyValues("ItemIconSelected", "icon", m_iIconIndex));
+ }
+
+ MESSAGE_FUNC_PTR( OnItemPanelMouseReleased, "ItemPanelMouseReleased", panel );
+
+ virtual void SetInternalImageBounds( int iX, int iY, int iWide, int iTall )
+ {
+ m_pItemPanel->SetBounds( iX, iY, iWide, iTall );
+ }
+
+ MESSAGE_FUNC_PTR( OnItemPanelEntered, "ItemPanelEntered", panel )
+ {
+ BaseClass::OnCursorEntered();
+ }
+
+ MESSAGE_FUNC_PTR( OnItemPanelExited, "ItemPanelExited", panel )
+ {
+ BaseClass::OnCursorExited();
+ }
+
+ void SetItem( int iIconIndex, int iItemDef )
+ {
+ m_iIconIndex = iIconIndex;
+
+ CEconItemView itemData;
+ itemData.Init( iItemDef, AE_UNIQUE, AE_USE_SCRIPT_VALUE, true );
+ m_pItemPanel->SetItem( &itemData );
+ }
+
+ void SetItem( int iIconIndex, CEconItemView *pItem )
+ {
+ m_iIconIndex = iIconIndex;
+ m_pItemPanel->SetItem( pItem );
+ }
+
+ CItemModelPanel *GetItemPanel( void ) { return m_pItemPanel; }
+
+private:
+ CItemModelPanel *m_pItemPanel;
+ int m_iIconIndex;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CStoreItemControlsPanel;
+class CStoreItemControlsPanel : public vgui::EditablePanel
+{
+public:
+ DECLARE_CLASS_SIMPLE( CStoreItemControlsPanel, vgui::EditablePanel );
+
+ CStoreItemControlsPanel( vgui::Panel *pParent, const char *pPanelName, CItemModelPanel *pItemModelPanel );
+ virtual ~CStoreItemControlsPanel() {}
+
+ void SetMouseHoverHandler( Panel *pHandler );
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+
+ const econ_store_entry_t *GetItem() const;
+ void SetItem( const econ_store_entry_t *pEntry );
+ void SetButtonsVisible( bool bVisible );
+
+ virtual void OnCursorEntered();
+ virtual void OnCursorExited();
+
+ void OnItemPanelEntered();
+ void OnItemPanelExited();
+
+ virtual void OnThink();
+ virtual void OnCommand( const char *command );
+
+ CItemModelPanel *GetItemModelPanel() { return m_pItemModelPanel; }
+
+protected:
+ CItemModelPanel *m_pItemModelPanel;
+ const econ_store_entry_t *m_pEntry;
+ bool m_bButtonsVisible;
+ bool m_bItemPanelEntered;
+ vgui::DHANDLE< Panel > m_pMouseHoverHandler;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CStorePricePanel : public vgui::EditablePanel
+{
+public:
+ DECLARE_CLASS_SIMPLE( CStorePricePanel, vgui::EditablePanel );
+
+ CStorePricePanel( vgui::Panel *pParent, const char *pPanelName );
+ virtual ~CStorePricePanel();
+
+ virtual const char* GetPanelResFile();
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout();
+
+ void SetPriceText( int iPrice, const char *pVariable, const econ_store_entry_t *pEntry );
+ virtual void SetItem( const econ_store_entry_t *pEntry );
+
+ MESSAGE_FUNC_PARAMS( OnStoreItemControlsPanelHover, "StoreItemControlsPanelHover", data );
+
+protected:
+ bool m_bOldDiscountVisibility;
+ CExLabel *m_pPrice;
+ CExLabel *m_pDiscount;
+ CExLabel *m_pNew;
+ CExLabel *m_pHighlighted;
+ CExLabel *m_pSale;
+ EditablePanel *m_pSaleBorder;
+ CExLabel *m_pOGPrice;
+ Panel *m_pCrossout;
+ Panel *m_pLimited;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CStorePage : public vgui::PropertyPage, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CStorePage, vgui::PropertyPage );
+public:
+ CStorePage( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData, const char *pPreviewItemResFile = NULL );
+ virtual ~CStorePage();
+
+ virtual void OnPostCreate();
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void OnCommand( const char *command );
+ virtual void PerformLayout( void );
+ virtual void FireGameEvent( IGameEvent *event );
+ virtual void OnMouseWheeled( int delta );
+
+ virtual CStorePricePanel* CreatePricePanel( int iIndex );
+
+ void SetBorderForItem( CItemModelPanel *pItemPanel, bool bMouseOver );
+ void CalculateItemButtonPos( CItemModelPanel *pItemPanel, int x, int y, int *iXPos, int *iYPos );
+ int AssignItemToPanel( CItemModelPanel *pPanel, int iIndex );
+ void PositionItemPanel( CItemModelPanel *pPanel, int iIndex );
+
+ void UpdateModelPanels( void );
+ virtual void UpdateSelectionInfoPanel( void );
+ void UpdateCart( void );
+ void AddSelectionToCart( void );
+ void PreviewSelectionItem( void );
+ void DoPreviewItem( item_definition_index_t usItemDef );
+ const econ_store_entry_t *GetSelectedEntry( void );
+
+ int GetNumItemPanels( void ) { return m_iItemPanels; }
+ int GetNumColumns( void ) { return m_iItemColumns; }
+ int GetNumPages( void );
+ virtual void ShowPreview( int iClass, const econ_store_entry_t* pEntry );
+ void SetDetailsVisible( bool bVisible );
+
+ const char* GetPageName( void ) { return m_pPageData ? m_pPageData->m_pchName : NULL; }
+
+ virtual bool FindAndSelectEntry( const econ_store_entry_t *pEntry );
+
+ CItemModelPanelToolTip *GetItemTooltip( void ) { return m_pMouseOverTooltip; }
+
+ MESSAGE_FUNC( OnPageShow, "PageShow" );
+ MESSAGE_FUNC_PTR( OnItemPanelMouseReleased, "ItemPanelMouseReleased", panel ); // Comes from CStoreItemControlsPanel
+ MESSAGE_FUNC_PTR( OnItemPanelMouseDoublePressed, "ItemPanelMouseDoublePressed", panel );
+ MESSAGE_FUNC_PTR( OnItemPanelEntered, "ItemPanelEntered", panel );
+ MESSAGE_FUNC_PTR( OnItemPanelExited, "ItemPanelExited", panel );
+ MESSAGE_FUNC_PTR( OnItemAddToCart, "ItemAddToCart", panel );
+ MESSAGE_FUNC_PARAMS( OnTextChanged, "TextChanged", data );
+ MESSAGE_FUNC_PARAMS( OnPreviewItem, "PreviewItem", data );
+
+ virtual const char *GetPageResFile();
+ virtual CStorePreviewItemPanel *CreatePreviewPanel( void );
+
+protected:
+ // Filtering
+ virtual bool DoesEntryFilterPassSecondaryFilter( const econ_store_entry_t *pEntry ) { return true; } // Allow derived classes to add an additional
+ virtual void UpdateFilteredItems( void );
+ virtual int GetNumPrimaryFilters( void ) { return 1; } // All Items
+ void SetFilter( int iFilter );
+ virtual void UpdateFilterComboBox( void );
+ virtual void GetFiltersForDef( GameItemDefinition_t *pDef, CUtlVector<int> *pVecFilters ) { pVecFilters->AddToTail( FILTER_ALL_ITEMS ); }
+
+ static int ItemDisplayOrderSort_UseSortOverride( const econ_store_entry_t *const *ppA, const econ_store_entry_t *const *ppB );
+ virtual void OrderItemsForDisplay( CUtlVector<const econ_store_entry_t *>& vecItems ) const;
+
+protected:
+ void CreateItemPanels( void );
+ void DeSelectAllItemPanels( void );
+ void ToggleSelectItemPanel( CItemModelPanel *pPanel );
+ void SelectItemPanel( CItemModelPanel *pPanel );
+ void UpdateBackpackLabel( void );
+ bool IsHomePage( void ) { return m_pPageData && m_pPageData->m_bIsHome; }
+
+protected:
+ const CEconStoreCategoryManager::StoreCategory_t *m_pPageData;
+ CStorePreviewItemPanel *m_pPreviewPanel;
+ const char *m_pPreviewItemResFile;
+ vgui::EditablePanel *m_pItemDetailsButtonPanel;
+ vgui::EditablePanel *m_pItemPreviewButtonPanel;
+
+ // Filtering
+ CUtlVector< const econ_store_entry_t* > m_FilteredEntries;
+ vgui::ComboBox *m_pFilterComboBox;
+ int m_iCurrentFilter;
+
+ // Selection info panel
+ int m_iSelectedItemDef;
+ int m_iOldSelectedItemDef;
+ int m_iSelectDefOnPageShow;
+ int m_iSelectPageOnPageShow;
+ CItemModelPanel *m_pSelectedPanel;
+ CItemModelPanel *m_pFeaturedItemPanel;
+ Color m_colBackpackOrg;
+
+ // Item model panels
+ struct item_panel
+ {
+ CItemModelPanel* m_pItemModelPanel;
+ CStorePricePanel* m_pStorePricePanel;
+ CStoreItemControlsPanel* m_pItemControlsPanel;
+ };
+ CUtlVector<item_panel> m_vecItemPanels;
+ CUtlVector<int> m_EntryIndices; // Easy lookup for which model panel is mapped to which entry index
+ CItemModelPanel *m_pMouseOverItemPanel;
+ CItemModelPanelToolTip *m_pMouseOverTooltip;
+ KeyValues *m_pItemModelPanelKVs;
+ KeyValues *m_pModelPanelLabelsKVs;
+ bool m_bReapplyItemKVs;
+
+ // Cart display
+ CExButton *m_pCartButton;
+ CUtlVector<CItemModelPanel*> m_pCartModelPanels;
+ KeyValues *m_pCartModelPanelKVs;
+ CUtlVector<CExLabel*> m_pCartQuantityLabels;
+ KeyValues *m_pCartQuantityLabelKVs;
+ vgui::ImagePanel *m_pCartFeaturedItemImage;
+
+ // Pages
+ int m_iCurrentPage;
+ vgui::Label *m_pCurPageLabel;
+ CExButton *m_pNextPageButton;
+ CExButton *m_pPrevPageButton;
+
+ CExButton *m_pCheckoutButton;
+ CExButton *m_pPreviewItemButton;
+ vgui::EditablePanel *m_pAddToCartButtonPanel;
+ vgui::Label *m_pBackpackLabel;
+ vgui::EditablePanel *m_pItemBackdropPanel;
+
+ bool m_bShouldDeletePreviewPanel; // Set to true by derived classes if the preview panel's panel should be deleted, which is necessary if its parent is NULL
+
+ CPanelAnimationVarAliasType( int, m_iItemOffcenterX, "item_offcenter_x", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iItemXDelta, "item_xdelta", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iItemYDelta, "item_ydelta", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iItemXPos, "item_xpos", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iItemYPos, "item_ypos", "0", "proportional_int" );
+ CPanelAnimationVar( int, m_iItemPanels, "item_panels", "35" );
+ CPanelAnimationVar( int, m_iItemColumns, "item_columns", "7" );
+ CPanelAnimationVar( bool, m_bShowItemBgPanel, "show_item_backdrop", "0" );
+ CPanelAnimationVarAliasType( int, m_iItemBackdropLeftMargin, "item_backdrop_left_margin", "20", "proportional_xpos" );
+ CPanelAnimationVarAliasType( int, m_iItemBackdropRightMargin, "item_backdrop_right_margin", "20", "proportional_xpos" );
+ CPanelAnimationVarAliasType( int, m_iItemBackdropTopMargin, "item_backdrop_top_margin", "20", "proportional_ypos" );
+ CPanelAnimationVarAliasType( int, m_iItemBackdropBottomMargin, "item_backdrop_bottom_margin", "20", "proportional_ypos" );
+ CPanelAnimationVar( int, m_iItemBackdropPaintBackgroundType, "item_backdrop_paintbackgroundtype", "50" );
+ CPanelAnimationVar( int, m_iItemBackdropZPos, "item_backdrop_zpos", "0" );
+ CPanelAnimationVarAliasType( int, m_iItemControlsXOffset, "item_controls_xoffset", "5", "proportional_xpos" );
+ CPanelAnimationVarAliasType( int, m_iItemControlsYOffset, "item_controls_yoffset", "5", "proportional_xpos" );
+ CPanelAnimationVar( int, m_iMaxCartModelPanels, "max_cart_model_panels", "10" );
+
+ Color m_colItemPanelBG;
+ Color m_colItemPanelBGMouseover;
+ Color m_colItemPanelBGSelected;
+ Color m_colItemBackdropPanel;
+
+ // The number of items in each filter options
+ CUtlVector<int> m_vecFilterCounts;
+
+ bool m_bFilterDirty;
+};
+
+void AddItemToCartHelper( const char *pszContext, const econ_store_entry_t *pEntry, ECartItemType eSelectedCartItemType );
+void AddItemToCartHelper( const char *pszContext, item_definition_index_t unItemDef, ECartItemType eSelectedCartItemType );
+
+#endif // STORE_PAGE_H
diff --git a/game/client/econ/store/store_page_halloween.cpp b/game/client/econ/store/store_page_halloween.cpp
new file mode 100644
index 0000000..a039b4d
--- /dev/null
+++ b/game/client/econ/store/store_page_halloween.cpp
@@ -0,0 +1,85 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store/store_page_halloween.h"
+#include "vgui/ISurface.h"
+#include "vgui/IInput.h"
+#include "vgui/ILocalize.h"
+#include "c_tf_player.h"
+#include "gamestringpool.h"
+#include "tf_item_inventory.h"
+#include "econ_item_system.h"
+#include "item_model_panel.h"
+#include "store/store_panel.h"
+#include "store_preview_item.h"
+#include "store_viewcart.h"
+#include "c_tf_gamestats.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFStorePage_SpecialPromo::CTFStorePage_SpecialPromo( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData ) : BaseClass( parent, pPageData )
+{
+ pszResFile = pPageData->m_pchPageRes;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+/*
+void CTFStorePage_SpecialPromo::OrderItemsForDisplay( CUtlVector<const econ_store_entry_t *>& vecItems ) const
+{
+ vecItems.Sort( &ItemDisplayOrderSort_UseSortOverride );
+}
+*/
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFStorePage_Popular::CTFStorePage_Popular( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData ) : BaseClass( parent, pPageData )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: The popular page draws its list of items from the overall popular items list.
+//-----------------------------------------------------------------------------
+void CTFStorePage_Popular::UpdateFilteredItems( void )
+{
+ m_FilteredEntries.Purge();
+ m_vecFilterCounts.SetCount( GetNumPrimaryFilters() );
+ if ( !m_vecFilterCounts.Count() )
+ return;
+
+ FOR_EACH_VEC( m_vecFilterCounts, i )
+ {
+ m_vecFilterCounts[i] = 0;
+ }
+
+ CStorePanel *pStorePanel = EconUI()->GetStorePanel();
+ if ( !pStorePanel )
+ return;
+
+ // Add all popular items
+ const CUtlVector<uint32>& popularItems = pStorePanel->GetPopularItems();
+
+ for ( int i=0; i<popularItems.Count(); ++i )
+ {
+ const econ_store_entry_t *pEntry = pStorePanel->GetPriceSheet()->GetEntry( popularItems[i] );
+ m_FilteredEntries.AddToTail( pEntry );
+ }
+
+ FOR_EACH_VEC( m_vecItemPanels, idx )
+ {
+ m_vecItemPanels[idx].m_pItemModelPanel->SetShowQuantity( false );
+ }
+
+ m_pFilterComboBox->SetVisible( false );
+} \ No newline at end of file
diff --git a/game/client/econ/store/store_page_halloween.h b/game/client/econ/store/store_page_halloween.h
new file mode 100644
index 0000000..7ab0c0a
--- /dev/null
+++ b/game/client/econ/store/store_page_halloween.h
@@ -0,0 +1,49 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef STORE_PAGE_SPECIALPROMO_H
+#define STORE_PAGE_SPECIALPROMO_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "store/v1/tf_store_page.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFStorePage_SpecialPromo : public CTFStorePage1
+{
+ DECLARE_CLASS_SIMPLE( CTFStorePage_SpecialPromo, CTFStorePage1 );
+public:
+ CTFStorePage_SpecialPromo( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData );
+
+ virtual const char* GetPageResFile() { return pszResFile; }
+
+protected:
+// virtual void OrderItemsForDisplay( CUtlVector<const econ_store_entry_t *>& vecItems ) const;
+
+private:
+ const char* pszResFile;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFStorePage_Popular : public CTFStorePage1
+{
+ DECLARE_CLASS_SIMPLE( CTFStorePage_Popular, CTFStorePage1 );
+public:
+ CTFStorePage_Popular( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData );
+
+protected:
+ virtual void UpdateFilteredItems( void );
+};
+
+
+#endif // STORE_PAGE_SPECIALPROMO_H
diff --git a/game/client/econ/store/store_page_new.cpp b/game/client/econ/store/store_page_new.cpp
new file mode 100644
index 0000000..f979236
--- /dev/null
+++ b/game/client/econ/store/store_page_new.cpp
@@ -0,0 +1,232 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store_page_new.h"
+#include "vgui/ISurface.h"
+#include "vgui/IInput.h"
+#include "vgui/ILocalize.h"
+#include "gamestringpool.h"
+#include "econ_item_inventory.h"
+#include "econ_item_system.h"
+#include "item_model_panel.h"
+#include "store/store_panel.h"
+#include "store_preview_item.h"
+#include "store_viewcart.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePricePanel_New::CStorePricePanel_New( vgui::Panel *pParent, const char *pPanelName )
+: CStorePricePanel( pParent, pPanelName )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel_New::SetItem( const econ_store_entry_t *pEntry )
+{
+ BaseClass::SetItem( pEntry );
+
+ CExLabel *pNew = dynamic_cast< CExLabel* >( FindChildByName( "New" ) );
+ if ( pNew )
+ {
+ pNew->SetVisible( false );
+ }
+
+ pNew = dynamic_cast< CExLabel* >( FindChildByName( "NewLarge" ) );
+ if ( pNew )
+ {
+ int contentWidth, contentHeight;
+ pNew->GetContentSize( contentWidth, contentHeight );
+ int iTextInsetX, iTextInsetY;
+ pNew->GetTextInset( &iTextInsetX, &iTextInsetY );
+ pNew->SetWide( contentWidth + iTextInsetX );
+ int iPosX, iPosY;
+ pNew->GetPos( iPosX, iPosY );
+ pNew->SetPos( GetWide() - pNew->GetWide(), iPosY );
+ pNew->SetVisible( true );
+ }
+
+ vgui::Panel* pLimited = FindChildByName( "LimitedLarge" );
+ if ( pLimited )
+ {
+ int iPosX, iPosY;
+ pLimited->GetPos( iPosX, iPosY );
+
+ if ( pNew && pEntry->m_bLimited )
+ {
+ iPosY = pNew->GetTall() + YRES( 3 );
+ }
+
+ pLimited->SetPos( GetWide() - pLimited->GetWide() - XRES( 3 ), iPosY );
+ pLimited->SetVisible( pEntry->m_bLimited );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePricePanel_Bundles::CStorePricePanel_Bundles( vgui::Panel *pParent, const char *pPanelName )
+: CStorePricePanel( pParent, pPanelName ),
+ m_pLimitedLarge( NULL )
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel_Bundles::SetItem( const econ_store_entry_t *pEntry )
+{
+ BaseClass::SetItem( pEntry );
+
+ if ( m_pLimitedLarge )
+ {
+ m_pLimitedLarge->SetVisible( pEntry->m_bLimited );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel_Bundles::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pLimitedLarge = dynamic_cast<ImagePanel *>( FindChildByName( "LimitedLarge" ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel_Bundles::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ if ( m_pLimitedLarge )
+ {
+ int aPos[2];
+ m_pLimitedLarge->GetPos( aPos[0], aPos[1] );
+
+ if ( m_pNew && m_pNew->IsVisible() )
+ {
+ aPos[1] = m_pNew->GetTall() + YRES( 3 );
+ }
+
+ m_pLimitedLarge->SetPos( GetWide() - m_pLimitedLarge->GetWide(), aPos[1] );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePricePanel_Jumbo::CStorePricePanel_Jumbo( vgui::Panel *pParent, const char *pPanelName )
+: CStorePricePanel( pParent, pPanelName )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePricePanel_Popular::CStorePricePanel_Popular( vgui::Panel *pParent, const char *pPanelName, int iPopularityRank )
+: CStorePricePanel( pParent, pPanelName )
+, m_iPopularityRank( iPopularityRank )
+{
+ m_pNewLarge = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel_Popular::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pNewLarge = dynamic_cast< CExLabel* >( FindChildByName( "NewLarge" ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel_Popular::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ if ( m_pNewLarge )
+ {
+ int contentWidth, contentHeight;
+ m_pNewLarge->GetContentSize( contentWidth, contentHeight );
+ int iTextInsetX, iTextInsetY;
+ m_pNewLarge->GetTextInset( &iTextInsetX, &iTextInsetY );
+ m_pNewLarge->SetWide( contentWidth + iTextInsetX );
+ int iPosX, iPosY;
+ m_pNewLarge->GetPos( iPosX, iPosY );
+ m_pNewLarge->SetPos( GetWide() - m_pNewLarge->GetWide(), iPosY );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel_Popular::SetItem( const econ_store_entry_t *pEntry )
+{
+ BaseClass::SetItem( pEntry );
+
+ CExLabel *pNew = dynamic_cast< CExLabel* >( FindChildByName( "New" ) );
+ if ( pNew )
+ {
+ pNew->SetVisible( false );
+ }
+
+ if ( m_pNewLarge )
+ {
+ if ( pEntry->m_bNew )
+ {
+ m_pNewLarge->SetVisible( true );
+ }
+ else
+ {
+ m_pNewLarge->SetVisible( false );
+ }
+ }
+
+ vgui::Panel* pLimited = FindChildByName( "LimitedLarge" );
+ if ( pLimited && m_pNewLarge )
+ {
+ int iPosX, iPosY;
+ pLimited->GetPos( iPosX, iPosY );
+
+ if ( pEntry->m_bLimited && pEntry->m_bNew )
+ {
+ iPosY = m_pNewLarge->GetTall() + YRES( 3 );
+ }
+
+ pLimited->SetPos( GetWide() - m_pNewLarge->GetWide() - 14, iPosY );
+ pLimited->SetVisible( pEntry->m_bLimited );
+ }
+
+ wchar_t wszRank[10];
+ _snwprintf( wszRank, ARRAYSIZE( wszRank ), L"%d", m_iPopularityRank );
+ wchar_t wszText[8];
+ g_pVGuiLocalize->ConstructString_safe( wszText, g_pVGuiLocalize->Find( "TF_Popularity_Rank" ), 1, wszRank );
+ SetDialogVariable( "rank1", wszText );
+ SetDialogVariable( "rank2", wszText );
+
+ // Show rank or rank2 based on old store/new store
+ CExLabel *pRank = dynamic_cast<CExLabel *>( FindChildByName( CFmtStr( "Rank%i", GetStoreVersion() ).Access() ) );
+ if ( pRank )
+ {
+ pRank->SetVisible( true );
+ }
+} \ No newline at end of file
diff --git a/game/client/econ/store/store_page_new.h b/game/client/econ/store/store_page_new.h
new file mode 100644
index 0000000..5c7224f
--- /dev/null
+++ b/game/client/econ/store/store_page_new.h
@@ -0,0 +1,107 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef STORE_PAGE_NEW_H
+#define STORE_PAGE_NEW_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_store.h"
+#include "item_model_panel.h"
+#include "store_page.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CStorePricePanel_New : public CStorePricePanel
+{
+public:
+ DECLARE_CLASS_SIMPLE( CStorePricePanel_New, CStorePricePanel );
+
+ CStorePricePanel_New( vgui::Panel *pParent, const char *pPanelName );
+
+ virtual const char *GetPanelResFile()
+ {
+ return "Resource/UI/econ/store/v1/StorePrice_New.res";
+ }
+
+ virtual void SetItem( const econ_store_entry_t *pEntry );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CStorePricePanel_Bundles : public CStorePricePanel
+{
+public:
+ DECLARE_CLASS_SIMPLE( CStorePricePanel_Bundles, CStorePricePanel );
+
+ CStorePricePanel_Bundles( vgui::Panel *pParent, const char *pPanelName );
+
+ virtual const char *GetPanelResFile()
+ {
+ return "Resource/UI/econ/store/v1/StorePrice_Bundles.res";
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout();
+
+ virtual void SetItem( const econ_store_entry_t *pEntry );
+
+private:
+ vgui::ImagePanel *m_pLimitedLarge;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CStorePricePanel_Jumbo : public CStorePricePanel
+{
+public:
+ DECLARE_CLASS_SIMPLE( CStorePricePanel_Jumbo, CStorePricePanel );
+
+ CStorePricePanel_Jumbo( vgui::Panel *pParent, const char *pPanelName );
+
+ virtual const char *GetPanelResFile()
+ {
+ return "Resource/UI/econ/store/v1/StorePrice_Jumbo.res";
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CStorePricePanel_Popular : public CStorePricePanel
+{
+public:
+ DECLARE_CLASS_SIMPLE( CStorePricePanel_Popular, CStorePricePanel );
+
+ CStorePricePanel_Popular( vgui::Panel *pParent, const char *pPanelName, int iPopularityRank );
+
+ virtual const char *GetPanelResFile()
+ {
+ return "Resource/UI/econ/store/v1/StorePrice_Popular.res";
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout();
+
+ virtual void SetItem( const econ_store_entry_t *pEntry );
+
+private:
+ int m_iPopularityRank;
+ CExLabel *m_pNewLarge;
+};
+
+#endif // STORE_PAGE_NEW_H
diff --git a/game/client/econ/store/store_panel.cpp b/game/client/econ/store/store_panel.cpp
new file mode 100644
index 0000000..258a657
--- /dev/null
+++ b/game/client/econ/store/store_panel.cpp
@@ -0,0 +1,2175 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store/store_panel.h"
+#include "vgui_controls/PropertySheet.h"
+#include "vgui/IInput.h"
+#include "baseviewport.h"
+#include "iclientmode.h"
+#include "ienginevgui.h"
+#include "econ_item_inventory.h"
+#include <vgui/ILocalize.h>
+#include "econ_item_system.h"
+#include "store_page_new.h"
+#include "store_viewcart.h"
+#include "confirm_dialog.h"
+#include <vgui_controls/AnimationController.h>
+#include "econ_ui.h"
+#include "gc_clientsystem.h"
+#include "steamworks_gamestats.h"
+#include "econ/econ_storecategory.h"
+
+#ifdef TF_CLIENT_DLL
+#include "tf_mapinfo.h"
+#include "c_tf_freeaccount.h"
+#include "tf_hud_statpanel.h"
+#include "rtime.h"
+#include "item_ad_panel.h"
+#include "tf_matchmaking_dashboard.h"
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+
+
+#ifdef TF_CLIENT_DLL
+// Dec 4st 2012
+#define TF_STORE_STAMP_UPSELL_BASELINE 1354579200
+// TEMP VALUE FOR TESTING! Nov 20th 2012
+//#define TF_STORE_STAMP_UPSELL_BASELINE 1353369600
+
+// 15 days
+#define TF_STORE_STAMP_UPSELL_COOLDOWN ( 60 /*sec*/ * 60 /*min*/ * 24 /*hr*/ * 7 /*day*/ )
+// TEMP VALUE FOR TESTING! 1 day
+//#define TF_STORE_STAMP_UPSELL_COOLDOWN ( 60 /*sec*/ * 60 /*min*/ * 24 /*hr*/ )
+
+#define TF_STORE_STAMP_UPSELL_GROUPS 15
+
+ConVar tf_store_stamp_donation_add_timestamp( "tf_store_stamp_donation_add_timestamp", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_ARCHIVE | FCVAR_HIDDEN, "Remember the last time that we offered a stamp donation at checkout." );
+#endif
+
+bool CStorePanel::m_bPricesheetLoaded = false;
+bool CStorePanel::m_bShowWarnings = false;
+
+class CServerNotConnectedToSteamDialog;
+CServerNotConnectedToSteamDialog *OpenServerNotConnectedToSteamDialog( vgui::Panel *pParent );
+
+class CStampUpsellDialog : public CTFGenericConfirmDialog
+{
+ DECLARE_CLASS_SIMPLE( CStampUpsellDialog, CTFGenericConfirmDialog );
+public:
+ CStampUpsellDialog( const char *pTitle, const wchar_t *pTextText, const wchar_t *pTextText2, CSchemaItemDefHandle hMapToken, const char *pItemDefName2 )
+ : CTFGenericConfirmDialog( pTitle, pTextText, NULL, NULL, NULL, NULL )
+ , hItemDef( hMapToken ), hItemDef2( pItemDefName2 )
+ {
+ V_wcsncpy( m_wszBuffer2, pTextText2, sizeof( m_wszBuffer2 ) );
+ m_flCreationTime = gpGlobals->curtime;
+ }
+
+ virtual const char *GetResFile() OVERRIDE
+ {
+ return "Resource/UI/StampDonationAdd.res";
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE
+ {
+ BaseClass::ApplySchemeSettings(pScheme);
+
+ vgui::ImagePanel *pItemImagePanel = dynamic_cast<vgui::ImagePanel *>( FindChildByName( "ItemImagePanel", true ) ); Assert( pItemImagePanel );
+ if ( pItemImagePanel && hItemDef )
+ {
+ pItemImagePanel->SetImage( CFmtStr( "../%s_large", hItemDef->GetInventoryImage() ) );
+ }
+
+ vgui::ImagePanel *pItemImagePanel2 = dynamic_cast<vgui::ImagePanel *>( FindChildByName( "ItemImagePanel2", true ) ); Assert( pItemImagePanel );
+ if ( pItemImagePanel2 && hItemDef2 )
+ {
+ pItemImagePanel2->SetImage( CFmtStr( "../%s_large", hItemDef2->GetInventoryImage() ) );
+ }
+
+ // Now go through the string and find the escape characters telling us where the color changes are
+ ColorizeLabel( static_cast< vgui::Label* >( FindChildByName( "ExplanationLabel" ) ), m_wszBuffer );
+ ColorizeLabel( static_cast< vgui::Label* >( FindChildByName( "ExplanationLabel2" ) ), m_wszBuffer2 );
+
+ CEconItemView itemData;
+ itemData.Init( hItemDef->GetDefinitionIndex(), AE_UNIQUE, AE_USE_SCRIPT_VALUE, true );
+ itemData.SetItemQuantity( 1 );
+ itemData.SetClientItemFlags( kEconItemFlagClient_Preview | kEconItemFlagClient_StoreItem );
+
+ const ECurrency eCurrency = EconUI()->GetStorePanel()->GetCurrency();
+ const econ_store_entry_t *pEntry = EconUI()->GetStorePanel()->GetPriceSheet()->GetEntry( hItemDef->GetDefinitionIndex() );
+ item_price_t unPrice = pEntry->GetCurrentPrice( eCurrency );
+
+ wchar_t wzLocalizedPrice[ kLocalizedPriceSizeInChararacters ];
+ MakeMoneyString( wzLocalizedPrice, ARRAYSIZE( wzLocalizedPrice ), unPrice, EconUI()->GetStorePanel()->GetCurrency() );
+ SetDialogVariable( "price", wzLocalizedPrice );
+
+ Panel *pConfirmButton = FindChildByName( "ConfirmButton" );
+ pConfirmButton->RequestFocus();
+ }
+
+ virtual void OnCommand( const char *command ) OVERRIDE
+ {
+ int nSecondsVisible = gpGlobals->curtime - m_flCreationTime;
+ if ( V_strcmp( command, "add_stamp_to_cart" ) == 0 )
+ {
+ FinishUp();
+ CStorePanel::ConfirmUpsellStamps( true, hItemDef, nSecondsVisible );
+ }
+ else if ( V_strcmp( command, "nope" ) == 0 )
+ {
+ FinishUp();
+ CStorePanel::ConfirmUpsellStamps( false, hItemDef, nSecondsVisible );
+ }
+ else
+ {
+ BaseClass::OnCommand( command );
+ }
+ }
+
+ virtual void OnKeyCodePressed( vgui::KeyCode code )
+ {
+ // ESC cancels
+ if ( code == KEY_XBUTTON_B )
+ {
+ OnCommand( "nope" );
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+ }
+
+ void ColorizeLabel( vgui::Label *pLabel, wchar_t *txt )
+ {
+ if ( pLabel )
+ {
+ pLabel->GetTextImage()->ClearColorChangeStream();
+
+ // We change the title's text color to match the colors of the matching model panel backgrounds
+ int iWChars = 0;
+ Color colCustom;
+ while ( txt && *txt )
+ {
+ switch ( *txt )
+ {
+ case 0x01: // Normal color
+ pLabel->GetTextImage()->AddColorChange( Color(200,80,60,255), iWChars );
+ break;
+ case 0x02: // Item 1 color
+ pLabel->GetTextImage()->AddColorChange( Color(255,255,255,255), iWChars );
+ break;
+ default:
+ break;
+ }
+ txt++;
+ iWChars++;
+ }
+ }
+ }
+
+ CSchemaItemDefHandle hItemDef;
+ CSchemaItemDefHandle hItemDef2;
+
+ wchar_t m_wszBuffer2[1024];
+ float m_flCreationTime;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: A dialog used to show the current state of store communication with steam.
+//-----------------------------------------------------------------------------
+class CStoreStatusDialog : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CStoreStatusDialog, vgui::EditablePanel );
+
+public:
+ CStoreStatusDialog( vgui::Panel *pParent, const char *pElementName );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *scheme );
+ virtual void OnCommand( const char *command );
+ void UpdateSchemeForVersion();
+ void ShowStatusUpdate( bool bAllowed, bool bShowOnExit, bool bCancel );
+
+private:
+ bool m_bShowOnExit;
+ bool m_bNotifyOnCancel;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static void ContactSupportConfirm( bool bConfirmed, void *pContext )
+{
+ if ( bConfirmed && steamapicontext && steamapicontext->SteamFriends() )
+ {
+ steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( "https://support.steampowered.com/" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePanel::CStorePanel( Panel *parent ) : PropertyDialog(parent, "store_panel")
+, m_CallbackMicroTransactionAuthResponse( this, &CStorePanel::OnMicroTransactionAuthResponse )
+{
+ // Store 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 );
+
+ ListenForGameEvent( "gameui_hidden" );
+
+ m_unTransactionID = 0;
+ m_bShouldFinalize = false;
+ m_iStartItemDef = 0;
+ m_bAddStartItemDefToCart = false;
+ m_bPreventClosure = false;
+ m_bOGSLogging = false;
+
+ m_iCheckoutAttempts = 0;
+ m_iLastPurchaseAttemptPrice = 0;
+
+ m_eCurrency = k_ECurrencyUSD;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePanel::~CStorePanel()
+{
+ vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( CFmtStr( "Resource/UI/econ/store/v%i/StorePanel.res", GetStoreVersion() ) );
+
+ SetOKButtonVisible(false);
+ SetCancelButtonVisible(false);
+
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::PerformLayout( void )
+{
+ if ( GetVParent() )
+ {
+ int w,h;
+ vgui::ipanel()->GetSize( GetVParent(), w, h );
+ SetBounds(0,0,w,h);
+ }
+
+#ifdef TF_CLIENT_DLL
+ bool bShowUpsellCheckbox = ( tf_store_stamp_donation_add_timestamp.GetFloat() > 0.0f && HasValidUpsellStamps() );
+ SetControlVisible( "SupportCommunityMapMakersCheckButton", bShowUpsellCheckbox );
+ SetControlVisible( "SupportCommunityMapMakersLabel", bShowUpsellCheckbox );
+#endif
+
+ BaseClass::PerformLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::OnStartShopping( void )
+{
+ // Move to the first tab
+ vgui::Panel *pPage = GetPropertySheet()->GetPage(1);
+ if ( pPage )
+ {
+ GetPropertySheet()->SetActivePage( pPage );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::OnFindAndSelectFeaturedItem( void )
+{
+ const econ_store_entry_t *pEntry = GetFeaturedEntry();
+ if ( !pEntry )
+ return;
+
+ FindAndSelectEntry( pEntry );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::FindAndSelectEntry( const econ_store_entry_t *pEntry )
+{
+ // Find the item in a store page and move to it
+ int iPages = GetPropertySheet()->GetNumPages();
+ for ( int i = 1; i < iPages; i++ )
+ {
+ CStorePage *pPage = dynamic_cast< CStorePage * >( GetPropertySheet()->GetPage(i) );
+ if ( !pPage )
+ continue;
+
+ if ( pPage->FindAndSelectEntry( pEntry ) )
+ {
+ if ( GetPropertySheet()->GetActivePage() != pPage )
+ {
+ GetPropertySheet()->SetActivePage( pPage );
+ }
+ else
+ {
+ // VGUI doesn't tell the starting active page that it's active, so we post a pageshow to it
+ ivgui()->PostMessage( pPage->GetVPanel(), new KeyValues("PageShow"), GetPropertySheet()->GetVPanel() );
+ }
+ return;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Static store page factory.
+//-----------------------------------------------------------------------------
+CStorePage *CStorePanel::CreateStorePage( const CEconStoreCategoryManager::StoreCategory_t *pPageData )
+{
+ // Default, standard store page.
+ return new CStorePage( this, pPageData );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CStorePanel::ShouldShowDx8PurchaseWarning() const
+{
+ static ConVarRef mat_dxlevel( "mat_dxlevel" );
+ if ( mat_dxlevel.GetInt() >= 90 )
+ return false;
+
+ // List of operations that have features that are not compatible with DX8.
+ const char* cpDX8WarningItems[] = {
+ "Unused Summer 2015 Operation Pass",
+ "Unused Operation Tough Break Pass",
+ NULL
+ };
+
+ for ( int i = 0; cpDX8WarningItems[ i ] != NULL; ++i )
+ {
+ if ( m_Cart.ContainsItemDefinition( ItemSystem()->GetStaticDataForItemByName( cpDX8WarningItems[ i ] )->GetDefinitionIndex() ) )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::OnItemLinkClicked( KeyValues *pParams )
+{
+ // Get the item definition index from the URL
+ const char *pURL = pParams->GetString( "url" );
+ int iItemDef = atoi( pURL + 7 );
+
+ if ( EconUI()->GetStorePanel()->GetPriceSheet() )
+ {
+ // Look up the item definition and see if there is a store remap
+ CEconItemSchema *pSchema = ItemSystem()->GetItemSchema();
+ if ( pSchema )
+ {
+ CEconItemDefinition *pItem = pSchema->GetItemDefinition( iItemDef );
+ if ( pItem )
+ {
+ int iStoreRemap = pItem->GetStoreRemap();
+ if ( iStoreRemap > 0 )
+ {
+ iItemDef = pItem->GetStoreRemap();
+ }
+ }
+ }
+
+ const econ_store_entry_t *pEntry = GetPriceSheet()->GetEntry( iItemDef );
+ if ( pEntry )
+ {
+ FindAndSelectEntry( pEntry );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::ShowPanel(bool bShow)
+{
+ m_bPreventClosure = false;
+
+#ifdef TF_CLIENT_DLL
+ // Keep the MM dashboard on top of us
+ bShow ? GetMMDashboardParentManager()->PushModalFullscreenPopup( this )
+ : GetMMDashboardParentManager()->PopModalFullscreenPopup( this );
+#endif
+
+ if ( bShow )
+ {
+ if ( !m_bOGSLogging )
+ {
+ EconUI()->Gamestats_Store( IE_STORE_ENTERED );
+ m_bOGSLogging = true;
+ }
+
+ ShowStorePanel();
+ }
+ else
+ {
+ if ( m_bOGSLogging )
+ {
+ EconUI()->Gamestats_Store( IE_STORE_EXITED );
+ m_bOGSLogging = false;
+ }
+ }
+
+ SetVisible( bShow );
+
+ if ( bShow && m_bAddStartItemDefToCart )
+ {
+ OpenStoreViewCartPanel();
+ m_bAddStartItemDefToCart = false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::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 CStorePanel::OnCommand( const char *command )
+{
+ if ( !Q_strnicmp( command, "checkout", 8 ) )
+ {
+ InitiateCheckout( false );
+ return;
+ }
+ else if ( !Q_stricmp( command, "close" ) )
+ {
+ ShowPanel( false );
+
+ // If we're connected to a game server, we also close the game UI.
+ if ( engine->IsInGame() )
+ {
+ engine->ClientCmd_Unrestricted( "gameui_hide" );
+ }
+
+#ifdef TF_CLIENT_DLL
+ if ( IsFreeTrialAccount() )
+ {
+ InventoryManager()->CheckForRoomAndForceDiscard();
+ }
+#endif
+ }
+ else if ( !Q_stricmp( command, "back" ) )
+ {
+ ShowPanel( true );
+ }
+ else
+ {
+ engine->ClientCmd( const_cast<char *>( command ) );
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::OnKeyCodeTyped(vgui::KeyCode code)
+{
+ if ( code == KEY_ESCAPE )
+ {
+ if ( !m_bPreventClosure )
+ {
+ ShowPanel( false );
+ }
+ }
+ else
+ {
+ BaseClass::OnKeyCodeTyped( code );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const econ_store_entry_t *CStorePanel::GetFeaturedEntry( void )
+{
+ const CEconStoreCategoryManager::StoreCategory_t *pFeaturedSection = GEconStoreCategoryManager()->GetFeaturedItems();
+ if ( !pFeaturedSection || pFeaturedSection->m_vecEntries.Count() == 0 )
+ return NULL;
+ uint32 idx = MIN( m_StoreSheet.GetFeaturedItemIndex(), (uint32)( pFeaturedSection->m_vecEntries.Count() - 1 ) );
+ return EconUI()->GetStorePanel()->GetPriceSheet()->GetEntry( pFeaturedSection->m_vecEntries[ idx ] );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Asks the GC to send us the price sheet.
+//-----------------------------------------------------------------------------
+void CStorePanel::RequestPricesheet( void )
+{
+ // Create the store panel the first time we request a price sheet
+ EconUI()->CreateStorePanel();
+
+ CGCClientJobGetUserData *pJob = new CGCClientJobGetUserData( GCClientSystem()->GetGCClient(), 0 );
+ pJob->StartJob( NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Asynchronous job for getting the price sheet from the GC
+//-----------------------------------------------------------------------------
+bool CGCClientJobGetUserData::BYieldingRunJob( void *pvStartParam )
+{
+ GCSDK::CProtoBufMsg<CMsgStoreGetUserData> msg( k_EMsgGCStoreGetUserData );
+ GCSDK::CProtoBufMsg<CMsgStoreGetUserDataResponse> msgResponse;
+
+ msg.Body().set_price_sheet_version( m_RTimeVersion );
+ if ( !BYldSendMessageAndGetReply( msg, 10, &msgResponse, k_EMsgGCStoreGetUserDataResponse ) )
+ {
+ // No response from the GC. Show a failure message.
+ if ( CStorePanel::ShouldShowWarnings() )
+ {
+ OpenStoreStatusDialog( NULL, "#StoreUpdate_NoGCResponse", true, false );
+ }
+ return false;
+ }
+
+#ifdef _DEBUG
+ Msg( "CGCClientJobGetUserData - Result: %d\n", msgResponse.Body().result() );
+#endif
+
+ if ( !CStorePanel::CheckMessageResult( (EPurchaseResult)msgResponse.Body().result() ) )
+ return true;
+
+ bool bInitialLoad = !CStorePanel::IsPricesheetLoaded();
+
+ CStorePanel *pStorePanel = EconUI()->GetStorePanel();
+
+ // Create the store panel.
+ if ( bInitialLoad )
+ {
+ // Close the loading status dialog.
+ CloseStoreStatusDialog();
+ }
+ else
+ {
+ pStorePanel->GetPropertySheet()->DeleteAllPages();
+
+ // Indicate to the user what happened.
+ if( pStorePanel->IsVisible() )
+ {
+ OpenStoreStatusDialog( NULL, "#StoreUpdate_NewPriceSheetLoaded", true, false );
+ }
+ }
+
+ // Set the prices & items on the store panel.
+ KeyValuesAD pKVPricesheet( "prices" );
+
+ // Allow a back-door for reading the local price sheet rather than the one from the GC.
+//#define READ_LOCAL_PRICE_SHEET // DO NOT CHECK IN
+#if defined( READ_LOCAL_PRICE_SHEET ) && defined( _DEBUG )
+ pKVPricesheet->LoadFromFile( g_pFullFileSystem, "scripts/items/unencrypted/store.txt", "MOD" );
+#else
+ CUtlBuffer bufRawData( msgResponse.Body().price_sheet().data(), msgResponse.Body().price_sheet().size(), CUtlBuffer::READ_ONLY );
+ pKVPricesheet->ReadAsBinary( bufRawData );
+#endif
+
+ pKVPricesheet->SetInt( "featured_item_index", msgResponse.Body().featured_item_idx() );
+ pKVPricesheet->SetInt( "default_sort_type", msgResponse.Body().default_item_sort() );
+ pStorePanel->LoadPricesheet( &pKVPricesheet );
+
+ // Manually copy over the version number the GC sent down.
+ Assert( pStorePanel->GetPriceSheetForEdit() );
+ pStorePanel->GetPriceSheetForEdit()->SetVersionStamp( msgResponse.Body().price_sheet_version() );
+
+ pStorePanel->ClearPopularItems();
+ for ( int i=0; i<msgResponse.Body().popular_items_size(); ++i )
+ {
+ pStorePanel->AddPopularItem( msgResponse.Body().popular_items(i) );
+ }
+
+ // Store our experiment membership value.
+ EconUI()->SetExperimentValue( msgResponse.Body().experiment_data() );
+
+ // Store the currency and country code.
+ if ( msgResponse.Body().currency() == k_ECurrencyInvalid )
+ {
+ // An invalid currency means the user must contact steam support to have their account set up.
+ OpenStoreStatusDialog( NULL, "#StoreUpdate_ContactSupport", true, false );
+ return true;
+ }
+
+ pStorePanel->SetCurrency( (ECurrency)msgResponse.Body().currency() );
+ pStorePanel->SetCountryCode( msgResponse.Body().country().c_str() );
+ // TODO: Also store the steam provided localization code (not implemented yet in the response).
+
+ // Open the store panel.
+ if ( !bInitialLoad )
+ {
+ // Not an initial load, but store was already up. Re-open it to clean it up.
+ if ( pStorePanel->IsVisible() )
+ {
+ pStorePanel->ShowPanel( true );
+ }
+ }
+ else
+ {
+#ifndef TF_CLIENT_DLL
+ // First time we've loaded the pricesheet, so open the store.
+ // You can remove this if you choose to load the pricesheet on game startup (like TF does)
+ EconUI()->OpenStorePanel( 0, false );
+#endif
+ }
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "store_pricesheet_updated" );
+ if ( event )
+ {
+ gameeventmanager->FireEventClientSide( event );
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePage *CStorePanel::AddPageFromPriceSheet( int iPage )
+{
+ CStorePage* pPage = CreateStorePage( GEconStoreCategoryManager()->GetCategoryFromIndex( iPage ) );
+ pPage->OnPostCreate();
+ pPage->AddActionSignalTarget( this );
+ AddPage( pPage, GEconStoreCategoryManager()->GetCategoryFromIndex( iPage )->m_pchName );
+
+ if ( iPage == 0 )
+ {
+ pPage->SetVisible( true );
+ }
+
+ return pPage;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Populates the storepanel with data from the GC
+//-----------------------------------------------------------------------------
+bool CStorePanel::LoadPricesheet( KeyValuesAD* pKVPricesheet )
+{
+ // Read the store KV file in, and parse it.
+ Verify( m_StoreSheet.InitFromKV( *pKVPricesheet ) );
+
+ // Add our pages
+ for ( int i = 0; i < GEconStoreCategoryManager()->GetNumCategories(); i++ )
+ {
+ // Skip subcategories
+ if ( GEconStoreCategoryManager()->GetCategoryFromIndex( i )->BIsSubcategory() )
+ continue;
+
+ AddPageFromPriceSheet( i );
+ }
+
+ // Clear the cart, since we may have new items.
+ GetCart()->EmptyCart();
+
+ m_bPricesheetLoaded = true;
+
+ return true;
+}
+
+#ifdef _DEBUG
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::ReAddPage( int iPage )
+{
+ CStorePage *pPage = AddPageFromPriceSheet( iPage );
+ if ( pPage )
+ {
+ pPage->InvalidateLayout( true, true );
+ pPage->SetVisible( true );
+ GetPropertySheet()->SetActivePage( pPage );
+ }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets currency type.
+//-----------------------------------------------------------------------------
+void CStorePanel::SetCurrency( ECurrency in_currency )
+{
+ // Do any currency related UI work here.
+ m_eCurrency = in_currency;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the country code. Later this will become an enum.
+//-----------------------------------------------------------------------------
+void CStorePanel::SetCountryCode( const char* in_country )
+{
+ V_strcpy_safe( m_rgchCountry, in_country );
+}
+
+bool CStorePanel::ShouldUpsellStamps( void )
+{
+#ifdef TF_CLIENT_DLL
+ bool bForceShow = false;
+
+ CheckButton *pSupportCheckButton = static_cast< CheckButton* >( FindChildByName( "SupportCommunityMapMakersCheckButton" ) );
+ if ( pSupportCheckButton && pSupportCheckButton->IsVisible() && pSupportCheckButton->IsSelected() )
+ {
+ bForceShow = true;
+ }
+
+ CStoreCart *pCart = EconUI()->GetStorePanel()->GetCart();
+ if ( !pCart || pCart->GetNumEntries() <= 0 )
+ return false;
+
+ if ( !bForceShow )
+ {
+ for ( int i = 0; i < pCart->GetNumEntries(); ++i )
+ {
+ cart_item_t *pItem = pCart->GetItem( i );
+ if ( pItem && pItem->pEntry && pItem->pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Maps ) )
+ {
+ return false;
+ }
+ }
+ }
+
+ if ( !HasValidUpsellStamps() )
+ return false;
+
+ RTime32 rtCurrentTime = CRTime::RTime32TimeCur();
+ RTime32 rtTimeStamp = tf_store_stamp_donation_add_timestamp.GetInt();
+
+ if ( !bForceShow )
+ {
+ if ( rtTimeStamp > rtCurrentTime )
+ {
+ // Time stamp set ahead of current time
+ // Set it back to the current time and save it
+ tf_store_stamp_donation_add_timestamp.SetValue( (int)rtCurrentTime );
+ engine->ClientCmd_Unrestricted( "host_writeconfig" );
+ return false;
+ }
+
+ AccountID_t nAccountID = steamapicontext->SteamUser()->GetSteamID().GetAccountID();
+ int nGroup = nAccountID % TF_STORE_STAMP_UPSELL_GROUPS;
+ RTime32 nBucketTimeOffset = ( TF_STORE_STAMP_UPSELL_COOLDOWN / TF_STORE_STAMP_UPSELL_GROUPS ) * nGroup;
+ RTime32 nBucketedTimeStamp = ( ( rtTimeStamp / TF_STORE_STAMP_UPSELL_COOLDOWN ) + 1 ) * TF_STORE_STAMP_UPSELL_COOLDOWN;
+ nBucketedTimeStamp = MAX( nBucketedTimeStamp, TF_STORE_STAMP_UPSELL_BASELINE );
+ nBucketedTimeStamp += nBucketTimeOffset;
+
+ if ( rtCurrentTime < nBucketedTimeStamp + TF_STORE_STAMP_UPSELL_COOLDOWN )
+ {
+ return false;
+ }
+ }
+
+ tf_store_stamp_donation_add_timestamp.SetValue( (int)rtCurrentTime );
+ engine->ClientCmd_Unrestricted( "host_writeconfig" );
+
+ return true;
+#else
+ return false;
+#endif
+}
+
+bool CStorePanel::HasValidUpsellStamps( void )
+{
+#ifdef TF_CLIENT_DLL
+ int nTotalDonationLevel = 0;
+
+ for ( int i = 0; i < GetItemSchema()->GetMapCount(); i++ )
+ {
+ const MapDef_t* pMap = GetItemSchema()->GetMasterMapDefByIndex( i );
+ if ( pMap->IsCommunityMap() )
+ {
+ int nDonationLevel = MapInfo_GetDonationAmount( steamapicontext->SteamUser()->GetSteamID().GetAccountID(), pMap->pszMapName );
+ nTotalDonationLevel += nDonationLevel;
+
+ // No need to upsell if they've spent $50 on stamps
+ if ( nTotalDonationLevel >= 50 )
+ return false;
+
+ // No need to upsell this map if they've spent more than $15 on it
+ if ( nDonationLevel > 20 )
+ continue;
+
+ // No need to upsell this map if they've played it less than an hour
+ MapStats_t &mapStats = CTFStatPanel::GetMapStats( pMap->GetStatsIdentifier() );
+ int nNumHours = ( mapStats.accumulated.m_iStat[TFMAPSTAT_PLAYTIME] ) / ( 60 /*sec*/ * 60 /*min*/ );
+
+ // No need to upsell this map if they've played it less than 2 hours
+ if ( nNumHours <= 1 )
+ continue;
+
+ int nRelativeDivisor = nNumHours / 4;
+ float flRelativePayoff = ( nRelativeDivisor == 0 ? 0.0f : static_cast< float >( nDonationLevel ) / nRelativeDivisor );
+
+ // No need to upsell this map if they've spend more than $1 per 10 hours
+ if ( flRelativePayoff >= 1.0f )
+ continue;
+
+ return true;
+ }
+ }
+
+ return false;
+#else
+ return false;
+#endif
+}
+
+void CStorePanel::UpsellStamps( void )
+{
+#if defined( TF_CLIENT_DLL )
+ const MapDef_t *pUpsellMap = NULL;
+ float flUpsellRelativePayoff = 1000.0f;
+ int nUpsellNumHours = 0;
+
+ for ( int i = 0; i < GetItemSchema()->GetMapCount(); i++ )
+ {
+ const MapDef_t* pMap = GetItemSchema()->GetMasterMapDefByIndex( i );
+ if ( pMap->IsCommunityMap() )
+ {
+ int nDonationLevel = MapInfo_GetDonationAmount( steamapicontext->SteamUser()->GetSteamID().GetAccountID(), pMap->pszMapName );
+
+ MapStats_t &mapStats = CTFStatPanel::GetMapStats( pMap->GetStatsIdentifier() );
+ int nNumHours = ( mapStats.accumulated.m_iStat[TFMAPSTAT_PLAYTIME] ) / ( 60 /*sec*/ * 60 /*min*/ );
+
+ // No need to upsell this map if they've played it less than 2 hours
+ if ( nNumHours <= 1 )
+ continue;
+
+ int nRelativeDivisor = nNumHours / 4;
+ float flRelativePayoff = ( nRelativeDivisor == 0 ? 0.0f : static_cast< float >( nDonationLevel ) / nRelativeDivisor );
+
+ if ( flUpsellRelativePayoff > flRelativePayoff || ( flUpsellRelativePayoff == flRelativePayoff && nUpsellNumHours < nNumHours ) )
+ {
+ pUpsellMap = pMap;
+ flUpsellRelativePayoff = flRelativePayoff;
+ nUpsellNumHours = nNumHours;
+ }
+ }
+ }
+
+ Assert( pUpsellMap );
+ if ( !pUpsellMap )
+ {
+ // Should have returned false from ShouldUpsell long before we got here
+ return;
+ }
+
+ wchar_t *pwchMapName = g_pVGuiLocalize->Find( pUpsellMap->pszMapNameLocKey );
+
+ char szMapHours[ 8 ];
+ V_snprintf( szMapHours, sizeof( szMapHours ), "%i", nUpsellNumHours );
+
+ wchar_t wszMapHours[ 8 ];
+ g_pVGuiLocalize->ConvertANSIToUnicode( szMapHours, wszMapHours, sizeof( wszMapHours ) );
+
+ wchar_t wchDonationDescription[ 512 ];
+ g_pVGuiLocalize->ConstructString_safe( wchDonationDescription, g_pVGuiLocalize->Find( "#Store_ConfirmStampDonationAddText" ), 2, pwchMapName, wszMapHours );
+
+ CStampUpsellDialog *pDialog = vgui::SETUP_PANEL( new CStampUpsellDialog( "#Store_ConfirmStampDonationAddTitle",
+ wchDonationDescription, g_pVGuiLocalize->Find( "#Store_ConfirmStampDonationAddText2" ),
+ pUpsellMap->mapStampDef, "World Traveler" ) );
+
+ if ( pDialog )
+ {
+ pDialog->Show();
+ vgui::surface()->PlaySound( "ui/vote_started.wav" );
+ }
+#else
+ return;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Attempts to begin a checkout.
+//-----------------------------------------------------------------------------
+void CStorePanel::InitiateCheckout( bool bSkipUpsell )
+{
+ // Check for holiday-restricted items and confirm with user before allowing checkout
+ if ( m_Cart.ContainsHolidayRestrictedItems() )
+ {
+ CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#Store_ConfirmHolidayRestrictionCheckoutTitle", "#Store_ConfirmHolidayRestrictionCheckoutText", "#Store_OK", "#TF_Back", &ConfirmCheckout );
+ if ( pDialog )
+ {
+ pDialog->SetContext( this );
+ }
+ return;
+ }
+ else if ( ShouldShowDx8PurchaseWarning( ) )
+ {
+ CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#Store_ConfirmDx8Summer2015OpPassTitle", "#Store_ConfirmDx8Summer2015OpPassText", "#Store_BuyAnyway", "#Store_NoThanks", &ConfirmCheckout );
+ if ( pDialog )
+ {
+ pDialog->SetContext( this );
+ }
+ return;
+ }
+ else if ( !bSkipUpsell && ShouldUpsellStamps() )
+ {
+ UpsellStamps();
+ return;
+ }
+
+ DoCheckout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+/*static*/ void CStorePanel::ConfirmCheckout( bool bConfirmed, void *pContext )
+{
+ CStorePanel *pStorePanel = ( CStorePanel * )pContext;
+ if ( bConfirmed )
+ {
+ if ( pStorePanel->ShouldUpsellStamps() )
+ {
+ pStorePanel->UpsellStamps();
+ return;
+ }
+ else
+ {
+ pStorePanel->DoCheckout();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+/*static*/ void CStorePanel::ConfirmUpsellStamps( bool bConfirmed, CSchemaItemDefHandle hItemDef, int nSecondsVisible )
+{
+ CStorePanel *pStorePanel = EconUI()->GetStorePanel();
+ if ( bConfirmed )
+ {
+ // Add to cart
+ CStorePage *pPage = dynamic_cast< CStorePage * >( pStorePanel->GetPropertySheet()->GetPage( 3 ) );
+
+ KeyValues *pParams = new KeyValues( "AddItemToCart" );
+ pParams->SetInt( "item_def", hItemDef->GetDefinitionIndex() );
+ pParams->SetInt( "cart_add_type", kCartItem_Purchase );
+ pStorePanel->PostMessage( pPage, pParams );
+
+ pStorePanel->PostMessage( pStorePanel, new KeyValues( "DoCheckout" ), 0.5f );
+ }
+ else
+ {
+ CheckButton *pCheckbox = static_cast< CheckButton* >( pStorePanel->FindChildByName( "SupportCommunityMapMakersCheckButton" ) );
+ if ( pCheckbox )
+ {
+ pCheckbox->SetSelected( false );
+ }
+
+ pStorePanel->DoCheckout();
+ }
+
+#if !defined(NO_STEAM)
+ KeyValues *pKVData = new KeyValues( "TF2StoreStampUpsell" );
+
+ // Create and Send the report
+ // AccountID, AcceptUpsell, CartItemsCount, CartItemsPrice, CartItemsFlags, SecondsVisible, EventTime
+
+ // ID - Auto
+
+ // AcceptUpsell
+ pKVData->SetInt( "AcceptUpsell", bConfirmed ? 1 : 0 );
+
+ // CartItemsCount (up to 100)
+ CStoreCart *pCart = EconUI()->GetStorePanel()->GetCart();
+ pKVData->SetInt( "CartItemsCount", pCart ? clamp( pCart->GetNumEntries(), 0, 100 ) : 0 );
+
+ // CartItemsPriceTotal
+ pKVData->SetInt( "CartItemsPrice", pCart ? pCart->GetTotalPrice() : 0 );
+
+ // CartItemsFlags (8-bits)
+ int nCartItemsFlags = 0;
+ if ( pCart )
+ {
+ for ( int i = 0; i < pCart->GetNumEntries(); ++i )
+ {
+ cart_item_t *pItem = pCart->GetItem( i );
+ if ( !pItem || !pItem->pEntry )
+ continue;
+
+ if ( pItem->pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Weapons ) ) nCartItemsFlags |= 0x0001;
+// else if ( pItem->pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Headgear ) ) nCartItemsFlags |= 0x0002; // DEPRECATED
+// else if ( pItem->pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Misc ) ) nCartItemsFlags |= 0x0004; // DEPRECATED
+ else if ( pItem->pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Tools ) ) nCartItemsFlags |= 0x0008;
+ else if ( pItem->pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Maps ) ) nCartItemsFlags |= 0x0010;
+ else if ( pItem->pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Bundles ) ) nCartItemsFlags |= 0x0020;
+ else if ( pItem->pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_New ) ) nCartItemsFlags |= 0x0040;
+ else if ( pItem->pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Limited ) ) nCartItemsFlags |= 0x0080;
+ else if ( pItem->pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Cosmetics ) ) nCartItemsFlags |= 0x0100;
+ else if ( pItem->pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Taunts ) ) nCartItemsFlags |= 0x0200;
+ }
+ }
+
+ pKVData->SetInt( "CartItemsFlags", nCartItemsFlags );
+
+ // SecondsVisible (up to 2 minutes)
+ pKVData->SetInt( "SecondsVisible", clamp( nSecondsVisible, 0, 120 ) );
+
+ // EventTime
+ pKVData->SetInt( "EventTime", GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch() );
+
+ // Send to DB
+ GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
+#endif // !defined(NO_STEAM)
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::DoCheckout()
+{
+ m_iCheckoutAttempts++;
+ EconUI()->Gamestats_Store( IE_STORE_CHECKOUT_ATTEMPT, NULL, NULL, 0, NULL, m_iCheckoutAttempts );
+
+ // Create the checkout job.
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_Loading", true, false, true );
+ CGCClientJobInitPurchase *pJob = new CGCClientJobInitPurchase( GCClientSystem()->GetGCClient() );
+ pJob->StartJob( NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static void ShowPurchaseInitError( const char *pszError )
+{
+ OpenStoreStatusDialog( NULL, pszError, true, false );
+ EconUI()->Gamestats_Store( IE_STORE_CHECKOUT_FAILURE, NULL, NULL, 0, NULL, EconUI()->GetStorePanel()->GetCheckoutAttempts(), pszError );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Asynchronous job for initiating a checkout from the Steam store.
+//-----------------------------------------------------------------------------
+bool CGCClientJobInitPurchase::BYieldingRunJob( void *pvStartParam )
+{
+ GCSDK::CProtoBufMsg<CMsgGCStorePurchaseInit> msg( k_EMsgGCStorePurchaseInit );
+ GCSDK::CProtoBufMsg<CMsgGCStorePurchaseInitResponse> msgResponse;
+
+ if ( !EconUI()->GetStorePanel() )
+ {
+ // This won't happen under the normal process of using the UI.
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_Unavailable", true, false );
+ return true;
+ }
+
+ CStoreCart *pCart = EconUI()->GetStorePanel()->GetCart();
+ char uilanguage[ 64 ];
+ uilanguage[0] = 0;
+ engine->GetUILanguage( uilanguage, sizeof( uilanguage ) );
+
+ // Populate the message.
+ msg.Body().set_currency( EconUI()->GetStorePanel()->GetCurrency() );
+ msg.Body().set_country( EconUI()->GetStorePanel()->GetCountryCode() );
+ msg.Body().set_language( PchLanguageToELanguage( uilanguage ) );
+
+ // We need to ensure there are more than 0 items in the cart, but no more than MAX_CART_ITEMS.
+ int total_items = pCart->GetTotalItems();
+ if ( total_items <= 0 )
+ {
+ ShowPurchaseInitError( "#StoreCheckout_NoItems" );
+ return true;
+ }
+
+ if ( total_items > MAX_CART_ITEMS )
+ {
+ ShowPurchaseInitError( "#StoreCheckout_TooManyItems" );
+ return true;
+ }
+
+ // Can the items we want to buy actually fit inside our backpack?
+ // This check will include items that we have discovered, but haven't been revealed to the player yet.
+ // So they will sometimes get this with apparently empty slots in their backpack.
+ if ( !InventoryManager()->GetLocalInventory()->CanPurchaseItems( pCart->GetTotalConcreteItems() ) )
+ {
+ const bool bInventoryIsAtMaxSize = InventoryManager()->GetLocalInventory()->GetMaxItemCount() == MAX_NUM_BACKPACK_SLOTS;
+
+ ShowPurchaseInitError( bInventoryIsAtMaxSize ? "#StoreCheckout_NotEnoughRoom_MaxSize" : "#StoreCheckout_NotEnoughRoom" );
+ return true;
+ }
+
+ // Add the items we are requesting.
+ int totalPrice = 0;
+ for ( int i=0; i<pCart->GetNumEntries(); i++ )
+ {
+ cart_item_t *pCartItem = pCart->GetItem( i );
+
+ CGCStorePurchaseInit_LineItem *pNewLineItem = msg.Body().add_line_items();
+
+ pNewLineItem->set_item_def_id( pCartItem->pEntry->GetItemDefinitionIndex() );
+ pNewLineItem->set_quantity( pCartItem->iQuantity );
+ pNewLineItem->set_purchase_type( pCartItem->eType );
+
+ int itemPrice = pCartItem->iQuantity * pCartItem->pEntry->GetCurrentPrice( EconUI()->GetStorePanel()->GetCurrency() );
+ pNewLineItem->set_cost_in_local_currency( itemPrice );
+
+ totalPrice += itemPrice;
+ }
+ EconUI()->GetStorePanel()->SetLastPurchaseAttemptPrice( totalPrice );
+
+ // Request to init this purchase.
+ if ( !BYldSendMessageAndGetReply( msg, 30, &msgResponse, k_EMsgGCStorePurchaseInitResponse ) )
+ {
+ // No transaction has been initialized. The GC isn't responding, so abort.
+ ShowPurchaseInitError( "#StoreCheckout_Unavailable" );
+ return false;
+ }
+
+#ifdef _DEBUG
+ Msg( "CGCClientJobInitPurchase - Result: %d, TxnID: %llu\n", msgResponse.Body().result(), msgResponse.Body().txn_id());
+#endif
+
+ // If we fail at this point Steam hasn't opened a transaction that we need to worry about.
+ if ( !CStorePanel::CheckMessageResult( (EPurchaseResult)msgResponse.Body().result() ) )
+ return false;
+
+ EconUI()->GetStorePanel()->SetTransactionID( msgResponse.Body().txn_id() );
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Immediately add the item corresponding to the item def passed into
+// the cart and checkout. This doesn't required opening the store front
+// so this can be called anywhere.
+//-----------------------------------------------------------------------------
+void CStorePanel::AddToCartAndCheckoutImmediately( item_definition_index_t nDefIndex )
+{
+ if ( GetPriceSheet() && GetCart() && steamapicontext && steamapicontext->SteamUser() )
+ {
+ // Add a the item to the users cart and checkout
+ GetCart()->EmptyCart();
+ AddItemToCartHelper( NULL, nDefIndex, kCartItem_Purchase );
+ DoCheckout();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Cancels a pending transaction, if possible.
+//-----------------------------------------------------------------------------
+void CStorePanel::CheckoutCancel( void )
+{
+ // The player pressed the CANCEL button on the TF2 GAME UI pop-up that appears over
+ // the store interface while they would be occupied with the overlay.
+ //
+ // If they press the CANCEL button on the overlay's authorize dialog a Steam callback
+ // (OnMicroTransactionAuthResponse) happens not this method.
+ //
+ // We don't expect this to happen! Once the transaction has been finalized we have
+ // a transaction ID and the GC and Steam are already doing the negotiations about
+ // where the money comes from and removing it so we can't back out. Allowing users
+ // to click this button results in a race condition that often results in users
+ // getting their money taken with no items because the GC can't resolve the
+ // "Canceled"/"Succeeded" discrepancy.
+ Assert( GetTransactionID() <= 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Attempts to cancel a purchase in progress.
+//-----------------------------------------------------------------------------
+bool CGCClientJobCancelPurchase::BYieldingRunJob( void *pvStartParam )
+{
+ GCSDK::CProtoBufMsg<CMsgGCStorePurchaseCancel> msg( k_EMsgGCStorePurchaseCancel );
+ GCSDK::CProtoBufMsg<CMsgGCStorePurchaseCancelResponse> msgResponse;
+
+ if ( !EconUI()->GetStorePanel() )
+ {
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_Unavailable", true, false );
+ return false;
+ }
+
+ msg.Body().set_txn_id( m_ulTxnID );
+
+ if ( !BYldSendMessageAndGetReply( msg, 10, &msgResponse, k_EMsgGCStorePurchaseCancelResponse ) )
+ {
+ // No response from the GC. Show a failure message.
+ OpenStoreStatusDialog( NULL, "#StoreUpdate_NoGCResponse", true, false );
+ return false;
+ }
+
+#ifdef _DEBUG
+ Msg( "CGCClientJobCancelPurchase Result: %d\n", msgResponse.Body().result() );
+#endif
+
+ // The current transaction has been canceled with the GC.
+ EconUI()->GetStorePanel()->SetTransactionID( 0 );
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when the player completes or cancels an in-progress transaction.
+//-----------------------------------------------------------------------------
+void CStorePanel::OnMicroTransactionAuthResponse( MicroTxnAuthorizationResponse_t *pMicroTxnAuthResponse )
+{
+ Assert( steamapicontext->SteamUserStats() );
+ if ( !steamapicontext->SteamUserStats() )
+ return;
+
+ if ( !pMicroTxnAuthResponse->m_bAuthorized )
+ {
+ const char* pszError = "#StoreCheckout_TransactionCanceled";
+ EconUI()->Gamestats_Store( IE_STORE_CHECKOUT_FAILURE, NULL, NULL, 0, NULL, EconUI()->GetStorePanel()->m_iCheckoutAttempts, pszError );
+ OpenStoreStatusDialog( NULL, pszError, true, false );
+ CGCClientJobCancelPurchase *pJob = new CGCClientJobCancelPurchase( GCClientSystem()->GetGCClient(), GetTransactionID() );
+ pJob->StartJob( NULL );
+ SetTransactionID( 0 );
+ }
+ else
+ {
+ // Replace the existing dialog with one that says "we're finalizing!" and only has an "OK" button.
+ // We let users close this if they want, which can be useful in exceptional circumstances like the
+ // GC crashing, but we don't have them do any work on the GC side -- once the finalization is in
+ // flight we can't take it back.
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_TransactionFinalizing", true, false, false );
+
+ // Finalize the transaction with the GC.
+ m_bShouldFinalize = true;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Look to see if we should finalize the open transaction.
+//-----------------------------------------------------------------------------
+void CStorePanel::OnTick( void )
+{
+ BaseClass::OnTick();
+
+ if ( m_bShouldFinalize )
+ {
+ m_bShouldFinalize = false;
+ FinalizeTransaction();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::FinalizeTransaction( void )
+{
+ // Tell the GC to release the items.
+ CGCClientJobFinalizePurchase *pJob = new CGCClientJobFinalizePurchase( GCClientSystem()->GetGCClient(), GetTransactionID() );
+ pJob->StartJob( NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Tell the GC that the purchase is finalized and we should create the items the player bought.
+// TODO: If something goes wrong here we need to inform the player, but tell them
+// that they MAY have been charged. If STEAM authorized & completed the transaction,
+// but we weren't able to finalize with the GC we won't get our items right away
+// but the player will have been charged.
+//-----------------------------------------------------------------------------
+bool CGCClientJobFinalizePurchase::BYieldingRunJob( void *pvStartParam )
+{
+ GCSDK::CProtoBufMsg<CMsgGCStorePurchaseFinalize> msg( k_EMsgGCStorePurchaseFinalize );
+ GCSDK::CProtoBufMsg<CMsgGCStorePurchaseFinalizeResponse> msgResponse;
+
+ msg.Body().set_txn_id( m_ulTxnID );
+
+ if ( !BYldSendMessageAndGetReply( msg, 10, &msgResponse, k_EMsgGCStorePurchaseFinalizeResponse ) )
+ {
+ // TODO: This is bad! The store might have taken our money, but we weren't able to finalize. How do we handle this?
+ // The message currently says "Unable to confirm success. If successful, your items will be delivered at a later date."
+ // We need to handle this case more gracefully.
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_CompleteButUnfinalized", true, false );
+ // @note Tom Bui & Joe Ludwig: We empty the cart here, just to make sure people don't hit checkout again
+ // and end up with dupes of all their items
+ if ( EconUI()->GetStorePanel() && EconUI()->GetStorePanel()->GetCart() )
+ {
+ EconUI()->GetStorePanel()->GetCart()->EmptyCart();
+ }
+ return false;
+ }
+
+ // Check the message result for errors and handle them.
+ if ( !CStorePanel::CheckMessageResult( (EPurchaseResult)msgResponse.Body().result() ) )
+ return false;
+
+ EconUI()->Gamestats_Store( IE_STORE_CHECKOUT_SUCCESS, NULL, NULL, 0, NULL, EconUI()->GetStorePanel()->GetCheckoutAttempts(), NULL, EconUI()->GetStorePanel()->GetLastPurchaseAttemptPrice(), EconUI()->GetStorePanel()->GetCurrency()+1 );
+ CStoreCart* cart = EconUI()->GetStorePanel()->GetCart();
+ for ( int i=0; i<cart->GetNumEntries(); ++i )
+ {
+ cart_item_t* item = cart->GetItem( i );
+ if ( !item )
+ continue;
+ EconUI()->Gamestats_Store( IE_STORE_CHECKOUT_ITEM, NULL, NULL, 0, item, EconUI()->GetStorePanel()->GetCheckoutAttempts(), NULL, 0, EconUI()->GetStorePanel()->GetCurrency()+1 );
+ }
+
+#ifdef DEBUG
+ Msg( "CGCClientJobFinalizePurchase Result: %d, Num Items: %i, Purchased Items:\n", msgResponse.Body().result(), msgResponse.Body().item_ids_size() );
+ if ( k_EPurchaseResultOK == msgResponse.Body().result() )
+ {
+ for ( int i = 0; i < msgResponse.Body().item_ids_size(); i++ )
+ {
+ Msg( "\t%llu\n", msgResponse.Body().item_ids(i) );
+ }
+ }
+#endif
+
+ EconUI()->GetStorePanel()->SetMostRecentSuccessfulTransactionID( m_ulTxnID );
+
+ // Transaction complete.
+ EconUI()->GetStorePanel()->SetTransactionID( 0 );
+
+ // Clear the cart.
+ EconUI()->GetStorePanel()->GetCart()->EmptyCart();
+
+ // If we were in the cart view, return to the store page
+ CStoreViewCartPanel *pCartPanel = GetStoreViewCartPanel();
+ if ( pCartPanel && pCartPanel->IsVisible() )
+ {
+ pCartPanel->ShowPanel( false );
+ }
+
+ // Let them know everything went well.
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_TransactionCompleted", true, true );
+
+ EconUI()->GetStorePanel()->PostTransactionCompleted();
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle an error from the GC.
+//-----------------------------------------------------------------------------
+bool CStorePanel::CheckMessageResult( EPurchaseResult msgResult )
+{
+ // Take action on the result code.
+ switch ( msgResult )
+ {
+ case k_EPurchaseResultOK:
+ return true;
+ break;
+
+ // GC / Steam errors: // These can all be combined into a single general unrecoverable error case.
+ case k_EPurchaseResultFail: // Generic error.
+ case k_EPurchaseResultInvalidParam: // Invalid parameter.
+ if ( CStorePanel::ShouldShowWarnings() )
+ {
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_Unavailable", true, false );
+ }
+ break;
+
+ // Internal error. Default string in English: "Unable to confirm success. If successful, your items will be
+ // delivered at a later date." Basically "something went real bad wrong and we don't know whether you'll get
+ // your items or not".
+ case k_EPurchaseResultInternalError:
+ if ( CStorePanel::ShouldShowWarnings() )
+ {
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_CompleteButUnfinalized", true, false );
+ }
+ break;
+
+ // Is the GC telling us this user does not have enough backpack space? This check takes into account
+ // any additional backpack space a user might get from upgrading to premium.
+ case k_EPurchaseResultNotEnoughBackpackSpace:
+ if ( CStorePanel::ShouldShowWarnings() )
+ {
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_NotEnoughRoom", true, false );
+ }
+ break;
+
+ case k_EPurchaseResultLimitedQuantityItemsUnavailable:
+ if ( CStorePanel::ShouldShowWarnings() )
+ {
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_LimitedQuantityItemsUnavailable", true, false );
+ }
+ break;
+
+ // errors that should never happen
+ case k_EPurchaseResultNotApproved: // Tried to finalize a transaction that has not yet been approved.
+ case k_EPurchaseResultAlreadyCommitted: // Tried to finalize a transaction that has already been committed.
+ case k_EPurchaseResultWrongCurrency: // Microtransaction's currency does not match user's wallet currency.
+ case k_EPurchaseResultAccountError: // User's account does not exist or is temporarily unavailable.
+ case k_EPurchaseResultTxnNotFound: // Could not find the transaction specified
+ default:
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_InternalError", true, false );
+ break;
+
+ case k_EMicroTxnResultFailedFraudChecks: // Steam thinks this transaction might be fraudulent
+ ShowConfirmDialog( "#StoreCheckout_ContactSupport_Dialog_Title", "#StoreCheckout_ContactSupport", "#StoreCheckout_ContactSupport_Dialog_Btn", "#Cancel", &ContactSupportConfirm );
+ break;
+
+ // User errors:
+ case k_EPurchaseResultUserNotLoggedIn: // User is not logged into Steam.
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_NotLoggedIn", true, false );
+ break;
+ case k_EPurchaseResultInsufficientFunds: // User does not have wallet funds
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_InsufficientFunds", true, false );
+ // This will happen if the user spends the money out of his wallet between funding & finalizing the transaction.
+ break;
+ case k_EPurchaseResultTimedOut: // Time limit for finalization has been exceeded
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_TimedOut", true, false );
+ // TODO: We should find out what happened to the transaction, since it still may be viable.
+ break;
+ case k_EPurchaseResultAcctDisabled: // Steam account is disabled.
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_SteamAccountDisabled", true, false );
+ break;
+ case k_EPurchaseResultAcctCannotPurchase: // Steam account is not allowed to make a purchase
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_SteamAccountNoPurchase", true, false );
+ break;
+
+ // Client state discrepancies:
+ case k_EPurchaseResultOldPriceSheet: // Information on the purchase didn't match the current price sheet
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_OldPriceSheet", false, false );
+
+ // Request a new price sheet. This will clear the store pages and the user's cart.
+ CStorePanel::RequestPricesheet();
+ break;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Shows the store panel.
+//-----------------------------------------------------------------------------
+void CStorePanel::ShowStorePanel( void )
+{
+ GetPropertySheet()->SetVisible( true );
+ m_bShowWarnings = true;
+
+ InvalidateLayout( false, true );
+ Activate();
+
+ if ( !m_bPricesheetLoaded )
+ return;
+
+ bool bStartOnHomePage = true;
+
+ if ( m_bAddStartItemDefToCart )
+ {
+ // Put the specified item in the cart, and go to the cart display screen.
+ GetCart()->EmptyCart();
+
+ CStorePage *pPage = dynamic_cast< CStorePage * >( GetPropertySheet()->GetActivePage() );
+ AddItemToCartHelper( pPage ? pPage->GetPageName() : NULL, m_bAddStartItemDefToCart, kCartItem_Purchase );
+
+ if ( pPage )
+ {
+ pPage->UpdateCart();
+ }
+ }
+ else
+ {
+ if ( m_iStartItemDef == STOREPANEL_SHOW_UPGRADESTEPS )
+ {
+ OpenStoreStatusDialog( NULL, "#TF_Trial_StoreUpgradeExplanation", true, false );
+ }
+ else if ( m_iStartItemDef )
+ {
+ const econ_store_entry_t *pEntry = FindEntryForItemDef( m_iStartItemDef );
+ if ( pEntry )
+ {
+ // !KLUDGE! Post this to a message queue to be handled later, because there
+ // have already been messages posted to scroll to other pages.
+ // VGUI really has a pretty systematic problem of posting WAY too much stuff
+ // to a queue, instead of dispatching it immediately
+ ivgui()->PostMessage( GetVPanel(), new KeyValues("JumpToItem", "ItemDefIndex", m_iStartItemDef), GetVPanel() );
+ //FindAndSelectEntry( pEntry );
+ bStartOnHomePage = false;
+ }
+ }
+
+ CExButton *pCloseButton = dynamic_cast<CExButton*>( FindChildByName("CloseButton") );
+ if ( pCloseButton )
+ {
+ pCloseButton->RequestFocus();
+ }
+ }
+ m_iStartItemDef = 0;
+
+ if ( bStartOnHomePage )
+ {
+ vgui::Panel *pPage = GetPropertySheet()->GetPage(0);
+ if ( pPage )
+ {
+ if ( GetPropertySheet()->GetActivePage() != pPage )
+ {
+ GetPropertySheet()->SetActivePage( pPage );
+ }
+ else
+ {
+ // VGUI doesn't tell the starting active page that it's active, so we post a pageshow to it
+ ivgui()->PostMessage( pPage->GetVPanel(), new KeyValues("PageShow"), GetPropertySheet()->GetVPanel() );
+ }
+ }
+ }
+
+ vgui::Panel *pPage = GetPropertySheet()->GetActivePage();
+ if ( pPage )
+ {
+ pPage->SetVisible( true );
+ }
+
+ // 6/13/2011
+ // Note: We no longer check for new items when the player enters
+ // the store. This is confusing and no longer necessary now that
+ // the notification system lets us push item discoveries to the player.
+ // InventoryManager()->ShowItemsPickedUp( true, false );
+}
+
+void CStorePanel::OnJumpToItem( KeyValues *pParams )
+{
+ const econ_store_entry_t *pEntry = FindEntryForItemDef( pParams->GetInt( "ItemDefIndex", -1 ) );
+ Assert( pEntry );
+ if ( pEntry )
+ {
+ FindAndSelectEntry( pEntry );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Open_Store( const CCommand &args )
+{
+ int iItemDef = ( args.ArgC() > 1 ) ? atoi(args[1]) : 0;
+ bool bToFeatured = ( ( ( args.ArgC() > 2 ) ? atoi(args[2]) : 0 ) != 0 );
+
+ EconUI()->OpenStorePanel( iItemDef, bToFeatured );
+}
+ConCommand open_store( "open_store", Open_Store, "Open the in-game store", FCVAR_NONE );
+
+//==================================================================================================
+// CART
+//==================================================================================================
+CStoreCart::CStoreCart( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreCart::AddToCart( const econ_store_entry_t *pEntry, const char* pszPageName, ECartItemType eCartItemType )
+{
+ // Steam doesn't allow purchases with more than 256 items in a single transaction
+ if ( GetTotalItems() >= 255 )
+ return;
+
+ if ( pEntry->m_bIsMarketItem )
+ {
+ CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinition( pEntry->GetItemDefinitionIndex() );
+ Assert( pItemDef );
+ if ( !pItemDef )
+ return;
+
+ if ( !CBaseAdPanel::CheckForRequiredSteamComponents( "#StoreUpdate_SteamRequired", "#MMenu_OverlayRequired" ) )
+ return;
+
+ 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 );
+ }
+ return;
+ }
+
+ int iIndex = GetIndexForEntry( pEntry, eCartItemType );
+ if ( iIndex == m_Items.InvalidIndex() )
+ {
+ iIndex = m_Items.AddToTail();
+ m_Items[iIndex].pEntry = pEntry;
+ m_Items[iIndex].iQuantity = 0;
+ m_Items[iIndex].eType = eCartItemType;
+ }
+
+ m_Items[iIndex].iQuantity++;
+
+ EconUI()->Gamestats_Store( IE_STORE_ITEM_ADDED_TO_CART, NULL, pszPageName, 0, &m_Items[iIndex] );
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "cart_updated" );
+ if ( event )
+ {
+ gameeventmanager->FireEventClientSide( event );
+ }
+
+ vgui::surface()->PlaySound( "ui/item_store_add_to_cart.wav" );
+
+ // find the cart button associated with the active page we are using
+ CExButton *pCartButton = dynamic_cast< CExButton * >( EconUI()->GetStorePanel()->GetActivePage()->FindChildByName( "CartButton", true ) );
+ if ( pCartButton )
+ {
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pCartButton->GetParent(), "AddToCartBlink" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreCart::RemoveFromCart( int iIndex )
+{
+ if ( iIndex >= 0 && iIndex < m_Items.Count() )
+ {
+ // play item's "drop" sound
+ if ( m_Items[iIndex].pEntry )
+ {
+ CEconItemDefinition *pDef = ItemSystem()->GetStaticDataForItemByDefIndex( m_Items[iIndex].pEntry->GetItemDefinitionIndex() );
+ const char *soundFilename = pDef->GetDefinitionString( "drop_sound", "ui/item_default_drop.wav" );
+
+ vgui::surface()->PlaySound( soundFilename );
+ }
+
+ m_Items[iIndex].iQuantity--;
+
+ EconUI()->Gamestats_Store( IE_STORE_ITEM_REMOVED_FROM_CART, NULL, NULL, 0, &m_Items[iIndex] );
+
+ if ( m_Items[iIndex].iQuantity <= 0 )
+ {
+ m_Items.Remove(iIndex);
+ }
+ }
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "cart_updated" );
+ if ( event )
+ {
+ gameeventmanager->FireEventClientSide( event );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreCart::EmptyCart( void )
+{
+ m_Items.Purge();
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "cart_updated" );
+ if ( event )
+ {
+ gameeventmanager->FireEventClientSide( event );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CStoreCart::GetIndexForEntry( const econ_store_entry_t *pEntry, ECartItemType eCartItemType ) const
+{
+ FOR_EACH_VEC( m_Items, i )
+ {
+ if ( m_Items[i].pEntry == pEntry && m_Items[i].eType == eCartItemType )
+ return i;
+ }
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CStoreCart::GetTotalItems( void ) const
+{
+ int iTotal = 0;
+ FOR_EACH_VEC( m_Items, i )
+ {
+ const cart_item_t &item = m_Items[i];
+ iTotal += item.iQuantity;
+ }
+ return iTotal;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CStoreCart::GetTotalConcreteItems( void ) const
+{
+ int iTotal = 0;
+ FOR_EACH_VEC( m_Items, i )
+ {
+ const cart_item_t &item = m_Items[i];
+#ifdef TF_CLIENT_DLL
+ CEconItemDefinition *pDef = ItemSystem()->GetStaticDataForItemByDefIndex( item.pEntry->GetItemDefinitionIndex() );
+ iTotal += pDef->GetNumConcreteItems() * item.iQuantity;
+#else
+ // Kyle says: this logic is totally wrong but we don't *have* a CStrike
+ // economy so I don't feel bad using this to get the build working.
+ iTotal += item.iQuantity;
+#endif
+ }
+ return iTotal;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+item_price_t cart_item_t::GetDisplayPrice() const
+{
+ const float fPriceScale = eType == kCartItem_TryOutUpgrade
+ ? GetEconPriceSheet()->GetPreviewPeriodDiscount()
+ : IsRentalCartItemType( eType )
+ ? pEntry->GetRentalPriceScale()
+ : 1.0f;
+
+ return (item_price_t)( pEntry->GetCurrentPrice( EconUI()->GetStorePanel()->GetCurrency() ) * fPriceScale ) * iQuantity;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+item_price_t CStoreCart::GetTotalPrice( void ) const
+{
+ item_price_t unTotal = 0;
+ FOR_EACH_VEC( m_Items, i )
+ {
+ unTotal += m_Items[i].GetDisplayPrice();
+ }
+ return unTotal;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CStoreCart::ContainsHolidayRestrictedItems() const
+{
+ FOR_EACH_VEC( m_Items, i )
+ {
+ CEconItemDefinition *pItemDef = ItemSystem()->GetStaticDataForItemByDefIndex( m_Items[i].pEntry->GetItemDefinitionIndex() );
+ if ( !pItemDef )
+ continue;
+
+ // If the string isn't empty, assume it has a restriction
+ if ( pItemDef->GetHolidayRestriction() )
+ return true;
+
+ const bundleinfo_t *pBundle = pItemDef->GetBundleInfo();
+ if ( pBundle )
+ {
+ FOR_EACH_VEC( pBundle->vecItemDefs, j )
+ {
+ if ( pBundle->vecItemDefs[j]->GetHolidayRestriction() )
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CStoreCart::ContainsItemDefinition( item_definition_index_t unItemDef ) const
+{
+ FOR_EACH_VEC( m_Items, i )
+ {
+ if ( m_Items[i].pEntry->GetItemDefinitionIndex() == unItemDef )
+ return true;
+ }
+
+ return false;
+}
+
+//================================================================================================================================
+// STORE STATUS DIALOG
+//================================================================================================================================
+static vgui::DHANDLE<CStoreStatusDialog> g_StoreStatusPanel;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStoreStatusDialog::CStoreStatusDialog( vgui::Panel *pParent, const char *pElementName ) : BaseClass( pParent, "StoreStatusDialog" )
+{
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
+ SetScheme(scheme);
+ SetProportional( true );
+ m_bNotifyOnCancel = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreStatusDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/UI/econ/store/v1/StoreStatusDialog.res" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreStatusDialog::OnCommand( const char *command )
+{
+ bool bClose = false;
+
+ if ( !Q_stricmp( command, "close" ) )
+ {
+ bClose = true;
+
+ if ( m_bShowOnExit )
+ {
+ InventoryManager()->ShowItemsPickedUp( true );
+ }
+ }
+ else if ( !Q_stricmp( command, "forceclose" ) )
+ {
+ bClose = true;
+ }
+
+ if ( bClose )
+ {
+ if ( m_bNotifyOnCancel )
+ {
+ EconUI()->GetStorePanel()->CheckoutCancel();
+ m_bNotifyOnCancel = false;
+ }
+
+ m_bShowOnExit = false;
+ TFModalStack()->PopModal( this );
+ SetVisible( false );
+ MarkForDeletion();
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreStatusDialog::UpdateSchemeForVersion()
+{
+ InvalidateLayout( false, true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreStatusDialog::ShowStatusUpdate( bool bAllowClose, bool bShowOnExit, bool bCancel )
+{
+ CExButton *pButton = dynamic_cast<CExButton*>( FindChildByName("CloseButton") );
+ if ( pButton )
+ {
+ pButton->SetVisible( bAllowClose );
+ pButton->SetEnabled( bAllowClose );
+ if ( bCancel )
+ {
+ pButton->SetText( "#Store_CANCEL" );
+ m_bNotifyOnCancel = true;
+ }
+ else
+ {
+ pButton->SetText( "#Store_OK" );
+ m_bNotifyOnCancel = false;
+ }
+ }
+
+ m_bShowOnExit = bShowOnExit;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void SetupStoreStatusDialog( vgui::Panel *pParent )
+{
+ // If we parent it to something, we get problems when another panel pops over it,
+ // because the modal dialog is no longer visible. So prevent parenting status dialogs.
+ pParent = NULL;
+
+ if ( !g_StoreStatusPanel.Get() )
+ {
+ g_StoreStatusPanel = vgui::SETUP_PANEL( new CStoreStatusDialog( pParent, NULL ) );
+ }
+ g_StoreStatusPanel->SetVisible( true );
+ if ( !pParent )
+ {
+ g_StoreStatusPanel->MakePopup();
+ }
+
+ g_StoreStatusPanel->MoveToFront();
+ g_StoreStatusPanel->SetKeyBoardInputEnabled(true);
+ g_StoreStatusPanel->SetMouseInputEnabled(true);
+ TFModalStack()->PushModal( g_StoreStatusPanel );
+}
+
+void OpenStoreStatusDialog( vgui::Panel *pParent, const char *pszText, bool bAllowClose, bool bShowOnExit, bool bCancel )
+{
+ // Figure out who we should be parented to
+ if ( !pParent )
+ {
+ CStoreViewCartPanel *pCartPanel = GetStoreViewCartPanel();
+ if ( pCartPanel && pCartPanel->IsVisible() )
+ {
+ pParent = pCartPanel;
+ }
+ else if ( EconUI()->GetStorePanel() && EconUI()->GetStorePanel()->IsVisible() )
+ {
+ pParent = EconUI()->GetStorePanel();
+ }
+ }
+
+ SetupStoreStatusDialog( pParent );
+ g_StoreStatusPanel->UpdateSchemeForVersion();
+ g_StoreStatusPanel->SetDialogVariable( "updatetext", g_pVGuiLocalize->Find( pszText ) );
+ g_StoreStatusPanel->ShowStatusUpdate( bAllowClose, bShowOnExit, bCancel );
+}
+
+void CloseStoreStatusDialog( void )
+{
+ if ( g_StoreStatusPanel )
+ {
+ g_StoreStatusPanel->OnCommand( "forceclose" );
+ }
+}
+
+#ifdef _DEBUG
+CON_COMMAND( re_add_store_page, "" )
+{
+ CStorePanel *pStorePanel = EconUI()->GetStorePanel();
+ if ( !pStorePanel )
+ return;
+
+ if ( args.ArgC() != 2 )
+ {
+ Warning( "Need a page index argument.\n" );
+ return;
+ }
+
+ pStorePanel->ReAddPage( atoi( args[ 1 ] ) );
+}
+#endif
+
+// these are disabled because they don't work anymore.
+#ifdef ENABLE_TEST_PURCHASE_COMMANDS
+
+//================================================================================================================================
+//================================================================================================================================
+//================================================================================================================================
+// TEST JOBS
+//================================================================================================================================
+//================================================================================================================================
+//================================================================================================================================
+
+
+
+
+CON_COMMAND( store_getuserdata, "Gets the latest pricesheet from the GC" )
+{
+ RTime32 rTimeVersion = 0;
+ if ( args.ArgC() > 1 )
+ {
+ rTimeVersion = V_atoi( args[1] );
+ }
+
+ CGCClientJobTESTGetUserData *pJob = new CGCClientJobTESTGetUserData( GCClientSystem()->GetGCClient(), rTimeVersion );
+ pJob->StartJob( NULL );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Dev-only command and job for initiating a purchase
+//-----------------------------------------------------------------------------
+class CGCClientJobTESTInitPurchase : public GCSDK::CGCClientJob
+{
+public:
+ CGCClientJobTESTInitPurchase( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) {}
+ virtual bool BYieldingRunJob( void *pvStartParam )
+ {
+ GCSDK::CGCMsg<MsgGCStorePurchaseInit_t> msg( k_EMsgGCStorePurchaseInit );
+ GCSDK::CGCMsg<MsgGCStorePurchaseInitResponse_t> msgResponse;
+
+ CStorePanel *pStore = GetStorePanel();
+ if ( !pStore )
+ {
+ Msg( "store_initpurchase: Store has not been initialized\n" );
+ return false;
+ }
+
+ CStoreCart *pCart = pStore->GetCart();
+
+ msg.Body().m_eCurrency = pStore->GetCurrency();
+ V_strncpy( msg.Body().m_rgchCountry, pStore->GetCountryCode(), sizeof( msg.Body().m_rgchCountry ) );
+ msg.Body().m_eLanguage = steamapicontext->SteamApps() ? PchLanguageToELanguage( steamapicontext->SteamApps()->GetCurrentGameLanguage() ) : k_Lang_English;
+ msg.Body().m_cLineItems = pCart->GetNumEntries();
+
+ // We really should check for zero items here and not let the purchase go through.
+ // Also, GetTotalItems() needs to be < 256
+
+ for ( int i = 0; i < pCart->GetNumEntries(); i++ )
+ {
+ cart_item_t *pCartItem = pCart->GetItem( i );
+ msg.AddUint16Data( pCartItem->pEntry->m_usDefIndex );
+ msg.AddUint8Data( pCartItem->iQuantity );
+ msg.AddUint16Data( pCartItem->iQuantity * pCartItem->pEntry->GetPrice( (ECurrency)msg.Body().m_eCurrency ) );
+ }
+
+ if ( !BYldSendMessageAndGetReply( msg, 10, &msgResponse, k_EMsgGCStorePurchaseInitResponse ) )
+ {
+ Msg( "store_initpurchase: No response from the GC\n" );
+ return false;
+ }
+
+ Msg( "Got response. Result: %d, TxnID: %llu\n", msgResponse.Body().m_eResult, msgResponse.Body().m_unTxnID );
+
+ return true;
+ }
+};
+
+CON_COMMAND( store_initpurchase, "Simulates pressing the checkout button in the store" )
+{
+ CGCClientJobTESTInitPurchase *pJob = new CGCClientJobTESTInitPurchase( GCClientSystem()->GetGCClient() );
+ pJob->StartJob( NULL );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Dev-only command and job for canceling a purchase
+//-----------------------------------------------------------------------------
+class CGCClientJobTESTCancelPurchase : public GCSDK::CGCClientJob
+{
+public:
+ CGCClientJobTESTCancelPurchase( GCSDK::CGCClient *pGCClient, uint64 ulTxnID ) : GCSDK::CGCClientJob( pGCClient ), m_ulTxnID( ulTxnID ) {}
+ virtual bool BYieldingRunJob( void *pvStartParam )
+ {
+ GCSDK::CGCMsg<MsgGCStorePurchaseCancel_t> msg( k_EMsgGCStorePurchaseCancel );
+ GCSDK::CGCMsg<MsgGCStorePurchaseCancelResponse_t> msgResponse;
+
+ msg.Body().m_ulTxnID = m_ulTxnID;
+
+ if ( !BYldSendMessageAndGetReply( msg, 10, &msgResponse, k_EMsgGCStorePurchaseCancelResponse ) )
+ {
+ Msg( "store_cancelpurchase: No response from the GC\n" );
+ return false;
+ }
+
+ Msg( "Got response. Result: %d\n", msgResponse.Body().m_eResult );
+ return true;
+ }
+
+private:
+ uint64 m_ulTxnID;
+};
+
+CON_COMMAND( store_cancelpurchase, "<TxnID> Simulates cancelling a purchase" )
+{
+
+ if ( args.ArgC() < 2 )
+ {
+ Msg( store_cancelpurchase_command.GetHelpText() );
+ }
+
+ uint64 ulTxnID;
+ ulTxnID = V_atoui64( args[1] );
+
+ CGCClientJobTESTCancelPurchase *pJob = new CGCClientJobTESTCancelPurchase( GCClientSystem()->GetGCClient(), ulTxnID );
+ pJob->StartJob( NULL );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Dev-only command and job for finalizing a purchase
+//-----------------------------------------------------------------------------
+class CGCClientJobTESTFinalizePurchase : public GCSDK::CGCClientJob
+{
+public:
+ CGCClientJobTESTFinalizePurchase( GCSDK::CGCClient *pGCClient, uint64 ulTxnID ) : GCSDK::CGCClientJob( pGCClient ), m_ulTxnID( ulTxnID ) {}
+ virtual bool BYieldingRunJob( void *pvStartParam )
+ {
+ GCSDK::CGCMsg<MsgGCStorePurchaseFinalize_t> msg( k_EMsgGCStorePurchaseFinalize );
+ GCSDK::CGCMsg<MsgGCStorePurchaseFinalizeResponse_t> msgResponse;
+
+ msg.Body().m_ulTxnID = m_ulTxnID;
+
+ if ( !BYldSendMessageAndGetReply( msg, 10, &msgResponse, k_EMsgGCStorePurchaseFinalizeResponse ) )
+ {
+ Msg( "store_finalizepurchase: No response from the GC\n" );
+ return false;
+ }
+
+ Msg( "Got response. Result: %d, Num Items: %d, Purchased Items:\n", msgResponse.Body().m_eResult, msgResponse.Body().m_cItemIDs );
+ if ( k_EPurchaseResultOK == msgResponse.Body().m_eResult )
+ {
+ for ( uint32 i = 0; i < msgResponse.Body().m_cItemIDs; i++ )
+ {
+ uint64 ulItemID;
+ if ( !msgResponse.BReadUint64Data( &ulItemID ) )
+ {
+ Msg( "Error: Underflow in msgResponse\n" );
+ break;
+ }
+
+ Msg( "\t%llu\n", ulItemID );
+ }
+ }
+
+ return true;
+ }
+
+private:
+ uint64 m_ulTxnID;
+};
+
+CON_COMMAND( store_finalizepurchase, "<TxnID> Simulates finalizing a purchase" )
+{
+
+ if ( args.ArgC() < 2 )
+ {
+ Msg( store_finalizepurchase_command.GetHelpText() );
+ }
+
+ uint64 ulTxnID;
+ ulTxnID = V_atoui64( args[1] );
+
+ CGCClientJobTESTFinalizePurchase *pJob = new CGCClientJobTESTFinalizePurchase( GCClientSystem()->GetGCClient(), ulTxnID );
+ pJob->StartJob( NULL );
+}
+
+#endif
diff --git a/game/client/econ/store/store_panel.h b/game/client/econ/store/store_panel.h
new file mode 100644
index 0000000..8ac0f0c
--- /dev/null
+++ b/game/client/econ/store/store_panel.h
@@ -0,0 +1,236 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef STORE_PANEL_H
+#define STORE_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_controls/PropertyDialog.h"
+#include "econ_ui.h"
+#include "GameEventListener.h"
+#include "store_page.h"
+#include "econ_store.h"
+#include "econ_gcmessages.h"
+#include "steam/isteamuser.h"
+
+#define MAX_CART_ITEMS 256
+
+#define STOREPANEL_SHOW_UPGRADESTEPS -1
+
+class CStorePage;
+
+// An "item" in the cart.
+struct cart_item_t
+{
+ const econ_store_entry_t *pEntry;
+ int iQuantity;
+ ECartItemType eType;
+
+ item_price_t GetDisplayPrice() const;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: The cart that contains items the player is purchasing
+//-----------------------------------------------------------------------------
+class CStoreCart
+{
+public:
+ CStoreCart( void );
+
+ void AddToCart( const econ_store_entry_t *pEntry, const char* pszPageName, ECartItemType eCartItemType );
+ void RemoveFromCart( int iEntryIndex );
+ void EmptyCart( void );
+
+ // Returns the total number of items in the cart
+ int GetTotalItems( void ) const;
+ int GetTotalConcreteItems( void ) const;
+ // Returns the number of different entries in the cart (ignoring quantities)
+ int GetNumEntries( void ) const { return m_Items.Count(); }
+ cart_item_t *GetItem( int iIndex ) { return ( ( GetNumEntries() > 0 ) ? &m_Items[iIndex] : NULL ); }
+
+ item_price_t GetTotalPrice( void ) const;
+
+ bool ContainsHolidayRestrictedItems() const;
+ bool ContainsItemDefinition( item_definition_index_t unItemDef ) const;
+
+private:
+ int GetIndexForEntry( const econ_store_entry_t *pEntry, ECartItemType eCartItemType ) const;
+
+private:
+ CUtlVector<cart_item_t> m_Items;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CStorePanel : public vgui::PropertyDialog, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CStorePanel, vgui::PropertyDialog );
+public:
+ CStorePanel( Panel *parent );
+ virtual ~CStorePanel();
+
+#ifdef _DEBUG
+ void ReAddPage( int iPage );
+#endif
+
+ // UI Layout
+ 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);
+ virtual void FireGameEvent( IGameEvent *event );
+ void SetPreventClosure( bool bPrevent ) { m_bPreventClosure = bPrevent; }
+ void StartAtItemDef( int iItemDef, bool bAddToCart ) { m_iStartItemDef = iItemDef; m_bAddStartItemDefToCart = bAddToCart; };
+
+ virtual void OnTick();
+
+ // Steam Interaction
+ STEAM_CALLBACK( CStorePanel, OnMicroTransactionAuthResponse, MicroTxnAuthorizationResponse_t, m_CallbackMicroTransactionAuthResponse );
+
+ // GC Management
+ static bool CheckMessageResult( EPurchaseResult msgResult );
+ void FinalizeTransaction( void );
+ virtual void PostTransactionCompleted( void ) { return; }
+
+ // Cart Management
+ CStoreCart *GetCart( void ) { return &m_Cart; }
+ void ShowStorePanel( void );
+ bool ShouldUpsellStamps( void );
+ bool HasValidUpsellStamps( void );
+ void UpsellStamps( void );
+ static void ConfirmUpsellStamps( bool bConfirmed, CSchemaItemDefHandle hItemDef, int nSecondsVisible );
+ void InitiateCheckout( bool bSkipUpsell );
+ void CheckoutCancel( void );
+ virtual void OnAddToCart( void ) {}
+ void AddToCartAndCheckoutImmediately( item_definition_index_t nDefIndex );
+
+ // Pricesheet Management
+ static bool IsPricesheetLoaded( void ) { return CStorePanel::m_bPricesheetLoaded; }
+ static bool ShouldShowWarnings( void ) { return CStorePanel::m_bShowWarnings; }
+ static void SetShouldShowWarnings( bool bShow ) { CStorePanel::m_bShowWarnings = bShow; }
+ static void RequestPricesheet( void );
+
+ const CEconStorePriceSheet *GetPriceSheet( void ) { return &m_StoreSheet; }
+ CEconStorePriceSheet *GetPriceSheetForEdit( void ) { return &m_StoreSheet; }
+ bool LoadPricesheet( KeyValuesAD* pKVPricesheet );
+ void SetCurrency( ECurrency in_currency );
+ ECurrency GetCurrency( void ) { return m_eCurrency; }
+ void SetCountryCode( const char* in_country );
+ char* GetCountryCode( void ) { return m_rgchCountry; }
+ const econ_store_entry_t *GetFeaturedEntry( void );
+ void SetMostRecentSuccessfulTransactionID( uint64 inID ) { m_unMostRecentSuccessfulTransaction = inID; }
+ uint64 GetMostRecentSuccessfulTransactionID() const { return m_unMostRecentSuccessfulTransaction; }
+ virtual void SetTransactionID( uint64 inID ) { m_unTransactionID = inID; }
+ uint64 GetTransactionID( void ) { return m_unTransactionID; }
+
+ int GetCheckoutAttempts() { return m_iCheckoutAttempts; }
+ void SetLastPurchaseAttemptPrice( int totalPrice ) { m_iLastPurchaseAttemptPrice = totalPrice; }
+ int GetLastPurchaseAttemptPrice() { return m_iLastPurchaseAttemptPrice; }
+
+ void ClearPopularItems( void ) { m_vPopularItems.Purge(); }
+ void AddPopularItem( uint32 iItemDef ) { m_vPopularItems.AddToTail(iItemDef); }
+ const CUtlVector<uint32>& GetPopularItems( void ) const { return m_vPopularItems; }
+
+ MESSAGE_FUNC( OnStartShopping, "StartShopping" );
+ MESSAGE_FUNC( OnFindAndSelectFeaturedItem, "FindAndSelectFeaturedItem" );
+ MESSAGE_FUNC_PARAMS( OnItemLinkClicked, "URLClicked", pParams );
+ MESSAGE_FUNC_PARAMS( OnJumpToItem, "JumpToItem", pParams );
+ MESSAGE_FUNC( DoCheckout, "DoCheckout" );
+
+protected:
+ void ParseStoreKV( void );
+ CStorePage *AddPageFromPriceSheet( int iPage );
+
+ void FindAndSelectEntry( const econ_store_entry_t *pEntry );
+ const econ_store_entry_t *FindEntryForItemDef( int iItemDef ) { return m_StoreSheet.GetEntry( iItemDef ); }
+
+ virtual CStorePage *CreateStorePage( const CEconStoreCategoryManager::StoreCategory_t *pPageData );
+
+ bool ShouldShowDx8PurchaseWarning( ) const;
+
+protected:
+ static void ConfirmCheckout( bool bConfirmed, void *pContext );
+
+ static bool m_bPricesheetLoaded;
+ static bool m_bShowWarnings;
+ bool m_bPreventClosure;
+ int m_iStartItemDef;
+ bool m_bAddStartItemDefToCart;
+ CStoreCart m_Cart;
+ CEconStorePriceSheet m_StoreSheet;
+
+ ECurrency m_eCurrency;
+ char m_rgchCountry[3]; // This will change to an enum soon.
+ uint64 m_unTransactionID;
+ uint64 m_unMostRecentSuccessfulTransaction;
+
+ bool m_bShouldFinalize;
+ bool m_bOGSLogging;
+
+ int m_iCheckoutAttempts;
+ int m_iLastPurchaseAttemptPrice;
+
+ CUtlVector<uint32> m_vPopularItems;
+};
+
+void OpenStoreStatusDialog( vgui::Panel *pParent, const char *pszText, bool bAllowClose, bool bShowOnExit, bool bCancel=false );
+void CloseStoreStatusDialog( void );
+
+//-----------------------------------------------------------------------------
+// Purpose: Asynchronous job for getting the price sheet from the GC
+//-----------------------------------------------------------------------------
+class CGCClientJobGetUserData : public GCSDK::CGCClientJob
+{
+public:
+ CGCClientJobGetUserData( GCSDK::CGCClient *pGCClient, RTime32 rTimeVersion ) : GCSDK::CGCClientJob( pGCClient ), m_RTimeVersion( rTimeVersion ) {}
+ virtual bool BYieldingRunJob( void *pvStartParam );
+
+private:
+ RTime32 m_RTimeVersion;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Asynchronous job for initiating a checkout from the Steam store.
+//-----------------------------------------------------------------------------
+class CGCClientJobInitPurchase : public GCSDK::CGCClientJob
+{
+public:
+ CGCClientJobInitPurchase( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) {}
+ virtual bool BYieldingRunJob( void *pvStartParam );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Asynchronous job for canceling a purchase in progress.
+//-----------------------------------------------------------------------------
+class CGCClientJobCancelPurchase : public GCSDK::CGCClientJob
+{
+public:
+ CGCClientJobCancelPurchase( GCSDK::CGCClient *pGCClient, uint64 ulTxnID ) : GCSDK::CGCClientJob( pGCClient ), m_ulTxnID( ulTxnID ) {}
+ virtual bool BYieldingRunJob( void *pvStartParam );
+
+private:
+ uint64 m_ulTxnID;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Asynchronous job for finalizing a purchase with the GC.
+//-----------------------------------------------------------------------------
+class CGCClientJobFinalizePurchase : public GCSDK::CGCClientJob
+{
+public:
+ CGCClientJobFinalizePurchase( GCSDK::CGCClient *pGCClient, uint64 ulTxnID ) : GCSDK::CGCClientJob( pGCClient ), m_ulTxnID( ulTxnID ) {}
+ virtual bool BYieldingRunJob( void *pvStartParam );
+
+private:
+ uint64 m_ulTxnID;
+};
+
+#endif // STORE_PANEL_H
diff --git a/game/client/econ/store/store_preview_item.cpp b/game/client/econ/store/store_preview_item.cpp
new file mode 100644
index 0000000..5179f1a
--- /dev/null
+++ b/game/client/econ/store/store_preview_item.cpp
@@ -0,0 +1,418 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store_page.h"
+#include "vgui/ISurface.h"
+#include "vgui/IInput.h"
+#include "vgui/ILocalize.h"
+#include "gamestringpool.h"
+#include "econ_item_inventory.h"
+#include "econ_item_system.h"
+#include "store_preview_item.h"
+#include "item_model_panel.h"
+#include "econ_ui.h"
+#include "store/store_panel.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+DECLARE_BUILD_FACTORY_DEFAULT_TEXT( CPreviewRotButton, CPreviewRotButton );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePreviewItemPanel::CStorePreviewItemPanel( vgui::Panel *pParent, const char *pResFile, const char *pPanelName, CStorePage *pOwner )
+: EditablePanel( pParent, "storepreviewitem" )
+{
+ m_pOwner = pOwner;
+ m_pResFile = pResFile != NULL ? pResFile : ( ShouldUseNewStore() ? "Resource/UI/econ/store/v2/StorePreviewItemPanel.res" : "Resource/UI/econ/store/v1/StorePreviewItemPanel.res" );
+ m_pDataTextRichText = NULL;
+ m_iCurrentIconPosition = 0;
+ m_iState = PS_ITEM;
+ m_pIconsMoveLeftButton = NULL;
+ m_pIconsMoveRightButton = NULL;
+
+ m_pItemFullImage = new CItemModelPanel( this, "PreviewItemModelPanel" );
+
+ SetDialogVariable("selectiontitle", g_pVGuiLocalize->Find("#TF_NoSelection") );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePreviewItemPanel::~CStorePreviewItemPanel()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePreviewItemPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( m_pResFile );
+
+ // Apply attribute changes to CItemModelPanel
+ m_pItemFullImage->UpdatePanels();
+
+ m_pIconsMoveLeftButton = dynamic_cast<CExButton*>( FindChildByName("IconsMoveLeftButton") );
+ if ( m_pIconsMoveLeftButton )
+ {
+ m_pIconsMoveLeftButton->AddActionSignalTarget( this );
+ }
+ m_pIconsMoveRightButton = dynamic_cast<CExButton*>( FindChildByName("IconsMoveRightButton") );
+ if ( m_pIconsMoveRightButton )
+ {
+ m_pIconsMoveRightButton->AddActionSignalTarget( this );
+ }
+
+ m_pDataTextRichText = dynamic_cast<CEconItemDetailsRichText*>( FindChildByName( "DetailsRichText" ) );
+ if ( m_pDataTextRichText )
+ {
+ m_pDataTextRichText->SetURLClickedHandler( EconUI()->GetStorePanel() );
+ m_pDataTextRichText->AllowItemSetLinks( true );
+ }
+
+ // Then find all our item icons
+ m_pItemIcons.Purge();
+ CStorePreviewItemIcon *pItemIcon = NULL;
+ int iIcon = 1;
+ do
+ {
+ pItemIcon = dynamic_cast<CStorePreviewItemIcon*>( FindChildByName( VarArgs("ItemIcon%d",iIcon)) );
+ if ( pItemIcon )
+ {
+ m_pItemIcons.AddToTail( pItemIcon );
+ if ( m_pOwner )
+ {
+ pItemIcon->GetItemPanel()->SetTooltip( m_pOwner->GetItemTooltip(), "" );
+ }
+ }
+ iIcon++;
+ } while ( pItemIcon );
+
+ // Update our item icons. Hide them all first. The code below will unhide ones used.
+ for ( int i = 0; i < m_pItemIcons.Count(); i++ )
+ {
+ m_pItemIcons[i]->SetVisible( false );
+ }
+
+ // Start with the item itself showing
+ SetState( PS_ITEM );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePreviewItemPanel::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+
+ // center the icons
+ int iNumItemIcons = 0;
+ FOR_EACH_VEC( m_pItemIcons, i )
+ {
+ if ( m_pItemIcons[i]->IsVisible() )
+ {
+ ++iNumItemIcons;
+ }
+ }
+ if ( iNumItemIcons )
+ {
+ int iCenterX = GetWide() / 2;
+ int interval = XRES(2);
+ int totalWidth = (iNumItemIcons * m_pItemIcons[0]->GetWide()) + (interval * (iNumItemIcons - 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++;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePreviewItemPanel::OnCommand( const char *command )
+{
+ if ( !Q_strnicmp( command, "close", 5 ) )
+ {
+ PostActionSignal(new KeyValues("HidePreview"));
+ SetVisible( false );
+ return;
+ }
+ else if ( !Q_stricmp( command, "icons_left" ) )
+ {
+ m_iCurrentIconPosition = MAX( m_iCurrentIconPosition - 1, 0 );
+ UpdateIcons();
+ }
+ else if ( !Q_stricmp( command, "icons_right" ) )
+ {
+ // It's only visible if we can still move right.
+ m_iCurrentIconPosition++;
+ UpdateIcons();
+ }
+ else
+ {
+ engine->ClientCmd( const_cast<char *>( command ) );
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePreviewItemPanel::OnRotButtonDown( KeyValues *data )
+{
+ int iRotDelta = data->GetInt( "rot", 0 );
+ m_iCurrentRotation = iRotDelta;
+ vgui::ivgui()->AddTickSignal( GetVPanel(), 33 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePreviewItemPanel::OnRotButtonUp( void )
+{
+ m_iCurrentRotation = 0;
+ vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePreviewItemPanel::PreviewItem( int iClass, CEconItemView *pItem, const econ_store_entry_t* pEntry /*= NULL*/ )
+{
+ m_iCurrentIconPosition = 0;
+ m_item = *pItem;
+
+ if ( m_item.IsValid() )
+ {
+ m_pItemFullImage->SetItem( &m_item );
+ if ( m_pDataTextRichText )
+ {
+ m_pDataTextRichText->SetLimitedItem( pEntry && pEntry->m_bLimited );
+ m_pDataTextRichText->UpdateDetailsForItem( m_item.GetItemDefinition() );
+ }
+
+ SetDialogVariable("selectiontitle", m_item.GetItemName() );
+
+ CExButton *pButton = dynamic_cast<CExButton*>( FindChildByName( "AddToCartButton" ) );
+ if ( pButton )
+ {
+ const CEconStorePriceSheet *pPriceSheet = EconUI()->GetStorePanel()->GetPriceSheet();
+ if ( pPriceSheet )
+ {
+ const econ_store_entry_t *pStoreEntry = pPriceSheet->GetEntry( pItem->GetItemDefIndex() );
+ if ( pStoreEntry->m_bIsMarketItem )
+ {
+ SetDialogVariable( "storeaddtocart", g_pVGuiLocalize->Find( "#Store_ViewMarket" ) );
+ }
+ else
+ {
+ SetDialogVariable( "storeaddtocart", g_pVGuiLocalize->Find( "#Store_AddToCart" ) );
+ }
+ }
+ }
+
+ }
+
+ InvalidateLayout();
+ UpdateIcons();
+
+ if ( m_iState == PS_PLAYER )
+ {
+ SetState( PS_ITEM );
+ }
+
+ Panel *pAddToCart = FindChildByName( "AddToCartButton" );
+ if ( pAddToCart )
+ {
+ pAddToCart->RequestFocus();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePreviewItemPanel::SetState( preview_state_t iState )
+{
+ // Only reset the position when moving from to items/details
+ if ( iState == PS_DETAILS || iState == PS_ITEM )
+ {
+ m_iCurrentIconPosition = 0;
+ }
+
+ m_iState = iState;
+
+ if ( m_pDataTextRichText )
+ {
+ m_pDataTextRichText->SetVisible( m_iState == PS_DETAILS );
+ }
+ m_pItemFullImage->SetVisible( m_iState == PS_ITEM );
+
+ UpdateIcons();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePreviewItemPanel::UpdateIcons( void )
+{
+ 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() > 0 )
+ {
+ 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 );
+ }
+ }
+ }
+ }
+ }
+ 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 );
+ }
+ }
+
+ if( m_pIconsMoveLeftButton )
+ m_pIconsMoveLeftButton->SetVisible( (m_iCurrentIconPosition > 0) );
+ if( m_pIconsMoveRightButton )
+ m_pIconsMoveRightButton->SetVisible( bAdditionalIcons );
+
+ InvalidateLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePreviewItemPanel::OnTick( void )
+{
+ BaseClass::OnTick();
+
+ if ( !IsVisible() )
+ {
+ vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+ return;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePreviewItemPanel::OnItemIconSelected( KeyValues *data )
+{
+ if ( m_iState == PS_DETAILS )
+ {
+ int iIcon = data->GetInt( "icon", 0 );
+ CEconItemView *pItem = m_pItemIcons[iIcon]->GetItemPanel()->GetItem();
+ if ( pItem )
+ {
+ if ( m_pDataTextRichText )
+ {
+ m_pDataTextRichText->UpdateDetailsForItem( pItem->GetStaticData() );
+ }
+ SetDialogVariable("selectiontitle", pItem->GetItemName() );
+ }
+ }
+ else
+ {
+ SetState( PS_ITEM );
+ }
+}
+
+//================================================================================================================
+// PREVIEW ROT BUTTON
+//================================================================================================================
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPreviewRotButton::OnMousePressed(vgui::MouseCode code)
+{
+ BaseClass::OnMousePressed( code );
+
+ if ( IsSelected() )
+ {
+ KeyValues *pCommand = GetCommand();
+ PostActionSignal(new KeyValues("RotButtonDown", "rot", pCommand->GetString("command", "0") ));
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPreviewRotButton::OnMouseReleased(vgui::MouseCode code)
+{
+ if ( IsSelected() )
+ {
+ PostActionSignal(new KeyValues("RotButtonUp"));
+ }
+
+ BaseClass::OnMouseReleased( code );
+}
diff --git a/game/client/econ/store/store_preview_item.h b/game/client/econ/store/store_preview_item.h
new file mode 100644
index 0000000..6493b0c
--- /dev/null
+++ b/game/client/econ/store/store_preview_item.h
@@ -0,0 +1,100 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef STORE_PREVIEW_ITEM_H
+#define STORE_PREVIEW_ITEM_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <vgui_controls/Panel.h>
+#include "econ_controls.h"
+#include "store_page.h"
+
+enum preview_state_t
+{
+ PS_ITEM,
+ PS_PLAYER,
+ PS_DETAILS,
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Button that handles the rotation of the preview model.
+//-----------------------------------------------------------------------------
+class CPreviewRotButton : public CExButton
+{
+ DECLARE_CLASS_SIMPLE( CPreviewRotButton, CExButton );
+public:
+ CPreviewRotButton( vgui::Panel *parent, const char *name, const char *text, vgui::Panel *pActionSignalTarget = NULL, const char *cmd = NULL ) :
+ CExButton( parent, name, text, pActionSignalTarget, cmd )
+ {
+ }
+ CPreviewRotButton( vgui::Panel *parent, const char *name, const wchar_t *wszText, vgui::Panel *pActionSignalTarget = NULL, const char *cmd = NULL ) :
+ CExButton( parent, name, wszText, pActionSignalTarget, cmd )
+ {
+ }
+
+ virtual void OnMousePressed(vgui::MouseCode code);
+ virtual void OnMouseReleased(vgui::MouseCode code);
+
+ // Our fire action signal does nothing, because it's all done in mouse pressed/released
+ virtual void FireActionSignal( void ) { return; }
+};
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CStorePreviewItemPanel : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CStorePreviewItemPanel, vgui::EditablePanel );
+public:
+ CStorePreviewItemPanel( vgui::Panel *pParent, const char *pResFile, const char *pPanelName, CStorePage *pOwner );
+ virtual ~CStorePreviewItemPanel();
+
+ CStorePage *GetOwningStorePage() { return m_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 );
+ virtual void SetState( preview_state_t iState );
+
+ // Subclass interface.
+ virtual int GetPreviewTeam() const { return 0; }
+
+ MESSAGE_FUNC_PARAMS( OnRotButtonDown, "RotButtonDown", data );
+ MESSAGE_FUNC( OnRotButtonUp, "RotButtonUp" );
+
+ MESSAGE_FUNC_PARAMS( OnItemIconSelected, "ItemIconSelected", data );
+
+protected:
+ virtual void UpdateIcons( void );
+
+protected:
+ const char *m_pResFile;
+ CUtlVector<CStorePreviewItemIcon*> m_pItemIcons;
+
+ int m_iCurrentIconPosition;
+
+ CEconItemDetailsRichText *m_pDataTextRichText;
+ CItemModelPanel *m_pItemFullImage;
+
+ CEconItemView m_item;
+ preview_state_t m_iState;
+
+ int m_iCurrentRotation;
+ CExButton *m_pIconsMoveLeftButton;
+ CExButton *m_pIconsMoveRightButton;
+
+ CStorePage *m_pOwner;
+};
+
+#endif // STORE_PREVIEW_ITEM_H
diff --git a/game/client/econ/store/store_viewcart.cpp b/game/client/econ/store/store_viewcart.cpp
new file mode 100644
index 0000000..a071dc5
--- /dev/null
+++ b/game/client/econ/store/store_viewcart.cpp
@@ -0,0 +1,387 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store_viewcart.h"
+#include "vgui/IInput.h"
+#include "baseviewport.h"
+#include "iclientmode.h"
+#include "ienginevgui.h"
+#include "econ_item_inventory.h"
+#include <vgui/ILocalize.h>
+#include "econ_item_system.h"
+#include "item_model_panel.h"
+#include "vgui_controls/ScrollBarSlider.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+DECLARE_BUILD_FACTORY( CCartViewItemEntry );
+
+//-----------------------------------------------------------------------------
+// Purpose: Basic help dialog
+//-----------------------------------------------------------------------------
+CStoreViewCartPanel::CStoreViewCartPanel( Panel *parent ) : Frame(parent, "store_viewcart_panel")
+{
+ // Store 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 );
+
+ ListenForGameEvent( "gameui_hidden" );
+ ListenForGameEvent( "cart_updated" );
+
+ m_pItemEntryKVs = NULL;
+ m_pClientArea = new EditablePanel(this, "ClientArea");
+ m_pItemListContainer = new vgui::EditablePanel( this, "ItemListContainer" );
+ m_pItemListContainerScroller = new vgui::ScrollableEditablePanel( m_pClientArea, m_pItemListContainer, "ItemListContainerScroller" );
+ m_pPurchaseFooter = new EditablePanel(m_pItemListContainer, "PurchaseFooter");
+ m_pEmptyCartLabel = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStoreViewCartPanel::~CStoreViewCartPanel()
+{
+ if ( m_pItemEntryKVs )
+ {
+ m_pItemEntryKVs->deleteThis();
+ m_pItemEntryKVs = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreViewCartPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( ShouldUseNewStore() ? "Resource/UI/econ/store/v2/StoreViewCartPanel.res" : "Resource/UI/econ/store/v1/StoreViewCartPanel.res" );
+ m_bReapplyItemKVs = true;
+
+ m_pItemListContainerScroller->GetScrollbar()->SetAutohideButtons( true );
+ m_pEmptyCartLabel = dynamic_cast<vgui::Label*>( m_pClientArea->FindChildByName("EmptyCartLabel") );
+
+ m_pFeaturedItemImage = dynamic_cast<vgui::ImagePanel*>( m_pItemListContainer->FindChildByName("FeaturedItemSymbol") );
+ if ( m_pFeaturedItemImage )
+ {
+ m_pFeaturedItemImage->SetMouseInputEnabled( false );
+ m_pFeaturedItemImage->SetKeyBoardInputEnabled( false );
+ }
+
+ CExButton *pCheckoutButton = dynamic_cast<CExButton*>( m_pClientArea->FindChildByName("CheckoutButton") );
+ if ( pCheckoutButton )
+ {
+ pCheckoutButton->AddActionSignalTarget( this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreViewCartPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ KeyValues *pItemKV = inResourceData->FindKey( "item_entry_kv" );
+ if ( pItemKV )
+ {
+ if ( m_pItemEntryKVs )
+ {
+ m_pItemEntryKVs->deleteThis();
+ }
+ m_pItemEntryKVs = new KeyValues("item_entry_kv");
+ pItemKV->CopySubkeys( m_pItemEntryKVs );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreViewCartPanel::PerformLayout( void )
+{
+ if ( GetVParent() )
+ {
+ int w,h;
+ vgui::ipanel()->GetSize( GetVParent(), w, h );
+ SetBounds(0,0,w,h);
+ }
+
+ if ( m_bReapplyItemKVs )
+ {
+ m_bReapplyItemKVs = false;
+
+ if ( m_pItemEntryKVs )
+ {
+ FOR_EACH_VEC( m_pItemEntries, i )
+ {
+ m_pItemEntries[i]->ApplySettings( m_pItemEntryKVs );
+ m_pItemEntries[i]->InvalidateLayout();
+ }
+ }
+ }
+
+ BaseClass::PerformLayout();
+
+ if ( m_pItemEntries.Count() )
+ {
+ int iTall = m_pItemEntries[0]->GetTall();
+ m_pItemListContainer->SetSize( m_pItemListContainer->GetWide(), (iTall * m_pItemEntries.Count()) + m_pPurchaseFooter->GetTall() );
+ m_pItemListContainerScroller->InvalidateLayout( true );
+ m_pItemListContainerScroller->GetScrollbar()->InvalidateLayout( true );
+
+ int iX,iY;
+ m_pItemEntries[0]->GetPos( iX, iY );
+ FOR_EACH_VEC( m_pItemEntries, i )
+ {
+ iY = (iTall * i);
+ m_pItemEntries[i]->SetPos( iX, iY );
+ }
+
+ m_pPurchaseFooter->SetVisible( true );
+ m_pPurchaseFooter->SetPos( 0, iTall * m_pItemEntries.Count() );
+ }
+ else
+ {
+ m_pItemListContainer->SetSize( m_pItemListContainer->GetWide(), 100 );
+ m_pItemListContainer->InvalidateLayout( true );
+ m_pItemListContainerScroller->InvalidateLayout( true );
+ m_pPurchaseFooter->SetVisible( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreViewCartPanel::ShowPanel(bool bShow)
+{
+ if ( bShow )
+ {
+ InvalidateLayout( false, true );
+ Activate();
+
+ CExButton *pCloseButton = dynamic_cast<CExButton*>( FindChildByName("CloseButton") );
+ if ( pCloseButton )
+ {
+ pCloseButton->RequestFocus();
+ }
+
+ // don't display the WA sales tax outside of the US
+ vgui::Panel *pPanel = FindChildByName( "WashingtonStateSalesTaxLabel", true );
+ if ( pPanel )
+ {
+ pPanel->SetVisible( FStrEq( EconUI()->GetStorePanel()->GetCountryCode(), "US" ) == true );
+ }
+ }
+
+ SetVisible( bShow );
+
+ if ( bShow )
+ {
+ UpdateCartItemList();
+ m_pItemListContainerScroller->GetScrollbar()->SetValue( 0 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreViewCartPanel::FireGameEvent( IGameEvent *event )
+{
+ const char * type = event->GetName();
+
+ if ( Q_strcmp(type, "gameui_hidden") == 0 )
+ {
+ ShowPanel( false );
+ }
+ else if ( Q_strcmp(type, "cart_updated") == 0 )
+ {
+ UpdateCartItemList();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreViewCartPanel::UpdateCartItemList( void )
+{
+ CStoreCart *pCart = EconUI()->GetStorePanel()->GetCart();
+ int iNumEntriesInCart = pCart->GetNumEntries();
+
+ // Update the item count
+ wchar_t wszCount[16];
+ _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", pCart->GetTotalItems() );
+ wchar_t wzLocalized[32];
+ g_pVGuiLocalize->ConstructString_safe( wzLocalized, g_pVGuiLocalize->Find( "#Store_CartItems" ), 1, wszCount );
+ m_pClientArea->SetDialogVariable("storecart", wzLocalized );
+
+ // Create / Update all the item entries
+ if ( m_pItemEntries.Count() < iNumEntriesInCart )
+ {
+ for ( int i = m_pItemEntries.Count(); i < iNumEntriesInCart; i++ )
+ {
+ CCartViewItemEntry *pPanel = vgui::SETUP_PANEL( new CCartViewItemEntry( m_pItemListContainer, VarArgs("itementry%d", i) ) );
+ pPanel->ApplySettings( m_pItemEntryKVs );
+ m_pItemEntries.AddToTail( pPanel );
+ }
+ }
+ else
+ {
+ for ( int i = m_pItemEntries.Count()-1; i >= iNumEntriesInCart; i-- )
+ {
+ m_pItemEntries[i]->MarkForDeletion();
+ m_pItemEntries.Remove( i );
+ }
+ }
+
+ if ( m_pEmptyCartLabel )
+ {
+ m_pEmptyCartLabel->SetVisible( iNumEntriesInCart == 0 );
+ }
+
+ InvalidateLayout( true );
+
+ if ( !iNumEntriesInCart )
+ return;
+
+ bool bFeaturedImagePanelVisible = false;
+
+ // Set all the entries up
+ FOR_EACH_VEC( m_pItemEntries, i )
+ {
+ if ( i >= iNumEntriesInCart )
+ {
+ m_pItemEntries[i]->SetVisible( false );
+ continue;
+ }
+
+ cart_item_t *pCartItem = pCart->GetItem(i);
+ m_pItemEntries[i]->SetEntry( pCartItem, i );
+ m_pItemEntries[i]->SetVisible( true );
+
+ // If we're the featured item, show it
+ if ( pCartItem && pCartItem->pEntry == EconUI()->GetStorePanel()->GetFeaturedEntry() )
+ {
+ bFeaturedImagePanelVisible = true;
+ int iX, iY;
+ m_pItemEntries[i]->GetPos( iX, iY );
+ m_pFeaturedItemImage->SetPos( iX, iY + m_pItemEntries[i]->GetTall() - m_pFeaturedItemImage->GetTall() );
+ }
+ }
+
+ if ( m_pFeaturedItemImage->IsVisible() != bFeaturedImagePanelVisible )
+ {
+ m_pFeaturedItemImage->SetVisible( bFeaturedImagePanelVisible );
+ }
+
+ // Update total price
+ item_price_t unTotalPrice = pCart->GetTotalPrice();
+ wchar_t wzLocalizedPrice[ kLocalizedPriceSizeInChararacters ];
+ MakeMoneyString( wzLocalizedPrice, ARRAYSIZE( wzLocalizedPrice ), unTotalPrice, EconUI()->GetStorePanel()->GetCurrency() );
+ m_pPurchaseFooter->SetDialogVariable( "totalprice", wzLocalizedPrice );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreViewCartPanel::OnCommand( const char *command )
+{
+ if ( !Q_stricmp( command, "close" ) )
+ {
+ ShowPanel( false );
+ }
+ else if ( !Q_strnicmp( command, "remove", 6 ) )
+ {
+ int iIndex = atoi(command+6);
+ if ( iIndex >= 0 && iIndex < m_pItemEntries.Count() )
+ {
+ CStoreCart *pCart = EconUI()->GetStorePanel()->GetCart();
+ pCart->RemoveFromCart( iIndex );
+ }
+ return;
+ }
+ else if ( !Q_stricmp( command, "checkout" ) )
+ {
+ EconUI()->GetStorePanel()->InitiateCheckout( false );
+ }
+ else
+ {
+ engine->ClientCmd( const_cast<char *>( command ) );
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+static vgui::DHANDLE<CStoreViewCartPanel> g_StoreViewCartPanel;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStoreViewCartPanel *OpenStoreViewCartPanel( void )
+{
+ if (!g_StoreViewCartPanel.Get())
+ {
+ g_StoreViewCartPanel = vgui::SETUP_PANEL( new CStoreViewCartPanel( NULL ) );
+ g_StoreViewCartPanel->InvalidateLayout( false, true );
+ }
+
+ engine->ClientCmd_Unrestricted( "gameui_activate" );
+ g_StoreViewCartPanel->ShowPanel( true );
+
+ return g_StoreViewCartPanel;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStoreViewCartPanel *GetStoreViewCartPanel( void )
+{
+ return g_StoreViewCartPanel.Get();
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCartViewItemEntry::SetEntry( cart_item_t *pEntry, int iEntryIndex )
+{
+ m_pEntry = pEntry;
+ SetDialogVariable( "quantity", pEntry->iQuantity );
+
+ int iSubTotal = pEntry->GetDisplayPrice();
+
+ wchar_t wzLocalizedPrice[ kLocalizedPriceSizeInChararacters ];
+ MakeMoneyString( wzLocalizedPrice, ARRAYSIZE( wzLocalizedPrice ), iSubTotal, EconUI()->GetStorePanel()->GetCurrency() );
+ SetDialogVariable("price", wzLocalizedPrice );
+
+ CItemModelPanel *pItemPanel = dynamic_cast<CItemModelPanel*>( FindChildByName("itempanel") );
+ if ( pItemPanel )
+ {
+ CEconItemView ItemData;
+ ItemData.Init( pEntry->pEntry->GetItemDefinitionIndex(), AE_UNIQUE, AE_USE_SCRIPT_VALUE, true );
+ pItemPanel->SetItem( &ItemData );
+ }
+
+ CExButton *pRemoveButton = dynamic_cast<CExButton*>( FindChildByName("RemoveButton") );
+ if ( pRemoveButton )
+ {
+ pRemoveButton->SetCommand( VarArgs("remove%d",iEntryIndex) );
+ pRemoveButton->AddActionSignalTarget( GetStoreViewCartPanel() );
+ }
+} \ No newline at end of file
diff --git a/game/client/econ/store/store_viewcart.h b/game/client/econ/store/store_viewcart.h
new file mode 100644
index 0000000..76d3586
--- /dev/null
+++ b/game/client/econ/store/store_viewcart.h
@@ -0,0 +1,75 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef STORE_VIEWCART_H
+#define STORE_VIEWCART_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_controls/Frame.h"
+#include "vgui_controls/ScrollableEditablePanel.h"
+#include "GameEventListener.h"
+#include "store/store_panel.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Shows a single item in the cart
+//-----------------------------------------------------------------------------
+class CCartViewItemEntry : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CCartViewItemEntry, vgui::EditablePanel );
+public:
+ CCartViewItemEntry( vgui::Panel *parent, const char *name ) : vgui::EditablePanel(parent,name)
+ {
+ m_pEntry = NULL;
+ }
+
+ void SetEntry( cart_item_t *pEntry, int iEntryIndex );
+ cart_item_t *GetEntry( void ) { return m_pEntry; }
+
+private:
+ cart_item_t *m_pEntry;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CStoreViewCartPanel : public vgui::Frame, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CStoreViewCartPanel, vgui::Frame );
+public:
+ CStoreViewCartPanel( Panel *parent );
+ virtual ~CStoreViewCartPanel();
+
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout( void );
+ virtual void OnCommand( const char *command );
+ virtual void ShowPanel( bool bShow );
+ virtual void FireGameEvent( IGameEvent *event );
+
+ void UpdateCartItemList( void );
+
+private:
+ vgui::EditablePanel *m_pClientArea;
+ vgui::EditablePanel *m_pPurchaseFooter;
+ KeyValues *m_pItemEntryKVs;
+ bool m_bReapplyItemKVs;
+ vgui::Label *m_pEmptyCartLabel;
+ vgui::ImagePanel *m_pFeaturedItemImage;
+
+ vgui::EditablePanel *m_pItemListContainer;
+ vgui::ScrollableEditablePanel *m_pItemListContainerScroller;
+ CUtlVector<CCartViewItemEntry*> m_pItemEntries;
+
+ CPanelAnimationVar( int, m_iSheetInsetBottom, "sheetinset_bottom", "32" );
+};
+
+CStoreViewCartPanel *OpenStoreViewCartPanel( void );
+CStoreViewCartPanel *GetStoreViewCartPanel( void );
+
+#endif // STORE_VIEWCART_H