Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2176 lines
71 KiB

5 years ago
//========= 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