diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/client/econ/store | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/client/econ/store')
| -rw-r--r-- | game/client/econ/store/store_page.cpp | 2233 | ||||
| -rw-r--r-- | game/client/econ/store/store_page.h | 424 | ||||
| -rw-r--r-- | game/client/econ/store/store_page_halloween.cpp | 85 | ||||
| -rw-r--r-- | game/client/econ/store/store_page_halloween.h | 49 | ||||
| -rw-r--r-- | game/client/econ/store/store_page_new.cpp | 232 | ||||
| -rw-r--r-- | game/client/econ/store/store_page_new.h | 107 | ||||
| -rw-r--r-- | game/client/econ/store/store_panel.cpp | 2175 | ||||
| -rw-r--r-- | game/client/econ/store/store_panel.h | 236 | ||||
| -rw-r--r-- | game/client/econ/store/store_preview_item.cpp | 418 | ||||
| -rw-r--r-- | game/client/econ/store/store_preview_item.h | 100 | ||||
| -rw-r--r-- | game/client/econ/store/store_viewcart.cpp | 387 | ||||
| -rw-r--r-- | game/client/econ/store/store_viewcart.h | 75 |
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 |