Team Fortress 2 Source Code as on 22/4/2020
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.

1672 lines
54 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Common objects and utilities related to the in-game item store
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #include "econ_store.h"
  8. #include "gcsdk/enumutils.h"
  9. #if defined(CLIENT_DLL)
  10. #include "econ_ui.h"
  11. #include "store/store_panel.h"
  12. #include "econ_item_inventory.h"
  13. #else
  14. #include "gcsdk/gcconstants.h"
  15. #endif
  16. #if defined(CLIENT_DLL) || defined(GAME_DLL)
  17. #include "econ_item_system.h"
  18. #endif
  19. // For localization
  20. #include "tier3/tier3.h"
  21. #include "vgui/ILocalize.h"
  22. #include "tier0/icommandline.h"
  23. #ifdef CLIENT_DLL
  24. // For formatting in locale
  25. #include <string>
  26. #include <sstream>
  27. #endif // CLIENT_DLL
  28. // memdbgon must be the last include file in a .cpp file!!!
  29. #include "tier0/memdbgon.h"
  30. ConVar store_version( "store_version", "1", FCVAR_CLIENTDLL | FCVAR_HIDDEN | FCVAR_ARCHIVE, "Which version of the store to display." );
  31. ENUMSTRINGS_START( ECurrency )
  32. { k_ECurrencyUSD, "USD" },
  33. { k_ECurrencyGBP, "GBP" },
  34. { k_ECurrencyEUR, "EUR" },
  35. { k_ECurrencyRUB, "RUB" },
  36. { k_ECurrencyBRL, "BRL" },
  37. { k_ECurrencyJPY, "JPY" },
  38. { k_ECurrencyNOK, "NOK" },
  39. { k_ECurrencyIDR, "IDR" },
  40. { k_ECurrencyMYR, "MYR" },
  41. { k_ECurrencyPHP, "PHP" },
  42. { k_ECurrencySGD, "SGD" },
  43. { k_ECurrencyTHB, "THB" },
  44. { k_ECurrencyVND, "VND" },
  45. { k_ECurrencyKRW, "KRW" },
  46. { k_ECurrencyTRY, "TRY" },
  47. { k_ECurrencyUAH, "UAH" },
  48. { k_ECurrencyMXN, "MXN" },
  49. { k_ECurrencyCAD, "CAD" },
  50. { k_ECurrencyAUD, "AUD" },
  51. { k_ECurrencyNZD, "NZD" },
  52. { k_ECurrencyPLN, "PLN" },
  53. { k_ECurrencyCHF, "CHF" },
  54. { k_ECurrencyCNY, "CNY" },
  55. { k_ECurrencyTWD, "TWD" },
  56. { k_ECurrencyHKD, "HKD" },
  57. { k_ECurrencyINR, "INR" },
  58. { k_ECurrencyAED, "AED" },
  59. { k_ECurrencySAR, "SAR" },
  60. { k_ECurrencyZAR, "ZAR" },
  61. { k_ECurrencyCOP, "COP" },
  62. { k_ECurrencyPEN, "PEN" },
  63. { k_ECurrencyCLP, "CLP" },
  64. { k_ECurrencyInvalid, "Invalid" }
  65. ENUMSTRINGS_REVERSE( ECurrency, k_ECurrencyInvalid )
  66. ENUMSTRINGS_START( EPurchaseState )
  67. { k_EPurchaseStateInvalid, "Invalid" },
  68. { k_EPurchaseStateInit, "Init" },
  69. { k_EPurchaseStateWaitingForAuthorization, "WaitingForAuthorization" },
  70. { k_EPurchaseStatePending, "Pending" },
  71. { k_EPurchaseStateComplete, "Complete" },
  72. { k_EPurchaseStateFailed, "Failed" },
  73. { k_EPurchaseStateCanceled, "Canceled" },
  74. { k_EPurchaseStateRefunded, "Refunded" },
  75. { k_EPurchaseStateChargeback, "Chargeback" },
  76. { k_EPurchaseStateChargebackReversed, "Chargeback Reversed" },
  77. ENUMSTRINGS_END( EPurchaseState )
  78. ENUMSTRINGS_START( EPurchaseResult )
  79. { k_EPurchaseResultOK, "OK" },
  80. { k_EPurchaseResultFail, "Fail" },
  81. { k_EPurchaseResultInvalidParam, "InvalidParam" },
  82. { k_EPurchaseResultInternalError, "InternalError" },
  83. { k_EPurchaseResultNotApproved, "NotApproved" },
  84. { k_EPurchaseResultAlreadyCommitted, "AlreadyCommitted" },
  85. { k_EPurchaseResultUserNotLoggedIn, "UserNotLoggedIn" },
  86. { k_EPurchaseResultWrongCurrency, "WrongCurrency" },
  87. { k_EPurchaseResultAccountError, "AccountError" },
  88. { k_EPurchaseResultInsufficientFunds, "InsufficientFunds Reversed" },
  89. { k_EPurchaseResultTimedOut, "TimedOut" },
  90. { k_EPurchaseResultAcctDisabled, "AcctDisabled" },
  91. { k_EPurchaseResultAcctCannotPurchase, "AcctCannotPurchase" },
  92. { k_EMicroTxnResultFailedFraudChecks, "PurchaseFailedSupport" }, // this string is mismatched so "fraud" doesn't appear in the client code
  93. { k_EPurchaseResultOldPriceSheet, "OldPriceSheet" },
  94. { k_EPurchaseResultTxnNotFound, "TxnNotFound" },
  95. ENUMSTRINGS_END( EPurchaseResult )
  96. ENUMSTRINGS_START( EGCTransactionAuditReason )
  97. { k_EGCTransactionAudit_GCTransactionCompleted, "Completed" },
  98. { k_EGCTransactionAudit_GCTransactionInit, "Init" },
  99. { k_EGCTransactionAudit_GCTransactionPostInit, "Post Init" },
  100. { k_EGCTransactionAudit_GCTransactionFinalize, "Finalize" },
  101. { k_EGCTransactionAudit_GCTransactionFinalizeFailed, "Finalize Failed" },
  102. { k_EGCTransactionAudit_GCTransactionCanceled, "Canceled" },
  103. { k_EGCTransactionAudit_SteamFailedMismatch, "Steam Failed State Mismatch" },
  104. { k_EGCTransactionAudit_GCRemovePurchasedItems, "Remove Purchased Items" },
  105. { k_EGCTransactionAudit_GCTransactionInsert, "Transaction Insert" },
  106. { k_EGCTransactionAudit_GCTransactionCompletedPostChargeback, "Completed (Post-Chargeback)" },
  107. ENUMSTRINGS_END( EGCTransactionAuditReason )
  108. //-----------------------------------------------------------------------------
  109. // Purpose:
  110. //-----------------------------------------------------------------------------
  111. void econ_store_entry_t::InitCategoryTags( const char *pTags )
  112. {
  113. // Default to "unrentable".
  114. m_fRentalPriceScale = 1.0f;
  115. if ( !pTags || !pTags[0] )
  116. return;
  117. // Cache tags - this pointer comes out of a KeyValues that isn't deleted
  118. m_pchCategoryTags = pTags;
  119. // Split the tags apart
  120. CUtlVector< char * > vecTokens;
  121. V_SplitString( pTags, "+", vecTokens );
  122. // Sanity check the results of V_SplitString()
  123. #ifdef _DEBUG
  124. const int nTagsLength = V_strlen( pTags );
  125. int nSepCount = 0; // # of separators
  126. for ( int i = 0; i < nTagsLength; ++i )
  127. {
  128. if ( pTags[i] == '+' )
  129. ++nSepCount;
  130. }
  131. // Assert( ( vecTokens.Size() - 1 ) == nSepCount );
  132. #endif
  133. // Calculate the maximum that we want to charge for this rental.
  134. float fMaxRentalPriceScale = 0.0f;
  135. // Generate symbols for each tag
  136. FOR_EACH_VEC( vecTokens, i )
  137. {
  138. CategoryTag_t info;
  139. info.m_strName = vecTokens[i];
  140. info.m_unID = CEconStoreCategoryManager::GetCategoryID( vecTokens[i] );
  141. m_vecCategoryTags.AddToTail( info );
  142. const float fCategoryRentalPriceScale = GetEconPriceSheet()->GetRentalPriceScale( vecTokens[i] );
  143. fMaxRentalPriceScale = MAX( fMaxRentalPriceScale, fCategoryRentalPriceScale );
  144. }
  145. m_fRentalPriceScale = fMaxRentalPriceScale <= 0.0f || fMaxRentalPriceScale >= 100.0f
  146. ? 1.0f
  147. : fMaxRentalPriceScale * 0.01f;
  148. // Clean up
  149. vecTokens.PurgeAndDeleteElements();
  150. }
  151. void econ_store_entry_t::SetItemDefinitionIndex( item_definition_index_t usDefIndex )
  152. {
  153. AssertMsg( usDefIndex != INVALID_ITEM_DEF_INDEX, "Invalid item definition index!" );
  154. m_usDefIndex = usDefIndex;
  155. }
  156. bool econ_store_entry_t::IsListedInCategory( StoreCategoryID_t unID ) const
  157. {
  158. AssertMsg( unID != CEconStoreCategoryManager::k_CategoryID_Invalid, "Did you mean to search for an invalid symbol?" );
  159. FOR_EACH_VEC( m_vecCategoryTags, i )
  160. {
  161. if ( m_vecCategoryTags[i].m_unID == unID )
  162. return true;
  163. }
  164. return false;
  165. }
  166. bool econ_store_entry_t::IsListedInSubcategories( const CEconStoreCategoryManager::StoreCategory_t &Category ) const
  167. {
  168. FOR_EACH_VEC( Category.m_vecSubcategories, iSubCategory )
  169. {
  170. if ( IsListedInCategory( Category.m_vecSubcategories[iSubCategory]->m_unID ) )
  171. return true;
  172. }
  173. return false;
  174. }
  175. bool econ_store_entry_t::IsListedInCategoryOrSubcategories( const CEconStoreCategoryManager::StoreCategory_t &Category ) const
  176. {
  177. return IsListedInCategory( Category.m_unID ) ||
  178. IsListedInSubcategories( Category );
  179. }
  180. bool econ_store_entry_t::IsOnSale( ECurrency eCurrency ) const
  181. {
  182. return GetSalePrice( eCurrency ) > 0
  183. && GetSalePrice( eCurrency ) < GetBasePrice( eCurrency );
  184. }
  185. bool econ_store_entry_t::IsRentable() const
  186. {
  187. return m_fRentalPriceScale < 1.0f
  188. && m_fRentalPriceScale > 0.0f;
  189. }
  190. #ifdef CLIENT_DLL
  191. bool econ_store_entry_t::HasDiscount( ECurrency eCurrency, item_price_t *out_punOptionalBasePrice ) const
  192. {
  193. // Items on sale always report as being discounted.
  194. if ( IsOnSale( eCurrency ) )
  195. {
  196. if ( out_punOptionalBasePrice )
  197. {
  198. *out_punOptionalBasePrice = GetBasePrice( eCurrency );
  199. }
  200. return true;
  201. }
  202. // If we're not a bundle we have no other way of being on sale -- abort.
  203. const CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinition( GetItemDefinitionIndex() );
  204. if ( !pItemDef || !pItemDef->IsBundle() )
  205. return false;
  206. const bundleinfo_t *pBundleInfo = pItemDef->GetBundleInfo();
  207. Assert( pBundleInfo );
  208. item_price_t unTotalPriceOfBundleItems = 0;
  209. FOR_EACH_VEC( pBundleInfo->vecItemDefs, bundleIdx )
  210. {
  211. const econ_store_entry_t *pCurEntry = pBundleInfo->vecItemDefs[bundleIdx]
  212. ? GetEconPriceSheet()->GetEntry( pBundleInfo->vecItemDefs[bundleIdx]->GetDefinitionIndex() )
  213. : NULL;
  214. if ( pCurEntry )
  215. {
  216. unTotalPriceOfBundleItems += pCurEntry->GetCurrentPrice( eCurrency );
  217. }
  218. }
  219. // Did the bundle provide an actual discount?
  220. const item_price_t unBasePrice = GetCurrentPrice( eCurrency );
  221. if ( unBasePrice < unTotalPriceOfBundleItems )
  222. {
  223. if ( out_punOptionalBasePrice )
  224. {
  225. *out_punOptionalBasePrice = unTotalPriceOfBundleItems;
  226. }
  227. return true;
  228. }
  229. // No discount. Leave the base price pointer untouched.
  230. return false;
  231. }
  232. #endif
  233. //-----------------------------------------------------------------------------
  234. // Purpose:
  235. //-----------------------------------------------------------------------------
  236. StoreCategoryID_t econ_store_entry_t::GetCategoryTagIDFromIndex( uint32 iIndex ) const
  237. {
  238. if ( !IsValidCategoryTagIndex( iIndex ) )
  239. return CEconStoreCategoryManager::k_CategoryID_Invalid;
  240. return m_vecCategoryTags[ iIndex ].m_unID;
  241. }
  242. item_price_t econ_store_entry_t::GetCurrentPrice( ECurrency eCurrency ) const
  243. {
  244. #ifdef CLIENT_DLL
  245. if ( m_bIsMarketItem )
  246. {
  247. const client_market_data_t *pClientMarketData = GetClientMarketData( GetItemDefinitionIndex(), AE_UNIQUE );
  248. if ( !pClientMarketData )
  249. return 0;
  250. return pClientMarketData->m_unLowestPrice;
  251. }
  252. #endif
  253. return IsOnSale( eCurrency )
  254. ? GetSalePrice( eCurrency )
  255. : GetBasePrice( eCurrency );
  256. }
  257. float econ_store_entry_t::GetRentalPriceScale() const
  258. {
  259. Assert( IsRentable() );
  260. return m_fRentalPriceScale;
  261. }
  262. //-----------------------------------------------------------------------------
  263. // Purpose:
  264. //-----------------------------------------------------------------------------
  265. void econ_store_entry_t::ValidatePrice( ECurrency eCurrency, item_price_t unPrice )
  266. {
  267. if ( m_bIsMarketItem )
  268. return;
  269. // If the price is 0 and this is not a pack item (an item that is not individually for sale, but is sold as part of a pack bundle), post an alert
  270. if ( unPrice == 0 && !m_bIsPackItem )
  271. {
  272. CFmtStr fmtError( "Warning: Invalid price for item (item def=%i)", GetItemDefinitionIndex() );
  273. #if defined( GC_DLL )
  274. GGCGameBase()->PostAlert( GCSDK::k_EAlertTypeReport, true, fmtError.Access() );
  275. #endif
  276. AssertMsg( false, "%s", fmtError.Access() );
  277. }
  278. }
  279. //-----------------------------------------------------------------------------
  280. // Purpose: Constructor
  281. //-----------------------------------------------------------------------------
  282. CEconStorePriceSheet::CEconStorePriceSheet()
  283. : m_pKVRaw( NULL )
  284. , m_mapEntries( DefLessFunc( uint16 ) )
  285. , m_mapRentalPriceScales( DefLessFunc( const char * ) )
  286. , m_RTimeVersionStamp( 0 )
  287. , m_pStorePromotionFirstTimePurchaseItem( NULL )
  288. , m_pStorePromotionFirstTimeWebPurchaseItem( NULL )
  289. , m_unFeaturedItemIndex( 0 )
  290. , m_eEconStoreSortType( kEconStoreSortType_Name_AToZ )
  291. {
  292. Clear();
  293. m_FeaturedItems.m_pchName = "featured_items";
  294. m_mapCurrencyPricePoints.SetLessFunc( &price_point_map_key_t::Less );
  295. }
  296. //-----------------------------------------------------------------------------
  297. // Purpose: Destructor
  298. //-----------------------------------------------------------------------------
  299. CEconStorePriceSheet::~CEconStorePriceSheet()
  300. {
  301. Clear();
  302. }
  303. //-----------------------------------------------------------------------------
  304. // Purpose: Clears out the parsed data
  305. //-----------------------------------------------------------------------------
  306. void CEconStorePriceSheet::Clear()
  307. {
  308. if ( m_pKVRaw )
  309. {
  310. m_pKVRaw->deleteThis();
  311. m_pKVRaw = NULL;
  312. }
  313. m_RTimeVersionStamp = 0;
  314. m_mapEntries.RemoveAll();
  315. m_FeaturedItems.m_vecEntries.RemoveAll();
  316. m_flPreviewPeriodDiscount = 0;
  317. m_mapCurrencyPricePoints.Purge();
  318. #ifdef CLIENT_DLL
  319. m_vecFeaturedItems.Purge();
  320. #endif // CLIENT_DLL
  321. // Clear categories in category manager
  322. ClearEconStoreCategoryManager();
  323. }
  324. //-----------------------------------------------------------------------------
  325. // Purpose: Gets the entry details for a specific item
  326. //-----------------------------------------------------------------------------
  327. const econ_store_entry_t *CEconStorePriceSheet::GetEntry( item_definition_index_t usDefIndex ) const
  328. {
  329. int iIndex = m_mapEntries.Find( usDefIndex );
  330. if ( m_mapEntries.IsValidIndex( iIndex ) )
  331. return &m_mapEntries[iIndex];
  332. return NULL;
  333. }
  334. #ifdef GC_DLL
  335. //-----------------------------------------------------------------------------
  336. // Purpose: Gets the entry details for a specific item and lets us modify the contents (GC-only)
  337. //-----------------------------------------------------------------------------
  338. econ_store_entry_t *CEconStorePriceSheet::GetEntryWriteable( item_definition_index_t unDefIndex )
  339. {
  340. int iIndex = m_mapEntries.Find( unDefIndex );
  341. if ( m_mapEntries.IsValidIndex( iIndex ) )
  342. return &m_mapEntries[iIndex];
  343. return NULL;
  344. }
  345. #endif // GC_DLL
  346. //-----------------------------------------------------------------------------
  347. // Purpose:
  348. //-----------------------------------------------------------------------------
  349. bool BInsertSinglePricePoint( CurrencyPricePointMap_t& out_mapPricePoints, const price_point_map_key_t& key, item_price_t unValue )
  350. {
  351. if ( out_mapPricePoints.Find( key ) != out_mapPricePoints.InvalidIndex() )
  352. return false;
  353. out_mapPricePoints.Insert( key, unValue );
  354. return true;
  355. }
  356. //-----------------------------------------------------------------------------
  357. // Purpose:
  358. //-----------------------------------------------------------------------------
  359. bool BInitializeCurrencyPricePoints( CurrencyPricePointMap_t& out_mapPricePoints, KeyValues *pKVPricePoints )
  360. {
  361. if ( !pKVPricePoints )
  362. return false;
  363. // Zero-price-point is universal.
  364. FOR_EACH_CURRENCY( eCurrency )
  365. {
  366. const price_point_map_key_t key = { 0, eCurrency };
  367. if ( !BInsertSinglePricePoint( out_mapPricePoints, key, 0 ) )
  368. return false;
  369. }
  370. // Individual price points specified in our store config.
  371. FOR_EACH_TRUE_SUBKEY( pKVPricePoints, pKVUSD )
  372. {
  373. const item_price_t unUSD = Q_atoi( pKVUSD->GetName() );
  374. // USD key, for ease of lookup.
  375. {
  376. const price_point_map_key_t key = { unUSD, k_ECurrencyUSD };
  377. if ( !BInsertSinglePricePoint( out_mapPricePoints, key, unUSD ) )
  378. return false;
  379. }
  380. // Exchange rates.
  381. FOR_EACH_VALUE( pKVUSD, pKVOtherCurrency )
  382. {
  383. const char *pszCurrencyName = pKVOtherCurrency->GetName();
  384. const ECurrency eCurrency = ECurrencyFromName( pszCurrencyName );
  385. if ( eCurrency == k_ECurrencyInvalid )
  386. {
  387. // Spew
  388. #ifdef GC_DLL
  389. EmitError( SPEW_GC, "Unknown Currency [%s] found in price sheet. Currency is unsupported!\n", pszCurrencyName );
  390. #endif
  391. // don't crash, just conintue
  392. continue;
  393. }
  394. const price_point_map_key_t key = { unUSD, eCurrency };
  395. if ( !BInsertSinglePricePoint( out_mapPricePoints, key, pKVOtherCurrency->GetInt() ) )
  396. return false;
  397. }
  398. }
  399. return true;
  400. }
  401. //-----------------------------------------------------------------------------
  402. // Purpose: Parses the KV version of the price sheet
  403. //-----------------------------------------------------------------------------
  404. bool CEconStorePriceSheet::InitFromKV( KeyValues *pKVRoot )
  405. {
  406. Clear();
  407. m_pKVRaw = pKVRoot->MakeCopy();
  408. // Initialize categories - needed to initialize store entries
  409. if ( !GEconStoreCategoryManager()->BInit( this, m_pKVRaw ) )
  410. {
  411. #ifdef GC_DLL
  412. EmitError( SPEW_GC, "Unable to Init GEconStoreCategoryManager \n" );
  413. #endif
  414. return false;
  415. }
  416. // Initialize map of currency price points.
  417. if ( !BInitializeCurrencyPricePoints( m_mapCurrencyPricePoints, pKVRoot->FindKey( "inventoryvalvegcpricesheet" ) ) )
  418. {
  419. #ifdef GC_DLL
  420. EmitError( SPEW_GC, "Unable to Init CurrencyPricePoints \n" );
  421. #endif
  422. return false;
  423. }
  424. m_unFeaturedItemIndex = pKVRoot->GetInt( "featured_item_index", 0 );
  425. // first time purchase gift
  426. m_pStorePromotionFirstTimePurchaseItem = GetItemSchema()->GetItemDefinitionByName( pKVRoot->GetString( "promotion_first_time_purchase_gift" ) );
  427. m_pStorePromotionFirstTimeWebPurchaseItem = GetItemSchema()->GetItemDefinitionByName( pKVRoot->GetString( "promotion_first_time_web_purchase_gift" ) );
  428. EUniverse eUniverse = GetUniverse();
  429. m_unPreviewPeriod = pKVRoot->GetInt( eUniverse == k_EUniversePublic ? "preview_period" : "preview_period_nonpublic" );
  430. m_unBonusDiscountPeriod = pKVRoot->GetInt( eUniverse == k_EUniversePublic ? "bonus_discount_period" : "bonus_discount_period_nonpublic" );
  431. m_flPreviewPeriodDiscount = pKVRoot->GetFloat( "preview_period_discount" );
  432. #ifdef ENABLE_STORE_RENTAL_BACKEND
  433. KeyValues *pRentalPriceScalesKV = pKVRoot->FindKey( "rental_price_scale" );
  434. if ( pRentalPriceScalesKV )
  435. {
  436. FOR_EACH_SUBKEY( pRentalPriceScalesKV, pCategoryKV )
  437. {
  438. m_mapRentalPriceScales.InsertOrReplace( pCategoryKV->GetName(), pCategoryKV->GetFloat() );
  439. }
  440. }
  441. #endif
  442. memset( &m_StorePromotionSpendForFreeItem, 0, sizeof( m_StorePromotionSpendForFreeItem ) );
  443. KeyValues *pPromotionsKV = m_pKVRaw->FindKey( "promotion_spend_for_free_item" );
  444. if ( pPromotionsKV )
  445. {
  446. item_price_t unFreeItemSpendAmountUSD = pPromotionsKV->GetInt( "price_threshold", 0 );
  447. FOR_EACH_CURRENCY( eCurrency )
  448. {
  449. const price_point_map_key_t key = { unFreeItemSpendAmountUSD, eCurrency };
  450. const CurrencyPricePointMap_t::IndexType_t unIdx = m_mapCurrencyPricePoints.Find( key );
  451. if ( unIdx == m_mapCurrencyPricePoints.InvalidIndex() )
  452. {
  453. #ifdef GC_DLL
  454. EmitError( SPEW_GC, "Unable to Find Currency %s in Currency Map. Likely missing from inventoryvalvegcpricesheet.vdf \n", PchNameFromECurrency(eCurrency) );
  455. #endif
  456. continue;
  457. }
  458. m_StorePromotionSpendForFreeItem.m_rgusPriceThreshold[ eCurrency ] = m_mapCurrencyPricePoints[unIdx];
  459. }
  460. #if !defined(CLIENT_DLL) && !defined(GAME_DLL)
  461. CEconItemSchema *pSchema = GEconManager()->GetItemSchema();
  462. m_StorePromotionSpendForFreeItem.m_pItemDef = pSchema->GetItemDefinitionByName( pPromotionsKV->GetString( "item_definition" ) );
  463. AssertMsg( m_StorePromotionSpendForFreeItem.m_pItemDef || !pPromotionsKV->GetString( "item_definition", NULL ),
  464. "Could find not the specified item for \"promotion_spend_for_free_item\"" );
  465. #endif
  466. }
  467. // Get the normal sections
  468. KeyValues *pEntriesKV = m_pKVRaw->FindKey( "entries" );
  469. if ( pEntriesKV )
  470. {
  471. FOR_EACH_TRUE_SUBKEY( pEntriesKV, pKVEntry )
  472. {
  473. if ( !BInitEntryFromKV( pKVEntry ) )
  474. {
  475. #ifdef GC_DLL
  476. EmitError( SPEW_GC, "Unable to Find Entries in Currency KVP \n" );
  477. #endif
  478. continue;
  479. }
  480. }
  481. }
  482. // Get a list of Market entries to populate in to the 'store'. GC never cares or reads these items
  483. // Only for Client
  484. #ifdef CLIENT_DLL
  485. KeyValues *pMarketEntriesKV = m_pKVRaw->FindKey( "market_entries" );
  486. if ( pMarketEntriesKV )
  487. {
  488. FOR_EACH_TRUE_SUBKEY( pMarketEntriesKV, pKVEntry )
  489. {
  490. if ( !BInitMarketEntryFromKV( pKVEntry ) )
  491. {
  492. return false;
  493. }
  494. }
  495. }
  496. KeyValues *pFeaturedItemsKV = m_pKVRaw->FindKey( "featured_items" );
  497. if ( pFeaturedItemsKV )
  498. {
  499. FOR_EACH_SUBKEY( pFeaturedItemsKV, pKVEntry )
  500. {
  501. const char *pszItemName = pKVEntry->GetName();
  502. const CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( pszItemName );
  503. if ( !pDef )
  504. {
  505. AssertMsg1( 0, "Unable to find item: %s", pszItemName );
  506. continue;
  507. }
  508. m_vecFeaturedItems.AddToTail( pDef->GetDefinitionIndex() );
  509. }
  510. }
  511. #endif
  512. // Generate a hash of all item def indices and cache it off
  513. m_unHashForAllItems = CalculateHashFromItems();
  514. #ifdef GC_DLL
  515. // Parse the sales block on the GC. We'll use this to dynamically adjust prices.
  516. KeyValues *pTimedSalesKV = m_pKVRaw->FindKey( "timed_sales" );
  517. if ( pTimedSalesKV )
  518. {
  519. FOR_EACH_TRUE_SUBKEY( pTimedSalesKV, pKVSale )
  520. {
  521. if ( !InitTimedSaleEntryFromKV( pKVSale ) )
  522. {
  523. EmitError( SPEW_GC, "Unable to Init Timed Sale \n" );
  524. }
  525. return false;
  526. }
  527. // Verify that none of our timed sales have overlapping items.
  528. if ( !VerifyTimedSaleEntries() )
  529. return false;
  530. }
  531. #endif // GC_DLL
  532. // Now that store entries are loaded, let the category manager do more stuff
  533. if ( !GEconStoreCategoryManager()->BOnPriceSheetLoaded( this ) )
  534. return false;
  535. return true;
  536. }
  537. uint32 CEconStorePriceSheet::CalculateHashFromItems() const
  538. {
  539. CRC32_t unHash;
  540. CRC32_Init( &unHash );
  541. FOR_EACH_MAP_FAST( m_mapEntries, idx )
  542. {
  543. const econ_store_entry_t &entry = m_mapEntries[idx];
  544. item_definition_index_t usDefIndexTmp = entry.GetItemDefinitionIndex();
  545. CRC32_ProcessBuffer( &unHash, &usDefIndexTmp, sizeof( usDefIndexTmp ) );
  546. }
  547. CRC32_Final( &unHash );
  548. return (uint32)unHash;
  549. }
  550. //-----------------------------------------------------------------------------
  551. // Purpose:
  552. //-----------------------------------------------------------------------------
  553. bool BInitializeStoreEntryPricePoints( econ_store_entry_t& out_entry, const CurrencyPricePointMap_t& mapCurrencyPricePoints, KeyValues *pKVPrice, int nSalePercent )
  554. {
  555. if ( !pKVPrice )
  556. return false;
  557. FOR_EACH_CURRENCY( eCurrency )
  558. {
  559. const price_point_map_key_t key = { (item_price_t)pKVPrice->GetInt(), eCurrency };
  560. const CurrencyPricePointMap_t::IndexType_t unIdx = mapCurrencyPricePoints.Find( key );
  561. // Looking for a price point that doesn't exist, or doesn't exist for this currency?
  562. if ( unIdx == mapCurrencyPricePoints.InvalidIndex() )
  563. {
  564. #ifdef GC_DLL
  565. EmitError( SPEW_GC, "Unable to Find Currency %s in init price points. Currency is missing from inventoryvalvegcpricesheet.vdf \n", PchNameFromECurrency( eCurrency ) );
  566. #endif
  567. continue;
  568. }
  569. // Weird initialization pattern: we're making sure that the value we read from the KeyValues
  570. // block is the value we're storing in memory. We do this to avoid integer conversion problems,
  571. // especially overflow (!).
  572. const item_price_t unPrice = mapCurrencyPricePoints[unIdx];
  573. out_entry.SetBasePrice( eCurrency, unPrice );
  574. #pragma warning(push)
  575. #pragma warning(disable : 4389)
  576. Assert( out_entry.GetBasePrice( eCurrency ) == unPrice ); // NOTE: DO NOT CAST unPrice - CHECKING FOR OVERFLOW
  577. #pragma warning(pop)
  578. if ( ( nSalePercent > 0 ) && ( nSalePercent < 100 ) )
  579. {
  580. const item_price_t unSalePrice = out_entry.CalculateSalePrice( &out_entry, eCurrency, (float)nSalePercent );
  581. out_entry.SetSalePrice( eCurrency, unSalePrice );
  582. }
  583. }
  584. return true;
  585. }
  586. //-----------------------------------------------------------------------------
  587. // Purpose: Parses the KV section piece and add a econ_store_entry_t
  588. //-----------------------------------------------------------------------------
  589. bool CEconStorePriceSheet::BInitEntryFromKV( KeyValues *pKVEntry )
  590. {
  591. const bool bIsPackItem = pKVEntry->GetBool( "is_pack_item", false );
  592. #if defined( CLIENT_DLL )
  593. // Skip pack items on the client
  594. if ( bIsPackItem )
  595. return true;
  596. #endif
  597. econ_store_entry_t entry;
  598. entry.InitCategoryTags( pKVEntry->GetString( "category_tags" ) );
  599. const bool bIsNew = entry.IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_New );
  600. entry.m_bLimited = entry.IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Limited );
  601. entry.m_bNew = bIsNew;
  602. entry.m_bPreviewAllowed = !bIsNew && entry.IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Weapons );
  603. entry.m_bIsMarketItem = false;
  604. const bool bIsHighlighted = entry.IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Highlighted );
  605. entry.m_bHighlighted = bIsHighlighted;
  606. if ( entry.GetCategoryTagCount() == 0 )
  607. {
  608. AssertMsg1( 0, "Unable to get category_tags for entry: %s", pKVEntry->GetName() );
  609. return false;
  610. }
  611. const CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( pKVEntry->GetString( "item_link" ) );
  612. if ( !pDef )
  613. {
  614. AssertMsg1( 0, "Unable to find item: %s", pKVEntry->GetString( "item_link" ) );
  615. return false;
  616. }
  617. entry.SetItemDefinitionIndex( pDef->GetDefinitionIndex() );
  618. int iQuantity = pKVEntry->GetInt( "quantity", 1 );
  619. entry.SetQuantity( iQuantity );
  620. Assert( entry.GetQuantity() == iQuantity );
  621. entry.SetSteamGiftPackageID( pKVEntry->GetUint64( "steam_gift_package_id", 0 ) );
  622. #if defined( TF_CLIENT_DLL ) || defined( TF_GC_DLL )
  623. entry.SetDate( pDef->GetFirstSaleDate() );
  624. #else
  625. entry.SetDate( pKVEntry->GetString( "date", "1/1/1900" ) );
  626. #endif
  627. // Is the item sold out of the store?
  628. entry.m_bSoldOut = pKVEntry->GetBool( "sold_out", false );
  629. // Is the item a pack item?
  630. entry.m_bIsPackItem = bIsPackItem;
  631. int nSalePercent = pKVEntry->GetInt( "sale_percentage", 0 );
  632. if ( ( nSalePercent < 0 ) || ( nSalePercent >= 100 ) )
  633. {
  634. AssertMsg( false, "Sale percentage for %s set to an invalid value: %d\n", pDef->GetDefinitionName(), nSalePercent );
  635. return false;
  636. }
  637. if ( !BInitializeStoreEntryPricePoints( entry, m_mapCurrencyPricePoints, pKVEntry->FindKey( "base_price" ), nSalePercent ) )
  638. return false;
  639. m_mapEntries.InsertOrReplace( entry.GetItemDefinitionIndex(), entry );
  640. return true;
  641. }
  642. #ifdef CLIENT_DLL
  643. //-----------------------------------------------------------------------------
  644. // Purpose: Parses the KV section piece and add a econ_store_entry_t
  645. //-----------------------------------------------------------------------------
  646. bool CEconStorePriceSheet::BInitMarketEntryFromKV( KeyValues *pKVEntry )
  647. {
  648. //"The Alien Cranium"
  649. //{
  650. // "item_link" "The Alien Cranium"
  651. // "category_tags" "Cosmetics+Halloween"
  652. // "date" "11/13/2014"
  653. // "base_price" "1299"
  654. //}
  655. econ_store_entry_t entry;
  656. entry.InitCategoryTags( pKVEntry->GetString( "category_tags" ) );
  657. const bool bIsNew = entry.IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_New );
  658. entry.m_bLimited = entry.IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Limited );
  659. entry.m_bNew = bIsNew;
  660. entry.m_bPreviewAllowed = false;
  661. entry.m_bIsMarketItem = true;
  662. if ( entry.GetCategoryTagCount() == 0 )
  663. {
  664. AssertMsg1( 0, "Unable to get category_tags for entry: %s", pKVEntry->GetName() );
  665. return false;
  666. }
  667. const CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( pKVEntry->GetString( "item_link" ) );
  668. if ( !pDef )
  669. {
  670. AssertMsg1( 0, "Unable to find item: %s", pKVEntry->GetString( "item_link" ) );
  671. return false;
  672. }
  673. entry.SetItemDefinitionIndex( pDef->GetDefinitionIndex() );
  674. entry.SetQuantity( 1 );
  675. entry.SetDate( pDef->GetFirstSaleDate() ); // FIXME?
  676. entry.m_bSoldOut = false;
  677. entry.m_bIsPackItem = false;
  678. // Entry should never already exist in mapEntries
  679. if ( m_mapEntries.Find( entry.GetItemDefinitionIndex() ) != m_mapEntries.InvalidIndex() )
  680. {
  681. AssertMsg1( 0, "Market Entry already eixsts : %s", pKVEntry->GetString( "item_link" ) );
  682. return false;
  683. }
  684. m_mapEntries.InsertOrReplace( entry.GetItemDefinitionIndex(), entry );
  685. return true;
  686. }
  687. #endif // CLIENT_DLL
  688. #ifdef GC_DLL
  689. //-----------------------------------------------------------------------------
  690. // Purpose: Parses the KV section piece and add a econ_store_entry_t
  691. //-----------------------------------------------------------------------------
  692. static RTime32 ConvertKVDateToRTime32( KeyValues *pKV, const char *pszKey )
  693. {
  694. Assert( pKV );
  695. Assert( pszKey );
  696. const char *pszTime = pKV->GetString( pszKey, NULL );
  697. if ( !pszTime || !pszTime[0] )
  698. return 0;
  699. RTime32 unConvertedTime = CRTime::RTime32FromString( pszTime );
  700. if ( unConvertedTime == (RTime32)-1 )
  701. return 0;
  702. return unConvertedTime;
  703. }
  704. bool CEconStorePriceSheet::InitTimedSaleEntryFromKV( KeyValues *pKVTimedSaleEntry )
  705. {
  706. Assert( pKVTimedSaleEntry );
  707. econ_store_timed_sale_t TimedSale;
  708. TimedSale.m_bSaleCurrentlyActive = false;
  709. TimedSale.m_sIdentifier = pKVTimedSaleEntry->GetName();
  710. TimedSale.m_SaleStartTime = ConvertKVDateToRTime32( pKVTimedSaleEntry, "sale_start_date" );
  711. TimedSale.m_SaleEndTime = ConvertKVDateToRTime32( pKVTimedSaleEntry, "sale_end_date" );
  712. // Sanity check -- make sure this sale lasts a greater-than-zero amount of time. This will also
  713. // catch any cases where the end time was invalid and so returned 0.
  714. if ( TimedSale.m_SaleStartTime >= TimedSale.m_SaleEndTime )
  715. {
  716. EG_ERROR( GCSDK::SPEW_GC, "Timed sale '%s' has invalid duration\n", pKVTimedSaleEntry->GetName() );
  717. return false;
  718. }
  719. // Make sure the end time is also greater than 0.
  720. if ( TimedSale.m_SaleStartTime <= 0 )
  721. {
  722. EG_ERROR( GCSDK::SPEW_GC, "Timed sale '%s' has invalid start time\n", pKVTimedSaleEntry->GetName() );
  723. return false;
  724. }
  725. // What items does this sale apply to?
  726. KeyValues *pKVSaleItems = pKVTimedSaleEntry->FindKey( "sale_items" );
  727. if ( !pKVSaleItems )
  728. {
  729. EG_ERROR( GCSDK::SPEW_GC, "Timed sale '%s' missing \"sale_items\" section\n", pKVTimedSaleEntry->GetName() );
  730. return false;
  731. }
  732. FOR_EACH_TRUE_SUBKEY( pKVSaleItems, pKVSaleItem )
  733. {
  734. const char *pszName = pKVSaleItem->GetString( "name", NULL );
  735. if ( !pszName )
  736. {
  737. EG_ERROR( GCSDK::SPEW_GC, "Timed sale '%s' has invalid/missing item name for entry '%s'\n", pKVTimedSaleEntry->GetName(), pKVSaleItem->GetName() );
  738. return false;
  739. }
  740. const CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinitionByName( pszName );
  741. if ( !pItemDef )
  742. {
  743. EG_ERROR( GCSDK::SPEW_GC, "Timed sale '%s' has missing item named '%s' for entry '%s'\n", pKVTimedSaleEntry->GetName(), pszName, pKVSaleItem->GetName() );
  744. return false;
  745. }
  746. const float fSalePercentageOff = pKVSaleItem->GetFloat( "sale_percentage_off", -1.0f );
  747. if ( fSalePercentageOff <= 0.0f || fSalePercentageOff > 100.0f )
  748. {
  749. EG_ERROR( GCSDK::SPEW_GC, "Timed sale '%s' has invalid sale percentage for item named '%s' for entry '%s'\n", pKVTimedSaleEntry->GetName(), pszName, pKVSaleItem->GetName() );
  750. return false;
  751. }
  752. const econ_store_entry_t *pStoreEntry = GetEntry( pItemDef->GetDefinitionIndex() );
  753. if ( !pStoreEntry )
  754. {
  755. EG_ERROR( GCSDK::SPEW_GC, "Timed sale '%s' has item named '%s' for entry '%s' that is not in the store\n", pKVTimedSaleEntry->GetName(), pszName, pKVSaleItem->GetName() );
  756. return false;
  757. }
  758. // Verify that no item in a timed sale is already in a hard-coded sale as well. This would break
  759. // our fragile pricing math assumptions.
  760. FOR_EACH_CURRENCY( eCurrency )
  761. {
  762. if ( pStoreEntry->IsOnSale( eCurrency ) )
  763. {
  764. EG_ERROR( GCSDK::SPEW_GC, "Timed sale '%s' has item named '%s' for entry '%s' that hard-coded to be on sale (currency: %i)\n", pKVTimedSaleEntry->GetName(), pszName, pKVSaleItem->GetName(), eCurrency );
  765. return false;
  766. }
  767. }
  768. econ_store_timed_sale_item_t SaleItem;
  769. SaleItem.m_unItemDef = pItemDef->GetDefinitionIndex();
  770. SaleItem.m_fPricePercentage = fSalePercentageOff;
  771. TimedSale.m_vecSaleItems.AddToTail( SaleItem );
  772. }
  773. if ( TimedSale.m_vecSaleItems.Count() <= 0 )
  774. {
  775. EG_ERROR( GCSDK::SPEW_GC, "Timed sale '%s' has no valid items to put on sale\n", pKVTimedSaleEntry->GetName() );
  776. return false;
  777. }
  778. // We made it this far with no errors, so add this as a timed sale block if it actually affects
  779. // any items.
  780. m_vecTimedSales.AddToTail( TimedSale );
  781. return true;
  782. }
  783. //-----------------------------------------------------------------------------
  784. // Purpose:
  785. //-----------------------------------------------------------------------------
  786. bool CEconStorePriceSheet::VerifyTimedSaleEntries()
  787. {
  788. // We could write an interval tree and be smart about this, but Fletcher made
  789. // faces at me when I suggested it so we just brute force it -- we find each
  790. // item in each sale sequentially, find each sale that overlaps with the current
  791. // sale, and make sure that it doesn't have the same item.
  792. for ( int i = 0; i < m_vecTimedSales.Count(); i++ )
  793. {
  794. const econ_store_timed_sale_t& BaseSale = m_vecTimedSales[i];
  795. Assert( BaseSale.m_vecSaleItems.Count() > 0 );
  796. for ( int j = 0; j < BaseSale.m_vecSaleItems.Count(); j++ )
  797. {
  798. const item_definition_index_t unSearchDefIndex = BaseSale.m_vecSaleItems[j].m_unItemDef;
  799. for ( int k = i; k < m_vecTimedSales.Count(); k++ )
  800. {
  801. // Does this sale overlap with our current sale?
  802. const econ_store_timed_sale_t& OtherSale = m_vecTimedSales[k];
  803. if ( k == i || MAX( BaseSale.m_SaleStartTime, OtherSale.m_SaleStartTime ) <= MIN( BaseSale.m_SaleEndTime, OtherSale.m_SaleEndTime ) )
  804. {
  805. // We overlap, so make sure this item doesn't show up in both lists. Start the search in our
  806. // current sale to make sure the same entry doesn't show up twice and then look at the full
  807. // list of items in other overlapping sales.
  808. for ( int l = (k == i ? j + 1 : 0); l < OtherSale.m_vecSaleItems.Count(); l++ )
  809. {
  810. const item_definition_index_t unMaybeMatchDefIndex = OtherSale.m_vecSaleItems[l].m_unItemDef;
  811. if ( unSearchDefIndex == unMaybeMatchDefIndex )
  812. {
  813. EG_ERROR( GCSDK::SPEW_GC, "Conflict detected for item index '%i' betweens timed sales '%s' / '%s'\n",
  814. unSearchDefIndex,
  815. BaseSale.m_sIdentifier.Get(),
  816. OtherSale.m_sIdentifier.Get() );
  817. return false;
  818. }
  819. }
  820. }
  821. }
  822. }
  823. }
  824. return true;
  825. }
  826. //-----------------------------------------------------------------------------
  827. // Purpose:
  828. //-----------------------------------------------------------------------------
  829. void CEconStorePriceSheet::UpdatePricesForTimedSales( const RTime32 curTime )
  830. {
  831. FOR_EACH_VEC( m_vecTimedSales, i )
  832. {
  833. econ_store_timed_sale_t& TimedSale = m_vecTimedSales[i];
  834. Assert( TimedSale.m_vecSaleItems.Count() > 0 );
  835. // Is this sale active in our current time?
  836. const bool bSaleShouldBeActive = (curTime >= TimedSale.m_SaleStartTime)
  837. && (curTime <= TimedSale.m_SaleEndTime);
  838. // Last time we processed it, was this sale active?
  839. const bool bSaleIsActive = TimedSale.m_bSaleCurrentlyActive;
  840. // State transition?
  841. if ( bSaleShouldBeActive != bSaleIsActive )
  842. {
  843. FOR_EACH_VEC( TimedSale.m_vecSaleItems, i )
  844. {
  845. econ_store_entry_t *unSaleStoreEntry = GetEntryWriteable( TimedSale.m_vecSaleItems[i].m_unItemDef );
  846. Assert( unSaleStoreEntry );
  847. // We don't support items being on sale in multiple ways at the same time (ie., a
  848. // sale specified in the base prices in store.txt and also a timed sale) so we just stomp
  849. // whatever the sale price is with the price we think we should be on sale for.
  850. FOR_EACH_CURRENCY( eCurrency )
  851. {
  852. unSaleStoreEntry->SetSalePrice( eCurrency,
  853. bSaleIsActive ? unSaleStoreEntry->GetBasePrice( eCurrency ) * TimedSale.m_vecSaleItems[i].m_fPricePercentage : 0 );
  854. }
  855. }
  856. // Update our last-updated timestamp if any of these sales changed -- use the most recent
  857. // "start of sale" date. This will allow people using the store prices WebAPI to know whether
  858. // prices have changed.
  859. // If items just went on sale, their price changed at the start of this sale.
  860. if ( bSaleIsActive )
  861. {
  862. m_RTimeVersionStamp = MAX( m_RTimeVersionStamp, TimedSale.m_SaleStartTime );
  863. }
  864. // If items just got taken off sale, their price changed at the end of this sale.
  865. else // if ( !bSaleIsActive )
  866. {
  867. m_RTimeVersionStamp = MAX( m_RTimeVersionStamp, TimedSale.m_SaleEndTime );
  868. }
  869. // Update our cached state.
  870. TimedSale.m_bSaleCurrentlyActive = bSaleShouldBeActive;
  871. // Debug output.
  872. EmitInfo( GCSDK::SPEW_GC, SPEW_ALWAYS, LOG_ALWAYS, "Timed sale '%s' has been %s.\n", TimedSale.m_sIdentifier.Get(), bSaleShouldBeActive ? "enabled" : "disabled" );
  873. }
  874. }
  875. }
  876. //-----------------------------------------------------------------------------
  877. // Purpose:
  878. //-----------------------------------------------------------------------------
  879. void CEconStorePriceSheet::DumpTimeSaleState( const RTime32 curTime ) const
  880. {
  881. char curTimeBuf[k_RTimeRenderBufferSize];
  882. EmitInfo( GCSDK::SPEW_GC, SPEW_ALWAYS, LOG_ALWAYS, "Current sale calculation time: %s\n", CRTime::RTime32ToString( curTime, curTimeBuf ) );
  883. FOR_EACH_VEC( m_vecTimedSales, i )
  884. {
  885. const econ_store_timed_sale_t& TimedSale = m_vecTimedSales[i];
  886. Assert( TimedSale.m_vecSaleItems.Count() > 0 );
  887. if ( !TimedSale.m_bSaleCurrentlyActive )
  888. {
  889. EmitInfo( GCSDK::SPEW_GC, SPEW_ALWAYS, LOG_ALWAYS, "\tSale '%s' (not active)\n", TimedSale.m_sIdentifier.Get() );
  890. }
  891. else
  892. {
  893. EmitInfo( GCSDK::SPEW_GC, SPEW_ALWAYS, LOG_ALWAYS, "\tSale '%s' (active):\n", TimedSale.m_sIdentifier.Get() );
  894. FOR_EACH_VEC( TimedSale.m_vecSaleItems, i )
  895. {
  896. EmitInfo( GCSDK::SPEW_GC, SPEW_ALWAYS, LOG_ALWAYS, "\t\t%s is %.0f%% off\n",
  897. GetItemSchema()->GetItemDefinition( TimedSale.m_vecSaleItems[i].m_unItemDef )->GetDefinitionName(),
  898. TimedSale.m_vecSaleItems[i].m_fPricePercentage );
  899. }
  900. }
  901. }
  902. }
  903. #endif // GC_DLL
  904. //-----------------------------------------------------------------------------
  905. // Performs calculation of a discounted price given a base price, and will then handle ensuring the appropriate number of zeros at the end of the price via rounding
  906. //-----------------------------------------------------------------------------
  907. /*static*/ item_price_t econ_store_entry_t::GetDiscountedPrice( ECurrency eCurrency, item_price_t unBasePrice, float fDiscountPercentage )
  908. {
  909. Assert( fDiscountPercentage > 0.0f );
  910. Assert( fDiscountPercentage < 100.0f );
  911. //if the base price is zero, it should never be anything but zero
  912. if( unBasePrice == 0 )
  913. return unBasePrice;
  914. item_price_t unNewPrice = ( item_price_t )( ( double )unBasePrice * ( clamp( 100.0 - fDiscountPercentage, 0.0, 100.0 ) / 100.0 ) );
  915. //determine what the unit of granularity we want to use for pricing. For example if you use 1, we keep 1/100th level precision, if you use 100 we'll round it to the nearest 100th (in USD this would be round to
  916. //the nearest dollar, etc)
  917. item_price_t unPriceGranularity = 1;
  918. switch ( eCurrency )
  919. {
  920. case k_ECurrencyRUB: unPriceGranularity = 100; break;
  921. case k_ECurrencyJPY: unPriceGranularity = 100; break;
  922. case k_ECurrencyNOK: unPriceGranularity = 100; break;
  923. case k_ECurrencyPHP: unPriceGranularity = 100; break;
  924. case k_ECurrencyTHB: unPriceGranularity = 100; break;
  925. case k_ECurrencyKRW: unPriceGranularity = 1000; break;
  926. case k_ECurrencyUAH: unPriceGranularity = 10; break;
  927. case k_ECurrencyIDR: unPriceGranularity = 100; break;
  928. case k_ECurrencyVND: unPriceGranularity = 1000; break;
  929. case k_ECurrencyCNY: unPriceGranularity = 100; break;
  930. case k_ECurrencyTWD: unPriceGranularity = 100; break;
  931. case k_ECurrencyINR: unPriceGranularity = 100; break;
  932. case k_ECurrencyCOP: unPriceGranularity = 100; break;
  933. case k_ECurrencyCLP: unPriceGranularity = 100; break;
  934. }
  935. //now handle the rounding to the specified price granularity
  936. if( unPriceGranularity > 1 )
  937. {
  938. const item_price_t unRemainder = unNewPrice % unPriceGranularity;
  939. //make sure to never let the price go to zero though
  940. if( ( unRemainder >= unPriceGranularity / 2 ) || ( unNewPrice < unPriceGranularity ) )
  941. {
  942. //round up
  943. unNewPrice += unPriceGranularity - unRemainder;
  944. }
  945. else
  946. {
  947. //round down
  948. unNewPrice -= unRemainder;
  949. }
  950. //sanity check
  951. Assert( ( unNewPrice % unPriceGranularity == 0 ) && ( unNewPrice > 0 ) );
  952. }
  953. return unNewPrice;
  954. }
  955. //-----------------------------------------------------------------------------
  956. // Purpose: This will calculate what the sale price will be for a particular
  957. // item -- the result should be non-zero, but this does not mean the item is
  958. // currently on sale. You can optionally pass in a pointer to a uint32 which
  959. // will get you the actual discount percentage, which can be different for
  960. // NXP, which has its own nonlinear pricing structure.
  961. //-----------------------------------------------------------------------------
  962. /*static*/ item_price_t econ_store_entry_t::CalculateSalePrice( const econ_store_entry_t* pSaleStoreEntry, ECurrency eCurrency, float fDiscountPercentage, int32 *out_pAdjustedDiscountPercentage/*=NULL*/ )
  963. {
  964. item_price_t unSalePrice = 0;
  965. /*
  966. // TF2 doesn't support NXP or RMB yet
  967. if ( eCurrency == k_ECurrencyNXP || eCurrency == k_ECurrencyRMB )
  968. {
  969. // For these currencies, we calculate the sale price based on the discount percentage times the *USD* base price -- rather than the discount percentage
  970. // times the base price for the given currency.
  971. const item_price_t unSalePrice_USD = econ_store_entry_t::GetDiscountedPrice( k_ECurrencyUSD, pSaleStoreEntry->GetBasePrice( k_ECurrencyUSD ), fDiscountPercentage );
  972. unSalePrice = ( eCurrency == k_ECurrencyNXP ) ? ConvertUSDToNXP( unSalePrice_USD ) : ConvertUSDToRMB( unSalePrice_USD );
  973. // Ensure that the sale price is strictly less
  974. Assert( unSalePrice < pSaleStoreEntry->GetBasePrice( eCurrency ) );
  975. if ( unSalePrice >= pSaleStoreEntry->GetBasePrice( eCurrency ) )
  976. {
  977. unSalePrice = pSaleStoreEntry->GetBasePrice( eCurrency );
  978. }
  979. }
  980. else
  981. */
  982. {
  983. unSalePrice = econ_store_entry_t::GetDiscountedPrice( eCurrency, pSaleStoreEntry->GetBasePrice( eCurrency ), fDiscountPercentage );
  984. }
  985. Assert( unSalePrice > 0 );
  986. // Also set a percentage per currency, since they can be different. RMB and NXP, for example,
  987. // calculate a sale price based on the USD sale price.
  988. const bool bUseDefaultDiscountPercentage = ( eCurrency == k_ECurrencyUSD );
  989. const double fActualPercent = 100.0 * ( 1.0 - ( double )unSalePrice / ( double )pSaleStoreEntry->GetBasePrice( eCurrency ) );
  990. const int32 nDiscountPercentageForCurrency = bUseDefaultDiscountPercentage ?
  991. RoundFloatToInt( fDiscountPercentage ) :
  992. RoundFloatToInt( fActualPercent );
  993. AssertMsg( nDiscountPercentageForCurrency >= 0 && nDiscountPercentageForCurrency < 100, "Invalid discount percentage of %u specified for item %u currency %u", nDiscountPercentageForCurrency, pSaleStoreEntry->GetItemDefinitionIndex(), eCurrency );
  994. if ( out_pAdjustedDiscountPercentage )
  995. {
  996. *out_pAdjustedDiscountPercentage = nDiscountPercentageForCurrency;
  997. }
  998. return unSalePrice;
  999. }
  1000. //----------------------------------------------------------------------------
  1001. // Purpose: Sort Entries by Name
  1002. //----------------------------------------------------------------------------
  1003. int ItemNameSortComparator( const econ_store_entry_t *const *ppEntryA, const econ_store_entry_t *const *ppEntryB )
  1004. {
  1005. Assert( ppEntryA );
  1006. Assert( ppEntryB );
  1007. Assert( *ppEntryA );
  1008. Assert( *ppEntryB );
  1009. const CEconItemDefinition *pItemDefA = GetItemSchema()->GetItemDefinition( (*ppEntryA)->GetItemDefinitionIndex() );
  1010. const CEconItemDefinition *pItemDefB = GetItemSchema()->GetItemDefinition( (*ppEntryB)->GetItemDefinitionIndex() );
  1011. int nComp = Q_stricmp( pItemDefA->GetItemTypeName(), pItemDefB->GetItemTypeName() );
  1012. if ( nComp != 0 )
  1013. return nComp;
  1014. // If the type matches, then sort by the item name
  1015. return Q_stricmp( pItemDefA->GetItemBaseName(), pItemDefB->GetItemBaseName() );
  1016. }
  1017. //-----------------------------------------------------------------------------
  1018. // Purpose: Sort by whatever sort type our price sheet is requesting
  1019. //-----------------------------------------------------------------------------
  1020. int FirstSaleDateSortComparator( const econ_store_entry_t *const *ppItemA, const econ_store_entry_t *const *ppItemB )
  1021. {
  1022. Assert( ppItemA );
  1023. Assert( ppItemB );
  1024. Assert( *ppItemA );
  1025. Assert( *ppItemB );
  1026. const CEconItemDefinition *pItemDefA = GetItemSchema()->GetItemDefinition( (*ppItemA)->GetItemDefinitionIndex() );
  1027. const CEconItemDefinition *pItemDefB = GetItemSchema()->GetItemDefinition( (*ppItemB)->GetItemDefinitionIndex() );
  1028. Assert( pItemDefA );
  1029. Assert( pItemDefB );
  1030. const char *pDateAddedA = pItemDefA->GetFirstSaleDate();
  1031. const char *pDateAddedB = pItemDefB->GetFirstSaleDate();
  1032. return -strcmp( pDateAddedA, pDateAddedB );
  1033. }
  1034. bool CEconStoreEntryLess::Less( const uint16& lhs, const uint16& rhs, void *pContext )
  1035. {
  1036. CEconItemSchema *pSchema = GetItemSchema();
  1037. CEconStorePriceSheet* pPriceSheet = (CEconStorePriceSheet*)pContext;
  1038. eEconStoreSortType sortType = pPriceSheet->GetEconStoreSortType();
  1039. const econ_store_entry_t *pItemA = pPriceSheet->GetEntry( lhs );
  1040. const econ_store_entry_t *pItemB = pPriceSheet->GetEntry( rhs );
  1041. Assert( pItemA );
  1042. Assert( pItemB );
  1043. CEconItemDefinition *pItemDefA = pSchema->GetItemDefinition( pItemA->GetItemDefinitionIndex() );
  1044. CEconItemDefinition *pItemDefB = pSchema->GetItemDefinition( pItemB->GetItemDefinitionIndex() );
  1045. #ifdef CLIENT_DLL
  1046. ECurrency eCurrency = EconUI()->GetStorePanel()->GetCurrency();
  1047. #else
  1048. ECurrency eCurrency = k_ECurrencyUSD;
  1049. #endif
  1050. if ( pItemDefA && pItemDefB )
  1051. {
  1052. switch ( sortType )
  1053. {
  1054. case kEconStoreSortType_Price_HighestToLowest:
  1055. if ( pItemA->GetCurrentPrice( eCurrency ) == pItemB->GetCurrentPrice( eCurrency ) )
  1056. {
  1057. return Q_strcmp( pItemDefA->GetItemBaseName(), pItemDefB->GetItemBaseName() ) < 0;
  1058. }
  1059. return pItemA->GetCurrentPrice( eCurrency ) > pItemB->GetCurrentPrice( eCurrency );
  1060. case kEconStoreSortType_Price_LowestToHighest:
  1061. if ( pItemA->GetCurrentPrice( eCurrency ) == pItemB->GetCurrentPrice( eCurrency ) )
  1062. {
  1063. return Q_strcmp( pItemDefA->GetItemBaseName(), pItemDefB->GetItemBaseName() ) < 0;
  1064. }
  1065. return pItemA->GetCurrentPrice( eCurrency ) < pItemB->GetCurrentPrice( eCurrency );
  1066. case kEconStoreSortType_DevName_AToZ:
  1067. return Q_strcmp( pItemDefA->GetItemBaseName(), pItemDefB->GetItemBaseName() ) < 0;
  1068. case kEconStoreSortType_DevName_ZToA:
  1069. return Q_strcmp( pItemDefA->GetItemBaseName(), pItemDefB->GetItemBaseName() ) > 0;
  1070. case kEconStoreSortType_DateNewest:
  1071. case kEconStoreSortType_DateOldest:
  1072. {
  1073. int iSortResult = FirstSaleDateSortComparator( &pItemA, &pItemB );
  1074. if ( iSortResult < 0 )
  1075. return sortType == kEconStoreSortType_DateNewest;
  1076. if ( iSortResult > 0 )
  1077. return sortType == kEconStoreSortType_DateOldest;
  1078. // Intentionally fall through to sorting by localized name if our dates match.
  1079. }
  1080. case kEconStoreSortType_Name_AToZ:
  1081. case kEconStoreSortType_Name_ZToA:
  1082. if ( g_pVGuiLocalize )
  1083. {
  1084. wchar_t *wszItemNameA = g_pVGuiLocalize->Find( pItemDefA->GetItemBaseName() );
  1085. wchar_t *wszItemNameB = g_pVGuiLocalize->Find( pItemDefB->GetItemBaseName() );
  1086. if ( wszItemNameA && wszItemNameB )
  1087. {
  1088. // Note: locale-savvy string sorting uses wcscoll, not wcscmp
  1089. return sortType == kEconStoreSortType_Name_ZToA
  1090. ? wcscoll( wszItemNameA, wszItemNameB ) > 0 // only sort in reverse alphabetical order if asked to -- fall-through cases uses forward order
  1091. : wcscoll( wszItemNameA, wszItemNameB ) < 0;
  1092. }
  1093. }
  1094. break;
  1095. case kEconStoreSortType_ItemDefIndex:
  1096. return pItemDefA->GetDefinitionIndex() < pItemDefB->GetDefinitionIndex();
  1097. case kEconStoreSortType_ReverseItemDefIndex:
  1098. return pItemDefB->GetDefinitionIndex() < pItemDefA->GetDefinitionIndex();
  1099. }
  1100. }
  1101. // default, highest to lowest price
  1102. return pItemA->GetCurrentPrice( eCurrency ) > pItemB->GetCurrentPrice( eCurrency );
  1103. }
  1104. #ifdef CLIENT_DLL
  1105. //-----------------------------------------------------------------------------
  1106. // Purpose: return the CLocale name that works with setlocale()
  1107. //-----------------------------------------------------------------------------
  1108. const char *GetLanguageCLocaleName( ELanguage eLang )
  1109. {
  1110. if ( eLang == k_Lang_None )
  1111. return "";
  1112. #ifdef _WIN32
  1113. // table for Win32 is here: http://msdn.microsoft.com/en-us/library/hzz3tw78(v=VS.80).aspx
  1114. // shortname works except for chinese
  1115. switch ( eLang )
  1116. {
  1117. case k_Lang_Simplified_Chinese:
  1118. return "chs"; // or "chinese-simplified"
  1119. case k_Lang_Traditional_Chinese:
  1120. return "cht"; // or "chinese-traditional"
  1121. case k_Lang_Korean:
  1122. return "korean"; // steam likes "koreana" for the name for some reason.
  1123. case k_Lang_Brazilian:
  1124. return "ptb"; // "portuguese-brazil" - that string fails even though it's in the MS lang table; ptb does work.
  1125. default:
  1126. return GetLanguageShortName( eLang );
  1127. }
  1128. #else
  1129. switch ( eLang )
  1130. {
  1131. case k_Lang_Simplified_Chinese:
  1132. case k_Lang_Traditional_Chinese:
  1133. return "zh_CN";
  1134. default:
  1135. ;
  1136. }
  1137. // ICU codes work on linux/osx
  1138. return GetLanguageICUName( eLang );
  1139. #endif
  1140. }
  1141. //-----------------------------------------------------------------------------
  1142. // Purpose: Get an I/O stream into the right local/settings for printing money - so to speak
  1143. //-----------------------------------------------------------------------------
  1144. static void InitStreamLocale( std::wostringstream &stream, ELanguage eLang, uint32 nExpectedAmount, ECurrency eCurrencyCode )
  1145. {
  1146. const char *pszLocale = GetLanguageCLocaleName( eLang );
  1147. #ifdef _PS3
  1148. stream.imbue(std::locale(pszLocale)); // no exception for PS3
  1149. #else
  1150. try
  1151. {
  1152. stream.imbue(std::locale(pszLocale));
  1153. }
  1154. catch (const std::exception &e)
  1155. {
  1156. Log( "stream::imbue() failed with locale: '%s', exception: %s\n", pszLocale, e.what() );
  1157. stream.imbue( std::locale("C") );
  1158. }
  1159. #endif
  1160. // Don't display fractional rubles
  1161. // But if our amount is fractional, we should show it regardless
  1162. // hack hack - certain currencies should not show fractional symbol - see if we can wire this through from config at some point
  1163. if ( ( eCurrencyCode == k_ECurrencyRUB || eCurrencyCode == k_ECurrencyJPY || eCurrencyCode == k_ECurrencyIDR || eCurrencyCode == k_ECurrencyKRW )
  1164. && nExpectedAmount % 100 == 0 )
  1165. {
  1166. stream.precision( 0 );
  1167. stream.setf( std::ios_base::fixed );
  1168. }
  1169. else
  1170. {
  1171. stream.precision( 2 );
  1172. stream.setf( std::ios_base::fixed, std::ios_base::floatfield );
  1173. }
  1174. }
  1175. //
  1176. // Partial Integration from \steam\main\src\common\amount.cpp
  1177. //
  1178. int MakeMoneyStringInternal( wchar_t *pchDest, uint32 nDest, item_price_t unPrice, ECurrency eCurrencyCode, ELanguage eLanguage )
  1179. {
  1180. // Use the actual currency symbol with the local number formatting.
  1181. // assume local locale - should not be used from server to send to client
  1182. // without passing in a valid pszCLocale parameter.
  1183. std::wostringstream stream;
  1184. InitStreamLocale( stream, eLanguage, unPrice, eCurrencyCode );
  1185. stream << (unPrice/100.0);
  1186. const auto sAmount = stream.str();
  1187. const wchar_t *wszAmount = sAmount.c_str();
  1188. // this code will be used by the client. An old client might encounter a currency code it doesn't know about. Handle that.
  1189. if ( eCurrencyCode == k_ECurrencyInvalid )
  1190. {
  1191. // just return the value
  1192. return V_snwprintf( pchDest, nDest, L"%ls", wszAmount );
  1193. }
  1194. // select symbol
  1195. const char *pchSymbol = "";
  1196. switch( eCurrencyCode )
  1197. {
  1198. case k_ECurrencyUSD:
  1199. pchSymbol = "$";
  1200. break;
  1201. case k_ECurrencyGBP:
  1202. pchSymbol = "\xC2\xA3";
  1203. break;
  1204. case k_ECurrencyEUR:
  1205. pchSymbol = "\xE2\x82\xAC";
  1206. break;
  1207. case k_ECurrencyCHF:
  1208. pchSymbol = "CHF";
  1209. break;
  1210. case k_ECurrencyRUB:
  1211. pchSymbol = "\xD1\x80\xD1\x83\xD0\xB1"; // localized py6
  1212. break;
  1213. case k_ECurrencyBRL:
  1214. pchSymbol = "R$";
  1215. break;
  1216. case k_ECurrencyJPY:
  1217. pchSymbol = "\xC2\xA5";
  1218. break;
  1219. case k_ECurrencyIDR:
  1220. pchSymbol = "Rp";
  1221. break;
  1222. case k_ECurrencyMYR:
  1223. pchSymbol = "RM";
  1224. break;
  1225. case k_ECurrencyPHP:
  1226. pchSymbol = "\xE2\x82\xB1";
  1227. break;
  1228. case k_ECurrencySGD:
  1229. pchSymbol = "S$";
  1230. break;
  1231. case k_ECurrencyTHB:
  1232. pchSymbol = "\xE0\xB8\xBF";
  1233. break;
  1234. case k_ECurrencyVND:
  1235. pchSymbol = "\xE2\x82\xAB";
  1236. break;
  1237. case k_ECurrencyKRW:
  1238. pchSymbol = "\xe2\x82\xa9";
  1239. break;
  1240. case k_ECurrencyTRY:
  1241. pchSymbol = "TL";
  1242. break;
  1243. case k_ECurrencyUAH:
  1244. pchSymbol = "\xe2\x82\xb4";
  1245. break;
  1246. case k_ECurrencyMXN:
  1247. pchSymbol = "Mex$";
  1248. break;
  1249. case k_ECurrencyCAD:
  1250. pchSymbol = "C$";
  1251. break;
  1252. case k_ECurrencyAUD:
  1253. pchSymbol = "A$";
  1254. break;
  1255. case k_ECurrencyNZD:
  1256. pchSymbol = "NZ$";
  1257. break;
  1258. case k_ECurrencyNOK:
  1259. pchSymbol = "kr";
  1260. break;
  1261. case k_ECurrencyPLN:
  1262. pchSymbol = "z\xc5\x82";
  1263. break;
  1264. case k_ECurrencyCNY:
  1265. pchSymbol = "\xc2\xa5";
  1266. break;
  1267. case k_ECurrencyINR:
  1268. pchSymbol = "\xe2\x82\xb9";
  1269. break;
  1270. case k_ECurrencyCLP:
  1271. pchSymbol = "$"; // bugbug - prefix it with CLP?
  1272. break;
  1273. case k_ECurrencyPEN:
  1274. pchSymbol = "S/.";
  1275. break;
  1276. case k_ECurrencyCOP:
  1277. pchSymbol = "COL$";
  1278. break;
  1279. case k_ECurrencyZAR:
  1280. pchSymbol = "R";
  1281. break;
  1282. case k_ECurrencyHKD:
  1283. pchSymbol = "HK$";
  1284. break;
  1285. case k_ECurrencyTWD:
  1286. pchSymbol = "NT$";
  1287. break;
  1288. case k_ECurrencySAR:
  1289. pchSymbol = "SR";
  1290. break;
  1291. case k_ECurrencyAED:
  1292. pchSymbol = "DH";
  1293. break;
  1294. default:
  1295. AssertMsg( false, "Unknown currency code" );
  1296. pchSymbol = "$";
  1297. break;
  1298. }
  1299. // BEGIN HACK GAME CLIENT CHARACTER SET CONVERSION
  1300. wchar_t wsSymbol[ 16 ];
  1301. V_UTF8ToUnicode( pchSymbol, wsSymbol, ARRAYSIZE( wsSymbol ) );
  1302. // END HACK GAME CLIENT CHARACTER SET CONVERSION
  1303. bool bFirstSymbolThenAmount = true; // Whether to show "$5" or "5E"
  1304. bool bSpaceBetweenTokens = false; // Whether to render a space between tokens like "$5" has no space, but "5 pyb" has a space
  1305. switch ( eCurrencyCode )
  1306. {
  1307. case k_ECurrencyEUR:
  1308. case k_ECurrencyUAH:
  1309. bFirstSymbolThenAmount = false;
  1310. bSpaceBetweenTokens = false;
  1311. break;
  1312. case k_ECurrencyRUB:
  1313. case k_ECurrencyVND:
  1314. case k_ECurrencyNOK:
  1315. case k_ECurrencyTRY:
  1316. case k_ECurrencyPLN:
  1317. case k_ECurrencySAR:
  1318. case k_ECurrencyAED:
  1319. bFirstSymbolThenAmount = false;
  1320. bSpaceBetweenTokens = true;
  1321. break;
  1322. case k_ECurrencyIDR:
  1323. case k_ECurrencyMXN:
  1324. case k_ECurrencyCAD:
  1325. case k_ECurrencyAUD:
  1326. case k_ECurrencyNZD:
  1327. case k_ECurrencyPEN:
  1328. case k_ECurrencyCOP:
  1329. case k_ECurrencyZAR:
  1330. case k_ECurrencyHKD:
  1331. case k_ECurrencyTWD:
  1332. case k_ECurrencyKRW:
  1333. case k_ECurrencyCHF:
  1334. bFirstSymbolThenAmount = true;
  1335. bSpaceBetweenTokens = true;
  1336. default:
  1337. bFirstSymbolThenAmount = true;
  1338. bSpaceBetweenTokens = false;
  1339. }
  1340. return V_snwprintf( pchDest, nDest, L"%ls%ls%ls",
  1341. ( bFirstSymbolThenAmount ? wsSymbol : wszAmount ),
  1342. ( bSpaceBetweenTokens ? L" " : L"" ),
  1343. ( bFirstSymbolThenAmount ? wszAmount : wsSymbol ) );
  1344. }
  1345. static ELanguage GetStoreLanguage()
  1346. {
  1347. if ( !engine )
  1348. return k_Lang_English;
  1349. char uilanguage[ 64 ];
  1350. uilanguage[0] = 0;
  1351. engine->GetUILanguage( uilanguage, sizeof( uilanguage ) );
  1352. return PchLanguageToELanguage( uilanguage );
  1353. }
  1354. void MakeMoneyString( wchar_t *pchDest, uint32 nDest, item_price_t unPrice, ECurrency eCurrencyCode )
  1355. {
  1356. (void)MakeMoneyStringInternal( pchDest, nDest, unPrice, eCurrencyCode, GetStoreLanguage() );
  1357. }
  1358. #endif // CLIENT_DLL
  1359. //-----------------------------------------------------------------------------
  1360. // Purpose:
  1361. //-----------------------------------------------------------------------------
  1362. bool CEconStorePriceSheet::BItemExistsInPriceSheet( item_definition_index_t unDefIndex ) const
  1363. {
  1364. CEconStoreCategoryManager *pCategoryManager = GEconStoreCategoryManager();
  1365. const int nCategoryCount = pCategoryManager->GetNumCategories();
  1366. for ( int i = 0; i < nCategoryCount; ++i )
  1367. {
  1368. const CEconStoreCategoryManager::StoreCategory_t *pCat = pCategoryManager->GetCategoryFromIndex( i );
  1369. // Intentionally not using CUtlSortVector<>::Find(), since it calls Less(), which is slow as shit for m_vecEntries.
  1370. FOR_EACH_VEC( pCat->m_vecEntries, j )
  1371. {
  1372. if ( pCat->m_vecEntries[j] == unDefIndex )
  1373. return true;
  1374. }
  1375. }
  1376. return false;
  1377. }
  1378. #ifdef CLIENT_DLL
  1379. //-----------------------------------------------------------------------------
  1380. // Purpose:
  1381. //-----------------------------------------------------------------------------
  1382. bool ShouldUseNewStore()
  1383. {
  1384. return true;
  1385. }
  1386. //-----------------------------------------------------------------------------
  1387. // Purpose:
  1388. //-----------------------------------------------------------------------------
  1389. int GetStoreVersion()
  1390. {
  1391. return 2;
  1392. }
  1393. #endif // CLIENT_DLL
  1394. //-----------------------------------------------------------------------------
  1395. // Purpose:
  1396. //-----------------------------------------------------------------------------
  1397. const CEconStorePriceSheet *GetEconPriceSheet()
  1398. {
  1399. #ifdef GC_DLL
  1400. return GEconManager()->GetPriceSheet();
  1401. #else
  1402. return EconUI() && EconUI()->GetStorePanel()
  1403. ? EconUI()->GetStorePanel()->GetPriceSheet()
  1404. : NULL;
  1405. #endif
  1406. }