summaryrefslogtreecommitdiff
path: root/game/shared/econ/econ_item_system.cpp
blob: 29026b99d50de7118da08e50264940eb0293c1e8 (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
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================

#include "cbase.h"
#include "tier1/KeyValues.h"
#include "econ_gcmessages.h"
#include "econ_item_system.h"
#include "econ_item_inventory.h"
#include "game_item_schema.h"
#include "gc_clientsystem.h"

#include "utldict.h"
#include "filesystem.h"
#include "steam/isteamhttp.h"


#if defined(CLIENT_DLL) || defined(GAME_DLL)
#include "gamestringpool.h"
#include "ihasattributes.h"
#include "tier0/icommandline.h"
#endif

#if defined(CLIENT_DLL)
#include "igameevents.h"
#endif

// FIXME FIXME FIXME
#if defined(TF_DLL) || defined(TF_CLIENT_DLL)
	#include "tf_item_system.h"
#endif // defined(TF_DLL) || defined(TF_CLIENT_DLL)

#if defined (DOTA_CLIENT_DLL) || defined (DOTA_DLL)
	#include "econ/dota_item_system.h"
#endif // defined (DOTA_CLIENT_DLL) || defined (DOTA_DLL)

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

#if ( defined( GAME_DLL ) || defined( CLIENT_DLL ) ) && ( defined( _DEBUG ) || defined( STAGING_ONLY ) )
ConVar item_debug( "item_debug", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
ConVar items_game_use_gc_copy( "items_game_use_gc_copy", "1", FCVAR_CHEAT | FCVAR_REPLICATED | FCVAR_ARCHIVE, "If set, items_game.txt will be stomped by the GC." );
ConVar item_debug_validation( "item_debug_validation", "1", FCVAR_REPLICATED | FCVAR_ARCHIVE, "If set, CEconEntity::ValidateEntityAttachedToPlayer behaves as it would in release builds and also allows bot players to take the same code path as real players." );
#endif

static ConVar item_quality_chance_unique( "item_quality_chance_unique", "0.1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Percentage chance that a random item is unique." );
static ConVar item_quality_chance_rare( "item_quality_chance_rare", "0.5", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Percentage chance that a random item is a rare." );
static ConVar item_quality_chance_common( "item_quality_chance_common", "1.0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Percentage chance that a random item is common." );


//-----------------------------------------------------------------------------
// Purpose: Get at the global item system
//-----------------------------------------------------------------------------
CEconItemSystem *ItemSystem( void )
{
	static GameItemSystem_t *pSystem = NULL;
	if ( !pSystem )
	{
		pSystem = new GameItemSystem_t();
	}

	return pSystem;
}


//-----------------------------------------------------------------------------
// Purpose: Global schema access, declared in game_item_schema.h
//-----------------------------------------------------------------------------
GameItemSchema_t *GetItemSchema()
{
	return ItemSystem()->GetItemSchema();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CEconItemSystem::CEconItemSystem( void )
{
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CEconItemSystem::~CEconItemSystem( void )
{
	Shutdown();
}


//-----------------------------------------------------------------------------
// Purpose: Parse in our data files.
//-----------------------------------------------------------------------------
void CEconItemSystem::Init( void )
{
#if defined(USES_ECON_ITEMS)
	ParseItemSchemaFile( "scripts/items/items_game.txt" );
#endif

#ifdef CLIENT_DLL
	IGameEvent *event = gameeventmanager->CreateEvent( "item_schema_initialized" );
	if ( event )
	{
		gameeventmanager->FireEventClientSide( event );
	}
#endif
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CEconItemSystem::Shutdown( void )
{
}

extern ConVar mp_tournament;

#ifdef GAME_DLL
ConVar mp_tournament_whitelist( "mp_tournament_whitelist", "item_whitelist.txt", FCVAR_NONE, "Specifies the item whitelist file to use." );
#endif

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CEconItemSystem::ReloadWhitelist( void )
{
	// Default state of items depends on whether we're in tourney mode, and whether there's a whitelist
	bool bDefault = true;
	bool bFoundWhitelist = false;

	KeyValues *pWhitelistKV = new KeyValues( "item_whitelist" );

#ifdef GAME_DLL
	if ( mp_tournament.GetBool() && mp_tournament_whitelist.GetString() )
	{
		const char *pszWhitelistFile = mp_tournament_whitelist.GetString();
		if ( pWhitelistKV->LoadFromFile( filesystem, pszWhitelistFile ) )
		{
			// Allow the whitelist to override the default, so they can turn it into a blacklist if they want to
			bDefault = pWhitelistKV->GetBool( "unlisted_items_default_to" );
			bFoundWhitelist = true;
		}
		else if ( pszWhitelistFile && pszWhitelistFile[0] )
		{
			Msg("Item Whitelist file '%s' could not be found. All items will be allowed.\n", pszWhitelistFile );
		}
	}
#endif

	const CEconItemSchema::ItemDefinitionMap_t& mapItemDefs = m_itemSchema.GetItemDefinitionMap();
	FOR_EACH_MAP_FAST( mapItemDefs, i )
	{
		mapItemDefs[i]->SetAllowedInMatch( bDefault );
	}

	// If we didn't find a file, we're done.
	if ( !bFoundWhitelist )
		return;

	// Otherwise, go through the KVs and turn on the matching items.
	Msg("Parsing item whitelist (default: %s)\n", bDefault ? "allowed" : "disallowed" );
	pWhitelistKV = pWhitelistKV->GetFirstSubKey();
	while ( pWhitelistKV )
	{
		bool bAllow = pWhitelistKV->GetBool();

		const char *pszItemName = pWhitelistKV->GetName();
		if ( pszItemName && pszItemName[0] && !FStrEq("unlisted_items_default_to", pszItemName) )
		{
			CEconItemDefinition *pItemDef = m_itemSchema.GetItemDefinitionByName( pszItemName );
			if ( pItemDef )
			{
				pItemDef->SetAllowedInMatch( bAllow );
				Msg(" -> %s '%s'\n", bAllow ? "Allowing" : "Removing", pszItemName );
			}
			else
			{
				Warning(" -> Could not find an item definition named '%s'\n", pszItemName );
			}
		}

		pWhitelistKV = pWhitelistKV->GetNextKey();
	}
	Msg("Finished.\n");
}

#ifdef GAME_DLL
CON_COMMAND_F( item_show_whitelistable_definitions, "Lists the item definitions that can be whitelisted in the item_whitelist.txt file in tournament mode.", FCVAR_CHEAT )
{
	Msg("Available item definitions for whitelisting:\n");
	const CEconItemSchema::SortedItemDefinitionMap_t& mapItemDefs = ItemSystem()->GetItemSchema()->GetSortedItemDefinitionMap();
	FOR_EACH_MAP( mapItemDefs, i )
	{
		const CEconItemDefinition *pItemDef = mapItemDefs[i];
		if ( pItemDef && pItemDef->GetQuality() != AE_NORMAL && !pItemDef->IsHidden() )
		{
			Msg("   '%s'\n", pItemDef->GetDefinitionName() );
		}
	}
}
#endif // GAME_DLL

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CEconItemSystem::ResetAttribStringCache( void )
{
	const CUtlMap<int, CEconItemAttributeDefinition, int> &mapDefs = m_itemSchema.GetAttributeDefinitionMap();
	FOR_EACH_MAP_FAST( mapDefs, i )
	{
		mapDefs[i].ClearStringCache();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CEconItemSystem::DecryptItemFiles( KeyValues *pKV, const char *pName )
{
	char szFullName[512];
	Q_snprintf(szFullName,sizeof(szFullName), "%s.ctx", pName );

	FileHandle_t f = filesystem->Open( szFullName, "rb", "MOD" );

	if (!f)
	{
#if !defined(CSTRIKE_DLL)
		Warning("No %s file found. May be unable to create items.\n", pName );
#endif // CSTRIKE_DLL
		return false;
	}

	int fileSize = filesystem->Size(f);
	char *buffer = (char*)MemAllocScratch(fileSize + 1);

	Assert(buffer);

	filesystem->Read(buffer, fileSize, f); // read into local buffer
	buffer[fileSize] = 0; // null terminate file as EOF
	filesystem->Close( f );	// close file after reading

	UTIL_DecodeICE( (unsigned char*)buffer, fileSize, GetEncryptionKey() );

	bool retOK = pKV->LoadFromBuffer( szFullName, buffer, filesystem );

	MemFreeScratch();

	if ( !retOK )
		return false;

	return true;
}


//-----------------------------------------------------------------------------
// Purpose: Read the specified item schema file. Init the item schema with the contents
//-----------------------------------------------------------------------------
void CEconItemSystem::ParseItemSchemaFile( const char *pFilename )
{
	CUtlVector< CUtlString > vecErrors;
	bool bSuccess = m_itemSchema.BInit( pFilename, "MOD", &vecErrors );

	if( !bSuccess )
	{
		FOR_EACH_VEC( vecErrors, nError )
		{
			// we want this to be an Error because several
			// places rely on loading a valid item schema 
			Error( "%s\n", vecErrors[nError].String() );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Generate a random item matching the specified criteria
//-----------------------------------------------------------------------------
item_definition_index_t CEconItemSystem::GenerateRandomItem( CItemSelectionCriteria *pCriteria, entityquality_t *outEntityQuality )
{
	// First, pick a random item quality (use the one passed in first)
	if ( !pCriteria->BQualitySet() )
	{
		pCriteria->SetQuality( GetRandomQualityForItem() );
	}

	pCriteria->SetIgnoreEnabledFlag( true );

	// Determine which item templates match the criteria
	CUtlVector<item_definition_index_t> vecMatches;
	const CEconItemSchema::ItemDefinitionMap_t &mapDefs = m_itemSchema.GetItemDefinitionMap();

HackMakeValidList:
	FOR_EACH_MAP_FAST( mapDefs, i )
	{
		if ( pCriteria->BEvaluate( mapDefs[i] ) )
		{
			vecMatches.AddToTail( mapDefs.Key( i ) );
		}
	}

	// No valid items?
	int iValidItems = vecMatches.Count();
	if ( !iValidItems )
	{
		// If we were searching for a unique item, drop back to a non-unique
		if ( pCriteria->GetQuality() == AE_UNIQUE )
		{
			pCriteria->SetQuality( GetRandomQualityForItem( true ) );
			goto HackMakeValidList;
		}
		return INVALID_ITEM_DEF_INDEX;
	}

	// Choose a random match
	int iChosenIdx = RandomInt( 0, (iValidItems-1) );
	item_definition_index_t iChosenItem = vecMatches[iChosenIdx];

	const CEconItemDefinition *pItemDef = m_itemSchema.GetItemDefinition( iChosenItem );
	if ( !pItemDef )
		return INVALID_ITEM_DEF_INDEX;

	// If we haven't specified an entity quality, we want to use the item's specified one
	if ( pCriteria->GetQuality() == AE_USE_SCRIPT_VALUE )
	{
		int32 iScriptQuality = pItemDef->GetQuality();
		pCriteria->SetQuality( iScriptQuality == AE_UNDEFINED ? GetRandomQualityForItem( true ) : iScriptQuality );
	}

	// If we haven't specified an item level, we want to use the item's specified one.
	if ( !pCriteria->BItemLevelSet() )
	{
		pCriteria->SetItemLevel( RandomInt( pItemDef->GetMinLevel(), pItemDef->GetMaxLevel() ) );
	}

	if ( outEntityQuality )
	{
		*outEntityQuality = pCriteria->GetQuality();
	}
	return iChosenItem;
}

//-----------------------------------------------------------------------------
// Purpose: Return a random quality for the item specified
//-----------------------------------------------------------------------------
entityquality_t CEconItemSystem::GetRandomQualityForItem( bool bPreventUnique )
{
	// Start on the rarest, and work backwards
	if ( !bPreventUnique )
	{
		if ( RandomFloat(0,1) < item_quality_chance_unique.GetFloat() )
			return AE_UNIQUE;
	}

	if ( RandomFloat(0,1) < item_quality_chance_rare.GetFloat() )
		return AE_RARITY2;

	if ( RandomFloat(0,1) < item_quality_chance_common.GetFloat() )
		return AE_RARITY1;

	return AE_NORMAL;
}

static ISteamHTTP *GetISteamHTTP()
{
	if ( steamapicontext != NULL && steamapicontext->SteamHTTP() )
	{
		return steamapicontext->SteamHTTP();
	}
	#ifndef CLIENT_DLL
		if ( steamgameserverapicontext != NULL )
		{
			return steamgameserverapicontext->SteamHTTP();
		}
	#endif
	return NULL;
}

//-----------------------------------------------------------------------------
// Purpose: Common functionality for using our raw buffer data to initialize
// the schema when safe.
//-----------------------------------------------------------------------------
bool IDelayedSchemaData::InitializeSchemaInternal( CEconItemSchema *pItemSchema, CUtlBuffer& bufRawData, bool bInitAsBinary, uint32 nExpectedVersion )
{
	Msg( "Applying new item schema, version %08X\n", nExpectedVersion );

	CUtlVector<CUtlString> vecErrors;
	bool bSuccess = bInitAsBinary
				  ? pItemSchema->BInitBinaryBuffer( bufRawData, &vecErrors )
				  : pItemSchema->BInitTextBuffer( bufRawData, &vecErrors );
	if( bSuccess )
	{
		// Sanity-check that we received the version that they sent us
		uint32 nOurVersion = pItemSchema->GetVersion();
		if ( nExpectedVersion != 0 && nOurVersion != nExpectedVersion )
		{
			Warning( "**WARNING** Item schema mismatch after update!\n" );
			Warning( "GC told us to expect %08X, we got %08X\n", nExpectedVersion, nOurVersion );
		}
	}
	else
	{
		Warning( "**WARNING** Failed to apply item schema!\n" );
		FOR_EACH_VEC( vecErrors, nError )
		{
			Warning( "%s\n", vecErrors[nError].Get() );
		}
	}
	return bSuccess;
}

//-----------------------------------------------------------------------------
// Purpose: The GC sent us a single block of binary data.
//-----------------------------------------------------------------------------
class DelayedSchemaData_GCDirectData : public IDelayedSchemaData
{
public:
	DelayedSchemaData_GCDirectData( const std::string& strBuffer )
		: m_bufRawData( strBuffer.data(), strBuffer.size(), CUtlBuffer::READ_ONLY )
	{
		//
	}

	virtual bool InitializeSchema( CEconItemSchema *pItemSchema )
	{
		return InitializeSchemaInternal( pItemSchema, m_bufRawData, true, 0 );
	}

private:
	CUtlBuffer m_bufRawData;
};

extern bool CheckValveSignature( const void *data, uint32 nDataSize, const void *signature, uint32 nSignatureSize );

//-----------------------------------------------------------------------------
// Purpose: We received a text file from an HTML request.
//-----------------------------------------------------------------------------
class DelayedSchemaData_HTTPResponseData : public IDelayedSchemaData
{
public:
	DelayedSchemaData_HTTPResponseData( ISteamHTTP *pHTTP, HTTPRequestHandle handleHTTPRequest, uint32 unBodySize, uint32 nExpectedVersion, const std::string &sSignature )
		: m_nExpectedVersion( nExpectedVersion )
	{
		Assert( pHTTP );

		m_bufRawData.SetBufferType( true, true );
		m_bufRawData.SeekPut( CUtlBuffer::SEEK_HEAD, unBodySize );

		m_bValid = pHTTP->GetHTTPResponseBodyData( handleHTTPRequest, (uint8*)m_bufRawData.Base(), m_bufRawData.TellPut() );
		if ( m_bValid )
			m_bValid = CheckValveSignature( m_bufRawData.Base(), m_bufRawData.TellPut(), sSignature.c_str(), sSignature.length() );
	}

	virtual bool InitializeSchema( CEconItemSchema *pItemSchema )
	{
		if ( !m_bValid )
			return false;

		return InitializeSchemaInternal( pItemSchema, m_bufRawData, false, m_nExpectedVersion );
	}

private:
	bool m_bValid;
	CUtlBuffer m_bufRawData;
	uint32 m_nExpectedVersion;
};

#define GC_ITEM_SCHEMA_UPDATE_APPLIED "Applied updated item schema from GC. %d bytes, version %08X.\n"
#define GC_ITEM_SCHEMA_UPDATE_QUEUED "Received %d bytes item schema version %08X direct data; update is queued.\n"

//-----------------------------------------------------------------------------
// Purpose: Update the item schema from the GC
//-----------------------------------------------------------------------------
class CGCUpdateItemSchema : public GCSDK::CGCClientJob
{
public:
	CGCUpdateItemSchema( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {
		m_szUrl[0] = '\0';
		m_nExpectedVersion = 0;
		bHTTPCompleted = false;
	}

	char m_szUrl[512];
	uint32 m_nExpectedVersion;
	bool bHTTPCompleted;
	CCallResult< CGCUpdateItemSchema, HTTPRequestCompleted_t > callback;
	std::string m_sSignature;

	virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
	{
		GCSDK::CProtoBufMsg< CMsgUpdateItemSchema > msg( pNetPacket );

#if ( defined( GAME_DLL ) || defined( CLIENT_DLL ) ) && ( defined( _DEBUG ) || defined( STAGING_ONLY ) )
		const bool bUseGCCopy = items_game_use_gc_copy.GetBool();
#else
		const bool bUseGCCopy = true;
#endif

		if ( bUseGCCopy == false && k_EUniversePublic != GetUniverse() )
		{
			Msg( "Loading item schema from local file.\n" );
			KeyValuesAD pItemsGameKV( "ItemsGameFile" );
			if ( pItemsGameKV->LoadFromFile( g_pFullFileSystem, "scripts/items/items_game.txt", "GAME" ) )
			{
				CUtlBuffer buffer;
				pItemsGameKV->WriteAsBinary( buffer );

				CUtlVector< CUtlString > vecErrors;
				bool bSuccess = ItemSystem()->GetItemSchema()->BInitBinaryBuffer( buffer, &vecErrors );
				if( !bSuccess )
				{
					FOR_EACH_VEC( vecErrors, nError )
					{
						Warning( "%s\n", vecErrors[nError].Get() );
					}
				}
			}

			return true;
		}

		// Check if we're already up-to-date
		m_nExpectedVersion = msg.Body().item_schema_version();
		uint32 nCurrentSchemaVersion = ItemSystem()->GetItemSchema()->GetVersion();
		if ( m_nExpectedVersion != 0 && m_nExpectedVersion == nCurrentSchemaVersion )
		{
			Msg( "Current item schema is up-to-date with version %08X.\n", nCurrentSchemaVersion );
			return true;
		}

		m_sSignature = msg.Body().signature();

		// !TEST!
		//const char *szURL = "http://cdn.beta.steampowered.com/apps/440/scripts/items/items_game.b8b7a85b4dd98b139957004b86ec0bc070a59d18.txt";
		if ( msg.Body().has_items_game() )
		{
			bool bDidInit = ItemSystem()->GetItemSchema()->MaybeInitFromBuffer( new DelayedSchemaData_GCDirectData( msg.Body().items_game() ) );
			Msg( bDidInit ? GC_ITEM_SCHEMA_UPDATE_APPLIED : GC_ITEM_SCHEMA_UPDATE_QUEUED, (int)msg.Body().items_game().size(), m_nExpectedVersion );
		}
		else
		{
			// Remember URL
			const char *szURL = msg.Body().items_game_url().c_str();
			if ( !szURL || !szURL[0] )
			{
				Warning( "GC sent malformed CGCUpdateItemSchema message: No schema data, no URL\n" );
			}
			else
			{
				Q_strncpy( m_szUrl, szURL, sizeof( m_szUrl ) );
				//Msg( "Fetching %s to update item schema\n", m_szUrl );

				// Send an HTTP request for the file
				ISteamHTTP *pHTTP = GetISteamHTTP();
				if ( !pHTTP )
				{
					//Warning( "Can't get ISteamHTTP to update item schema\n");
					return true;
				}
				HTTPRequestHandle hReq = pHTTP->CreateHTTPRequest( k_EHTTPMethodGET, m_szUrl );
				pHTTP->SetHTTPRequestNetworkActivityTimeout( hReq, 10 );
				SteamAPICall_t hCall;
				if ( !pHTTP->SendHTTPRequest( hReq, &hCall ) )
				{
					Warning( "Failed to update item schema: couldn't fetch %s\n", m_szUrl );
					return true;
				}

				//
				// *Wait* for completion.
				//
				// This is important.  The GC needs to be able to safely assume that
				// we will not process the next message until we have finished
				// dealing with this one.
				//
				bHTTPCompleted = false;
				#ifndef CLIENT_DLL
					if ( steamgameserverapicontext != NULL && pHTTP == steamgameserverapicontext->SteamHTTP() )
					{
						callback.SetGameserverFlag();
					}
				#endif
				callback.Set( hCall, this, &CGCUpdateItemSchema::OnHTTPCompleted );

				// Wait for it to finish.
				while ( !bHTTPCompleted )
				{
					BYieldingWaitOneFrame();
				}
			}
		}

		return true;
	}

	void OnHTTPCompleted( HTTPRequestCompleted_t *arg, bool bFailed )
	{
		// Clear flag, no matter what else, so we can stop yielding
		bHTTPCompleted = true;

		ISteamHTTP *pHTTP = GetISteamHTTP();
		Assert( pHTTP );
		if ( !pHTTP ) return;

		if ( arg->m_eStatusCode != k_EHTTPStatusCode200OK )
		{
			Warning( "Failed to update item schema: HTTP status %d fetching %s\n", arg->m_eStatusCode, m_szUrl );
		}
		else
		{
			if ( !arg->m_bRequestSuccessful )
			{
				bFailed = true;
			}
			if ( !bFailed )
			{
				uint32 unBodySize;
				if ( !pHTTP->GetHTTPResponseBodySize( arg->m_hRequest, &unBodySize ) )
				{
					Assert( false );
					bFailed = true;
				}
				else
				{
					bool bDidInit = ItemSystem()->GetItemSchema()->MaybeInitFromBuffer( new DelayedSchemaData_HTTPResponseData( pHTTP, arg->m_hRequest, unBodySize, m_nExpectedVersion, m_sSignature ) );
					Msg( bDidInit ? GC_ITEM_SCHEMA_UPDATE_APPLIED : GC_ITEM_SCHEMA_UPDATE_QUEUED, unBodySize, m_nExpectedVersion );
				}
			}

			if ( bFailed )
			{
				Warning( "Failed to update item schema from %s\n", m_szUrl );
			}
		}

		pHTTP->ReleaseHTTPRequest( arg->m_hRequest );
	}
};
GC_REG_JOB( GCSDK::CGCClient, CGCUpdateItemSchema, "CGCUpdateItemSchema", k_EMsgGCUpdateItemSchema, GCSDK::k_EServerTypeGCClient );

#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose: Update the item schema from the GC
//-----------------------------------------------------------------------------
CON_COMMAND_F( econ_show_items_with_tag, "Lists the item definitions that have a specified tag.", FCVAR_CLIENTDLL )
{
	if ( args.ArgC() != 2 )
		return;

	econ_tag_handle_t tagHandle = GetItemSchema()->GetHandleForTag( args.Arg( 1 ) );
	FOR_EACH_MAP( GetItemSchema()->GetSortedItemDefinitionMap(), i )
	{
		const CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinitionMap()[i];

		if ( pItemDef->HasEconTag( tagHandle ) )
		{
			Msg("   '%s'\n", pItemDef->GetDefinitionName() );
		}
	}
}
#endif // CLIENT_DLL

#ifdef STAGING_ONLY
//-----------------------------------------------------------------------------
// Purpose: Update the item schema from the GC
//-----------------------------------------------------------------------------
#ifdef CLIENT_DLL
CON_COMMAND_F( cl_reload_local_item_schema, "Reloads the local item schema copy.", FCVAR_CLIENTDLL )
#else
CON_COMMAND_F( sv_reload_local_item_schema, "Reloads the local item schema copy.", FCVAR_GAMEDLL )
#endif
{
#ifdef CLIENT_DLL
	engine->ClientCmd_Unrestricted( "cmd sv_reload_local_item_schema" );
#endif

	Msg( "Loading item schema from local file.\n" );
	KeyValuesAD pItemsGameKV( "ItemsGameFile" );
	if ( pItemsGameKV->LoadFromFile( g_pFullFileSystem, "scripts/items/items_game.txt", "GAME" ) )
	{
		CUtlBuffer buffer;
		pItemsGameKV->WriteAsBinary( buffer );

		CUtlVector< CUtlString > vecErrors;
		bool bSuccess = ItemSystem()->GetItemSchema()->BInitBinaryBuffer( buffer, &vecErrors );
		if( !bSuccess )
		{
			FOR_EACH_VEC( vecErrors, nError )
			{
				Warning( "%s\n", vecErrors[nError].Get() );
			}
		}
	}
}
#endif