summaryrefslogtreecommitdiff
path: root/game/shared/econ/econ_store.h
blob: 56cf59993ccb2921faf387b468c6a37cbce083a6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Common objects and utilities related to the in-game item store
//
//=============================================================================

#ifndef ECON_STORE_H
#define ECON_STORE_H
#ifdef _WIN32
#pragma once
#endif

#include "UtlSortVector.h"
#include "vstdlib/IKeyValuesSystem.h"
#include "econ/econ_storecategory.h"
#ifdef CLIENT_DLL
#include "client_community_market.h"
#endif // CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose: Error code enum for purchase messages
//-----------------------------------------------------------------------------
enum EPurchaseResult
{
	k_EPurchaseResultOK	= 1,					// Success
	k_EPurchaseResultFail = 2,					// Generic error
	k_EPurchaseResultInvalidParam = 3,			// Invalid parameter
	k_EPurchaseResultInternalError = 4,			// Internal error
	k_EPurchaseResultNotApproved = 5,			// Tried to finalize a transaction that has not yet been approved
	k_EPurchaseResultAlreadyCommitted = 6,		// Tried to finalize a transaction that has already been committed
	k_EPurchaseResultUserNotLoggedIn = 7,		// User is not logged into Steam
	k_EPurchaseResultWrongCurrency = 8,			// Microtransaction's currency does not match user's wallet currency
	k_EPurchaseResultAccountError = 9,			// User's account does not exist or is temporarily unavailable
	k_EPurchaseResultInvalidItem = 10,			// User is trying to purchase an item that doesn't exist or is not for sale
	k_EPurchaseResultNotEnoughBackpackSpace = 11,	// User did not have enough backpack space
	k_EPurchaseResultLimitedQuantityItemsUnavailable = 12,	// User tried to purchase limited-quantity items but there weren't enough left in stock

	k_EPurchaseResultInsufficientFunds = 100,	// User does not have wallet funds
	k_EPurchaseResultTimedOut = 101,			// Time limit for finalization has been exceeded
	k_EPurchaseResultAcctDisabled = 102,		// Steam account is disabled
	k_EPurchaseResultAcctCannotPurchase = 103,	// Steam account is not allowed to make a purchase
	k_EMicroTxnResultFailedFraudChecks = 104,   // Fraud checks inside of Steam failed

	k_EPurchaseResultOldPriceSheet = 150,		// Information on the purchase didn't match the current price sheet
	k_EPurchaseResultTxnNotFound = 151			// Could not find the transaction specified
};


const char *PchNameFromEPurchaseResult( EPurchaseResult ePurchaseState );

//-----------------------------------------------------------------------------
// Purpose: State of a transaction
//
// WARNING: VALUES STORED IN DATABASE. DO NOT RENUMBER!!!
//-----------------------------------------------------------------------------
enum EPurchaseState
{
	k_EPurchaseStateInvalid = 0,				// Invalid
	k_EPurchaseStateInit = 1,					// We have sent InitPurchase to Steam
	k_EPurchaseStateWaitingForAuthorization = 2, // We have gotten initial authorization from Steam. Waiting for user to authorize.
	k_EPurchaseStatePending = 3,				// We are attempting to commit the transaction
	k_EPurchaseStateComplete = 4,				// The transaction was successful
	k_EPurchaseStateFailed = 5,					// The transaction failed
	k_EPurchaseStateCanceled = 6,				// The transaction was canceled
	k_EPurchaseStateRefunded = 7,				// The transaction was refunded
	k_EPurchaseStateChargeback = 8,				// The transaction was charged back
	k_EPurchaseStateChargebackReversed = 9,		// A chargeback has failed and we got the money
	k_EPurchaseStateLast = k_EPurchaseStateChargebackReversed,
};

const char *PchNameFromEPurchaseState( EPurchaseState ePurchaseState );

// DO NOT RENUMBER! These values are stored in the audit log table.
enum EGCTransactionAuditReason
{
	k_EGCTransactionAudit_GCTransactionCompleted = 0,				// The transaction completed successfully on the GC.
	k_EGCTransactionAudit_GCTransactionInit = 1,					// The transaction was initialized.
	k_EGCTransactionAudit_GCTransactionPostInit = 2,				// The result of attempting to initialize the transaction. This is where the SteamTxnID is set.
	k_EGCTransactionAudit_GCTransactionFinalize = 3,				// We have started to finalize the transaction (so probably set it to pending).
	k_EGCTransactionAudit_GCTransactionFinalizeFailed = 4,			// Our attempt to finalize the transaction failed for some reason (not due to a timeout).
	k_EGCTransactionAudit_GCTransactionCanceled = 5,				// The client requested that we cancel the transaction.
	k_EGCTransactionAudit_SteamFailedMismatch = 6,					// Steam failed the transaction but we did not.
	k_EGCTransactionAudit_GCRemovePurchasedItems = 7,				// We are attempting to remove the purchased items from their backpack (due to rollback or failure).
	k_EGCTransactionAudit_GCTransactionInsert = 8,					// We are inserting a transaction, usually one created by a web interface (i.e.: a cd-key operation) instead of a standard store interaction.
	k_EGCTransactionAudit_GCTransactionCompletedPostChargeback = 9,	// We thought this transaction had been charged back but Steam came back later and told us it was successful after all.

	k_EGCTransactionAuditLast = k_EGCTransactionAudit_GCTransactionCompletedPostChargeback,
};

const char *PchNameFromEGCTransactionAuditReason( EGCTransactionAuditReason eAuditReason );

enum EGCTransactionAuditInsertReason
{
	k_EGCTransactionAuditInsert_Invalid = 0,					// 
	k_EGCTransactionAuditInsert_CDKey = 1,						// A CD Key was used for this transaction.
	k_EGCTransactionAuditInsert_SettlementNoMatch_Failed = 2,	// We inserted a failed record during settlement to unblock the process.
	k_EGCTransactionAuditInsert_SettlementNoMatch_Pending = 3,	// We inserted a pending record during settlement to unblock the process.

	k_EGCTransactionAuditInsertLast = k_EGCTransactionAuditInsert_SettlementNoMatch_Pending,
};

//-----------------------------------------------------------------------------
// Purpose: Currencies we support
//
// WARNING: VALUES STORED IN DATABASE. DO NOT RENUMBER!!!
// WARNING: THESE DON'T MATCH THE STEAM NUMERIC IDS!!! WE TALK USING CURRENCY
//			CODES LIKE "VND" AND IF YOU SAY "15" TERRIBLE THINGS WILL HAPPEN
//-----------------------------------------------------------------------------
enum ECurrency
{
	k_ECurrencyFirst = 0,
	k_ECurrencyUSD = 0,
	k_ECurrencyGBP = 1,
	k_ECurrencyEUR = 2,
	k_ECurrencyRUB = 3,
	k_ECurrencyBRL = 4,
	// space for Dota currencies
	k_ECurrencyJPY = 8,
	k_ECurrencyNOK = 9,
	k_ECurrencyIDR = 10,
	k_ECurrencyMYR = 11,
	k_ECurrencyPHP = 12,
	k_ECurrencySGD = 13,
	k_ECurrencyTHB = 14,
	k_ECurrencyVND = 15,
	k_ECurrencyKRW = 16,
	k_ECurrencyTRY = 17,
	k_ECurrencyUAH = 18,
	k_ECurrencyMXN = 19,
	k_ECurrencyCAD = 20,
	k_ECurrencyAUD = 21,
	k_ECurrencyNZD = 22,
	k_ECurrencyPLN = 23,
	k_ECurrencyCHF = 24,
	k_ECurrencyCNY = 25,
	k_ECurrencyTWD = 26,
	k_ECurrencyHKD = 27,
	k_ECurrencyINR = 28,
	k_ECurrencyAED = 29,
	k_ECurrencySAR = 30,
	k_ECurrencyZAR = 31,
	k_ECurrencyCOP = 32,
	k_ECurrencyPEN = 33,
	k_ECurrencyCLP = 34,

	// NOTE: Not actually the Maximum currency value, but the Terminator for the possible currency code range.
	k_ECurrencyMax = 35,

	// make this a big number so we can avoid having to move it when we add another currency type
	k_ECurrencyInvalid = 255,
	k_ECurrencyCDKeyTransaction = k_ECurrencyInvalid,
};

// Macro for looping across all valid currencies
#define FOR_EACH_CURRENCY( _i ) for ( ECurrency _i = GetFirstValidCurrency(); _i != k_ECurrencyInvalid; _i = GetNextValidCurrency( _i ) )

const char *PchNameFromECurrency( ECurrency eCurrency );	// NOTE: Defined with ENUMSTRINGS_START/ENUMSTRINGS_REVERSE macros
ECurrency ECurrencyFromName( const char *pchName );			//

inline bool BIsCurrencyValid( ECurrency eCurrency )
{
	switch ( eCurrency )
	{
	case k_ECurrencyUSD:
	case k_ECurrencyGBP:
	case k_ECurrencyEUR:
	case k_ECurrencyRUB:
	case k_ECurrencyBRL:
	case k_ECurrencyJPY:
	case k_ECurrencyNOK:
	case k_ECurrencyIDR:
	case k_ECurrencyMYR:
	case k_ECurrencyPHP:
	case k_ECurrencySGD:
	case k_ECurrencyTHB:
	case k_ECurrencyVND:
	case k_ECurrencyKRW:
	case k_ECurrencyTRY:
	case k_ECurrencyUAH:
	case k_ECurrencyMXN:
	case k_ECurrencyCAD:
	case k_ECurrencyAUD:
	case k_ECurrencyNZD:
	//case k_ECurrencyPLN:
	case k_ECurrencyCHF:
	case k_ECurrencyCNY:
	case k_ECurrencyTWD:
	case k_ECurrencyHKD:
	case k_ECurrencyINR:
	case k_ECurrencyAED:
	case k_ECurrencySAR:
	case k_ECurrencyZAR:
	case k_ECurrencyCOP:
	case k_ECurrencyPEN:
	case k_ECurrencyCLP:
		return true;
	}

	return false;
}

inline ECurrency GetFirstValidCurrency()
{
	for ( int i = k_ECurrencyFirst; i < k_ECurrencyMax; i++ )
	{
		if ( BIsCurrencyValid( (ECurrency)i ) )
			return (ECurrency)i;
	}
	return k_ECurrencyInvalid;
}

inline ECurrency GetNextValidCurrency( ECurrency ePrevious )
{
	for ( int i = ePrevious + 1; i < k_ECurrencyMax; i++ )
	{
		if ( BIsCurrencyValid( (ECurrency)i ) )
			return (ECurrency)i;
	}
	return k_ECurrencyInvalid;
}

//-----------------------------------------------------------------------------
// Purpose: Simple struct for pairing a sort type with a localization string
//-----------------------------------------------------------------------------
struct ItemSortTypeData_t
{
	const char *szSortDesc;		// localization string
	uint32		iSortType;		// maps to the GC-specific sort value
};

// Description of a single item for sale
struct econ_store_entry_t
{
	// Constructor
	econ_store_entry_t()
	:	m_pchCategoryTags( NULL ),
		m_unGiftSteamPackageID( 0 ),
		m_bHighlighted( false )
	{
		V_memset( m_unBaseCosts, 0, sizeof(m_unBaseCosts) );
		V_memset( m_unSaleCosts, 0, sizeof(m_unSaleCosts) );
	}

	void SetItemDefinitionIndex( item_definition_index_t usDefIndex );
	item_definition_index_t GetItemDefinitionIndex() const { return m_usDefIndex; }
	
	void InitCategoryTags( const char *pTags );	// Sets m_pchCategoryTags and initializes m_vecTagIds and m_fRentalPriceScale

	bool IsListedInCategory( StoreCategoryID_t unID ) const;	// Is this item listed in the given category?
	bool IsListedInSubcategories( const CEconStoreCategoryManager::StoreCategory_t &Category ) const;	// Is this item listed in one of Category's subcategories?
	bool IsListedInCategoryOrSubcategories( const CEconStoreCategoryManager::StoreCategory_t &Category ) const;	// Is this item listed in Category or one of Category's subcategories?
	
	bool IsOnSale( ECurrency eCurrency ) const;
	bool IsRentable() const;
#ifdef CLIENT_DLL
	bool HasDiscount( ECurrency eCurrency, item_price_t *out_punOptionalBasePrice ) const;			// returns true if we're on sale or if we're a bundle with a discounted total price
#endif // CLIENT_DLL
	item_price_t GetCurrentPrice( ECurrency eCurrency ) const;
	float GetRentalPriceScale() const;

	uint32 GetGiftSteamPackageID() const { return m_unGiftSteamPackageID; }

	// Helper function -- so we do this calculation in a single place.
	static item_price_t GetDiscountedPrice( ECurrency eCurrency, item_price_t unBasePrice, float fDiscountPercentage );

	static item_price_t CalculateSalePrice( const econ_store_entry_t* pSaleStoreEntry, ECurrency eCurrency, float fDiscountPercentage, int32 *out_pAdjustedDiscountPercentage = NULL );

	item_price_t GetBasePrice( ECurrency eCurrency ) const
	{
		Assert( eCurrency >= k_ECurrencyFirst );
		Assert( eCurrency < k_ECurrencyMax );
		if ( !( eCurrency >= 0 && eCurrency < k_ECurrencyMax ) )
			return 0;
#ifdef CLIENT_DLL
		if ( m_bIsMarketItem )
		{
			const client_market_data_t *pClientMarketData = GetClientMarketData( GetItemDefinitionIndex(), AE_UNIQUE );
			if ( !pClientMarketData )
				return 0;
			return pClientMarketData->m_unLowestPrice;
		}
#endif
		// Weird-looking pattern: we're making sure that the value we're about to return fits correctly
		// into the variable we're about to put it into. We do this to avoid integer conversion problems,
		// especially overflow (!) where someone changes one of the return type or the storage type but
		// not the other.
		Assert( (item_price_t)m_unBaseCosts[eCurrency] == m_unBaseCosts[eCurrency] );
		return m_unBaseCosts[eCurrency];
	}

	item_price_t GetSalePrice( ECurrency eCurrency ) const
	{
		Assert( eCurrency >= k_ECurrencyFirst );
		Assert( eCurrency < k_ECurrencyMax );
		if ( !( eCurrency >= 0 && eCurrency < k_ECurrencyMax ) )
			return 0;
#ifdef CLIENT_DLL
		if ( m_bIsMarketItem )
		{
			const client_market_data_t *pClientMarketData = GetClientMarketData( GetItemDefinitionIndex(), AE_UNIQUE );
			if ( !pClientMarketData )
				return 0;
			return pClientMarketData->m_unLowestPrice;
		}
#endif
		// Weird-looking pattern: we're making sure that the value we're about to return fits correctly
		// into the variable we're about to put it into. We do this to avoid integer conversion problems,
		// especially overflow (!) where someone changes one of the return type or the storage type but
		// not the other. 
		Assert( (item_price_t)m_unSaleCosts[eCurrency] == m_unSaleCosts[eCurrency] );
		return m_unSaleCosts[eCurrency];
	}

	uint16 GetQuantity() const
	{
		return m_usQuantity;
	}

	const char* GetDate() const
	{
		return m_strDate.Get();
	}

	bool CanPreview() const
	{
		// No previewing of new items or weapons.
		return m_bPreviewAllowed;
	}

	void SetQuantity( uint16 usQuantity )
	{
		Assert( usQuantity > 0 );
		m_usQuantity = usQuantity;
	}

	void ValidatePrice( ECurrency eCurrency, item_price_t unPrice );

	void SetBasePrice( ECurrency eCurrency, item_price_t unPrice )
	{
		Assert( eCurrency >= k_ECurrencyFirst );
		Assert( eCurrency < k_ECurrencyMax );
		if ( !( eCurrency >= 0 && eCurrency < k_ECurrencyMax ) )
			return;

		ValidatePrice( eCurrency, unPrice );

		m_unBaseCosts[eCurrency] = unPrice;
	}

	void SetSalePrice( ECurrency eCurrency, item_price_t unPrice )
	{
		Assert( eCurrency >= k_ECurrencyFirst );
		Assert( eCurrency < k_ECurrencyMax );
		if ( !( eCurrency >= 0 && eCurrency < k_ECurrencyMax ) )
			return;

		ValidatePrice( eCurrency, unPrice );

		// It's legal to have a sale price of zero, meaninig "this item is not on sale" in this
		// currency.
		// Assert( unPrice > 0 );
		m_unSaleCosts[eCurrency] = unPrice;
	}

	void SetSteamGiftPackageID( uint32 unGiftSteamPackageID )
	{
		m_unGiftSteamPackageID = unGiftSteamPackageID;
	}
	
	void SetDate( const char* pszDate )
	{
		m_strDate.Set( pszDate );
	}


	bool IsValidCategoryTagIndex( uint32 iIndex ) const
	{
		AssertMsg( m_vecCategoryTags.IsValidIndex( iIndex ), "Category tag index out of range." );
		return m_vecCategoryTags.IsValidIndex( iIndex );
	}

	uint32 GetCategoryTagCount() const
	{
		return m_vecCategoryTags.Count();
	}

	const char *GetCategoryTagNameFromIndex( uint32 iIndex ) const
	{
		if ( !IsValidCategoryTagIndex( iIndex ) )
			return NULL;

		return m_vecCategoryTags[ iIndex ].m_strName;
	}

	StoreCategoryID_t GetCategoryTagIDFromIndex( uint32 iIndex ) const;

	const char *GetCategoryTagString() const
	{
		return m_pchCategoryTags;
	}

	bool							m_bLimited;							// Item is a limited sale
	bool							m_bNew;								// Item is new
	bool							m_bHighlighted;						// Item is highlighted
	CUtlString						m_strDate;							// Date Added
	bool							m_bSoldOut;							// True if the item is sold out from the store (for example if the item is a ticket or another physical item)
	bool							m_bPreviewAllowed;					// Is this item previewable?
	bool							m_bIsPackItem;						// Is this item a pack item? Pack items are items which are not individually for sale, but are sold via a bundle known as a "pack bundle"

	bool							m_bIsMarketItem;					// Is Market Item Link

private:
	item_definition_index_t			m_usDefIndex;						// DefIndex of the item

	// Private data so that we can check in the accessor functions that the data fits before returning it.
	item_price_t					m_unBaseCosts[k_ECurrencyMax];		// Costs of the items indexed by ECurrency -- if the items are on sale, this will be the current sale price
	item_price_t					m_unSaleCosts[k_ECurrencyMax];		// Original costs of the items indexed by ECurrency -- if the items are on sale, this will be the pre-sale price
	uint16							m_usQuantity;						// Quantity sold in a single purchase (ie., dueling pistols come in stacks of five)
	float							m_fRentalPriceScale;				// 100.0 or greater means "unavailable to rent"
	uint32							m_unGiftSteamPackageID;				// if non-zero, when this item is purchased (including inside bundles, etc.), grant a gift copy of this Steam package

	struct CategoryTag_t
	{
		CUtlString				m_strName;								// Individual tag name, like "Weapons," "New," etc.
		StoreCategoryID_t		m_unID;									// The category ID
	};
	CCopyableUtlVector< CategoryTag_t >	m_vecCategoryTags;				// Category tag data
	
	const char					*m_pchCategoryTags;						// All tags - this string will something like: "New" or "Weapons+New" etc.
};

#ifdef GC_DLL
struct econ_store_timed_sale_item_t
{
	item_definition_index_t m_unItemDef;
	float					m_fPricePercentage;	// 100.0 = regular price; 50.0 = half price
};

struct econ_store_timed_sale_t
{
	bool m_bSaleCurrentlyActive;				// set in ::UpdatePricesForTimedSales()
	CUtlConstString m_sIdentifier;				// can't point to memory in the base KV because we toss it afterwards
	RTime32	m_SaleStartTime;
	RTime32	m_SaleEndTime;
	CUtlVector<econ_store_timed_sale_item_t> m_vecSaleItems;

	// Work around protected default vector constructor.
	econ_store_timed_sale_t() { }
	econ_store_timed_sale_t( const econ_store_timed_sale_t& other )
		: m_bSaleCurrentlyActive( other.m_bSaleCurrentlyActive )
		, m_sIdentifier( other.m_sIdentifier )
		, m_SaleStartTime( other.m_SaleStartTime )
		, m_SaleEndTime( other.m_SaleEndTime )
	{
		m_vecSaleItems.CopyArray( other.m_vecSaleItems.Base(), other.m_vecSaleItems.Count() );
	}
};
#endif // GC_DLL

// Spend xxx amount of money, get a free item from the loot list
struct store_promotion_spend_for_free_item_t
{
	const CEconItemDefinition	  *m_pItemDef;
	item_price_t				   m_rgusPriceThreshold[k_ECurrencyMax];	// Price threshold to get an item from the loot list indexed by ECurrency
};


//-----------------------------------------------------------------------------
// Purpose: Class that represents what's currently for sale in TF
//-----------------------------------------------------------------------------
typedef enum
{
	kEconStoreSortType_Price_HighestToLowest = 0,
	kEconStoreSortType_Price_LowestToHighest = 1,
	kEconStoreSortType_DevName_AToZ = 2,
	kEconStoreSortType_DevName_ZToA = 3,
	kEconStoreSortType_Name_AToZ = 4,
	kEconStoreSortType_Name_ZToA = 5,
	kEconStoreSortType_ItemDefIndex = 6,
	kEconStoreSortType_ReverseItemDefIndex = 7,
	kEconStoreSortType_DateNewest = 8,
	kEconStoreSortType_DateOldest = 9,
} eEconStoreSortType;

struct price_point_map_key_t
{
	item_price_t m_unPriceUSD;
	ECurrency m_eCurrency;

	static bool Less( const price_point_map_key_t& a, const price_point_map_key_t& b )
	{
		if ( a.m_eCurrency == b.m_eCurrency )
			return a.m_unPriceUSD < b.m_unPriceUSD;

		return a.m_eCurrency < b.m_eCurrency;
	}
};

typedef CUtlMap<price_point_map_key_t, item_price_t> CurrencyPricePointMap_t;

class CEconStorePriceSheet
{
public:
	typedef CUtlMap<item_definition_index_t, econ_store_entry_t> StoreEntryMap_t;
	typedef CUtlMap<const char *, float> RentalPriceScaleMap_t;
	typedef CUtlVector<item_definition_index_t> FeaturedItems_t;

	CEconStorePriceSheet();
	~CEconStorePriceSheet();

	bool InitFromKV( KeyValues *pKVPrices );

	// Gets or sets the version stamp. This is just a number the GC can use
	// to know if the client is in sync without sending it down on every
	// request.
	RTime32 GetVersionStamp( void ) const { return m_RTimeVersionStamp; }
	void SetVersionStamp( RTime32 stamp ) { m_RTimeVersionStamp = stamp; }

	uint32 GetHashForAllItems() const { return m_unHashForAllItems; }

	typedef CUtlMap<uint16, econ_store_entry_t> EconStoreEntryMap_t;
	EconStoreEntryMap_t &GetEntries() { return m_mapEntries; }
	
#ifdef GC_DLL
	econ_store_entry_t *GetEntryWriteable( item_definition_index_t unDefIndex );
#endif // GC_DLL

	const StoreEntryMap_t &GetEntries() const { return m_mapEntries; }
	const CEconStoreCategoryManager::StoreCategory_t *GetFeaturedItems( void ) { return &m_FeaturedItems; }
	const econ_store_entry_t *GetEntry( item_definition_index_t usDefIndex ) const;

	uint32 GetFeaturedItemIndex() const { return m_unFeaturedItemIndex; }
	void SetFeaturedItemIndex( uint32 unIdx ) { m_unFeaturedItemIndex = unIdx; }

	void SetEconStoreSortType( eEconStoreSortType eType ) { m_eEconStoreSortType = eType; }
	eEconStoreSortType GetEconStoreSortType() { return m_eEconStoreSortType; }

	const store_promotion_spend_for_free_item_t *GetStorePromotion_SpendForFreeItem() const { return &m_StorePromotionSpendForFreeItem; }
	const CEconItemDefinition * GetStorePromotion_FirstTimePurchaseItem() const { return m_pStorePromotionFirstTimePurchaseItem; }
	const CEconItemDefinition * GetStorePromotion_FirstTimeWebPurchaseItem() const { return m_pStorePromotionFirstTimeWebPurchaseItem; }

	uint32 GetPreviewPeriod() const { return m_unPreviewPeriod; }
	uint32 GetBonusDiscountPeriod() const { return m_unBonusDiscountPeriod; }
	float GetPreviewPeriodDiscount() const { return m_flPreviewPeriodDiscount; }

	bool BItemExistsInPriceSheet( item_definition_index_t unDefIndex ) const;

	float GetRentalPriceScale( const char *pszCategory ) const
	{
		RentalPriceScaleMap_t::IndexType_t i = m_mapRentalPriceScales.Find( pszCategory );
		if ( i == RentalPriceScaleMap_t::InvalidIndex() )
			return 1.0f;

		return m_mapRentalPriceScales[i];
	}

	KeyValues *GetRawData() const { return m_pKVRaw; }

#ifdef GC_DLL
	void UpdatePricesForTimedSales( const RTime32 curTime );
	void DumpTimeSaleState( const RTime32 curTime ) const;
#endif // GC_DLL

#ifdef CLIENT_DLL
	const FeaturedItems_t& GetFeaturedItems() const { return m_vecFeaturedItems; }
#endif // CLIENT_DLL

private:
	bool BInitEntryFromKV( KeyValues *pKVEntry );
#ifdef CLIENT_DLL
	bool BInitMarketEntryFromKV( KeyValues *pKVEntry );
#endif // CLIENT_DLL

#ifdef GC_DLL
	bool InitTimedSaleEntryFromKV( KeyValues *pKVTimedSaleEntry );
	bool VerifyTimedSaleEntries();
#endif // GC_DLL

private:
	void Clear();
	uint32 CalculateHashFromItems() const;
	
	KeyValues	*m_pKVRaw;
	RTime32		m_RTimeVersionStamp;
	CEconStoreCategoryManager::StoreCategory_t	m_FeaturedItems;		// Special section, not a tab, kept outside m_vecContents
	StoreEntryMap_t m_mapEntries;
	RentalPriceScaleMap_t m_mapRentalPriceScales;
	store_promotion_spend_for_free_item_t m_StorePromotionSpendForFreeItem;
	CEconItemDefinition* m_pStorePromotionFirstTimePurchaseItem;
	CEconItemDefinition* m_pStorePromotionFirstTimeWebPurchaseItem;

#ifdef CLIENT_DLL
	FeaturedItems_t m_vecFeaturedItems;
#endif // CLIENT_DLL

#ifdef GC_DLL
	CUtlVector<econ_store_timed_sale_t> m_vecTimedSales;
#endif // GC_DLL

	// changes based on experiments
	uint32	m_unFeaturedItemIndex;
	eEconStoreSortType m_eEconStoreSortType;

	uint32 m_unPreviewPeriod;
	uint32 m_unBonusDiscountPeriod;
	float m_flPreviewPeriodDiscount;
	uint32 m_unHashForAllItems;

	// price point lookup
	CurrencyPricePointMap_t m_mapCurrencyPricePoints;
};

#ifdef CLIENT_DLL
void MakeMoneyString( wchar_t *pchDest, uint32 nDest, item_price_t unPrice, ECurrency eCurrencyCode );

bool ShouldUseNewStore();
int GetStoreVersion();
#endif // CLIENT_DLL

const CEconStorePriceSheet *GetEconPriceSheet();

#endif // ECON_STORE_H